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 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
8 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
9 
10 
11 // on Mac with X11: -L-L/usr/X11/lib 
12 
13 /+
14 
15 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
16 
17 	Progress bar in taskbar
18 		- i can probably just set a property on the window...
19 		  it sets that prop to an integer 0 .. 100. Taskbar
20 		  deletes it or window deletes it when it is handled.
21 		- prolly display it as a nice little line at the bottom.
22 
23 
24 from gtk:
25 
26 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
27 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
28 
29 >+  if (cardinal > 0)
30 >+  {
31 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
32 >+                     xid,
33 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
34 >+                     XA_CARDINAL, 32,
35 >+                     PropModeReplace,
36 >+                     (guchar *) &cardinal, 1);
37 >+  }
38 >+  else
39 >+  {
40 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
41 >+                     xid,
42 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
43 >+  }
44 
45 from Windows:
46 
47 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
48 
49 interface
50 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 
51 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 
52 listen for msg, return TRUE
53 interface->SetProgressState(hwnd, TBPF_NORMAL); 
54 interface->SetProgressValue(hwnd, 40, 100); 
55 
56 
57 	My new notification system.
58 		- use a unix socket? or a x property? or a udp port?
59 		- could of course also get on the dbus train but ugh.
60 		- it could also reply with the info as a string for easy remote examination.
61 
62 +/
63 
64 /*
65 	Event Loop would be nices:
66 
67 	* add on idle - runs when nothing else happens
68 		* which can specify how long to yield for
69 	* send messages without a recipient window
70 	* setTimeout
71 	* setInterval
72 */
73 
74 /*
75 	Classic games I want to add:
76 		* my tetris clone
77 		* pac man
78 */
79 
80 /*
81 	Text layout needs a lot of work. Plain drawText is useful but too
82 	limited. It will need some kind of text context thing which it will
83 	update and you can pass it on and get more details out of it.
84 
85 	It will need a bounding box, a current cursor location that is updated
86 	as drawing continues, and various changable facts (which can also be
87 	changed on the painter i guess) like font, color, size, background,
88 	etc.
89 
90 	We can also fetch the caret location from it somehow.
91 
92 	Should prolly be an overload of drawText
93 
94 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
95 
96 		WS_EX_NOACTIVATE
97 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
98 		full screen windows. Can just set the atom on X. Windows will be harder.
99 
100 		moving windows. resizing windows.
101 
102 		hide cursor, capture cursor, change cursor.
103 
104 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
105 	sure the pieces are there to do its job easily and make other jobs possible.
106 */
107 
108 /++
109 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
110 	including creating windows, drawing on them, working with the clipboard,
111 	timers, OpenGL, and more. However, it does NOT provide high level GUI
112 	widgets. See my minigui.d, an extension to this module, for that
113 	functionality.
114 
115 	simpledisplay provides cross-platform wrapping for Windows and Linux
116 	(and perhaps other OSes that use X11), but also does not prevent you
117 	from using the underlying facilities if you need them. It has a goal
118 	of working efficiently over a remote X link (at least as far as Xlib
119 	reasonably allows.)
120 
121 	simpledisplay depends on [arsd.color|color.d], which should be available from the
122 	same place where you got this file. Other than that, however, it has
123 	very few dependencies and ones that don't come with the OS and/or the
124 	compiler are all opt-in.
125 
126 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
127 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
128 
129 	simpledisplay is basically stable. I plan to refactor the internals,
130 	and may add new features and fix bugs, but It do not expect to
131 	significantly change the API. It has been stable a few years already now.
132 
133 	Installation_instructions:
134 
135 	`simpledisplay.d` does not have any dependencies outside the
136 	operating system and `color.d`, so it should just work most the
137 	time, but there are a few caveats on some systems:
138 
139 	Please note when compiling on Win64, you need to explicitly list
140 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
141 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
142 
143 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
144 	note the "w".
145 
146 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
147 	console to be automatically allocated.
148 
149 	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.
150 
151 	On Ubuntu, you might need to install X11 development libraries to
152 	successfully link.
153 
154 	$(CONSOLE
155 		$ sudo apt-get install libglc-dev
156 		$ sudo apt-get install libx11-dev
157 	)
158 
159 
160 	Jump_list:
161 
162 	Don't worry, you don't have to read this whole documentation file!
163 
164 	Check out the [#event-example] and [#Pong-example] to get started quickly.
165 
166 	The main classes you may want to create are [SimpleWindow], [Timer],
167 	[Image], and [Sprite].
168 
169 	The main functions you'll want are [setClipboardText] and [getClipboardText].
170 
171 	There are also platform-specific functions available such as [XDisplayConnection]
172 	and [GetAtom] for X11, among others.
173 
174 	See the examples and topics list below to learn more.
175 
176 	$(WARNING
177 		There should only be one GUI thread per application,
178 		and all windows should be created in it and your
179 		event loop should run there.
180 
181 		To do otherwise is undefined behavior and has no
182 		cross platform guarantees.
183 	)
184 
185 	$(H2 About this documentation)
186 
187 	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.
188 
189 	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!
190 
191 	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.
192 
193 	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.
194 
195 	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.
196 
197 	At points, I will talk about implementation details in the documentation. These are sometimes
198 	subject to change, but nevertheless useful to understand what is really going on. You can learn
199 	more about some of the referenced things by searching the web for info about using them from C.
200 	You can always look at the source of simpledisplay.d too for the most authoritative source on
201 	its specific implementation. If you disagree with how I did something, please contact me so we
202 	can discuss it!
203 
204 	$(H2 Using with fibers)
205 
206 	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).
207 
208 	$(H2 Topics)
209 
210 	$(H3 $(ID topic-windows) Windows)
211 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
212 		window on the user's screen.
213 
214 		You may create multiple windows, if the underlying platform supports it. You may check
215 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
216 		SimpleWindow's constructor at runtime to handle those cases.
217 
218 		A single running event loop will handle as many windows as needed.
219 
220 		setEventHandlers function
221 		eventLoop function
222 		draw function
223 		title property
224 
225 	$(H3 $(ID topic-event-loops) Event loops)
226 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
227 
228 		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:
229 
230 		---
231 		// dmd example.d simpledisplay.d color.d
232 		import arsd.simpledisplay;
233 		void main() {
234 			auto window = new SimpleWindow(200, 200);
235 			window.eventLoop(0,
236 			  delegate (dchar) { /* got a character key press */ }
237 			);
238 		}
239 		---
240 
241 		$(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.)
242 
243 		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.
244 
245 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
246 
247 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
248 
249 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatic terminate when there's no open windows, you will want to have one anyway.
250 
251 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
252 		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.
253 
254 		See the [NotificationAreaIcon] class.
255 
256 	$(H3 $(ID topic-input-handling) Input handling)
257 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
258 
259 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
260 
261 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
262 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
263 
264 		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:
265 
266 		---
267 		// dmd example.d simpledisplay.d color.d
268 		import arsd.simpledisplay;
269 		void main() {
270 			auto window = new SimpleWindow(200, 200);
271 			{ // introduce sub-scope
272 				auto painter = window.draw(); // begin drawing
273 				/* draw here */
274 				painter.outlineColor = Color.red;
275 				painter.fillColor = Color.black;
276 				painter.drawRectangle(Point(0, 0), 200, 200);
277 			} // end scope, calling `painter`'s destructor, drawing to the screen.
278 			window.eventLoop(0); // handle events
279 		}
280 		---
281 
282 		Painting is done based on two color properties, a pen and a brush.
283 
284 		At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
285 
286 		FIXME add example of 2d opengl drawing here
287 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
288 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
289 
290 		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.
291 
292 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
293 
294 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
295 
296 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
297 
298 		simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
299 
300 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
301 
302 		---
303 		import arsd.simpledisplay;
304 
305 		void main() {
306 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
307 
308 			float otherColor = 0.0;
309 			float colorDelta = 0.05;
310 
311 			window.redrawOpenGlScene = delegate() {
312 				glLoadIdentity();
313 				glBegin(GL_QUADS);
314 
315 				glColor3f(1.0, otherColor, 0);
316 				glVertex3f(-0.8, -0.8, 0);
317 
318 				glColor3f(1.0, otherColor, 1.0);
319 				glVertex3f(0.8, -0.8, 0);
320 
321 				glColor3f(0, 1.0, otherColor);
322 				glVertex3f(0.8, 0.8, 0);
323 
324 				glColor3f(otherColor, 0, 1.0);
325 				glVertex3f(-0.8, 0.8, 0);
326 
327 				glEnd();
328 			};
329 
330 			window.eventLoop(50, () {
331 				otherColor += colorDelta;
332 				if(otherColor > 1.0) {
333 					otherColor = 1.0;
334 					colorDelta = -0.05;
335 				}
336 				if(otherColor < 0) {
337 					otherColor = 0;
338 					colorDelta = 0.05;
339 				}
340 				// at the end of the timer, we have to request a redraw
341 				// or we won't see the changes.
342 				window.redrawOpenGlSceneSoon();
343 			});
344 		}
345 		---
346 
347 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
348 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
349 		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.
350 
351 		This example program shows how you can set up a shader to draw a rectangle:
352 
353 		---
354 		module opengl3test;
355 		import arsd.simpledisplay;
356 
357 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
358 
359 		void main() {
360 			// First thing we do, before creating the window, is declare what version we want.
361 			setOpenGLContextVersion(3, 3);
362 			// turning off legacy compat is required to use version 3.3 and newer
363 			openGLContextCompatible = false;
364 
365 			uint VAO;
366 			OpenGlShader shader;
367 
368 			// then we can create the window.
369 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
370 
371 			// additional setup needs to be done when it is visible, simpledisplay offers a property
372 			// for exactly that:
373 			window.visibleForTheFirstTime = delegate() {
374 				// now with the window loaded, we can start loading the modern opengl functions.
375 
376 				// you MUST set the context first.
377 				window.setAsCurrentOpenGlContext;
378 				// then load the remainder of the library
379 				gl3.loadDynamicLibrary();
380 
381 				// now you can create the shaders, etc.
382 				shader = new OpenGlShader(
383 					OpenGlShader.Source(GL_VERTEX_SHADER, `
384 						#version 330 core
385 						layout (location = 0) in vec3 aPos;
386 						void main() {
387 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
388 						}
389 					`),
390 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
391 						#version 330 core
392 						out vec4 FragColor;
393 						uniform vec4 mycolor;
394 						void main() {
395 							FragColor = mycolor;
396 						}
397 					`),
398 				);
399 
400 				// and do whatever other setup you want.
401 
402 				float[] vertices = [
403 					0.5f,  0.5f, 0.0f,  // top right
404 					0.5f, -0.5f, 0.0f,  // bottom right
405 					-0.5f, -0.5f, 0.0f,  // bottom left
406 					-0.5f,  0.5f, 0.0f   // top left 
407 				];
408 				uint[] indices = [  // note that we start from 0!
409 					0, 1, 3,  // first Triangle
410 					1, 2, 3   // second Triangle
411 				];
412 				uint VBO, EBO;
413 				glGenVertexArrays(1, &VAO);
414 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
415 				glBindVertexArray(VAO);
416 
417 				glGenBuffers(1, &VBO);
418 				glGenBuffers(1, &EBO);
419 
420 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
421 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
422 
423 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
424 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
425 
426 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
427 				glEnableVertexAttribArray(0);
428 
429 				// the library will set the initial viewport and trigger our first draw,
430 				// so these next two lines are NOT needed. they are just here as comments
431 				// to show what would happen next.
432 
433 				// glViewport(0, 0, window.width, window.height);
434 				// window.redrawOpenGlSceneNow();
435 			};
436 
437 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
438 			// it is our render method.
439 			window.redrawOpenGlScene = delegate() {
440 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
441 				glClear(GL_COLOR_BUFFER_BIT);
442 
443 				glUseProgram(shader.shaderProgram);
444 
445 				// the shader helper class has methods to set uniforms too
446 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
447 
448 				glBindVertexArray(VAO);
449 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
450 			};
451 
452 			window.eventLoop(0);
453 		}
454 		---
455 
456 	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.
457 
458 
459 	$(H3 $(ID topic-images) Displaying images)
460 		You can also load PNG images using [arsd.png].
461 
462 		---
463 		// dmd example.d simpledisplay.d color.d png.d
464 		import arsd.simpledisplay;
465 		import arsd.png;
466 
467 		void main() {
468 			auto image = Image.fromMemoryImage(readPng("image.png"));
469 			displayImage(image);
470 		}
471 		---
472 
473 		Compile with `dmd example.d simpledisplay.d png.d`.
474 
475 		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.
476 
477 	$(H3 $(ID topic-sprites) Sprites)
478 		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.
479 
480 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
481 
482 	$(H3 $(ID topic-clipboard) Clipboard)
483 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
484 
485 		It also has helpers for handling X-specific events.
486 
487 	$(H3 $(ID topic-dnd) Drag and Drop)
488 		See [enableDragAndDrop] and [draggable].
489 
490 	$(H3 $(ID topic-timers) Timers)
491 		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].
492 
493 		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.
494 
495 		---
496 			import arsd.simpledisplay;
497 
498 			void main() {
499 				auto window = new SimpleWindow(400, 400);
500 				// every 100 ms, it will draw a random line
501 				// on the window.
502 				window.eventLoop(100, {
503 					auto painter = window.draw();
504 
505 					import std.random;
506 					// random color
507 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
508 					// random line
509 					painter.drawLine(
510 						Point(uniform(0, window.width), uniform(0, window.height)),
511 						Point(uniform(0, window.width), uniform(0, window.height)));
512 
513 				});
514 			}
515 		---
516 
517 		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.
518 
519 		The pulse timer and instances of the [Timer] class may be combined at will.
520 
521 		---
522 			import arsd.simpledisplay;
523 
524 			void main() {
525 				auto window = new SimpleWindow(400, 400);
526 				auto timer = new Timer(1000, delegate {
527 					auto painter = window.draw();
528 					painter.clear();
529 				});
530 
531 				window.eventLoop(0);
532 			}
533 		---
534 
535 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
536 
537 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
538 		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.
539 
540 		See also: `xwindows.d` from my github.
541 
542 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
543 		`handleNativeEvent` and `handleNativeGlobalEvent`.
544 
545 	$(H3 $(ID topic-integration) Integration with other libraries)
546 		Integration with a third-party event loop is possible.
547 
548 		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.
549 
550 	$(H3 $(ID topic-guis) GUI widgets)
551 		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!
552 
553 		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.
554 
555 		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.)
556 
557 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
558 
559 	$(H2 Platform-specific tips and tricks)
560 
561 	Windows_tips:
562 
563 	You can add icons or manifest files to your exe using a resource file.
564 
565 	To create a Windows .ico file, use the gimp or something. I'll write a helper
566 	program later.
567 
568 	Create `yourapp.rc`:
569 
570 	```rc
571 		1 ICON filename.ico
572 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
573 	```
574 
575 	And `yourapp.exe.manifest`:
576 
577 	```xml
578 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
579 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
580 		<assemblyIdentity
581 		    version="1.0.0.0"
582 		    processorArchitecture="*"
583 		    name="CompanyName.ProductName.YourApplication"
584 		    type="win32"
585 		/>
586 		<description>Your application description here.</description>
587 		<dependency>
588 		    <dependentAssembly>
589 			<assemblyIdentity
590 			    type="win32"
591 			    name="Microsoft.Windows.Common-Controls"
592 			    version="6.0.0.0"
593 			    processorArchitecture="*"
594 			    publicKeyToken="6595b64144ccf1df"
595 			    language="*"
596 			/>
597 		    </dependentAssembly>
598 		</dependency>
599 		</assembly>
600 	```
601 
602 	$(H2 Tips)
603 
604 	$(H3 Name conflicts)
605 
606 	simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import:
607 
608 	---
609 	static import sdpy = arsd.simpledisplay;
610 	import arsd.simpledisplay : SimpleWindow;
611 
612 	void main() {
613 		auto window = new SimpleWindow();
614 		sdpy.EventLoop.get.run();
615 	}
616 	---
617 
618 	$(H2 $(ID developer-notes) Developer notes)
619 
620 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
621 	implementation though.
622 
623 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
624 	suck. If I was rewriting it, I wouldn't do it that way again.
625 
626 	This file must not have any more required dependencies. If you need bindings, add
627 	them right to this file. Once it gets into druntime and is there for a while, remove
628 	bindings from here to avoid conflicts (or put them in an appropriate version block
629 	so it continues to just work on old dmd), but wait a couple releases before making the
630 	transition so this module remains usable with older versions of dmd.
631 
632 	You may have optional dependencies if needed by putting them in version blocks or
633 	template functions. You may also extend the module with other modules with UFCS without
634 	actually editing this - that is nice to do if you can.
635 
636 	Try to make functions work the same way across operating systems. I typically make
637 	it thinly wrap Windows, then emulate that on Linux.
638 
639 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
640 	Phobos! So try to avoid it.
641 
642 	See more comments throughout the source.
643 
644 	I realize this file is fairly large, but over half that is just bindings at the bottom
645 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
646 	to understand. I suggest you jump around the source by looking for a particular
647 	declaration you're interested in, like `class SimpleWindow` using your editor's search
648 	function, then look at one piece at a time.
649 
650 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
651 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
652 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
653 
654 	I live in the eastern United States, so I will most likely not be around at night in
655 	that US east timezone.
656 
657 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
658 
659 	Building documentation: use my adrdox generator, `dub run adrdox`.
660 
661 	Examples:
662 
663 	$(DIV $(ID Event-example))
664 	$(H3 $(ID event-example) Event example)
665 	This program creates a window and draws events inside them as they
666 	happen, scrolling the text in the window as needed. Run this program
667 	and experiment to get a feel for where basic input events take place
668 	in the library.
669 
670 	---
671 	// dmd example.d simpledisplay.d color.d
672 	import arsd.simpledisplay;
673 	import std.conv;
674 
675 	void main() {
676 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
677 
678 		int y = 0;
679 
680 		void addLine(string text) {
681 			auto painter = window.draw();
682 
683 			if(y + painter.fontHeight >= window.height) {
684 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
685 				y -= painter.fontHeight;
686 			}
687 
688 			painter.outlineColor = Color.red;
689 			painter.fillColor = Color.black;
690 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
691 
692 			painter.outlineColor = Color.white;
693 
694 			painter.drawText(Point(10, y), text);
695 
696 			y += painter.fontHeight;
697 		}
698 
699 		window.eventLoop(1000,
700 		  () {
701 			addLine("Timer went off!");
702 		  },
703 		  (KeyEvent event) {
704 			addLine(to!string(event));
705 		  },
706 		  (MouseEvent event) {
707 			addLine(to!string(event));
708 		  },
709 		  (dchar ch) {
710 			addLine(to!string(ch));
711 		  }
712 		);
713 	}
714 	---
715 
716 	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.
717 
718 	$(COMMENT
719 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
720 
721 	---
722 
723 	---
724 	)
725 
726 
727 +/
728 module arsd.simpledisplay;
729 
730 // FIXME: tetris demo
731 // FIXME: space invaders demo
732 // FIXME: asteroids demo
733 
734 /++ $(ID Pong-example)
735 	$(H3 Pong)
736 
737 	This program creates a little Pong-like game. Player one is controlled
738 	with the keyboard.  Player two is controlled with the mouse. It demos
739 	the pulse timer, event handling, and some basic drawing.
740 +/
741 version(demos)
742 unittest {
743 	// dmd example.d simpledisplay.d color.d
744 	import arsd.simpledisplay;
745 
746 	enum paddleMovementSpeed = 8;
747 	enum paddleHeight = 48;
748 
749 	void main() {
750 		auto window = new SimpleWindow(600, 400, "Pong game!");
751 
752 		int playerOnePosition, playerTwoPosition;
753 		int playerOneMovement, playerTwoMovement;
754 		int playerOneScore, playerTwoScore;
755 
756 		int ballX, ballY;
757 		int ballDx, ballDy;
758 
759 		void serve() {
760 			import std.random;
761 
762 			ballX = window.width / 2;
763 			ballY = window.height / 2;
764 			ballDx = uniform(-4, 4) * 3;
765 			ballDy = uniform(-4, 4) * 3;
766 			if(ballDx == 0)
767 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
768 		}
769 
770 		serve();
771 
772 		window.eventLoop(50, // set a 50 ms timer pulls
773 			// This runs once per timer pulse
774 			delegate () {
775 				auto painter = window.draw();
776 
777 				painter.clear();
778 
779 				// Update everyone's motion
780 				playerOnePosition += playerOneMovement;
781 				playerTwoPosition += playerTwoMovement;
782 
783 				ballX += ballDx;
784 				ballY += ballDy;
785 
786 				// Bounce off the top and bottom edges of the window
787 				if(ballY + 7 >= window.height)
788 					ballDy = -ballDy;
789 				if(ballY - 8 <= 0)
790 					ballDy = -ballDy;
791 
792 				// Bounce off the paddle, if it is in position
793 				if(ballX - 8 <= 16) {
794 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
795 						ballDx = -ballDx + 1; // add some speed to keep it interesting
796 						ballDy += playerOneMovement; // and y movement based on your controls too
797 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
798 					} else {
799 						// Missed it
800 						playerTwoScore ++;
801 						serve();
802 					}
803 				}
804 
805 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
806 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
807 						ballDx = -ballDx - 1;
808 						ballDy += playerTwoMovement;
809 						ballX = window.width - 24;
810 					} else {
811 						// Missed it
812 						playerOneScore ++;
813 						serve();
814 					}
815 				}
816 
817 				// Draw the paddles
818 				painter.outlineColor = Color.black;
819 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
820 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
821 
822 				// Draw the ball
823 				painter.fillColor = Color.red;
824 				painter.outlineColor = Color.yellow;
825 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
826 
827 				// Draw the score
828 				painter.outlineColor = Color.blue;
829 				import std.conv;
830 				painter.drawText(Point(64, 4), to!string(playerOneScore));
831 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
832 
833 			},
834 			delegate (KeyEvent event) {
835 				// Player 1's controls are the arrow keys on the keyboard
836 				if(event.key == Key.Down)
837 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
838 				if(event.key == Key.Up)
839 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
840 
841 			},
842 			delegate (MouseEvent event) {
843 				// Player 2's controls are mouse movement while the left button is held down
844 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
845 					if(event.dy > 0)
846 						playerTwoMovement = paddleMovementSpeed;
847 					else if(event.dy < 0)
848 						playerTwoMovement = -paddleMovementSpeed;
849 				} else {
850 					playerTwoMovement = 0;
851 				}
852 			}
853 		);
854 	}
855 }
856 
857 /++ $(H3 $(ID example-minesweeper) Minesweeper)
858 
859 	This minesweeper demo shows how we can implement another classic
860 	game with simpledisplay and shows some mouse input and basic output
861 	code.
862 +/
863 version(demos)
864 unittest {
865 	import arsd.simpledisplay;
866 
867 	enum GameSquare {
868 		mine = 0,
869 		clear,
870 		m1, m2, m3, m4, m5, m6, m7, m8
871 	}
872 
873 	enum UserSquare {
874 		unknown,
875 		revealed,
876 		flagged,
877 		questioned
878 	}
879 
880 	enum GameState {
881 		inProgress,
882 		lose,
883 		win
884 	}
885 
886 	GameSquare[] board;
887 	UserSquare[] userState;
888 	GameState gameState;
889 	int boardWidth;
890 	int boardHeight;
891 
892 	bool isMine(int x, int y) {
893 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
894 			return false;
895 		return board[y * boardWidth + x] == GameSquare.mine;
896 	}
897 
898 	GameState reveal(int x, int y) {
899 		if(board[y * boardWidth + x] == GameSquare.clear) {
900 			floodFill(userState, boardWidth, boardHeight,
901 				UserSquare.unknown, UserSquare.revealed,
902 				x, y,
903 				(x, y) {
904 					if(board[y * boardWidth + x] == GameSquare.clear)
905 						return true;
906 					else {
907 						userState[y * boardWidth + x] = UserSquare.revealed;
908 						return false;
909 					}
910 				});
911 		} else {
912 			userState[y * boardWidth + x] = UserSquare.revealed;
913 			if(isMine(x, y))
914 				return GameState.lose;
915 		}
916 
917 		foreach(state; userState) {
918 			if(state == UserSquare.unknown || state == UserSquare.questioned)
919 				return GameState.inProgress;
920 		}
921 
922 		return GameState.win;
923 	}
924 
925 	void initializeBoard(int width, int height, int numberOfMines) {
926 		boardWidth = width;
927 		boardHeight = height;
928 		board.length = width * height;
929 
930 		userState.length = width * height;
931 		userState[] = UserSquare.unknown; 
932 
933 		import std.algorithm, std.random, std.range;
934 
935 		board[] = GameSquare.clear;
936 
937 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
938 			board[minePosition] = GameSquare.mine;
939 
940 		int x;
941 		int y;
942 		foreach(idx, ref square; board) {
943 			if(square == GameSquare.clear) {
944 				int danger = 0;
945 				danger += isMine(x-1, y-1)?1:0;
946 				danger += isMine(x-1, y)?1:0;
947 				danger += isMine(x-1, y+1)?1:0;
948 				danger += isMine(x, y-1)?1:0;
949 				danger += isMine(x, y+1)?1:0;
950 				danger += isMine(x+1, y-1)?1:0;
951 				danger += isMine(x+1, y)?1:0;
952 				danger += isMine(x+1, y+1)?1:0;
953 
954 				square = cast(GameSquare) (danger + 1);
955 			}
956 
957 			x++;
958 			if(x == width) {
959 				x = 0;
960 				y++;
961 			}
962 		}
963 	}
964 
965 	void redraw(SimpleWindow window) {
966 		import std.conv;
967 
968 		auto painter = window.draw();
969 
970 		painter.clear();
971 
972 		final switch(gameState) with(GameState) {
973 			case inProgress:
974 				break;
975 			case win:
976 				painter.fillColor = Color.green;
977 				painter.drawRectangle(Point(0, 0), window.width, window.height);
978 				return;
979 			case lose:
980 				painter.fillColor = Color.red;
981 				painter.drawRectangle(Point(0, 0), window.width, window.height);
982 				return;
983 		}
984 
985 		int x = 0;
986 		int y = 0;
987 
988 		foreach(idx, square; board) {
989 			auto state = userState[idx];
990 
991 			final switch(state) with(UserSquare) {
992 				case unknown:
993 					painter.outlineColor = Color.black;
994 					painter.fillColor = Color(128,128,128);
995 
996 					painter.drawRectangle(
997 						Point(x * 20, y * 20),
998 						20, 20
999 					);
1000 				break;
1001 				case revealed:
1002 					if(square == GameSquare.clear) {
1003 						painter.outlineColor = Color.white;
1004 						painter.fillColor = Color.white;
1005 
1006 						painter.drawRectangle(
1007 							Point(x * 20, y * 20),
1008 							20, 20
1009 						);
1010 					} else {
1011 						painter.outlineColor = Color.black;
1012 						painter.fillColor = Color.white;
1013 
1014 						painter.drawText(
1015 							Point(x * 20, y * 20),
1016 							to!string(square)[1..2],
1017 							Point(x * 20 + 20, y * 20 + 20),
1018 							TextAlignment.Center | TextAlignment.VerticalCenter);
1019 					}
1020 				break;
1021 				case flagged:
1022 					painter.outlineColor = Color.black;
1023 					painter.fillColor = Color.red;
1024 					painter.drawRectangle(
1025 						Point(x * 20, y * 20),
1026 						20, 20
1027 					);
1028 				break;
1029 				case questioned:
1030 					painter.outlineColor = Color.black;
1031 					painter.fillColor = Color.yellow;
1032 					painter.drawRectangle(
1033 						Point(x * 20, y * 20),
1034 						20, 20
1035 					);
1036 				break;
1037 			}
1038 
1039 			x++;
1040 			if(x == boardWidth) {
1041 				x = 0;
1042 				y++;
1043 			}
1044 		}
1045 
1046 	}
1047 
1048 	void main() {
1049 		auto window = new SimpleWindow(200, 200);
1050 
1051 		initializeBoard(10, 10, 10);
1052 
1053 		redraw(window);
1054 		window.eventLoop(0,
1055 			delegate (MouseEvent me) {
1056 				if(me.type != MouseEventType.buttonPressed)
1057 					return;
1058 				auto x = me.x / 20;
1059 				auto y = me.y / 20;
1060 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1061 					if(me.button == MouseButton.left) {
1062 						gameState = reveal(x, y);
1063 					} else {
1064 						userState[y*boardWidth+x] = UserSquare.flagged;
1065 					}
1066 					redraw(window);
1067 				}
1068 			}
1069 		);
1070 	}
1071 }
1072 
1073 /*
1074 version(OSX) {
1075 	version=without_opengl;
1076 	version=allow_unimplemented_features;
1077 	version=OSXCocoa;
1078 	pragma(linkerDirective, "-framework Cocoa");
1079 }
1080 */
1081 
1082 version(without_opengl) {
1083 	enum SdpyIsUsingIVGLBinds = false;
1084 } else /*version(Posix)*/ {
1085 	static if (__traits(compiles, (){import iv.glbinds;})) {
1086 		enum SdpyIsUsingIVGLBinds = true;
1087 		public import iv.glbinds;
1088 		//pragma(msg, "SDPY: using iv.glbinds");
1089 	} else {
1090 		enum SdpyIsUsingIVGLBinds = false;
1091 	}
1092 //} else {
1093 //	enum SdpyIsUsingIVGLBinds = false;
1094 }
1095 
1096 
1097 version(Windows) {
1098 	//import core.sys.windows.windows;
1099 	import core.sys.windows.winnls;
1100 	import core.sys.windows.windef;
1101 	import core.sys.windows.basetyps;
1102 	import core.sys.windows.winbase;
1103 	import core.sys.windows.winuser;
1104 	import core.sys.windows.shellapi;
1105 	import core.sys.windows.wingdi;
1106 	static import gdi = core.sys.windows.wingdi; // so i
1107 
1108 	pragma(lib, "gdi32");
1109 	pragma(lib, "user32");
1110 
1111 	// for AlphaBlend... a breaking change....
1112 	version(CRuntime_DigitalMars) { } else
1113 		pragma(lib, "msimg32");
1114 } else version (linux) {
1115 	//k8: this is hack for rdmd. sorry.
1116 	static import core.sys.linux.epoll;
1117 	static import core.sys.linux.timerfd;
1118 }
1119 
1120 
1121 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1122 
1123 // http://wiki.dlang.org/Simpledisplay.d
1124 
1125 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1126 
1127 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1128 // but can i control the scroll lock led
1129 
1130 
1131 // Note: if you are using Image on X, you might want to do:
1132 /*
1133 	static if(UsingSimpledisplayX11) {
1134 		if(!Image.impl.xshmAvailable) {
1135 			// the images will use the slower XPutImage, you might
1136 			// want to consider an alternative method to get better speed
1137 		}
1138 	}
1139 
1140 	If the shared memory extension is available though, simpledisplay uses it
1141 	for a significant speed boost whenever you draw large Images.
1142 */
1143 
1144 // 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.
1145 
1146 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1147 
1148 /*
1149 	Biggest FIXME:
1150 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1151 
1152 		clean up opengl contexts when their windows close
1153 
1154 		fix resizing the bitmaps/pixmaps
1155 */
1156 
1157 // BTW on Windows:
1158 // -L/SUBSYSTEM:WINDOWS:5.0
1159 // to dmd will make a nice windows binary w/o a console if you want that.
1160 
1161 /*
1162 	Stuff to add:
1163 
1164 	use multibyte functions everywhere we can
1165 
1166 	OpenGL windows
1167 	more event stuff
1168 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1169 
1170 
1171 	resizeEvent
1172 		and make the windows non-resizable by default,
1173 		or perhaps stretched (if I can find something in X like StretchBlt)
1174 
1175 	take a screenshot function!
1176 
1177 	Pens and brushes?
1178 	Maybe a global event loop?
1179 
1180 	Mouse deltas
1181 	Key items
1182 */
1183 
1184 /*
1185 From MSDN:
1186 
1187 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1188 
1189 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.
1190 
1191 */
1192 
1193 version(linux) {
1194 	version = X11;
1195 	version(without_libnotify) {
1196 		// we cool
1197 	}
1198 	else
1199 		version = libnotify;
1200 }
1201 
1202 version(libnotify) {
1203 	pragma(lib, "dl");
1204 	import core.sys.posix.dlfcn;
1205 
1206 	void delegate()[int] libnotify_action_delegates;
1207 	int libnotify_action_delegates_count;
1208 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1209 		auto idx = cast(int) user_data;
1210 		if(auto dgptr = idx in libnotify_action_delegates) {
1211 			(*dgptr)();
1212 			libnotify_action_delegates.remove(idx);
1213 		}
1214 	}
1215 
1216 	struct C_DynamicLibrary {
1217 		void* handle;
1218 		this(string name) {
1219 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1220 			if(handle is null)
1221 				throw new Exception("dlopen");
1222 		}
1223 
1224 		void close() {
1225 			dlclose(handle);
1226 		}
1227 
1228 		~this() {
1229 			// close
1230 		}
1231 
1232 		// FIXME: this looks up by name every time.... 
1233 		template call(string func, Ret, Args...) {
1234 			extern(C) Ret function(Args) fptr;
1235 			typeof(fptr) call() {
1236 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1237 				return fptr;
1238 			}
1239 		}
1240 	}
1241 
1242 	C_DynamicLibrary* libnotify;
1243 }
1244 
1245 version(OSX) {
1246 	version(OSXCocoa) {}
1247 	else { version = X11; }
1248 }
1249 	//version = OSXCocoa; // this was written by KennyTM
1250 version(FreeBSD)
1251 	version = X11;
1252 version(Solaris)
1253 	version = X11;
1254 
1255 version(X11) {
1256 	version(without_xft) {}
1257 	else version=with_xft;
1258 }
1259 
1260 void featureNotImplemented()() {
1261 	version(allow_unimplemented_features)
1262 		throw new NotYetImplementedException();
1263 	else
1264 		static assert(0);
1265 }
1266 
1267 // these are so the static asserts don't trigger unless you want to
1268 // add support to it for an OS
1269 version(Windows)
1270 	version = with_timer;
1271 version(linux)
1272 	version = with_timer;
1273 
1274 version(with_timer)
1275 	enum bool SimpledisplayTimerAvailable = true;
1276 else
1277 	enum bool SimpledisplayTimerAvailable = false;
1278 
1279 /// 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.
1280 version(Windows)
1281 	enum bool UsingSimpledisplayWindows = true;
1282 else
1283 	enum bool UsingSimpledisplayWindows = false;
1284 
1285 /// 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.
1286 version(X11)
1287 	enum bool UsingSimpledisplayX11 = true;
1288 else
1289 	enum bool UsingSimpledisplayX11 = false;
1290 
1291 /// 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.
1292 version(OSXCocoa)
1293 	enum bool UsingSimpledisplayCocoa = true;
1294 else
1295 	enum bool UsingSimpledisplayCocoa = false;
1296 
1297 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1298 version(Windows)
1299 	enum multipleWindowsSupported = true;
1300 else version(X11)
1301 	enum multipleWindowsSupported = true;
1302 else version(OSXCocoa)
1303 	enum multipleWindowsSupported = true;
1304 else
1305 	static assert(0);
1306 
1307 version(without_opengl)
1308 	enum bool OpenGlEnabled = false;
1309 else
1310 	enum bool OpenGlEnabled = true;
1311 
1312 
1313 /++
1314 	After selecting a type from [WindowTypes], you may further customize
1315 	its behavior by setting one or more of these flags.
1316 
1317 
1318 	The different window types have different meanings of `normal`. If the
1319 	window type already is a good match for what you want to do, you should
1320 	just use [WindowFlags.normal], the default, which will do the right thing
1321 	for your users.
1322 
1323 	The window flags will not always be honored by the operating system
1324 	and window managers; they are hints, not commands.
1325 +/
1326 enum WindowFlags : int {
1327 	normal = 0, ///
1328 	skipTaskbar = 1, ///
1329 	alwaysOnTop = 2, ///
1330 	alwaysOnBottom = 4, ///
1331 	cannotBeActivated = 8, ///
1332 	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.
1333 	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.
1334 	/++
1335 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1336 		it is still a top-level window. This should NOT be set separately for most window types.
1337 
1338 		A transient window will not keep the application open if its main window closes.
1339 
1340 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1341 
1342 
1343 		From the ICCM:
1344 
1345 		$(BLOCKQUOTE
1346 			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. 
1347 
1348 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1349 		)
1350 
1351 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1352 
1353 		History:
1354 			Added February 23, 2021 but not yet stabilized.
1355 	+/
1356 	transient = 64,
1357 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1358 }
1359 
1360 /++
1361 	When creating a window, you can pass a type to SimpleWindow's constructor,
1362 	then further customize the window by changing `WindowFlags`.
1363 
1364 
1365 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1366 	use. The others are there to build a foundation for a higher level GUI toolkit,
1367 	but are themselves not as high level as you might think from their names.
1368 
1369 	This list is based on the EMWH spec for X11.
1370 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1371 +/
1372 enum WindowTypes : int {
1373 	/// An ordinary application window.
1374 	normal,
1375 	/// 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.
1376 	undecorated,
1377 	/// 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.
1378 	eventOnly,
1379 	/// A drop down menu, such as from a menu bar
1380 	dropdownMenu,
1381 	/// A popup menu, such as from a right click
1382 	popupMenu,
1383 	/// A popup bubble notification
1384 	notification,
1385 	/*
1386 	menu, /// a tearable menu bar
1387 	splashScreen, /// a loading splash screen for your application
1388 	tooltip, /// A tiny window showing temporary help text or something.
1389 	comboBoxDropdown,
1390 	dialog,
1391 	toolbar
1392 	*/
1393 	/// a child nested inside the parent. You must pass a parent window to the ctor
1394 	nestedChild,
1395 }
1396 
1397 
1398 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1399 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1400 private __gshared char* sdpyWindowClassStr = null;
1401 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1402 
1403 /**
1404 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1405 	You may want to change context version if you want to use advanced shaders or
1406 	other modern OpenGL techinques. This setting doesn't affect already created
1407 	windows. You may use version 2.1 as your default, which should be supported
1408 	by any box since 2006, so seems to be a reasonable choice.
1409 
1410 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1411 	old context creation code without any version specified. This is the safest
1412 	way to init OpenGL, but it may not give you access to advanced features.
1413 
1414 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1415 */
1416 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1417 
1418 /**
1419 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1420 	pipeline functions, and without "compatible" mode you won't be able to use
1421 	your old non-shader-based code with such contexts. By default SimpleDisplay
1422 	creates compatible context, so you can gradually upgrade your OpenGL code if
1423 	you want to (or leave it as is, as it should "just work").
1424 */
1425 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1426 
1427 /**
1428 	Set to `true` to allow creating OpenGL context with lower version than requested
1429 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1430 	`openGLContextFallbackActivated()` will return `true`.
1431 	*/
1432 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1433 
1434 /**
1435 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1436 	*/
1437 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1438 
1439 
1440 /**
1441 	Set window class name for all following `new SimpleWindow()` calls.
1442 
1443 	WARNING! For Windows, you should set your class name before creating any
1444 	window, and NEVER change it after that!
1445 */
1446 void sdpyWindowClass (const(char)[] v) {
1447 	import core.stdc.stdlib : realloc;
1448 	if (v.length == 0) v = "SimpleDisplayWindow";
1449 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1450 	if (sdpyWindowClassStr is null) return; // oops
1451 	sdpyWindowClassStr[0..v.length+1] = 0;
1452 	sdpyWindowClassStr[0..v.length] = v[];
1453 }
1454 
1455 /**
1456 	Get current window class name.
1457 */
1458 string sdpyWindowClass () {
1459 	if (sdpyWindowClassStr is null) return null;
1460 	foreach (immutable idx; 0..size_t.max-1) {
1461 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1462 	}
1463 	return null;
1464 }
1465 
1466 /++
1467 	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.
1468 +/
1469 float[2] getDpi() {
1470 	float[2] dpi;
1471 	version(Windows) {
1472 		HDC screen = GetDC(null);
1473 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1474 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1475 	} else version(X11) {
1476 		auto display = XDisplayConnection.get;
1477 		auto screen = DefaultScreen(display);
1478 
1479 		void fallback() {
1480 			// 25.4 millimeters in an inch...
1481 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1482 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1483 		}
1484 
1485 		char* resourceString = XResourceManagerString(display);
1486 		XrmInitialize();
1487 
1488 		auto db = XrmGetStringDatabase(resourceString);
1489 
1490 		if (resourceString) {
1491 			XrmValue value;
1492 			char* type;
1493 			if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1494 				if (value.addr) {
1495 					import core.stdc.stdlib;
1496 					dpi[0] = atof(cast(char*) value.addr);
1497 					dpi[1] = dpi[0];
1498 				} else {
1499 					fallback();
1500 				}
1501 			} else {
1502 				fallback();
1503 			}
1504 		} else {
1505 			fallback();
1506 		}
1507 	}
1508 
1509 	return dpi;
1510 }
1511 
1512 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height) {
1513 	TrueColorImage got;
1514 	version(X11) {
1515 		auto display = XDisplayConnection.get;
1516 		auto image = XGetImage(display, handle, 0, 0, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1517 
1518 		// https://github.com/adamdruppe/arsd/issues/98
1519 
1520 		auto i = new Image(image);
1521 		got = i.toTrueColorImage();
1522 
1523 		XDestroyImage(image);
1524 	} else version(Windows) {
1525 		// I just need to BitBlt that shit... BUT WAIT IT IS ALREADY IN A DIB!!!!!!!
1526 
1527 		auto hdc = GetDC(handle);
1528 		scope(exit) ReleaseDC(handle, hdc);
1529 		auto i = new Image(width, height);
1530 		HDC hdcMem = CreateCompatibleDC(hdc);
1531 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1532 		BitBlt(hdcMem, 0, 0, width, height, hdc, 0, 0, SRCCOPY);
1533 		SelectObject(hdcMem, hbmOld);
1534 		DeleteDC(hdcMem);
1535 
1536 		got = i.toTrueColorImage();
1537 	} else featureNotImplemented();
1538 
1539 	return got;
1540 }
1541 
1542 /++
1543 	The flagship window class.
1544 
1545 
1546 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1547 	out of more advanced or complex features of the underlying windowing system.
1548 
1549 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1550 	and get a suitable window to work with.
1551 
1552 	From there, you can opt into additional features, like custom resizability and OpenGL support
1553 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1554 	and customization flags with the final two constructor arguments.
1555 
1556 	If none of that works for you, you can also create a window using native function calls, then
1557 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1558 	though, if you do this, managing the window is still your own responsibility! Notably, you
1559 	will need to destroy it yourself.
1560 +/
1561 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1562 
1563 	/++
1564 		Copies the window's current state into a [TrueColorImage].
1565 
1566 		Be warned: this can be a very slow operation
1567 
1568 		History:
1569 			Actually implemented on March 14, 2021
1570 	+/
1571 	TrueColorImage takeScreenshot() {
1572 		version(Windows)
1573 			return trueColorImageFromNativeHandle(impl.hwnd, width, height);
1574 		else version(OSXCocoa)
1575 			throw new NotYetImplementedException();
1576 		else
1577 			return trueColorImageFromNativeHandle(impl.window, width, height);
1578 	}
1579 
1580 	version(X11) {
1581 		void recreateAfterDisconnect() {
1582 			if(!stateDiscarded) return;
1583 
1584 			if(_parent !is null && _parent.stateDiscarded)
1585 				_parent.recreateAfterDisconnect();
1586 
1587 			bool wasHidden = hidden;
1588 
1589 			activeScreenPainter = null; // should already be done but just to confirm
1590 
1591 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1592 
1593 			if(auto dh = dropHandler) {
1594 				dropHandler = null;
1595 				enableDragAndDrop(this, dh);
1596 			}
1597 
1598 			if(recreateAdditionalConnectionState)
1599 				recreateAdditionalConnectionState();
1600 
1601 			hidden = wasHidden;
1602 			stateDiscarded = false;
1603 		}
1604 
1605 		bool stateDiscarded;
1606 		void discardConnectionState() {
1607 			if(XDisplayConnection.display)
1608 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
1609 			if(discardAdditionalConnectionState)
1610 				discardAdditionalConnectionState();
1611 			stateDiscarded = true;
1612 		}
1613 
1614 		void delegate() discardAdditionalConnectionState;
1615 		void delegate() recreateAdditionalConnectionState;
1616 
1617 	}
1618 
1619 	private DropHandler dropHandler;
1620 
1621 	SimpleWindow _parent;
1622 	bool beingOpenKeepsAppOpen = true;
1623 	/++
1624 		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.
1625 
1626 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
1627 
1628 		Params:
1629 
1630 		width = the width of the window's client area, in pixels
1631 		height = the height of the window's client area, in pixels
1632 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
1633 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
1634 		resizable = [Resizability] has three options:
1635 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
1636 			$(P `fixedSize` will not allow the user to resize the window.)
1637 			$(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.)
1638 		windowType = The type of window you want to make.
1639 		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.
1640 		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".
1641 	+/
1642 	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) {
1643 		claimGuiThread();
1644 		version(sdpy_thread_checks) assert(thisIsGuiThread);
1645 		this._width = width;
1646 		this._height = height;
1647 		this.openglMode = opengl;
1648 		this.resizability = resizable;
1649 		this.windowType = windowType;
1650 		this.customizationFlags = customizationFlags;
1651 		this._title = (title is null ? "D Application" : title);
1652 		this._parent = parent;
1653 		impl.createWindow(width, height, this._title, opengl, parent);
1654 
1655 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
1656 			beingOpenKeepsAppOpen = false;
1657 	}
1658 
1659 	/// ditto
1660 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
1661 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
1662 	}
1663 
1664 	/// Same as above, except using the `Size` struct instead of separate width and height.
1665 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
1666 		this(size.width, size.height, title, opengl, resizable);
1667 	}
1668 
1669 	/// ditto
1670 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
1671 		this(size, title, opengl, resizable);
1672 	}
1673 
1674 
1675 	/++
1676 		Creates a window based on the given [Image]. It's client area
1677 		width and height is equal to the image. (A window's client area
1678 		is the drawable space inside; it excludes the title bar, etc.)
1679 
1680 		Windows based on images will not be resizable and do not use OpenGL.
1681 
1682 		It will draw the image in upon creation, but this will be overwritten
1683 		upon any draws, including the initial window visible event.
1684 
1685 		You probably do not want to use this and it may be removed from
1686 		the library eventually, or I might change it to be a "permanent"
1687 		background image; one that is automatically drawn on it before any
1688 		other drawing event. idk.
1689 	+/
1690 	this(Image image, string title = null) {
1691 		this(image.width, image.height, title);
1692 		this.image = image;
1693 	}
1694 
1695 	/++
1696 		Wraps a native window handle with very little additional processing - notably no destruction
1697 		this is incomplete so don't use it for much right now. The purpose of this is to make native
1698 		windows created through the low level API (so you can use platform-specific options and
1699 		other details SimpleWindow does not expose) available to the event loop wrappers.
1700 	+/
1701 	this(NativeWindowHandle nativeWindow) {
1702 		version(Windows)
1703 			impl.hwnd = nativeWindow;
1704 		else version(X11) {
1705 			impl.window = nativeWindow;
1706 			display = XDisplayConnection.get(); // get initial display to not segfault
1707 		} else version(OSXCocoa)
1708 			throw new NotYetImplementedException();
1709 		else featureNotImplemented();
1710 		// FIXME: set the size correctly
1711 		_width = 1;
1712 		_height = 1;
1713 		nativeMapping[nativeWindow] = this;
1714 
1715 		beingOpenKeepsAppOpen = false;
1716 
1717 		CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
1718 		_suppressDestruction = true; // so it doesn't try to close
1719 	}
1720 
1721 	/// Experimental, do not use yet
1722 	/++
1723 		Grabs exclusive input from the user until you release it with
1724 		[releaseInputGrab].
1725 
1726 
1727 		Note: it is extremely rude to do this without good reason.
1728 		Reasons may include doing some kind of mouse drag operation
1729 		or popping up a temporary menu that should get events and will
1730 		be dismissed at ease by the user clicking away.
1731 
1732 		Params:
1733 			keyboard = do you want to grab keyboard input?
1734 			mouse = grab mouse input?
1735 			confine = confine the mouse cursor to inside this window?
1736 
1737 		History:
1738 			Prior to March 11, 2021, grabbing the keyboard would always also
1739 			set the X input focus. Now, it only focuses if it is a non-transient
1740 			window and otherwise manages the input direction internally.
1741 
1742 			This means spurious focus/blur events will no longer be sent and the
1743 			application will not steal focus from other applications (which the
1744 			window manager may have rejected anyway).
1745 	+/
1746 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
1747 		static if(UsingSimpledisplayX11) {
1748 			XSync(XDisplayConnection.get, 0);
1749 			if(keyboard) {
1750 				if(isTransient && _parent) {
1751 					/*
1752 					FIXME:
1753 						setting the keyboard focus is not actually that helpful, what I more likely want
1754 						is the events from the parent window to be sent over here if we're transient.
1755 					*/
1756 
1757 					_parent.inputProxy = this;
1758 				} else {
1759 					XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
1760 				}
1761 			}
1762 			if(mouse) {
1763 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 
1764 				EventMask.PointerMotionMask // FIXME: not efficient
1765 				| EventMask.ButtonPressMask
1766 				| EventMask.ButtonReleaseMask
1767 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
1768 				)
1769 			{
1770 				XSync(XDisplayConnection.get, 0);
1771 				import core.stdc.stdio;
1772 				printf("Grab input failed %d\n", res);
1773 				//throw new Exception("Grab input failed");
1774 			} else {
1775 				// cool
1776 			}
1777 			}
1778 
1779 		} else version(Windows) {
1780 			// FIXME: keyboard?
1781 			SetCapture(impl.hwnd);
1782 			if(confine) {
1783 				RECT rcClip;
1784 				//RECT rcOldClip;
1785 				//GetClipCursor(&rcOldClip); 
1786 				GetWindowRect(hwnd, &rcClip); 
1787 				ClipCursor(&rcClip); 
1788 			}
1789 		} else version(OSXCocoa) {
1790 			throw new NotYetImplementedException();
1791 		} else static assert(0);
1792 	}
1793 
1794 	private bool isTransient() {
1795 		with(WindowTypes)
1796 		final switch(windowType) {
1797 			case normal, undecorated, eventOnly:
1798 			case nestedChild:
1799 				return (customizationFlags & WindowFlags.transient) ? true : false;
1800 			case dropdownMenu, popupMenu, notification:
1801 				return true;
1802 		}
1803 	}
1804 
1805 	private SimpleWindow inputProxy;
1806 
1807 	/++
1808 		Releases the grab acquired by [grabInput].
1809 	+/
1810 	void releaseInputGrab() {
1811 		static if(UsingSimpledisplayX11) {
1812 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
1813 			if(_parent)
1814 				_parent.inputProxy = null;
1815 		} else version(Windows) {
1816 			ReleaseCapture();
1817 			ClipCursor(null); 
1818 		} else version(OSXCocoa) {
1819 			throw new NotYetImplementedException();
1820 		} else static assert(0);
1821 	}
1822 
1823 	/++
1824 		Sets the input focus to this window.
1825 
1826 		You shouldn't call this very often - please let the user control the input focus.
1827 	+/
1828 	void focus() {
1829 		static if(UsingSimpledisplayX11) {
1830 			XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
1831 		} else version(Windows) {
1832 			SetFocus(this.impl.hwnd);
1833 		} else version(OSXCocoa) {
1834 			throw new NotYetImplementedException();
1835 		} else static assert(0);
1836 	}
1837 
1838 	/++
1839 		Requests attention from the user for this window.
1840 
1841 
1842 		The typical result of this function is to change the color
1843 		of the taskbar icon, though it may be tweaked on specific
1844 		platforms.
1845 
1846 		It is meant to unobtrusively tell the user that something
1847 		relevant to them happened in the background and they should
1848 		check the window when they get a chance. Upon receiving the
1849 		keyboard focus, the window will automatically return to its
1850 		natural state.
1851 
1852 		If the window already has the keyboard focus, this function
1853 		may do nothing, because the user is presumed to already be
1854 		giving the window attention.
1855 
1856 		Implementation_note:
1857 
1858 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
1859 		atom on X11 and the FlashWindow function on Windows.
1860 	+/
1861 	void requestAttention() {
1862 		if(_focused)
1863 			return;
1864 
1865 		version(Windows) {
1866 			FLASHWINFO info;
1867 			info.cbSize = info.sizeof;
1868 			info.hwnd = impl.hwnd;
1869 			info.dwFlags = FLASHW_TRAY;
1870 			info.uCount = 1;
1871 
1872 			FlashWindowEx(&info);
1873 
1874 		} else version(X11) {
1875 			demandingAttention = true;
1876 			demandAttention(this, true);
1877 		} else version(OSXCocoa) {
1878 			throw new NotYetImplementedException();
1879 		} else static assert(0);
1880 	}
1881 
1882 	private bool _focused;
1883 
1884 	version(X11) private bool demandingAttention;
1885 
1886 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
1887 	/// You'll have to call `close()` manually if you set this delegate.
1888 	void delegate () closeQuery;
1889 
1890 	/// This will be called when window visibility was changed.
1891 	void delegate (bool becomesVisible) visibilityChanged;
1892 
1893 	/// This will be called when window becomes visible for the first time.
1894 	/// You can do OpenGL initialization here. Note that in X11 you can't call
1895 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
1896 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
1897 	private bool _visibleForTheFirstTimeCalled;
1898 	void delegate () visibleForTheFirstTime;
1899 
1900 	/// Returns true if the window has been closed.
1901 	final @property bool closed() { return _closed; }
1902 
1903 	/// Returns true if the window is focused.
1904 	final @property bool focused() { return _focused; }
1905 
1906 	private bool _visible;
1907 	/// Returns true if the window is visible (mapped).
1908 	final @property bool visible() { return _visible; }
1909 
1910 	/// Closes the window. If there are no more open windows, the event loop will terminate.
1911 	void close() {
1912 		if (!_closed) {
1913 			runInGuiThread( {
1914 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
1915 				if (onClosing !is null) onClosing();
1916 				impl.closeWindow();
1917 				_closed = true;
1918 			} );
1919 		}
1920 	}
1921 
1922 	/++
1923 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
1924 
1925 		History:
1926 			Overload added on March 7, 2021.
1927 	+/
1928 	void close() shared {
1929 		(cast() this).close();
1930 	}
1931 
1932 	/++
1933 
1934 	+/
1935 	void maximize() {
1936 		version(Windows)
1937 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
1938 		else version(X11) {
1939 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
1940 
1941 			// also note _NET_WM_STATE_FULLSCREEN
1942 		}
1943 
1944 	}
1945 
1946 	private bool _fullscreen;
1947 
1948 	/// not fully implemented but planned for a future release
1949 	void fullscreen(bool yes) {
1950 		version(X11)
1951 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
1952 
1953 		_fullscreen = yes;
1954 
1955 	}
1956 
1957 	bool fullscreen() {
1958 		return _fullscreen;
1959 	}
1960 
1961 	/++
1962 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
1963 
1964 	+/
1965 	void minimize() {
1966 		version(Windows)
1967 			ShowWindow(impl.hwnd, SW_MINIMIZE);
1968 		//else version(X11)
1969 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
1970 	}
1971 
1972 	/// Alias for `hidden = false`
1973 	void show() {
1974 		hidden = false;
1975 	}
1976 
1977 	/// Alias for `hidden = true`
1978 	void hide() {
1979 		hidden = true;
1980 	}
1981 
1982 	/// Hide cursor when it enters the window.
1983 	void hideCursor() {
1984 		version(OSXCocoa) throw new NotYetImplementedException(); else
1985 		if (!_closed) impl.hideCursor();
1986 	}
1987 
1988 	/// Don't hide cursor when it enters the window.
1989 	void showCursor() {
1990 		version(OSXCocoa) throw new NotYetImplementedException(); else
1991 		if (!_closed) impl.showCursor();
1992 	}
1993 
1994 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
1995 	 *
1996 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
1997 	 * control. Try to think for other approaches before using this function.
1998 	 *
1999 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2000 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2001 	 *       receive "mouse moved here" event.
2002 	 */
2003 	bool warpMouse (int x, int y) {
2004 		version(X11) {
2005 			if (!_closed) { impl.warpMouse(x, y); return true; }
2006 		} else version(Windows) {
2007 			if (!_closed) {
2008 				POINT point;
2009 				point.x = x;
2010 				point.y = y;
2011 				if(ClientToScreen(impl.hwnd, &point)) {
2012 					SetCursorPos(point.x, point.y);
2013 					return true;
2014 				}
2015 			}
2016 		}
2017 		return false;
2018 	}
2019 
2020 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2021 	void sendDummyEvent () {
2022 		version(X11) {
2023 			if (!_closed) { impl.sendDummyEvent(); }
2024 		}
2025 	}
2026 
2027 	/// Set window minimal size.
2028 	void setMinSize (int minwidth, int minheight) {
2029 		version(OSXCocoa) throw new NotYetImplementedException(); else
2030 		if (!_closed) impl.setMinSize(minwidth, minheight);
2031 	}
2032 
2033 	/// Set window maximal size.
2034 	void setMaxSize (int maxwidth, int maxheight) {
2035 		version(OSXCocoa) throw new NotYetImplementedException(); else
2036 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2037 	}
2038 
2039 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2040 	/// Currently only supported on X11.
2041 	void setResizeGranularity (int granx, int grany) {
2042 		version(OSXCocoa) throw new NotYetImplementedException(); else
2043 		if (!_closed) impl.setResizeGranularity(granx, grany);
2044 	}
2045 
2046 	/// Move window.
2047 	void move(int x, int y) {
2048 		version(OSXCocoa) throw new NotYetImplementedException(); else
2049 		if (!_closed) impl.move(x, y);
2050 	}
2051 
2052 	/// ditto
2053 	void move(Point p) {
2054 		version(OSXCocoa) throw new NotYetImplementedException(); else
2055 		if (!_closed) impl.move(p.x, p.y);
2056 	}
2057 
2058 	/++
2059 		Resize window.
2060 
2061 		Note that the width and height of the window are NOT instantly
2062 		updated - it waits for the window manager to approve the resize
2063 		request, which means you must return to the event loop before the
2064 		width and height are actually changed.
2065 	+/
2066 	void resize(int w, int h) {
2067 		if(!_closed && _fullscreen) fullscreen = false;
2068 		version(OSXCocoa) throw new NotYetImplementedException(); else
2069 		if (!_closed) impl.resize(w, h);
2070 	}
2071 
2072 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2073 	void moveResize (int x, int y, int w, int h) {
2074 		if(!_closed && _fullscreen) fullscreen = false;
2075 		version(OSXCocoa) throw new NotYetImplementedException(); else
2076 		if (!_closed) impl.moveResize(x, y, w, h);
2077 	}
2078 
2079 	private bool _hidden;
2080 
2081 	/// Returns true if the window is hidden.
2082 	final @property bool hidden() {
2083 		return _hidden;
2084 	}
2085 
2086 	/// Shows or hides the window based on the bool argument.
2087 	final @property void hidden(bool b) {
2088 		_hidden = b;
2089 		version(Windows) {
2090 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2091 		} else version(X11) {
2092 			if(b)
2093 				//XUnmapWindow(impl.display, impl.window);
2094 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2095 			else
2096 				XMapWindow(impl.display, impl.window);
2097 		} else version(OSXCocoa) {
2098 			throw new NotYetImplementedException();
2099 		} else static assert(0);
2100 	}
2101 
2102 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2103 	void opacity(double opacity) @property
2104 	in {
2105 		assert(opacity >= 0 && opacity <= 1);
2106 	} do {
2107 		version (Windows) {
2108 			impl.setOpacity(cast(ubyte)(255 * opacity));
2109 		} else version (X11) {
2110 			impl.setOpacity(cast(uint)(uint.max * opacity));
2111 		} else throw new NotYetImplementedException();
2112 	}
2113 
2114 	/++
2115 		Sets your event handlers, without entering the event loop. Useful if you
2116 		have multiple windows - set the handlers on each window, then only do eventLoop on your main window.
2117 	+/
2118 	void setEventHandlers(T...)(T eventHandlers) {
2119 		// FIXME: add more events
2120 		foreach(handler; eventHandlers) {
2121 			static if(__traits(compiles, handleKeyEvent = handler)) {
2122 				handleKeyEvent = handler;
2123 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2124 				handleCharEvent = handler;
2125 			} else static if(__traits(compiles, handlePulse = handler)) {
2126 				handlePulse = handler;
2127 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2128 				handleMouseEvent = handler;
2129 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2130 		}
2131 	}
2132 
2133 	/// The event loop automatically returns when the window is closed
2134 	/// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2135 	/// pulse timer is created. The event loop will block until an event
2136 	/// arrives or the pulse timer goes off.
2137 	final int eventLoop(T...)(
2138 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2139 		T eventHandlers) /// delegate list like std.concurrency.receive
2140 	{
2141 		setEventHandlers(eventHandlers);
2142 
2143 		version(with_eventloop) {
2144 			// delegates event loop to my other module
2145 			version(X11)
2146 				XFlush(display);
2147 
2148 			import arsd.eventloop;
2149 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2150 			scope(exit) clearInterval(handle);
2151 
2152 			loop();
2153 			return 0;
2154 		} else version(OSXCocoa) {
2155 			// FIXME
2156 			if (handlePulse !is null && pulseTimeout != 0) {
2157 				timer = scheduledTimer(pulseTimeout*1e-3,
2158 					view, sel_registerName("simpledisplay_pulse"),
2159 					null, true);
2160 			}
2161 
2162             		setNeedsDisplay(view, true);
2163             		run(NSApp);
2164             		return 0;
2165         	} else {
2166 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2167 			return el.run();
2168 		}
2169 	}
2170 
2171 	/++
2172 		This lets you draw on the window (or its backing buffer) using basic
2173 		2D primitives.
2174 
2175 		Be sure to call this in a limited scope because your changes will not
2176 		actually appear on the window until ScreenPainter's destructor runs.
2177 
2178 		Returns: an instance of [ScreenPainter], which has the drawing methods
2179 		on it to draw on this window.
2180 	+/
2181 	ScreenPainter draw() {
2182 		return impl.getPainter();
2183 	}
2184 
2185 	// This is here to implement the interface we use for various native handlers.
2186 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2187 
2188 	// maps native window handles to SimpleWindow instances, if there are any
2189 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2190 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2191 
2192 	/// Width of the window's drawable client area, in pixels.
2193 	@scriptable
2194 	final @property int width() const pure nothrow @safe @nogc { return _width; }
2195 
2196 	/// Height of the window's drawable client area, in pixels.
2197 	@scriptable
2198 	final @property int height() const pure nothrow @safe @nogc { return _height; }
2199 
2200 	private int _width;
2201 	private int _height;
2202 
2203 	// HACK: making the best of some copy constructor woes with refcounting
2204 	private ScreenPainterImplementation* activeScreenPainter_;
2205 
2206 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2207 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2208 
2209 	private OpenGlOptions openglMode;
2210 	private Resizability resizability;
2211 	private WindowTypes windowType;
2212 	private int customizationFlags;
2213 
2214 	/// `true` if OpenGL was initialized for this window.
2215 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2216 		version(without_opengl)
2217 			return false;
2218 		else
2219 			return (openglMode == OpenGlOptions.yes);
2220 	}
2221 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2222 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2223 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2224 
2225 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2226 	/// to call this, as it's not recommended to share window between threads.
2227 	void mtLock () {
2228 		version(X11) {
2229 			XLockDisplay(this.display);
2230 		}
2231 	}
2232 
2233 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2234 	/// to call this, as it's not recommended to share window between threads.
2235 	void mtUnlock () {
2236 		version(X11) {
2237 			XUnlockDisplay(this.display);
2238 		}
2239 	}
2240 
2241 	/// Emit a beep to get user's attention.
2242 	void beep () {
2243 		version(X11) {
2244 			XBell(this.display, 100);
2245 		} else version(Windows) {
2246 			MessageBeep(0xFFFFFFFF);
2247 		}
2248 	}
2249 
2250 
2251 
2252 	version(without_opengl) {} else {
2253 
2254 		/// 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`.
2255 		void delegate() redrawOpenGlScene;
2256 
2257 		/// This will allow you to change OpenGL vsync state.
2258 		final @property void vsync (bool wait) {
2259 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2260 		  version(X11) {
2261 		    setAsCurrentOpenGlContext();
2262 		    glxSetVSync(display, impl.window, wait);
2263 		  } else version(Windows) {
2264 		    setAsCurrentOpenGlContext();
2265                     wglSetVSync(wait);
2266 		  }
2267 		}
2268 
2269 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
2270 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
2271 		/// enough without waiting 'em to finish their frame bussiness.
2272 		bool useGLFinish = true;
2273 
2274 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
2275 		/// 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.
2276 		void redrawOpenGlSceneNow() {
2277 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
2278 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2279 			if(redrawOpenGlScene is null)
2280 				return;
2281 
2282 			this.mtLock();
2283 			scope(exit) this.mtUnlock();
2284 
2285 			this.setAsCurrentOpenGlContext();
2286 
2287 			redrawOpenGlScene();
2288 
2289 			this.swapOpenGlBuffers();
2290 			// 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.
2291 			if (useGLFinish) glFinish();
2292 		}
2293 
2294 		private bool redrawOpenGlSceneSoonSet = false;
2295 		private static class RedrawOpenGlSceneEvent {
2296 			SimpleWindow w;
2297 			this(SimpleWindow w) { this.w = w; }
2298 		}
2299 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
2300 		void redrawOpenGlSceneSoon() {
2301 			if(!redrawOpenGlSceneSoonSet) {
2302 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
2303 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
2304 				redrawOpenGlSceneSoonSet = true;
2305 			}
2306 			this.postEvent(redrawOpenGlSceneEvent, true);
2307 		}
2308 
2309 
2310 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2311 		void setAsCurrentOpenGlContext() {
2312 			assert(openglMode == OpenGlOptions.yes);
2313 			version(X11) {
2314 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
2315 					throw new Exception("glXMakeCurrent");
2316 			} else version(Windows) {
2317 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2318 				if (!wglMakeCurrent(ghDC, ghRC))
2319 					throw new Exception("wglMakeCurrent"); // let windows users suffer too
2320 			}
2321 		}
2322 
2323 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2324 		/// This doesn't throw, returning success flag instead.
2325 		bool setAsCurrentOpenGlContextNT() nothrow {
2326 			assert(openglMode == OpenGlOptions.yes);
2327 			version(X11) {
2328 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
2329 			} else version(Windows) {
2330 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2331 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
2332 			}
2333 		}
2334 
2335 		/// 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.
2336 		/// This doesn't throw, returning success flag instead.
2337 		bool releaseCurrentOpenGlContext() nothrow {
2338 			assert(openglMode == OpenGlOptions.yes);
2339 			version(X11) {
2340 				return (glXMakeCurrent(display, 0, null) != 0);
2341 			} else version(Windows) {
2342 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2343 				return wglMakeCurrent(ghDC, null) ? true : false;
2344 			}
2345 		}
2346 
2347 		/++
2348 			simpledisplay always uses double buffering, usually automatically. This
2349 			manually swaps the OpenGL buffers.
2350 
2351 
2352 			You should not need to call this yourself because simpledisplay will do it
2353 			for you after calling your `redrawOpenGlScene`.
2354 
2355 			Remember that this may throw an exception, which you can catch in a multithreaded
2356 			application to keep your thread from dying from an unhandled exception.
2357 		+/
2358 		void swapOpenGlBuffers() {
2359 			assert(openglMode == OpenGlOptions.yes);
2360 			version(X11) {
2361 				if (!this._visible) return; // no need to do this if window is invisible
2362 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2363 				glXSwapBuffers(display, impl.window);
2364 			} else version(Windows) {
2365 				SwapBuffers(ghDC);
2366 			}
2367 		}
2368 	}
2369 
2370 	/++
2371 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
2372 
2373 
2374 		---
2375 			auto window = new SimpleWindow(100, 100, "First title");
2376 			window.title = "A new title";
2377 		---
2378 
2379 		You may call this function at any time.
2380 	+/
2381 	@property void title(string title) {
2382 		_title = title;
2383 		version(OSXCocoa) throw new NotYetImplementedException(); else
2384 		impl.setTitle(title);
2385 	}
2386 
2387 	private string _title;
2388 
2389 	/// Gets the title
2390 	@property string title() {
2391 		if(_title is null)
2392 			_title = getRealTitle();
2393 		return _title;
2394 	}
2395 
2396 	/++
2397 		Get the title as set by the window manager.
2398 		May not match what you attempted to set.
2399 	+/
2400 	string getRealTitle() {
2401 		static if(is(typeof(impl.getTitle())))
2402 			return impl.getTitle();
2403 		else
2404 			return null;
2405 	}
2406 
2407 	// don't use this generally it is not yet really released
2408 	version(X11)
2409 	@property Image secret_icon() {
2410 		return secret_icon_inner;
2411 	}
2412 	private Image secret_icon_inner;
2413 
2414 
2415 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user.
2416 	@property void icon(MemoryImage icon) {
2417 		auto tci = icon.getAsTrueColorImage();
2418 		version(Windows) {
2419 			winIcon = new WindowsIcon(icon);
2420 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
2421 		} else version(X11) {
2422 			secret_icon_inner = Image.fromMemoryImage(icon);
2423 			// FIXME: ensure this is correct
2424 			auto display = XDisplayConnection.get;
2425 			arch_ulong[] buffer;
2426 			buffer ~= icon.width;
2427 			buffer ~= icon.height;
2428 			foreach(c; tci.imageData.colors) {
2429 				arch_ulong b;
2430 				b |= c.a << 24;
2431 				b |= c.r << 16;
2432 				b |= c.g << 8;
2433 				b |= c.b;
2434 				buffer ~= b;
2435 			}
2436 
2437 			XChangeProperty(
2438 				display,
2439 				impl.window,
2440 				GetAtom!("_NET_WM_ICON", true)(display),
2441 				GetAtom!"CARDINAL"(display),
2442 				32 /* bits */,
2443 				0 /*PropModeReplace*/,
2444 				buffer.ptr,
2445 				cast(int) buffer.length);
2446 		} else version(OSXCocoa) {
2447 			throw new NotYetImplementedException();
2448 		} else static assert(0);
2449 	}
2450 
2451 	version(Windows)
2452 		private WindowsIcon winIcon;
2453 
2454 	bool _suppressDestruction;
2455 
2456 	~this() {
2457 		if(_suppressDestruction)
2458 			return;
2459 		impl.dispose();
2460 	}
2461 
2462 	private bool _closed;
2463 
2464 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
2465 	/*
2466 	ScreenPainter drawTransiently() {
2467 		return impl.getPainter();
2468 	}
2469 	*/
2470 
2471 	/// Draws an image on the window. This is meant to provide quick look
2472 	/// of a static image generated elsewhere.
2473 	@property void image(Image i) {
2474 		version(Windows) {
2475 			BITMAP bm;
2476 			HDC hdc = GetDC(hwnd);
2477 			HDC hdcMem = CreateCompatibleDC(hdc);
2478 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
2479 
2480 			GetObject(i.handle, bm.sizeof, &bm);
2481 
2482 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
2483 
2484 			SelectObject(hdcMem, hbmOld);
2485 			DeleteDC(hdcMem);
2486 			ReleaseDC(hwnd, hdc);
2487 
2488 			/*
2489 			RECT r;
2490 			r.right = i.width;
2491 			r.bottom = i.height;
2492 			InvalidateRect(hwnd, &r, false);
2493 			*/
2494 		} else
2495 		version(X11) {
2496 			if(!destroyed) {
2497 				if(i.usingXshm)
2498 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
2499 				else
2500 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
2501 			}
2502 		} else
2503 		version(OSXCocoa) {
2504 			draw().drawImage(Point(0, 0), i);
2505 			setNeedsDisplay(view, true);
2506 		} else static assert(0);
2507 	}
2508 
2509 	/++
2510 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
2511 
2512 		---
2513 		window.cursor = GenericCursor.Help;
2514 		// now the window mouse cursor is set to a generic help
2515 		---
2516 
2517 	+/
2518 	@property void cursor(MouseCursor cursor) {
2519 		version(OSXCocoa)
2520 			featureNotImplemented();
2521 		else
2522 		if(this.impl.curHidden <= 0) {
2523 			static if(UsingSimpledisplayX11) {
2524 				auto ch = cursor.cursorHandle;
2525 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
2526 			} else version(Windows) {
2527 				auto ch = cursor.cursorHandle;
2528 				impl.currentCursor = ch;
2529 				SetCursor(ch); // redraw without waiting for mouse movement to update
2530 			} else featureNotImplemented();
2531 		}
2532 
2533 	}
2534 
2535 	/// What follows are the event handlers. These are set automatically
2536 	/// by the eventLoop function, but are still public so you can change
2537 	/// them later. wasPressed == true means key down. false == key up.
2538 
2539 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
2540 	void delegate(KeyEvent ke) handleKeyEvent;
2541 
2542 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
2543 	void delegate(dchar c) handleCharEvent;
2544 
2545 	/// Handles a timer pulse. Settable through setEventHandlers.
2546 	void delegate() handlePulse;
2547 
2548 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
2549 	void delegate(bool) onFocusChange;
2550 
2551 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
2552 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
2553 	void delegate() onClosing;
2554 
2555 	/** Called when we received destroy notification. At this stage we cannot do much with our window
2556 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
2557 	 * last minute cleanup. */
2558 	void delegate() onDestroyed;
2559 
2560 	static if (UsingSimpledisplayX11)
2561 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
2562 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
2563 	 * You will probably never need to setup this handler, it is for very low-level stuff.
2564 	 *
2565 	 * WARNING! Xlib is multithread-locked when this handles is called! */
2566 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
2567 
2568 	//version(Windows)
2569 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
2570 
2571 	private {
2572 		int lastMouseX = int.min;
2573 		int lastMouseY = int.min;
2574 		void mdx(ref MouseEvent ev) {
2575 			if(lastMouseX == int.min || lastMouseY == int.min) {
2576 				ev.dx = 0;
2577 				ev.dy = 0;
2578 			} else {
2579 				ev.dx = ev.x - lastMouseX;
2580 				ev.dy = ev.y - lastMouseY;
2581 			}
2582 
2583 			lastMouseX = ev.x;
2584 			lastMouseY = ev.y;
2585 		}
2586 	}
2587 
2588 	/// Mouse event handler. Settable through setEventHandlers.
2589 	void delegate(MouseEvent) handleMouseEvent;
2590 
2591 	/// use to redraw child widgets if you use system apis to add stuff
2592 	void delegate() paintingFinished;
2593 
2594 	void delegate() paintingFinishedDg() {
2595 		return paintingFinished;
2596 	}
2597 
2598 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
2599 	/// for this to ever happen.
2600 	void delegate(int width, int height) windowResized;
2601 
2602 	/** Platform specific - handle any native messages this window gets.
2603 	  *
2604 	  * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it.
2605 
2606 	  * On Windows, it takes the form of int delegate(HWND,UINT, WPARAM, LPARAM).
2607 
2608 	  * On X11, it takes the form of int delegate(XEvent).
2609 
2610 	  * IMPORTANT: it used to be static in old versions of simpledisplay.d, but I always used
2611 	  * it as if it wasn't static... so now I just fixed it so it isn't anymore.
2612 	**/
2613 	NativeEventHandler handleNativeEvent;
2614 
2615 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
2616 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
2617 	/// this instead and it will work the same way.
2618 	__gshared NativeEventHandler handleNativeGlobalEvent;
2619 
2620 //  private:
2621 	/// The native implementation is available, but you shouldn't use it unless you are
2622 	/// familiar with the underlying operating system, don't mind depending on it, and
2623 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
2624 	/// do what you need to do with handleNativeEvent instead.
2625 	///
2626 	/// This is likely to eventually change to be just a struct holding platform-specific
2627 	/// handles instead of a template mixin at some point because I'm not happy with the
2628 	/// code duplication here (ironically).
2629 	mixin NativeSimpleWindowImplementation!() impl;
2630 
2631 	/**
2632 		This is in-process one-way (from anything to window) event sending mechanics.
2633 		It is thread-safe, so it can be used in multi-threaded applications to send,
2634 		for example, "wake up and repaint" events when thread completed some operation.
2635 		This will allow to avoid using timer pulse to check events with synchronization,
2636 		'cause event handler will be called in UI thread. You can stop guessing which
2637 		pulse frequency will be enough for your app.
2638 		Note that events handlers may be called in arbitrary order, i.e. last registered
2639 		handler can be called first, and vice versa.
2640 	*/
2641 public:
2642 	/** Is our custom event queue empty? Can be used in simple cases to prevent
2643 	 * "spamming" window with events it can't cope with.
2644 	 * It is safe to call this from non-UI threads.
2645 	 */
2646 	@property bool eventQueueEmpty() () {
2647 		synchronized(this) {
2648 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
2649 		}
2650 		return true;
2651 	}
2652 
2653 	/** Does our custom event queue contains at least one with the given type?
2654 	 * Can be used in simple cases to prevent "spamming" window with events
2655 	 * it can't cope with.
2656 	 * It is safe to call this from non-UI threads.
2657 	 */
2658 	@property bool eventQueued(ET:Object) () {
2659 		synchronized(this) {
2660 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
2661 				if (!o.doProcess) {
2662 					if (cast(ET)(o.evt)) return true;
2663 				}
2664 			}
2665 		}
2666 		return false;
2667 	}
2668 
2669 	/++
2670 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
2671 
2672 		History:
2673 			Added May 12, 2021
2674 	+/
2675 	void delegate(Exception e) nothrow eventUncaughtException;
2676 
2677 	/** Add listener for custom event. Can be used like this:
2678 	 *
2679 	 * ---------------------
2680 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
2681 	 *   ...
2682 	 *   win.removeEventListener(eid);
2683 	 * ---------------------
2684 	 *
2685 	 * Returns: 0 on failure (should never happen, so ignore it)
2686 	 *
2687 	 * $(WARNING Don't use this method in object destructors!)
2688 	 *
2689 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
2690 	 *           'cause if event handler id counter will overflow, you won't be able
2691 	 *           to register any more events.)
2692 	 */
2693 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
2694 		if (dg is null) return 0; // ignore empty handlers
2695 		synchronized(this) {
2696 			//FIXME: abort on overflow?
2697 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
2698 			EventHandlerEntry e;
2699 			e.dg = delegate (Object o) {
2700 				if (auto co = cast(ET)o) {
2701 					try {
2702 						dg(co);
2703 					} catch (Exception e) {
2704 						// sorry!
2705 						if(eventUncaughtException)
2706 							eventUncaughtException(e);
2707 					}
2708 					return true;
2709 				}
2710 				return false;
2711 			};
2712 			e.id = lastUsedHandlerId;
2713 			auto optr = eventHandlers.ptr;
2714 			eventHandlers ~= e;
2715 			if (eventHandlers.ptr !is optr) {
2716 				import core.memory : GC;
2717 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
2718 			}
2719 			return lastUsedHandlerId;
2720 		}
2721 	}
2722 
2723 	/// Remove event listener. It is safe to pass invalid event id here.
2724 	/// $(WARNING Don't use this method in object destructors!)
2725 	void removeEventListener() (uint id) {
2726 		if (id == 0 || id > lastUsedHandlerId) return;
2727 		synchronized(this) {
2728 			foreach (immutable idx; 0..eventHandlers.length) {
2729 				if (eventHandlers[idx].id == id) {
2730 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
2731 					eventHandlers[$-1].dg = null;
2732 					eventHandlers.length -= 1;
2733 					eventHandlers.assumeSafeAppend;
2734 					return;
2735 				}
2736 			}
2737 		}
2738 	}
2739 
2740 	/// Post event to queue. It is safe to call this from non-UI threads.
2741 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
2742 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
2743 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
2744 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
2745 		if (this.closed) return false; // closed windows can't handle events
2746 
2747 		// remove all events of type `ET`
2748 		void removeAllET () {
2749 			uint eidx = 0, ec = eventQueueUsed;
2750 			auto eptr = eventQueue.ptr;
2751 			while (eidx < ec) {
2752 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
2753 				if (cast(ET)eptr.evt !is null) {
2754 					// i found her!
2755 					if (inCustomEventProcessor) {
2756 						// if we're in custom event processing loop, processor will clear it for us
2757 						eptr.evt = null;
2758 						++eidx;
2759 						++eptr;
2760 					} else {
2761 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2762 						ec = --eventQueueUsed;
2763 						// clear last event (it is already copied)
2764 						eventQueue.ptr[ec].evt = null;
2765 					}
2766 				} else {
2767 					++eidx;
2768 					++eptr;
2769 				}
2770 			}
2771 		}
2772 
2773 		if (evt is null) {
2774 			if (replace) { synchronized(this) removeAllET(); }
2775 			// ignore empty events, they can't be handled anyway
2776 			return false;
2777 		}
2778 
2779 		// add events even if no event FD/event object created yet
2780 		synchronized(this) {
2781 			if (replace) removeAllET();
2782 			if (eventQueueUsed == uint.max) return false; // just in case
2783 			if (eventQueueUsed < eventQueue.length) {
2784 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
2785 			} else {
2786 				if (eventQueue.capacity == eventQueue.length) {
2787 					// need to reallocate; do a trick to ensure that old array is cleared
2788 					auto oarr = eventQueue;
2789 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
2790 					// just in case, do yet another check
2791 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
2792 					import core.memory : GC;
2793 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
2794 				} else {
2795 					auto optr = eventQueue.ptr;
2796 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
2797 					assert(eventQueue.ptr is optr);
2798 				}
2799 				++eventQueueUsed;
2800 				assert(eventQueueUsed == eventQueue.length);
2801 			}
2802 			if (!eventWakeUp()) {
2803 				// can't wake up event processor, so there is no reason to keep the event
2804 				assert(eventQueueUsed > 0);
2805 				eventQueue[--eventQueueUsed].evt = null;
2806 				return false;
2807 			}
2808 			return true;
2809 		}
2810 	}
2811 
2812 	/// Post event to queue. It is safe to call this from non-UI threads.
2813 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
2814 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
2815 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
2816 		return postTimeout!ET(evt, 0, replace);
2817 	}
2818 
2819 private:
2820 	private import core.time : MonoTime;
2821 
2822 	version(Posix) {
2823 		__gshared int customEventFDRead = -1;
2824 		__gshared int customEventFDWrite = -1;
2825 		__gshared int customSignalFD = -1;
2826 	} else version(Windows) {
2827 		__gshared HANDLE customEventH = null;
2828 	}
2829 
2830 	// wake up event processor
2831 	static bool eventWakeUp () {
2832 		version(X11) {
2833 			import core.sys.posix.unistd : write;
2834 			ulong n = 1;
2835 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
2836 			return true;
2837 		} else version(Windows) {
2838 			if (customEventH !is null) SetEvent(customEventH);
2839 			return true;
2840 		} else {
2841 			// not implemented for other OSes
2842 			return false;
2843 		}
2844 	}
2845 
2846 	static struct QueuedEvent {
2847 		Object evt;
2848 		bool timed = false;
2849 		MonoTime hittime = MonoTime.zero;
2850 		bool doProcess = false; // process event at the current iteration (internal flag)
2851 
2852 		this (Object aevt, uint toutmsecs) {
2853 			evt = aevt;
2854 			if (toutmsecs > 0) {
2855 				import core.time : msecs;
2856 				timed = true;
2857 				hittime = MonoTime.currTime+toutmsecs.msecs;
2858 			}
2859 		}
2860 	}
2861 
2862 	alias CustomEventHandler = bool delegate (Object o) nothrow;
2863 	static struct EventHandlerEntry {
2864 		CustomEventHandler dg;
2865 		uint id;
2866 	}
2867 
2868 	uint lastUsedHandlerId;
2869 	EventHandlerEntry[] eventHandlers;
2870 	QueuedEvent[] eventQueue = null;
2871 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
2872 	bool inCustomEventProcessor = false; // required to properly remove events
2873 
2874 	// process queued events and call custom event handlers
2875 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
2876 	void processCustomEvents () {
2877 		bool hasSomethingToDo = false;
2878 		uint ecount;
2879 		bool ocep;
2880 		synchronized(this) {
2881 			ocep = inCustomEventProcessor;
2882 			inCustomEventProcessor = true;
2883 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
2884 			auto ctt = MonoTime.currTime;
2885 			bool hasEmpty = false;
2886 			// mark events to process (this is required for `eventQueued()`)
2887 			foreach (ref qe; eventQueue[0..ecount]) {
2888 				if (qe.evt is null) { hasEmpty = true; continue; }
2889 				if (qe.timed) {
2890 					qe.doProcess = (qe.hittime <= ctt);
2891 				} else {
2892 					qe.doProcess = true;
2893 				}
2894 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
2895 			}
2896 			if (!hasSomethingToDo) {
2897 				// remove empty events
2898 				if (hasEmpty) {
2899 					uint eidx = 0, ec = eventQueueUsed;
2900 					auto eptr = eventQueue.ptr;
2901 					while (eidx < ec) {
2902 						if (eptr.evt is null) {
2903 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2904 							ec = --eventQueueUsed;
2905 							eventQueue.ptr[ec].evt = null; // make GC life easier
2906 						} else {
2907 							++eidx;
2908 							++eptr;
2909 						}
2910 					}
2911 				}
2912 				inCustomEventProcessor = ocep;
2913 				return;
2914 			}
2915 		}
2916 		// process marked events
2917 		uint efree = 0; // non-processed events will be put at this index
2918 		EventHandlerEntry[] eh;
2919 		Object evt;
2920 		foreach (immutable eidx; 0..ecount) {
2921 			synchronized(this) {
2922 				if (!eventQueue[eidx].doProcess) {
2923 					// skip this event
2924 					assert(efree <= eidx);
2925 					if (efree != eidx) {
2926 						// copy this event to queue start
2927 						eventQueue[efree] = eventQueue[eidx];
2928 						eventQueue[eidx].evt = null; // just in case
2929 					}
2930 					++efree;
2931 					continue;
2932 				}
2933 				evt = eventQueue[eidx].evt;
2934 				eventQueue[eidx].evt = null; // in case event handler will hit GC
2935 				if (evt is null) continue; // just in case
2936 				// try all handlers; this can be slow, but meh...
2937 				eh = eventHandlers;
2938 			}
2939 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
2940 			evt = null;
2941 			eh = null;
2942 		}
2943 		synchronized(this) {
2944 			// move all unprocessed events to queue top; efree holds first "free index"
2945 			foreach (immutable eidx; ecount..eventQueueUsed) {
2946 				assert(efree <= eidx);
2947 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
2948 				++efree;
2949 			}
2950 			eventQueueUsed = efree;
2951 			// wake up event processor on next event loop iteration if we have more queued events
2952 			// also, remove empty events
2953 			bool awaken = false;
2954 			uint eidx = 0, ec = eventQueueUsed;
2955 			auto eptr = eventQueue.ptr;
2956 			while (eidx < ec) {
2957 				if (eptr.evt is null) {
2958 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2959 					ec = --eventQueueUsed;
2960 					eventQueue.ptr[ec].evt = null; // make GC life easier
2961 				} else {
2962 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
2963 					++eidx;
2964 					++eptr;
2965 				}
2966 			}
2967 			inCustomEventProcessor = ocep;
2968 		}
2969 	}
2970 
2971 	// for all windows in nativeMapping
2972 	static void processAllCustomEvents () {
2973 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
2974 			if (sw is null || sw.closed) continue;
2975 			sw.processCustomEvents();
2976 		}
2977 
2978 		runPendingRunInGuiThreadDelegates();
2979 	}
2980 
2981 	// 0: infinite (i.e. no scheduled events in queue)
2982 	uint eventQueueTimeoutMSecs () {
2983 		synchronized(this) {
2984 			if (eventQueueUsed == 0) return 0;
2985 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
2986 			uint res = int.max;
2987 			auto ctt = MonoTime.currTime;
2988 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
2989 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
2990 				if (qe.doProcess) continue; // just in case
2991 				if (!qe.timed) return 1; // minimal
2992 				if (qe.hittime <= ctt) return 1; // minimal
2993 				auto tms = (qe.hittime-ctt).total!"msecs";
2994 				if (tms < 1) tms = 1; // safety net
2995 				if (tms >= int.max) tms = int.max-1; // and another safety net
2996 				if (res > tms) res = cast(uint)tms;
2997 			}
2998 			return (res >= int.max ? 0 : res);
2999 		}
3000 	}
3001 
3002 	// for all windows in nativeMapping
3003 	static uint eventAllQueueTimeoutMSecs () {
3004 		uint res = uint.max;
3005 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3006 			if (sw is null || sw.closed) continue;
3007 			uint to = sw.eventQueueTimeoutMSecs();
3008 			if (to && to < res) {
3009 				res = to;
3010 				if (to == 1) break; // can't have less than this
3011 			}
3012 		}
3013 		return (res >= int.max ? 0 : res);
3014 	}
3015 }
3016 
3017 /* Drag and drop support { */
3018 version(X11) {
3019 
3020 } else version(Windows) {
3021 	import core.sys.windows.uuid;
3022 	import core.sys.windows.ole2;
3023 	import core.sys.windows.oleidl;
3024 	import core.sys.windows.objidl;
3025 	import core.sys.windows.wtypes;
3026 
3027 	pragma(lib, "ole32");
3028 	void initDnd() {
3029 		auto err = OleInitialize(null);
3030 		if(err != S_OK && err != S_FALSE)
3031 			throw new Exception("init");//err);
3032 	}
3033 }
3034 /* } End drag and drop support */
3035 
3036 
3037 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3038 /// See [GenericCursor]
3039 class MouseCursor {
3040 	int osId;
3041 	bool isStockCursor;
3042 	private this(int osId) {
3043 		this.osId = osId;
3044 		this.isStockCursor = true;
3045 	}
3046 
3047 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3048 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3049 
3050 	version(Windows) {
3051 		HCURSOR cursor_;
3052 		HCURSOR cursorHandle() {
3053 			if(cursor_ is null)
3054 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3055 			return cursor_;
3056 		}
3057 
3058 	} else static if(UsingSimpledisplayX11) {
3059 		Cursor cursor_ = None;
3060 		int xDisplaySequence;
3061 
3062 		Cursor cursorHandle() {
3063 			if(this.osId == None)
3064 				return None;
3065 
3066 			// we need to reload if we on a new X connection
3067 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3068 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3069 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3070 			}
3071 			return cursor_;
3072 		}
3073 	}
3074 }
3075 
3076 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3077 // https://tronche.com/gui/x/xlib/appendix/b/
3078 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3079 /// 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
3080 enum GenericCursorType {
3081 	Default, /// The default arrow pointer.
3082 	Wait, /// A cursor indicating something is loading and the user must wait.
3083 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3084 	Help, /// A cursor indicating the user can get help about the pointer location.
3085 	Cross, /// A crosshair.
3086 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3087 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll
3088 	UpArrow, /// An arrow pointing straight up.
3089 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3090 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3091 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator)
3092 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator)
3093 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator)
3094 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator)
3095 
3096 }
3097 
3098 /*
3099 	X_plus == css cell == Windows ?
3100 */
3101 
3102 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3103 static struct GenericCursor {
3104 	static:
3105 	///
3106 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3107 		static MouseCursor mc;
3108 
3109 		auto type = __traits(getMember, GenericCursorType, str);
3110 
3111 		if(mc is null) {
3112 
3113 			version(Windows) {
3114 				int osId;
3115 				final switch(type) {
3116 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3117 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3118 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3119 					case GenericCursorType.Help: osId = IDC_HELP; break;
3120 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3121 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3122 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3123 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3124 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3125 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3126 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3127 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3128 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3129 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3130 				}
3131 			} else static if(UsingSimpledisplayX11) {
3132 				int osId;
3133 				final switch(type) {
3134 					case GenericCursorType.Default: osId = None; break;
3135 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3136 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3137 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3138 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3139 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3140 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3141 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3142 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3143 
3144 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3145 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3146 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3147 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3148 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3149 				}
3150 
3151 			} else featureNotImplemented();
3152 
3153 			mc = new MouseCursor(osId);
3154 		}
3155 		return mc;
3156 	}
3157 }
3158 
3159 
3160 /++
3161 	If you want to get more control over the event loop, you can use this.
3162 
3163 	Typically though, you can just call [SimpleWindow.eventLoop].
3164 +/
3165 struct EventLoop {
3166 	@disable this();
3167 
3168 	/// Gets a reference to an existing event loop
3169 	static EventLoop get() {
3170 		return EventLoop(0, null);
3171 	}
3172 
3173 	__gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3174 
3175 	/// Construct an application-global event loop for yourself
3176 	/// See_Also: [SimpleWindow.setEventHandlers]
3177 	this(long pulseTimeout, void delegate() handlePulse) {
3178 		synchronized(monitor) {
3179 			if(impl is null) {
3180 				claimGuiThread();
3181 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3182 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
3183 			} else {
3184 				if(pulseTimeout) {
3185 					impl.pulseTimeout = pulseTimeout;
3186 					impl.handlePulse = handlePulse;
3187 				}
3188 			}
3189 			impl.refcount++;
3190 		}
3191 	}
3192 
3193 	~this() {
3194 		if(impl is null)
3195 			return;
3196 		impl.refcount--;
3197 		if(impl.refcount == 0) {
3198 			impl.dispose();
3199 			if(thisIsGuiThread)
3200 				guiThreadFinalize();
3201 		}
3202 
3203 	}
3204 
3205 	this(this) {
3206 		if(impl is null)
3207 			return;
3208 		impl.refcount++;
3209 	}
3210 
3211 	/// Runs the event loop until the whileCondition, if present, returns false
3212 	int run(bool delegate() whileCondition = null) {
3213 		assert(impl !is null);
3214 		impl.notExited = true;
3215 		return impl.run(whileCondition);
3216 	}
3217 
3218 	/// Exits the event loop
3219 	void exit() {
3220 		assert(impl !is null);
3221 		impl.notExited = false;
3222 	}
3223 
3224 	version(linux)
3225 	ref void delegate(int) signalHandler() {
3226 		assert(impl !is null);
3227 		return impl.signalHandler;
3228 	}
3229 
3230 	__gshared static EventLoopImpl* impl;
3231 }
3232 
3233 version(linux)
3234 	void delegate(int, int) globalHupHandler;
3235 
3236 version(Posix)
3237 	void makeNonBlocking(int fd) {
3238 		import fcntl = core.sys.posix.fcntl;
3239 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
3240 		if(flags == -1)
3241 			throw new Exception("fcntl get");
3242 		flags |= fcntl.O_NONBLOCK;
3243 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
3244 		if(s == -1)
3245 			throw new Exception("fcntl set");
3246 	}
3247 
3248 struct EventLoopImpl {
3249 	int refcount;
3250 
3251 	bool notExited = true;
3252 
3253 	version(linux) {
3254 		static import ep = core.sys.linux.epoll;
3255 		static import unix = core.sys.posix.unistd;
3256 		static import err = core.stdc.errno;
3257 		import core.sys.linux.timerfd;
3258 
3259 		void delegate(int) signalHandler;
3260 	}
3261 
3262 	version(X11) {
3263 		int pulseFd = -1;
3264 		version(linux) ep.epoll_event[16] events = void;
3265 	} else version(Windows) {
3266 		Timer pulser;
3267 		HANDLE[] handles;
3268 	}
3269 
3270 
3271 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3272 	/// to call this, as it's not recommended to share window between threads.
3273 	void mtLock () {
3274 		version(X11) {
3275 			XLockDisplay(this.display);
3276 		}
3277 	}
3278 
3279 	version(X11)
3280 	auto display() { return XDisplayConnection.get; }
3281 
3282 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3283 	/// to call this, as it's not recommended to share window between threads.
3284 	void mtUnlock () {
3285 		version(X11) {
3286 			XUnlockDisplay(this.display);
3287 		}
3288 	}
3289 
3290 	version(with_eventloop)
3291 	void initialize(long pulseTimeout) {}
3292 	else
3293 	void initialize(long pulseTimeout) {
3294 		version(Windows) {
3295 			if(pulseTimeout && handlePulse !is null)
3296 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
3297 
3298 			if (customEventH is null) {
3299 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
3300 				if (customEventH !is null) {
3301 					handles ~= customEventH;
3302 				} else {
3303 					// this is something that should not be; better be safe than sorry
3304 					throw new Exception("can't create eventfd for custom event processing");
3305 				}
3306 			}
3307 
3308 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
3309 		}
3310 
3311 		version(linux) {
3312 			prepareEventLoop();
3313 			{
3314 				auto display = XDisplayConnection.get;
3315 				// adding Xlib file
3316 				ep.epoll_event ev = void;
3317 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3318 				ev.events = ep.EPOLLIN;
3319 				ev.data.fd = display.fd;
3320 				//import std.conv;
3321 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
3322 					throw new Exception("add x fd");// ~ to!string(epollFd));
3323 				displayFd = display.fd;
3324 			}
3325 
3326 			if(pulseTimeout && handlePulse !is null) {
3327 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
3328 				if(pulseFd == -1)
3329 					throw new Exception("pulse timer create failed");
3330 
3331 				itimerspec value;
3332 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
3333 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3334 
3335 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
3336 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3337 
3338 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
3339 					throw new Exception("couldn't make pulse timer");
3340 
3341 				ep.epoll_event ev = void;
3342 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3343 				ev.events = ep.EPOLLIN;
3344 				ev.data.fd = pulseFd;
3345 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
3346 			}
3347 
3348 			// eventfd for custom events
3349 			if (customEventFDWrite == -1) {
3350 				customEventFDWrite = eventfd(0, 0);
3351 				customEventFDRead = customEventFDWrite;
3352 				if (customEventFDRead >= 0) {
3353 					ep.epoll_event ev = void;
3354 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3355 					ev.events = ep.EPOLLIN;
3356 					ev.data.fd = customEventFDRead;
3357 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
3358 				} else {
3359 					// this is something that should not be; better be safe than sorry
3360 					throw new Exception("can't create eventfd for custom event processing");
3361 				}
3362 			}
3363 
3364 			if (customSignalFD == -1) {
3365 				import core.sys.linux.sys.signalfd;
3366 
3367 				sigset_t sigset;
3368 				auto err = sigemptyset(&sigset);
3369 				assert(!err);
3370 				err = sigaddset(&sigset, SIGINT);
3371 				assert(!err);
3372 				err = sigaddset(&sigset, SIGHUP);
3373 				assert(!err);
3374 				err = sigprocmask(SIG_BLOCK, &sigset, null);
3375 				assert(!err);
3376 
3377 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
3378 				assert(customSignalFD != -1);
3379 
3380 				ep.epoll_event ev = void;
3381 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3382 				ev.events = ep.EPOLLIN;
3383 				ev.data.fd = customSignalFD;
3384 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
3385 			}
3386 		} else version(Posix) {
3387 			prepareEventLoop();
3388 			if (customEventFDRead == -1) {
3389 				int[2] bfr;
3390 				import core.sys.posix.unistd;
3391 				auto ret = pipe(bfr);
3392 				if(ret == -1) throw new Exception("pipe");
3393 				customEventFDRead = bfr[0];
3394 				customEventFDWrite = bfr[1];
3395 			}
3396 
3397 		}
3398 
3399 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
3400 
3401 		version(linux) {
3402 			this.mtLock();
3403 			scope(exit) this.mtUnlock();
3404 			XPending(display); // no, really
3405 		}
3406 
3407 		disposed = false;
3408 	}
3409 
3410 	bool disposed = true;
3411 	version(X11)
3412 		int displayFd = -1;
3413 
3414 	version(with_eventloop)
3415 	void dispose() {}
3416 	else
3417 	void dispose() {
3418 		disposed = true;
3419 		version(X11) {
3420 			if(pulseFd != -1) {
3421 				import unix = core.sys.posix.unistd;
3422 				unix.close(pulseFd);
3423 				pulseFd = -1;
3424 			}
3425 
3426 				version(linux)
3427 				if(displayFd != -1) {
3428 					// 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
3429 					ep.epoll_event ev = void;
3430 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3431 					ev.events = ep.EPOLLIN;
3432 					ev.data.fd = displayFd;
3433 					//import std.conv;
3434 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
3435 					displayFd = -1;
3436 				}
3437 
3438 		} else version(Windows) {
3439 			if(pulser !is null) {
3440 				pulser.destroy();
3441 				pulser = null;
3442 			}
3443 			if (customEventH !is null) {
3444 				CloseHandle(customEventH);
3445 				customEventH = null;
3446 			}
3447 		}
3448 	}
3449 
3450 	this(long pulseTimeout, void delegate() handlePulse) {
3451 		this.pulseTimeout = pulseTimeout;
3452 		this.handlePulse = handlePulse;
3453 		initialize(pulseTimeout);
3454 	}
3455 
3456 	private long pulseTimeout;
3457 	void delegate() handlePulse;
3458 
3459 	~this() {
3460 		dispose();
3461 	}
3462 
3463 	version(Posix)
3464 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
3465 	version(Posix)
3466 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
3467 	version(linux)
3468 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
3469 	version(Windows)
3470 	ref auto customEventH() { return SimpleWindow.customEventH; }
3471 
3472 	version(with_eventloop) {
3473 		int loopHelper(bool delegate() whileCondition) {
3474 			// FIXME: whileCondition
3475 			import arsd.eventloop;
3476 			loop();
3477 			return 0;
3478 		}
3479 	} else
3480 	int loopHelper(bool delegate() whileCondition) {
3481 		version(X11) {
3482 			bool done = false;
3483 
3484 			XFlush(display);
3485 			insideXEventLoop = true;
3486 			scope(exit) insideXEventLoop = false;
3487 
3488 			version(linux) {
3489 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
3490 					bool forceXPending = false;
3491 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
3492 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
3493 					{
3494 						this.mtLock();
3495 						scope(exit) this.mtUnlock();
3496 						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
3497 					}
3498 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
3499 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
3500 					if(nfds == -1) {
3501 						if(err.errno == err.EINTR) {
3502 							//if(forceXPending) goto xpending;
3503 							continue; // interrupted by signal, just try again
3504 						}
3505 						throw new Exception("epoll wait failure");
3506 					}
3507 
3508 					SimpleWindow.processAllCustomEvents(); // anyway
3509 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
3510 					foreach(idx; 0 .. nfds) {
3511 						if(done) break;
3512 						auto fd = events[idx].data.fd;
3513 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
3514 						auto flags = events[idx].events;
3515 						if(flags & ep.EPOLLIN) {
3516 							if (fd == customSignalFD) {
3517 								version(linux) {
3518 									import core.sys.linux.sys.signalfd;
3519 									import core.sys.posix.unistd : read;
3520 									signalfd_siginfo info;
3521 									read(customSignalFD, &info, info.sizeof);
3522 
3523 									auto sig = info.ssi_signo;
3524 
3525 									if(EventLoop.get.signalHandler !is null) {
3526 										EventLoop.get.signalHandler()(sig);
3527 									} else {
3528 										EventLoop.get.exit();
3529 									}
3530 								}
3531 							} else if(fd == display.fd) {
3532 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
3533 								this.mtLock();
3534 								scope(exit) this.mtUnlock();
3535 								while(!done && XPending(display)) {
3536 									done = doXNextEvent(this.display);
3537 								}
3538 								forceXPending = false;
3539 							} else if(fd == pulseFd) {
3540 								long expirationCount;
3541 								// 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...
3542 
3543 								handlePulse();
3544 
3545 								// read just to clear the buffer so poll doesn't trigger again
3546 								// BTW I read AFTER the pulse because if the pulse handler takes
3547 								// a lot of time to execute, we don't want the app to get stuck
3548 								// in a loop of timer hits without a chance to do anything else
3549 								//
3550 								// IOW handlePulse happens at most once per pulse interval.
3551 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
3552 								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
3553 							} else if (fd == customEventFDRead) {
3554 								// we have some custom events; process 'em
3555 								import core.sys.posix.unistd : read;
3556 								ulong n;
3557 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
3558 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
3559 								//SimpleWindow.processAllCustomEvents();
3560 							} else {
3561 								// some other timer
3562 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
3563 
3564 								if(Timer* t = fd in Timer.mapping)
3565 									(*t).trigger();
3566 
3567 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3568 									(*pfr).ready(flags);
3569 
3570 								// or i might add support for other FDs too
3571 								// but for now it is just timer
3572 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
3573 							}
3574 						}
3575 						if(flags & ep.EPOLLHUP) {
3576 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3577 								(*pfr).hup(flags);
3578 							if(globalHupHandler)
3579 								globalHupHandler(fd, flags);
3580 						}
3581 						/+
3582 						} else {
3583 							// not interested in OUT, we are just reading here.
3584 							//
3585 							// error or hup might also be reported
3586 							// but it shouldn't here since we are only
3587 							// using a few types of FD and Xlib will report
3588 							// if it dies.
3589 							// so instead of thoughtfully handling it, I'll
3590 							// just throw. for now at least
3591 
3592 							throw new Exception("epoll did something else");
3593 						}
3594 						+/
3595 					}
3596 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
3597 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
3598 					xpending:
3599 					if (!done && forceXPending) {
3600 						this.mtLock();
3601 						scope(exit) this.mtUnlock();
3602 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
3603 						while(!done && XPending(display)) {
3604 							done = doXNextEvent(this.display);
3605 						}
3606 					}
3607 				}
3608 			} else {
3609 				// Generic fallback: yes to simple pulse support,
3610 				// but NO timer support!
3611 
3612 				// FIXME: we could probably support the POSIX timer_create
3613 				// signal-based option, but I'm in no rush to write it since
3614 				// I prefer the fd-based functions.
3615 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
3616 
3617 					import core.sys.posix.poll;
3618 
3619 					pollfd[] pfds;
3620 					pollfd[32] pfdsBuffer;
3621 					auto len = PosixFdReader.mapping.length + 2;
3622 					// FIXME: i should just reuse the buffer
3623 					if(len < pfdsBuffer.length)
3624 						pfds = pfdsBuffer[0 .. len];
3625 					else
3626 						pfds = new pollfd[](len);
3627 
3628 					pfds[0].fd = display.fd;
3629 					pfds[0].events = POLLIN;
3630 					pfds[0].revents = 0;
3631 
3632 					int slot = 1;
3633 
3634 					if(customEventFDRead != -1) {
3635 						pfds[slot].fd = customEventFDRead;
3636 						pfds[slot].events = POLLIN;
3637 						pfds[slot].revents = 0;
3638 
3639 						slot++;
3640 					}
3641 
3642 					foreach(fd, obj; PosixFdReader.mapping) {
3643 						if(!obj.enabled) continue;
3644 						pfds[slot].fd = fd;
3645 						pfds[slot].events = POLLIN;
3646 						pfds[slot].revents = 0;
3647 
3648 						slot++;
3649 					}
3650 
3651 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
3652 					if(ret == -1) throw new Exception("poll");
3653 
3654 					if(ret == 0) {
3655 						// FIXME it may not necessarily time out if events keep coming
3656 						if(handlePulse !is null)
3657 							handlePulse();
3658 					} else {
3659 						foreach(s; 0 .. slot) {
3660 							if(pfds[s].revents == 0) continue;
3661 
3662 							if(pfds[s].fd == display.fd) {
3663 								while(!done && XPending(display)) {
3664 									this.mtLock();
3665 									scope(exit) this.mtUnlock();
3666 									done = doXNextEvent(this.display);
3667 								}
3668 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
3669 
3670 								import core.sys.posix.unistd : read;
3671 								ulong n;
3672 								read(customEventFDRead, &n, n.sizeof);
3673 								SimpleWindow.processAllCustomEvents();
3674 							} else {
3675 								auto obj = PosixFdReader.mapping[pfds[s].fd];
3676 								if(pfds[s].revents & POLLNVAL) {
3677 									obj.dispose();
3678 								} else {
3679 									obj.ready(pfds[s].revents);
3680 								}
3681 							}
3682 
3683 							ret--;
3684 							if(ret == 0) break;
3685 						}
3686 					}
3687 				}
3688 			}
3689 		}
3690 		
3691 		version(Windows) {
3692 			int ret = -1;
3693 			MSG message;
3694 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
3695 				eventLoopRound++;
3696 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
3697 				auto waitResult = MsgWaitForMultipleObjectsEx(
3698 					cast(int) handles.length, handles.ptr,
3699 					(wto == 0 ? INFINITE : wto), /* timeout */
3700 					0x04FF, /* QS_ALLINPUT */
3701 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
3702 
3703 				SimpleWindow.processAllCustomEvents(); // anyway
3704 				enum WAIT_OBJECT_0 = 0;
3705 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
3706 					auto h = handles[waitResult - WAIT_OBJECT_0];
3707 					if(auto e = h in WindowsHandleReader.mapping) {
3708 						(*e).ready();
3709 					}
3710 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
3711 					// message ready
3712 					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
3713 						ret = GetMessage(&message, null, 0, 0);
3714 						if(ret == -1)
3715 							throw new Exception("GetMessage failed");
3716 						TranslateMessage(&message);
3717 						DispatchMessage(&message);
3718 
3719 						if(ret == 0) // WM_QUIT
3720 							break;
3721 					}
3722 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
3723 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
3724 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
3725 					// timeout, should never happen since we aren't using it
3726 				} else if(waitResult == 0xFFFFFFFF) {
3727 						// failed
3728 						throw new Exception("MsgWaitForMultipleObjectsEx failed");
3729 				} else {
3730 					// idk....
3731 				}
3732 			}
3733 
3734 			// return message.wParam;
3735 			return 0;
3736 		} else {
3737 			return 0;
3738 		}
3739 	}
3740 
3741 	int run(bool delegate() whileCondition = null) {
3742 		if(disposed)
3743 			initialize(this.pulseTimeout);
3744 
3745 		version(X11) {
3746 			try {
3747 				return loopHelper(whileCondition);
3748 			} catch(XDisconnectException e) {
3749 				if(e.userRequested) {
3750 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
3751 						item.discardConnectionState();
3752 					XCloseDisplay(XDisplayConnection.display);
3753 				}
3754 
3755 				XDisplayConnection.display = null;
3756 
3757 				this.dispose();
3758 
3759 				throw e;
3760 			}
3761 		} else {
3762 			return loopHelper(whileCondition);
3763 		}
3764 	}
3765 }
3766 
3767 
3768 /++
3769 	Provides an icon on the system notification area (also known as the system tray).
3770 
3771 
3772 	If a notification area is not available with the NotificationIcon object is created,
3773 	it will silently succeed and simply attempt to create one when an area becomes available.
3774 
3775 
3776 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
3777 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
3778 	use the older version.
3779 +/
3780 version(OSXCocoa) {} else // NotYetImplementedException
3781 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
3782 
3783 	version(X11) {
3784 		void recreateAfterDisconnect() {
3785 			stateDiscarded = false;
3786 			clippixmap = None;
3787 			throw new Exception("NOT IMPLEMENTED");
3788 		}
3789 
3790 		bool stateDiscarded;
3791 		void discardConnectionState() {
3792 			stateDiscarded = true;
3793 		}
3794 	}
3795 
3796 
3797 	version(X11) {
3798 		Image img;
3799 
3800 		NativeEventHandler getNativeEventHandler() {
3801 			return delegate int(XEvent e) {
3802 				switch(e.type) {
3803 					case EventType.Expose:
3804 					//case EventType.VisibilityNotify:
3805 						redraw();
3806 					break;
3807 					case EventType.ClientMessage:
3808 						version(sddddd) {
3809 						import std.stdio;
3810 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
3811 						writeln("\t", e.xclient.format);
3812 						writeln("\t", e.xclient.data.l);
3813 						}
3814 					break;
3815 					case EventType.ButtonPress:
3816 						auto event = e.xbutton;
3817 						if (onClick !is null || onClickEx !is null) {
3818 							MouseButton mb = cast(MouseButton)0;
3819 							switch (event.button) {
3820 								case 1: mb = MouseButton.left; break; // left
3821 								case 2: mb = MouseButton.middle; break; // middle
3822 								case 3: mb = MouseButton.right; break; // right
3823 								case 4: mb = MouseButton.wheelUp; break; // scroll up
3824 								case 5: mb = MouseButton.wheelDown; break; // scroll down
3825 								case 6: break; // idk
3826 								case 7: break; // idk
3827 								case 8: mb = MouseButton.backButton; break;
3828 								case 9: mb = MouseButton.forwardButton; break;
3829 								default:
3830 							}
3831 							if (mb) {
3832 								try { onClick()(mb); } catch (Exception) {}
3833 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
3834 							}
3835 						}
3836 					break;
3837 					case EventType.EnterNotify:
3838 						if (onEnter !is null) {
3839 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
3840 						}
3841 						break;
3842 					case EventType.LeaveNotify:
3843 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
3844 						break;
3845 					case EventType.DestroyNotify:
3846 						active = false;
3847 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
3848 					break;
3849 					case EventType.ConfigureNotify:
3850 						auto event = e.xconfigure;
3851 						this.width = event.width;
3852 						this.height = event.height;
3853 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
3854 						redraw();
3855 					break;
3856 					default: return 1;
3857 				}
3858 				return 1;
3859 			};
3860 		}
3861 
3862 		/* private */ void hideBalloon() {
3863 			balloon.close();
3864 			version(with_timer)
3865 				timer.destroy();
3866 			balloon = null;
3867 			version(with_timer)
3868 				timer = null;
3869 		}
3870 
3871 		void redraw() {
3872 			if (!active) return;
3873 
3874 			auto display = XDisplayConnection.get;
3875 			auto gc = DefaultGC(display, DefaultScreen(display));
3876 			XClearWindow(display, nativeHandle);
3877 
3878 			XSetClipMask(display, gc, clippixmap);
3879 
3880 			XSetForeground(display, gc,
3881 				cast(uint) 0 << 16 |
3882 				cast(uint) 0 << 8 |
3883 				cast(uint) 0);
3884 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
3885 
3886 			if (img is null) {
3887 				XSetForeground(display, gc,
3888 					cast(uint) 0 << 16 |
3889 					cast(uint) 127 << 8 |
3890 					cast(uint) 0);
3891 				XFillArc(display, nativeHandle,
3892 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
3893 			} else {
3894 				int dx = 0;
3895 				int dy = 0;
3896 				if(width > img.width)
3897 					dx = (width - img.width) / 2;
3898 				if(height > img.height)
3899 					dy = (height - img.height) / 2;
3900 				XSetClipOrigin(display, gc, dx, dy);
3901 
3902 				if (img.usingXshm)
3903 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
3904 				else
3905 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
3906 			}
3907 			XSetClipMask(display, gc, None);
3908 			flushGui();
3909 		}
3910 
3911 		static Window getTrayOwner() {
3912 			auto display = XDisplayConnection.get;
3913 			auto i = cast(int) DefaultScreen(display);
3914 			if(i < 10 && i >= 0) {
3915 				static Atom atom;
3916 				if(atom == None)
3917 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
3918 				return XGetSelectionOwner(display, atom);
3919 			}
3920 			return None;
3921 		}
3922 
3923 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
3924 			auto to = getTrayOwner();
3925 			auto display = XDisplayConnection.get;
3926 			XEvent ev;
3927 			ev.xclient.type = EventType.ClientMessage;
3928 			ev.xclient.window = to;
3929 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
3930 			ev.xclient.format = 32;
3931 			ev.xclient.data.l[0] = CurrentTime;
3932 			ev.xclient.data.l[1] = message;
3933 			ev.xclient.data.l[2] = d1;
3934 			ev.xclient.data.l[3] = d2;
3935 			ev.xclient.data.l[4] = d3;
3936 
3937 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
3938 		}
3939 
3940 		private static NotificationAreaIcon[] activeIcons;
3941 
3942 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
3943 		private void newManager() {
3944 			close();
3945 			createXWin();
3946 
3947 			if(this.clippixmap)
3948 				XFreePixmap(XDisplayConnection.get, clippixmap);
3949 			if(this.originalMemoryImage)
3950 				this.icon = this.originalMemoryImage;
3951 			else if(this.img)
3952 				this.icon = this.img;
3953 		}
3954 
3955 		private void createXWin () {
3956 			// create window
3957 			auto display = XDisplayConnection.get;
3958 
3959 			// to check for MANAGER on root window to catch new/changed tray owners
3960 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
3961 			// so if a thing does appear, we can handle it
3962 			foreach(ai; activeIcons)
3963 				if(ai is this)
3964 					goto alreadythere;
3965 			activeIcons ~= this;
3966 			alreadythere:
3967 
3968 			// and check for an existing tray
3969 			auto trayOwner = getTrayOwner();
3970 			if(trayOwner == None)
3971 				return;
3972 				//throw new Exception("No notification area found");
3973 
3974 			Visual* v = cast(Visual*) CopyFromParent;
3975 			/+
3976 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
3977 			if(visualProp !is null) {
3978 				c_ulong[] info = cast(c_ulong[]) visualProp;
3979 				if(info.length == 1) {
3980 					auto vid = info[0];
3981 					int returned;
3982 					XVisualInfo t;
3983 					t.visualid = vid;
3984 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
3985 					if(got !is null) {
3986 						if(returned == 1) {
3987 							v = got.visual;
3988 							import std.stdio;
3989 							writeln("using special visual ", *got);
3990 						}
3991 						XFree(got);
3992 					}
3993 				}
3994 			}
3995 			+/
3996 
3997 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
3998 			assert(nativeWindow);
3999 
4000 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4001 
4002 			nativeHandle = nativeWindow;
4003 
4004 			///+
4005 			arch_ulong[2] info;
4006 			info[0] = 0;
4007 			info[1] = 1;
4008 
4009 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4010 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4011 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4012 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4013 
4014 			XChangeProperty(
4015 				display,
4016 				nativeWindow,
4017 				GetAtom!("_XEMBED_INFO", true)(display),
4018 				GetAtom!("_XEMBED_INFO", true)(display),
4019 				32 /* bits */,
4020 				0 /*PropModeReplace*/,
4021 				info.ptr,
4022 				2);
4023 
4024 			import core.sys.posix.unistd;
4025 			arch_ulong pid = getpid();
4026 
4027 			XChangeProperty(
4028 				display,
4029 				nativeWindow,
4030 				GetAtom!("_NET_WM_PID", true)(display),
4031 				XA_CARDINAL,
4032 				32 /* bits */,
4033 				0 /*PropModeReplace*/,
4034 				&pid,
4035 				1);
4036 
4037 			updateNetWmIcon();
4038 
4039 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4040 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4041 				XClassHint klass;
4042 				XWMHints wh;
4043 				XSizeHints size;
4044 				klass.res_name = sdpyWindowClassStr;
4045 				klass.res_class = sdpyWindowClassStr;
4046 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4047 			}
4048 
4049 				// believe it or not, THIS is what xfce needed for the 9999 issue
4050 				XSizeHints sh;
4051 					c_long spr;
4052 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4053 					sh.flags |= PMaxSize | PMinSize;
4054 				// FIXME maybe nicer resizing
4055 				sh.min_width = 16;
4056 				sh.min_height = 16;
4057 				sh.max_width = 16;
4058 				sh.max_height = 16;
4059 				XSetWMNormalHints(display, nativeWindow, &sh);
4060 
4061 
4062 			//+/
4063 
4064 
4065 			XSelectInput(display, nativeWindow,
4066 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4067 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4068 
4069 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4070 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4071 			active = true;
4072 		}
4073 
4074 		void updateNetWmIcon() {
4075 			if(img is null) return;
4076 			auto display = XDisplayConnection.get;
4077 			// FIXME: ensure this is correct
4078 			arch_ulong[] buffer;
4079 			auto imgMi = img.toTrueColorImage;
4080 			buffer ~= imgMi.width;
4081 			buffer ~= imgMi.height;
4082 			foreach(c; imgMi.imageData.colors) {
4083 				arch_ulong b;
4084 				b |= c.a << 24;
4085 				b |= c.r << 16;
4086 				b |= c.g << 8;
4087 				b |= c.b;
4088 				buffer ~= b;
4089 			}
4090 
4091 			XChangeProperty(
4092 				display,
4093 				nativeHandle,
4094 				GetAtom!"_NET_WM_ICON"(display),
4095 				GetAtom!"CARDINAL"(display),
4096 				32 /* bits */,
4097 				0 /*PropModeReplace*/,
4098 				buffer.ptr,
4099 				cast(int) buffer.length);
4100 		}
4101 
4102 
4103 
4104 		private SimpleWindow balloon;
4105 		version(with_timer)
4106 		private Timer timer;
4107 
4108 		private Window nativeHandle;
4109 		private Pixmap clippixmap = None;
4110 		private int width = 16;
4111 		private int height = 16;
4112 		private bool active = false;
4113 
4114 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4115 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4116 		void delegate () onLeave; /// X11 only.
4117 
4118 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4119 
4120 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4121 		void getWindowRect (out int x, out int y, out int width, out int height) {
4122 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4123 			Window dummyw;
4124 			auto dpy = XDisplayConnection.get;
4125 			//XWindowAttributes xwa;
4126 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4127 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4128 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4129 			width = this.width;
4130 			height = this.height;
4131 		}
4132 	}
4133 
4134 	/+
4135 		What I actually want from this:
4136 
4137 		* set / change: icon, tooltip
4138 		* handle: mouse click, right click
4139 		* show: notification bubble.
4140 	+/
4141 
4142 	version(Windows) {
4143 		WindowsIcon win32Icon;
4144 		HWND hwnd;
4145 
4146 		NOTIFYICONDATAW data;
4147 
4148 		NativeEventHandler getNativeEventHandler() {
4149 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
4150 				if(msg == WM_USER) {
4151 					auto event = LOWORD(lParam);
4152 					auto iconId = HIWORD(lParam);
4153 					//auto x = GET_X_LPARAM(wParam);
4154 					//auto y = GET_Y_LPARAM(wParam);
4155 					switch(event) {
4156 						case WM_LBUTTONDOWN:
4157 							onClick()(MouseButton.left);
4158 						break;
4159 						case WM_RBUTTONDOWN:
4160 							onClick()(MouseButton.right);
4161 						break;
4162 						case WM_MBUTTONDOWN:
4163 							onClick()(MouseButton.middle);
4164 						break;
4165 						case WM_MOUSEMOVE:
4166 							// sent, we could use it.
4167 						break;
4168 						case WM_MOUSEWHEEL:
4169 							// NOT SENT
4170 						break;
4171 						//case NIN_KEYSELECT:
4172 						//case NIN_SELECT:
4173 						//break;
4174 						default: {}
4175 					}
4176 				}
4177 				return 0;
4178 			};
4179 		}
4180 
4181 		enum NIF_SHOWTIP = 0x00000080;
4182 
4183 		private static struct NOTIFYICONDATAW {
4184 			DWORD cbSize;
4185 			HWND  hWnd;
4186 			UINT  uID;
4187 			UINT  uFlags;
4188 			UINT  uCallbackMessage;
4189 			HICON hIcon;
4190 			WCHAR[128] szTip;
4191 			DWORD dwState;
4192 			DWORD dwStateMask;
4193 			WCHAR[256] szInfo;
4194 			union {
4195 				UINT uTimeout;
4196 				UINT uVersion;
4197 			}
4198 			WCHAR[64] szInfoTitle;
4199 			DWORD dwInfoFlags;
4200 			GUID  guidItem;
4201 			HICON hBalloonIcon;
4202 		}
4203 
4204 	}
4205 
4206 	/++
4207 		Note that on Windows, only left, right, and middle buttons are sent.
4208 		Mouse wheel buttons are NOT set, so don't rely on those events if your
4209 		program is meant to be used on Windows too.
4210 	+/
4211 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
4212 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
4213 		// but on X, we need an Image, so its canonical ctor is there. They should
4214 		// forward to each other though.
4215 		version(X11) {
4216 			this.name = name;
4217 			this.onClick = onClick;
4218 			createXWin();
4219 			this.icon = icon;
4220 		} else version(Windows) {
4221 			this.onClick = onClick;
4222 			this.win32Icon = new WindowsIcon(icon);
4223 
4224 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
4225 
4226 			static bool registered = false;
4227 			if(!registered) {
4228 				WNDCLASSEX wc;
4229 				wc.cbSize = wc.sizeof;
4230 				wc.hInstance = hInstance;
4231 				wc.lpfnWndProc = &WndProc;
4232 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
4233 				if(!RegisterClassExW(&wc))
4234 					throw new WindowsApiException("RegisterClass");
4235 				registered = true;
4236 			}
4237 
4238 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
4239 			if(hwnd is null)
4240 				throw new Exception("CreateWindow");
4241 
4242 			data.cbSize = data.sizeof;
4243 			data.hWnd = hwnd;
4244 			data.uID = cast(uint) cast(void*) this;
4245 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
4246 				// NIF_INFO means show balloon
4247 			data.uCallbackMessage = WM_USER;
4248 			data.hIcon = this.win32Icon.hIcon;
4249 			data.szTip = ""; // FIXME
4250 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4251 			data.dwStateMask = NIS_HIDDEN; // windows vista
4252 
4253 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
4254 
4255 
4256 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
4257 
4258 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
4259 		} else version(OSXCocoa) {
4260 			throw new NotYetImplementedException();
4261 		} else static assert(0);
4262 	}
4263 
4264 	/// ditto
4265 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
4266 		version(X11) {
4267 			this.onClick = onClick;
4268 			this.name = name;
4269 			createXWin();
4270 			this.icon = icon;
4271 		} else version(Windows) {
4272 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
4273 		} else version(OSXCocoa) {
4274 			throw new NotYetImplementedException();
4275 		} else static assert(0);
4276 	}
4277 
4278 	version(X11) {
4279 		/++
4280 			X-specific extension (for now at least)
4281 		+/
4282 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4283 			this.onClickEx = onClickEx;
4284 			createXWin();
4285 			if (icon !is null) this.icon = icon;
4286 		}
4287 
4288 		/// ditto
4289 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4290 			this.onClickEx = onClickEx;
4291 			createXWin();
4292 			this.icon = icon;
4293 		}
4294 	}
4295 
4296 	private void delegate (MouseButton button) onClick_;
4297 
4298 	///
4299 	@property final void delegate(MouseButton) onClick() {
4300 		if(onClick_ is null)
4301 			onClick_ = delegate void(MouseButton) {};
4302 		return onClick_;
4303 	}
4304 
4305 	/// ditto
4306 	@property final void onClick(void delegate(MouseButton) handler) {
4307 		// I made this a property setter so we can wrap smaller arg
4308 		// delegates and just forward all to onClickEx or something.
4309 		onClick_ = handler;
4310 	}
4311 
4312 
4313 	string name_;
4314 	@property void name(string n) {
4315 		name_ = n;
4316 	}
4317 
4318 	@property string name() {
4319 		return name_;
4320 	}
4321 
4322 	private MemoryImage originalMemoryImage;
4323 
4324 	///
4325 	@property void icon(MemoryImage i) {
4326 		version(X11) {
4327 			this.originalMemoryImage = i;
4328 			if (!active) return;
4329 			if (i !is null) {
4330 				this.img = Image.fromMemoryImage(i);
4331 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
4332 				//import std.stdio; writeln("using pixmap ", clippixmap);
4333 				updateNetWmIcon();
4334 				redraw();
4335 			} else {
4336 				if (this.img !is null) {
4337 					this.img = null;
4338 					redraw();
4339 				}
4340 			}
4341 		} else version(Windows) {
4342 			this.win32Icon = new WindowsIcon(i);
4343 
4344 			data.uFlags = NIF_ICON;
4345 			data.hIcon = this.win32Icon.hIcon;
4346 
4347 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4348 		} else version(OSXCocoa) {
4349 			throw new NotYetImplementedException();
4350 		} else static assert(0);
4351 	}
4352 
4353 	/// ditto
4354 	@property void icon (Image i) {
4355 		version(X11) {
4356 			if (!active) return;
4357 			if (i !is img) {
4358 				originalMemoryImage = null;
4359 				img = i;
4360 				redraw();
4361 			}
4362 		} else version(Windows) {
4363 			this.icon(i is null ? null : i.toTrueColorImage());
4364 		} else version(OSXCocoa) {
4365 			throw new NotYetImplementedException();
4366 		} else static assert(0);
4367 	}
4368 
4369 	/++
4370 		Shows a balloon notification. You can only show one balloon at a time, if you call
4371 		it twice while one is already up, the first balloon will be replaced.
4372 		
4373 		
4374 		The user is free to block notifications and they will automatically disappear after
4375 		a timeout period.
4376 
4377 		Params:
4378 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
4379 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
4380 			icon = the icon to display with the notification. If null, it uses your existing icon.
4381 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
4382 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
4383 	+/
4384 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
4385 		bool useCustom = true;
4386 		version(libnotify) {
4387 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
4388 			try {
4389 				if(!active) return;
4390 
4391 				if(libnotify is null) {
4392 					libnotify = new C_DynamicLibrary("libnotify.so");
4393 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
4394 				}
4395 
4396 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
4397 
4398 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
4399 
4400 				if(onclick) {
4401 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
4402 					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);
4403 					libnotify_action_delegates_count++;
4404 				}
4405 
4406 				// FIXME icon
4407 
4408 				// set hint image-data
4409 				// set default action for onclick
4410 
4411 				void* error;
4412 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
4413 
4414 				useCustom = false;
4415 			} catch(Exception e) {
4416 
4417 			}
4418 		}
4419 		
4420 		version(X11) {
4421 		if(useCustom) {
4422 			if(!active) return;
4423 			if(balloon) {
4424 				hideBalloon();
4425 			}
4426 			// I know there are two specs for this, but one is never
4427 			// implemented by any window manager I have ever seen, and
4428 			// the other is a bloated mess and too complicated for simpledisplay...
4429 			// so doing my own little window instead.
4430 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
4431 
4432 			int x, y, width, height;
4433 			getWindowRect(x, y, width, height);
4434 
4435 			int bx = x - balloon.width;
4436 			int by = y - balloon.height;
4437 			if(bx < 0)
4438 				bx = x + width + balloon.width;
4439 			if(by < 0)
4440 				by = y + height;
4441 
4442 			// just in case, make sure it is actually on scren
4443 			if(bx < 0)
4444 				bx = 0;
4445 			if(by < 0)
4446 				by = 0;
4447 
4448 			balloon.move(bx, by);
4449 			auto painter = balloon.draw();
4450 			painter.fillColor = Color(220, 220, 220);
4451 			painter.outlineColor = Color.black;
4452 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
4453 			auto iconWidth = icon is null ? 0 : icon.width;
4454 			if(icon)
4455 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
4456 			iconWidth += 6; // margin around the icon
4457 
4458 			// draw a close button
4459 			painter.outlineColor = Color(44, 44, 44);
4460 			painter.fillColor = Color(255, 255, 255);
4461 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
4462 			painter.pen = Pen(Color.black, 3);
4463 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
4464 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
4465 			painter.pen = Pen(Color.black, 1);
4466 			painter.fillColor = Color(220, 220, 220);
4467 
4468 			// Draw the title and message
4469 			painter.drawText(Point(4 + iconWidth, 4), title);
4470 			painter.drawLine(
4471 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
4472 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
4473 			);
4474 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
4475 
4476 			balloon.setEventHandlers(
4477 				(MouseEvent ev) {
4478 					if(ev.type == MouseEventType.buttonPressed) {
4479 						if(ev.x > balloon.width - 16 && ev.y < 16)
4480 							hideBalloon();
4481 						else if(onclick)
4482 							onclick();
4483 					}
4484 				}
4485 			);
4486 			balloon.show();
4487 
4488 			version(with_timer)
4489 			timer = new Timer(timeout, &hideBalloon);
4490 			else {} // FIXME
4491 		}
4492 		} else version(Windows) {
4493 			enum NIF_INFO = 0x00000010;
4494 
4495 			data.uFlags = NIF_INFO;
4496 
4497 			// FIXME: go back to the last valid unicode code point
4498 			if(title.length > 40)
4499 				title = title[0 .. 40];
4500 			if(message.length > 220)
4501 				message = message[0 .. 220];
4502 
4503 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
4504 			enum NIIF_LARGE_ICON  = 0x00000020;
4505 			enum NIIF_NOSOUND = 0x00000010;
4506 			enum NIIF_USER = 0x00000004;
4507 			enum NIIF_ERROR = 0x00000003;
4508 			enum NIIF_WARNING = 0x00000002;
4509 			enum NIIF_INFO = 0x00000001;
4510 			enum NIIF_NONE = 0;
4511 
4512 			WCharzBuffer t = WCharzBuffer(title);
4513 			WCharzBuffer m = WCharzBuffer(message);
4514 
4515 			t.copyInto(data.szInfoTitle);
4516 			m.copyInto(data.szInfo);
4517 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
4518 
4519 			if(icon !is null) {
4520 				auto i = new WindowsIcon(icon);
4521 				data.hBalloonIcon = i.hIcon;
4522 				data.dwInfoFlags |= NIIF_USER;
4523 			}
4524 
4525 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4526 		} else version(OSXCocoa) {
4527 			throw new NotYetImplementedException();
4528 		} else static assert(0);
4529 	}
4530 
4531 	///
4532 	//version(Windows)
4533 	void show() {
4534 		version(X11) {
4535 			if(!hidden)
4536 				return;
4537 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
4538 			hidden = false;
4539 		} else version(Windows) {
4540 			data.uFlags = NIF_STATE;
4541 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4542 			data.dwStateMask = NIS_HIDDEN; // windows vista
4543 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4544 		} else version(OSXCocoa) {
4545 			throw new NotYetImplementedException();
4546 		} else static assert(0);
4547 	}
4548 
4549 	version(X11)
4550 		bool hidden = false;
4551 
4552 	///
4553 	//version(Windows)
4554 	void hide() {
4555 		version(X11) {
4556 			if(hidden)
4557 				return;
4558 			hidden = true;
4559 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
4560 		} else version(Windows) {
4561 			data.uFlags = NIF_STATE;
4562 			data.dwState = NIS_HIDDEN; // windows vista
4563 			data.dwStateMask = NIS_HIDDEN; // windows vista
4564 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4565 		} else version(OSXCocoa) {
4566 			throw new NotYetImplementedException();
4567 		} else static assert(0);
4568 	}
4569 
4570 	///
4571 	void close () {
4572 		version(X11) {
4573 			if (active) {
4574 				active = false; // event handler will set this too, but meh
4575 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
4576 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
4577 				flushGui();
4578 			}
4579 		} else version(Windows) {
4580 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
4581 		} else version(OSXCocoa) {
4582 			throw new NotYetImplementedException();
4583 		} else static assert(0);
4584 	}
4585 
4586 	~this() {
4587 		version(X11)
4588 			if(clippixmap != None)
4589 				XFreePixmap(XDisplayConnection.get, clippixmap);
4590 		close();
4591 	}
4592 }
4593 
4594 version(X11)
4595 /// call XFreePixmap on the return value
4596 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
4597 	char[] data = new char[](i.width * i.height / 8 + 2);
4598 	data[] = 0;
4599 
4600 	int bitOffset = 0;
4601 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
4602 		ubyte v = c.a > 128 ? 1 : 0;
4603 		data[bitOffset / 8] |= v << (bitOffset%8);
4604 		bitOffset++;
4605 	}
4606 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
4607 	return handle;
4608 }
4609 
4610 
4611 // basic functions to make timers
4612 /**
4613 	A timer that will trigger your function on a given interval.
4614 
4615 
4616 	You create a timer with an interval and a callback. It will continue
4617 	to fire on the interval until it is destroyed.
4618 
4619 	There are currently no one-off timers (instead, just create one and
4620 	destroy it when it is triggered) nor are there pause/resume functions -
4621 	the timer must again be destroyed and recreated if you want to pause it.
4622 
4623 	auto timer = new Timer(50, { it happened!; });
4624 	timer.destroy();
4625 
4626 	Timers can only be expected to fire when the event loop is running and only
4627 	once per iteration through the event loop.
4628 
4629 	History:
4630 		Prior to December 9, 2020, a timer pulse set too high with a handler too
4631 		slow could lock up the event loop. It now guarantees other things will
4632 		get a chance to run between timer calls, even if that means not keeping up
4633 		with the requested interval.
4634 */
4635 version(with_timer) {
4636 class Timer {
4637 // FIXME: needs pause and unpause
4638 	// FIXME: I might add overloads for ones that take a count of
4639 	// how many elapsed since last time (on Windows, it will divide
4640 	// the ticks thing given, on Linux it is just available) and
4641 	// maybe one that takes an instance of the Timer itself too
4642 	/// Create a timer with a callback when it triggers.
4643 	this(int intervalInMilliseconds, void delegate() onPulse) {
4644 		assert(onPulse !is null);
4645 
4646 		this.intervalInMilliseconds = intervalInMilliseconds;
4647 		this.onPulse = onPulse;
4648 
4649 		version(Windows) {
4650 			/*
4651 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4652 			if(handle == 0)
4653 				throw new Exception("SetTimer fail");
4654 			*/
4655 
4656 			// thanks to Archival 998 for the WaitableTimer blocks
4657 			handle = CreateWaitableTimer(null, false, null);
4658 			long initialTime = -intervalInMilliseconds;
4659 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
4660 				throw new Exception("SetWaitableTimer Failed");
4661 
4662 			mapping[handle] = this;
4663 
4664 		} else version(linux) {
4665 			static import ep = core.sys.linux.epoll;
4666 
4667 			import core.sys.linux.timerfd;
4668 
4669 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
4670 			if(fd == -1)
4671 				throw new Exception("timer create failed");
4672 
4673 			mapping[fd] = this;
4674 
4675 			itimerspec value;
4676 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
4677 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
4678 
4679 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
4680 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
4681 
4682 			if(timerfd_settime(fd, 0, &value, null) == -1)
4683 				throw new Exception("couldn't make pulse timer");
4684 
4685 			version(with_eventloop) {
4686 				import arsd.eventloop;
4687 				addFileEventListeners(fd, &trigger, null, null);
4688 			} else {
4689 				prepareEventLoop();
4690 
4691 				ep.epoll_event ev = void;
4692 				ev.events = ep.EPOLLIN;
4693 				ev.data.fd = fd;
4694 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
4695 			}
4696 		} else featureNotImplemented();
4697 	}
4698 
4699 	private int intervalInMilliseconds;
4700 
4701 	/// Stop and destroy the timer object.
4702 	void destroy() {
4703 		version(Windows) {
4704 			if(handle) {
4705 				// KillTimer(null, handle);
4706 				CancelWaitableTimer(cast(void*)handle);
4707 				mapping.remove(handle);
4708 				CloseHandle(handle);
4709 				handle = null;
4710 			}
4711 		} else version(linux) {
4712 			if(fd != -1) {
4713 				import unix = core.sys.posix.unistd;
4714 				static import ep = core.sys.linux.epoll;
4715 
4716 				version(with_eventloop) {
4717 					import arsd.eventloop;
4718 					removeFileEventListeners(fd);
4719 				} else {
4720 					ep.epoll_event ev = void;
4721 					ev.events = ep.EPOLLIN;
4722 					ev.data.fd = fd;
4723 
4724 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
4725 				}
4726 				unix.close(fd);
4727 				mapping.remove(fd);
4728 				fd = -1;
4729 			}
4730 		} else featureNotImplemented();
4731 	}
4732 
4733 	~this() {
4734 		destroy();
4735 	}
4736 
4737 
4738 	void changeTime(int intervalInMilliseconds)
4739 	{
4740 		this.intervalInMilliseconds = intervalInMilliseconds;
4741 		version(Windows)
4742 		{
4743 			if(handle)
4744 			{
4745 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4746 				long initialTime = -intervalInMilliseconds;
4747 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
4748 					throw new Exception("couldn't change pulse timer");
4749 			}
4750 		}
4751 	}
4752 
4753 
4754 	private:
4755 
4756 	void delegate() onPulse;
4757 
4758 	int lastEventLoopRoundTriggered;
4759 
4760 	void trigger() {
4761 		version(linux) {
4762 			import unix = core.sys.posix.unistd;
4763 			long val;
4764 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
4765 		} else version(Windows) {
4766 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
4767 				return; // never try to actually run faster than the event loop
4768 			lastEventLoopRoundTriggered = eventLoopRound;
4769 		} else featureNotImplemented();
4770 
4771 		onPulse();
4772 	}
4773 
4774 	version(Windows)
4775 	void rearm() {
4776 
4777 	}
4778 
4779 	version(Windows)
4780 		extern(Windows)
4781 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
4782 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
4783 			if(Timer* t = timer in mapping) {
4784 				try
4785 				(*t).trigger();
4786 				catch(Exception e) { sdpy_abort(e); assert(0); }
4787 			}
4788 		}
4789 
4790 	version(Windows) {
4791 		//UINT_PTR handle;
4792 		//static Timer[UINT_PTR] mapping;
4793 		HANDLE handle;
4794 		__gshared Timer[HANDLE] mapping;
4795 	} else version(linux) {
4796 		int fd = -1;
4797 		__gshared Timer[int] mapping;
4798 	} else static assert(0, "timer not supported");
4799 }
4800 }
4801 
4802 version(Windows)
4803 private int eventLoopRound;
4804 
4805 version(Windows)
4806 /// 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
4807 class WindowsHandleReader {
4808 	///
4809 	this(void delegate() onReady, HANDLE handle) {
4810 		this.onReady = onReady;
4811 		this.handle = handle;
4812 
4813 		mapping[handle] = this;
4814 
4815 		enable();
4816 	}
4817 
4818 	///
4819 	void enable() {
4820 		auto el = EventLoop.get().impl;
4821 		el.handles ~= handle;
4822 	}
4823 
4824 	///
4825 	void disable() {
4826 		auto el = EventLoop.get().impl;
4827 		for(int i = 0; i < el.handles.length; i++) {
4828 			if(el.handles[i] is handle) {
4829 				el.handles[i] = el.handles[$-1];
4830 				el.handles = el.handles[0 .. $-1];
4831 				return;
4832 			}
4833 		}
4834 	}
4835 
4836 	void dispose() {
4837 		disable();
4838 		if(handle)
4839 			mapping.remove(handle);
4840 		handle = null;
4841 	}
4842 
4843 	void ready() {
4844 		if(onReady)
4845 			onReady();
4846 	}
4847 
4848 	HANDLE handle;
4849 	void delegate() onReady;
4850 
4851 	__gshared WindowsHandleReader[HANDLE] mapping;
4852 }
4853 
4854 version(Posix)
4855 /// Lets you add files to the event loop for reading. Use at your own risk.
4856 class PosixFdReader {
4857 	///
4858 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4859 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
4860 	}
4861 
4862 	///
4863 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4864 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
4865 	}
4866 
4867 	///
4868 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4869 		this.onReady = onReady;
4870 		this.fd = fd;
4871 		this.captureWrites = captureWrites;
4872 		this.captureReads = captureReads;
4873 
4874 		mapping[fd] = this;
4875 
4876 		version(with_eventloop) {
4877 			import arsd.eventloop;
4878 			addFileEventListeners(fd, &readyel);
4879 		} else {
4880 			enable();
4881 		}
4882 	}
4883 
4884 	bool captureReads;
4885 	bool captureWrites;
4886 
4887 	version(with_eventloop) {} else
4888 	///
4889 	void enable() {
4890 		prepareEventLoop();
4891 
4892 		enabled = true;
4893 
4894 		version(linux) {
4895 			static import ep = core.sys.linux.epoll;
4896 			ep.epoll_event ev = void;
4897 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
4898 			//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
4899 			ev.data.fd = fd;
4900 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
4901 		} else {
4902 
4903 		}
4904 	}
4905 
4906 	version(with_eventloop) {} else
4907 	///
4908 	void disable() {
4909 		prepareEventLoop();
4910 
4911 		enabled = false;
4912 
4913 		version(linux) {
4914 			static import ep = core.sys.linux.epoll;
4915 			ep.epoll_event ev = void;
4916 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
4917 			//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
4918 			ev.data.fd = fd;
4919 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
4920 		}
4921 	}
4922 
4923 	version(with_eventloop) {} else
4924 	///
4925 	void dispose() {
4926 		if(enabled)
4927 			disable();
4928 		if(fd != -1)
4929 			mapping.remove(fd);
4930 		fd = -1;
4931 	}
4932 
4933 	void delegate(int, bool, bool) onReady;
4934 
4935 	version(with_eventloop)
4936 	void readyel() {
4937 		onReady(fd, true, true);
4938 	}
4939 
4940 	void ready(uint flags) {
4941 		version(linux) {
4942 			static import ep = core.sys.linux.epoll;
4943 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
4944 		} else {
4945 			import core.sys.posix.poll;
4946 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
4947 		}
4948 	}
4949 
4950 	void hup(uint flags) {
4951 		if(onHup)
4952 			onHup();
4953 	}
4954 
4955 	void delegate() onHup;
4956 
4957 	int fd = -1;
4958 	private bool enabled;
4959 	__gshared PosixFdReader[int] mapping;
4960 }
4961 
4962 // basic functions to access the clipboard
4963 /+
4964 
4965 
4966 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
4967 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
4968 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
4969 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
4970 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
4971 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
4972 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
4973 
4974 +/
4975 
4976 /++
4977 	this does a delegate because it is actually an async call on X...
4978 	the receiver may never be called if the clipboard is empty or unavailable
4979 	gets plain text from the clipboard
4980 +/
4981 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
4982 	version(Windows) {
4983 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
4984 		if(OpenClipboard(hwndOwner) == 0)
4985 			throw new Exception("OpenClipboard");
4986 		scope(exit)
4987 			CloseClipboard();
4988 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
4989 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
4990 
4991 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
4992 				scope(exit)
4993 					GlobalUnlock(dataHandle);
4994 
4995 				// FIXME: CR/LF conversions
4996 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
4997 				int len = 0;
4998 				auto d = data;
4999 				while(*d) {
5000 					d++;
5001 					len++;
5002 				}
5003 				string s;
5004 				s.reserve(len);
5005 				foreach(dchar ch; data[0 .. len]) {
5006 					s ~= ch;
5007 				}
5008 				receiver(s);
5009 			}
5010 		}
5011 	} else version(X11) {
5012 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5013 	} else version(OSXCocoa) {
5014 		throw new NotYetImplementedException();
5015 	} else static assert(0);
5016 }
5017 
5018 // FIXME: a clipboard listener might be cool btw
5019 
5020 /++
5021 	this does a delegate because it is actually an async call on X...
5022 	the receiver may never be called if the clipboard is empty or unavailable
5023 	gets image from the clipboard
5024 
5025 	templated because it introduces an optional dependency on arsd.bmp
5026 +/
5027 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5028 	version(Windows) {
5029 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5030 		if(OpenClipboard(hwndOwner) == 0)
5031 			throw new Exception("OpenClipboard");
5032 		scope(exit)
5033 			CloseClipboard();
5034 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5035 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5036 				scope(exit)
5037 					GlobalUnlock(dataHandle);
5038 
5039 				auto len = GlobalSize(dataHandle);
5040 
5041 				import arsd.bmp;
5042 				auto img = readBmp(data[0 .. len], false);
5043 				receiver(img);
5044 			}
5045 		}
5046 	} else version(X11) {
5047 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5048 	} else version(OSXCocoa) {
5049 		throw new NotYetImplementedException();
5050 	} else static assert(0);
5051 }
5052 
5053 version(Windows)
5054 struct WCharzBuffer {
5055 	wchar[] buffer;
5056 	wchar[256] staticBuffer = void;
5057 
5058 	size_t length() {
5059 		return buffer.length;
5060 	}
5061 
5062 	wchar* ptr() {
5063 		return buffer.ptr;
5064 	}
5065 
5066 	wchar[] slice() {
5067 		return buffer;
5068 	}
5069 
5070 	void copyInto(R)(ref R r) {
5071 		static if(is(R == wchar[N], size_t N)) {
5072 			r[0 .. this.length] = slice[];
5073 			r[this.length] = 0;
5074 		} else static assert(0, "can only copy into wchar[n], not " ~ R.stringof);
5075 	}
5076 
5077 	/++
5078 		conversionFlags = [WindowsStringConversionFlags]
5079 	+/
5080 	this(in char[] data, int conversionFlags = 0) {
5081 		conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name
5082 		auto sz = sizeOfConvertedWstring(data, conversionFlags);
5083 		if(sz > staticBuffer.length)
5084 			buffer = new wchar[](sz);
5085 		else
5086 			buffer = staticBuffer[];
5087 
5088 		buffer = makeWindowsString(data, buffer, conversionFlags);
5089 	}
5090 }
5091 
5092 version(Windows)
5093 int sizeOfConvertedWstring(in char[] s, int conversionFlags) {
5094 	int size = 0;
5095 
5096 	if(conversionFlags & WindowsStringConversionFlags.convertNewLines) {
5097 		// need to convert line endings, which means the length will get bigger.
5098 
5099 		// BTW I betcha this could be faster with some simd stuff.
5100 		char last;
5101 		foreach(char ch; s) {
5102 			if(ch == 10 && last != 13)
5103 				size++; // will add a 13 before it...
5104 			size++;
5105 			last = ch;
5106 		}
5107 	} else {
5108 		// no conversion necessary, just estimate based on length
5109 		/*
5110 			I don't think there's any string with a longer length
5111 			in code units when encoded in UTF-16 than it has in UTF-8.
5112 			This will probably over allocate, but that's OK.
5113 		*/
5114 		size = cast(int) s.length;
5115 	}
5116 
5117 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate)
5118 		size++;
5119 
5120 	return size;
5121 }
5122 
5123 version(Windows)
5124 enum WindowsStringConversionFlags : int {
5125 	zeroTerminate = 1,
5126 	convertNewLines = 2,
5127 }
5128 
5129 version(Windows)
5130 class WindowsApiException : Exception {
5131 	char[256] buffer;
5132 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5133 		assert(msg.length < 100);
5134 
5135 		auto error = GetLastError();
5136 		buffer[0 .. msg.length] = msg;
5137 		buffer[msg.length] = ' ';
5138 
5139 		int pos = cast(int) msg.length + 1;
5140 
5141 		if(error == 0)
5142 			buffer[pos++] = '0';
5143 		else {
5144 
5145 			auto ec = error;
5146 			auto init = pos;
5147 			while(ec) {
5148 				buffer[pos++] = (ec % 10) + '0';
5149 				ec /= 10;
5150 			}
5151 
5152 			buffer[pos++] = ' ';
5153 
5154 			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);
5155 
5156 			pos += size;
5157 		}
5158 
5159 
5160 		super(cast(string) buffer[0 .. pos], file, line, next);
5161 	}
5162 }
5163 
5164 class ErrnoApiException : Exception {
5165 	char[256] buffer;
5166 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5167 		assert(msg.length < 100);
5168 
5169 		import core.stdc.errno;
5170 		auto error = errno;
5171 		buffer[0 .. msg.length] = msg;
5172 		buffer[msg.length] = ' ';
5173 
5174 		int pos = cast(int) msg.length + 1;
5175 
5176 		if(error == 0)
5177 			buffer[pos++] = '0';
5178 		else {
5179 			auto init = pos;
5180 			while(error) {
5181 				buffer[pos++] = (error % 10) + '0';
5182 				error /= 10;
5183 			}
5184 			for(int i = 0; i < (pos - init) / 2; i++) {
5185 				char c = buffer[i + init];
5186 				buffer[i + init] = buffer[pos - (i + init) - 1];
5187 				buffer[pos - (i + init) - 1] = c;
5188 			}
5189 		}
5190 
5191 
5192 		super(cast(string) buffer[0 .. pos], file, line, next);
5193 	}
5194 
5195 }
5196 
5197 version(Windows)
5198 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) {
5199 	if(str.length == 0)
5200 		return null;
5201 
5202 	int pos = 0;
5203 	dchar last;
5204 	foreach(dchar c; str) {
5205 		if(c <= 0xFFFF) {
5206 			if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13)
5207 				buffer[pos++] = 13;
5208 			buffer[pos++] = cast(wchar) c;
5209 		} else if(c <= 0x10FFFF) {
5210 			buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
5211 			buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
5212 		}
5213 
5214 		last = c;
5215 	}
5216 
5217 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) {
5218 		buffer[pos] = 0;
5219 	}
5220 
5221 	return buffer[0 .. pos];
5222 }
5223 
5224 version(Windows)
5225 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
5226 	if(str.length == 0)
5227 		return null;
5228 
5229 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
5230 	if(got == 0) {
5231 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5232 			throw new Exception("not enough buffer");
5233 		else
5234 			throw new Exception("conversion"); // FIXME: GetLastError
5235 	}
5236 	return buffer[0 .. got];
5237 }
5238 
5239 version(Windows)
5240 string makeUtf8StringFromWindowsString(in wchar[] str) {
5241 	char[] buffer;
5242 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
5243 	buffer.length = got;
5244 
5245 	// it is unique because we just allocated it above!
5246 	return cast(string) makeUtf8StringFromWindowsString(str, buffer);
5247 }
5248 
5249 version(Windows)
5250 string makeUtf8StringFromWindowsString(wchar* str) {
5251 	char[] buffer;
5252 	auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
5253 	buffer.length = got;
5254 
5255 	got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
5256 	if(got == 0) {
5257 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5258 			throw new Exception("not enough buffer");
5259 		else
5260 			throw new Exception("conversion"); // FIXME: GetLastError
5261 	}
5262 	return cast(string) buffer[0 .. got];
5263 }
5264 
5265 int findIndexOfZero(in wchar[] str) {
5266 	foreach(idx, wchar ch; str)
5267 		if(ch == 0)
5268 			return cast(int) idx;
5269 	return cast(int) str.length;
5270 }
5271 int findIndexOfZero(in char[] str) {
5272 	foreach(idx, char ch; str)
5273 		if(ch == 0)
5274 			return cast(int) idx;
5275 	return cast(int) str.length;
5276 }
5277 
5278 /// copies some text to the clipboard
5279 void setClipboardText(SimpleWindow clipboardOwner, string text) {
5280 	assert(clipboardOwner !is null);
5281 	version(Windows) {
5282 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5283 			throw new Exception("OpenClipboard");
5284 		scope(exit)
5285 			CloseClipboard();
5286 		EmptyClipboard();
5287 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5288 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
5289 		if(handle is null) throw new Exception("GlobalAlloc");
5290 		if(auto data = cast(wchar*) GlobalLock(handle)) {
5291 			auto slice = data[0 .. sz];
5292 			scope(failure)
5293 				GlobalUnlock(handle);
5294 
5295 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5296 
5297 			GlobalUnlock(handle);
5298 			SetClipboardData(CF_UNICODETEXT, handle);
5299 		}
5300 	} else version(X11) {
5301 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
5302 	} else version(OSXCocoa) {
5303 		throw new NotYetImplementedException();
5304 	} else static assert(0);
5305 }
5306 
5307 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
5308 	assert(clipboardOwner !is null);
5309 	version(Windows) {
5310 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5311 			throw new Exception("OpenClipboard");
5312 		scope(exit)
5313 			CloseClipboard();
5314 		EmptyClipboard();
5315 
5316 
5317 		import arsd.bmp;
5318 		ubyte[] mdata;
5319 		mdata.reserve(img.width * img.height);
5320 		void sink(ubyte b) {
5321 			mdata ~= b;
5322 		}
5323 		writeBmpIndirect(img, &sink, false);
5324 
5325 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
5326 		if(handle is null) throw new Exception("GlobalAlloc");
5327 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
5328 			auto slice = data[0 .. mdata.length];
5329 			scope(failure)
5330 				GlobalUnlock(handle);
5331 
5332 			slice[] = mdata[];
5333 
5334 			GlobalUnlock(handle);
5335 			SetClipboardData(CF_DIB, handle);
5336 		}
5337 	} else version(X11) {
5338 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
5339 			mixin X11SetSelectionHandler_Basics;
5340 			private const(ubyte)[] mdata;
5341 			private const(ubyte)[] mdata_original;
5342 			this(MemoryImage img) {
5343 				import arsd.bmp;
5344 
5345 				mdata.reserve(img.width * img.height);
5346 				void sink(ubyte b) {
5347 					mdata ~= b;
5348 				}
5349 				writeBmpIndirect(img, &sink, true);
5350 
5351 				mdata_original = mdata;
5352 			}
5353 
5354 			Atom[] availableFormats() {
5355 				auto display = XDisplayConnection.get;
5356 				return [
5357 					GetAtom!"image/bmp"(display),
5358 					GetAtom!"TARGETS"(display)
5359 				];
5360 			}
5361 
5362 			ubyte[] getData(Atom format, return scope ubyte[] data) {
5363 				if(mdata.length < data.length) {
5364 					data[0 .. mdata.length] = mdata[];
5365 					auto ret = data[0 .. mdata.length];
5366 					mdata = mdata[$..$];
5367 					return ret;
5368 				} else {
5369 					data[] = mdata[0 .. data.length];
5370 					mdata = mdata[data.length .. $];
5371 					return data[];
5372 				}
5373 			}
5374 
5375 			void done() {
5376 				mdata = mdata_original;
5377 			}
5378 		}
5379 
5380 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
5381 	} else version(OSXCocoa) {
5382 		throw new NotYetImplementedException();
5383 	} else static assert(0);
5384 }
5385 
5386 
5387 version(X11) {
5388 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
5389 
5390 	private Atom*[] interredAtoms; // for discardAndRecreate
5391 
5392 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
5393 	/// Platform specific for X11
5394 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
5395 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
5396 		static Atom a;
5397 		if(!a) {
5398 			a = XInternAtom(display, name, !create);
5399 			interredAtoms ~= &a;
5400 		}
5401 		if(a == None)
5402 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
5403 		return a;
5404 	}
5405 
5406 	/// Platform specific for X11 - gets atom names as a string
5407 	string getAtomName(Atom atom, Display* display) {
5408 		auto got = XGetAtomName(display, atom);
5409 		scope(exit) XFree(got);
5410 		import core.stdc.string;
5411 		string s = got[0 .. strlen(got)].idup;
5412 		return s;
5413 	}
5414 
5415 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later
5416 	void setPrimarySelection(SimpleWindow window, string text) {
5417 		setX11Selection!"PRIMARY"(window, text);
5418 	}
5419 
5420 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later
5421 	void setSecondarySelection(SimpleWindow window, string text) {
5422 		setX11Selection!"SECONDARY"(window, text);
5423 	}
5424 
5425 	interface X11SetSelectionHandler {
5426 		// should include TARGETS right now
5427 		Atom[] availableFormats();
5428 		// Return the slice of data you filled, empty slice if done.
5429 		// this is to support the incremental thing
5430 		ubyte[] getData(Atom format, return scope ubyte[] data);
5431 
5432 		void done();
5433 
5434 		void handleRequest(XEvent);
5435 
5436 		bool matchesIncr(Window, Atom);
5437 		void sendMoreIncr(XPropertyEvent*);
5438 	}
5439 
5440 	mixin template X11SetSelectionHandler_Basics() {
5441 		Window incrWindow;
5442 		Atom incrAtom;
5443 		Atom selectionAtom;
5444 		Atom formatAtom;
5445 		ubyte[] toSend;
5446 		bool matchesIncr(Window w, Atom a) {
5447 			return incrAtom && incrAtom == a && w == incrWindow;
5448 		}
5449 		void sendMoreIncr(XPropertyEvent* event) {
5450 			auto display = XDisplayConnection.get;
5451 
5452 			XChangeProperty (display,
5453 				incrWindow,
5454 				incrAtom,
5455 				formatAtom,
5456 				8 /* bits */, PropModeReplace,
5457 				toSend.ptr, cast(int) toSend.length);
5458 
5459 			if(toSend.length != 0) {
5460 				toSend = this.getData(formatAtom, toSend[]);
5461 			} else {
5462 				this.done();
5463 				incrWindow = None;
5464 				incrAtom = None;
5465 				selectionAtom = None;
5466 				formatAtom = None;
5467 				toSend = null;
5468 			}
5469 		}
5470 		void handleRequest(XEvent ev) {
5471 
5472 			auto display = XDisplayConnection.get;
5473 
5474 			XSelectionRequestEvent* event = &ev.xselectionrequest;
5475 			XSelectionEvent selectionEvent;
5476 			selectionEvent.type = EventType.SelectionNotify;
5477 			selectionEvent.display = event.display;
5478 			selectionEvent.requestor = event.requestor;
5479 			selectionEvent.selection = event.selection;
5480 			selectionEvent.time = event.time;
5481 			selectionEvent.target = event.target;
5482 
5483 			bool supportedType() {
5484 				foreach(t; this.availableFormats())
5485 					if(t == event.target)
5486 						return true;
5487 				return false;
5488 			}
5489 
5490 			if(event.property == None) {
5491 				selectionEvent.property = event.target;
5492 
5493 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5494 				XFlush(display);
5495 			} if(event.target == GetAtom!"TARGETS"(display)) {
5496 				/* respond with the supported types */
5497 				auto tlist = this.availableFormats();
5498 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
5499 				selectionEvent.property = event.property;
5500 
5501 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5502 				XFlush(display);
5503 			} else if(supportedType()) {
5504 				auto buffer = new ubyte[](1024 * 64);
5505 				auto toSend = this.getData(event.target, buffer[]);
5506 
5507 				if(toSend.length < 32 * 1024) {
5508 					// small enough to send directly...
5509 					selectionEvent.property = event.property;
5510 					XChangeProperty (display,
5511 						selectionEvent.requestor,
5512 						selectionEvent.property,
5513 						event.target,
5514 						8 /* bits */, 0 /* PropModeReplace */,
5515 						toSend.ptr, cast(int) toSend.length);
5516 
5517 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5518 					XFlush(display);
5519 				} else {
5520 					// large, let's send incrementally
5521 					arch_ulong l = toSend.length;
5522 
5523 					// if I wanted other events from this window don't want to clear that out....
5524 					XWindowAttributes xwa;
5525 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
5526 
5527 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
5528 
5529 					incrWindow = event.requestor;
5530 					incrAtom = event.property;
5531 					formatAtom = event.target;
5532 					selectionAtom = event.selection;
5533 					this.toSend = toSend;
5534 
5535 					selectionEvent.property = event.property;
5536 					XChangeProperty (display,
5537 						selectionEvent.requestor,
5538 						selectionEvent.property,
5539 						GetAtom!"INCR"(display),
5540 						32 /* bits */, PropModeReplace,
5541 						&l, 1);
5542 
5543 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5544 					XFlush(display);
5545 				}
5546 				//if(after)
5547 					//after();
5548 			} else {
5549 				debug(sdpy_clip) {
5550 					import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display));
5551 				}
5552 				selectionEvent.property = None; // I don't know how to handle this type...
5553 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
5554 				XFlush(display);
5555 			}
5556 		}
5557 	}
5558 
5559 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
5560 		mixin X11SetSelectionHandler_Basics;
5561 		private const(ubyte)[] text;
5562 		private const(ubyte)[] text_original;
5563 		this(string text) {
5564 			this.text = cast(const ubyte[]) text;
5565 			this.text_original = this.text;
5566 		}
5567 		Atom[] availableFormats() {
5568 			auto display = XDisplayConnection.get;
5569 			return [
5570 				GetAtom!"UTF8_STRING"(display),
5571 				GetAtom!"text/plain"(display),
5572 				XA_STRING,
5573 				GetAtom!"TARGETS"(display)
5574 			];
5575 		}
5576 
5577 		ubyte[] getData(Atom format, return scope ubyte[] data) {
5578 			if(text.length < data.length) {
5579 				data[0 .. text.length] = text[];
5580 				return data[0 .. text.length];
5581 			} else {
5582 				data[] = text[0 .. data.length];
5583 				text = text[data.length .. $];
5584 				return data[];
5585 			}
5586 		}
5587 
5588 		void done() {
5589 			text = text_original;
5590 		}
5591 	}
5592 
5593 	/// 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?!)
5594 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
5595 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
5596 	}
5597 
5598 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
5599 		assert(window !is null);
5600 
5601 		auto display = XDisplayConnection.get();
5602 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
5603 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
5604 		else Atom a = GetAtom!atomName(display);
5605 
5606 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
5607 
5608 		window.impl.setSelectionHandlers[a] = data;
5609 	}
5610 
5611 	///
5612 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
5613 		getX11Selection!"PRIMARY"(window, handler);
5614 	}
5615 
5616 	// added July 28, 2020
5617 	// undocumented as experimental tho
5618 	interface X11GetSelectionHandler {
5619 		void handleData(Atom target, in ubyte[] data);
5620 		Atom findBestFormat(Atom[] answer);
5621 
5622 		void prepareIncremental(Window, Atom);
5623 		bool matchesIncr(Window, Atom);
5624 		void handleIncrData(Atom, in ubyte[] data);
5625 	}
5626 
5627 	mixin template X11GetSelectionHandler_Basics() {
5628 		Window incrWindow;
5629 		Atom incrAtom;
5630 
5631 		void prepareIncremental(Window w, Atom a) {
5632 			incrWindow = w;
5633 			incrAtom = a;
5634 		}
5635 		bool matchesIncr(Window w, Atom a) {
5636 			return incrWindow == w && incrAtom == a;
5637 		}
5638 
5639 		Atom incrFormatAtom;
5640 		ubyte[] incrData;
5641 		void handleIncrData(Atom format, in ubyte[] data) {
5642 			incrFormatAtom = format;
5643 
5644 			if(data.length)
5645 				incrData ~= data;
5646 			else
5647 				handleData(incrFormatAtom, incrData);
5648 
5649 		}
5650 	}
5651 
5652 	///
5653 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
5654 		assert(window !is null);
5655 
5656 		auto display = XDisplayConnection.get();
5657 		auto atom = GetAtom!atomName(display);
5658 
5659 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
5660 			this(void delegate(in char[]) handler) {
5661 				this.handler = handler;
5662 			}
5663 
5664 			mixin X11GetSelectionHandler_Basics;
5665 
5666 			void delegate(in char[]) handler;
5667 
5668 			void handleData(Atom target, in ubyte[] data) {
5669 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
5670 					handler(cast(const char[]) data);
5671 			}
5672 
5673 			Atom findBestFormat(Atom[] answer) {
5674 				Atom best = None;
5675 				foreach(option; answer) {
5676 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
5677 						best = option;
5678 						break;
5679 					} else if(option == XA_STRING) {
5680 						best = option;
5681 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
5682 						best = option;
5683 					}
5684 				}
5685 				return best;
5686 			}
5687 		}
5688 
5689 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
5690 
5691 		auto target = GetAtom!"TARGETS"(display);
5692 
5693 		// SDD_DATA is "simpledisplay.d data"
5694 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
5695 	}
5696 
5697 	/// Gets the image on the clipboard, if there is one. Added July 2020.
5698 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
5699 		assert(window !is null);
5700 
5701 		auto display = XDisplayConnection.get();
5702 		auto atom = GetAtom!atomName(display);
5703 
5704 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
5705 			this(void delegate(MemoryImage) handler) {
5706 				this.handler = handler;
5707 			}
5708 
5709 			mixin X11GetSelectionHandler_Basics;
5710 
5711 			void delegate(MemoryImage) handler;
5712 
5713 			void handleData(Atom target, in ubyte[] data) {
5714 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
5715 					import arsd.bmp;
5716 					handler(readBmp(data));
5717 				}
5718 			}
5719 
5720 			Atom findBestFormat(Atom[] answer) {
5721 				Atom best = None;
5722 				foreach(option; answer) {
5723 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
5724 						best = option;
5725 					}
5726 				}
5727 				return best;
5728 			}
5729 
5730 		}
5731 
5732 
5733 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
5734 
5735 		auto target = GetAtom!"TARGETS"(display);
5736 
5737 		// SDD_DATA is "simpledisplay.d data"
5738 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
5739 	}
5740 
5741 
5742 	///
5743 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
5744 		Atom actualType;
5745 		int actualFormat;
5746 		arch_ulong actualItems;
5747 		arch_ulong bytesRemaining;
5748 		void* data;
5749 
5750 		auto display = XDisplayConnection.get();
5751 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
5752 			if(actualFormat == 0)
5753 				return null;
5754 			else {
5755 				int byteLength;
5756 				if(actualFormat == 32) {
5757 					// 32 means it is a C long... which is variable length
5758 					actualFormat = cast(int) arch_long.sizeof * 8;
5759 				}
5760 
5761 				// then it is just a bit count
5762 				byteLength = cast(int) (actualItems * actualFormat / 8);
5763 
5764 				auto d = new ubyte[](byteLength);
5765 				d[] = cast(ubyte[]) data[0 .. byteLength];
5766 				XFree(data);
5767 				return d;
5768 			}
5769 		}
5770 		return null;
5771 	}
5772 
5773 	/* defined in the systray spec */
5774 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
5775 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
5776 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
5777 
5778 
5779 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
5780 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
5781 	public class GlobalHotkey {
5782 		KeyEvent key;
5783 		void delegate () handler;
5784 
5785 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
5786 
5787 		/// Create from initialzed KeyEvent object
5788 		this (KeyEvent akey, void delegate () ahandler=null) {
5789 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
5790 			key = akey;
5791 			handler = ahandler;
5792 		}
5793 
5794 		/// Create from emacs-like key name ("C-M-Y", etc.)
5795 		this (const(char)[] akey, void delegate () ahandler=null) {
5796 			key = KeyEvent.parse(akey);
5797 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
5798 			handler = ahandler;
5799 		}
5800 
5801 	}
5802 
5803 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
5804 		//conwriteln("failed to grab key");
5805 		GlobalHotkeyManager.ghfailed = true;
5806 		return 0;
5807 	}
5808 
5809 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
5810 		Image.impl.xshmfailed = true;
5811 		return 0;
5812 	}
5813 
5814 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
5815 		import core.stdc.stdio;
5816 		char[265] buffer;
5817 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
5818 		debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, evt.serial, evt.request_code, evt.minor_code, evt.resourceid);
5819 		return 0;
5820 	}
5821 
5822 	/++
5823 		Global hotkey manager. It contains static methods to manage global hotkeys.
5824 
5825 		---
5826 		 try {
5827 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
5828 		} catch (Exception e) {
5829 			conwriteln("ERROR registering hotkey!");
5830 		}
5831 		---
5832 
5833 		The key strings are based on Emacs. In practical terms,
5834 		`M` means `alt` and `H` means the Windows logo key. `C`
5835 		is `ctrl`.
5836 
5837 		$(WARNING
5838 			This is X-specific right now. If you are on
5839 			Windows, try [registerHotKey] instead.
5840 
5841 			We will probably merge these into a single
5842 			interface later.
5843 		)
5844 	+/
5845 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
5846 		version(X11) {
5847 			void recreateAfterDisconnect() {
5848 				throw new Exception("NOT IMPLEMENTED");
5849 			}
5850 			void discardConnectionState() {
5851 				throw new Exception("NOT IMPLEMENTED");
5852 			}
5853 		}
5854 
5855 		private static immutable uint[8] masklist = [ 0,
5856 			KeyOrButtonMask.LockMask,
5857 			KeyOrButtonMask.Mod2Mask,
5858 			KeyOrButtonMask.Mod3Mask,
5859 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
5860 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
5861 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
5862 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
5863 		];
5864 		private __gshared GlobalHotkeyManager ghmanager;
5865 		private __gshared bool ghfailed = false;
5866 
5867 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
5868 			if (modmask == 0) return false;
5869 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
5870 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
5871 			return true;
5872 		}
5873 
5874 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
5875 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
5876 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
5877 			return modmask;
5878 		}
5879 
5880 		private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) {
5881 			uint keycode = cast(uint)ke.key;
5882 			auto dpy = XDisplayConnection.get;
5883 			return XKeysymToKeycode(dpy, keycode);
5884 		}
5885 
5886 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
5887 
5888 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
5889 
5890 		NativeEventHandler getNativeEventHandler () {
5891 			return delegate int (XEvent e) {
5892 				if (e.type != EventType.KeyPress) return 1;
5893 				auto kev = cast(const(XKeyEvent)*)&e;
5894 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
5895 				if (auto ghkp = hash in globalHotkeyList) {
5896 					try {
5897 						ghkp.doHandle();
5898 					} catch (Exception e) {
5899 						import core.stdc.stdio : stderr, fprintf;
5900 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
5901 					}
5902 				}
5903 				return 1;
5904 			};
5905 		}
5906 
5907 		private this () {
5908 			auto dpy = XDisplayConnection.get;
5909 			auto root = RootWindow(dpy, DefaultScreen(dpy));
5910 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
5911 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
5912 		}
5913 
5914 		/// Register new global hotkey with initialized `GlobalHotkey` object.
5915 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
5916 		static void register (GlobalHotkey gh) {
5917 			if (gh is null) return;
5918 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
5919 
5920 			auto dpy = XDisplayConnection.get;
5921 			immutable keycode = keyEvent2KeyCode(gh.key);
5922 
5923 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
5924 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
5925 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
5926 			XSync(dpy, 0/*False*/);
5927 
5928 			Window root = RootWindow(dpy, DefaultScreen(dpy));
5929 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5930 			ghfailed = false;
5931 			foreach (immutable uint ormask; masklist[]) {
5932 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
5933 			}
5934 			XSync(dpy, 0/*False*/);
5935 			XSetErrorHandler(savedErrorHandler);
5936 
5937 			if (ghfailed) {
5938 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5939 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
5940 				XSync(dpy, 0/*False*/);
5941 				XSetErrorHandler(savedErrorHandler);
5942 				throw new Exception("cannot register global hotkey");
5943 			}
5944 
5945 			globalHotkeyList[hash] = gh;
5946 		}
5947 
5948 		/// Ditto
5949 		static void register (const(char)[] akey, void delegate () ahandler) {
5950 			register(new GlobalHotkey(akey, ahandler));
5951 		}
5952 
5953 		private static void removeByHash (ulong hash) {
5954 			if (auto ghp = hash in globalHotkeyList) {
5955 				auto dpy = XDisplayConnection.get;
5956 				immutable keycode = keyEvent2KeyCode(ghp.key);
5957 				Window root = RootWindow(dpy, DefaultScreen(dpy));
5958 				XSync(dpy, 0/*False*/);
5959 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5960 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
5961 				XSync(dpy, 0/*False*/);
5962 				XSetErrorHandler(savedErrorHandler);
5963 				globalHotkeyList.remove(hash);
5964 			}
5965 		}
5966 
5967 		/// Register new global hotkey with previously used `GlobalHotkey` object.
5968 		/// It is safe to unregister unknown or invalid hotkey.
5969 		static void unregister (GlobalHotkey gh) {
5970 			//TODO: add second AA for faster search? prolly doesn't worth it.
5971 			if (gh is null) return;
5972 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
5973 				if (kv.value is gh) {
5974 					removeByHash(kv.key);
5975 					return;
5976 				}
5977 			}
5978 		}
5979 
5980 		/// Ditto.
5981 		static void unregister (const(char)[] key) {
5982 			auto kev = KeyEvent.parse(key);
5983 			immutable keycode = keyEvent2KeyCode(kev);
5984 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
5985 		}
5986 	}
5987 }
5988 
5989 version(Windows) {
5990 	/// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application)
5991 	void sendSyntheticInput(wstring s) {
5992 		INPUT[] inputs;
5993 		inputs.reserve(s.length * 2);
5994 
5995 		foreach(wchar c; s) {
5996 			INPUT input;
5997 			input.type = INPUT_KEYBOARD;
5998 			input.ki.wScan = c;
5999 			input.ki.dwFlags = KEYEVENTF_UNICODE;
6000 			inputs ~= input;
6001 
6002 			input.ki.dwFlags |= KEYEVENTF_KEYUP;
6003 			inputs ~= input;
6004 		}
6005 
6006 		if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6007 			throw new Exception("SendInput failed");
6008 		}
6009 	}
6010 
6011 
6012 	// global hotkey helper function
6013 
6014 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID.
6015 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6016 		__gshared int hotkeyId = 0;
6017 		int id = ++hotkeyId;
6018 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6019 			throw new Exception("RegisterHotKey failed");
6020 
6021 		__gshared void delegate()[WPARAM][HWND] handlers;
6022 
6023 		handlers[window.impl.hwnd][id] = handler;
6024 
6025 		int delegate(HWND, UINT, WPARAM, LPARAM) oldHandler;
6026 
6027 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
6028 			switch(msg) {
6029 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6030 				case WM_HOTKEY:
6031 					if(auto list = hwnd in handlers) {
6032 						if(auto h = wParam in *list) {
6033 							(*h)();
6034 							return 0;
6035 						}
6036 					}
6037 				goto default;
6038 				default:
6039 			}
6040 			if(oldHandler)
6041 				return oldHandler(hwnd, msg, wParam, lParam);
6042 			return 1; // pass it on
6043 		};
6044 
6045 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6046 			oldHandler = window.handleNativeEvent;
6047 			window.handleNativeEvent = nativeEventHandler;
6048 		}
6049 
6050 		return id;
6051 	}
6052 
6053 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey.
6054 	void unregisterHotKey(SimpleWindow window, int id) {
6055 		if(!UnregisterHotKey(window.impl.hwnd, id))
6056 			throw new Exception("UnregisterHotKey");
6057 	}
6058 }
6059 
6060 version (X11) {
6061 	pragma(lib, "dl");
6062 	import core.sys.posix.dlfcn;
6063 
6064 	/++
6065 		Allows for sending synthetic input to the X server via the Xtst
6066 		extension.
6067 
6068 		Please remember user input is meant to be user - don't use this
6069 		if you have some other alternative!
6070 
6071 		If you need this on Windows btw, the top-level [sendSyntheticInput] shows
6072 		the Win32 api to start it, but I only did basics there, PR welcome if you like,
6073 		it is an easy enough function to use.
6074 
6075 		History: Added May 17, 2020.
6076 	+/
6077 	struct SyntheticInput {
6078 		@disable this();
6079 
6080 		private void* lib;
6081 		private int* refcount;
6082 
6083 		private extern(C) {
6084 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6085 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6086 		}
6087 
6088 		/// The dummy param must be 0.
6089 		this(int dummy) {
6090 			lib = dlopen("libXtst.so", RTLD_NOW);
6091 			if(lib is null)
6092 				throw new Exception("cannot load xtest lib extension");
6093 			scope(failure)
6094 				dlclose(lib);
6095 
6096 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6097 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6098 
6099 			if(XTestFakeKeyEvent is null)
6100 				throw new Exception("No XTestFakeKeyEvent");
6101 			if(XTestFakeButtonEvent is null)
6102 				throw new Exception("No XTestFakeButtonEvent");
6103 
6104 			refcount = new int;
6105 			*refcount = 1;
6106 		}
6107 
6108 		this(this) {
6109 			if(refcount)
6110 				*refcount += 1;
6111 		}
6112 
6113 		~this() {
6114 			if(refcount) {
6115 				*refcount -= 1;
6116 				if(*refcount == 0)
6117 					// I commented this because if I close the lib before
6118 					// XCloseDisplay, it is liable to segfault... so just
6119 					// gonna keep it loaded if it is loaded, no big deal
6120 					// anyway.
6121 					{} // dlclose(lib);
6122 			}
6123 		}
6124 
6125 		/// This ONLY works with basic ascii!
6126 		void sendSyntheticInput(string s) {
6127 			int delay = 0;
6128 			foreach(ch; s) {
6129 				pressKey(cast(Key) ch, true, delay);
6130 				pressKey(cast(Key) ch, false, delay);
6131 				delay += 5;
6132 			}
6133 		}
6134 
6135 		/++
6136 			Sends a fake press key event.
6137 
6138 			Please note you need to call [flushGui] or return to the event loop for this to actually be sent.
6139 		+/
6140 		void pressKey(Key key, bool pressed, int delay = 0) {
6141 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6142 		}
6143 
6144 		///
6145 		void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6146 			int btn;
6147 
6148 			switch(button) {
6149 				case MouseButton.left: btn = 1; break;
6150 				case MouseButton.middle: btn = 2; break;
6151 				case MouseButton.right: btn = 3; break;
6152 				case MouseButton.wheelUp: btn = 4; break;
6153 				case MouseButton.wheelDown: btn = 5; break;
6154 				case MouseButton.backButton: btn = 8; break;
6155 				case MouseButton.forwardButton: btn = 9; break;
6156 				default:
6157 			}
6158 
6159 			assert(btn);
6160 
6161 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6162 		}
6163 
6164 		///
6165 		static void moveMouseArrowBy(int dx, int dy) {
6166 			auto disp = XDisplayConnection.get();
6167 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
6168 			XFlush(disp);
6169 		}
6170 
6171 		///
6172 		static void moveMouseArrowTo(int x, int y) {
6173 			auto disp = XDisplayConnection.get();
6174 			auto root = RootWindow(disp, DefaultScreen(disp));
6175 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
6176 			XFlush(disp);
6177 		}
6178 	}
6179 }
6180 
6181 
6182 
6183 /++
6184 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
6185 
6186 	See_Also:
6187 	$(LIST
6188 		*[ScreenPainter]
6189 		*[ScreenPainter.rasterOp]
6190 	)
6191 +/
6192 enum RasterOp {
6193 	normal, /// Replaces the pixel.
6194 	xor, /// Uses bitwise xor to draw.
6195 }
6196 
6197 // being phobos-free keeps the size WAY down
6198 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
6199 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
6200 package(arsd) const(wchar)* toWStringz(string s) {
6201 	wstring r;
6202 	foreach(dchar c; s)
6203 		r ~= c;
6204 	r ~= '\0';
6205 	return r.ptr;
6206 }
6207 private string[] split(in void[] a, char c) {
6208 		string[] ret;
6209 		size_t previous = 0;
6210 		foreach(i, char ch; cast(ubyte[]) a) {
6211 			if(ch == c) {
6212 				ret ~= cast(string) a[previous .. i];
6213 				previous = i + 1;
6214 			}
6215 		}
6216 		if(previous != a.length)
6217 			ret ~= cast(string) a[previous .. $];
6218 		return ret;
6219 	}
6220 
6221 version(without_opengl) {
6222 	enum OpenGlOptions {
6223 		no,
6224 	}
6225 } else {
6226 	/++
6227 		Determines if you want an OpenGL context created on the new window.
6228 
6229 
6230 		See more: [#topics-3d|in the 3d topic].
6231 
6232 		---
6233 		import arsd.simpledisplay;
6234 		void main() {
6235 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
6236 
6237 			// Set up the matrix
6238 			window.setAsCurrentOpenGlContext(); // make this window active
6239 
6240 			// This is called on each frame, we will draw our scene
6241 			window.redrawOpenGlScene = delegate() {
6242 
6243 			};
6244 
6245 			window.eventLoop(0);
6246 		}
6247 		---
6248 	+/
6249 	enum OpenGlOptions {
6250 		no, /// No OpenGL context is created
6251 		yes, /// Yes, create an OpenGL context
6252 	}
6253 
6254 	version(X11) {
6255 		static if (!SdpyIsUsingIVGLBinds) {
6256 
6257 
6258 			struct __GLXFBConfigRec {}
6259 			alias GLXFBConfig = __GLXFBConfigRec*;
6260 
6261 			//pragma(lib, "GL");
6262 			//pragma(lib, "GLU");
6263 			interface GLX {
6264 			extern(C) nothrow @nogc {
6265 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
6266 						const int *attrib_list);
6267 
6268 				 void glXCopyContext(Display *dpy, GLXContext src,
6269 						GLXContext dst, arch_ulong mask);
6270 
6271 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
6272 						GLXContext share_list, Bool direct);
6273 
6274 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
6275 						Pixmap pixmap);
6276 
6277 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
6278 
6279 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
6280 
6281 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
6282 						int attrib, int *value);
6283 
6284 				 GLXContext glXGetCurrentContext();
6285 
6286 				 GLXDrawable glXGetCurrentDrawable();
6287 
6288 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
6289 
6290 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
6291 						GLXContext ctx);
6292 
6293 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
6294 
6295 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
6296 
6297 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
6298 
6299 				 void glXUseXFont(Font font, int first, int count, int list_base);
6300 
6301 				 void glXWaitGL();
6302 
6303 				 void glXWaitX();
6304 
6305 
6306 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
6307 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
6308 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
6309 
6310 				char* glXQueryExtensionsString (Display*, int);
6311 				void* glXGetProcAddress (const(char)*);
6312 
6313 			}
6314 			}
6315 
6316 			version(OSX)
6317 			mixin DynamicLoad!(GLX, "GL", 0, true) glx;
6318 			else
6319 			mixin DynamicLoad!(GLX, "GLX", 0, true) glx;
6320 			shared static this() {
6321 				glx.loadDynamicLibrary();
6322 			}
6323 
6324 			alias glbindGetProcAddress = glXGetProcAddress;
6325 		}
6326 	} else version(Windows) {
6327 		/* it is done below by interface GL */
6328 	} else
6329 		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.");
6330 }
6331 
6332 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
6333 alias Resizablity = Resizability;
6334 
6335 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
6336 enum Resizability {
6337 	fixedSize, /// the window cannot be resized
6338 	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.
6339 	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.
6340 
6341 	// FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events
6342 }
6343 
6344 
6345 /++
6346 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
6347 +/
6348 enum TextAlignment : uint {
6349 	Left = 0, ///
6350 	Center = 1, ///
6351 	Right = 2, ///
6352 
6353 	VerticalTop = 0, ///
6354 	VerticalCenter = 4, ///
6355 	VerticalBottom = 8, ///
6356 }
6357 
6358 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
6359 alias Rectangle = arsd.color.Rectangle;
6360 
6361 
6362 /++
6363 	Keyboard press and release events
6364 +/
6365 struct KeyEvent {
6366 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
6367 	Key key;
6368 	ubyte hardwareCode; /// A platform and hardware specific code for the key
6369 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
6370 
6371 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
6372 
6373 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
6374 
6375 	SimpleWindow window; /// associated Window
6376 
6377 	/++
6378 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
6379 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
6380 		to predict if char events are actually coming..
6381 
6382 		Only available on X systems since this information is not given ahead of time elsewhere.
6383 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
6384 
6385 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
6386 		and potential quirks I'd recommend avoiding it.
6387 
6388 		History:
6389 			Added April 26, 2021 (dub v9.5)
6390 	+/
6391 	version(X11)
6392 		dchar[] charsPossible;
6393 
6394 	// convert key event to simplified string representation a-la emacs
6395 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
6396 		uint dpos = 0;
6397 		void put (const(char)[] s...) nothrow @trusted {
6398 			static if (growdest) {
6399 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
6400 			} else {
6401 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
6402 			}
6403 		}
6404 
6405 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
6406 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
6407 		}
6408 
6409 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
6410 
6411 		// put modifiers
6412 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
6413 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
6414 		putMod(ModifierState.alt, Key.Alt, "Alt+");
6415 		putMod(ModifierState.windows, Key.Shift, "Windows+");
6416 		putMod(ModifierState.shift, Key.Shift, "Shift+");
6417 
6418 		if (this.key) {
6419 			foreach (string kn; __traits(allMembers, Key)) {
6420 				if (this.key == __traits(getMember, Key, kn)) {
6421 					// HACK!
6422 					static if (kn == "N0") put("0");
6423 					else static if (kn == "N1") put("1");
6424 					else static if (kn == "N2") put("2");
6425 					else static if (kn == "N3") put("3");
6426 					else static if (kn == "N4") put("4");
6427 					else static if (kn == "N5") put("5");
6428 					else static if (kn == "N6") put("6");
6429 					else static if (kn == "N7") put("7");
6430 					else static if (kn == "N8") put("8");
6431 					else static if (kn == "N9") put("9");
6432 					else put(kn);
6433 					return dest[0..dpos];
6434 				}
6435 			}
6436 			put("Unknown");
6437 		} else {
6438 			if (dpos && dest[dpos-1] == '+') --dpos;
6439 		}
6440 		return dest[0..dpos];
6441 	}
6442 
6443 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
6444 
6445 	/** Parse string into key name with modifiers. It accepts things like:
6446 	 *
6447 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
6448 	 *
6449 	 * Ctrl+Win+1 -- windows style
6450 	 *
6451 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
6452 	 *
6453 	 * Ctrl Win 1 -- and space
6454 	 *
6455 	 * and even "Win + 1 + Ctrl".
6456 	 */
6457 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
6458 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
6459 
6460 		// remove trailing spaces
6461 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
6462 
6463 		// tokens delimited by blank, '+', or '-'
6464 		// null on eol
6465 		const(char)[] getToken () nothrow @trusted @nogc {
6466 			// remove leading spaces and delimiters
6467 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
6468 			if (name.length == 0) return null; // oops, no more tokens
6469 			// get token
6470 			size_t epos = 0;
6471 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
6472 			assert(epos > 0 && epos <= name.length);
6473 			auto res = name[0..epos];
6474 			name = name[epos..$];
6475 			return res;
6476 		}
6477 
6478 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
6479 			if (s0.length != s1.length) return false;
6480 			foreach (immutable ci, char c0; s0) {
6481 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
6482 				char c1 = s1[ci];
6483 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
6484 				if (c0 != c1) return false;
6485 			}
6486 			return true;
6487 		}
6488 
6489 		if (ignoreModsOut !is null) *ignoreModsOut = false;
6490 		if (updown !is null) *updown = -1;
6491 		KeyEvent res;
6492 		res.key = cast(Key)0; // just in case
6493 		const(char)[] tk, tkn; // last token
6494 		bool allowEmascStyle = true;
6495 		bool ignoreModifiers = false;
6496 		tokenloop: for (;;) {
6497 			tk = tkn;
6498 			tkn = getToken();
6499 			//k8: yay, i took "Bloody Mess" trait from Fallout!
6500 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
6501 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
6502 			if (allowEmascStyle && tkn.length != 0) {
6503 				if (tk.length == 1) {
6504 					char mdc = tk[0];
6505 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
6506 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
6507 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
6508 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
6509 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
6510 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
6511 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
6512 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
6513 				}
6514 			}
6515 			allowEmascStyle = false;
6516 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
6517 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
6518 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
6519 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
6520 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
6521 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
6522 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
6523 			if (tk.length == 0) continue;
6524 			// try key name
6525 			if (res.key == 0) {
6526 				// little hack
6527 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
6528 					final switch (tk[0]) {
6529 						case '0': tk = "N0"; break;
6530 						case '1': tk = "N1"; break;
6531 						case '2': tk = "N2"; break;
6532 						case '3': tk = "N3"; break;
6533 						case '4': tk = "N4"; break;
6534 						case '5': tk = "N5"; break;
6535 						case '6': tk = "N6"; break;
6536 						case '7': tk = "N7"; break;
6537 						case '8': tk = "N8"; break;
6538 						case '9': tk = "N9"; break;
6539 					}
6540 				}
6541 				foreach (string kn; __traits(allMembers, Key)) {
6542 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
6543 				}
6544 			}
6545 			// unknown or duplicate key name, get out of here
6546 			break;
6547 		}
6548 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
6549 		return res; // something
6550 	}
6551 
6552 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
6553 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
6554 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
6555 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
6556 		}
6557 		bool ignoreMods;
6558 		int updown;
6559 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
6560 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
6561 		if (this.key != ke.key) {
6562 			// things like "ctrl+alt" are complicated
6563 			uint tkm = this.modifierState&modmask;
6564 			uint kkm = ke.modifierState&modmask;
6565 			Key tk = this.key;
6566 			// ke
6567 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
6568 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
6569 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
6570 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
6571 			// this
6572 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
6573 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
6574 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
6575 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
6576 			return (tk == ke.key && tkm == kkm);
6577 		}
6578 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
6579 	}
6580 }
6581 
6582 /// sets the application name.
6583 @property string ApplicationName(string name) {
6584 	return _applicationName = name;
6585 }
6586 
6587 string _applicationName;
6588 
6589 /// ditto
6590 @property string ApplicationName() {
6591 	if(_applicationName is null) {
6592 		import core.runtime;
6593 		return Runtime.args[0];
6594 	}
6595 	return _applicationName;
6596 }
6597 
6598 
6599 /// Type of a [MouseEvent]
6600 enum MouseEventType : int {
6601 	motion = 0, /// The mouse moved inside the window
6602 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
6603 	buttonReleased = 2, /// A mouse button was released
6604 }
6605 
6606 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
6607 /++
6608 	Listen for this on your event listeners if you are interested in mouse action.
6609 
6610 	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.
6611 
6612 	Examples:
6613 
6614 	This will draw boxes on the window with the mouse as you hold the left button.
6615 	---
6616 	import arsd.simpledisplay;
6617 
6618 	void main() {
6619 		auto window = new SimpleWindow();
6620 
6621 		window.eventLoop(0,
6622 			(MouseEvent ev) {
6623 				if(ev.modifierState & ModifierState.leftButtonDown) {
6624 					auto painter = window.draw();
6625 					painter.fillColor = Color.red;
6626 					painter.outlineColor = Color.black;
6627 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
6628 				}
6629 			}
6630 		);
6631 	}
6632 	---
6633 +/
6634 struct MouseEvent {
6635 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
6636 
6637 	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.
6638 	int y; /// Current Y position of the cursor when the event fired.
6639 
6640 	int dx; /// Change in X position since last report
6641 	int dy; /// Change in Y position since last report
6642 
6643 	MouseButton button; /// See [MouseButton]
6644 	int modifierState; /// See [ModifierState]
6645 
6646 	version(X11)
6647 		private Time timestamp;
6648 
6649 	/// Returns a linear representation of mouse button,
6650 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
6651 	///
6652 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
6653 	@property ubyte buttonLinear() const {
6654 		import core.bitop;
6655 		if(button == 0)
6656 			return 0;
6657 		return (bsf(button) + 1) & 0b1111;
6658 	}
6659 
6660 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
6661 
6662 	SimpleWindow window; /// The window in which the event happened.
6663 
6664 	Point globalCoordinates() {
6665 		Point p;
6666 		if(window is null)
6667 			throw new Exception("wtf");
6668 		static if(UsingSimpledisplayX11) {
6669 			Window child;
6670 			XTranslateCoordinates(
6671 				XDisplayConnection.get,
6672 				window.impl.window,
6673 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
6674 				x, y, &p.x, &p.y, &child);
6675 			return p;
6676 		} else version(Windows) {
6677 			POINT[1] points;
6678 			points[0].x = x;
6679 			points[0].y = y;
6680 			MapWindowPoints(
6681 				window.impl.hwnd,
6682 				null,
6683 				points.ptr,
6684 				points.length
6685 			);
6686 			p.x = points[0].x;
6687 			p.y = points[0].y;
6688 
6689 			return p;
6690 		} else version(OSXCocoa) {
6691 			throw new NotYetImplementedException();
6692 		} else static assert(0);
6693 	}
6694 
6695 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
6696 
6697 	/**
6698 	can contain emacs-like modifier prefix
6699 	case-insensitive names:
6700 		lmbX/leftX
6701 		rmbX/rightX
6702 		mmbX/middleX
6703 		wheelX
6704 		motion (no prefix allowed)
6705 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
6706 	*/
6707 	static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
6708 		if (str.length == 0) return false; // just in case
6709 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
6710 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
6711 		auto anchor = str;
6712 		uint mods = 0; // uint.max == any
6713 		// interesting bits in kmod
6714 		uint kmodmask =
6715 			ModifierState.shift|
6716 			ModifierState.ctrl|
6717 			ModifierState.alt|
6718 			ModifierState.windows|
6719 			ModifierState.leftButtonDown|
6720 			ModifierState.middleButtonDown|
6721 			ModifierState.rightButtonDown|
6722 			0;
6723 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
6724 		bool wasButtons = false;
6725 		while (str.length) {
6726 			if (str.ptr[0] <= ' ') {
6727 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
6728 				continue;
6729 			}
6730 			// one-letter modifier?
6731 			if (str.length >= 2 && str.ptr[1] == '-') {
6732 				switch (str.ptr[0]) {
6733 					case '*': // "any" modifier (cannot be undone)
6734 						mods = mods.max;
6735 						break;
6736 					case 'C': case 'c': // emacs "ctrl"
6737 						if (mods != mods.max) mods |= ModifierState.ctrl;
6738 						break;
6739 					case 'M': case 'm': // emacs "meta"
6740 						if (mods != mods.max) mods |= ModifierState.alt;
6741 						break;
6742 					case 'S': case 's': // emacs "shift"
6743 						if (mods != mods.max) mods |= ModifierState.shift;
6744 						break;
6745 					case 'H': case 'h': // emacs "hyper" (aka winkey)
6746 						if (mods != mods.max) mods |= ModifierState.windows;
6747 						break;
6748 					default:
6749 						return false; // unknown modifier
6750 				}
6751 				str = str[2..$];
6752 				continue;
6753 			}
6754 			// word
6755 			char[16] buf = void; // locased
6756 			auto wep = 0;
6757 			while (str.length) {
6758 				immutable char ch = str.ptr[0];
6759 				if (ch <= ' ' || ch == '-') break;
6760 				str = str[1..$];
6761 				if (wep > buf.length) return false; // too long
6762 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
6763 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
6764 				else return false; // invalid char
6765 			}
6766 			if (wep == 0) return false; // just in case
6767 			uint bnum;
6768 			enum UpDown { None = -1, Up, Down, Any }
6769 			auto updown = UpDown.None; // 0: up; 1: down
6770 			switch (buf[0..wep]) {
6771 				// left button
6772 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
6773 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
6774 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
6775 				case "lmb": case "left": bnum = 0; break;
6776 				// middle button
6777 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
6778 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
6779 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
6780 				case "mmb": case "middle": bnum = 1; break;
6781 				// right button
6782 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
6783 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
6784 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
6785 				case "rmb": case "right": bnum = 2; break;
6786 				// wheel
6787 				case "wheelup": updown = UpDown.Up; goto case "wheel";
6788 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
6789 				case "wheelany": updown = UpDown.Any; goto case "wheel";
6790 				case "wheel": bnum = 3; break;
6791 				// motion
6792 				case "motion": bnum = 7; break;
6793 				// unknown
6794 				default: return false;
6795 			}
6796 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
6797 			// parse possible "-up" or "-down"
6798 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
6799 				wep = 0;
6800 				foreach (immutable idx, immutable char ch; str[1..$]) {
6801 					if (ch <= ' ' || ch == '-') break;
6802 					assert(idx == wep); // for now; trick
6803 					if (wep > buf.length) { wep = 0; break; } // too long
6804 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
6805 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
6806 					else { wep = 0; break; } // invalid char
6807 				}
6808 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
6809 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
6810 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
6811 				// remove parsed part
6812 				if (updown != UpDown.None) str = str[wep+1..$];
6813 			}
6814 			if (updown == UpDown.None) {
6815 				updown = UpDown.Down;
6816 			}
6817 			wasButtons = wasButtons || (bnum <= 2);
6818 			//assert(updown != UpDown.None);
6819 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
6820 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
6821 			if (lastButt != lastButt.max) {
6822 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
6823 				if (mods != mods.max) {
6824 					uint butbit = 0;
6825 					final switch (lastButt&0x03) {
6826 						case 0: butbit = ModifierState.leftButtonDown; break;
6827 						case 1: butbit = ModifierState.middleButtonDown; break;
6828 						case 2: butbit = ModifierState.rightButtonDown; break;
6829 					}
6830 					     if (lastButt&Flag.Down) mods |= butbit;
6831 					else if (lastButt&Flag.Up) mods &= ~butbit;
6832 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
6833 				}
6834 			}
6835 			// remember last button
6836 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
6837 		}
6838 		// no button -- nothing to do
6839 		if (lastButt == lastButt.max) return false;
6840 		// done parsing, check if something's left
6841 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
6842 		// remove action button from mask
6843 		if ((lastButt&0xff) < 3) {
6844 			final switch (lastButt&0x03) {
6845 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
6846 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
6847 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
6848 			}
6849 		}
6850 		// special case: "Motion" means "ignore buttons"
6851 		if ((lastButt&0xff) == 7 && !wasButtons) {
6852 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
6853 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
6854 		}
6855 		uint kmod = event.modifierState&kmodmask;
6856 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
6857 		// check modifier state
6858 		if (mods != mods.max) {
6859 			if (kmod != mods) return false;
6860 		}
6861 		// now check type
6862 		if ((lastButt&0xff) == 7) {
6863 			// motion
6864 			if (event.type != MouseEventType.motion) return false;
6865 		} else if ((lastButt&0xff) == 3) {
6866 			// wheel
6867 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
6868 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
6869 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
6870 			return false;
6871 		} else {
6872 			// buttons
6873 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
6874 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
6875 			{
6876 				return false;
6877 			}
6878 			// button number
6879 			switch (lastButt&0x03) {
6880 				case 0: if (event.button != MouseButton.left) return false; break;
6881 				case 1: if (event.button != MouseButton.middle) return false; break;
6882 				case 2: if (event.button != MouseButton.right) return false; break;
6883 				default: return false;
6884 			}
6885 		}
6886 		return true;
6887 	}
6888 }
6889 
6890 version(arsd_mevent_strcmp_test) unittest {
6891 	MouseEvent event;
6892 	event.type = MouseEventType.buttonPressed;
6893 	event.button = MouseButton.left;
6894 	event.modifierState = ModifierState.ctrl;
6895 	assert(event == "C-LMB");
6896 	assert(event != "C-LMBUP");
6897 	assert(event != "C-LMB-UP");
6898 	assert(event != "C-S-LMB");
6899 	assert(event == "*-LMB");
6900 	assert(event != "*-LMB-UP");
6901 
6902 	event.type = MouseEventType.buttonReleased;
6903 	assert(event != "C-LMB");
6904 	assert(event == "C-LMBUP");
6905 	assert(event == "C-LMB-UP");
6906 	assert(event != "C-S-LMB");
6907 	assert(event != "*-LMB");
6908 	assert(event == "*-LMB-UP");
6909 
6910 	event.button = MouseButton.right;
6911 	event.modifierState |= ModifierState.shift;
6912 	event.type = MouseEventType.buttonPressed;
6913 	assert(event != "C-LMB");
6914 	assert(event != "C-LMBUP");
6915 	assert(event != "C-LMB-UP");
6916 	assert(event != "C-S-LMB");
6917 	assert(event != "*-LMB");
6918 	assert(event != "*-LMB-UP");
6919 
6920 	assert(event != "C-RMB");
6921 	assert(event != "C-RMBUP");
6922 	assert(event != "C-RMB-UP");
6923 	assert(event == "C-S-RMB");
6924 	assert(event == "*-RMB");
6925 	assert(event != "*-RMB-UP");
6926 }
6927 
6928 /// This gives a few more options to drawing lines and such
6929 struct Pen {
6930 	Color color; /// the foreground color
6931 	int width = 1; /// width of the line. please note that on X, wide lines are drawn centered on the coordinates, so you may have to offset things.
6932 	Style style; /// See [Style]
6933 /+
6934 // From X.h
6935 
6936 #define LineSolid		0
6937 #define LineOnOffDash		1
6938 #define LineDoubleDash		2
6939        LineDou-        The full path of the line is drawn, but the
6940        bleDash         even dashes are filled differently from the
6941                        odd dashes (see fill-style) with CapButt
6942                        style used where even and odd dashes meet.
6943 
6944 
6945 
6946 /* capStyle */
6947 
6948 #define CapNotLast		0
6949 #define CapButt			1
6950 #define CapRound		2
6951 #define CapProjecting		3
6952 
6953 /* joinStyle */
6954 
6955 #define JoinMiter		0
6956 #define JoinRound		1
6957 #define JoinBevel		2
6958 
6959 /* fillStyle */
6960 
6961 #define FillSolid		0
6962 #define FillTiled		1
6963 #define FillStippled		2
6964 #define FillOpaqueStippled	3
6965 
6966 
6967 +/
6968 	/// Style of lines drawn
6969 	enum Style {
6970 		Solid, /// a solid line
6971 		Dashed, /// a dashed line
6972 		Dotted, /// a dotted line
6973 	}
6974 }
6975 
6976 
6977 /++
6978 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
6979 
6980 
6981 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
6982 
6983 	$(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.)
6984 
6985 	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.
6986 
6987 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
6988 
6989 	$(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.
6990 
6991 	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!
6992 
6993 	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!)
6994 
6995 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
6996 
6997 	---
6998 		auto image = new Image(256, 256);
6999 		scope(exit) destroy(image);
7000 	---
7001 
7002 	As long as you don't hold on to it outside the scope.
7003 
7004 	I might change it to be an owned pointer at some point in the future.
7005 
7006 	)
7007 
7008 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7009 	you can also often get a fair amount of speedup by getting the raw data format and
7010 	writing some custom code.
7011 
7012 	FIXME INSERT EXAMPLES HERE
7013 
7014 
7015 +/
7016 final class Image {
7017 	///
7018 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7019 		this.width = width;
7020 		this.height = height;
7021 		this.enableAlpha = enableAlpha;
7022 
7023 		impl.createImage(width, height, forcexshm, enableAlpha);
7024 	}
7025 
7026 	///
7027 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7028 		this(size.width, size.height, forcexshm, enableAlpha);
7029 	}
7030 
7031 	private bool suppressDestruction;
7032 
7033 	version(X11)
7034 	this(XImage* handle) {
7035 		this.handle = handle;
7036 		this.rawData = cast(ubyte*) handle.data;
7037 		this.width = handle.width;
7038 		this.height = handle.height;
7039 		this.enableAlpha = handle.depth == 32;
7040 		suppressDestruction = true;
7041 	}
7042 
7043 	~this() {
7044 		if(suppressDestruction) return;
7045 		impl.dispose();
7046 	}
7047 
7048 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7049 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7050 	pure const @system nothrow {
7051 		/*
7052 			To use these to draw a blue rectangle with size WxH at position X,Y...
7053 
7054 			// make certain that it will fit before we proceed
7055 			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!
7056 
7057 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7058 			// (though calculating them isn't really that expensive).
7059 			auto nextLineAdjustment = img.adjustmentForNextLine();
7060 			auto offR = img.redByteOffset();
7061 			auto offB = img.blueByteOffset();
7062 			auto offG = img.greenByteOffset();
7063 			auto bpp = img.bytesPerPixel();
7064 
7065 			auto data = img.getDataPointer();
7066 
7067 			// figure out the starting byte offset
7068 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7069 
7070 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7071 
7072 			// and now our drawing loop for the rectangle
7073 			foreach(y; 0 .. H) {
7074 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7075 				foreach(x; 0 .. W) {
7076 					// write our color
7077 					data[offR] = 0;
7078 					data[offG] = 0;
7079 					data[offB] = 255;
7080 
7081 					data += bpp; // moving to the next pixel is just an addition...
7082 				}
7083 				startOfLine += nextLineAdjustment;
7084 			}
7085 
7086 
7087 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7088 
7089 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7090 			can be made into a bitmask or something so we can write them as *uint...
7091 		*/
7092 
7093 		///
7094 		int offsetForTopLeftPixel() {
7095 			version(X11) {
7096 				return 0;
7097 			} else version(Windows) {
7098 				if(enableAlpha) {
7099 					return (width * 4) * (height - 1);
7100 				} else {
7101 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7102 				}
7103 			} else version(OSXCocoa) {
7104 				return 0 ; //throw new NotYetImplementedException();
7105 			} else static assert(0, "fill in this info for other OSes");
7106 		}
7107 
7108 		///
7109 		int offsetForPixel(int x, int y) {
7110 			version(X11) {
7111 				auto offset = (y * width + x) * 4;
7112 				return offset;
7113 			} else version(Windows) {
7114 				if(enableAlpha) {
7115 					auto itemsPerLine = width * 4;
7116 					// remember, bmps are upside down
7117 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7118 					return offset;
7119 				} else {
7120 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7121 					// remember, bmps are upside down
7122 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7123 					return offset;
7124 				}
7125 			} else version(OSXCocoa) {
7126 				return 0 ; //throw new NotYetImplementedException();
7127 			} else static assert(0, "fill in this info for other OSes");
7128 		}
7129 
7130 		///
7131 		int adjustmentForNextLine() {
7132 			version(X11) {
7133 				return width * 4;
7134 			} else version(Windows) {
7135 				// windows bmps are upside down, so the adjustment is actually negative
7136 				if(enableAlpha)
7137 					return - (cast(int) width * 4);
7138 				else
7139 					return -((cast(int) width * 3 + 3) / 4) * 4;
7140 			} else version(OSXCocoa) {
7141 				return 0 ; //throw new NotYetImplementedException();
7142 			} else static assert(0, "fill in this info for other OSes");
7143 		}
7144 
7145 		/// once you have the position of a pixel, use these to get to the proper color
7146 		int redByteOffset() {
7147 			version(X11) {
7148 				return 2;
7149 			} else version(Windows) {
7150 				return 2;
7151 			} else version(OSXCocoa) {
7152 				return 0 ; //throw new NotYetImplementedException();
7153 			} else static assert(0, "fill in this info for other OSes");
7154 		}
7155 
7156 		///
7157 		int greenByteOffset() {
7158 			version(X11) {
7159 				return 1;
7160 			} else version(Windows) {
7161 				return 1;
7162 			} else version(OSXCocoa) {
7163 				return 0 ; //throw new NotYetImplementedException();
7164 			} else static assert(0, "fill in this info for other OSes");
7165 		}
7166 
7167 		///
7168 		int blueByteOffset() {
7169 			version(X11) {
7170 				return 0;
7171 			} else version(Windows) {
7172 				return 0;
7173 			} else version(OSXCocoa) {
7174 				return 0 ; //throw new NotYetImplementedException();
7175 			} else static assert(0, "fill in this info for other OSes");
7176 		}
7177 
7178 		/// Only valid if [enableAlpha] is true
7179 		int alphaByteOffset() {
7180 			version(X11) {
7181 				return 3;
7182 			} else version(Windows) {
7183 				return 3;
7184 			} else version(OSXCocoa) {
7185 				return 3; //throw new NotYetImplementedException();
7186 			} else static assert(0, "fill in this info for other OSes");
7187 		}
7188 	}
7189 
7190 	///
7191 	final void putPixel(int x, int y, Color c) {
7192 		if(x < 0 || x >= width)
7193 			return;
7194 		if(y < 0 || y >= height)
7195 			return;
7196 
7197 		impl.setPixel(x, y, c);
7198 	}
7199 
7200 	///
7201 	final Color getPixel(int x, int y) {
7202 		if(x < 0 || x >= width)
7203 			return Color.transparent;
7204 		if(y < 0 || y >= height)
7205 			return Color.transparent;
7206 
7207 		version(OSXCocoa) throw new NotYetImplementedException(); else
7208 		return impl.getPixel(x, y);
7209 	}
7210 
7211 	///
7212 	final void opIndexAssign(Color c, int x, int y) {
7213 		putPixel(x, y, c);
7214 	}
7215 
7216 	///
7217 	TrueColorImage toTrueColorImage() {
7218 		auto tci = new TrueColorImage(width, height);
7219 		convertToRgbaBytes(tci.imageData.bytes);
7220 		return tci;
7221 	}
7222 
7223 	///
7224 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) {
7225 		auto tci = i.getAsTrueColorImage();
7226 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
7227 		img.setRgbaBytes(tci.imageData.bytes);
7228 		return img;
7229 	}
7230 
7231 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
7232 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
7233 	/// if you pass null, it will allocate a new one.
7234 	ubyte[] getRgbaBytes(ubyte[] where = null) {
7235 		if(where is null)
7236 			where = new ubyte[this.width*this.height*4];
7237 		convertToRgbaBytes(where);
7238 		return where;
7239 	}
7240 
7241 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
7242 	void setRgbaBytes(in ubyte[] from ) {
7243 		assert(from.length == this.width * this.height * 4);
7244 		setFromRgbaBytes(from);
7245 	}
7246 
7247 	// FIXME: make properly cross platform by getting rgba right
7248 
7249 	/// warning: this is not portable across platforms because the data format can change
7250 	ubyte* getDataPointer() {
7251 		return impl.rawData;
7252 	}
7253 
7254 	/// for use with getDataPointer
7255 	final int bytesPerLine() const pure @safe nothrow {
7256 		version(Windows)
7257 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
7258 		else version(X11)
7259 			return 4 * width;
7260 		else version(OSXCocoa)
7261 			return 4 * width;
7262 		else static assert(0);
7263 	}
7264 
7265 	/// for use with getDataPointer
7266 	final int bytesPerPixel() const pure @safe nothrow {
7267 		version(Windows)
7268 			return enableAlpha ? 4 : 3;
7269 		else version(X11)
7270 			return 4;
7271 		else version(OSXCocoa)
7272 			return 4;
7273 		else static assert(0);
7274 	}
7275 
7276 	///
7277 	immutable int width;
7278 
7279 	///
7280 	immutable int height;
7281 
7282 	///
7283 	immutable bool enableAlpha;
7284     //private:
7285 	mixin NativeImageImplementation!() impl;
7286 }
7287 
7288 /// A convenience function to pop up a window displaying the image.
7289 /// If you pass a win, it will draw the image in it. Otherwise, it will
7290 /// create a window with the size of the image and run its event loop, closing
7291 /// when a key is pressed.
7292 void displayImage(Image image, SimpleWindow win = null) {
7293 	if(win is null) {
7294 		win = new SimpleWindow(image);
7295 		{
7296 			auto p = win.draw;
7297 			p.drawImage(Point(0, 0), image);
7298 		}
7299 		win.eventLoop(0,
7300 			(KeyEvent ev) {
7301 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
7302 			} );
7303 	} else {
7304 		win.image = image;
7305 	}
7306 }
7307 
7308 enum FontWeight : int {
7309 	dontcare = 0,
7310 	thin = 100,
7311 	extralight = 200,
7312 	light = 300,
7313 	regular = 400,
7314 	medium = 500,
7315 	semibold = 600,
7316 	bold = 700,
7317 	extrabold = 800,
7318 	heavy = 900
7319 }
7320 
7321 // FIXME: i need a font cache and it needs to handle disconnects.
7322 
7323 /++
7324 	Represents a font loaded off the operating system or the X server.
7325 
7326 
7327 	While the api here is unified cross platform, the fonts are not necessarily
7328 	available, even across machines of the same platform, so be sure to always check
7329 	for null (using [isNull]) and have a fallback plan.
7330 
7331 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
7332 
7333 	Worst case, a null font will automatically fall back to the default font loaded
7334 	for your system.
7335 +/
7336 class OperatingSystemFont {
7337 	// FIXME: when the X Connection is lost, these need to be invalidated!
7338 	// that means I need to store the original stuff again to reconstruct it too.
7339 
7340 	version(X11) {
7341 		XFontStruct* font;
7342 		XFontSet fontset;
7343 
7344 		version(with_xft) {
7345 			XftFont* xftFont;
7346 			bool isXft;
7347 		}
7348 	} else version(Windows) {
7349 		HFONT font;
7350 		int width_;
7351 		int height_;
7352 	} else version(OSXCocoa) {
7353 		// FIXME
7354 	} else static assert(0);
7355 
7356 	/++
7357 		Constructs the class and immediately calls [load].
7358 	+/
7359 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7360 		load(name, size, weight, italic);
7361 	}
7362 
7363 	/++
7364 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
7365 
7366 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
7367 
7368 		History:
7369 			Added January 24, 2021.
7370 	+/
7371 	this() {
7372 		// this space intentionally left blank
7373 	}
7374 
7375 	/++
7376 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
7377 
7378 		History:
7379 			Added November 13, 2020.
7380 	+/
7381 	version(with_xft)
7382 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7383 		unload();
7384 
7385 		if(!XftLibrary.attempted) {
7386 			XftLibrary.loadDynamicLibrary();
7387 		}
7388 
7389 		if(!XftLibrary.loadSuccessful)
7390 			return false;
7391 
7392 		auto display = XDisplayConnection.get;
7393 
7394 		char[256] nameBuffer = void;
7395 		int nbp = 0;
7396 
7397 		void add(in char[] a) {
7398 			nameBuffer[nbp .. nbp + a.length] = a[];
7399 			nbp += a.length;
7400 		}
7401 		add(name);
7402 
7403 		if(size) {
7404 			add(":size=");
7405 			add(toInternal!string(size));
7406 		}
7407 		if(weight != FontWeight.dontcare) {
7408 			add(":weight=");
7409 			add(weightToString(weight));
7410 		}
7411 		if(italic)
7412 			add(":slant=100");
7413 
7414 		nameBuffer[nbp] = 0;
7415 
7416 		this.xftFont = XftFontOpenName(
7417 			display,
7418 			DefaultScreen(display),
7419 			nameBuffer.ptr
7420 		);
7421 
7422 		this.isXft = true;
7423 
7424 		if(xftFont !is null) {
7425 			isMonospace_ = stringWidth("x") == stringWidth("M");
7426 			ascent_ = xftFont.ascent;
7427 			descent_ = xftFont.descent;
7428 		}
7429 
7430 		return !isNull();
7431 	}
7432 
7433 	/++
7434 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
7435 
7436 
7437 		Fonts will be fed to you (possibly! it is platform and implementation dependent on if it is called immediately or later) asynchronously through the given delegate. It should return `true` if you want more, `false` if you are done. The delegate will be called once after finishing with a `init` value to let you know it is done and you can do final processing.
7438 
7439 		If `pattern` is null, it returns all available font families.
7440 
7441 		Please note that you may also receive fonts that do not match your given pattern. You should still filter them in the handler; the pattern is really just an optimization hint rather than a formal guarantee.
7442 
7443 		The format of the pattern is platform-specific.
7444 
7445 		History:
7446 			Added May 1, 2021 (dub v9.5)
7447 	+/
7448 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
7449 		version(Windows) {
7450 			auto hdc = GetDC(null);
7451 			scope(exit) ReleaseDC(null, hdc);
7452 			LOGFONT logfont;
7453 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
7454 				auto localHandler = *(cast(typeof(handler)*) p);
7455 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
7456 			}
7457 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
7458 		} else version(X11) {
7459 			//import core.stdc.stdio;
7460 			bool done = false;
7461 			version(with_xft) {
7462 				if(!XftLibrary.attempted) {
7463 					XftLibrary.loadDynamicLibrary();
7464 				}
7465 
7466 				if(!XftLibrary.loadSuccessful)
7467 					goto skipXft;
7468 
7469 				if(!FontConfigLibrary.attempted)
7470 					FontConfigLibrary.loadDynamicLibrary();
7471 				if(!FontConfigLibrary.loadSuccessful)
7472 					goto skipXft;
7473 
7474 				{
7475 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
7476 					if(got is null)
7477 						goto skipXft;
7478 					scope(exit) FcFontSetDestroy(got);
7479 
7480 					auto fontPatterns = got.fonts[0 .. got.nfont];
7481 					foreach(candidate; fontPatterns) {
7482 						char* where, whereStyle;
7483 
7484 						char* pmg = FcNameUnparse(candidate);
7485 
7486 						//FcPatternGetString(candidate, "family", 0, &where);
7487 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
7488 						//if(where && whereStyle) {
7489 						if(pmg) {
7490 							if(!handler(pmg.sliceCString))
7491 								return;
7492 							//printf("%s || %s %s\n", pmg, where, whereStyle);
7493 						}
7494 					}
7495 				}
7496 			}
7497 
7498 			skipXft:
7499 
7500 			if(pattern is null)
7501 				pattern = "*";
7502 
7503 			int count;
7504 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
7505 			scope(exit) XFreeFontNames(coreFontsRaw);
7506 
7507 			auto coreFonts = coreFontsRaw[0 .. count];
7508 
7509 			foreach(font; coreFonts) {
7510 				char[128] tmp;
7511 				tmp[0 ..5] = "core:";
7512 				auto cf = font.sliceCString;
7513 				if(5 + cf.length > tmp.length)
7514 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
7515 				tmp[5 .. 5 + cf.length] = cf;
7516 				if(!handler(tmp[0 .. 5 + cf.length]))
7517 					return;
7518 			}
7519 		}
7520 	}
7521 
7522 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
7523 
7524 	private string weightToString(FontWeight weight) {
7525 		with(FontWeight)
7526 		final switch(weight) {
7527 			case dontcare: return "*";
7528 			case thin: return "extralight";
7529 			case extralight: return "extralight";
7530 			case light: return "light";
7531 			case regular: return "regular";
7532 			case medium: return "medium";
7533 			case semibold: return "demibold";
7534 			case bold: return "bold";
7535 			case extrabold: return "demibold";
7536 			case heavy: return "black";
7537 		}
7538 	}
7539 
7540 	/++
7541 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
7542 
7543 		History:
7544 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
7545 	+/
7546 	version(X11)
7547 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7548 		unload();
7549 
7550 		string xfontstr;
7551 
7552 		if(name.length > 3 && name[0 .. 3] == "-*-") {
7553 			// this is kinda a disgusting hack but if the user sends an exact
7554 			// string I'd like to honor it...
7555 			xfontstr = name;
7556 		} else {
7557 			string weightstr = weightToString(weight);
7558 			string sizestr;
7559 			if(size == 0)
7560 				sizestr = "*";
7561 			else
7562 				sizestr = toInternal!string(size);
7563 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
7564 		}
7565 
7566 		//import std.stdio; writeln(xfontstr);
7567 
7568 		auto display = XDisplayConnection.get;
7569 
7570 		font = XLoadQueryFont(display, xfontstr.ptr);
7571 		if(font is null)
7572 			return false;
7573 
7574 		char** lol;
7575 		int lol2;
7576 		char* lol3;
7577 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
7578 
7579 		prepareFontInfo();
7580 
7581 		return !isNull();
7582 	}
7583 
7584 	version(X11)
7585 	private void prepareFontInfo() {
7586 		if(font !is null) {
7587 			isMonospace_ = stringWidth("l") == stringWidth("M");
7588 			ascent_ = font.max_bounds.ascent;
7589 			descent_ = font.max_bounds.descent;
7590 		}
7591 	}
7592 
7593 	/++
7594 		Loads a Windows font. You probably want to use [load] instead to be more generic.
7595 
7596 		History:
7597 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
7598 	+/
7599 	version(Windows)
7600 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
7601 		unload();
7602 
7603 		WCharzBuffer buffer = WCharzBuffer(name);
7604 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
7605 
7606 		prepareFontInfo(hdc);
7607 
7608 		return !isNull();
7609 	}
7610 
7611 	version(Windows)
7612 	void prepareFontInfo(HDC hdc = null) {
7613 		if(font is null)
7614 			return;
7615 
7616 		TEXTMETRIC tm;
7617 		auto dc = hdc ? hdc : GetDC(null);
7618 		auto orig = SelectObject(dc, font);
7619 		GetTextMetrics(dc, &tm);
7620 		SelectObject(dc, orig);
7621 		if(hdc is null)
7622 			ReleaseDC(null, dc);
7623 
7624 		width_ = tm.tmAveCharWidth;
7625 		height_ = tm.tmHeight;
7626 		ascent_ = tm.tmAscent;
7627 		descent_ = tm.tmDescent;
7628 		// 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.
7629 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
7630 	}
7631 
7632 
7633 	/++
7634 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
7635 
7636 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
7637 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
7638 
7639 		On Windows, it forwards directly to [loadWin32].
7640 
7641 		Params:
7642 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
7643 			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.
7644 			weight = approximate boldness, results may vary.
7645 			italic = try to get a slanted version of the given font.
7646 
7647 		History:
7648 			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.
7649 	+/
7650 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7651 		version(X11) {
7652 			version(with_xft) {
7653 				if(name.length > 5 && name[0 .. 5] == "core:") {
7654 					goto core;
7655 				}
7656 
7657 				if(loadXft(name, size, weight, italic))
7658 					return true;
7659 				// if xft fails, fallback to core to avoid breaking
7660 				// code that already depended on this.
7661 			}
7662 
7663 			core:
7664 
7665 			if(name.length > 5 && name[0 .. 5] == "core:") {
7666 				name = name[5 .. $];
7667 			}
7668 
7669 			return loadCoreX(name, size, weight, italic);
7670 		} else version(Windows) {
7671 			return loadWin32(name, size, weight, italic);
7672 		} else version(OSXCocoa) {
7673 			// FIXME
7674 			return false;
7675 		} else static assert(0);
7676 	}
7677 
7678 	///
7679 	void unload() {
7680 		if(isNull())
7681 			return;
7682 
7683 		version(X11) {
7684 			auto display = XDisplayConnection.display;
7685 
7686 			if(display is null)
7687 				return;
7688 
7689 			version(with_xft) {
7690 				if(isXft) {
7691 					if(xftFont)
7692 						XftFontClose(display, xftFont);
7693 					isXft = false;
7694 					xftFont = null;
7695 					return;
7696 				}
7697 			}
7698 
7699 			if(font && font !is ScreenPainterImplementation.defaultfont)
7700 				XFreeFont(display, font);
7701 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
7702 				XFreeFontSet(display, fontset);
7703 
7704 			font = null;
7705 			fontset = null;
7706 		} else version(Windows) {
7707 			DeleteObject(font);
7708 			font = null;
7709 		} else version(OSXCocoa) {
7710 			// FIXME
7711 		} else static assert(0);
7712 	}
7713 
7714 	private bool isMonospace_;
7715 
7716 	/++
7717 		History:
7718 			Added January 16, 2021
7719 	+/
7720 	bool isMonospace() {
7721 		return isMonospace_;
7722 	}
7723 
7724 	/++
7725 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
7726 
7727 		History:
7728 			Added March 26, 2020
7729 			Documented January 16, 2021
7730 	+/
7731 	int averageWidth() {
7732 		version(X11) {
7733 			return stringWidth("x");
7734 		} else version(Windows)
7735 			return width_;
7736 		else assert(0);
7737 	}
7738 
7739 	/++
7740 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
7741 
7742 		History:
7743 			Added January 16, 2021
7744 	+/
7745 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
7746 	// FIXME: what about tab?
7747 		if(isNull)
7748 			return 0;
7749 
7750 		version(X11) {
7751 			version(with_xft)
7752 				if(isXft && xftFont !is null) {
7753 					//return xftFont.max_advance_width;
7754 					XGlyphInfo extents;
7755 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
7756 					//import std.stdio; writeln(extents);
7757 					return extents.xOff;
7758 				}
7759 			if(font is null)
7760 				return 0;
7761 			else if(fontset) {
7762 				XRectangle rect;
7763 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
7764 
7765 				return rect.width;
7766 			} else {
7767 				return XTextWidth(font, s.ptr, cast(int) s.length);
7768 			}
7769 		} else version(Windows) {
7770 			WCharzBuffer buffer = WCharzBuffer(s);
7771 
7772 			return stringWidth(buffer.slice, window);
7773 		}
7774 		else assert(0);
7775 	}
7776 
7777 	version(Windows)
7778 	/// ditto
7779 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
7780 		if(isNull)
7781 			return 0;
7782 		version(Windows) {
7783 			SIZE size;
7784 
7785 			prepareContext(window);
7786 			scope(exit) releaseContext();
7787 
7788 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
7789 
7790 			return size.cx;
7791 		} else {
7792 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
7793 			static assert(0, "not implemented yet");
7794 			//return stringWidth(s, window);
7795 		}
7796 	}
7797 
7798 	private {
7799 		int prepRefcount;
7800 
7801 		version(Windows) {
7802 			HDC dc;
7803 			HANDLE orig;
7804 			HWND hwnd;
7805 		}
7806 	}
7807 	/++
7808 		[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.
7809 
7810 		History:
7811 			Added January 23, 2021
7812 	+/
7813 	void prepareContext(SimpleWindow window = null) {
7814 		prepRefcount++;
7815 		if(prepRefcount == 1) {
7816 			version(Windows) {
7817 				hwnd = window is null ? null : window.impl.hwnd;
7818 				dc = GetDC(hwnd);
7819 				orig = SelectObject(dc, font);
7820 			}
7821 		}
7822 	}
7823 	/// ditto
7824 	void releaseContext() {
7825 		prepRefcount--;
7826 		if(prepRefcount == 0) {
7827 			version(Windows) {
7828 				SelectObject(dc, orig);
7829 				ReleaseDC(hwnd, dc);
7830 				hwnd = null;
7831 				dc = null;
7832 				orig = null;
7833 			}
7834 		}
7835 	}
7836 
7837 	/+
7838 		FIXME: I think I need advance and kerning pair
7839 
7840 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
7841 	+/
7842 
7843 	/++
7844 		Returns the height of the font.
7845 
7846 		History:
7847 			Added March 26, 2020
7848 			Documented January 16, 2021
7849 	+/
7850 	int height() {
7851 		version(X11) {
7852 			version(with_xft)
7853 				if(isXft && xftFont !is null) {
7854 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
7855 				}
7856 			if(font is null)
7857 				return 0;
7858 			return font.max_bounds.ascent + font.max_bounds.descent;
7859 		} else version(Windows)
7860 			return height_;
7861 		else assert(0);
7862 	}
7863 
7864 	private int ascent_;
7865 	private int descent_;
7866 
7867 	/++
7868 		Max ascent above the baseline.
7869 
7870 		History:
7871 			Added January 22, 2021
7872 	+/
7873 	int ascent() {
7874 		return ascent_;
7875 	}
7876 
7877 	/++
7878 		Max descent below the baseline.
7879 
7880 		History:
7881 			Added January 22, 2021
7882 	+/
7883 	int descent() {
7884 		return descent_;
7885 	}
7886 
7887 	/++
7888 		Loads the default font used by [ScreenPainter] if none others are loaded.
7889 
7890 		Returns:
7891 			This method mutates the `this` object, but then returns `this` for
7892 			easy chaining like:
7893 
7894 			---
7895 			auto font = foo.isNull ? foo : foo.loadDefault
7896 			---
7897 
7898 		History:
7899 			Added previously, but left unimplemented until January 24, 2021.
7900 	+/
7901 	OperatingSystemFont loadDefault() {
7902 		unload();
7903 
7904 		version(X11) {
7905 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
7906 			// but meh since sdpy does its own thing, this should be ok too
7907 
7908 			ScreenPainterImplementation.ensureDefaultFontLoaded();
7909 			this.font = ScreenPainterImplementation.defaultfont;
7910 			this.fontset = ScreenPainterImplementation.defaultfontset;
7911 
7912 			prepareFontInfo();
7913 		} else version(Windows) {
7914 			ScreenPainterImplementation.ensureDefaultFontLoaded();
7915 			this.font = ScreenPainterImplementation.defaultGuiFont;
7916 
7917 			prepareFontInfo();
7918 		} else throw new NotYetImplementedException();
7919 
7920 		return this;
7921 	}
7922 
7923 	///
7924 	bool isNull() {
7925 		version(OSXCocoa) throw new NotYetImplementedException(); else {
7926 			version(with_xft)
7927 				if(isXft)
7928 					return xftFont is null;
7929 			return font is null;
7930 		}
7931 	}
7932 
7933 	/* Metrics */
7934 	/+
7935 		GetABCWidth
7936 		GetKerningPairs
7937 
7938 		if I do it right, I can size it all here, and match
7939 		what happens when I draw the full string with the OS functions.
7940 
7941 		subclasses might do the same thing while getting the glyphs on images
7942 	struct GlyphInfo {
7943 		int glyph;
7944 
7945 		size_t stringIdxStart;
7946 		size_t stringIdxEnd;
7947 
7948 		Rectangle boundingBox;
7949 	}
7950 	GlyphInfo[] getCharBoxes() {
7951 		// XftTextExtentsUtf8
7952 		return null;
7953 
7954 	}
7955 	+/
7956 
7957 	~this() {
7958 		unload();
7959 	}
7960 }
7961 
7962 version(Windows)
7963 private string sliceCString(const(wchar)[] w) {
7964 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
7965 }
7966 
7967 private inout(char)[] sliceCString(inout(char)* s) {
7968 	import core.stdc.string;
7969 	auto len = strlen(s);
7970 	return s[0 .. len];
7971 }
7972 
7973 /**
7974 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
7975 	than constructing it directly. Then, it is reference counted so you can pass it
7976 	at around and when the last ref goes out of scope, the buffered drawing activities
7977 	are all carried out.
7978 
7979 
7980 	Most functions use the outlineColor instead of taking a color themselves.
7981 	ScreenPainter is reference counted and draws its buffer to the screen when its
7982 	final reference goes out of scope.
7983 */
7984 struct ScreenPainter {
7985 	CapableOfBeingDrawnUpon window;
7986 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) {
7987 		this.window = window;
7988 		if(window.closed)
7989 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
7990 		currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
7991 		if(window.activeScreenPainter !is null) {
7992 			impl = window.activeScreenPainter;
7993 			if(impl.referenceCount == 0) {
7994 				impl.window = window;
7995 				impl.create(handle);
7996 			}
7997 			impl.referenceCount++;
7998 		//	writeln("refcount ++ ", impl.referenceCount);
7999 		} else {
8000 			impl = new ScreenPainterImplementation;
8001 			impl.window = window;
8002 			impl.create(handle);
8003 			impl.referenceCount = 1;
8004 			window.activeScreenPainter = impl;
8005 			//import std.stdio; writeln("constructed");
8006 		}
8007 
8008 		copyActiveOriginals();
8009 	}
8010 
8011 	private Pen originalPen;
8012 	private Color originalFillColor;
8013 	private arsd.color.Rectangle originalClipRectangle;
8014 	void copyActiveOriginals() {
8015 		if(impl is null) return;
8016 		originalPen = impl._activePen;
8017 		originalFillColor = impl._fillColor;
8018 		originalClipRectangle = impl._clipRectangle;
8019 	}
8020 
8021 	~this() {
8022 		if(impl is null) return;
8023 		impl.referenceCount--;
8024 		//writeln("refcount -- ", impl.referenceCount);
8025 		if(impl.referenceCount == 0) {
8026 			//import std.stdio; writeln("destructed");
8027 			impl.dispose();
8028 			*window.activeScreenPainter = ScreenPainterImplementation.init;
8029 			//import std.stdio; writeln("paint finished");
8030 		} else {
8031 			// there is still an active reference, reset stuff so the
8032 			// next user doesn't get weirdness via the reference
8033 			this.rasterOp = RasterOp.normal;
8034 			pen = originalPen;
8035 			fillColor = originalFillColor;
8036 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
8037 		}
8038 	}
8039 
8040 	this(this) {
8041 		if(impl is null) return;
8042 		impl.referenceCount++;
8043 		//writeln("refcount ++ ", impl.referenceCount);
8044 
8045 		copyActiveOriginals();
8046 	}
8047 
8048 	private int _originX;
8049 	private int _originY;
8050 	@property int originX() { return _originX; }
8051 	@property int originY() { return _originY; }
8052 	@property int originX(int a) {
8053 		//currentClipRectangle.left += a - _originX;
8054 		//currentClipRectangle.right += a - _originX;
8055 		_originX = a;
8056 		return _originX;
8057 	}
8058 	@property int originY(int a) {
8059 		//currentClipRectangle.top += a - _originY;
8060 		//currentClipRectangle.bottom += a - _originY;
8061 		_originY = a;
8062 		return _originY;
8063 	}
8064 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
8065 	private void transform(ref Point p) {
8066 		if(impl is null) return;
8067 		p.x += _originX;
8068 		p.y += _originY;
8069 	}
8070 
8071 	// this needs to be checked BEFORE the originX/Y transformation
8072 	private bool isClipped(Point p) {
8073 		return !currentClipRectangle.contains(p);
8074 	}
8075 	private bool isClipped(Point p, int width, int height) {
8076 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
8077 	}
8078 	private bool isClipped(Point p, Size s) {
8079 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
8080 	}
8081 	private bool isClipped(Point p, Point p2) {
8082 		// need to ensure the end points are actually included inside, so the +1 does that
8083 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
8084 	}
8085 
8086 
8087 	/++
8088 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
8089 
8090 		Returns:
8091 			The old clip rectangle.
8092 
8093 		History:
8094 			Return value was `void` prior to May 10, 2021.
8095 
8096 	+/
8097 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
8098 		if(impl is null) return currentClipRectangle;
8099 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
8100 			return currentClipRectangle; // no need to do anything
8101 		auto old = currentClipRectangle;
8102 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
8103 		transform(pt);
8104 
8105 		impl.setClipRectangle(pt.x, pt.y, width, height);
8106 
8107 		return old;
8108 	}
8109 
8110 	/// ditto
8111 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
8112 		if(impl is null) return currentClipRectangle;
8113 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
8114 	}
8115 
8116 	///
8117 	void setFont(OperatingSystemFont font) {
8118 		if(impl is null) return;
8119 		impl.setFont(font);
8120 	}
8121 
8122 	///
8123 	int fontHeight() {
8124 		if(impl is null) return 0;
8125 		return impl.fontHeight();
8126 	}
8127 
8128 	private Pen activePen;
8129 
8130 	///
8131 	@property void pen(Pen p) {
8132 		if(impl is null) return;
8133 		activePen = p;
8134 		impl.pen(p);
8135 	}
8136 
8137 	///
8138 	@scriptable
8139 	@property void outlineColor(Color c) {
8140 		if(impl is null) return;
8141 		if(activePen.color == c)
8142 			return;
8143 		activePen.color = c;
8144 		impl.pen(activePen);
8145 	}
8146 
8147 	///
8148 	@scriptable
8149 	@property void fillColor(Color c) {
8150 		if(impl is null) return;
8151 		impl.fillColor(c);
8152 	}
8153 
8154 	///
8155 	@property void rasterOp(RasterOp op) {
8156 		if(impl is null) return;
8157 		impl.rasterOp(op);
8158 	}
8159 
8160 
8161 	void updateDisplay() {
8162 		// FIXME this should do what the dtor does
8163 	}
8164 
8165 	/// 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)
8166 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
8167 		if(impl is null) return;
8168 		if(isClipped(upperLeft, width, height)) return;
8169 		transform(upperLeft);
8170 		version(Windows) {
8171 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
8172 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
8173 			RECT clip = scroll;
8174 			RECT uncovered;
8175 			HRGN hrgn;
8176 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
8177 				throw new Exception("ScrollDC");
8178 
8179 		} else version(X11) {
8180 			// FIXME: clip stuff outside this rectangle
8181 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
8182 		} else version(OSXCocoa) {
8183 			throw new NotYetImplementedException();
8184 		} else static assert(0);
8185 	}
8186 
8187 	///
8188 	void clear(Color color = Color.white()) {
8189 		if(impl is null) return;
8190 		fillColor = color;
8191 		outlineColor = color;
8192 		drawRectangle(Point(0, 0), window.width, window.height);
8193 	}
8194 
8195 	/++
8196 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
8197 
8198 		Params:
8199 			upperLeft = point on the window where the upper left corner of the image will be drawn
8200 			imageUpperLeft = point on the image to start the slice to draw
8201 			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.
8202 		History:
8203 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
8204 	+/
8205 	version(OSXCocoa) {} else // NotYetImplementedException
8206 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
8207 		if(impl is null) return;
8208 		if(isClipped(upperLeft, s.width, s.height)) return;
8209 		transform(upperLeft);
8210 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
8211 	}
8212 
8213 	///
8214 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
8215 		if(impl is null) return;
8216 		//if(isClipped(upperLeft, w, h)) return; // FIXME
8217 		transform(upperLeft);
8218 		if(w == 0 || w > i.width)
8219 			w = i.width;
8220 		if(h == 0 || h > i.height)
8221 			h = i.height;
8222 		if(upperLeftOfImage.x < 0)
8223 			upperLeftOfImage.x = 0;
8224 		if(upperLeftOfImage.y < 0)
8225 			upperLeftOfImage.y = 0;
8226 
8227 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
8228 	}
8229 
8230 	///
8231 	Size textSize(in char[] text) {
8232 		if(impl is null) return Size(0, 0);
8233 		return impl.textSize(text);
8234 	}
8235 
8236 	/++
8237 		Draws a string in the window with the set font (see [setFont] to change it).
8238 
8239 		Params:
8240 			upperLeft = the upper left point of the bounding box of the text
8241 			text = the string to draw
8242 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
8243 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
8244 	+/
8245 	@scriptable
8246 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
8247 		if(impl is null) return;
8248 		if(lowerRight.x != 0 || lowerRight.y != 0) {
8249 			if(isClipped(upperLeft, lowerRight)) return;
8250 			transform(lowerRight);
8251 		} else {
8252 			if(isClipped(upperLeft, textSize(text))) return;
8253 		}
8254 		transform(upperLeft);
8255 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
8256 	}
8257 
8258 	/++
8259 		Draws text using a custom font.
8260 
8261 		This is still MAJOR work in progress.
8262 
8263 		Creating a [DrawableFont] can be tricky and require additional dependencies.
8264 	+/
8265 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
8266 		if(impl is null) return;
8267 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
8268 		transform(upperLeft);
8269 		font.drawString(this, upperLeft, text);
8270 	}
8271 
8272 	version(Windows)
8273 	void drawText(Point upperLeft, scope const(wchar)[] text) {
8274 		if(impl is null) return;
8275 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
8276 		transform(upperLeft);
8277 
8278 		if(text.length && text[$-1] == '\n')
8279 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
8280 
8281 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
8282 	}
8283 
8284 	static struct TextDrawingContext {
8285 		Point boundingBoxUpperLeft;
8286 		Point boundingBoxLowerRight;
8287 
8288 		Point currentLocation;
8289 
8290 		Point lastDrewUpperLeft;
8291 		Point lastDrewLowerRight;
8292 
8293 		// how do i do right aligned rich text?
8294 		// i kinda want to do a pre-made drawing then right align
8295 		// draw the whole block.
8296 		//
8297 		// That's exactly the diff: inline vs block stuff.
8298 
8299 		// I need to get coordinates of an inline section out too,
8300 		// not just a bounding box, but a series of bounding boxes
8301 		// should be ok. Consider what's needed to detect a click
8302 		// on a link in the middle of a paragraph breaking a line.
8303 		//
8304 		// Generally, we should be able to get the rectangles of
8305 		// any portion we draw.
8306 		//
8307 		// It also needs to tell what text is left if it overflows
8308 		// out of the box, so we can do stuff like float images around
8309 		// it. It should not attempt to draw a letter that would be
8310 		// clipped.
8311 		//
8312 		// I might also turn off word wrap stuff.
8313 	}
8314 
8315 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
8316 		if(impl is null) return;
8317 		// FIXME
8318 	}
8319 
8320 	/// Drawing an individual pixel is slow. Avoid it if possible.
8321 	void drawPixel(Point where) {
8322 		if(impl is null) return;
8323 		if(isClipped(where)) return;
8324 		transform(where);
8325 		impl.drawPixel(where.x, where.y);
8326 	}
8327 
8328 
8329 	/// Draws a pen using the current pen / outlineColor
8330 	@scriptable
8331 	void drawLine(Point starting, Point ending) {
8332 		if(impl is null) return;
8333 		if(isClipped(starting, ending)) return;
8334 		transform(starting);
8335 		transform(ending);
8336 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
8337 	}
8338 
8339 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
8340 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
8341 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
8342 	@scriptable
8343 	void drawRectangle(Point upperLeft, int width, int height) {
8344 		if(impl is null) return;
8345 		if(isClipped(upperLeft, width, height)) return;
8346 		transform(upperLeft);
8347 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
8348 	}
8349 
8350 	/// ditto
8351 	void drawRectangle(Point upperLeft, Size size) {
8352 		if(impl is null) return;
8353 		if(isClipped(upperLeft, size.width, size.height)) return;
8354 		transform(upperLeft);
8355 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
8356 	}
8357 
8358 	/// ditto
8359 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
8360 		if(impl is null) return;
8361 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
8362 		transform(upperLeft);
8363 		transform(lowerRightInclusive);
8364 		impl.drawRectangle(upperLeft.x, upperLeft.y,
8365 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
8366 	}
8367 
8368 	// overload added on May 12, 2021
8369 	/// ditto
8370 	void drawRectangle(Rectangle rect) {
8371 		drawRectangle(rect.upperLeft, rect.size);
8372 	}
8373 
8374 	/// Arguments are the points of the bounding rectangle
8375 	void drawEllipse(Point upperLeft, Point lowerRight) {
8376 		if(impl is null) return;
8377 		if(isClipped(upperLeft, lowerRight)) return;
8378 		transform(upperLeft);
8379 		transform(lowerRight);
8380 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
8381 	}
8382 
8383 	/++
8384 		start and finish are units of degrees * 64
8385 	+/
8386 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
8387 		if(impl is null) return;
8388 		// FIXME: not actually implemented
8389 		if(isClipped(upperLeft, width, height)) return;
8390 		transform(upperLeft);
8391 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
8392 	}
8393 
8394 	//this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
8395 	void drawCircle(Point upperLeft, int diameter) {
8396 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
8397 	}
8398 
8399 	/// .
8400 	void drawPolygon(Point[] vertexes) {
8401 		if(impl is null) return;
8402 		assert(vertexes.length);
8403 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
8404 		foreach(ref vertex; vertexes) {
8405 			if(vertex.x < minX)
8406 				minX = vertex.x;
8407 			if(vertex.y < minY)
8408 				minY = vertex.y;
8409 			if(vertex.x > maxX)
8410 				maxX = vertex.x;
8411 			if(vertex.y > maxY)
8412 				maxY = vertex.y;
8413 			transform(vertex);
8414 		}
8415 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
8416 		impl.drawPolygon(vertexes);
8417 	}
8418 
8419 	/// ditto
8420 	void drawPolygon(Point[] vertexes...) {
8421 		if(impl is null) return;
8422 		drawPolygon(vertexes);
8423 	}
8424 
8425 
8426 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
8427 
8428 	//mixin NativeScreenPainterImplementation!() impl;
8429 
8430 
8431 	// HACK: if I mixin the impl directly, it won't let me override the copy
8432 	// constructor! The linker complains about there being multiple definitions.
8433 	// I'll make the best of it and reference count it though.
8434 	ScreenPainterImplementation* impl;
8435 }
8436 
8437 	// HACK: I need a pointer to the implementation so it's separate
8438 	struct ScreenPainterImplementation {
8439 		CapableOfBeingDrawnUpon window;
8440 		int referenceCount;
8441 		mixin NativeScreenPainterImplementation!();
8442 	}
8443 
8444 // FIXME: i haven't actually tested the sprite class on MS Windows
8445 
8446 /**
8447 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
8448 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
8449 
8450 
8451 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
8452 	though I'm not sure that's ideal and the implementation might change.
8453 
8454 	You create one by giving a window and an image. It optimizes for that window,
8455 	and copies the image into it to use as the initial picture. Creating a sprite
8456 	can be quite slow (especially over a network connection) so you should do it
8457 	as little as possible and just hold on to your sprite handles after making them.
8458 	simpledisplay does try to do its best though, using the XSHM extension if available,
8459 	but you should still write your code as if it will always be slow.
8460 
8461 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
8462 	a fast operation - much faster than drawing the Image itself every time.
8463 
8464 	`Sprite` represents a scarce resource which should be freed when you
8465 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
8466 	after it has been disposed. If you are unsure about this, don't take chances,
8467 	just let the garbage collector do it for you. But ideally, you can manage its
8468 	lifetime more efficiently.
8469 
8470 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
8471 	support alpha blending in its drawing at this time. That might change in the
8472 	future, but if you need alpha blending right now, use OpenGL instead. See
8473 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
8474 
8475 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
8476 	in by setting the enableAlpha = true in the constructor.
8477 */
8478 version(OSXCocoa) {} else // NotYetImplementedException
8479 class Sprite : CapableOfBeingDrawnUpon {
8480 
8481 	///
8482 	ScreenPainter draw() {
8483 		return ScreenPainter(this, handle);
8484 	}
8485 
8486 	/++
8487 		Copies the sprite's current state into a [TrueColorImage].
8488 
8489 		Be warned: this can be a very slow operation
8490 
8491 		History:
8492 			Actually implemented on March 14, 2021
8493 	+/
8494 	TrueColorImage takeScreenshot() {
8495 		return trueColorImageFromNativeHandle(handle, width, height);
8496 	}
8497 
8498 	void delegate() paintingFinishedDg() { return null; }
8499 	bool closed() { return false; }
8500 	ScreenPainterImplementation* activeScreenPainter_;
8501 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
8502 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
8503 
8504 	version(Windows)
8505 		private ubyte* rawData;
8506 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
8507 	// ditto on the XPicture stuff
8508 
8509 	version(X11) {
8510 		private static XRenderPictFormat* RGB24;
8511 		private static XRenderPictFormat* ARGB32;
8512 
8513 		private Picture xrenderPicture;
8514 	}
8515 
8516 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
8517 		this._width = width;
8518 		this._height = height;
8519 		this.enableAlpha = enableAlpha;
8520 
8521 		version(X11) {
8522 			auto display = XDisplayConnection.get();
8523 
8524 			if(enableAlpha) {
8525 				if(!XRenderLibrary.loadAttempted) {
8526 					XRenderLibrary.loadDynamicLibrary();
8527 				}
8528 
8529 				if(!XRenderLibrary.loadSuccessful)
8530 					throw new Exception("XRender library load failure");
8531 			}
8532 
8533 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
8534 
8535 			if(enableAlpha) {
8536 				if(RGB24 is null)
8537 					RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
8538 				if(ARGB32 is null)
8539 					ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
8540 
8541 				XRenderPictureAttributes attrs;
8542 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
8543 			}
8544 		} else version(Windows) {
8545 			version(CRuntime_DigitalMars) {
8546 				//if(enableAlpha)
8547 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
8548 			}
8549 
8550 			BITMAPINFO infoheader;
8551 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
8552 			infoheader.bmiHeader.biWidth = width;
8553 			infoheader.bmiHeader.biHeight = height;
8554 			infoheader.bmiHeader.biPlanes = 1;
8555 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
8556 			infoheader.bmiHeader.biCompression = BI_RGB;
8557 
8558 			// FIXME: this should prolly be a device dependent bitmap...
8559 			handle = CreateDIBSection(
8560 				null,
8561 				&infoheader,
8562 				DIB_RGB_COLORS,
8563 				cast(void**) &rawData,
8564 				null,
8565 				0);
8566 
8567 			if(handle is null)
8568 				throw new Exception("couldn't create pixmap");
8569 		}
8570 	}
8571 
8572 	/// Makes a sprite based on the image with the initial contents from the Image
8573 	this(SimpleWindow win, Image i) {
8574 		this(win, i.width, i.height, i.enableAlpha);
8575 
8576 		version(X11) {
8577 			auto display = XDisplayConnection.get();
8578 			auto gc = XCreateGC(display, this.handle, 0, null);
8579 			scope(exit) XFreeGC(display, gc);
8580 			if(i.usingXshm)
8581 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
8582 			else
8583 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
8584 		} else version(Windows) {
8585 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
8586 			auto arrLength = itemsPerLine * height;
8587 			rawData[0..arrLength] = i.rawData[0..arrLength];
8588 		} else version(OSXCocoa) {
8589 			// FIXME: I have no idea if this is even any good
8590 			ubyte* rawData;
8591 
8592 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
8593 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
8594 				colorSpace,
8595 				kCGImageAlphaPremultipliedLast
8596 				|kCGBitmapByteOrder32Big);
8597 			CGColorSpaceRelease(colorSpace);
8598 			rawData = CGBitmapContextGetData(context);
8599 
8600 			auto rdl = (width * height * 4);
8601 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
8602 		} else static assert(0);
8603 	}
8604 
8605 	/++
8606 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
8607 
8608 		Params:
8609 			where = point on the window where the upper left corner of the image will be drawn
8610 			imageUpperLeft = point on the image to start the slice to draw
8611 			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.
8612 		History:
8613 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
8614 	+/
8615 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
8616 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
8617 	}
8618 
8619 	/// Call this when you're ready to get rid of it
8620 	void dispose() {
8621 		version(X11) {
8622 			if(xrenderPicture) {
8623 				XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
8624 				xrenderPicture = None;
8625 			}
8626 			if(handle)
8627 				XFreePixmap(XDisplayConnection.get(), handle);
8628 			handle = None;
8629 		} else version(Windows) {
8630 			if(handle)
8631 				DeleteObject(handle);
8632 			handle = null;
8633 		} else version(OSXCocoa) {
8634 			if(context)
8635 				CGContextRelease(context);
8636 			context = null;
8637 		} else static assert(0);
8638 
8639 	}
8640 
8641 	~this() {
8642 		dispose();
8643 	}
8644 
8645 	///
8646 	final @property int width() { return _width; }
8647 
8648 	///
8649 	final @property int height() { return _height; }
8650 
8651 	///
8652 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
8653 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
8654 	}
8655 
8656 	private:
8657 
8658 	int _width;
8659 	int _height;
8660 	bool enableAlpha;
8661 	version(X11)
8662 		Pixmap handle;
8663 	else version(Windows)
8664 		HBITMAP handle;
8665 	else version(OSXCocoa)
8666 		CGContextRef context;
8667 	else static assert(0);
8668 }
8669 
8670 /++
8671 	NOT IMPLEMENTED
8672 
8673 	A display-stored image optimized for relatively quick drawing, like
8674 	[Sprite], but this one supports alpha channel blending and does NOT
8675 	support direct drawing upon it with a [ScreenPainter].
8676 
8677 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
8678 	plain [ScreenPainter]... sort of.
8679 
8680 	On X11, it requires the Xrender extension and library. This is available
8681 	almost everywhere though.
8682 
8683 	History:
8684 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
8685 +/
8686 version(none)
8687 class AlphaSprite {
8688 	/++
8689 		Copies the given image into it.
8690 	+/
8691 	this(MemoryImage img) {
8692 
8693 		if(!XRenderLibrary.loadAttempted) {
8694 			XRenderLibrary.loadDynamicLibrary();
8695 
8696 			// FIXME: this needs to be reconstructed when the X server changes
8697 			repopulateX();
8698 		}
8699 		if(!XRenderLibrary.loadSuccessful)
8700 			throw new Exception("XRender library load failure");
8701 
8702 		// I probably need to put the alpha mask in a separate Picture
8703 		// ugh
8704 		// maybe the Sprite itself can have an alpha bitmask anyway
8705 
8706 
8707 		auto display = XDisplayConnection.get();
8708 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
8709 
8710 
8711 		XRenderPictureAttributes attrs;
8712 
8713 		handle = XRenderCreatePicture(
8714 			XDisplayConnection.get,
8715 			pixmap,
8716 			RGBA,
8717 			0,
8718 			&attrs
8719 		);
8720 
8721 	}
8722 
8723 	// maybe i'll use the create gradient functions too with static factories..
8724 
8725 	void drawAt(ScreenPainter painter, Point where) {
8726 		//painter.drawPixmap(this, where);
8727 
8728 		XRenderPictureAttributes attrs;
8729 
8730 		auto pic = XRenderCreatePicture(
8731 			XDisplayConnection.get,
8732 			painter.impl.d,
8733 			RGB,
8734 			0,
8735 			&attrs
8736 		);
8737 
8738 		XRenderComposite(
8739 			XDisplayConnection.get,
8740 			3, // PictOpOver
8741 			handle,
8742 			None,
8743 			pic,
8744 			0, // src
8745 			0,
8746 			0, // mask
8747 			0,
8748 			10, // dest
8749 			10,
8750 			100, // width
8751 			100
8752 		);
8753 
8754 		/+
8755 		XRenderFreePicture(
8756 			XDisplayConnection.get,
8757 			pic
8758 		);
8759 
8760 		XRenderFreePicture(
8761 			XDisplayConnection.get,
8762 			fill
8763 		);
8764 		+/
8765 		// on Windows you can stretch but Xrender still can't :(
8766 	}
8767 
8768 	static XRenderPictFormat* RGB;
8769 	static XRenderPictFormat* RGBA;
8770 	static void repopulateX() {
8771 		auto display = XDisplayConnection.get;
8772 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
8773 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
8774 	}
8775 
8776 	XPixmap pixmap;
8777 	Picture handle;
8778 }
8779 
8780 ///
8781 interface CapableOfBeingDrawnUpon {
8782 	///
8783 	ScreenPainter draw();
8784 	///
8785 	int width();
8786 	///
8787 	int height();
8788 	protected ScreenPainterImplementation* activeScreenPainter();
8789 	protected void activeScreenPainter(ScreenPainterImplementation*);
8790 	bool closed();
8791 
8792 	void delegate() paintingFinishedDg();
8793 
8794 	/// Be warned: this can be a very slow operation
8795 	TrueColorImage takeScreenshot();
8796 }
8797 
8798 /// 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()
8799 void flushGui() {
8800 	version(X11) {
8801 		auto dpy = XDisplayConnection.get();
8802 		XLockDisplay(dpy);
8803 		scope(exit) XUnlockDisplay(dpy);
8804 		XFlush(dpy);
8805 	}
8806 }
8807 
8808 /++
8809 	Runs the given code in the GUI thread when its event loop
8810 	is available, blocking until it completes. This allows you
8811 	to create and manipulate windows from another thread without
8812 	invoking undefined behavior.
8813 
8814 	If this is the gui thread, it runs the code immediately.
8815 
8816 	If no gui thread exists yet, the current thread is assumed
8817 	to be it. Attempting to create windows or run the event loop
8818 	in any other thread will cause an assertion failure.
8819 
8820 
8821 	$(TIP
8822 		Did you know you can use UFCS on delegate literals?
8823 
8824 		() {
8825 			// code here
8826 		}.runInGuiThread;
8827 	)
8828 
8829 	Returns:
8830 		`true` if the function was called, `false` if it was not.
8831 		The function may not be called because the gui thread had
8832 		already terminated by the time you called this.
8833 
8834 	History:
8835 		Added April 10, 2020 (v7.2.0)
8836 
8837 		Return value added and implementation tweaked to avoid locking
8838 		at program termination on February 24, 2021 (v9.2.1).
8839 +/
8840 bool runInGuiThread(scope void delegate() dg) @trusted {
8841 	claimGuiThread();
8842 
8843 	if(thisIsGuiThread) {
8844 		dg();
8845 		return true;
8846 	}
8847 
8848 	if(guiThreadTerminating)
8849 		return false;
8850 
8851 	import core.sync.semaphore;
8852 	static Semaphore sc;
8853 	if(sc is null)
8854 		sc = new Semaphore();
8855 
8856 	static RunQueueMember* rqm;
8857 	if(rqm is null)
8858 		rqm = new RunQueueMember;
8859 	rqm.dg = cast(typeof(rqm.dg)) dg;
8860 	rqm.signal = sc;
8861 	rqm.thrown = null;
8862 
8863 	synchronized(runInGuiThreadLock) {
8864 		runInGuiThreadQueue ~= rqm;
8865 	}
8866 
8867 	if(!SimpleWindow.eventWakeUp())
8868 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
8869 
8870 	rqm.signal.wait();
8871 	auto t = rqm.thrown;
8872 
8873 	if(t)
8874 		throw t;
8875 
8876 	return true;
8877 }
8878 
8879 private void runPendingRunInGuiThreadDelegates() {
8880 	more:
8881 	RunQueueMember* next;
8882 	synchronized(runInGuiThreadLock) {
8883 		if(runInGuiThreadQueue.length) {
8884 			next = runInGuiThreadQueue[0];
8885 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
8886 		} else {
8887 			next = null;
8888 		}
8889 	}
8890 
8891 	if(next) {
8892 		try {
8893 			next.dg();
8894 			next.thrown = null;
8895 		} catch(Throwable t) {
8896 			next.thrown = t;
8897 		}
8898 
8899 		next.signal.notify();
8900 
8901 		goto more;
8902 	}
8903 }
8904 
8905 private void claimGuiThread() {
8906 	import core.atomic;
8907 	if(cas(&guiThreadExists, false, true))
8908 		thisIsGuiThread = true;
8909 }
8910 
8911 private struct RunQueueMember {
8912 	void delegate() dg;
8913 	import core.sync.semaphore;
8914 	Semaphore signal;
8915 	Throwable thrown;
8916 }
8917 
8918 private __gshared RunQueueMember*[] runInGuiThreadQueue;
8919 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
8920 private bool thisIsGuiThread = false;
8921 private shared bool guiThreadExists = false;
8922 private shared bool guiThreadTerminating = false;
8923 
8924 private void guiThreadFinalize() {
8925 	assert(thisIsGuiThread);
8926 
8927 	guiThreadTerminating = true; // don't add any more from this point on
8928 	runPendingRunInGuiThreadDelegates();
8929 }
8930 
8931 /+
8932 interface IPromise {
8933 	void reportProgress(int current, int max, string message);
8934 
8935 	/+ // not formally in cuz of templates but still
8936 	IPromise Then();
8937 	IPromise Catch();
8938 	IPromise Finally();
8939 	+/
8940 }
8941 
8942 /+
8943 	auto promise = async({ ... });
8944 	promise.Then(whatever).
8945 		Then(whateverelse).
8946 		Catch((exception) { });
8947 
8948 
8949 	A promise is run inside a fiber and it looks something like:
8950 
8951 	try {
8952 		auto res = whatever();
8953 		auto res2 = whateverelse(res);
8954 	} catch(Exception e) {
8955 		{ }(e);
8956 	}
8957 
8958 	When a thing succeeds, it is passed as an arg to the next
8959 +/
8960 class Promise(T) : IPromise {
8961 	auto Then() { return null; }
8962 	auto Catch() { return null; }
8963 	auto Finally() { return null; }
8964 
8965 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
8966 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
8967 	T await();
8968 }
8969 
8970 interface Task {
8971 }
8972 
8973 interface Resolvable(T) : Task {
8974 	void run();
8975 
8976 	void resolve(T);
8977 
8978 	Resolvable!T then(void delegate(T)); // returns a new promise
8979 	Resolvable!T error(Throwable); // js catch
8980 	Resolvable!T completed(); // js finally
8981 
8982 }
8983 
8984 /++
8985 	Runs `work` in a helper thread and sends its return value back to the main gui
8986 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
8987 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
8988 	kill the program.
8989 
8990 	You can call reportProgress(position, max, message) to update your parent window
8991 	on your progress.
8992 
8993 	I should also use `shared` methods. FIXME
8994 
8995 	History:
8996 		Added March 6, 2021 (dub version 9.3).
8997 +/
8998 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
8999 	uponCompletion(work(null));
9000 }
9001 
9002 +/
9003 
9004 /// Used internal to dispatch events to various classes.
9005 interface CapableOfHandlingNativeEvent {
9006 	NativeEventHandler getNativeEventHandler();
9007 
9008 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
9009 
9010 	version(X11) {
9011 		// if this is impossible, you are allowed to just throw from it
9012 		// Note: if you call it from another object, set a flag cuz the manger will call you again
9013 		void recreateAfterDisconnect();
9014 		// discard any *connection specific* state, but keep enough that you
9015 		// can be recreated if possible. discardConnectionState() is always called immediately
9016 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
9017 		// you need initialization order
9018 		void discardConnectionState();
9019 	}
9020 }
9021 
9022 version(X11)
9023 /++
9024 	State of keys on mouse events, especially motion.
9025 
9026 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
9027 +/
9028 enum ModifierState : uint {
9029 	shift = 1, ///
9030 	capsLock = 2, ///
9031 	ctrl = 4, ///
9032 	alt = 8, /// Not always available on Windows
9033 	windows = 64, /// ditto
9034 	numLock = 16, ///
9035 
9036 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
9037 	middleButtonDown = 512, /// ditto
9038 	rightButtonDown = 1024, /// ditto
9039 }
9040 else version(Windows)
9041 /// ditto
9042 enum ModifierState : uint {
9043 	shift = 4, ///
9044 	ctrl = 8, ///
9045 
9046 	// i'm not sure if the next two are available
9047 	alt = 256, /// not always available on Windows
9048 	windows = 512, /// ditto
9049 
9050 	capsLock = 1024, ///
9051 	numLock = 2048, ///
9052 
9053 	leftButtonDown = 1, /// not available on key events
9054 	middleButtonDown = 16, /// ditto
9055 	rightButtonDown = 2, /// ditto
9056 
9057 	backButtonDown = 0x20, /// not available on X
9058 	forwardButtonDown = 0x40, /// ditto
9059 }
9060 else version(OSXCocoa)
9061 // FIXME FIXME NotYetImplementedException
9062 enum ModifierState : uint {
9063 	shift = 1, ///
9064 	capsLock = 2, ///
9065 	ctrl = 4, ///
9066 	alt = 8, /// Not always available on Windows
9067 	windows = 64, /// ditto
9068 	numLock = 16, ///
9069 
9070 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
9071 	middleButtonDown = 512, /// ditto
9072 	rightButtonDown = 1024, /// ditto
9073 }
9074 
9075 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them
9076 enum MouseButton : int {
9077 	none = 0,
9078 	left = 1, ///
9079 	right = 2, ///
9080 	middle = 4, ///
9081 	wheelUp = 8, ///
9082 	wheelDown = 16, ///
9083 	backButton = 32, /// often found on the thumb and used for back in browsers
9084 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
9085 }
9086 
9087 version(X11) {
9088 	// FIXME: match ASCII whenever we can. Most of it is already there,
9089 	// but there's a few exceptions and mismatches with Windows
9090 
9091 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
9092 	enum Key {
9093 		Escape = 0xff1b, ///
9094 		F1 = 0xffbe, ///
9095 		F2 = 0xffbf, ///
9096 		F3 = 0xffc0, ///
9097 		F4 = 0xffc1, ///
9098 		F5 = 0xffc2, ///
9099 		F6 = 0xffc3, ///
9100 		F7 = 0xffc4, ///
9101 		F8 = 0xffc5, ///
9102 		F9 = 0xffc6, ///
9103 		F10 = 0xffc7, ///
9104 		F11 = 0xffc8, ///
9105 		F12 = 0xffc9, ///
9106 		PrintScreen = 0xff61, ///
9107 		ScrollLock = 0xff14, ///
9108 		Pause = 0xff13, ///
9109 		Grave = 0x60, /// The $(BACKTICK) ~ key
9110 		// number keys across the top of the keyboard
9111 		N1 = 0x31, /// Number key atop the keyboard
9112 		N2 = 0x32, ///
9113 		N3 = 0x33, ///
9114 		N4 = 0x34, ///
9115 		N5 = 0x35, ///
9116 		N6 = 0x36, ///
9117 		N7 = 0x37, ///
9118 		N8 = 0x38, ///
9119 		N9 = 0x39, ///
9120 		N0 = 0x30, ///
9121 		Dash = 0x2d, ///
9122 		Equals = 0x3d, ///
9123 		Backslash = 0x5c, /// The \ | key
9124 		Backspace = 0xff08, ///
9125 		Insert = 0xff63, ///
9126 		Home = 0xff50, ///
9127 		PageUp = 0xff55, ///
9128 		Delete = 0xffff, ///
9129 		End = 0xff57, ///
9130 		PageDown = 0xff56, ///
9131 		Up = 0xff52, ///
9132 		Down = 0xff54, ///
9133 		Left = 0xff51, ///
9134 		Right = 0xff53, ///
9135 
9136 		Tab = 0xff09, ///
9137 		Q = 0x71, ///
9138 		W = 0x77, ///
9139 		E = 0x65, ///
9140 		R = 0x72, ///
9141 		T = 0x74, ///
9142 		Y = 0x79, ///
9143 		U = 0x75, ///
9144 		I = 0x69, ///
9145 		O = 0x6f, ///
9146 		P = 0x70, ///
9147 		LeftBracket = 0x5b, /// the [ { key
9148 		RightBracket = 0x5d, /// the ] } key
9149 		CapsLock = 0xffe5, ///
9150 		A = 0x61, ///
9151 		S = 0x73, ///
9152 		D = 0x64, ///
9153 		F = 0x66, ///
9154 		G = 0x67, ///
9155 		H = 0x68, ///
9156 		J = 0x6a, ///
9157 		K = 0x6b, ///
9158 		L = 0x6c, ///
9159 		Semicolon = 0x3b, ///
9160 		Apostrophe = 0x27, ///
9161 		Enter = 0xff0d, ///
9162 		Shift = 0xffe1, ///
9163 		Z = 0x7a, ///
9164 		X = 0x78, ///
9165 		C = 0x63, ///
9166 		V = 0x76, ///
9167 		B = 0x62, ///
9168 		N = 0x6e, ///
9169 		M = 0x6d, ///
9170 		Comma = 0x2c, ///
9171 		Period = 0x2e, ///
9172 		Slash = 0x2f, /// the / ? key
9173 		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
9174 		Ctrl = 0xffe3, ///
9175 		Windows = 0xffeb, ///
9176 		Alt = 0xffe9, ///
9177 		Space = 0x20, ///
9178 		Alt_r = 0xffea, /// ditto of shift_r
9179 		Windows_r = 0xffec, ///
9180 		Menu = 0xff67, ///
9181 		Ctrl_r = 0xffe4, ///
9182 
9183 		NumLock = 0xff7f, ///
9184 		Divide = 0xffaf, /// The / key on the number pad
9185 		Multiply = 0xffaa, /// The * key on the number pad
9186 		Minus = 0xffad, /// The - key on the number pad
9187 		Plus = 0xffab, /// The + key on the number pad
9188 		PadEnter = 0xff8d, /// Numberpad enter key
9189 		Pad1 = 0xff9c, /// Numberpad keys
9190 		Pad2 = 0xff99, ///
9191 		Pad3 = 0xff9b, ///
9192 		Pad4 = 0xff96, ///
9193 		Pad5 = 0xff9d, ///
9194 		Pad6 = 0xff98, ///
9195 		Pad7 = 0xff95, ///
9196 		Pad8 = 0xff97, ///
9197 		Pad9 = 0xff9a, ///
9198 		Pad0 = 0xff9e, ///
9199 		PadDot = 0xff9f, ///
9200 	}
9201 } else version(Windows) {
9202 	// the character here is for en-us layouts and for illustration only
9203 	// if you actually want to get characters, wait for character events
9204 	// (the argument to your event handler is simply a dchar)
9205 	// those will be converted by the OS for the right locale.
9206 
9207 	enum Key {
9208 		Escape = 0x1b,
9209 		F1 = 0x70,
9210 		F2 = 0x71,
9211 		F3 = 0x72,
9212 		F4 = 0x73,
9213 		F5 = 0x74,
9214 		F6 = 0x75,
9215 		F7 = 0x76,
9216 		F8 = 0x77,
9217 		F9 = 0x78,
9218 		F10 = 0x79,
9219 		F11 = 0x7a,
9220 		F12 = 0x7b,
9221 		PrintScreen = 0x2c,
9222 		ScrollLock = 0x91,
9223 		Pause = 0x13,
9224 		Grave = 0xc0,
9225 		// number keys across the top of the keyboard
9226 		N1 = 0x31,
9227 		N2 = 0x32,
9228 		N3 = 0x33,
9229 		N4 = 0x34,
9230 		N5 = 0x35,
9231 		N6 = 0x36,
9232 		N7 = 0x37,
9233 		N8 = 0x38,
9234 		N9 = 0x39,
9235 		N0 = 0x30,
9236 		Dash = 0xbd,
9237 		Equals = 0xbb,
9238 		Backslash = 0xdc,
9239 		Backspace = 0x08,
9240 		Insert = 0x2d,
9241 		Home = 0x24,
9242 		PageUp = 0x21,
9243 		Delete = 0x2e,
9244 		End = 0x23,
9245 		PageDown = 0x22,
9246 		Up = 0x26,
9247 		Down = 0x28,
9248 		Left = 0x25,
9249 		Right = 0x27,
9250 
9251 		Tab = 0x09,
9252 		Q = 0x51,
9253 		W = 0x57,
9254 		E = 0x45,
9255 		R = 0x52,
9256 		T = 0x54,
9257 		Y = 0x59,
9258 		U = 0x55,
9259 		I = 0x49,
9260 		O = 0x4f,
9261 		P = 0x50,
9262 		LeftBracket = 0xdb,
9263 		RightBracket = 0xdd,
9264 		CapsLock = 0x14,
9265 		A = 0x41,
9266 		S = 0x53,
9267 		D = 0x44,
9268 		F = 0x46,
9269 		G = 0x47,
9270 		H = 0x48,
9271 		J = 0x4a,
9272 		K = 0x4b,
9273 		L = 0x4c,
9274 		Semicolon = 0xba,
9275 		Apostrophe = 0xde,
9276 		Enter = 0x0d,
9277 		Shift = 0x10,
9278 		Z = 0x5a,
9279 		X = 0x58,
9280 		C = 0x43,
9281 		V = 0x56,
9282 		B = 0x42,
9283 		N = 0x4e,
9284 		M = 0x4d,
9285 		Comma = 0xbc,
9286 		Period = 0xbe,
9287 		Slash = 0xbf,
9288 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
9289 		Ctrl = 0x11,
9290 		Windows = 0x5b,
9291 		Alt = -5, // FIXME
9292 		Space = 0x20,
9293 		Alt_r = 0xffea, // ditto of shift_r
9294 		Windows_r = 0x5c, // ditto of shift_r
9295 		Menu = 0x5d,
9296 		Ctrl_r = 0xa3, // ditto of shift_r
9297 
9298 		NumLock = 0x90,
9299 		Divide = 0x6f,
9300 		Multiply = 0x6a,
9301 		Minus = 0x6d,
9302 		Plus = 0x6b,
9303 		PadEnter = -8, // FIXME
9304 		Pad1 = 0x61,
9305 		Pad2 = 0x62,
9306 		Pad3 = 0x63,
9307 		Pad4 = 0x64,
9308 		Pad5 = 0x65,
9309 		Pad6 = 0x66,
9310 		Pad7 = 0x67,
9311 		Pad8 = 0x68,
9312 		Pad9 = 0x69,
9313 		Pad0 = 0x60,
9314 		PadDot = 0x6e,
9315 	}
9316 
9317 	// I'm keeping this around for reference purposes
9318 	// ideally all these buttons will be listed for all platforms,
9319 	// but now now I'm just focusing on my US keyboard
9320 	version(none)
9321 	enum Key {
9322 		LBUTTON = 0x01,
9323 		RBUTTON = 0x02,
9324 		CANCEL = 0x03,
9325 		MBUTTON = 0x04,
9326 		//static if (_WIN32_WINNT > =  0x500) {
9327 		XBUTTON1 = 0x05,
9328 		XBUTTON2 = 0x06,
9329 		//}
9330 		BACK = 0x08,
9331 		TAB = 0x09,
9332 		CLEAR = 0x0C,
9333 		RETURN = 0x0D,
9334 		SHIFT = 0x10,
9335 		CONTROL = 0x11,
9336 		MENU = 0x12,
9337 		PAUSE = 0x13,
9338 		CAPITAL = 0x14,
9339 		KANA = 0x15,
9340 		HANGEUL = 0x15,
9341 		HANGUL = 0x15,
9342 		JUNJA = 0x17,
9343 		FINAL = 0x18,
9344 		HANJA = 0x19,
9345 		KANJI = 0x19,
9346 		ESCAPE = 0x1B,
9347 		CONVERT = 0x1C,
9348 		NONCONVERT = 0x1D,
9349 		ACCEPT = 0x1E,
9350 		MODECHANGE = 0x1F,
9351 		SPACE = 0x20,
9352 		PRIOR = 0x21,
9353 		NEXT = 0x22,
9354 		END = 0x23,
9355 		HOME = 0x24,
9356 		LEFT = 0x25,
9357 		UP = 0x26,
9358 		RIGHT = 0x27,
9359 		DOWN = 0x28,
9360 		SELECT = 0x29,
9361 		PRINT = 0x2A,
9362 		EXECUTE = 0x2B,
9363 		SNAPSHOT = 0x2C,
9364 		INSERT = 0x2D,
9365 		DELETE = 0x2E,
9366 		HELP = 0x2F,
9367 		LWIN = 0x5B,
9368 		RWIN = 0x5C,
9369 		APPS = 0x5D,
9370 		SLEEP = 0x5F,
9371 		NUMPAD0 = 0x60,
9372 		NUMPAD1 = 0x61,
9373 		NUMPAD2 = 0x62,
9374 		NUMPAD3 = 0x63,
9375 		NUMPAD4 = 0x64,
9376 		NUMPAD5 = 0x65,
9377 		NUMPAD6 = 0x66,
9378 		NUMPAD7 = 0x67,
9379 		NUMPAD8 = 0x68,
9380 		NUMPAD9 = 0x69,
9381 		MULTIPLY = 0x6A,
9382 		ADD = 0x6B,
9383 		SEPARATOR = 0x6C,
9384 		SUBTRACT = 0x6D,
9385 		DECIMAL = 0x6E,
9386 		DIVIDE = 0x6F,
9387 		F1 = 0x70,
9388 		F2 = 0x71,
9389 		F3 = 0x72,
9390 		F4 = 0x73,
9391 		F5 = 0x74,
9392 		F6 = 0x75,
9393 		F7 = 0x76,
9394 		F8 = 0x77,
9395 		F9 = 0x78,
9396 		F10 = 0x79,
9397 		F11 = 0x7A,
9398 		F12 = 0x7B,
9399 		F13 = 0x7C,
9400 		F14 = 0x7D,
9401 		F15 = 0x7E,
9402 		F16 = 0x7F,
9403 		F17 = 0x80,
9404 		F18 = 0x81,
9405 		F19 = 0x82,
9406 		F20 = 0x83,
9407 		F21 = 0x84,
9408 		F22 = 0x85,
9409 		F23 = 0x86,
9410 		F24 = 0x87,
9411 		NUMLOCK = 0x90,
9412 		SCROLL = 0x91,
9413 		LSHIFT = 0xA0,
9414 		RSHIFT = 0xA1,
9415 		LCONTROL = 0xA2,
9416 		RCONTROL = 0xA3,
9417 		LMENU = 0xA4,
9418 		RMENU = 0xA5,
9419 		//static if (_WIN32_WINNT > =  0x500) {
9420 		BROWSER_BACK = 0xA6,
9421 		BROWSER_FORWARD = 0xA7,
9422 		BROWSER_REFRESH = 0xA8,
9423 		BROWSER_STOP = 0xA9,
9424 		BROWSER_SEARCH = 0xAA,
9425 		BROWSER_FAVORITES = 0xAB,
9426 		BROWSER_HOME = 0xAC,
9427 		VOLUME_MUTE = 0xAD,
9428 		VOLUME_DOWN = 0xAE,
9429 		VOLUME_UP = 0xAF,
9430 		MEDIA_NEXT_TRACK = 0xB0,
9431 		MEDIA_PREV_TRACK = 0xB1,
9432 		MEDIA_STOP = 0xB2,
9433 		MEDIA_PLAY_PAUSE = 0xB3,
9434 		LAUNCH_MAIL = 0xB4,
9435 		LAUNCH_MEDIA_SELECT = 0xB5,
9436 		LAUNCH_APP1 = 0xB6,
9437 		LAUNCH_APP2 = 0xB7,
9438 		//}
9439 		OEM_1 = 0xBA,
9440 		//static if (_WIN32_WINNT > =  0x500) {
9441 		OEM_PLUS = 0xBB,
9442 		OEM_COMMA = 0xBC,
9443 		OEM_MINUS = 0xBD,
9444 		OEM_PERIOD = 0xBE,
9445 		//}
9446 		OEM_2 = 0xBF,
9447 		OEM_3 = 0xC0,
9448 		OEM_4 = 0xDB,
9449 		OEM_5 = 0xDC,
9450 		OEM_6 = 0xDD,
9451 		OEM_7 = 0xDE,
9452 		OEM_8 = 0xDF,
9453 		//static if (_WIN32_WINNT > =  0x500) {
9454 		OEM_102 = 0xE2,
9455 		//}
9456 		PROCESSKEY = 0xE5,
9457 		//static if (_WIN32_WINNT > =  0x500) {
9458 		PACKET = 0xE7,
9459 		//}
9460 		ATTN = 0xF6,
9461 		CRSEL = 0xF7,
9462 		EXSEL = 0xF8,
9463 		EREOF = 0xF9,
9464 		PLAY = 0xFA,
9465 		ZOOM = 0xFB,
9466 		NONAME = 0xFC,
9467 		PA1 = 0xFD,
9468 		OEM_CLEAR = 0xFE,
9469 	}
9470 
9471 } else version(OSXCocoa) {
9472 	// FIXME
9473 	enum Key {
9474 		Escape = 0x1b,
9475 		F1 = 0x70,
9476 		F2 = 0x71,
9477 		F3 = 0x72,
9478 		F4 = 0x73,
9479 		F5 = 0x74,
9480 		F6 = 0x75,
9481 		F7 = 0x76,
9482 		F8 = 0x77,
9483 		F9 = 0x78,
9484 		F10 = 0x79,
9485 		F11 = 0x7a,
9486 		F12 = 0x7b,
9487 		PrintScreen = 0x2c,
9488 		ScrollLock = -2, // FIXME
9489 		Pause = -3, // FIXME
9490 		Grave = 0xc0,
9491 		// number keys across the top of the keyboard
9492 		N1 = 0x31,
9493 		N2 = 0x32,
9494 		N3 = 0x33,
9495 		N4 = 0x34,
9496 		N5 = 0x35,
9497 		N6 = 0x36,
9498 		N7 = 0x37,
9499 		N8 = 0x38,
9500 		N9 = 0x39,
9501 		N0 = 0x30,
9502 		Dash = 0xbd,
9503 		Equals = 0xbb,
9504 		Backslash = 0xdc,
9505 		Backspace = 0x08,
9506 		Insert = 0x2d,
9507 		Home = 0x24,
9508 		PageUp = 0x21,
9509 		Delete = 0x2e,
9510 		End = 0x23,
9511 		PageDown = 0x22,
9512 		Up = 0x26,
9513 		Down = 0x28,
9514 		Left = 0x25,
9515 		Right = 0x27,
9516 
9517 		Tab = 0x09,
9518 		Q = 0x51,
9519 		W = 0x57,
9520 		E = 0x45,
9521 		R = 0x52,
9522 		T = 0x54,
9523 		Y = 0x59,
9524 		U = 0x55,
9525 		I = 0x49,
9526 		O = 0x4f,
9527 		P = 0x50,
9528 		LeftBracket = 0xdb,
9529 		RightBracket = 0xdd,
9530 		CapsLock = 0x14,
9531 		A = 0x41,
9532 		S = 0x53,
9533 		D = 0x44,
9534 		F = 0x46,
9535 		G = 0x47,
9536 		H = 0x48,
9537 		J = 0x4a,
9538 		K = 0x4b,
9539 		L = 0x4c,
9540 		Semicolon = 0xba,
9541 		Apostrophe = 0xde,
9542 		Enter = 0x0d,
9543 		Shift = 0x10,
9544 		Z = 0x5a,
9545 		X = 0x58,
9546 		C = 0x43,
9547 		V = 0x56,
9548 		B = 0x42,
9549 		N = 0x4e,
9550 		M = 0x4d,
9551 		Comma = 0xbc,
9552 		Period = 0xbe,
9553 		Slash = 0xbf,
9554 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
9555 		Ctrl = 0x11,
9556 		Windows = 0x5b,
9557 		Alt = -5, // FIXME
9558 		Space = 0x20,
9559 		Alt_r = 0xffea, // ditto of shift_r
9560 		Windows_r = -6, // FIXME
9561 		Menu = 0x5d,
9562 		Ctrl_r = -7, // FIXME
9563 
9564 		NumLock = 0x90,
9565 		Divide = 0x6f,
9566 		Multiply = 0x6a,
9567 		Minus = 0x6d,
9568 		Plus = 0x6b,
9569 		PadEnter = -8, // FIXME
9570 		// FIXME for the rest of these:
9571 		Pad1 = 0xff9c,
9572 		Pad2 = 0xff99,
9573 		Pad3 = 0xff9b,
9574 		Pad4 = 0xff96,
9575 		Pad5 = 0xff9d,
9576 		Pad6 = 0xff98,
9577 		Pad7 = 0xff95,
9578 		Pad8 = 0xff97,
9579 		Pad9 = 0xff9a,
9580 		Pad0 = 0xff9e,
9581 		PadDot = 0xff9f,
9582 	}
9583 
9584 }
9585 
9586 /* Additional utilities */
9587 
9588 
9589 Color fromHsl(real h, real s, real l) {
9590 	return arsd.color.fromHsl([h,s,l]);
9591 }
9592 
9593 
9594 
9595 /* ********** What follows is the system-specific implementations *********/
9596 version(Windows) {
9597 
9598 
9599 	// helpers for making HICONs from MemoryImages
9600 	class WindowsIcon {
9601 		struct Win32Icon(int colorCount) {
9602 		align(1):
9603 			uint biSize;
9604 			int biWidth;
9605 			int biHeight;
9606 			ushort biPlanes;
9607 			ushort biBitCount;
9608 			uint biCompression;
9609 			uint biSizeImage;
9610 			int biXPelsPerMeter;
9611 			int biYPelsPerMeter;
9612 			uint biClrUsed;
9613 			uint biClrImportant;
9614 			RGBQUAD[colorCount] biColors;
9615 			/* Pixels:
9616 			Uint8 pixels[]
9617 			*/
9618 			/* Mask:
9619 			Uint8 mask[]
9620 			*/
9621 
9622 			ubyte[4096] data;
9623 
9624 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
9625 				width = mi.width;
9626 				height = mi.height;
9627 
9628 				auto indexedImage = cast(IndexedImage) mi;
9629 				if(indexedImage is null)
9630 					indexedImage = quantize(mi.getAsTrueColorImage());
9631 
9632 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
9633 				assert(height %4 == 0);
9634 
9635 				int icon_plen = height*((width+3)&~3);
9636 				int icon_mlen = height*((((width+7)/8)+3)&~3);
9637 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
9638 
9639 				biSize = 40;
9640 				biWidth = width;
9641 				biHeight = height*2;
9642 				biPlanes = 1;
9643 				biBitCount = 8;
9644 				biSizeImage = icon_plen+icon_mlen;
9645 
9646 				int offset = 0;
9647 				int andOff = icon_plen * 8; // the and offset is in bits
9648 				for(int y = height - 1; y >= 0; y--) {
9649 					int off2 = y * width;
9650 					foreach(x; 0 .. width) {
9651 						const b = indexedImage.data[off2 + x];
9652 						data[offset] = b;
9653 						offset++;
9654 
9655 						const andBit = andOff % 8;
9656 						const andIdx = andOff / 8;
9657 						assert(b < indexedImage.palette.length);
9658 						// this is anded to the destination, since and 0 means erase,
9659 						// we want that to  be opaque, and 1 for transparent
9660 						auto transparent = (indexedImage.palette[b].a <= 127);
9661 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
9662 
9663 						andOff++;
9664 					}
9665 
9666 					andOff += andOff % 32;
9667 				}
9668 
9669 				foreach(idx, entry; indexedImage.palette) {
9670 					if(entry.a > 127) {
9671 						biColors[idx].rgbBlue = entry.b;
9672 						biColors[idx].rgbGreen = entry.g;
9673 						biColors[idx].rgbRed = entry.r;
9674 					} else {
9675 						biColors[idx].rgbBlue = 255;
9676 						biColors[idx].rgbGreen = 255;
9677 						biColors[idx].rgbRed = 255;
9678 					}
9679 				}
9680 
9681 				/*
9682 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
9683 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
9684 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
9685 				auto pngMap = fetchPaletteWin32(png);
9686 				biColors[0..pngMap.length] = pngMap[];
9687 				*/
9688 			}
9689 		}
9690 
9691 
9692 		Win32Icon!(256) icon_win32;
9693 
9694 
9695 		this(MemoryImage mi) {
9696 			int icon_len, width, height;
9697 
9698 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
9699 
9700 			/*
9701 			PNG* png = readPnpngData);
9702 			PNGHeader pngh = getHeader(png);
9703 			void* icon_win32;
9704 			if(pngh.depth == 4) {
9705 				auto i = new Win32Icon!(16);
9706 				i.fromPNG(png, pngh, icon_len, width, height);
9707 				icon_win32 = i;
9708 			}
9709 			else if(pngh.depth == 8) {
9710 				auto i = new Win32Icon!(256);
9711 				i.fromPNG(png, pngh, icon_len, width, height);
9712 				icon_win32 = i;
9713 			} else assert(0);
9714 			*/
9715 
9716 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
9717 
9718 			if(hIcon is null) throw new Exception("CreateIconFromResourceEx");
9719 		}
9720 
9721 		~this() {
9722 			DestroyIcon(hIcon);
9723 		}
9724 
9725 		HICON hIcon;
9726 	}
9727 
9728 
9729 
9730 
9731 
9732 
9733 	alias int delegate(HWND, UINT, WPARAM, LPARAM) NativeEventHandler;
9734 	alias HWND NativeWindowHandle;
9735 
9736 	extern(Windows)
9737 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
9738 		try {
9739 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
9740 				// it returns zero if the message is handled, so we won't do anything more there
9741 				// do I like that though?
9742 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam);
9743 				if(ret == 0)
9744 					return ret;
9745 			}
9746 
9747 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
9748 				if(window.getNativeEventHandler !is null) {
9749 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam);
9750 					if(ret == 0)
9751 						return ret;
9752 				}
9753 				if(auto w = cast(SimpleWindow) (*window))
9754 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
9755 				else
9756 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
9757 			} else {
9758 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
9759 			}
9760 		} catch (Exception e) {
9761 			try {
9762 				sdpy_abort(e);
9763 				return 0;
9764 			} catch(Exception e) { assert(0); }
9765 		}
9766 	}
9767 
9768 	void sdpy_abort(Throwable e) nothrow {
9769 		try
9770 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
9771 		catch(Exception e)
9772 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
9773 		ExitProcess(1);
9774 	}
9775 
9776 	mixin template NativeScreenPainterImplementation() {
9777 		HDC hdc;
9778 		HWND hwnd;
9779 		//HDC windowHdc;
9780 		HBITMAP oldBmp;
9781 
9782 		void create(NativeWindowHandle window) {
9783 			hwnd = window;
9784 
9785 			if(auto sw = cast(SimpleWindow) this.window) {
9786 				// drawing on a window, double buffer
9787 				auto windowHdc = GetDC(hwnd);
9788 
9789 				auto buffer = sw.impl.buffer;
9790 				if(buffer is null) {
9791 					hdc = windowHdc;
9792 					windowDc = true;
9793 				} else {
9794 					hdc = CreateCompatibleDC(windowHdc);
9795 
9796 					ReleaseDC(hwnd, windowHdc);
9797 
9798 					oldBmp = SelectObject(hdc, buffer);
9799 				}
9800 			} else {
9801 				// drawing on something else, draw directly
9802 				hdc = CreateCompatibleDC(null);
9803 				SelectObject(hdc, window);
9804 
9805 			}
9806 
9807 			// X doesn't draw a text background, so neither should we
9808 			SetBkMode(hdc, TRANSPARENT);
9809 
9810 			ensureDefaultFontLoaded();
9811 
9812 			if(defaultGuiFont) {
9813 				SelectObject(hdc, defaultGuiFont);
9814 				// DeleteObject(defaultGuiFont);
9815 			}
9816 		}
9817 
9818 		static HFONT defaultGuiFont;
9819 		static void ensureDefaultFontLoaded() {
9820 			static bool triedDefaultGuiFont = false;
9821 			if(!triedDefaultGuiFont) {
9822 				NONCLIENTMETRICS params;
9823 				params.cbSize = params.sizeof;
9824 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
9825 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
9826 				}
9827 				triedDefaultGuiFont = true;
9828 			}
9829 		}
9830 
9831 		void setFont(OperatingSystemFont font) {
9832 			if(font && font.font) {
9833 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
9834 					// error... how to handle tho?
9835 				}
9836 			}
9837 			else if(defaultGuiFont)
9838 				SelectObject(hdc, defaultGuiFont);
9839 		}
9840 
9841 		arsd.color.Rectangle _clipRectangle;
9842 
9843 		void setClipRectangle(int x, int y, int width, int height) {
9844 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
9845 
9846 			if(width == 0 || height == 0) {
9847 				SelectClipRgn(hdc, null);
9848 			} else {
9849 				auto region = CreateRectRgn(x, y, x + width, y + height);
9850 				SelectClipRgn(hdc, region);
9851 				DeleteObject(region);
9852 			}
9853 		}
9854 
9855 
9856 		// just because we can on Windows...
9857 		//void create(Image image);
9858 
9859 		void dispose() {
9860 			// FIXME: this.window.width/height is probably wrong
9861 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
9862 			// ReleaseDC(hwnd, windowHdc);
9863 
9864 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
9865 			if(cast(SimpleWindow) this.window)
9866 			InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
9867 
9868 			if(originalPen !is null)
9869 				SelectObject(hdc, originalPen);
9870 			if(currentPen !is null)
9871 				DeleteObject(currentPen);
9872 			if(originalBrush !is null)
9873 				SelectObject(hdc, originalBrush);
9874 			if(currentBrush !is null)
9875 				DeleteObject(currentBrush);
9876 
9877 			SelectObject(hdc, oldBmp);
9878 
9879 			if(windowDc)
9880 				ReleaseDC(hwnd, hdc);
9881 			else
9882 				DeleteDC(hdc);
9883 
9884 			if(window.paintingFinishedDg !is null)
9885 				window.paintingFinishedDg()();
9886 		}
9887 
9888 		bool windowDc;
9889 		HPEN originalPen;
9890 		HPEN currentPen;
9891 
9892 		Pen _activePen;
9893 
9894 		@property void pen(Pen p) {
9895 			_activePen = p;
9896 
9897 			HPEN pen;
9898 			if(p.color.a == 0) {
9899 				pen = GetStockObject(NULL_PEN);
9900 			} else {
9901 				int style = PS_SOLID;
9902 				final switch(p.style) {
9903 					case Pen.Style.Solid:
9904 						style = PS_SOLID;
9905 					break;
9906 					case Pen.Style.Dashed:
9907 						style = PS_DASH;
9908 					break;
9909 					case Pen.Style.Dotted:
9910 						style = PS_DOT;
9911 					break;
9912 				}
9913 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
9914 			}
9915 			auto orig = SelectObject(hdc, pen);
9916 			if(originalPen is null)
9917 				originalPen = orig;
9918 
9919 			if(currentPen !is null)
9920 				DeleteObject(currentPen);
9921 
9922 			currentPen = pen;
9923 
9924 			// the outline is like a foreground since it's done that way on X
9925 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
9926 
9927 		}
9928 
9929 		@property void rasterOp(RasterOp op) {
9930 			int mode;
9931 			final switch(op) {
9932 				case RasterOp.normal:
9933 					mode = R2_COPYPEN;
9934 				break;
9935 				case RasterOp.xor:
9936 					mode = R2_XORPEN;
9937 				break;
9938 			}
9939 			SetROP2(hdc, mode);
9940 		}
9941 
9942 		HBRUSH originalBrush;
9943 		HBRUSH currentBrush;
9944 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
9945 		@property void fillColor(Color c) {
9946 			if(c == _fillColor)
9947 				return;
9948 			_fillColor = c;
9949 			HBRUSH brush;
9950 			if(c.a == 0) {
9951 				brush = GetStockObject(HOLLOW_BRUSH);
9952 			} else {
9953 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
9954 			}
9955 			auto orig = SelectObject(hdc, brush);
9956 			if(originalBrush is null)
9957 				originalBrush = orig;
9958 
9959 			if(currentBrush !is null)
9960 				DeleteObject(currentBrush);
9961 
9962 			currentBrush = brush;
9963 
9964 			// background color is NOT set because X doesn't draw text backgrounds
9965 			//   SetBkColor(hdc, RGB(255, 255, 255));
9966 		}
9967 
9968 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
9969 			BITMAP bm;
9970 
9971 			HDC hdcMem = CreateCompatibleDC(hdc);
9972 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
9973 
9974 			GetObject(i.handle, bm.sizeof, &bm);
9975 
9976 			// or should I AlphaBlend!??!?!
9977 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
9978 
9979 			SelectObject(hdcMem, hbmOld);
9980 			DeleteDC(hdcMem);
9981 		}
9982 
9983 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
9984 			BITMAP bm;
9985 
9986 			HDC hdcMem = CreateCompatibleDC(hdc);
9987 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
9988 
9989 			GetObject(s.handle, bm.sizeof, &bm);
9990 
9991 			version(CRuntime_DigitalMars) goto noalpha;
9992 
9993 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
9994 			if(s.enableAlpha) {
9995 				auto dw = w ? w : bm.bmWidth;
9996 				auto dh = h ? h : bm.bmHeight;
9997 				BLENDFUNCTION bf;
9998 				bf.BlendOp = AC_SRC_OVER;
9999 				bf.SourceConstantAlpha = 255;
10000 				bf.AlphaFormat = AC_SRC_ALPHA;
10001 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
10002 			} else {
10003 				noalpha:
10004 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
10005 			}
10006 
10007 			SelectObject(hdcMem, hbmOld);
10008 			DeleteDC(hdcMem);
10009 		}
10010 
10011 		Size textSize(scope const(char)[] text) {
10012 			bool dummyX;
10013 			if(text.length == 0) {
10014 				text = " ";
10015 				dummyX = true;
10016 			}
10017 			RECT rect;
10018 			WCharzBuffer buffer = WCharzBuffer(text);
10019 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT);
10020 			return Size(dummyX ? 0 : rect.right, rect.bottom);
10021 		}
10022 
10023 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
10024 			if(text.length && text[$-1] == '\n')
10025 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
10026 			if(text.length && text[$-1] == '\r')
10027 				text = text[0 .. $-1];
10028 
10029 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
10030 			if(x2 == 0 && y2 == 0)
10031 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
10032 			else {
10033 				RECT rect;
10034 				rect.left = x;
10035 				rect.top = y;
10036 				rect.right = x2;
10037 				rect.bottom = y2;
10038 
10039 				uint mode = DT_LEFT;
10040 				if(alignment & TextAlignment.Right)
10041 					mode = DT_RIGHT;
10042 				else if(alignment & TextAlignment.Center)
10043 					mode = DT_CENTER;
10044 
10045 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
10046 				if(alignment & TextAlignment.VerticalCenter)
10047 					mode |= DT_VCENTER | DT_SINGLELINE;
10048 
10049 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode);
10050 			}
10051 
10052 			/*
10053 			uint mode;
10054 
10055 			if(alignment & TextAlignment.Center)
10056 				mode = TA_CENTER;
10057 
10058 			SetTextAlign(hdc, mode);
10059 			*/
10060 		}
10061 
10062 		int fontHeight() {
10063 			TEXTMETRIC metric;
10064 			if(GetTextMetricsW(hdc, &metric)) {
10065 				return metric.tmHeight;
10066 			}
10067 
10068 			return 16; // idk just guessing here, maybe we should throw
10069 		}
10070 
10071 		void drawPixel(int x, int y) {
10072 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
10073 		}
10074 
10075 		// The basic shapes, outlined
10076 
10077 		void drawLine(int x1, int y1, int x2, int y2) {
10078 			MoveToEx(hdc, x1, y1, null);
10079 			LineTo(hdc, x2, y2);
10080 		}
10081 
10082 		void drawRectangle(int x, int y, int width, int height) {
10083 			// FIXME: with a wider pen this might not draw quite right. im not sure.
10084 			gdi.Rectangle(hdc, x, y, x + width, y + height);
10085 		}
10086 
10087 		/// Arguments are the points of the bounding rectangle
10088 		void drawEllipse(int x1, int y1, int x2, int y2) {
10089 			Ellipse(hdc, x1, y1, x2, y2);
10090 		}
10091 
10092 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
10093 			if((start % (360*64)) == (finish % (360*64)))
10094 				drawEllipse(x1, y1, x1 + width, y1 + height);
10095 			else {
10096 				import core.stdc.math;
10097 				float startAngle = start * 64 * 180 / 3.14159265;
10098 				float endAngle = finish * 64 * 180 / 3.14159265;
10099 				Arc(hdc, x1, y1, x1 + width, y1 + height,
10100 					cast(int)(cos(startAngle) * width / 2 + x1),
10101 					cast(int)(sin(startAngle) * height / 2 + y1),
10102 					cast(int)(cos(endAngle) * width / 2 + x1),
10103 					cast(int)(sin(endAngle) * height / 2 + y1),
10104 				);
10105 			}
10106 		}
10107 
10108 		void drawPolygon(Point[] vertexes) {
10109 			POINT[] points;
10110 			points.length = vertexes.length;
10111 
10112 			foreach(i, p; vertexes) {
10113 				points[i].x = p.x;
10114 				points[i].y = p.y;
10115 			}
10116 
10117 			Polygon(hdc, points.ptr, cast(int) points.length);
10118 		}
10119 	}
10120 
10121 
10122 	// Mix this into the SimpleWindow class
10123 	mixin template NativeSimpleWindowImplementation() {
10124 		int curHidden = 0; // counter
10125 		__gshared static bool[string] knownWinClasses;
10126 		static bool altPressed = false;
10127 
10128 		HANDLE oldCursor;
10129 
10130 		void hideCursor () {
10131 			if(curHidden == 0)
10132 				oldCursor = SetCursor(null);
10133 			++curHidden;
10134 		}
10135 
10136 		void showCursor () {
10137 			--curHidden;
10138 			if(curHidden == 0) {
10139 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
10140 			}
10141 		}
10142 
10143 
10144 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
10145 
10146 		void setMinSize (int minwidth, int minheight) {
10147 			minWidth = minwidth;
10148 			minHeight = minheight;
10149 		}
10150 		void setMaxSize (int maxwidth, int maxheight) {
10151 			maxWidth = maxwidth;
10152 			maxHeight = maxheight;
10153 		}
10154 
10155 		// FIXME i'm not sure that Windows has this functionality
10156 		// though it is nonessential anyway.
10157 		void setResizeGranularity (int granx, int grany) {}
10158 
10159 		ScreenPainter getPainter() {
10160 			return ScreenPainter(this, hwnd);
10161 		}
10162 
10163 		HBITMAP buffer;
10164 
10165 		void setTitle(string title) {
10166 			WCharzBuffer bfr = WCharzBuffer(title);
10167 			SetWindowTextW(hwnd, bfr.ptr);
10168 		}
10169 
10170 		string getTitle() {
10171 			auto len = GetWindowTextLengthW(hwnd);
10172 			if (!len)
10173 				return null;
10174 			wchar[256] tmpBuffer;
10175 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
10176 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
10177 			auto str = buffer[0 .. len2];
10178 			return makeUtf8StringFromWindowsString(str);
10179 		}
10180 
10181 		void move(int x, int y) {
10182 			RECT rect;
10183 			GetWindowRect(hwnd, &rect);
10184 			// move it while maintaining the same size...
10185 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
10186 		}
10187 
10188 		void resize(int w, int h) {
10189 			RECT rect;
10190 			GetWindowRect(hwnd, &rect);
10191 
10192 			RECT client;
10193 			GetClientRect(hwnd, &client);
10194 
10195 			rect.right = rect.right - client.right + w;
10196 			rect.bottom = rect.bottom - client.bottom + h;
10197 
10198 			// same position, new size for the client rectangle
10199 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
10200 
10201 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
10202 		}
10203 
10204 		void moveResize (int x, int y, int w, int h) {
10205 			// what's given is the client rectangle, we need to adjust
10206 
10207 			RECT rect;
10208 			rect.left = x;
10209 			rect.top = y;
10210 			rect.right = w + x;
10211 			rect.bottom = h + y;
10212 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
10213 				throw new Exception("AdjustWindowRect");
10214 
10215 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
10216 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
10217 			if (windowResized !is null) windowResized(w, h);
10218 		}
10219 
10220 		version(without_opengl) {} else {
10221 			HGLRC ghRC;
10222 			HDC ghDC;
10223 		}
10224 
10225 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
10226 			string cnamec;
10227 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
10228 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
10229 				cnamec = "DSimpleWindow";
10230 			} else {
10231 				cnamec = sdpyWindowClass;
10232 			}
10233 
10234 			WCharzBuffer cn = WCharzBuffer(cnamec);
10235 
10236 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
10237 
10238 			if(cnamec !in knownWinClasses) {
10239 				WNDCLASSEX wc;
10240 
10241 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
10242 				// to the object. Maybe.
10243 				wc.cbSize = wc.sizeof;
10244 				wc.cbClsExtra = 0;
10245 				wc.cbWndExtra = 0;
10246 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
10247 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
10248 				wc.hIcon = LoadIcon(hInstance, null);
10249 				wc.hInstance = hInstance;
10250 				wc.lpfnWndProc = &WndProc;
10251 				wc.lpszClassName = cn.ptr;
10252 				wc.hIconSm = null;
10253 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
10254 				if(!RegisterClassExW(&wc))
10255 					throw new WindowsApiException("RegisterClassExW");
10256 				knownWinClasses[cnamec] = true;
10257 			}
10258 
10259 			int style;
10260 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
10261 
10262 			// FIXME: windowType and customizationFlags
10263 			final switch(windowType) {
10264 				case WindowTypes.normal:
10265 					style = WS_OVERLAPPEDWINDOW;
10266 				break;
10267 				case WindowTypes.undecorated:
10268 					style = WS_POPUP | WS_SYSMENU;
10269 				break;
10270 				case WindowTypes.eventOnly:
10271 					_hidden = true;
10272 				break;
10273 				case WindowTypes.dropdownMenu:
10274 				case WindowTypes.popupMenu:
10275 				case WindowTypes.notification:
10276 					style = WS_POPUP;
10277 					flags |= WS_EX_NOACTIVATE;
10278 				break;
10279 				case WindowTypes.nestedChild:
10280 					style = WS_CHILD;
10281 				break;
10282 			}
10283 
10284 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
10285 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
10286 
10287 			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
10288 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
10289 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
10290 
10291 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
10292 				setOpacity(255);
10293 
10294 			SimpleWindow.nativeMapping[hwnd] = this;
10295 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
10296 
10297 			if(windowType == WindowTypes.eventOnly)
10298 				return;
10299 
10300 			HDC hdc = GetDC(hwnd);
10301 
10302 
10303 			version(without_opengl) {}
10304 			else {
10305 				if(opengl == OpenGlOptions.yes) {
10306 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
10307 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
10308 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
10309 					ghDC = hdc;
10310 					PIXELFORMATDESCRIPTOR pfd;
10311 
10312 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
10313 					pfd.nVersion = 1;
10314 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
10315 					pfd.dwLayerMask = PFD_MAIN_PLANE;
10316 					pfd.iPixelType = PFD_TYPE_RGBA;
10317 					pfd.cColorBits = 24;
10318 					pfd.cDepthBits = 24;
10319 					pfd.cAccumBits = 0;
10320 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
10321 
10322 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
10323 
10324 					if (pixelformat == 0)
10325 						throw new WindowsApiException("ChoosePixelFormat");
10326 
10327 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
10328 						throw new WindowsApiException("SetPixelFormat");
10329 
10330 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
10331 						// windoze is idiotic: we have to have OpenGL context to get function addresses
10332 						// so we will create fake context to get that stupid address
10333 						auto tmpcc = wglCreateContext(ghDC);
10334 						if (tmpcc !is null) {
10335 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
10336 							wglMakeCurrent(ghDC, tmpcc);
10337 							wglInitOtherFunctions();
10338 						}
10339 					}
10340 
10341 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
10342 						int[9] contextAttribs = [
10343 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
10344 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
10345 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
10346 							// for modern context, set "forward compatibility" flag too
10347 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
10348 							0/*None*/,
10349 						];
10350 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
10351 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
10352 							// activate fallback mode
10353 							// 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;
10354 							ghRC = wglCreateContext(ghDC);
10355 						}
10356 						if (ghRC is null)
10357 							throw new WindowsApiException("wglCreateContextAttribsARB");
10358 					} else {
10359 						// try to do at least something
10360 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
10361 							sdpyOpenGLContextVersion = 0;
10362 							ghRC = wglCreateContext(ghDC);
10363 						}
10364 						if (ghRC is null)
10365 							throw new WindowsApiException("wglCreateContext");
10366 					}
10367 				}
10368 			}
10369 
10370 			if(opengl == OpenGlOptions.no) {
10371 				buffer = CreateCompatibleBitmap(hdc, width, height);
10372 
10373 				auto hdcBmp = CreateCompatibleDC(hdc);
10374 				// make sure it's filled with a blank slate
10375 				auto oldBmp = SelectObject(hdcBmp, buffer);
10376 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
10377 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
10378 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
10379 				SelectObject(hdcBmp, oldBmp);
10380 				SelectObject(hdcBmp, oldBrush);
10381 				SelectObject(hdcBmp, oldPen);
10382 				DeleteDC(hdcBmp);
10383 
10384 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
10385 			}
10386 
10387 			// We want the window's client area to match the image size
10388 			RECT rcClient, rcWindow;
10389 			POINT ptDiff;
10390 			GetClientRect(hwnd, &rcClient);
10391 			GetWindowRect(hwnd, &rcWindow);
10392 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
10393 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
10394 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
10395 
10396 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
10397 				ShowWindow(hwnd, SW_SHOWNORMAL);
10398 			} else {
10399 				_hidden = true;
10400 			}
10401 			this._visibleForTheFirstTimeCalled = false; // hack!
10402 		}
10403 
10404 
10405 		void dispose() {
10406 			if(buffer)
10407 				DeleteObject(buffer);
10408 		}
10409 
10410 		void closeWindow() {
10411 			DestroyWindow(hwnd);
10412 		}
10413 
10414 		bool setOpacity(ubyte alpha) {
10415 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
10416 		}
10417 
10418 		HANDLE currentCursor;
10419 
10420 		// returns zero if it recognized the event
10421 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
10422 			MouseEvent mouse;
10423 
10424 			void mouseEvent(bool isScreen, ulong mods) {
10425 				auto x = LOWORD(lParam);
10426 				auto y = HIWORD(lParam);
10427 				if(isScreen) {
10428 					POINT p;
10429 					p.x = x;
10430 					p.y = y;
10431 					ScreenToClient(hwnd, &p);
10432 					x = cast(ushort) p.x;
10433 					y = cast(ushort) p.y;
10434 				}
10435 				mouse.x = x + offsetX;
10436 				mouse.y = y + offsetY;
10437 
10438 				wind.mdx(mouse);
10439 				mouse.modifierState = cast(int) mods;
10440 				mouse.window = wind;
10441 
10442 				if(wind.handleMouseEvent)
10443 					wind.handleMouseEvent(mouse);
10444 			}
10445 
10446 			switch(msg) {
10447 				case WM_GETMINMAXINFO:
10448 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
10449 
10450 					if(wind.minWidth > 0) {
10451 						RECT rect;
10452 						rect.left = 100;
10453 						rect.top = 100;
10454 						rect.right = wind.minWidth + 100;
10455 						rect.bottom = wind.minHeight + 100;
10456 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
10457 							throw new WindowsApiException("AdjustWindowRect");
10458 
10459 						mmi.ptMinTrackSize.x = rect.right - rect.left;
10460 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
10461 					}
10462 
10463 					if(wind.maxWidth < int.max) {
10464 						RECT rect;
10465 						rect.left = 100;
10466 						rect.top = 100;
10467 						rect.right = wind.maxWidth + 100;
10468 						rect.bottom = wind.maxHeight + 100;
10469 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
10470 							throw new WindowsApiException("AdjustWindowRect");
10471 
10472 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
10473 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
10474 					}
10475 				break;
10476 				case WM_CHAR:
10477 					wchar c = cast(wchar) wParam;
10478 					if(wind.handleCharEvent)
10479 						wind.handleCharEvent(cast(dchar) c);
10480 				break;
10481 				  case WM_SETFOCUS:
10482 				  case WM_KILLFOCUS:
10483 					wind._focused = (msg == WM_SETFOCUS);
10484 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
10485 					if(wind.onFocusChange)
10486 						wind.onFocusChange(msg == WM_SETFOCUS);
10487 				  break;
10488 				case WM_SYSKEYDOWN:
10489 					goto case;
10490 				case WM_SYSKEYUP:
10491 					if(lParam & (1 << 29)) {
10492 						goto case;
10493 					} else {
10494 						// no window has keyboard focus
10495 						goto default;
10496 					}
10497 				case WM_KEYDOWN:
10498 				case WM_KEYUP:
10499 					KeyEvent ev;
10500 					ev.key = cast(Key) wParam;
10501 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
10502 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
10503 
10504 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
10505 
10506 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
10507 						ev.modifierState |= ModifierState.shift;
10508 					//k8: this doesn't work; thanks for nothing, windows
10509 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
10510 						ev.modifierState |= ModifierState.alt;*/
10511 					if (wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN);
10512 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
10513 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
10514 						ev.modifierState |= ModifierState.ctrl;
10515 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
10516 						ev.modifierState |= ModifierState.windows;
10517 					if(GetKeyState(Key.NumLock))
10518 						ev.modifierState |= ModifierState.numLock;
10519 					if(GetKeyState(Key.CapsLock))
10520 						ev.modifierState |= ModifierState.capsLock;
10521 
10522 					/+
10523 					// we always want to send the character too, so let's convert it
10524 					ubyte[256] state;
10525 					wchar[16] buffer;
10526 					GetKeyboardState(state.ptr);
10527 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
10528 
10529 					foreach(dchar d; buffer) {
10530 						ev.character = d;
10531 						break;
10532 					}
10533 					+/
10534 
10535 					ev.window = wind;
10536 					if(wind.handleKeyEvent)
10537 						wind.handleKeyEvent(ev);
10538 				break;
10539 				case 0x020a /*WM_MOUSEWHEEL*/:
10540 					// send click
10541 					mouse.type = cast(MouseEventType) 1;
10542 					mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp);
10543 					mouseEvent(true, LOWORD(wParam));
10544 
10545 					// also send release
10546 					mouse.type = cast(MouseEventType) 2;
10547 					mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp);
10548 					mouseEvent(true, LOWORD(wParam));
10549 				break;
10550 				case WM_MOUSEMOVE:
10551 					mouse.type = cast(MouseEventType) 0;
10552 					mouseEvent(false, wParam);
10553 				break;
10554 				case WM_LBUTTONDOWN:
10555 				case WM_LBUTTONDBLCLK:
10556 					mouse.type = cast(MouseEventType) 1;
10557 					mouse.button = MouseButton.left;
10558 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
10559 					mouseEvent(false, wParam);
10560 				break;
10561 				case WM_LBUTTONUP:
10562 					mouse.type = cast(MouseEventType) 2;
10563 					mouse.button = MouseButton.left;
10564 					mouseEvent(false, wParam);
10565 				break;
10566 				case WM_RBUTTONDOWN:
10567 				case WM_RBUTTONDBLCLK:
10568 					mouse.type = cast(MouseEventType) 1;
10569 					mouse.button = MouseButton.right;
10570 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
10571 					mouseEvent(false, wParam);
10572 				break;
10573 				case WM_RBUTTONUP:
10574 					mouse.type = cast(MouseEventType) 2;
10575 					mouse.button = MouseButton.right;
10576 					mouseEvent(false, wParam);
10577 				break;
10578 				case WM_MBUTTONDOWN:
10579 				case WM_MBUTTONDBLCLK:
10580 					mouse.type = cast(MouseEventType) 1;
10581 					mouse.button = MouseButton.middle;
10582 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
10583 					mouseEvent(false, wParam);
10584 				break;
10585 				case WM_MBUTTONUP:
10586 					mouse.type = cast(MouseEventType) 2;
10587 					mouse.button = MouseButton.middle;
10588 					mouseEvent(false, wParam);
10589 				break;
10590 				case WM_XBUTTONDOWN:
10591 				case WM_XBUTTONDBLCLK:
10592 					mouse.type = cast(MouseEventType) 1;
10593 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
10594 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
10595 					mouseEvent(false, wParam);
10596 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
10597 				case WM_XBUTTONUP:
10598 					mouse.type = cast(MouseEventType) 2;
10599 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
10600 					mouseEvent(false, wParam);
10601 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
10602 
10603 				default: return 1;
10604 			}
10605 			return 0;
10606 		}
10607 
10608 		HWND hwnd;
10609 		int oldWidth;
10610 		int oldHeight;
10611 		bool inSizeMove;
10612 
10613 		// the extern(Windows) wndproc should just forward to this
10614 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
10615 			assert(hwnd is this.hwnd);
10616 
10617 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
10618 			switch(msg) {
10619 				case WM_SETCURSOR:
10620 					if(cast(HWND) wParam !is hwnd)
10621 						return 0; // further processing elsewhere
10622 
10623 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
10624 						SetCursor(this.curHidden > 0 ? null : currentCursor);
10625 						return 1;
10626 					} else {
10627 						return DefWindowProc(hwnd, msg, wParam, lParam);
10628 					}
10629 				//break;
10630 
10631 				case WM_CLOSE:
10632 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
10633 				break;
10634 				case WM_DESTROY:
10635 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
10636 					SimpleWindow.nativeMapping.remove(hwnd);
10637 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
10638 
10639 					bool anyImportant = false;
10640 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
10641 						if(w.beingOpenKeepsAppOpen) {
10642 							anyImportant = true;
10643 							break;
10644 						}
10645 					if(!anyImportant) {
10646 						PostQuitMessage(0);
10647 					}
10648 				break;
10649 				case WM_SIZE:
10650 					if(wParam == 1 /* SIZE_MINIMIZED */)
10651 						break;
10652 					_width = LOWORD(lParam);
10653 					_height = HIWORD(lParam);
10654 
10655 					// I want to avoid tearing in the windows (my code is inefficient
10656 					// so this is a hack around that) so while sizing, we don't trigger,
10657 					// but we do want to trigger on events like mazimize.
10658 					if(!inSizeMove)
10659 						goto size_changed;
10660 				break;
10661 				// I don't like the tearing I get when redrawing on WM_SIZE
10662 				// (I know there's other ways to fix that but I don't like that behavior anyway)
10663 				// so instead it is going to redraw only at the end of a size.
10664 				case 0x0231: /* WM_ENTERSIZEMOVE */
10665 					oldWidth = this.width;
10666 					oldHeight = this.height;
10667 					inSizeMove = true;
10668 				break;
10669 				case 0x0232: /* WM_EXITSIZEMOVE */
10670 					inSizeMove = false;
10671 					// nothing relevant changed, don't bother redrawing
10672 					if(oldWidth == width && oldHeight == height)
10673 						break;
10674 
10675 					size_changed:
10676 
10677 					// note: OpenGL windows don't use a backing bmp, so no need to change them
10678 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
10679 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
10680 						// gotta get the double buffer bmp to match the window
10681 					// FIXME: could this be more efficient? It isn't really necessary to make
10682 					// a new buffer if we're sizing down at least.
10683 						auto hdc = GetDC(hwnd);
10684 						auto oldBuffer = buffer;
10685 						buffer = CreateCompatibleBitmap(hdc, width, height);
10686 
10687 						auto hdcBmp = CreateCompatibleDC(hdc);
10688 						auto oldBmp = SelectObject(hdcBmp, buffer);
10689 
10690 						auto hdcOldBmp = CreateCompatibleDC(hdc);
10691 						auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp);
10692 
10693 						BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY);
10694 
10695 						SelectObject(hdcOldBmp, oldOldBmp);
10696 						DeleteDC(hdcOldBmp);
10697 
10698 						SelectObject(hdcBmp, oldBmp);
10699 						DeleteDC(hdcBmp);
10700 
10701 						ReleaseDC(hwnd, hdc);
10702 
10703 						DeleteObject(oldBuffer);
10704 					}
10705 
10706 					version(without_opengl) {} else
10707 					if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
10708 						glViewport(0, 0, width, height);
10709 					}
10710 
10711 					if(windowResized !is null)
10712 						windowResized(width, height);
10713 				break;
10714 				case WM_ERASEBKGND:
10715 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
10716 					if (!this._visibleForTheFirstTimeCalled) {
10717 						this._visibleForTheFirstTimeCalled = true;
10718 						if (this.visibleForTheFirstTime !is null) {
10719 							version(without_opengl) {} else {
10720 								if(openglMode == OpenGlOptions.yes) {
10721 									this.setAsCurrentOpenGlContextNT();
10722 									glViewport(0, 0, width, height);
10723 								}
10724 							}
10725 							this.visibleForTheFirstTime();
10726 						}
10727 					}
10728 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
10729 					version(without_opengl) {} else {
10730 						if (openglMode == OpenGlOptions.yes) return 1;
10731 					}
10732 					// call windows default handler, so it can paint standard controls
10733 					goto default;
10734 				case WM_CTLCOLORBTN:
10735 				case WM_CTLCOLORSTATIC:
10736 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
10737 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
10738 					GetSysColorBrush(COLOR_3DFACE);
10739 				//break;
10740 				case WM_SHOWWINDOW:
10741 					this._visible = (wParam != 0);
10742 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
10743 						this._visibleForTheFirstTimeCalled = true;
10744 						if (this.visibleForTheFirstTime !is null) {
10745 							version(without_opengl) {} else {
10746 								if(openglMode == OpenGlOptions.yes) {
10747 									this.setAsCurrentOpenGlContextNT();
10748 									glViewport(0, 0, width, height);
10749 								}
10750 							}
10751 							this.visibleForTheFirstTime();
10752 						}
10753 					}
10754 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
10755 					break;
10756 				case WM_PAINT: {
10757 					if (!this._visibleForTheFirstTimeCalled) {
10758 						this._visibleForTheFirstTimeCalled = true;
10759 						if (this.visibleForTheFirstTime !is null) {
10760 							version(without_opengl) {} else {
10761 								if(openglMode == OpenGlOptions.yes) {
10762 									this.setAsCurrentOpenGlContextNT();
10763 									glViewport(0, 0, width, height);
10764 								}
10765 							}
10766 							this.visibleForTheFirstTime();
10767 						}
10768 					}
10769 
10770 					BITMAP bm;
10771 					PAINTSTRUCT ps;
10772 
10773 					HDC hdc = BeginPaint(hwnd, &ps);
10774 
10775 					if(openglMode == OpenGlOptions.no) {
10776 
10777 						HDC hdcMem = CreateCompatibleDC(hdc);
10778 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
10779 
10780 						GetObject(buffer, bm.sizeof, &bm);
10781 
10782 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
10783 						if(resizability == Resizability.automaticallyScaleIfPossible)
10784 						StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
10785 						else
10786 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
10787 
10788 						SelectObject(hdcMem, hbmOld);
10789 						DeleteDC(hdcMem);
10790 						EndPaint(hwnd, &ps);
10791 					} else {
10792 						EndPaint(hwnd, &ps);
10793 						version(without_opengl) {} else
10794 							redrawOpenGlSceneNow();
10795 					}
10796 				} break;
10797 				  default:
10798 					return DefWindowProc(hwnd, msg, wParam, lParam);
10799 			}
10800 			 return 0;
10801 
10802 		}
10803 	}
10804 
10805 	mixin template NativeImageImplementation() {
10806 		HBITMAP handle;
10807 		ubyte* rawData;
10808 
10809 	final:
10810 
10811 		Color getPixel(int x, int y) {
10812 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
10813 			// remember, bmps are upside down
10814 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
10815 
10816 			Color c;
10817 			if(enableAlpha)
10818 				c.a = rawData[offset + 3];
10819 			else
10820 				c.a = 255;
10821 			c.b = rawData[offset + 0];
10822 			c.g = rawData[offset + 1];
10823 			c.r = rawData[offset + 2];
10824 			c.unPremultiply();
10825 			return c;
10826 		}
10827 
10828 		void setPixel(int x, int y, Color c) {
10829 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
10830 			// remember, bmps are upside down
10831 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
10832 
10833 			if(enableAlpha)
10834 				c.premultiply();
10835 
10836 			rawData[offset + 0] = c.b;
10837 			rawData[offset + 1] = c.g;
10838 			rawData[offset + 2] = c.r;
10839 			if(enableAlpha)
10840 				rawData[offset + 3] = c.a;
10841 		}
10842 
10843 		void convertToRgbaBytes(ubyte[] where) {
10844 			assert(where.length == this.width * this.height * 4);
10845 
10846 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
10847 			int idx = 0;
10848 			int offset = itemsPerLine * (height - 1);
10849 			// remember, bmps are upside down
10850 			for(int y = height - 1; y >= 0; y--) {
10851 				auto offsetStart = offset;
10852 				for(int x = 0; x < width; x++) {
10853 					where[idx + 0] = rawData[offset + 2]; // r
10854 					where[idx + 1] = rawData[offset + 1]; // g
10855 					where[idx + 2] = rawData[offset + 0]; // b
10856 					if(enableAlpha) {
10857 						where[idx + 3] = rawData[offset + 3]; // a
10858 						unPremultiplyRgba(where[idx .. idx + 4]);
10859 						offset++;
10860 					} else
10861 						where[idx + 3] = 255; // a
10862 					idx += 4;
10863 					offset += 3;
10864 				}
10865 
10866 				offset = offsetStart - itemsPerLine;
10867 			}
10868 		}
10869 
10870 		void setFromRgbaBytes(in ubyte[] what) {
10871 			assert(what.length == this.width * this.height * 4);
10872 
10873 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
10874 			int idx = 0;
10875 			int offset = itemsPerLine * (height - 1);
10876 			// remember, bmps are upside down
10877 			for(int y = height - 1; y >= 0; y--) {
10878 				auto offsetStart = offset;
10879 				for(int x = 0; x < width; x++) {
10880 					if(enableAlpha) {
10881 						auto a = what[idx + 3];
10882 
10883 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
10884 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
10885 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
10886 						rawData[offset + 3] = a; // a
10887 						premultiplyBgra(rawData[offset .. offset + 4]);
10888 						offset++;
10889 					} else {
10890 						rawData[offset + 2] = what[idx + 0]; // r
10891 						rawData[offset + 1] = what[idx + 1]; // g
10892 						rawData[offset + 0] = what[idx + 2]; // b
10893 					}
10894 					idx += 4;
10895 					offset += 3;
10896 				}
10897 
10898 				offset = offsetStart - itemsPerLine;
10899 			}
10900 		}
10901 
10902 
10903 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
10904 			BITMAPINFO infoheader;
10905 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10906 			infoheader.bmiHeader.biWidth = width;
10907 			infoheader.bmiHeader.biHeight = height;
10908 			infoheader.bmiHeader.biPlanes = 1;
10909 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
10910 			infoheader.bmiHeader.biCompression = BI_RGB;
10911 
10912 			handle = CreateDIBSection(
10913 				null,
10914 				&infoheader,
10915 				DIB_RGB_COLORS,
10916 				cast(void**) &rawData,
10917 				null,
10918 				0);
10919 			if(handle is null)
10920 				throw new WindowsApiException("create image failed");
10921 
10922 		}
10923 
10924 		void dispose() {
10925 			DeleteObject(handle);
10926 		}
10927 	}
10928 
10929 	enum KEY_ESCAPE = 27;
10930 }
10931 version(X11) {
10932 	/// This is the default font used. You might change this before doing anything else with
10933 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
10934 	/// for cross-platform compatibility.
10935 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
10936 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
10937 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
10938 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
10939 
10940 	alias int delegate(XEvent) NativeEventHandler;
10941 	alias Window NativeWindowHandle;
10942 
10943 	enum KEY_ESCAPE = 9;
10944 
10945 	mixin template NativeScreenPainterImplementation() {
10946 		Display* display;
10947 		Drawable d;
10948 		Drawable destiny;
10949 
10950 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
10951 		GC gc;
10952 
10953 		__gshared bool fontAttempted;
10954 
10955 		__gshared XFontStruct* defaultfont;
10956 		__gshared XFontSet defaultfontset;
10957 
10958 		XFontStruct* font;
10959 		XFontSet fontset;
10960 
10961 		void create(NativeWindowHandle window) {
10962 			this.display = XDisplayConnection.get();
10963 
10964 			Drawable buffer = None;
10965 			if(auto sw = cast(SimpleWindow) this.window) {
10966 				buffer = sw.impl.buffer;
10967 				this.destiny = cast(Drawable) window;
10968 			} else {
10969 				buffer = cast(Drawable) window;
10970 				this.destiny = None;
10971 			}
10972 
10973 			this.d = cast(Drawable) buffer;
10974 
10975 			auto dgc = DefaultGC(display, DefaultScreen(display));
10976 
10977 			this.gc = XCreateGC(display, d, 0, null);
10978 
10979 			XCopyGC(display, dgc, 0xffffffff, this.gc);
10980 
10981 			ensureDefaultFontLoaded();
10982 
10983 			font = defaultfont;
10984 			fontset = defaultfontset;
10985 
10986 			if(font) {
10987 				XSetFont(display, gc, font.fid);
10988 			}
10989 		}
10990 
10991 		static void ensureDefaultFontLoaded() {
10992 			if(!fontAttempted) {
10993 				auto display = XDisplayConnection.get;
10994 				auto font = XLoadQueryFont(display, xfontstr.ptr);
10995 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
10996 				if(font is null) {
10997 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
10998 					font = XLoadQueryFont(display, xfontstr.ptr);
10999 				}
11000 
11001 				char** lol;
11002 				int lol2;
11003 				char* lol3;
11004 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
11005 
11006 				fontAttempted = true;
11007 
11008 				defaultfont = font;
11009 				defaultfontset = fontset;
11010 			}
11011 		}
11012 
11013 		arsd.color.Rectangle _clipRectangle;
11014 		void setClipRectangle(int x, int y, int width, int height) {
11015 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11016 			if(width == 0 || height == 0) {
11017 				XSetClipMask(display, gc, None);
11018 
11019 				version(with_xft) {
11020 					if(xftFont is null || xftDraw is null)
11021 						return;
11022 					XftDrawSetClip(xftDraw, null);
11023 				}
11024 			} else {
11025 				XRectangle[1] rects;
11026 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
11027 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
11028 
11029 				version(with_xft) {
11030 					if(xftFont is null || xftDraw is null)
11031 						return;
11032 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
11033 				}
11034 			}
11035 		}
11036 
11037 		version(with_xft) {
11038 			XftFont* xftFont;
11039 			XftDraw* xftDraw;
11040 
11041 			XftColor xftColor;
11042 
11043 			void updateXftColor() {
11044 				if(xftFont is null)
11045 					return;
11046 
11047 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
11048 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
11049 
11050 				XftColorAllocValue(
11051 					display,
11052 					DefaultVisual(display, DefaultScreen(display)),
11053 					DefaultColormap(display, 0),
11054 					&colorIn,
11055 					&xftColor
11056 				);
11057 			}
11058 		}
11059 
11060 		void setFont(OperatingSystemFont font) {
11061 			version(with_xft) {
11062 				if(font && font.isXft && font.xftFont)
11063 					this.xftFont = font.xftFont;
11064 				else
11065 					this.xftFont = null;
11066 
11067 				if(this.xftFont) {
11068 					if(xftDraw is null) {
11069 						xftDraw = XftDrawCreate(
11070 							display,
11071 							d,
11072 							DefaultVisual(display, DefaultScreen(display)),
11073 							DefaultColormap(display, 0)
11074 						);
11075 
11076 						updateXftColor();
11077 					}
11078 
11079 					return;
11080 				}
11081 			}
11082 
11083 			if(font && font.font) {
11084 				this.font = font.font;
11085 				this.fontset = font.fontset;
11086 				XSetFont(display, gc, font.font.fid);
11087 			} else {
11088 				this.font = defaultfont;
11089 				this.fontset = defaultfontset;
11090 			}
11091 
11092 		}
11093 
11094 		private Picture xrenderPicturePainter;
11095 
11096 		void dispose() {
11097 			this.rasterOp = RasterOp.normal;
11098 
11099 			if(xrenderPicturePainter) {
11100 				XRenderFreePicture(display, xrenderPicturePainter);
11101 				xrenderPicturePainter = None;
11102 			}
11103 
11104 			// FIXME: this.window.width/height is probably wrong
11105 
11106 			// src x,y     then dest x, y
11107 			if(destiny != None) {
11108 				XSetClipMask(display, gc, None);
11109 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
11110 			}
11111 
11112 			XFreeGC(display, gc);
11113 
11114 			version(with_xft)
11115 			if(xftDraw) {
11116 				XftDrawDestroy(xftDraw);
11117 				xftDraw = null;
11118 			}
11119 
11120 			/+
11121 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
11122 			if(font && font !is defaultfont) {
11123 				XFreeFont(display, font);
11124 				font = null;
11125 			}
11126 			if(fontset && fontset !is defaultfontset) {
11127 				XFreeFontSet(display, fontset);
11128 				fontset = null;
11129 			}
11130 			+/
11131 			XFlush(display);
11132 
11133 			if(window.paintingFinishedDg !is null)
11134 				window.paintingFinishedDg()();
11135 		}
11136 
11137 		bool backgroundIsNotTransparent = true;
11138 		bool foregroundIsNotTransparent = true;
11139 
11140 		bool _penInitialized = false;
11141 		Pen _activePen;
11142 
11143 		Color _outlineColor;
11144 		Color _fillColor;
11145 
11146 		@property void pen(Pen p) {
11147 			if(_penInitialized && p == _activePen) {
11148 				return;
11149 			}
11150 			_penInitialized = true;
11151 			_activePen = p;
11152 			_outlineColor = p.color;
11153 
11154 			int style;
11155 
11156 			byte dashLength;
11157 
11158 			final switch(p.style) {
11159 				case Pen.Style.Solid:
11160 					style = 0 /*LineSolid*/;
11161 				break;
11162 				case Pen.Style.Dashed:
11163 					style = 1 /*LineOnOffDash*/;
11164 					dashLength = 4;
11165 				break;
11166 				case Pen.Style.Dotted:
11167 					style = 1 /*LineOnOffDash*/;
11168 					dashLength = 1;
11169 				break;
11170 			}
11171 
11172 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
11173 			if(dashLength)
11174 				XSetDashes(display, gc, 0, &dashLength, 1);
11175 
11176 			if(p.color.a == 0) {
11177 				foregroundIsNotTransparent = false;
11178 				return;
11179 			}
11180 
11181 			foregroundIsNotTransparent = true;
11182 
11183 			XSetForeground(display, gc, colorToX(p.color, display));
11184 
11185 			version(with_xft)
11186 				updateXftColor();
11187 		}
11188 
11189 		RasterOp _currentRasterOp;
11190 		bool _currentRasterOpInitialized = false;
11191 		@property void rasterOp(RasterOp op) {
11192 			if(_currentRasterOpInitialized && _currentRasterOp == op)
11193 				return;
11194 			_currentRasterOp = op;
11195 			_currentRasterOpInitialized = true;
11196 			int mode;
11197 			final switch(op) {
11198 				case RasterOp.normal:
11199 					mode = GXcopy;
11200 				break;
11201 				case RasterOp.xor:
11202 					mode = GXxor;
11203 				break;
11204 			}
11205 			XSetFunction(display, gc, mode);
11206 		}
11207 
11208 
11209 		bool _fillColorInitialized = false;
11210 
11211 		@property void fillColor(Color c) {
11212 			if(_fillColorInitialized && _fillColor == c)
11213 				return; // already good, no need to waste time calling it
11214 			_fillColor = c;
11215 			_fillColorInitialized = true;
11216 			if(c.a == 0) {
11217 				backgroundIsNotTransparent = false;
11218 				return;
11219 			}
11220 
11221 			backgroundIsNotTransparent = true;
11222 
11223 			XSetBackground(display, gc, colorToX(c, display));
11224 
11225 		}
11226 
11227 		void swapColors() {
11228 			auto tmp = _fillColor;
11229 			fillColor = _outlineColor;
11230 			auto newPen = _activePen;
11231 			newPen.color = tmp;
11232 			pen(newPen);
11233 		}
11234 
11235 		uint colorToX(Color c, Display* display) {
11236 			auto visual = DefaultVisual(display, DefaultScreen(display));
11237 			import core.bitop;
11238 			uint color = 0;
11239 			{
11240 			auto startBit = bsf(visual.red_mask);
11241 			auto lastBit = bsr(visual.red_mask);
11242 			auto r = cast(uint) c.r;
11243 			r >>= 7 - (lastBit - startBit);
11244 			r <<= startBit;
11245 			color |= r;
11246 			}
11247 			{
11248 			auto startBit = bsf(visual.green_mask);
11249 			auto lastBit = bsr(visual.green_mask);
11250 			auto g = cast(uint) c.g;
11251 			g >>= 7 - (lastBit - startBit);
11252 			g <<= startBit;
11253 			color |= g;
11254 			}
11255 			{
11256 			auto startBit = bsf(visual.blue_mask);
11257 			auto lastBit = bsr(visual.blue_mask);
11258 			auto b = cast(uint) c.b;
11259 			b >>= 7 - (lastBit - startBit);
11260 			b <<= startBit;
11261 			color |= b;
11262 			}
11263 
11264 
11265 
11266 			return color;
11267 		}
11268 
11269 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11270 			// source x, source y
11271 			if(ix >= i.width) return;
11272 			if(iy >= i.height) return;
11273 			if(ix + w > i.width) w = i.width - ix;
11274 			if(iy + h > i.height) h = i.height - iy;
11275 			if(i.usingXshm)
11276 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
11277 			else
11278 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
11279 		}
11280 
11281 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11282 			if(s.enableAlpha) {
11283 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
11284 				if(this.xrenderPicturePainter == None) {
11285 					XRenderPictureAttributes attrs;
11286 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
11287 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
11288 				}
11289 
11290 				XRenderComposite(
11291 					display,
11292 					3, // PicOpOver
11293 					s.xrenderPicture,
11294 					None,
11295 					this.xrenderPicturePainter,
11296 					ix,
11297 					iy,
11298 					0,
11299 					0,
11300 					x,
11301 					y,
11302 					w ? w : s.width,
11303 					h ? h : s.height
11304 				);
11305 			} else {
11306 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
11307 			}
11308 		}
11309 
11310 		int fontHeight() {
11311 			version(with_xft)
11312 				if(xftFont !is null)
11313 					return xftFont.height;
11314 			if(font)
11315 				return font.max_bounds.ascent + font.max_bounds.descent;
11316 			return 12; // pretty common default...
11317 		}
11318 
11319 		int textWidth(in char[] line) {
11320 			version(with_xft)
11321 			if(xftFont) {
11322 				if(line.length == 0)
11323 					return 0;
11324 				XGlyphInfo extents;
11325 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
11326 				return extents.width;
11327 			}
11328 
11329 			if(fontset) {
11330 				if(line.length == 0)
11331 					return 0;
11332 				XRectangle rect;
11333 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
11334 
11335 				return rect.width;
11336 			}
11337 
11338 			if(font)
11339 				// FIXME: unicode
11340 				return XTextWidth( font, line.ptr, cast(int) line.length);
11341 			else
11342 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
11343 		}
11344 
11345 		Size textSize(in char[] text) {
11346 			auto maxWidth = 0;
11347 			auto lineHeight = fontHeight;
11348 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
11349 			foreach(line; text.split('\n')) {
11350 				int textWidth = this.textWidth(line);
11351 				if(textWidth > maxWidth)
11352 					maxWidth = textWidth;
11353 				h += lineHeight + 4;
11354 			}
11355 			return Size(maxWidth, h);
11356 		}
11357 
11358 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
11359 			const(char)[] text;
11360 			version(with_xft)
11361 			if(xftFont) {
11362 				text = originalText;
11363 				goto loaded;
11364 			}
11365 
11366 			if(fontset)
11367 				text = originalText;
11368 			else {
11369 				text.reserve(originalText.length);
11370 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
11371 				// then strip the rest so there isn't garbage
11372 				foreach(dchar ch; originalText)
11373 					if(ch < 256)
11374 						text ~= cast(ubyte) ch;
11375 					else
11376 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
11377 			}
11378 			loaded:
11379 			if(text.length == 0)
11380 				return;
11381 
11382 			// FIXME: should we clip it to the bounding box?
11383 			int textHeight = fontHeight;
11384 
11385 			auto lines = text.split('\n');
11386 
11387 			const lineHeight = textHeight;
11388 			textHeight *= lines.length;
11389 
11390 			int cy = y;
11391 
11392 			if(alignment & TextAlignment.VerticalBottom) {
11393 				assert(y2);
11394 				auto h = y2 - y;
11395 				if(h > textHeight) {
11396 					cy += h - textHeight;
11397 					cy -= lineHeight / 2;
11398 				}
11399 			} else if(alignment & TextAlignment.VerticalCenter) {
11400 				assert(y2);
11401 				auto h = y2 - y;
11402 				if(textHeight < h) {
11403 					cy += (h - textHeight) / 2;
11404 					//cy -= lineHeight / 4;
11405 				}
11406 			}
11407 
11408 			foreach(line; text.split('\n')) {
11409 				int textWidth = this.textWidth(line);
11410 
11411 				int px = x, py = cy;
11412 
11413 				if(alignment & TextAlignment.Center) {
11414 					assert(x2);
11415 					auto w = x2 - x;
11416 					if(w > textWidth)
11417 						px += (w - textWidth) / 2;
11418 				} else if(alignment & TextAlignment.Right) {
11419 					assert(x2);
11420 					auto pos = x2 - textWidth;
11421 					if(pos > x)
11422 						px = pos;
11423 				}
11424 
11425 				version(with_xft)
11426 				if(xftFont) {
11427 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
11428 
11429 					goto carry_on;
11430 				}
11431 
11432 				if(fontset)
11433 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
11434 
11435 				else
11436 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
11437 				carry_on:
11438 				cy += lineHeight + 4;
11439 			}
11440 		}
11441 
11442 		void drawPixel(int x, int y) {
11443 			XDrawPoint(display, d, gc, x, y);
11444 		}
11445 
11446 		// The basic shapes, outlined
11447 
11448 		void drawLine(int x1, int y1, int x2, int y2) {
11449 			if(foregroundIsNotTransparent)
11450 				XDrawLine(display, d, gc, x1, y1, x2, y2);
11451 		}
11452 
11453 		void drawRectangle(int x, int y, int width, int height) {
11454 			if(backgroundIsNotTransparent) {
11455 				swapColors();
11456 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
11457 				swapColors();
11458 			}
11459 			// since X centers the line on the coordinates, we try to undo that with the width/2 thing here so it is aligned in the rectangle's bounds
11460 			if(foregroundIsNotTransparent)
11461 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
11462 		}
11463 
11464 		/// Arguments are the points of the bounding rectangle
11465 		void drawEllipse(int x1, int y1, int x2, int y2) {
11466 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
11467 		}
11468 
11469 		// NOTE: start and finish are in units of degrees * 64
11470 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11471 			if(backgroundIsNotTransparent) {
11472 				swapColors();
11473 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
11474 				swapColors();
11475 			}
11476 			if(foregroundIsNotTransparent)
11477 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
11478 		}
11479 
11480 		void drawPolygon(Point[] vertexes) {
11481 			XPoint[16] pointsBuffer;
11482 			XPoint[] points;
11483 			if(vertexes.length <= pointsBuffer.length)
11484 				points = pointsBuffer[0 .. vertexes.length];
11485 			else
11486 				points.length = vertexes.length;
11487 
11488 			foreach(i, p; vertexes) {
11489 				points[i].x = cast(short) p.x;
11490 				points[i].y = cast(short) p.y;
11491 			}
11492 
11493 			if(backgroundIsNotTransparent) {
11494 				swapColors();
11495 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
11496 				swapColors();
11497 			}
11498 			if(foregroundIsNotTransparent) {
11499 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
11500 			}
11501 		}
11502 	}
11503 
11504 	/* XRender { */
11505 
11506 	struct XRenderColor {
11507 		ushort red;
11508 		ushort green;
11509 		ushort blue;
11510 		ushort alpha;
11511 	}
11512 
11513 	alias Picture = XID;
11514 	alias PictFormat = XID;
11515 
11516 	struct XGlyphInfo {
11517 		ushort width;
11518 		ushort height;
11519 		short x;
11520 		short y;
11521 		short xOff;
11522 		short yOff;
11523 	}
11524 
11525 struct XRenderDirectFormat {
11526     short   red;
11527     short   redMask;
11528     short   green;
11529     short   greenMask;
11530     short   blue;
11531     short   blueMask;
11532     short   alpha;
11533     short   alphaMask;
11534 }
11535 
11536 struct XRenderPictFormat {
11537     PictFormat		id;
11538     int			type;
11539     int			depth;
11540     XRenderDirectFormat	direct;
11541     Colormap		colormap;
11542 }
11543 
11544 enum PictFormatID	=   (1 << 0);
11545 enum PictFormatType	=   (1 << 1);
11546 enum PictFormatDepth	=   (1 << 2);
11547 enum PictFormatRed	=   (1 << 3);
11548 enum PictFormatRedMask  =(1 << 4);
11549 enum PictFormatGreen	=   (1 << 5);
11550 enum PictFormatGreenMask=(1 << 6);
11551 enum PictFormatBlue	=   (1 << 7);
11552 enum PictFormatBlueMask =(1 << 8);
11553 enum PictFormatAlpha	=   (1 << 9);
11554 enum PictFormatAlphaMask=(1 << 10);
11555 enum PictFormatColormap =(1 << 11);
11556 
11557 struct XRenderPictureAttributes {
11558 	int 		repeat;
11559 	Picture		alpha_map;
11560 	int			alpha_x_origin;
11561 	int			alpha_y_origin;
11562 	int			clip_x_origin;
11563 	int			clip_y_origin;
11564 	Pixmap		clip_mask;
11565 	Bool		graphics_exposures;
11566 	int			subwindow_mode;
11567 	int			poly_edge;
11568 	int			poly_mode;
11569 	Atom		dither;
11570 	Bool		component_alpha;
11571 }
11572 
11573 alias int XFixed;
11574 
11575 struct XPointFixed {
11576     XFixed  x, y;
11577 }
11578 
11579 struct XCircle {
11580     XFixed x;
11581     XFixed y;
11582     XFixed radius;
11583 }
11584 
11585 struct XTransform {
11586     XFixed[3][3]  matrix;
11587 }
11588 
11589 struct XFilters {
11590     int	    nfilter;
11591     char    **filter;
11592     int	    nalias;
11593     short   *alias_;
11594 }
11595 
11596 struct XIndexValue {
11597     c_ulong    pixel;
11598     ushort   red, green, blue, alpha;
11599 }
11600 
11601 struct XAnimCursor {
11602     Cursor	    cursor;
11603     c_ulong   delay;
11604 }
11605 
11606 struct XLinearGradient {
11607     XPointFixed p1;
11608     XPointFixed p2;
11609 }
11610 
11611 struct XRadialGradient {
11612     XCircle inner;
11613     XCircle outer;
11614 }
11615 
11616 struct XConicalGradient {
11617     XPointFixed center;
11618     XFixed angle; /* in degrees */
11619 }
11620 
11621 enum PictStandardARGB32  = 0;
11622 enum PictStandardRGB24   = 1;
11623 enum PictStandardA8	 =  2;
11624 enum PictStandardA4	 =  3;
11625 enum PictStandardA1	 =  4;
11626 enum PictStandardNUM	 =  5;
11627 
11628 interface XRender {
11629 extern(C) @nogc:
11630 
11631 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
11632 
11633 	Status XRenderQueryVersion (Display *dpy,
11634 			int     *major_versionp,
11635 			int     *minor_versionp);
11636 
11637 	Status XRenderQueryFormats (Display *dpy);
11638 
11639 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
11640 
11641 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
11642 
11643 	XRenderPictFormat *
11644 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
11645 
11646 	XRenderPictFormat *
11647 		XRenderFindFormat (Display			*dpy,
11648 				c_ulong		mask,
11649 				const XRenderPictFormat	*templ,
11650 				int				count);
11651 	XRenderPictFormat *
11652 		XRenderFindStandardFormat (Display		*dpy,
11653 				int			format);
11654 
11655 	XIndexValue *
11656 		XRenderQueryPictIndexValues(Display			*dpy,
11657 				const XRenderPictFormat	*format,
11658 				int				*num);
11659 
11660 	Picture XRenderCreatePicture(
11661 		Display *dpy,
11662 		Drawable drawable,
11663 		const XRenderPictFormat *format,
11664 		c_ulong valuemask,
11665 		const XRenderPictureAttributes *attributes);
11666 
11667 	void XRenderChangePicture (Display				*dpy,
11668 				Picture				picture,
11669 				c_ulong			valuemask,
11670 				const XRenderPictureAttributes  *attributes);
11671 
11672 	void
11673 		XRenderSetPictureClipRectangles (Display	    *dpy,
11674 				Picture	    picture,
11675 				int		    xOrigin,
11676 				int		    yOrigin,
11677 				const XRectangle *rects,
11678 				int		    n);
11679 
11680 	void
11681 		XRenderSetPictureClipRegion (Display	    *dpy,
11682 				Picture	    picture,
11683 				Region	    r);
11684 
11685 	void
11686 		XRenderSetPictureTransform (Display	    *dpy,
11687 				Picture	    picture,
11688 				XTransform	    *transform);
11689 
11690 	void
11691 		XRenderFreePicture (Display                   *dpy,
11692 				Picture                   picture);
11693 
11694 	void
11695 		XRenderComposite (Display   *dpy,
11696 				int	    op,
11697 				Picture   src,
11698 				Picture   mask,
11699 				Picture   dst,
11700 				int	    src_x,
11701 				int	    src_y,
11702 				int	    mask_x,
11703 				int	    mask_y,
11704 				int	    dst_x,
11705 				int	    dst_y,
11706 				uint	width,
11707 				uint	height);
11708 
11709 
11710 	Picture XRenderCreateSolidFill (Display *dpy,
11711 			const XRenderColor *color);
11712 
11713 	Picture XRenderCreateLinearGradient (Display *dpy,
11714 			const XLinearGradient *gradient,
11715 			const XFixed *stops,
11716 			const XRenderColor *colors,
11717 			int nstops);
11718 
11719 	Picture XRenderCreateRadialGradient (Display *dpy,
11720 			const XRadialGradient *gradient,
11721 			const XFixed *stops,
11722 			const XRenderColor *colors,
11723 			int nstops);
11724 
11725 	Picture XRenderCreateConicalGradient (Display *dpy,
11726 			const XConicalGradient *gradient,
11727 			const XFixed *stops,
11728 			const XRenderColor *colors,
11729 			int nstops);
11730 
11731 
11732 
11733 	Cursor
11734 		XRenderCreateCursor (Display	    *dpy,
11735 				Picture	    source,
11736 				uint   x,
11737 				uint   y);
11738 
11739 	XFilters *
11740 		XRenderQueryFilters (Display *dpy, Drawable drawable);
11741 
11742 	void
11743 		XRenderSetPictureFilter (Display    *dpy,
11744 				Picture    picture,
11745 				const char *filter,
11746 				XFixed	    *params,
11747 				int	    nparams);
11748 
11749 	Cursor
11750 		XRenderCreateAnimCursor (Display	*dpy,
11751 				int		ncursor,
11752 				XAnimCursor	*cursors);
11753 }
11754 
11755 mixin DynamicLoad!(XRender, "Xrender", 1, false, true) XRenderLibrary;
11756 
11757 
11758 
11759 	/* XRender } */
11760 
11761 	/* Xft { */
11762 
11763 	// actually freetype
11764 	alias void FT_Face;
11765 
11766 	// actually fontconfig
11767 	private alias FcBool = int;
11768 	alias void FcCharSet;
11769 	alias void FcPattern;
11770 	alias void FcResult;
11771 	enum FcEndian { FcEndianBig, FcEndianLittle }
11772 	struct FcFontSet {
11773 		int nfont;
11774 		int sfont;
11775 		FcPattern** fonts;
11776 	}
11777 
11778 	// actually XRegion
11779 	struct BOX {
11780 		short x1, x2, y1, y2;
11781 	}
11782 	struct _XRegion {
11783 		c_long size;
11784 		c_long numRects;
11785 		BOX* rects;
11786 		BOX extents;
11787 	}
11788 
11789 	alias Region = _XRegion*;
11790 
11791 	// ok actually Xft
11792 
11793 	struct XftFontInfo;
11794 
11795 	struct XftFont {
11796 		int         ascent;
11797 		int         descent;
11798 		int         height;
11799 		int         max_advance_width;
11800 		FcCharSet*  charset;
11801 		FcPattern*  pattern;
11802 	}
11803 
11804 	struct XftDraw;
11805 
11806 	struct XftColor {
11807 		c_ulong pixel;
11808 		XRenderColor color;
11809 	}
11810 
11811 	struct XftCharSpec {
11812 		dchar           ucs4;
11813 		short           x;
11814 		short           y;
11815 	}
11816 
11817 	struct XftCharFontSpec {
11818 		XftFont         *font;
11819 		dchar           ucs4;
11820 		short           x;
11821 		short           y;
11822 	}
11823 
11824 	struct XftGlyphSpec {
11825 		uint            glyph;
11826 		short           x;
11827 		short           y;
11828 	}
11829 
11830 	struct XftGlyphFontSpec {
11831 		XftFont         *font;
11832 		uint            glyph;
11833 		short           x;
11834 		short           y;
11835 	}
11836 
11837 	interface Xft {
11838 	extern(C) @nogc pure:
11839 
11840 	Bool XftColorAllocName (Display  *dpy,
11841 				const Visual   *visual,
11842 				Colormap cmap,
11843 				const char     *name,
11844 				XftColor *result);
11845 
11846 	Bool XftColorAllocValue (Display         *dpy,
11847 				Visual          *visual,
11848 				Colormap        cmap,
11849 				const XRenderColor    *color,
11850 				XftColor        *result);
11851 
11852 	void XftColorFree (Display   *dpy,
11853 				Visual    *visual,
11854 				Colormap  cmap,
11855 				XftColor  *color);
11856 
11857 	Bool XftDefaultHasRender (Display *dpy);
11858 
11859 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
11860 
11861 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
11862 
11863 	XftDraw * XftDrawCreate (Display   *dpy,
11864 		       Drawable  drawable,
11865 		       Visual    *visual,
11866 		       Colormap  colormap);
11867 
11868 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
11869 			     Pixmap   bitmap);
11870 
11871 	XftDraw * XftDrawCreateAlpha (Display *dpy,
11872 			    Pixmap  pixmap,
11873 			    int     depth);
11874 
11875 	void XftDrawChange (XftDraw  *draw,
11876 		       Drawable drawable);
11877 
11878 	Display * XftDrawDisplay (XftDraw *draw);
11879 
11880 	Drawable XftDrawDrawable (XftDraw *draw);
11881 
11882 	Colormap XftDrawColormap (XftDraw *draw);
11883 
11884 	Visual * XftDrawVisual (XftDraw *draw);
11885 
11886 	void XftDrawDestroy (XftDraw *draw);
11887 
11888 	Picture XftDrawPicture (XftDraw *draw);
11889 
11890 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
11891 
11892 	void XftDrawGlyphs (XftDraw          *draw,
11893 				const XftColor *color,
11894 				XftFont          *pub,
11895 				int              x,
11896 				int              y,
11897 				const uint  *glyphs,
11898 				int              nglyphs);
11899 
11900 	void XftDrawString8 (XftDraw             *draw,
11901 				const XftColor    *color,
11902 				XftFont             *pub,
11903 				int                 x,
11904 				int                 y,
11905 				const char     *string,
11906 				int                 len);
11907 
11908 	void XftDrawString16 (XftDraw            *draw,
11909 				const XftColor   *color,
11910 				XftFont            *pub,
11911 				int                x,
11912 				int                y,
11913 				const wchar   *string,
11914 				int                len);
11915 
11916 	void XftDrawString32 (XftDraw            *draw,
11917 				const XftColor   *color,
11918 				XftFont            *pub,
11919 				int                x,
11920 				int                y,
11921 				const dchar   *string,
11922 				int                len);
11923 
11924 	void XftDrawStringUtf8 (XftDraw          *draw,
11925 				const XftColor *color,
11926 				XftFont          *pub,
11927 				int              x,
11928 				int              y,
11929 				const char  *string,
11930 				int              len);
11931 	void XftDrawStringUtf16 (XftDraw             *draw,
11932 				const XftColor    *color,
11933 				XftFont             *pub,
11934 				int                 x,
11935 				int                 y,
11936 				const char     *string,
11937 				FcEndian            endian,
11938 				int                 len);
11939 
11940 	void XftDrawCharSpec (XftDraw                *draw,
11941 				const XftColor       *color,
11942 				XftFont                *pub,
11943 				const XftCharSpec    *chars,
11944 				int                    len);
11945 
11946 	void XftDrawCharFontSpec (XftDraw                    *draw,
11947 				const XftColor           *color,
11948 				const XftCharFontSpec    *chars,
11949 				int                        len);
11950 
11951 	void XftDrawGlyphSpec (XftDraw               *draw,
11952 				const XftColor      *color,
11953 				XftFont               *pub,
11954 				const XftGlyphSpec  *glyphs,
11955 				int                   len);
11956 
11957 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
11958 				const XftColor          *color,
11959 				const XftGlyphFontSpec  *glyphs,
11960 				int                       len);
11961 
11962 	void XftDrawRect (XftDraw            *draw,
11963 				const XftColor   *color,
11964 				int                x,
11965 				int                y,
11966 				uint       width,
11967 				uint       height);
11968 
11969 	Bool XftDrawSetClip (XftDraw     *draw,
11970 				Region      r);
11971 
11972 
11973 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
11974 				int                   xOrigin,
11975 				int                   yOrigin,
11976 				const XRectangle    *rects,
11977 				int                   n);
11978 
11979 	void XftDrawSetSubwindowMode (XftDraw    *draw,
11980 				int        mode);
11981 
11982 	void XftGlyphExtents (Display            *dpy,
11983 				XftFont            *pub,
11984 				const uint    *glyphs,
11985 				int                nglyphs,
11986 				XGlyphInfo         *extents);
11987 
11988 	void XftTextExtents8 (Display            *dpy,
11989 				XftFont            *pub,
11990 				const char    *string,
11991 				int                len,
11992 				XGlyphInfo         *extents);
11993 
11994 	void XftTextExtents16 (Display           *dpy,
11995 				XftFont           *pub,
11996 				const wchar  *string,
11997 				int               len,
11998 				XGlyphInfo        *extents);
11999 
12000 	void XftTextExtents32 (Display           *dpy,
12001 				XftFont           *pub,
12002 				const dchar  *string,
12003 				int               len,
12004 				XGlyphInfo        *extents);
12005 
12006 	void XftTextExtentsUtf8 (Display         *dpy,
12007 				XftFont         *pub,
12008 				const char *string,
12009 				int             len,
12010 				XGlyphInfo      *extents);
12011 
12012 	void XftTextExtentsUtf16 (Display            *dpy,
12013 				XftFont            *pub,
12014 				const char    *string,
12015 				FcEndian           endian,
12016 				int                len,
12017 				XGlyphInfo         *extents);
12018 
12019 	FcPattern * XftFontMatch (Display           *dpy,
12020 				int               screen,
12021 				const FcPattern *pattern,
12022 				FcResult          *result);
12023 
12024 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
12025 
12026 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
12027 
12028 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
12029 
12030 	FT_Face XftLockFace (XftFont *pub);
12031 
12032 	void XftUnlockFace (XftFont *pub);
12033 
12034 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
12035 
12036 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
12037 
12038 	dchar XftFontInfoHash (const XftFontInfo *fi);
12039 
12040 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
12041 
12042 	XftFont * XftFontOpenInfo (Display        *dpy,
12043 				FcPattern      *pattern,
12044 				XftFontInfo    *fi);
12045 
12046 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
12047 
12048 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
12049 
12050 	void XftFontClose (Display *dpy, XftFont *pub);
12051 
12052 	FcBool XftInitFtLibrary();
12053 	void XftFontLoadGlyphs (Display          *dpy,
12054 				XftFont          *pub,
12055 				FcBool           need_bitmaps,
12056 				const uint  *glyphs,
12057 				int              nglyph);
12058 
12059 	void XftFontUnloadGlyphs (Display            *dpy,
12060 				XftFont            *pub,
12061 				const uint    *glyphs,
12062 				int                nglyph);
12063 
12064 	FcBool XftFontCheckGlyph (Display  *dpy,
12065 				XftFont  *pub,
12066 				FcBool   need_bitmaps,
12067 				uint  glyph,
12068 				uint  *missing,
12069 				int      *nmissing);
12070 
12071 	FcBool XftCharExists (Display      *dpy,
12072 				XftFont      *pub,
12073 				dchar    ucs4);
12074 
12075 	uint XftCharIndex (Display       *dpy,
12076 				XftFont       *pub,
12077 				dchar      ucs4);
12078 	FcBool XftInit (const char *config);
12079 
12080 	int XftGetVersion ();
12081 
12082 	FcFontSet * XftListFonts (Display   *dpy,
12083 				int       screen,
12084 				...);
12085 
12086 	FcPattern *XftNameParse (const char *name);
12087 
12088 	void XftGlyphRender (Display         *dpy,
12089 				int             op,
12090 				Picture         src,
12091 				XftFont         *pub,
12092 				Picture         dst,
12093 				int             srcx,
12094 				int             srcy,
12095 				int             x,
12096 				int             y,
12097 				const uint *glyphs,
12098 				int             nglyphs);
12099 
12100 	void XftGlyphSpecRender (Display                 *dpy,
12101 				int                     op,
12102 				Picture                 src,
12103 				XftFont                 *pub,
12104 				Picture                 dst,
12105 				int                     srcx,
12106 				int                     srcy,
12107 				const XftGlyphSpec    *glyphs,
12108 				int                     nglyphs);
12109 
12110 	void XftCharSpecRender (Display              *dpy,
12111 				int                  op,
12112 				Picture              src,
12113 				XftFont              *pub,
12114 				Picture              dst,
12115 				int                  srcx,
12116 				int                  srcy,
12117 				const XftCharSpec  *chars,
12118 				int                  len);
12119 	void XftGlyphFontSpecRender (Display                     *dpy,
12120 				int                         op,
12121 				Picture                     src,
12122 				Picture                     dst,
12123 				int                         srcx,
12124 				int                         srcy,
12125 				const XftGlyphFontSpec    *glyphs,
12126 				int                         nglyphs);
12127 
12128 	void XftCharFontSpecRender (Display                  *dpy,
12129 				int                      op,
12130 				Picture                  src,
12131 				Picture                  dst,
12132 				int                      srcx,
12133 				int                      srcy,
12134 				const XftCharFontSpec  *chars,
12135 				int                      len);
12136 
12137 	void XftTextRender8 (Display         *dpy,
12138 				int             op,
12139 				Picture         src,
12140 				XftFont         *pub,
12141 				Picture         dst,
12142 				int             srcx,
12143 				int             srcy,
12144 				int             x,
12145 				int             y,
12146 				const char *string,
12147 				int             len);
12148 	void XftTextRender16 (Display            *dpy,
12149 				int                op,
12150 				Picture            src,
12151 				XftFont            *pub,
12152 				Picture            dst,
12153 				int                srcx,
12154 				int                srcy,
12155 				int                x,
12156 				int                y,
12157 				const wchar   *string,
12158 				int                len);
12159 
12160 	void XftTextRender16BE (Display          *dpy,
12161 				int              op,
12162 				Picture          src,
12163 				XftFont          *pub,
12164 				Picture          dst,
12165 				int              srcx,
12166 				int              srcy,
12167 				int              x,
12168 				int              y,
12169 				const char  *string,
12170 				int              len);
12171 
12172 	void XftTextRender16LE (Display          *dpy,
12173 				int              op,
12174 				Picture          src,
12175 				XftFont          *pub,
12176 				Picture          dst,
12177 				int              srcx,
12178 				int              srcy,
12179 				int              x,
12180 				int              y,
12181 				const char  *string,
12182 				int              len);
12183 
12184 	void XftTextRender32 (Display            *dpy,
12185 				int                op,
12186 				Picture            src,
12187 				XftFont            *pub,
12188 				Picture            dst,
12189 				int                srcx,
12190 				int                srcy,
12191 				int                x,
12192 				int                y,
12193 				const dchar   *string,
12194 				int                len);
12195 
12196 	void XftTextRender32BE (Display          *dpy,
12197 				int              op,
12198 				Picture          src,
12199 				XftFont          *pub,
12200 				Picture          dst,
12201 				int              srcx,
12202 				int              srcy,
12203 				int              x,
12204 				int              y,
12205 				const char  *string,
12206 				int              len);
12207 
12208 	void XftTextRender32LE (Display          *dpy,
12209 				int              op,
12210 				Picture          src,
12211 				XftFont          *pub,
12212 				Picture          dst,
12213 				int              srcx,
12214 				int              srcy,
12215 				int              x,
12216 				int              y,
12217 				const char  *string,
12218 				int              len);
12219 
12220 	void XftTextRenderUtf8 (Display          *dpy,
12221 				int              op,
12222 				Picture          src,
12223 				XftFont          *pub,
12224 				Picture          dst,
12225 				int              srcx,
12226 				int              srcy,
12227 				int              x,
12228 				int              y,
12229 				const char  *string,
12230 				int              len);
12231 
12232 	void XftTextRenderUtf16 (Display         *dpy,
12233 				int             op,
12234 				Picture         src,
12235 				XftFont         *pub,
12236 				Picture         dst,
12237 				int             srcx,
12238 				int             srcy,
12239 				int             x,
12240 				int             y,
12241 				const char *string,
12242 				FcEndian        endian,
12243 				int             len);
12244 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
12245 
12246 	}
12247 
12248 	interface FontConfig {
12249 	extern(C) @nogc pure:
12250 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
12251 		void FcFontSetDestroy(FcFontSet*);
12252 		char* FcNameUnparse(const FcPattern *);
12253 	}
12254 
12255 	mixin DynamicLoad!(Xft, "Xft", 2) XftLibrary;
12256 	mixin DynamicLoad!(FontConfig, "fontconfig", 1) FontConfigLibrary;
12257 
12258 
12259 	/* Xft } */
12260 
12261 	class XDisconnectException : Exception {
12262 		bool userRequested;
12263 		this(bool userRequested = true) {
12264 			this.userRequested = userRequested;
12265 			super("X disconnected");
12266 		}
12267 	}
12268 
12269 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display*
12270 	class XDisplayConnection {
12271 		private __gshared Display* display;
12272 		private __gshared XIM xim;
12273 		private __gshared char* displayName;
12274 
12275 		private __gshared int connectionSequence_;
12276 		private __gshared bool isLocal_;
12277 
12278 		/// use this for lazy caching when reconnection
12279 		static int connectionSequenceNumber() { return connectionSequence_; }
12280 
12281 		/++
12282 			Guesses if the connection appears to be local.
12283 
12284 			History:
12285 				Added June 3, 2021
12286 		+/
12287 		static @property bool isLocal() nothrow @trusted @nogc {
12288 			return isLocal_;
12289 		}
12290 
12291 		/// Attempts recreation of state, may require application assistance
12292 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
12293 		/// then call this, and if successful, reenter the loop.
12294 		static void discardAndRecreate(string newDisplayString = null) {
12295 			if(insideXEventLoop)
12296 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
12297 
12298 			// 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
12299 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
12300 
12301 			foreach(handle; chnenhm) {
12302 				handle.discardConnectionState();
12303 			}
12304 
12305 			discardState();
12306 
12307 			if(newDisplayString !is null)
12308 				setDisplayName(newDisplayString);
12309 
12310 			auto display = get();
12311 
12312 			foreach(handle; chnenhm) {
12313 				handle.recreateAfterDisconnect();
12314 			}
12315 		}
12316 
12317 		private __gshared EventMask rootEventMask;
12318 
12319 		/++
12320 			Requests the specified input from the root window on the connection, in addition to any other request.
12321 
12322 			
12323 			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.
12324 
12325 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
12326 		+/
12327 		static void addRootInput(EventMask mask) {
12328 			auto old = rootEventMask;
12329 			rootEventMask |= mask;
12330 			get(); // to ensure display connected
12331 			if(display !is null && rootEventMask != old)
12332 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
12333 		}
12334 
12335 		static void discardState() {
12336 			freeImages();
12337 
12338 			foreach(atomPtr; interredAtoms)
12339 				*atomPtr = 0;
12340 			interredAtoms = null;
12341 			interredAtoms.assumeSafeAppend();
12342 
12343 			ScreenPainterImplementation.fontAttempted = false;
12344 			ScreenPainterImplementation.defaultfont = null;
12345 			ScreenPainterImplementation.defaultfontset = null;
12346 
12347 			Image.impl.xshmQueryCompleted = false;
12348 			Image.impl._xshmAvailable = false;
12349 
12350 			SimpleWindow.nativeMapping = null;
12351 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
12352 			// GlobalHotkeyManager
12353 
12354 			display = null;
12355 			xim = null;
12356 		}
12357 
12358 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
12359 		private static void createXIM () {
12360 			import core.stdc.locale : setlocale, LC_ALL;
12361 			import core.stdc.stdio : stderr, fprintf;
12362 			import core.stdc.stdlib : free;
12363 			import core.stdc.string : strdup;
12364 
12365 			static immutable string[3] mtry = [ null, "@im=local", "@im=" ];
12366 
12367 			auto olocale = strdup(setlocale(LC_ALL, null));
12368 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
12369 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
12370 
12371 			//fprintf(stderr, "opening IM...\n");
12372 			foreach (string s; mtry) {
12373 				if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
12374 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
12375 			}
12376 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
12377 		}
12378 
12379 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
12380 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
12381 		static struct ImgList {
12382 			size_t img; // class; hide it from GC
12383 			ImgList* next;
12384 		}
12385 
12386 		static __gshared ImgList* imglist = null;
12387 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
12388 
12389 		static void registerImage (Image img) {
12390 			if (!imglistLocked && img !is null) {
12391 				import core.stdc.stdlib : malloc;
12392 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
12393 				assert(it !is null); // do proper checks
12394 				it.img = cast(size_t)cast(void*)img;
12395 				it.next = imglist;
12396 				imglist = it;
12397 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
12398 			}
12399 		}
12400 
12401 		static void unregisterImage (Image img) {
12402 			if (!imglistLocked && img !is null) {
12403 				import core.stdc.stdlib : free;
12404 				ImgList* prev = null;
12405 				ImgList* cur = imglist;
12406 				while (cur !is null) {
12407 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
12408 					prev = cur;
12409 					cur = cur.next;
12410 				}
12411 				if (cur !is null) {
12412 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
12413 					free(cur);
12414 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
12415 				} else {
12416 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
12417 				}
12418 			}
12419 		}
12420 
12421 		static void freeImages () { // needed for discardAndRecreate
12422 			imglistLocked = true;
12423 			scope(exit) imglistLocked = false;
12424 			ImgList* cur = imglist;
12425 			ImgList* next = null;
12426 			while (cur !is null) {
12427 				import core.stdc.stdlib : free;
12428 				next = cur.next;
12429 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
12430 				(cast(Image)cast(void*)cur.img).dispose();
12431 				free(cur);
12432 				cur = next;
12433 			}
12434 			imglist = null;
12435 		}
12436 
12437 		/// can be used to override normal handling of display name
12438 		/// from environment and/or command line
12439 		static setDisplayName(string newDisplayName) {
12440 			displayName = cast(char*) (newDisplayName ~ '\0');
12441 		}
12442 
12443 		/// resets to the default display string
12444 		static resetDisplayName() {
12445 			displayName = null;
12446 		}
12447 
12448 		///
12449 		static Display* get() {
12450 			if(display is null) {
12451 				if(!librariesSuccessfullyLoaded)
12452 					throw new Exception("Unable to load X11 client libraries");
12453 				display = XOpenDisplay(displayName);
12454 
12455 				isLocal_ = false;
12456 
12457 				connectionSequence_++;
12458 				if(display is null)
12459 					throw new Exception("Unable to open X display");
12460 
12461 				auto str = display.display_name;
12462 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
12463 				// and otherwise it probably isn't
12464 				if(str is null || (str[0] != ':' && str[0] != '/'))
12465 					isLocal_ = false;
12466 				else
12467 					isLocal_ = true;
12468 
12469 				//XSetErrorHandler(&adrlogger);
12470 				//XSynchronize(display, true);
12471 
12472 
12473 				XSetIOErrorHandler(&x11ioerrCB);
12474 				Bool sup;
12475 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
12476 				createXIM();
12477 				version(with_eventloop) {
12478 					import arsd.eventloop;
12479 					addFileEventListeners(display.fd, &eventListener, null, null);
12480 				}
12481 			}
12482 
12483 			return display;
12484 		}
12485 
12486 		extern(C)
12487 		static int x11ioerrCB(Display* dpy) {
12488 			throw new XDisconnectException(false);
12489 		}
12490 
12491 		version(with_eventloop) {
12492 			import arsd.eventloop;
12493 			static void eventListener(OsFileHandle fd) {
12494 				//this.mtLock();
12495 				//scope(exit) this.mtUnlock();
12496 				while(XPending(display))
12497 					doXNextEvent(display);
12498 			}
12499 		}
12500 
12501 		// close connection on program exit -- we need this to properly free all images
12502 		static ~this () {
12503 			// the gui thread must clean up after itself or else Xlib might deadlock
12504 			// using this flag on any thread destruction is the easiest way i know of
12505 			// (shared static this is run by the LAST thread to exit, which may not be
12506 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
12507 			if(thisIsGuiThread)
12508 				close();
12509 		}
12510 
12511 		///
12512 		static void close() {
12513 			if(display is null)
12514 				return;
12515 
12516 			version(with_eventloop) {
12517 				import arsd.eventloop;
12518 				removeFileEventListeners(display.fd);
12519 			}
12520 
12521 			// now remove all registered images to prevent shared memory leaks
12522 			freeImages();
12523 
12524 			// tbh I don't know why it is doing this but like if this happens to run
12525 			// from the other thread there's frequent hanging inside here.
12526 			if(thisIsGuiThread)
12527 				XCloseDisplay(display);
12528 			display = null;
12529 		}
12530 	}
12531 
12532 	mixin template NativeImageImplementation() {
12533 		XImage* handle;
12534 		ubyte* rawData;
12535 
12536 		XShmSegmentInfo shminfo;
12537 
12538 		__gshared bool xshmQueryCompleted;
12539 		__gshared bool _xshmAvailable;
12540 		public static @property bool xshmAvailable() {
12541 			if(!xshmQueryCompleted) {
12542 				int i1, i2, i3;
12543 				xshmQueryCompleted = true;
12544 
12545 				if(!XDisplayConnection.isLocal)
12546 					_xshmAvailable = false;
12547 				else
12548 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
12549 			}
12550 			return _xshmAvailable;
12551 		}
12552 
12553 		bool usingXshm;
12554 	final:
12555 
12556 		private __gshared bool xshmfailed;
12557 
12558 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12559 			auto display = XDisplayConnection.get();
12560 			assert(display !is null);
12561 			auto screen = DefaultScreen(display);
12562 
12563 			// it will only use shared memory for somewhat largish images,
12564 			// since otherwise we risk wasting shared memory handles on a lot of little ones
12565 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
12566 
12567 
12568 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
12569 				// the actual use still fails. For example, if the program is in a container and permission denied
12570 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
12571 				//
12572 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
12573 
12574 
12575 				// synchronize so preexisting buffers are clear
12576 				XSync(display, false);
12577 				xshmfailed = false;
12578 
12579 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
12580 
12581 
12582 				usingXshm = true;
12583 				handle = XShmCreateImage(
12584 					display,
12585 					DefaultVisual(display, screen),
12586 					enableAlpha ? 32: 24,
12587 					ImageFormat.ZPixmap,
12588 					null,
12589 					&shminfo,
12590 					width, height);
12591 				if(handle is null)
12592 					goto abortXshm1;
12593 
12594 				if(handle.bytes_per_line != 4 * width)
12595 					goto abortXshm2;
12596 
12597 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
12598 				if(shminfo.shmid < 0)
12599 					goto abortXshm3;
12600 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
12601 				if(rawData == cast(ubyte*) -1)
12602 					goto abortXshm4;
12603 				shminfo.readOnly = 0;
12604 				XShmAttach(display, &shminfo);
12605 
12606 				// and now to the final error check to ensure it actually worked.
12607 				XSync(display, false);
12608 				if(xshmfailed)
12609 					goto abortXshm5;
12610 
12611 				XSetErrorHandler(oldErrorHandler);
12612 
12613 				XDisplayConnection.registerImage(this);
12614 				// if I don't flush here there's a chance the dtor will run before the
12615 				// ctor and lead to a bad value X error. While this hurts the efficiency
12616 				// it is local anyway so prolly better to keep it simple
12617 				XFlush(display);
12618 
12619 				return;
12620 
12621 				abortXshm5:
12622 					shmdt(shminfo.shmaddr);
12623 					rawData = null;
12624 
12625 				abortXshm4:
12626 					shmctl(shminfo.shmid, IPC_RMID, null);
12627 
12628 				abortXshm3:
12629 					// nothing needed, the shmget failed so there's nothing to free
12630 
12631 				abortXshm2:
12632 					XDestroyImage(handle);
12633 					handle = null;
12634 
12635 				abortXshm1:
12636 					XSetErrorHandler(oldErrorHandler);
12637 					usingXshm = false;
12638 					handle = null;
12639 
12640 					shminfo = typeof(shminfo).init;
12641 
12642 					_xshmAvailable = false; // don't try again in the future
12643 
12644 					//import std.stdio; writeln("fallingback");
12645 
12646 					goto fallback;
12647 
12648 			} else {
12649 				fallback:
12650 
12651 				if (forcexshm) throw new Exception("can't create XShm Image");
12652 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
12653 				import core.stdc.stdlib : malloc;
12654 				rawData = cast(ubyte*) malloc(width * height * 4);
12655 
12656 				handle = XCreateImage(
12657 					display,
12658 					DefaultVisual(display, screen),
12659 					enableAlpha ? 32 : 24, // bpp
12660 					ImageFormat.ZPixmap,
12661 					0, // offset
12662 					rawData,
12663 					width, height,
12664 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
12665 			}
12666 		}
12667 
12668 		void dispose() {
12669 			// note: this calls free(rawData) for us
12670 			if(handle) {
12671 				if (usingXshm) {
12672 					XDisplayConnection.unregisterImage(this);
12673 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
12674 				}
12675 				XDestroyImage(handle);
12676 				if(usingXshm) {
12677 					shmdt(shminfo.shmaddr);
12678 					shmctl(shminfo.shmid, IPC_RMID, null);
12679 				}
12680 				handle = null;
12681 			}
12682 		}
12683 
12684 		Color getPixel(int x, int y) {
12685 			auto offset = (y * width + x) * 4;
12686 			Color c;
12687 			c.a = enableAlpha ? rawData[offset + 3] : 255;
12688 			c.b = rawData[offset + 0];
12689 			c.g = rawData[offset + 1];
12690 			c.r = rawData[offset + 2];
12691 			if(enableAlpha)
12692 				c.unPremultiply;
12693 			return c;
12694 		}
12695 
12696 		void setPixel(int x, int y, Color c) {
12697 			if(enableAlpha)
12698 				c.premultiply();
12699 			auto offset = (y * width + x) * 4;
12700 			rawData[offset + 0] = c.b;
12701 			rawData[offset + 1] = c.g;
12702 			rawData[offset + 2] = c.r;
12703 			if(enableAlpha)
12704 				rawData[offset + 3] = c.a;
12705 		}
12706 
12707 		void convertToRgbaBytes(ubyte[] where) {
12708 			assert(where.length == this.width * this.height * 4);
12709 
12710 			// if rawData had a length....
12711 			//assert(rawData.length == where.length);
12712 			for(int idx = 0; idx < where.length; idx += 4) {
12713 				where[idx + 0] = rawData[idx + 2]; // r
12714 				where[idx + 1] = rawData[idx + 1]; // g
12715 				where[idx + 2] = rawData[idx + 0]; // b
12716 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
12717 
12718 				if(enableAlpha)
12719 					unPremultiplyRgba(where[idx .. idx + 4]);
12720 			}
12721 		}
12722 
12723 		void setFromRgbaBytes(in ubyte[] where) {
12724 			assert(where.length == this.width * this.height * 4);
12725 
12726 			// if rawData had a length....
12727 			//assert(rawData.length == where.length);
12728 			for(int idx = 0; idx < where.length; idx += 4) {
12729 				rawData[idx + 2] = where[idx + 0]; // r
12730 				rawData[idx + 1] = where[idx + 1]; // g
12731 				rawData[idx + 0] = where[idx + 2]; // b
12732 				if(enableAlpha) {
12733 					rawData[idx + 3] = where[idx + 3]; // a
12734 					premultiplyBgra(rawData[idx .. idx + 4]);
12735 				}
12736 			}
12737 		}
12738 
12739 	}
12740 
12741 	mixin template NativeSimpleWindowImplementation() {
12742 		GC gc;
12743 		Window window;
12744 		Display* display;
12745 
12746 		Pixmap buffer;
12747 		int bufferw, bufferh; // size of the buffer; can be bigger than window
12748 		XIC xic; // input context
12749 		int curHidden = 0; // counter
12750 		Cursor blankCurPtr = 0;
12751 		int cursorSequenceNumber = 0;
12752 		int warpEventCount = 0; // number of mouse movement events to eat
12753 
12754 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
12755 		X11GetSelectionHandler[Atom] getSelectionHandlers;
12756 
12757 		version(without_opengl) {} else
12758 		GLXContext glc;
12759 
12760 		private void fixFixedSize(bool forced=false) (int width, int height) {
12761 			if (forced || this.resizability == Resizability.fixedSize) {
12762 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
12763 				XSizeHints sh;
12764 				static if (!forced) {
12765 					c_long spr;
12766 					XGetWMNormalHints(display, window, &sh, &spr);
12767 					sh.flags |= PMaxSize | PMinSize;
12768 				} else {
12769 					sh.flags = PMaxSize | PMinSize;
12770 				}
12771 				sh.min_width = width;
12772 				sh.min_height = height;
12773 				sh.max_width = width;
12774 				sh.max_height = height;
12775 				XSetWMNormalHints(display, window, &sh);
12776 				//XFlush(display);
12777 			}
12778 		}
12779 
12780 		ScreenPainter getPainter() {
12781 			return ScreenPainter(this, window);
12782 		}
12783 
12784 		void move(int x, int y) {
12785 			XMoveWindow(display, window, x, y);
12786 		}
12787 
12788 		void resize(int w, int h) {
12789 			if (w < 1) w = 1;
12790 			if (h < 1) h = 1;
12791 			XResizeWindow(display, window, w, h);
12792 
12793 			// calling this now to avoid waiting for the server to
12794 			// acknowledge the resize; draws without returning to the
12795 			// event loop will thus actually work. the server's event
12796 			// btw might overrule this and resize it again
12797 			recordX11Resize(display, this, w, h);
12798 
12799 			// FIXME: do we need to set this as the opengl context to do the glViewport change?
12800 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
12801 		}
12802 
12803 		void moveResize (int x, int y, int w, int h) {
12804 			if (w < 1) w = 1;
12805 			if (h < 1) h = 1;
12806 			XMoveResizeWindow(display, window, x, y, w, h);
12807 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
12808 		}
12809 
12810 		void hideCursor () {
12811 			if (curHidden++ == 0) {
12812 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
12813 					static const(char)[1] cmbmp = 0;
12814 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
12815 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
12816 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
12817 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
12818 					XFreePixmap(display, pm);
12819 				}
12820 				XDefineCursor(display, window, blankCurPtr);
12821 			}
12822 		}
12823 
12824 		void showCursor () {
12825 			if (--curHidden == 0) XUndefineCursor(display, window);
12826 		}
12827 
12828 		void warpMouse (int x, int y) {
12829 			// here i will send dummy "ignore next mouse motion" event,
12830 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
12831 			// and we don't need to report it to the user (as warping is
12832 			// used when the user needs movement deltas).
12833 			//XClientMessageEvent xclient;
12834 			XEvent e;
12835 			e.xclient.type = EventType.ClientMessage;
12836 			e.xclient.window = window;
12837 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
12838 			e.xclient.format = 32;
12839 			e.xclient.data.l[0] = 0;
12840 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
12841 			//{ 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]); }
12842 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
12843 			// now warp pointer...
12844 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
12845 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
12846 			// ...and flush
12847 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
12848 			XFlush(display);
12849 		}
12850 
12851 		void sendDummyEvent () {
12852 			// here i will send dummy event to ping event queue
12853 			XEvent e;
12854 			e.xclient.type = EventType.ClientMessage;
12855 			e.xclient.window = window;
12856 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
12857 			e.xclient.format = 32;
12858 			e.xclient.data.l[0] = 0;
12859 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
12860 			XFlush(display);
12861 		}
12862 
12863 		void setTitle(string title) {
12864 			if (title.ptr is null) title = "";
12865 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
12866 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
12867 			XTextProperty windowName;
12868 			windowName.value = title.ptr;
12869 			windowName.encoding = XA_UTF8; //XA_STRING;
12870 			windowName.format = 8;
12871 			windowName.nitems = cast(uint)title.length;
12872 			XSetWMName(display, window, &windowName);
12873 			char[1024] namebuf = 0;
12874 			auto maxlen = namebuf.length-1;
12875 			if (maxlen > title.length) maxlen = title.length;
12876 			namebuf[0..maxlen] = title[0..maxlen];
12877 			XStoreName(display, window, namebuf.ptr);
12878 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
12879 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
12880 		}
12881 
12882 		string[] getTitles() {
12883 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
12884 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
12885 			XTextProperty textProp;
12886 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
12887 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
12888 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
12889 				} else
12890 					return [];
12891 			} else
12892 				return null;
12893 		}
12894 
12895 		string getTitle() {
12896 			auto titles = getTitles();
12897 			return titles.length ? titles[0] : null;
12898 		}
12899 
12900 		void setMinSize (int minwidth, int minheight) {
12901 			import core.stdc.config : c_long;
12902 			if (minwidth < 1) minwidth = 1;
12903 			if (minheight < 1) minheight = 1;
12904 			XSizeHints sh;
12905 			c_long spr;
12906 			XGetWMNormalHints(display, window, &sh, &spr);
12907 			sh.min_width = minwidth;
12908 			sh.min_height = minheight;
12909 			sh.flags |= PMinSize;
12910 			XSetWMNormalHints(display, window, &sh);
12911 			flushGui();
12912 		}
12913 
12914 		void setMaxSize (int maxwidth, int maxheight) {
12915 			import core.stdc.config : c_long;
12916 			if (maxwidth < 1) maxwidth = 1;
12917 			if (maxheight < 1) maxheight = 1;
12918 			XSizeHints sh;
12919 			c_long spr;
12920 			XGetWMNormalHints(display, window, &sh, &spr);
12921 			sh.max_width = maxwidth;
12922 			sh.max_height = maxheight;
12923 			sh.flags |= PMaxSize;
12924 			XSetWMNormalHints(display, window, &sh);
12925 			flushGui();
12926 		}
12927 
12928 		void setResizeGranularity (int granx, int grany) {
12929 			import core.stdc.config : c_long;
12930 			if (granx < 1) granx = 1;
12931 			if (grany < 1) grany = 1;
12932 			XSizeHints sh;
12933 			c_long spr;
12934 			XGetWMNormalHints(display, window, &sh, &spr);
12935 			sh.width_inc = granx;
12936 			sh.height_inc = grany;
12937 			sh.flags |= PResizeInc;
12938 			XSetWMNormalHints(display, window, &sh);
12939 			flushGui();
12940 		}
12941 
12942 		void setOpacity (uint opacity) {
12943 			arch_ulong o = opacity;
12944 			if (opacity == uint.max)
12945 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
12946 			else
12947 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
12948 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
12949 		}
12950 
12951 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
12952 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12953 			display = XDisplayConnection.get();
12954 			auto screen = DefaultScreen(display);
12955 
12956 			version(without_opengl) {}
12957 			else {
12958 				if(opengl == OpenGlOptions.yes) {
12959 					GLXFBConfig fbconf = null;
12960 					XVisualInfo* vi = null;
12961 					bool useLegacy = false;
12962 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12963 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
12964 						int[23] visualAttribs = [
12965 							GLX_X_RENDERABLE , 1/*True*/,
12966 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
12967 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
12968 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
12969 							GLX_RED_SIZE     , 8,
12970 							GLX_GREEN_SIZE   , 8,
12971 							GLX_BLUE_SIZE    , 8,
12972 							GLX_ALPHA_SIZE   , 8,
12973 							GLX_DEPTH_SIZE   , 24,
12974 							GLX_STENCIL_SIZE , 8,
12975 							GLX_DOUBLEBUFFER , 1/*True*/,
12976 							0/*None*/,
12977 						];
12978 						int fbcount;
12979 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
12980 						if (fbcount == 0) {
12981 							useLegacy = true; // try to do at least something
12982 						} else {
12983 							// pick the FB config/visual with the most samples per pixel
12984 							int bestidx = -1, bestns = -1;
12985 							foreach (int fbi; 0..fbcount) {
12986 								int sb, samples;
12987 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
12988 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
12989 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
12990 							}
12991 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
12992 							fbconf = fbc[bestidx];
12993 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
12994 							XFree(fbc);
12995 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
12996 						}
12997 					}
12998 					if (vi is null || useLegacy) {
12999 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
13000 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
13001 						useLegacy = true;
13002 					}
13003 					if (vi is null) throw new Exception("no open gl visual found");
13004 
13005 					XSetWindowAttributes swa;
13006 					auto root = RootWindow(display, screen);
13007 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
13008 
13009 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
13010 						0, 0, width, height,
13011 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa);
13012 
13013 					// now try to use `glXCreateContextAttribsARB()` if it's here
13014 					if (!useLegacy) {
13015 						// request fairly advanced context, even with stencil buffer!
13016 						int[9] contextAttribs = [
13017 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
13018 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
13019 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
13020 							// for modern context, set "forward compatibility" flag too
13021 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
13022 							0/*None*/,
13023 						];
13024 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
13025 						if (glc is null && sdpyOpenGLContextAllowFallback) {
13026 							sdpyOpenGLContextVersion = 0;
13027 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
13028 						}
13029 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
13030 					} else {
13031 						// fallback to old GLX call
13032 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
13033 							sdpyOpenGLContextVersion = 0;
13034 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
13035 						}
13036 					}
13037 					// sync to ensure any errors generated are processed
13038 					XSync(display, 0/*False*/);
13039 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
13040 					if(glc is null)
13041 						throw new Exception("glc");
13042 				}
13043 			}
13044 
13045 			if(opengl == OpenGlOptions.no) {
13046 
13047 				bool overrideRedirect = false;
13048 				if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)
13049 					overrideRedirect = true;
13050 
13051 				XSetWindowAttributes swa;
13052 				swa.background_pixel = WhitePixel(display, screen);
13053 				swa.border_pixel = BlackPixel(display, screen);
13054 				swa.override_redirect = overrideRedirect;
13055 				auto root = RootWindow(display, screen);
13056 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
13057 
13058 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
13059 					0, 0, width, height,
13060 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa);
13061 
13062 
13063 
13064 				/*
13065 				window = XCreateSimpleWindow(
13066 					display,
13067 					parent is null ? RootWindow(display, screen) : parent.impl.window,
13068 					0, 0, // x, y
13069 					width, height,
13070 					1, // border width
13071 					BlackPixel(display, screen), // border
13072 					WhitePixel(display, screen)); // background
13073 				*/
13074 
13075 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
13076 				bufferw = width;
13077 				bufferh = height;
13078 
13079 				gc = DefaultGC(display, screen);
13080 
13081 				// clear out the buffer to get us started...
13082 				XSetForeground(display, gc, WhitePixel(display, screen));
13083 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
13084 				XSetForeground(display, gc, BlackPixel(display, screen));
13085 			}
13086 
13087 			// input context
13088 			//TODO: create this only for top-level windows, and reuse that?
13089 			if (XDisplayConnection.xim !is null) {
13090 				xic = XCreateIC(XDisplayConnection.xim,
13091 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
13092 						/*XNClientWindow*/"clientWindow".ptr, window,
13093 						/*XNFocusWindow*/"focusWindow".ptr, window,
13094 						null);
13095 				if (xic is null) {
13096 					import core.stdc.stdio : stderr, fprintf;
13097 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
13098 				}
13099 			}
13100 
13101 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
13102 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
13103 			// window class
13104 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
13105 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
13106 				XClassHint klass;
13107 				XWMHints wh;
13108 				XSizeHints size;
13109 				klass.res_name = sdpyWindowClassStr;
13110 				klass.res_class = sdpyWindowClassStr;
13111 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
13112 			}
13113 
13114 			setTitle(title);
13115 			SimpleWindow.nativeMapping[window] = this;
13116 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
13117 
13118 			// This gives our window a close button
13119 			if (windowType != WindowTypes.eventOnly) {
13120 				// FIXME: actually implement the WM_TAKE_FOCUS correctly
13121 				//Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
13122 				Atom[1] atoms = [GetAtom!"WM_DELETE_WINDOW"(display)];
13123 				XSetWMProtocols(display, window, atoms.ptr, cast(int) atoms.length);
13124 			}
13125 
13126 			// FIXME: windowType and customizationFlags
13127 			Atom[8] wsatoms; // here, due to goto
13128 			int wmsacount = 0; // here, due to goto
13129 
13130 			try
13131 			final switch(windowType) {
13132 				case WindowTypes.normal:
13133 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
13134 				break;
13135 				case WindowTypes.undecorated:
13136 					motifHideDecorations();
13137 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
13138 				break;
13139 				case WindowTypes.eventOnly:
13140 					_hidden = true;
13141 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
13142 					goto hiddenWindow;
13143 				//break;
13144 				case WindowTypes.nestedChild:
13145 					// handled in XCreateWindow calls
13146 				break;
13147 
13148 				case WindowTypes.dropdownMenu:
13149 					motifHideDecorations();
13150 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
13151 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
13152 				break;
13153 				case WindowTypes.popupMenu:
13154 					motifHideDecorations();
13155 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
13156 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
13157 				break;
13158 				case WindowTypes.notification:
13159 					motifHideDecorations();
13160 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
13161 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
13162 				break;
13163 				/+
13164 				case WindowTypes.menu:
13165 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
13166 					motifHideDecorations();
13167 				break;
13168 				case WindowTypes.desktop:
13169 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
13170 				break;
13171 				case WindowTypes.dock:
13172 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
13173 				break;
13174 				case WindowTypes.toolbar:
13175 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
13176 				break;
13177 				case WindowTypes.menu:
13178 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
13179 				break;
13180 				case WindowTypes.utility:
13181 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
13182 				break;
13183 				case WindowTypes.splash:
13184 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
13185 				break;
13186 				case WindowTypes.dialog:
13187 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
13188 				break;
13189 				case WindowTypes.tooltip:
13190 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
13191 				break;
13192 				case WindowTypes.notification:
13193 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
13194 				break;
13195 				case WindowTypes.combo:
13196 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
13197 				break;
13198 				case WindowTypes.dnd:
13199 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
13200 				break;
13201 				+/
13202 			}
13203 			catch(Exception e) {
13204 				// XInternAtom failed, prolly a WM
13205 				// that doesn't support these things
13206 			}
13207 
13208 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
13209 			// the two following flags may be ignored by WM
13210 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
13211 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
13212 
13213 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
13214 
13215 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
13216 
13217 			// What would be ideal here is if they only were
13218 			// selected if there was actually an event handler
13219 			// for them...
13220 
13221 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
13222 
13223 			hiddenWindow:
13224 
13225 			// set the pid property for lookup later by window managers
13226 			// a standard convenience
13227 			import core.sys.posix.unistd;
13228 			arch_ulong pid = getpid();
13229 
13230 			XChangeProperty(
13231 				display,
13232 				impl.window,
13233 				GetAtom!("_NET_WM_PID", true)(display),
13234 				XA_CARDINAL,
13235 				32 /* bits */,
13236 				0 /*PropModeReplace*/,
13237 				&pid,
13238 				1);
13239 
13240 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
13241 				if(parent is null) assert(0);
13242 				XChangeProperty(
13243 					display,
13244 					impl.window,
13245 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
13246 					XA_WINDOW,
13247 					32 /* bits */,
13248 					0 /*PropModeReplace*/,
13249 					&parent.impl.window,
13250 					1);
13251 
13252 			}
13253 
13254 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
13255 				XMapWindow(display, window);
13256 			} else {
13257 				_hidden = true;
13258 			}
13259 		}
13260 
13261 		void selectDefaultInput(bool forceIncludeMouseMotion) {
13262 			auto mask = EventMask.ExposureMask |
13263 				EventMask.KeyPressMask |
13264 				EventMask.KeyReleaseMask |
13265 				EventMask.PropertyChangeMask |
13266 				EventMask.FocusChangeMask |
13267 				EventMask.StructureNotifyMask |
13268 				EventMask.VisibilityChangeMask
13269 				| EventMask.ButtonPressMask
13270 				| EventMask.ButtonReleaseMask
13271 			;
13272 
13273 			// xshm is our shortcut for local connections
13274 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
13275 				mask |= EventMask.PointerMotionMask;
13276 			else
13277 				mask |= EventMask.ButtonMotionMask;
13278 
13279 			XSelectInput(display, window, mask);
13280 		}
13281 
13282 
13283 		void setNetWMWindowType(Atom type) {
13284 			Atom[2] atoms;
13285 
13286 			atoms[0] = type;
13287 			// generic fallback
13288 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
13289 
13290 			XChangeProperty(
13291 				display,
13292 				impl.window,
13293 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
13294 				XA_ATOM,
13295 				32 /* bits */,
13296 				0 /*PropModeReplace*/,
13297 				atoms.ptr,
13298 				cast(int) atoms.length);
13299 		}
13300 
13301 		void motifHideDecorations() {
13302 			MwmHints hints;
13303 			hints.flags = MWM_HINTS_DECORATIONS;
13304 
13305 			XChangeProperty(
13306 				display,
13307 				impl.window,
13308 				GetAtom!"_MOTIF_WM_HINTS"(display),
13309 				GetAtom!"_MOTIF_WM_HINTS"(display),
13310 				32 /* bits */,
13311 				0 /*PropModeReplace*/,
13312 				&hints,
13313 				hints.sizeof / 4);
13314 		}
13315 
13316 		/*k8: unused
13317 		void createOpenGlContext() {
13318 
13319 		}
13320 		*/
13321 
13322 		void closeWindow() {
13323 			// I can't close this or a child window closing will
13324 			// break events for everyone. So I'm just leaking it right
13325 			// now and that is probably perfectly fine...
13326 			version(none)
13327 			if (customEventFDRead != -1) {
13328 				import core.sys.posix.unistd : close;
13329 				auto same = customEventFDRead == customEventFDWrite;
13330 
13331 				close(customEventFDRead);
13332 				if(!same)
13333 					close(customEventFDWrite);
13334 				customEventFDRead = -1;
13335 				customEventFDWrite = -1;
13336 			}
13337 			if(buffer)
13338 				XFreePixmap(display, buffer);
13339 			bufferw = bufferh = 0;
13340 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
13341 			XDestroyWindow(display, window);
13342 			XFlush(display);
13343 		}
13344 
13345 		void dispose() {
13346 		}
13347 
13348 		bool destroyed = false;
13349 	}
13350 
13351 	bool insideXEventLoop;
13352 }
13353 
13354 version(X11) {
13355 
13356 	int mouseDoubleClickTimeout = 350; /// double click timeout. X only, you probably shouldn't change this.
13357 
13358 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
13359 		if(width != win.width || height != win.height) {
13360 			win._width = width;
13361 			win._height = height;
13362 
13363 			if(win.openglMode == OpenGlOptions.no) {
13364 				// FIXME: could this be more efficient?
13365 
13366 				if (win.bufferw < width || win.bufferh < height) {
13367 					//{ 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); }
13368 					// grow the internal buffer to match the window...
13369 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
13370 					{
13371 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
13372 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
13373 						scope(exit) XFreeGC(win.display, xgc);
13374 						XSetClipMask(win.display, xgc, None);
13375 						XSetForeground(win.display, xgc, 0);
13376 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
13377 					}
13378 					XCopyArea(display,
13379 						cast(Drawable) win.buffer,
13380 						cast(Drawable) newPixmap,
13381 						win.gc, 0, 0,
13382 						win.bufferw < width ? win.bufferw : win.width,
13383 						win.bufferh < height ? win.bufferh : win.height,
13384 						0, 0);
13385 
13386 					XFreePixmap(display, win.buffer);
13387 					win.buffer = newPixmap;
13388 					win.bufferw = width;
13389 					win.bufferh = height;
13390 				}
13391 
13392 				// clear unused parts of the buffer
13393 				if (win.bufferw > width || win.bufferh > height) {
13394 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
13395 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
13396 					scope(exit) XFreeGC(win.display, xgc);
13397 					XSetClipMask(win.display, xgc, None);
13398 					XSetForeground(win.display, xgc, 0);
13399 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
13400 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
13401 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
13402 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
13403 				}
13404 
13405 			}
13406 
13407 			version(without_opengl) {} else
13408 			if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) {
13409 				glViewport(0, 0, width, height);
13410 			}
13411 
13412 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
13413 
13414 			if(win.windowResized !is null) {
13415 				XUnlockDisplay(display);
13416 				scope(exit) XLockDisplay(display);
13417 				win.windowResized(width, height);
13418 			}
13419 		}
13420 	}
13421 
13422 
13423 	/// Platform-specific, you might use it when doing a custom event loop
13424 	bool doXNextEvent(Display* display) {
13425 		bool done;
13426 		XEvent e;
13427 		XNextEvent(display, &e);
13428 		version(sddddd) {
13429 			import std.stdio, std.conv : to;
13430 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
13431 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
13432 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
13433 			}
13434 		}
13435 
13436 		// filter out compose events
13437 		if (XFilterEvent(&e, None)) {
13438 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
13439 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
13440 			return false;
13441 		}
13442 		// process keyboard mapping changes
13443 		if (e.type == EventType.KeymapNotify) {
13444 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
13445 			XRefreshKeyboardMapping(&e.xmapping);
13446 			return false;
13447 		}
13448 
13449 		version(with_eventloop)
13450 			import arsd.eventloop;
13451 
13452 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
13453 			// see windows impl's comments
13454 			XUnlockDisplay(display);
13455 			scope(exit) XLockDisplay(display);
13456 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
13457 			if(ret == 0)
13458 				return done;
13459 		}
13460 
13461 
13462 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
13463 			if(win.getNativeEventHandler !is null) {
13464 				XUnlockDisplay(display);
13465 				scope(exit) XLockDisplay(display);
13466 				auto ret = win.getNativeEventHandler()(e);
13467 				if(ret == 0)
13468 					return done;
13469 			}
13470 		}
13471 
13472 		switch(e.type) {
13473 		  case EventType.SelectionClear:
13474 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
13475 				// FIXME so it is supposed to finish any in progress transfers... but idk...
13476 				//import std.stdio; writeln("SelectionClear");
13477 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
13478 			}
13479 		  break;
13480 		  case EventType.SelectionRequest:
13481 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
13482 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
13483 				// import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
13484 				XUnlockDisplay(display);
13485 				scope(exit) XLockDisplay(display);
13486 				(*ssh).handleRequest(e);
13487 			}
13488 		  break;
13489 		  case EventType.PropertyNotify:
13490 			// import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
13491 
13492 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
13493 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
13494 					ssh.sendMoreIncr(&e.xproperty);
13495 			}
13496 
13497 
13498 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
13499 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
13500 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
13501 					Atom target;
13502 					int format;
13503 					arch_ulong bytesafter, length;
13504 					void* value;
13505 
13506 					ubyte[] s;
13507 					Atom targetToKeep;
13508 
13509 					XGetWindowProperty(
13510 						e.xproperty.display,
13511 						e.xproperty.window,
13512 						e.xproperty.atom,
13513 						0,
13514 						100000 /* length */,
13515 						true, /* erase it to signal we got it and want more */
13516 						0 /*AnyPropertyType*/,
13517 						&target, &format, &length, &bytesafter, &value);
13518 
13519 					if(!targetToKeep)
13520 						targetToKeep = target;
13521 
13522 					auto id = (cast(ubyte*) value)[0 .. length];
13523 
13524 					handler.handleIncrData(targetToKeep, id);
13525 
13526 					XFree(value);
13527 				}
13528 			}
13529 		  break;
13530 		  case EventType.SelectionNotify:
13531 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
13532 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
13533 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
13534 					XUnlockDisplay(display);
13535 					scope(exit) XLockDisplay(display);
13536 					handler.handleData(None, null);
13537 				} else {
13538 					Atom target;
13539 					int format;
13540 					arch_ulong bytesafter, length;
13541 					void* value;
13542 					XGetWindowProperty(
13543 						e.xselection.display,
13544 						e.xselection.requestor,
13545 						e.xselection.property,
13546 						0,
13547 						100000 /* length */,
13548 						//false, /* don't erase it */
13549 						true, /* do erase it lol */
13550 						0 /*AnyPropertyType*/,
13551 						&target, &format, &length, &bytesafter, &value);
13552 
13553 					// FIXME: I don't have to copy it now since it is in char[] instead of string
13554 
13555 					{
13556 						XUnlockDisplay(display);
13557 						scope(exit) XLockDisplay(display);
13558 
13559 						if(target == XA_ATOM) {
13560 							// initial request, see what they are able to work with and request the best one
13561 							// we can handle, if available
13562 
13563 							Atom[] answer = (cast(Atom*) value)[0 .. length];
13564 							Atom best = handler.findBestFormat(answer);
13565 
13566 							/+
13567 							writeln("got ", answer);
13568 							foreach(a; answer)
13569 								printf("%s\n", XGetAtomName(display, a));
13570 							writeln("best ", best);
13571 							+/
13572 
13573 							if(best != None) {
13574 								// actually request the best format
13575 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
13576 							}
13577 						} else if(target == GetAtom!"INCR"(display)) {
13578 							// incremental
13579 
13580 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
13581 
13582 							// signal the sending program that we see
13583 							// the incr and are ready to receive more.
13584 							XDeleteProperty(
13585 								e.xselection.display,
13586 								e.xselection.requestor,
13587 								e.xselection.property);
13588 						} else {
13589 							// unsupported type... maybe, forward
13590 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
13591 						}
13592 					}
13593 					XFree(value);
13594 					/*
13595 					XDeleteProperty(
13596 						e.xselection.display,
13597 						e.xselection.requestor,
13598 						e.xselection.property);
13599 					*/
13600 				}
13601 			}
13602 		  break;
13603 		  case EventType.ConfigureNotify:
13604 			auto event = e.xconfigure;
13605 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
13606 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
13607 
13608 				recordX11Resize(display, *win, event.width, event.height);
13609 			}
13610 		  break;
13611 		  case EventType.Expose:
13612 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
13613 				// if it is closing from a popup menu, it can get
13614 				// an Expose event right by the end and trigger a
13615 				// BadDrawable error ... we'll just check
13616 				// closed to handle that.
13617 				if((*win).closed) break;
13618 				if((*win).openglMode == OpenGlOptions.no) {
13619 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
13620 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
13621 					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);
13622 				} else {
13623 					// need to redraw the scene somehow
13624 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
13625 						XUnlockDisplay(display);
13626 						scope(exit) XLockDisplay(display);
13627 						version(without_opengl) {} else
13628 						win.redrawOpenGlSceneSoon();
13629 					}
13630 				}
13631 			}
13632 		  break;
13633 		  case EventType.FocusIn:
13634 		  case EventType.FocusOut:
13635 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
13636 				if (win.xic !is null) {
13637 					//{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); }
13638 					if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic);
13639 				}
13640 
13641 				win._focused = e.type == EventType.FocusIn;
13642 
13643 				if(win.demandingAttention)
13644 					demandAttention(*win, false);
13645 
13646 				if(win.onFocusChange) {
13647 					XUnlockDisplay(display);
13648 					scope(exit) XLockDisplay(display);
13649 					win.onFocusChange(e.type == EventType.FocusIn);
13650 				}
13651 			}
13652 		  break;
13653 		  case EventType.VisibilityNotify:
13654 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
13655 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
13656 						if (win.visibilityChanged !is null) {
13657 								XUnlockDisplay(display);
13658 								scope(exit) XLockDisplay(display);
13659 								win.visibilityChanged(false);
13660 							}
13661 					} else {
13662 						if (win.visibilityChanged !is null) {
13663 							XUnlockDisplay(display);
13664 							scope(exit) XLockDisplay(display);
13665 							win.visibilityChanged(true);
13666 						}
13667 					}
13668 				}
13669 				break;
13670 		  case EventType.ClientMessage:
13671 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
13672 					// "ignore next mouse motion" event, increment ignore counter for teh window
13673 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
13674 						++(*win).warpEventCount;
13675 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
13676 					} else {
13677 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
13678 					}
13679 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
13680 					// user clicked the close button on the window manager
13681 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
13682 						XUnlockDisplay(display);
13683 						scope(exit) XLockDisplay(display);
13684 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
13685 					}
13686 
13687 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
13688 					import std.stdio; writeln("HAPPENED");
13689 					// user clicked the close button on the window manager
13690 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
13691 						XUnlockDisplay(display);
13692 						scope(exit) XLockDisplay(display);
13693 
13694 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
13695 						XSetInputFocus(display, e.xclient.window, RevertToParent, e.xclient.data.l[1]);
13696 					}
13697 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
13698 					foreach(nai; NotificationAreaIcon.activeIcons)
13699 						nai.newManager();
13700 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
13701 
13702 					bool xDragWindow = true;
13703 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
13704 						//XDefineCursor(display, xDragWindow.impl.window,
13705 							//import std.stdio; writeln("XdndStatus ", e.xclient.data.l);
13706 					}
13707 					if(auto dh = win.dropHandler) {
13708 
13709 						static Atom[3] xFormatsBuffer;
13710 						static Atom[] xFormats;
13711 
13712 						void resetXFormats() {
13713 							xFormatsBuffer[] = 0;
13714 							xFormats = xFormatsBuffer[];
13715 						}
13716 
13717 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
13718 							// on Windows it is supposed to return the effect you actually do FIXME
13719 
13720 							auto sourceWindow =  e.xclient.data.l[0];
13721 
13722 							xFormatsBuffer[0] = e.xclient.data.l[2];
13723 							xFormatsBuffer[1] = e.xclient.data.l[3];
13724 							xFormatsBuffer[2] = e.xclient.data.l[4];
13725 
13726 							if(e.xclient.data.l[1] & 1) {
13727 								// can just grab it all but like we don't necessarily need them...
13728 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
13729 							} else {
13730 								int len;
13731 								foreach(fmt; xFormatsBuffer)
13732 									if(fmt) len++;
13733 								xFormats = xFormatsBuffer[0 .. len];
13734 							}
13735 
13736 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
13737 
13738 							dh.dragEnter(&pkg);
13739 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
13740 
13741 							auto pack = e.xclient.data.l[2];
13742 
13743 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
13744 
13745 
13746 							XClientMessageEvent xclient;
13747 
13748 							xclient.type = EventType.ClientMessage;
13749 							xclient.window = e.xclient.data.l[0];
13750 							xclient.message_type = GetAtom!"XdndStatus"(display);
13751 							xclient.format = 32;
13752 							xclient.data.l[0] = win.impl.window;
13753 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
13754 							auto r = result.consistentWithin;
13755 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
13756 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
13757 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
13758 
13759 							XSendEvent(
13760 								display,
13761 								e.xclient.data.l[0],
13762 								false,
13763 								EventMask.NoEventMask,
13764 								cast(XEvent*) &xclient
13765 							);
13766 
13767 
13768 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
13769 							//import std.stdio; writeln("XdndLeave");
13770 							// drop cancelled.
13771 							// data.l[0] is the source window
13772 							dh.dragLeave();
13773 
13774 							resetXFormats();
13775 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
13776 							// drop happening, should fetch data, then send finished
13777 							//import std.stdio; writeln("XdndDrop");
13778 
13779 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
13780 
13781 							dh.drop(&pkg);
13782 
13783 							resetXFormats();
13784 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
13785 							// import std.stdio; writeln("XdndFinished");
13786 
13787 							dh.finish();
13788 						}
13789 
13790 					}
13791 				}
13792 		  break;
13793 		  case EventType.MapNotify:
13794 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
13795 					(*win)._visible = true;
13796 					if (!(*win)._visibleForTheFirstTimeCalled) {
13797 						(*win)._visibleForTheFirstTimeCalled = true;
13798 						if ((*win).visibleForTheFirstTime !is null) {
13799 							XUnlockDisplay(display);
13800 							scope(exit) XLockDisplay(display);
13801 							version(without_opengl) {} else {
13802 								if((*win).openglMode == OpenGlOptions.yes) {
13803 									(*win).setAsCurrentOpenGlContextNT();
13804 									glViewport(0, 0, (*win).width, (*win).height);
13805 								}
13806 							}
13807 							(*win).visibleForTheFirstTime();
13808 						}
13809 					}
13810 					if ((*win).visibilityChanged !is null) {
13811 						XUnlockDisplay(display);
13812 						scope(exit) XLockDisplay(display);
13813 						(*win).visibilityChanged(true);
13814 					}
13815 				}
13816 		  break;
13817 		  case EventType.UnmapNotify:
13818 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
13819 					win._visible = false;
13820 					if (win.visibilityChanged !is null) {
13821 						XUnlockDisplay(display);
13822 						scope(exit) XLockDisplay(display);
13823 						win.visibilityChanged(false);
13824 					}
13825 			}
13826 		  break;
13827 		  case EventType.DestroyNotify:
13828 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
13829 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
13830 				win._closed = true; // just in case
13831 				win.destroyed = true;
13832 				if (win.xic !is null) {
13833 					XDestroyIC(win.xic);
13834 					win.xic = null; // just in calse
13835 				}
13836 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
13837 				bool anyImportant = false;
13838 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
13839 					if(w.beingOpenKeepsAppOpen) {
13840 						anyImportant = true;
13841 						break;
13842 					}
13843 				if(!anyImportant)
13844 					done = true;
13845 			}
13846 			auto window = e.xdestroywindow.window;
13847 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
13848 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
13849 
13850 			version(with_eventloop) {
13851 				if(done) exit();
13852 			}
13853 		  break;
13854 
13855 		  case EventType.MotionNotify:
13856 			MouseEvent mouse;
13857 			auto event = e.xmotion;
13858 
13859 			mouse.type = MouseEventType.motion;
13860 			mouse.x = event.x;
13861 			mouse.y = event.y;
13862 			mouse.modifierState = event.state;
13863 
13864 			mouse.timestamp = event.time;
13865 
13866 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
13867 				mouse.window = *win;
13868 				if (win.warpEventCount > 0) {
13869 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
13870 					--(*win).warpEventCount;
13871 					(*win).mdx(mouse); // so deltas will be correctly updated
13872 				} else {
13873 					win.warpEventCount = 0; // just in case
13874 					(*win).mdx(mouse);
13875 					if((*win).handleMouseEvent) {
13876 						XUnlockDisplay(display);
13877 						scope(exit) XLockDisplay(display);
13878 						(*win).handleMouseEvent(mouse);
13879 					}
13880 				}
13881 			}
13882 
13883 		  	version(with_eventloop)
13884 				send(mouse);
13885 		  break;
13886 		  case EventType.ButtonPress:
13887 		  case EventType.ButtonRelease:
13888 			MouseEvent mouse;
13889 			auto event = e.xbutton;
13890 
13891 			mouse.timestamp = event.time;
13892 
13893 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
13894 			mouse.x = event.x;
13895 			mouse.y = event.y;
13896 
13897 			static Time lastMouseDownTime = 0;
13898 
13899 			mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
13900 			if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time;
13901 
13902 			switch(event.button) {
13903 				case 1: mouse.button = MouseButton.left; break; // left
13904 				case 2: mouse.button = MouseButton.middle; break; // middle
13905 				case 3: mouse.button = MouseButton.right; break; // right
13906 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
13907 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
13908 				case 6: break; // idk
13909 				case 7: break; // idk
13910 				case 8: mouse.button = MouseButton.backButton; break;
13911 				case 9: mouse.button = MouseButton.forwardButton; break;
13912 				default:
13913 			}
13914 
13915 			// FIXME: double check this
13916 			mouse.modifierState = event.state;
13917 
13918 			//mouse.modifierState = event.detail;
13919 
13920 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
13921 				mouse.window = *win;
13922 				(*win).mdx(mouse);
13923 				if((*win).handleMouseEvent) {
13924 					XUnlockDisplay(display);
13925 					scope(exit) XLockDisplay(display);
13926 					(*win).handleMouseEvent(mouse);
13927 				}
13928 			}
13929 			version(with_eventloop)
13930 				send(mouse);
13931 		  break;
13932 
13933 		  case EventType.KeyPress:
13934 		  case EventType.KeyRelease:
13935 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
13936 			KeyEvent ke;
13937 			ke.pressed = e.type == EventType.KeyPress;
13938 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
13939 
13940 			auto sym = XKeycodeToKeysym(
13941 				XDisplayConnection.get(),
13942 				e.xkey.keycode,
13943 				0);
13944 
13945 			ke.key = cast(Key) sym;//e.xkey.keycode;
13946 
13947 			ke.modifierState = e.xkey.state;
13948 
13949 			// import std.stdio; writefln("%x", sym);
13950 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
13951 			int charbuflen = 0; // return value of XwcLookupString
13952 			if (ke.pressed) {
13953 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
13954 				if (win !is null && win.xic !is null) {
13955 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
13956 					Status status;
13957 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
13958 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
13959 				} else {
13960 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
13961 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
13962 					char[16] buffer;
13963 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
13964 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
13965 				}
13966 			}
13967 
13968 			// if there's no char, subst one
13969 			if (charbuflen == 0) {
13970 				switch (sym) {
13971 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
13972 					case 0xff8d: // keypad enter
13973 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
13974 					default : // ignore
13975 				}
13976 			}
13977 
13978 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
13979 				ke.window = *win;
13980 
13981 
13982 				if(win.inputProxy)
13983 					win = &win.inputProxy;
13984 
13985 				// char events are separate since they are on Windows too
13986 				// also, xcompose can generate long char sequences
13987 				// don't send char events if Meta and/or Hyper is pressed
13988 				// TODO: ctrl+char should only send control chars; not yet
13989 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
13990 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
13991 				}
13992 
13993 				dchar[32] charsComingBuffer;
13994 				int charsComingPosition;
13995 				dchar[] charsComing = charsComingBuffer[];
13996 
13997 				if (ke.pressed && charbuflen > 0) {
13998 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
13999 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
14000 						if(charsComingPosition >= charsComing.length)
14001 							charsComing.length = charsComingPosition + 8;
14002 
14003 						charsComing[charsComingPosition++] = ch;
14004 					}
14005 
14006 					charsComing = charsComing[0 .. charsComingPosition];
14007 				} else {
14008 					charsComing = null;
14009 				}
14010 
14011 				ke.charsPossible = charsComing;
14012 
14013 				if (win.handleKeyEvent) {
14014 					XUnlockDisplay(display);
14015 					scope(exit) XLockDisplay(display);
14016 					win.handleKeyEvent(ke);
14017 				}
14018 
14019 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
14020 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
14021 					XUnlockDisplay(display);
14022 					scope(exit) XLockDisplay(display);
14023 					foreach(ch; charsComing)
14024 						win.handleCharEvent(ch);
14025 				}
14026 			}
14027 
14028 			version(with_eventloop)
14029 				send(ke);
14030 		  break;
14031 		  default:
14032 		}
14033 
14034 		return done;
14035 	}
14036 }
14037 
14038 /* *************************************** */
14039 /*      Done with simpledisplay stuff      */
14040 /* *************************************** */
14041 
14042 // Necessary C library bindings follow
14043 version(Windows) {} else
14044 version(X11) {
14045 
14046 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
14047 
14048 // X11 bindings needed here
14049 /*
14050 	A little of this is from the bindings project on
14051 	D Source and some of it is copy/paste from the C
14052 	header.
14053 
14054 	The DSource listing consistently used D's long
14055 	where C used long. That's wrong - C long is 32 bit, so
14056 	it should be int in D. I changed that here.
14057 
14058 	Note:
14059 	This isn't complete, just took what I needed for myself.
14060 */
14061 
14062 import core.stdc.stddef : wchar_t;
14063 
14064 interface XLib {
14065 extern(C) nothrow @nogc {
14066 	char* XResourceManagerString(Display*);
14067 	void XrmInitialize();
14068 	XrmDatabase XrmGetStringDatabase(char* data);
14069 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
14070 
14071 	Cursor XCreateFontCursor(Display*, uint shape);
14072 	int XDefineCursor(Display* display, Window w, Cursor cursor);
14073 	int XUndefineCursor(Display* display, Window w);
14074 
14075 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
14076 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
14077 	int XFreeCursor(Display* display, Cursor cursor);
14078 
14079 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
14080 
14081 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
14082 
14083 	char *XKeysymToString(KeySym keysym);
14084 	KeySym XKeycodeToKeysym(
14085 		Display*		/* display */,
14086 		KeyCode		/* keycode */,
14087 		int			/* index */
14088 	);
14089 
14090 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
14091 
14092 	int XFree(void*);
14093 	int XDeleteProperty(Display *display, Window w, Atom property);
14094 
14095 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
14096 
14097 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
14098 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
14099 		*actual_type_return, int *actual_format_return, arch_ulong
14100 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
14101 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
14102 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
14103 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
14104 
14105 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
14106 
14107 	Window XGetSelectionOwner(Display *display, Atom selection);
14108 
14109 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
14110 
14111 	char** XListFonts(Display*, const char*, int, int*);
14112 	void XFreeFontNames(char**);
14113 
14114 	Display* XOpenDisplay(const char*);
14115 	int XCloseDisplay(Display*);
14116 
14117 	int XSynchronize(Display*, bool);
14118 
14119 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
14120 
14121 	Bool XSupportsLocale();
14122 	char* XSetLocaleModifiers(const(char)* modifier_list);
14123 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
14124 	Status XCloseOM(XOM om);
14125 
14126 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
14127 	Status XCloseIM(XIM im);
14128 
14129 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
14130 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
14131 	Display* XDisplayOfIM(XIM im);
14132 	char* XLocaleOfIM(XIM im);
14133 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
14134 	void XDestroyIC(XIC ic);
14135 	void XSetICFocus(XIC ic);
14136 	void XUnsetICFocus(XIC ic);
14137 	//wchar_t* XwcResetIC(XIC ic);
14138 	char* XmbResetIC(XIC ic);
14139 	char* Xutf8ResetIC(XIC ic);
14140 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
14141 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
14142 	XIM XIMOfIC(XIC ic);
14143 
14144 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
14145 
14146 
14147 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
14148 	int XFreeFont(Display *display, XFontStruct *font_struct);
14149 	int XSetFont(Display* display, GC gc, Font font);
14150 	int XTextWidth(XFontStruct*, in char*, int);
14151 
14152 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
14153 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
14154 
14155 	Window XCreateSimpleWindow(
14156 		Display*	/* display */,
14157 		Window		/* parent */,
14158 		int			/* x */,
14159 		int			/* y */,
14160 		uint		/* width */,
14161 		uint		/* height */,
14162 		uint		/* border_width */,
14163 		uint		/* border */,
14164 		uint		/* background */
14165 	);
14166 	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);
14167 
14168 	int XReparentWindow(Display*, Window, Window, int, int);
14169 	int XClearWindow(Display*, Window);
14170 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
14171 	int XMoveWindow(Display*, Window, int, int);
14172 	int XResizeWindow(Display *display, Window w, uint width, uint height);
14173 
14174 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
14175 
14176 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
14177 
14178 	XImage *XCreateImage(
14179 		Display*		/* display */,
14180 		Visual*		/* visual */,
14181 		uint	/* depth */,
14182 		int			/* format */,
14183 		int			/* offset */,
14184 		ubyte*		/* data */,
14185 		uint	/* width */,
14186 		uint	/* height */,
14187 		int			/* bitmap_pad */,
14188 		int			/* bytes_per_line */
14189 	);
14190 
14191 	Status XInitImage (XImage* image);
14192 
14193 	Atom XInternAtom(
14194 		Display*		/* display */,
14195 		const char*	/* atom_name */,
14196 		Bool		/* only_if_exists */
14197 	);
14198 
14199 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
14200 	char* XGetAtomName(Display*, Atom);
14201 	Status XGetAtomNames(Display*, Atom*, int count, char**);
14202 
14203 	int XPutImage(
14204 		Display*	/* display */,
14205 		Drawable	/* d */,
14206 		GC			/* gc */,
14207 		XImage*	/* image */,
14208 		int			/* src_x */,
14209 		int			/* src_y */,
14210 		int			/* dest_x */,
14211 		int			/* dest_y */,
14212 		uint		/* width */,
14213 		uint		/* height */
14214 	);
14215 
14216 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
14217 
14218 
14219 	int XDestroyWindow(
14220 		Display*	/* display */,
14221 		Window		/* w */
14222 	);
14223 
14224 	int XDestroyImage(XImage*);
14225 
14226 	int XSelectInput(
14227 		Display*	/* display */,
14228 		Window		/* w */,
14229 		EventMask	/* event_mask */
14230 	);
14231 
14232 	int XMapWindow(
14233 		Display*	/* display */,
14234 		Window		/* w */
14235 	);
14236 
14237 	Status XIconifyWindow(Display*, Window, int);
14238 	int XMapRaised(Display*, Window);
14239 	int XMapSubwindows(Display*, Window);
14240 
14241 	int XNextEvent(
14242 		Display*	/* display */,
14243 		XEvent*		/* event_return */
14244 	);
14245 
14246 	int XMaskEvent(Display*, arch_long, XEvent*);
14247 
14248 	Bool XFilterEvent(XEvent *event, Window window);
14249 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
14250 
14251 	Status XSetWMProtocols(
14252 		Display*	/* display */,
14253 		Window		/* w */,
14254 		Atom*		/* protocols */,
14255 		int			/* count */
14256 	);
14257 
14258 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
14259 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
14260 
14261 
14262 	Status XInitThreads();
14263 	void XLockDisplay (Display* display);
14264 	void XUnlockDisplay (Display* display);
14265 
14266 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
14267 
14268 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
14269 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
14270 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
14271 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
14272 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
14273 
14274 
14275 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
14276 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
14277 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
14278 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
14279 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
14280 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
14281 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
14282 	int XDrawPoint(Display*, Drawable, GC, int, int);
14283 	int XSetForeground(Display*, GC, uint);
14284 	int XSetBackground(Display*, GC, uint);
14285 
14286 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
14287 	void XFreeFontSet(Display*, XFontSet);
14288 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
14289 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
14290 
14291 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
14292 	 	
14293 
14294 //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);
14295 
14296 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
14297 	int XSetFunction(Display*, GC, int);
14298 
14299 	GC XCreateGC(Display*, Drawable, uint, void*);
14300 	int XCopyGC(Display*, GC, uint, GC);
14301 	int XFreeGC(Display*, GC);
14302 
14303 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
14304 	bool XCheckMaskEvent(Display*, int, XEvent*);
14305 
14306 	int XPending(Display*);
14307 	int XEventsQueued(Display* display, int mode);
14308 
14309 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
14310 	int XFreePixmap(Display*, Pixmap);
14311 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
14312 	int XFlush(Display*);
14313 	int XBell(Display*, int);
14314 	int XSync(Display*, bool);
14315 
14316 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
14317 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
14318 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
14319 
14320 	KeySym XStringToKeysym(const char *string);
14321 
14322 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
14323 
14324 	Window XDefaultRootWindow(Display*);
14325 
14326 	int XGrabButton(Display *display, uint button, uint modifiers, Window grab_window, Bool owner_events, uint event_mask, int pointer_mode, int keyboard_mode, Window confine_to, Cursor cursor);
14327 
14328 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 
14329 
14330 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
14331 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
14332 
14333 	Status XAllocColor(Display*, Colormap, XColor*);
14334 
14335 	int XWithdrawWindow(Display*, Window, int);
14336 	int XUnmapWindow(Display*, Window);
14337 	int XLowerWindow(Display*, Window);
14338 	int XRaiseWindow(Display*, Window);
14339 
14340 	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);
14341 	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);
14342 
14343 	int XGetInputFocus(Display*, Window*, int*);
14344 	int XSetInputFocus(Display*, Window, int, Time);
14345 
14346 	XErrorHandler XSetErrorHandler(XErrorHandler);
14347 
14348 	int XGetErrorText(Display*, int, char*, int);
14349 
14350 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
14351 
14352 
14353 	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);
14354 	int XUngrabPointer(Display *display, Time time);
14355 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
14356 
14357 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
14358 
14359 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
14360 	int XSetClipMask(Display*, GC, Pixmap);
14361 	int XSetClipOrigin(Display*, GC, int, int);
14362 
14363 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
14364 
14365 	void XSetWMName(Display*, Window, XTextProperty*);
14366 	Status XGetWMName(Display*, Window, XTextProperty*);
14367 	int XStoreName(Display* display, Window w, const(char)* window_name);
14368 
14369 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
14370 
14371 }
14372 }
14373 
14374 interface Xext {
14375 extern(C) nothrow @nogc {
14376 	Status XShmAttach(Display*, XShmSegmentInfo*);
14377 	Status XShmDetach(Display*, XShmSegmentInfo*);
14378 	Status XShmPutImage(
14379 		Display*            /* dpy */,
14380 		Drawable            /* d */,
14381 		GC                  /* gc */,
14382 		XImage*             /* image */,
14383 		int                 /* src_x */,
14384 		int                 /* src_y */,
14385 		int                 /* dst_x */,
14386 		int                 /* dst_y */,
14387 		uint        /* src_width */,
14388 		uint        /* src_height */,
14389 		Bool                /* send_event */
14390 	);
14391 
14392 	Status XShmQueryExtension(Display*);
14393 
14394 	XImage *XShmCreateImage(
14395 		Display*            /* dpy */,
14396 		Visual*             /* visual */,
14397 		uint        /* depth */,
14398 		int                 /* format */,
14399 		char*               /* data */,
14400 		XShmSegmentInfo*    /* shminfo */,
14401 		uint        /* width */,
14402 		uint        /* height */
14403 	);
14404 
14405 	Pixmap XShmCreatePixmap(
14406 		Display*            /* dpy */,
14407 		Drawable            /* d */,
14408 		char*               /* data */,
14409 		XShmSegmentInfo*    /* shminfo */,
14410 		uint        /* width */,
14411 		uint        /* height */,
14412 		uint        /* depth */
14413 	);
14414 
14415 }
14416 }
14417 
14418 	// this requires -lXpm
14419 	//int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
14420 
14421 
14422 mixin DynamicLoad!(XLib, "X11", 6) xlib;
14423 mixin DynamicLoad!(Xext, "Xext", 6) xext;
14424 shared static this() {
14425 	xlib.loadDynamicLibrary();
14426 	xext.loadDynamicLibrary();
14427 }
14428 
14429 
14430 extern(C) nothrow @nogc {
14431 
14432 alias XrmDatabase = void*;
14433 struct XrmValue {
14434 	uint size;
14435 	void* addr;
14436 }
14437 
14438 struct XVisualInfo {
14439 	Visual* visual;
14440 	VisualID visualid;
14441 	int screen;
14442 	uint depth;
14443 	int c_class;
14444 	c_ulong red_mask;
14445 	c_ulong green_mask;
14446 	c_ulong blue_mask;
14447 	int colormap_size;
14448 	int bits_per_rgb;
14449 }
14450 
14451 enum VisualNoMask=	0x0;
14452 enum VisualIDMask=	0x1;
14453 enum VisualScreenMask=0x2;
14454 enum VisualDepthMask=	0x4;
14455 enum VisualClassMask=	0x8;
14456 enum VisualRedMaskMask=0x10;
14457 enum VisualGreenMaskMask=0x20;
14458 enum VisualBlueMaskMask=0x40;
14459 enum VisualColormapSizeMask=0x80;
14460 enum VisualBitsPerRGBMask=0x100;
14461 enum VisualAllMask=	0x1FF;
14462 
14463 
14464 // XIM and other crap
14465 struct _XOM {}
14466 struct _XIM {}
14467 struct _XIC {}
14468 alias XOM = _XOM*;
14469 alias XIM = _XIM*;
14470 alias XIC = _XIC*;
14471 
14472 alias XIMStyle = arch_ulong;
14473 enum : arch_ulong {
14474 	XIMPreeditArea      = 0x0001,
14475 	XIMPreeditCallbacks = 0x0002,
14476 	XIMPreeditPosition  = 0x0004,
14477 	XIMPreeditNothing   = 0x0008,
14478 	XIMPreeditNone      = 0x0010,
14479 	XIMStatusArea       = 0x0100,
14480 	XIMStatusCallbacks  = 0x0200,
14481 	XIMStatusNothing    = 0x0400,
14482 	XIMStatusNone       = 0x0800,
14483 }
14484 
14485 
14486 /* X Shared Memory Extension functions */
14487 	//pragma(lib, "Xshm");
14488 	alias arch_ulong ShmSeg;
14489 	struct XShmSegmentInfo {
14490 		ShmSeg shmseg;
14491 		int shmid;
14492 		ubyte* shmaddr;
14493 		Bool readOnly;
14494 	}
14495 
14496 	// and the necessary OS functions
14497 	int shmget(int, size_t, int);
14498 	void* shmat(int, in void*, int);
14499 	int shmdt(in void*);
14500 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
14501 
14502 	enum IPC_PRIVATE = 0;
14503 	enum IPC_CREAT = 512;
14504 	enum IPC_RMID = 0;
14505 
14506 /* MIT-SHM end */
14507 
14508 
14509 enum MappingType:int {
14510 	MappingModifier		=0,
14511 	MappingKeyboard		=1,
14512 	MappingPointer		=2
14513 }
14514 
14515 /* ImageFormat -- PutImage, GetImage */
14516 enum ImageFormat:int {
14517 	XYBitmap	=0,	/* depth 1, XYFormat */
14518 	XYPixmap	=1,	/* depth == drawable depth */
14519 	ZPixmap	=2	/* depth == drawable depth */
14520 }
14521 
14522 enum ModifierName:int {
14523 	ShiftMapIndex	=0,
14524 	LockMapIndex	=1,
14525 	ControlMapIndex	=2,
14526 	Mod1MapIndex	=3,
14527 	Mod2MapIndex	=4,
14528 	Mod3MapIndex	=5,
14529 	Mod4MapIndex	=6,
14530 	Mod5MapIndex	=7
14531 }
14532 
14533 enum ButtonMask:int {
14534 	Button1Mask	=1<<8,
14535 	Button2Mask	=1<<9,
14536 	Button3Mask	=1<<10,
14537 	Button4Mask	=1<<11,
14538 	Button5Mask	=1<<12,
14539 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
14540 }
14541 
14542 enum KeyOrButtonMask:uint {
14543 	ShiftMask	=1<<0,
14544 	LockMask	=1<<1,
14545 	ControlMask	=1<<2,
14546 	Mod1Mask	=1<<3,
14547 	Mod2Mask	=1<<4,
14548 	Mod3Mask	=1<<5,
14549 	Mod4Mask	=1<<6,
14550 	Mod5Mask	=1<<7,
14551 	Button1Mask	=1<<8,
14552 	Button2Mask	=1<<9,
14553 	Button3Mask	=1<<10,
14554 	Button4Mask	=1<<11,
14555 	Button5Mask	=1<<12,
14556 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
14557 }
14558 
14559 enum ButtonName:int {
14560 	Button1	=1,
14561 	Button2	=2,
14562 	Button3	=3,
14563 	Button4	=4,
14564 	Button5	=5
14565 }
14566 
14567 /* Notify modes */
14568 enum NotifyModes:int
14569 {
14570 	NotifyNormal		=0,
14571 	NotifyGrab			=1,
14572 	NotifyUngrab		=2,
14573 	NotifyWhileGrabbed	=3
14574 }
14575 enum NotifyHint = 1;	/* for MotionNotify events */
14576 
14577 /* Notify detail */
14578 enum NotifyDetail:int
14579 {
14580 	NotifyAncestor			=0,
14581 	NotifyVirtual			=1,
14582 	NotifyInferior			=2,
14583 	NotifyNonlinear			=3,
14584 	NotifyNonlinearVirtual	=4,
14585 	NotifyPointer			=5,
14586 	NotifyPointerRoot		=6,
14587 	NotifyDetailNone		=7
14588 }
14589 
14590 /* Visibility notify */
14591 
14592 enum VisibilityNotify:int
14593 {
14594 VisibilityUnobscured		=0,
14595 VisibilityPartiallyObscured	=1,
14596 VisibilityFullyObscured		=2
14597 }
14598 
14599 
14600 enum WindowStackingMethod:int
14601 {
14602 	Above		=0,
14603 	Below		=1,
14604 	TopIf		=2,
14605 	BottomIf	=3,
14606 	Opposite	=4
14607 }
14608 
14609 /* Circulation request */
14610 enum CirculationRequest:int
14611 {
14612 	PlaceOnTop		=0,
14613 	PlaceOnBottom	=1
14614 }
14615 
14616 enum PropertyNotification:int
14617 {
14618 	PropertyNewValue	=0,
14619 	PropertyDelete		=1
14620 }
14621 
14622 enum ColorMapNotification:int
14623 {
14624 	ColormapUninstalled	=0,
14625 	ColormapInstalled		=1
14626 }
14627 
14628 
14629 	struct _XPrivate {}
14630 	struct _XrmHashBucketRec {}
14631 
14632 	alias void* XPointer;
14633 	alias void* XExtData;
14634 
14635 	version( X86_64 ) {
14636 		alias ulong XID;
14637 		alias ulong arch_ulong;
14638 		alias long arch_long;
14639 	} else {
14640 		alias uint XID;
14641 		alias uint arch_ulong;
14642 		alias int arch_long;
14643 	}
14644 
14645 	alias XID Window;
14646 	alias XID Drawable;
14647 	alias XID Pixmap;
14648 
14649 	alias arch_ulong Atom;
14650 	alias int Bool;
14651 	alias Display XDisplay;
14652 
14653 	alias int ByteOrder;
14654 	alias arch_ulong Time;
14655 	alias void ScreenFormat;
14656 
14657 	struct XImage {
14658 		int width, height;			/* size of image */
14659 		int xoffset;				/* number of pixels offset in X direction */
14660 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
14661 		void *data;					/* pointer to image data */
14662 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
14663 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
14664 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
14665 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
14666 		int depth;					/* depth of image */
14667 		int bytes_per_line;			/* accelarator to next line */
14668 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
14669 		arch_ulong red_mask;	/* bits in z arrangment */
14670 		arch_ulong green_mask;
14671 		arch_ulong blue_mask;
14672 		XPointer obdata;			/* hook for the object routines to hang on */
14673 		static struct F {				/* image manipulation routines */
14674 			XImage* function(
14675 				XDisplay* 			/* display */,
14676 				Visual*				/* visual */,
14677 				uint				/* depth */,
14678 				int					/* format */,
14679 				int					/* offset */,
14680 				ubyte*				/* data */,
14681 				uint				/* width */,
14682 				uint				/* height */,
14683 				int					/* bitmap_pad */,
14684 				int					/* bytes_per_line */) create_image;
14685 			int function(XImage *) destroy_image;
14686 			arch_ulong function(XImage *, int, int) get_pixel;
14687 			int function(XImage *, int, int, arch_ulong) put_pixel;
14688 			XImage* function(XImage *, int, int, uint, uint) sub_image;
14689 			int function(XImage *, arch_long) add_pixel;
14690 		}
14691 		F f;
14692 	}
14693 	version(X86_64) static assert(XImage.sizeof == 136);
14694 	else version(X86) static assert(XImage.sizeof == 88);
14695 
14696 struct XCharStruct {
14697 	short       lbearing;       /* origin to left edge of raster */
14698 	short       rbearing;       /* origin to right edge of raster */
14699 	short       width;          /* advance to next char's origin */
14700 	short       ascent;         /* baseline to top edge of raster */
14701 	short       descent;        /* baseline to bottom edge of raster */
14702 	ushort attributes;  /* per char flags (not predefined) */
14703 }
14704 
14705 /*
14706  * To allow arbitrary information with fonts, there are additional properties
14707  * returned.
14708  */
14709 struct XFontProp {
14710 	Atom name;
14711 	arch_ulong card32;
14712 }
14713 
14714 alias Atom Font;
14715 
14716 struct XFontStruct {
14717 	XExtData *ext_data;           /* Hook for extension to hang data */
14718 	Font fid;                     /* Font ID for this font */
14719 	uint direction;           /* Direction the font is painted */
14720 	uint min_char_or_byte2;   /* First character */
14721 	uint max_char_or_byte2;   /* Last character */
14722 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
14723 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
14724 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
14725 	uint default_char;        /* Char to print for undefined character */
14726 	int n_properties;             /* How many properties there are */
14727 	XFontProp *properties;        /* Pointer to array of additional properties*/
14728 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
14729 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
14730 	XCharStruct *per_char;        /* first_char to last_char information */
14731 	int ascent;                   /* Max extent above baseline for spacing */
14732 	int descent;                  /* Max descent below baseline for spacing */
14733 }
14734 
14735 
14736 /*
14737  * Definitions of specific events.
14738  */
14739 struct XKeyEvent
14740 {
14741 	int type;			/* of event */
14742 	arch_ulong serial;		/* # of last request processed by server */
14743 	Bool send_event;	/* true if this came from a SendEvent request */
14744 	Display *display;	/* Display the event was read from */
14745 	Window window;	        /* "event" window it is reported relative to */
14746 	Window root;	        /* root window that the event occurred on */
14747 	Window subwindow;	/* child window */
14748 	Time time;		/* milliseconds */
14749 	int x, y;		/* pointer x, y coordinates in event window */
14750 	int x_root, y_root;	/* coordinates relative to root */
14751 	KeyOrButtonMask state;	/* key or button mask */
14752 	uint keycode;	/* detail */
14753 	Bool same_screen;	/* same screen flag */
14754 }
14755 version(X86_64) static assert(XKeyEvent.sizeof == 96);
14756 alias XKeyEvent XKeyPressedEvent;
14757 alias XKeyEvent XKeyReleasedEvent;
14758 
14759 struct XButtonEvent
14760 {
14761 	int type;		/* of event */
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;	        /* "event" window it is reported relative to */
14766 	Window root;	        /* root window that the event occurred on */
14767 	Window subwindow;	/* child window */
14768 	Time time;		/* milliseconds */
14769 	int x, y;		/* pointer x, y coordinates in event window */
14770 	int x_root, y_root;	/* coordinates relative to root */
14771 	KeyOrButtonMask state;	/* key or button mask */
14772 	uint button;	/* detail */
14773 	Bool same_screen;	/* same screen flag */
14774 }
14775 alias XButtonEvent XButtonPressedEvent;
14776 alias XButtonEvent XButtonReleasedEvent;
14777 
14778 struct XMotionEvent{
14779 	int type;		/* of event */
14780 	arch_ulong serial;	/* # of last request processed by server */
14781 	Bool send_event;	/* true if this came from a SendEvent request */
14782 	Display *display;	/* Display the event was read from */
14783 	Window window;	        /* "event" window reported relative to */
14784 	Window root;	        /* root window that the event occurred on */
14785 	Window subwindow;	/* child window */
14786 	Time time;		/* milliseconds */
14787 	int x, y;		/* pointer x, y coordinates in event window */
14788 	int x_root, y_root;	/* coordinates relative to root */
14789 	KeyOrButtonMask state;	/* key or button mask */
14790 	byte is_hint;		/* detail */
14791 	Bool same_screen;	/* same screen flag */
14792 }
14793 alias XMotionEvent XPointerMovedEvent;
14794 
14795 struct XCrossingEvent{
14796 	int type;		/* of event */
14797 	arch_ulong serial;	/* # of last request processed by server */
14798 	Bool send_event;	/* true if this came from a SendEvent request */
14799 	Display *display;	/* Display the event was read from */
14800 	Window window;	        /* "event" window reported relative to */
14801 	Window root;	        /* root window that the event occurred on */
14802 	Window subwindow;	/* child window */
14803 	Time time;		/* milliseconds */
14804 	int x, y;		/* pointer x, y coordinates in event window */
14805 	int x_root, y_root;	/* coordinates relative to root */
14806 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
14807 	NotifyDetail detail;
14808 	/*
14809 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
14810 	 * NotifyNonlinear,NotifyNonlinearVirtual
14811 	 */
14812 	Bool same_screen;	/* same screen flag */
14813 	Bool focus;		/* Boolean focus */
14814 	KeyOrButtonMask state;	/* key or button mask */
14815 }
14816 alias XCrossingEvent XEnterWindowEvent;
14817 alias XCrossingEvent XLeaveWindowEvent;
14818 
14819 struct XFocusChangeEvent{
14820 	int type;		/* FocusIn or FocusOut */
14821 	arch_ulong serial;	/* # of last request processed by server */
14822 	Bool send_event;	/* true if this came from a SendEvent request */
14823 	Display *display;	/* Display the event was read from */
14824 	Window window;		/* window of event */
14825 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
14826 				   NotifyGrab, NotifyUngrab */
14827 	NotifyDetail detail;
14828 	/*
14829 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
14830 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
14831 	 * NotifyPointerRoot, NotifyDetailNone
14832 	 */
14833 }
14834 alias XFocusChangeEvent XFocusInEvent;
14835 alias XFocusChangeEvent XFocusOutEvent;
14836 
14837 enum CWBackPixmap              = (1L<<0);
14838 enum CWBackPixel               = (1L<<1);
14839 enum CWBorderPixmap            = (1L<<2);
14840 enum CWBorderPixel             = (1L<<3);
14841 enum CWBitGravity              = (1L<<4);
14842 enum CWWinGravity              = (1L<<5);
14843 enum CWBackingStore            = (1L<<6);
14844 enum CWBackingPlanes           = (1L<<7);
14845 enum CWBackingPixel            = (1L<<8);
14846 enum CWOverrideRedirect        = (1L<<9);
14847 enum CWSaveUnder               = (1L<<10);
14848 enum CWEventMask               = (1L<<11);
14849 enum CWDontPropagate           = (1L<<12);
14850 enum CWColormap                = (1L<<13);
14851 enum CWCursor                  = (1L<<14);
14852 
14853 struct XWindowAttributes {
14854 	int x, y;			/* location of window */
14855 	int width, height;		/* width and height of window */
14856 	int border_width;		/* border width of window */
14857 	int depth;			/* depth of window */
14858 	Visual *visual;			/* the associated visual structure */
14859 	Window root;			/* root of screen containing window */
14860 	int class_;			/* InputOutput, InputOnly*/
14861 	int bit_gravity;		/* one of the bit gravity values */
14862 	int win_gravity;		/* one of the window gravity values */
14863 	int backing_store;		/* NotUseful, WhenMapped, Always */
14864 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
14865 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
14866 	Bool save_under;		/* boolean, should bits under be saved? */
14867 	Colormap colormap;		/* color map to be associated with window */
14868 	Bool map_installed;		/* boolean, is color map currently installed*/
14869 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
14870 	arch_long all_event_masks;		/* set of events all people have interest in*/
14871 	arch_long your_event_mask;		/* my event mask */
14872 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
14873 	Bool override_redirect;		/* boolean value for override-redirect */
14874 	Screen *screen;			/* back pointer to correct screen */
14875 }
14876 
14877 enum IsUnmapped = 0;
14878 enum IsUnviewable = 1;
14879 enum IsViewable = 2;
14880 
14881 struct XSetWindowAttributes {
14882 	Pixmap background_pixmap;/* background, None, or ParentRelative */
14883 	arch_ulong background_pixel;/* background pixel */
14884 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
14885 	arch_ulong border_pixel;/* border pixel value */
14886 	int bit_gravity;         /* one of bit gravity values */
14887 	int win_gravity;         /* one of the window gravity values */
14888 	int backing_store;       /* NotUseful, WhenMapped, Always */
14889 	arch_ulong backing_planes;/* planes to be preserved if possible */
14890 	arch_ulong backing_pixel;/* value to use in restoring planes */
14891 	Bool save_under;         /* should bits under be saved? (popups) */
14892 	arch_long event_mask;         /* set of events that should be saved */
14893 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
14894 	Bool override_redirect;  /* boolean value for override_redirect */
14895 	Colormap colormap;       /* color map to be associated with window */
14896 	Cursor cursor;           /* cursor to be displayed (or None) */
14897 }
14898 
14899 
14900 alias int Status;
14901 
14902 
14903 enum EventMask:int
14904 {
14905 	NoEventMask				=0,
14906 	KeyPressMask			=1<<0,
14907 	KeyReleaseMask			=1<<1,
14908 	ButtonPressMask			=1<<2,
14909 	ButtonReleaseMask		=1<<3,
14910 	EnterWindowMask			=1<<4,
14911 	LeaveWindowMask			=1<<5,
14912 	PointerMotionMask		=1<<6,
14913 	PointerMotionHintMask	=1<<7,
14914 	Button1MotionMask		=1<<8,
14915 	Button2MotionMask		=1<<9,
14916 	Button3MotionMask		=1<<10,
14917 	Button4MotionMask		=1<<11,
14918 	Button5MotionMask		=1<<12,
14919 	ButtonMotionMask		=1<<13,
14920 	KeymapStateMask		=1<<14,
14921 	ExposureMask			=1<<15,
14922 	VisibilityChangeMask	=1<<16,
14923 	StructureNotifyMask		=1<<17,
14924 	ResizeRedirectMask		=1<<18,
14925 	SubstructureNotifyMask	=1<<19,
14926 	SubstructureRedirectMask=1<<20,
14927 	FocusChangeMask			=1<<21,
14928 	PropertyChangeMask		=1<<22,
14929 	ColormapChangeMask		=1<<23,
14930 	OwnerGrabButtonMask		=1<<24
14931 }
14932 
14933 struct MwmHints {
14934 	int flags;
14935 	int functions;
14936 	int decorations;
14937 	int input_mode;
14938 	int status;
14939 }
14940 
14941 enum {
14942 	MWM_HINTS_FUNCTIONS = (1L << 0),
14943 	MWM_HINTS_DECORATIONS =  (1L << 1),
14944 
14945 	MWM_FUNC_ALL = (1L << 0),
14946 	MWM_FUNC_RESIZE = (1L << 1),
14947 	MWM_FUNC_MOVE = (1L << 2),
14948 	MWM_FUNC_MINIMIZE = (1L << 3),
14949 	MWM_FUNC_MAXIMIZE = (1L << 4),
14950 	MWM_FUNC_CLOSE = (1L << 5)
14951 }
14952 
14953 import core.stdc.config : c_long, c_ulong;
14954 
14955 	/* Size hints mask bits */
14956 
14957 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
14958 	enum   USSize      = (1L << 1)          /* user specified width, height */;
14959 	enum   PPosition   = (1L << 2)          /* program specified position */;
14960 	enum   PSize       = (1L << 3)          /* program specified size */;
14961 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
14962 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
14963 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
14964 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
14965 	enum   PBaseSize   = (1L << 8);
14966 	enum   PWinGravity = (1L << 9);
14967 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
14968 	struct XSizeHints {
14969 		arch_long flags;         /* marks which fields in this structure are defined */
14970 		int x, y;           /* Obsolete */
14971 		int width, height;  /* Obsolete */
14972 		int min_width, min_height;
14973 		int max_width, max_height;
14974 		int width_inc, height_inc;
14975 		struct Aspect {
14976 			int x;       /* numerator */
14977 			int y;       /* denominator */
14978 		}
14979 
14980 		Aspect min_aspect;
14981 		Aspect max_aspect;
14982 		int base_width, base_height;
14983 		int win_gravity;
14984 		/* this structure may be extended in the future */
14985 	}
14986 
14987 
14988 
14989 enum EventType:int
14990 {
14991 	KeyPress			=2,
14992 	KeyRelease			=3,
14993 	ButtonPress			=4,
14994 	ButtonRelease		=5,
14995 	MotionNotify		=6,
14996 	EnterNotify			=7,
14997 	LeaveNotify			=8,
14998 	FocusIn				=9,
14999 	FocusOut			=10,
15000 	KeymapNotify		=11,
15001 	Expose				=12,
15002 	GraphicsExpose		=13,
15003 	NoExpose			=14,
15004 	VisibilityNotify	=15,
15005 	CreateNotify		=16,
15006 	DestroyNotify		=17,
15007 	UnmapNotify		=18,
15008 	MapNotify			=19,
15009 	MapRequest			=20,
15010 	ReparentNotify		=21,
15011 	ConfigureNotify		=22,
15012 	ConfigureRequest	=23,
15013 	GravityNotify		=24,
15014 	ResizeRequest		=25,
15015 	CirculateNotify		=26,
15016 	CirculateRequest	=27,
15017 	PropertyNotify		=28,
15018 	SelectionClear		=29,
15019 	SelectionRequest	=30,
15020 	SelectionNotify		=31,
15021 	ColormapNotify		=32,
15022 	ClientMessage		=33,
15023 	MappingNotify		=34,
15024 	LASTEvent			=35	/* must be bigger than any event # */
15025 }
15026 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
15027 struct XKeymapEvent
15028 {
15029 	int type;
15030 	arch_ulong serial;	/* # of last request processed by server */
15031 	Bool send_event;	/* true if this came from a SendEvent request */
15032 	Display *display;	/* Display the event was read from */
15033 	Window window;
15034 	byte[32] key_vector;
15035 }
15036 
15037 struct XExposeEvent
15038 {
15039 	int type;
15040 	arch_ulong serial;	/* # of last request processed by server */
15041 	Bool send_event;	/* true if this came from a SendEvent request */
15042 	Display *display;	/* Display the event was read from */
15043 	Window window;
15044 	int x, y;
15045 	int width, height;
15046 	int count;		/* if non-zero, at least this many more */
15047 }
15048 
15049 struct XGraphicsExposeEvent{
15050 	int type;
15051 	arch_ulong serial;	/* # of last request processed by server */
15052 	Bool send_event;	/* true if this came from a SendEvent request */
15053 	Display *display;	/* Display the event was read from */
15054 	Drawable drawable;
15055 	int x, y;
15056 	int width, height;
15057 	int count;		/* if non-zero, at least this many more */
15058 	int major_code;		/* core is CopyArea or CopyPlane */
15059 	int minor_code;		/* not defined in the core */
15060 }
15061 
15062 struct XNoExposeEvent{
15063 	int type;
15064 	arch_ulong serial;	/* # of last request processed by server */
15065 	Bool send_event;	/* true if this came from a SendEvent request */
15066 	Display *display;	/* Display the event was read from */
15067 	Drawable drawable;
15068 	int major_code;		/* core is CopyArea or CopyPlane */
15069 	int minor_code;		/* not defined in the core */
15070 }
15071 
15072 struct XVisibilityEvent{
15073 	int type;
15074 	arch_ulong serial;	/* # of last request processed by server */
15075 	Bool send_event;	/* true if this came from a SendEvent request */
15076 	Display *display;	/* Display the event was read from */
15077 	Window window;
15078 	VisibilityNotify state;		/* Visibility state */
15079 }
15080 
15081 struct XCreateWindowEvent{
15082 	int type;
15083 	arch_ulong serial;	/* # of last request processed by server */
15084 	Bool send_event;	/* true if this came from a SendEvent request */
15085 	Display *display;	/* Display the event was read from */
15086 	Window parent;		/* parent of the window */
15087 	Window window;		/* window id of window created */
15088 	int x, y;		/* window location */
15089 	int width, height;	/* size of window */
15090 	int border_width;	/* border width */
15091 	Bool override_redirect;	/* creation should be overridden */
15092 }
15093 
15094 struct XDestroyWindowEvent
15095 {
15096 	int type;
15097 	arch_ulong serial;		/* # of last request processed by server */
15098 	Bool send_event;	/* true if this came from a SendEvent request */
15099 	Display *display;	/* Display the event was read from */
15100 	Window event;
15101 	Window window;
15102 }
15103 
15104 struct XUnmapEvent
15105 {
15106 	int type;
15107 	arch_ulong serial;		/* # of last request processed by server */
15108 	Bool send_event;	/* true if this came from a SendEvent request */
15109 	Display *display;	/* Display the event was read from */
15110 	Window event;
15111 	Window window;
15112 	Bool from_configure;
15113 }
15114 
15115 struct XMapEvent
15116 {
15117 	int type;
15118 	arch_ulong serial;		/* # of last request processed by server */
15119 	Bool send_event;	/* true if this came from a SendEvent request */
15120 	Display *display;	/* Display the event was read from */
15121 	Window event;
15122 	Window window;
15123 	Bool override_redirect;	/* Boolean, is override set... */
15124 }
15125 
15126 struct XMapRequestEvent
15127 {
15128 	int type;
15129 	arch_ulong serial;	/* # of last request processed by server */
15130 	Bool send_event;	/* true if this came from a SendEvent request */
15131 	Display *display;	/* Display the event was read from */
15132 	Window parent;
15133 	Window window;
15134 }
15135 
15136 struct XReparentEvent
15137 {
15138 	int type;
15139 	arch_ulong serial;	/* # of last request processed by server */
15140 	Bool send_event;	/* true if this came from a SendEvent request */
15141 	Display *display;	/* Display the event was read from */
15142 	Window event;
15143 	Window window;
15144 	Window parent;
15145 	int x, y;
15146 	Bool override_redirect;
15147 }
15148 
15149 struct XConfigureEvent
15150 {
15151 	int type;
15152 	arch_ulong serial;	/* # of last request processed by server */
15153 	Bool send_event;	/* true if this came from a SendEvent request */
15154 	Display *display;	/* Display the event was read from */
15155 	Window event;
15156 	Window window;
15157 	int x, y;
15158 	int width, height;
15159 	int border_width;
15160 	Window above;
15161 	Bool override_redirect;
15162 }
15163 
15164 struct XGravityEvent
15165 {
15166 	int type;
15167 	arch_ulong serial;	/* # of last request processed by server */
15168 	Bool send_event;	/* true if this came from a SendEvent request */
15169 	Display *display;	/* Display the event was read from */
15170 	Window event;
15171 	Window window;
15172 	int x, y;
15173 }
15174 
15175 struct XResizeRequestEvent
15176 {
15177 	int type;
15178 	arch_ulong serial;	/* # of last request processed by server */
15179 	Bool send_event;	/* true if this came from a SendEvent request */
15180 	Display *display;	/* Display the event was read from */
15181 	Window window;
15182 	int width, height;
15183 }
15184 
15185 struct  XConfigureRequestEvent
15186 {
15187 	int type;
15188 	arch_ulong serial;	/* # of last request processed by server */
15189 	Bool send_event;	/* true if this came from a SendEvent request */
15190 	Display *display;	/* Display the event was read from */
15191 	Window parent;
15192 	Window window;
15193 	int x, y;
15194 	int width, height;
15195 	int border_width;
15196 	Window above;
15197 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
15198 	arch_ulong value_mask;
15199 }
15200 
15201 struct XCirculateEvent
15202 {
15203 	int type;
15204 	arch_ulong serial;	/* # of last request processed by server */
15205 	Bool send_event;	/* true if this came from a SendEvent request */
15206 	Display *display;	/* Display the event was read from */
15207 	Window event;
15208 	Window window;
15209 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
15210 }
15211 
15212 struct XCirculateRequestEvent
15213 {
15214 	int type;
15215 	arch_ulong serial;	/* # of last request processed by server */
15216 	Bool send_event;	/* true if this came from a SendEvent request */
15217 	Display *display;	/* Display the event was read from */
15218 	Window parent;
15219 	Window window;
15220 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
15221 }
15222 
15223 struct XPropertyEvent
15224 {
15225 	int type;
15226 	arch_ulong serial;	/* # of last request processed by server */
15227 	Bool send_event;	/* true if this came from a SendEvent request */
15228 	Display *display;	/* Display the event was read from */
15229 	Window window;
15230 	Atom atom;
15231 	Time time;
15232 	PropertyNotification state;		/* NewValue, Deleted */
15233 }
15234 
15235 struct XSelectionClearEvent
15236 {
15237 	int type;
15238 	arch_ulong serial;	/* # of last request processed by server */
15239 	Bool send_event;	/* true if this came from a SendEvent request */
15240 	Display *display;	/* Display the event was read from */
15241 	Window window;
15242 	Atom selection;
15243 	Time time;
15244 }
15245 
15246 struct XSelectionRequestEvent
15247 {
15248 	int type;
15249 	arch_ulong serial;	/* # of last request processed by server */
15250 	Bool send_event;	/* true if this came from a SendEvent request */
15251 	Display *display;	/* Display the event was read from */
15252 	Window owner;
15253 	Window requestor;
15254 	Atom selection;
15255 	Atom target;
15256 	Atom property;
15257 	Time time;
15258 }
15259 
15260 struct XSelectionEvent
15261 {
15262 	int type;
15263 	arch_ulong serial;	/* # of last request processed by server */
15264 	Bool send_event;	/* true if this came from a SendEvent request */
15265 	Display *display;	/* Display the event was read from */
15266 	Window requestor;
15267 	Atom selection;
15268 	Atom target;
15269 	Atom property;		/* ATOM or None */
15270 	Time time;
15271 }
15272 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
15273 
15274 struct XColormapEvent
15275 {
15276 	int type;
15277 	arch_ulong serial;	/* # of last request processed by server */
15278 	Bool send_event;	/* true if this came from a SendEvent request */
15279 	Display *display;	/* Display the event was read from */
15280 	Window window;
15281 	Colormap colormap;	/* COLORMAP or None */
15282 	Bool new_;		/* C++ */
15283 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
15284 }
15285 version(X86_64) static assert(XColormapEvent.sizeof == 56);
15286 
15287 struct XClientMessageEvent
15288 {
15289 	int type;
15290 	arch_ulong serial;	/* # of last request processed by server */
15291 	Bool send_event;	/* true if this came from a SendEvent request */
15292 	Display *display;	/* Display the event was read from */
15293 	Window window;
15294 	Atom message_type;
15295 	int format;
15296 	union Data{
15297 		byte[20] b;
15298 		short[10] s;
15299 		arch_ulong[5] l;
15300 	}
15301 	Data data;
15302 
15303 }
15304 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
15305 
15306 struct XMappingEvent
15307 {
15308 	int type;
15309 	arch_ulong serial;	/* # of last request processed by server */
15310 	Bool send_event;	/* true if this came from a SendEvent request */
15311 	Display *display;	/* Display the event was read from */
15312 	Window window;		/* unused */
15313 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
15314 				   MappingPointer */
15315 	int first_keycode;	/* first keycode */
15316 	int count;		/* defines range of change w. first_keycode*/
15317 }
15318 
15319 struct XErrorEvent
15320 {
15321 	int type;
15322 	Display *display;	/* Display the event was read from */
15323 	XID resourceid;		/* resource id */
15324 	arch_ulong serial;	/* serial number of failed request */
15325 	ubyte error_code;	/* error code of failed request */
15326 	ubyte request_code;	/* Major op-code of failed request */
15327 	ubyte minor_code;	/* Minor op-code of failed request */
15328 }
15329 
15330 struct XAnyEvent
15331 {
15332 	int type;
15333 	arch_ulong serial;	/* # of last request processed by server */
15334 	Bool send_event;	/* true if this came from a SendEvent request */
15335 	Display *display;/* Display the event was read from */
15336 	Window window;	/* window on which event was requested in event mask */
15337 }
15338 
15339 union XEvent{
15340 	int type;		/* must not be changed; first element */
15341 	XAnyEvent xany;
15342 	XKeyEvent xkey;
15343 	XButtonEvent xbutton;
15344 	XMotionEvent xmotion;
15345 	XCrossingEvent xcrossing;
15346 	XFocusChangeEvent xfocus;
15347 	XExposeEvent xexpose;
15348 	XGraphicsExposeEvent xgraphicsexpose;
15349 	XNoExposeEvent xnoexpose;
15350 	XVisibilityEvent xvisibility;
15351 	XCreateWindowEvent xcreatewindow;
15352 	XDestroyWindowEvent xdestroywindow;
15353 	XUnmapEvent xunmap;
15354 	XMapEvent xmap;
15355 	XMapRequestEvent xmaprequest;
15356 	XReparentEvent xreparent;
15357 	XConfigureEvent xconfigure;
15358 	XGravityEvent xgravity;
15359 	XResizeRequestEvent xresizerequest;
15360 	XConfigureRequestEvent xconfigurerequest;
15361 	XCirculateEvent xcirculate;
15362 	XCirculateRequestEvent xcirculaterequest;
15363 	XPropertyEvent xproperty;
15364 	XSelectionClearEvent xselectionclear;
15365 	XSelectionRequestEvent xselectionrequest;
15366 	XSelectionEvent xselection;
15367 	XColormapEvent xcolormap;
15368 	XClientMessageEvent xclient;
15369 	XMappingEvent xmapping;
15370 	XErrorEvent xerror;
15371 	XKeymapEvent xkeymap;
15372 	arch_ulong[24] pad;
15373 }
15374 
15375 
15376 	struct Display {
15377 		XExtData *ext_data;	/* hook for extension to hang data */
15378 		_XPrivate *private1;
15379 		int fd;			/* Network socket. */
15380 		int private2;
15381 		int proto_major_version;/* major version of server's X protocol */
15382 		int proto_minor_version;/* minor version of servers X protocol */
15383 		char *vendor;		/* vendor of the server hardware */
15384 	    	XID private3;
15385 		XID private4;
15386 		XID private5;
15387 		int private6;
15388 		XID function(Display*)resource_alloc;/* allocator function */
15389 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
15390 		int bitmap_unit;	/* padding and data requirements */
15391 		int bitmap_pad;		/* padding requirements on bitmaps */
15392 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
15393 		int nformats;		/* number of pixmap formats in list */
15394 		ScreenFormat *pixmap_format;	/* pixmap format list */
15395 		int private8;
15396 		int release;		/* release of the server */
15397 		_XPrivate *private9;
15398 		_XPrivate *private10;
15399 		int qlen;		/* Length of input event queue */
15400 		arch_ulong last_request_read; /* seq number of last event read */
15401 		arch_ulong request;	/* sequence number of last request. */
15402 		XPointer private11;
15403 		XPointer private12;
15404 		XPointer private13;
15405 		XPointer private14;
15406 		uint max_request_size; /* maximum number 32 bit words in request*/
15407 		_XrmHashBucketRec *db;
15408 		int function  (Display*)private15;
15409 		char *display_name;	/* "host:display" string used on this connect*/
15410 		int default_screen;	/* default screen for operations */
15411 		int nscreens;		/* number of screens on this server*/
15412 		Screen *screens;	/* pointer to list of screens */
15413 		arch_ulong motion_buffer;	/* size of motion buffer */
15414 		arch_ulong private16;
15415 		int min_keycode;	/* minimum defined keycode */
15416 		int max_keycode;	/* maximum defined keycode */
15417 		XPointer private17;
15418 		XPointer private18;
15419 		int private19;
15420 		byte *xdefaults;	/* contents of defaults from server */
15421 		/* there is more to this structure, but it is private to Xlib */
15422 	}
15423 
15424 	// I got these numbers from a C program as a sanity test
15425 	version(X86_64) {
15426 		static assert(Display.sizeof == 296);
15427 		static assert(XPointer.sizeof == 8);
15428 		static assert(XErrorEvent.sizeof == 40);
15429 		static assert(XAnyEvent.sizeof == 40);
15430 		static assert(XMappingEvent.sizeof == 56);
15431 		static assert(XEvent.sizeof == 192);
15432 	} else {
15433 		static assert(Display.sizeof == 176);
15434 		static assert(XPointer.sizeof == 4);
15435 		static assert(XEvent.sizeof == 96);
15436 	}
15437 
15438 struct Depth
15439 {
15440 	int depth;		/* this depth (Z) of the depth */
15441 	int nvisuals;		/* number of Visual types at this depth */
15442 	Visual *visuals;	/* list of visuals possible at this depth */
15443 }
15444 
15445 alias void* GC;
15446 alias c_ulong VisualID;
15447 alias XID Colormap;
15448 alias XID Cursor;
15449 alias XID KeySym;
15450 alias uint KeyCode;
15451 enum None = 0;
15452 }
15453 
15454 version(without_opengl) {}
15455 else {
15456 extern(C) nothrow @nogc {
15457 
15458 
15459 static if(!SdpyIsUsingIVGLBinds) {
15460 enum GLX_USE_GL=            1;       /* support GLX rendering */
15461 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
15462 enum GLX_LEVEL=             3;       /* level in plane stacking */
15463 enum GLX_RGBA=              4;       /* true if RGBA mode */
15464 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
15465 enum GLX_STEREO=            6;       /* stereo buffering supported */
15466 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
15467 enum GLX_RED_SIZE=          8;       /* number of red component bits */
15468 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
15469 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
15470 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
15471 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
15472 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
15473 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
15474 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
15475 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
15476 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
15477 
15478 
15479 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
15480 
15481 
15482 
15483 enum GL_TRUE = 1;
15484 enum GL_FALSE = 0;
15485 alias int GLint;
15486 }
15487 
15488 alias XID GLXContextID;
15489 alias XID GLXPixmap;
15490 alias XID GLXDrawable;
15491 alias XID GLXPbuffer;
15492 alias XID GLXWindow;
15493 alias XID GLXFBConfigID;
15494 alias void* GLXContext;
15495 
15496 }
15497 }
15498 
15499 enum AllocNone = 0;
15500 
15501 extern(C) {
15502 	/* WARNING, this type not in Xlib spec */
15503 	extern(C) alias XIOErrorHandler = int function (Display* display);
15504 }
15505 
15506 extern(C) nothrow @nogc {
15507 struct Screen{
15508 	XExtData *ext_data;		/* hook for extension to hang data */
15509 	Display *display;		/* back pointer to display structure */
15510 	Window root;			/* Root window id. */
15511 	int width, height;		/* width and height of screen */
15512 	int mwidth, mheight;	/* width and height of  in millimeters */
15513 	int ndepths;			/* number of depths possible */
15514 	Depth *depths;			/* list of allowable depths on the screen */
15515 	int root_depth;			/* bits per pixel */
15516 	Visual *root_visual;	/* root visual */
15517 	GC default_gc;			/* GC for the root root visual */
15518 	Colormap cmap;			/* default color map */
15519 	uint white_pixel;
15520 	uint black_pixel;		/* White and Black pixel values */
15521 	int max_maps, min_maps;	/* max and min color maps */
15522 	int backing_store;		/* Never, WhenMapped, Always */
15523 	bool save_unders;
15524 	int root_input_mask;	/* initial root input mask */
15525 }
15526 
15527 struct Visual
15528 {
15529 	XExtData *ext_data;	/* hook for extension to hang data */
15530 	VisualID visualid;	/* visual id of this visual */
15531 	int class_;			/* class of screen (monochrome, etc.) */
15532 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
15533 	int bits_per_rgb;	/* log base 2 of distinct color values */
15534 	int map_entries;	/* color map entries */
15535 }
15536 
15537 	alias Display* _XPrivDisplay;
15538 
15539 	Screen* ScreenOfDisplay(Display* dpy, int scr) {
15540 		assert(dpy !is null);
15541 		return &dpy.screens[scr];
15542 	}
15543 
15544 	Window	RootWindow(Display *dpy,int scr) {
15545 		return ScreenOfDisplay(dpy,scr).root;
15546 	}
15547 
15548 	struct XWMHints {
15549 		arch_long flags;
15550 		Bool input;
15551 		int initial_state;
15552 		Pixmap icon_pixmap;
15553 		Window icon_window;
15554 		int icon_x, icon_y;
15555 		Pixmap icon_mask;
15556 		XID window_group;
15557 	}
15558 
15559 	struct XClassHint {
15560 		char* res_name;
15561 		char* res_class;
15562 	}
15563 
15564 	int DefaultScreen(Display *dpy) {
15565 		return dpy.default_screen;
15566 	}
15567 
15568 	int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
15569 	int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
15570 	int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
15571 	int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
15572 	int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
15573 	auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
15574 
15575 	int ConnectionNumber(Display* dpy) { return dpy.fd; }
15576 
15577 	enum int AnyPropertyType = 0;
15578 	enum int Success = 0;
15579 
15580 	enum int RevertToNone = None;
15581 	enum int PointerRoot = 1;
15582 	enum Time CurrentTime = 0;
15583 	enum int RevertToPointerRoot = PointerRoot;
15584 	enum int RevertToParent = 2;
15585 
15586 	int DefaultDepthOfDisplay(Display* dpy) {
15587 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
15588 	}
15589 
15590 	Visual* DefaultVisual(Display *dpy,int scr) {
15591 		return ScreenOfDisplay(dpy,scr).root_visual;
15592 	}
15593 
15594 	GC DefaultGC(Display *dpy,int scr) {
15595 		return ScreenOfDisplay(dpy,scr).default_gc;
15596 	}
15597 
15598 	uint BlackPixel(Display *dpy,int scr) {
15599 		return ScreenOfDisplay(dpy,scr).black_pixel;
15600 	}
15601 
15602 	uint WhitePixel(Display *dpy,int scr) {
15603 		return ScreenOfDisplay(dpy,scr).white_pixel;
15604 	}
15605 
15606 	alias void* XFontSet; // i think
15607 	struct XmbTextItem {
15608 		char* chars;
15609 		int nchars;
15610 		int delta;
15611 		XFontSet font_set;
15612 	}
15613 
15614 	struct XTextItem {
15615 		char* chars;
15616 		int nchars;
15617 		int delta;
15618 		Font font;
15619 	}
15620 
15621 	enum {
15622 		GXclear        = 0x0, /* 0 */
15623 		GXand          = 0x1, /* src AND dst */
15624 		GXandReverse   = 0x2, /* src AND NOT dst */
15625 		GXcopy         = 0x3, /* src */
15626 		GXandInverted  = 0x4, /* NOT src AND dst */
15627 		GXnoop         = 0x5, /* dst */
15628 		GXxor          = 0x6, /* src XOR dst */
15629 		GXor           = 0x7, /* src OR dst */
15630 		GXnor          = 0x8, /* NOT src AND NOT dst */
15631 		GXequiv        = 0x9, /* NOT src XOR dst */
15632 		GXinvert       = 0xa, /* NOT dst */
15633 		GXorReverse    = 0xb, /* src OR NOT dst */
15634 		GXcopyInverted = 0xc, /* NOT src */
15635 		GXorInverted   = 0xd, /* NOT src OR dst */
15636 		GXnand         = 0xe, /* NOT src OR NOT dst */
15637 		GXset          = 0xf, /* 1 */
15638 	}
15639 	enum QueueMode : int {
15640 		QueuedAlready,
15641 		QueuedAfterReading,
15642 		QueuedAfterFlush
15643 	}
15644 
15645 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
15646 
15647 	struct XPoint {
15648 		short x;
15649 		short y;
15650 	}
15651 
15652 	enum CoordMode:int {
15653 		CoordModeOrigin = 0,
15654 		CoordModePrevious = 1
15655 	}
15656 
15657 	enum PolygonShape:int {
15658 		Complex = 0,
15659 		Nonconvex = 1,
15660 		Convex = 2
15661 	}
15662 
15663 	struct XTextProperty {
15664 		const(char)* value;		/* same as Property routines */
15665 		Atom encoding;			/* prop type */
15666 		int format;				/* prop data format: 8, 16, or 32 */
15667 		arch_ulong nitems;		/* number of data items in value */
15668 	}
15669 
15670 	version( X86_64 ) {
15671 		static assert(XTextProperty.sizeof == 32);
15672 	}
15673 
15674 
15675 	struct XGCValues {
15676 		int function_;           /* logical operation */
15677 		arch_ulong plane_mask;/* plane mask */
15678 		arch_ulong foreground;/* foreground pixel */
15679 		arch_ulong background;/* background pixel */
15680 		int line_width;         /* line width */
15681 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
15682 		int cap_style;          /* CapNotLast, CapButt,
15683 					   CapRound, CapProjecting */
15684 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
15685 		int fill_style;         /* FillSolid, FillTiled,
15686 					   FillStippled, FillOpaeueStippled */
15687 		int fill_rule;          /* EvenOddRule, WindingRule */
15688 		int arc_mode;           /* ArcChord, ArcPieSlice */
15689 		Pixmap tile;            /* tile pixmap for tiling operations */
15690 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
15691 		int ts_x_origin;        /* offset for tile or stipple operations */
15692 		int ts_y_origin;
15693 		Font font;              /* default text font for text operations */
15694 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
15695 		Bool graphics_exposures;/* boolean, should exposures be generated */
15696 		int clip_x_origin;      /* origin for clipping */
15697 		int clip_y_origin;
15698 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
15699 		int dash_offset;        /* patterned/dashed line information */
15700 		char dashes;
15701 	}
15702 
15703 	struct XColor {
15704 		arch_ulong pixel;
15705 		ushort red, green, blue;
15706 		byte flags;
15707 		byte pad;
15708 	}
15709 
15710 	alias XErrorHandler = int function(Display*, XErrorEvent*);
15711 
15712 	struct XRectangle {
15713 		short x;
15714 		short y;
15715 		ushort width;
15716 		ushort height;
15717 	}
15718 
15719 	enum ClipByChildren = 0;
15720 	enum IncludeInferiors = 1;
15721 
15722 	enum Atom XA_PRIMARY = 1;
15723 	enum Atom XA_SECONDARY = 2;
15724 	enum Atom XA_STRING = 31;
15725 	enum Atom XA_CARDINAL = 6;
15726 	enum Atom XA_WM_NAME = 39;
15727 	enum Atom XA_ATOM = 4;
15728 	enum Atom XA_WINDOW = 33;
15729 	enum Atom XA_WM_HINTS = 35;
15730 	enum int PropModeAppend = 2;
15731 	enum int PropModeReplace = 0;
15732 	enum int PropModePrepend = 1;
15733 
15734 	enum int CopyFromParent = 0;
15735 	enum int InputOutput = 1;
15736 
15737 	// XWMHints
15738 	enum InputHint = 1 << 0;
15739 	enum StateHint = 1 << 1;
15740 	enum IconPixmapHint = (1L << 2);
15741 	enum IconWindowHint = (1L << 3);
15742 	enum IconPositionHint = (1L << 4);
15743 	enum IconMaskHint = (1L << 5);
15744 	enum WindowGroupHint = (1L << 6);
15745 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
15746 	enum XUrgencyHint = (1L << 8);
15747 
15748 	// GC Components
15749 	enum GCFunction           =   (1L<<0);
15750 	enum GCPlaneMask         =    (1L<<1);
15751 	enum GCForeground       =     (1L<<2);
15752 	enum GCBackground      =      (1L<<3);
15753 	enum GCLineWidth      =       (1L<<4);
15754 	enum GCLineStyle     =        (1L<<5);
15755 	enum GCCapStyle     =         (1L<<6);
15756 	enum GCJoinStyle   =          (1L<<7);
15757 	enum GCFillStyle  =           (1L<<8);
15758 	enum GCFillRule  =            (1L<<9);
15759 	enum GCTile     =             (1L<<10);
15760 	enum GCStipple           =    (1L<<11);
15761 	enum GCTileStipXOrigin  =     (1L<<12);
15762 	enum GCTileStipYOrigin =      (1L<<13);
15763 	enum GCFont               =   (1L<<14);
15764 	enum GCSubwindowMode     =    (1L<<15);
15765 	enum GCGraphicsExposures=     (1L<<16);
15766 	enum GCClipXOrigin     =      (1L<<17);
15767 	enum GCClipYOrigin    =       (1L<<18);
15768 	enum GCClipMask      =        (1L<<19);
15769 	enum GCDashOffset   =         (1L<<20);
15770 	enum GCDashList    =          (1L<<21);
15771 	enum GCArcMode    =           (1L<<22);
15772 	enum GCLastBit   =            22;
15773 
15774 
15775 	enum int WithdrawnState = 0;
15776 	enum int NormalState = 1;
15777 	enum int IconicState = 3;
15778 
15779 }
15780 } else version (OSXCocoa) {
15781 private:
15782 	alias void* id;
15783 	alias void* Class;
15784 	alias void* SEL;
15785 	alias void* IMP;
15786 	alias void* Ivar;
15787 	alias byte BOOL;
15788 	alias const(void)* CFStringRef;
15789 	alias const(void)* CFAllocatorRef;
15790 	alias const(void)* CFTypeRef;
15791 	alias const(void)* CGContextRef;
15792 	alias const(void)* CGColorSpaceRef;
15793 	alias const(void)* CGImageRef;
15794 	alias ulong CGBitmapInfo;
15795 
15796 	struct objc_super {
15797 		id self;
15798 		Class superclass;
15799 	}
15800 
15801 	struct CFRange {
15802 		long location, length;
15803 	}
15804 
15805 	struct NSPoint {
15806 		double x, y;
15807 
15808 		static fromTuple(T)(T tupl) {
15809 			return NSPoint(tupl.tupleof);
15810 		}
15811 	}
15812 	struct NSSize {
15813 		double width, height;
15814 	}
15815 	struct NSRect {
15816 		NSPoint origin;
15817 		NSSize size;
15818 	}
15819 	alias NSPoint CGPoint;
15820 	alias NSSize CGSize;
15821 	alias NSRect CGRect;
15822 
15823 	struct CGAffineTransform {
15824 		double a, b, c, d, tx, ty;
15825 	}
15826 
15827 	enum NSApplicationActivationPolicyRegular = 0;
15828 	enum NSBackingStoreBuffered = 2;
15829 	enum kCFStringEncodingUTF8 = 0x08000100;
15830 
15831 	enum : size_t {
15832 		NSBorderlessWindowMask = 0,
15833 		NSTitledWindowMask = 1 << 0,
15834 		NSClosableWindowMask = 1 << 1,
15835 		NSMiniaturizableWindowMask = 1 << 2,
15836 		NSResizableWindowMask = 1 << 3,
15837 		NSTexturedBackgroundWindowMask = 1 << 8
15838 	}
15839 
15840 	enum : ulong {
15841 		kCGImageAlphaNone,
15842 		kCGImageAlphaPremultipliedLast,
15843 		kCGImageAlphaPremultipliedFirst,
15844 		kCGImageAlphaLast,
15845 		kCGImageAlphaFirst,
15846 		kCGImageAlphaNoneSkipLast,
15847 		kCGImageAlphaNoneSkipFirst
15848 	}
15849 	enum : ulong {
15850 		kCGBitmapAlphaInfoMask = 0x1F,
15851 		kCGBitmapFloatComponents = (1 << 8),
15852 		kCGBitmapByteOrderMask = 0x7000,
15853 		kCGBitmapByteOrderDefault = (0 << 12),
15854 		kCGBitmapByteOrder16Little = (1 << 12),
15855 		kCGBitmapByteOrder32Little = (2 << 12),
15856 		kCGBitmapByteOrder16Big = (3 << 12),
15857 		kCGBitmapByteOrder32Big = (4 << 12)
15858 	}
15859 	enum CGPathDrawingMode {
15860 		kCGPathFill,
15861 		kCGPathEOFill,
15862 		kCGPathStroke,
15863 		kCGPathFillStroke,
15864 		kCGPathEOFillStroke
15865 	}
15866 	enum objc_AssociationPolicy : size_t {
15867 		OBJC_ASSOCIATION_ASSIGN = 0,
15868 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
15869 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
15870 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
15871 		OBJC_ASSOCIATION_COPY = 0x303 //01403
15872 	}
15873 
15874 	extern(C) {
15875 		id objc_msgSend(id receiver, SEL selector, ...);
15876 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
15877 		id objc_getClass(const(char)* name);
15878 		SEL sel_registerName(const(char)* str);
15879 		Class objc_allocateClassPair(Class superclass, const(char)* name,
15880 									 size_t extra_bytes);
15881 		void objc_registerClassPair(Class cls);
15882 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
15883 		id objc_getAssociatedObject(id object, void* key);
15884 		void objc_setAssociatedObject(id object, void* key, id value,
15885 									  objc_AssociationPolicy policy);
15886 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
15887 		id object_getIvar(id object, Ivar ivar);
15888 		void object_setIvar(id object, Ivar ivar, id value);
15889 		BOOL class_addIvar(Class cls, const(char)* name,
15890 						   size_t size, ubyte alignment, const(char)* types);
15891 
15892 		extern __gshared id NSApp;
15893 
15894 		void CFRelease(CFTypeRef obj);
15895 
15896 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
15897 											const(char)* bytes, long numBytes,
15898 											long encoding,
15899 											BOOL isExternalRepresentation);
15900 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
15901 							 char lossByte, bool isExternalRepresentation,
15902 							 char* buffer, long maxBufLen, long* usedBufLen);
15903 		long CFStringGetLength(CFStringRef theString);
15904 
15905 		CGContextRef CGBitmapContextCreate(void* data,
15906 										   size_t width, size_t height,
15907 										   size_t bitsPerComponent,
15908 										   size_t bytesPerRow,
15909 										   CGColorSpaceRef colorspace,
15910 										   CGBitmapInfo bitmapInfo);
15911 		void CGContextRelease(CGContextRef c);
15912 		ubyte* CGBitmapContextGetData(CGContextRef c);
15913 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
15914 		size_t CGBitmapContextGetWidth(CGContextRef c);
15915 		size_t CGBitmapContextGetHeight(CGContextRef c);
15916 
15917 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
15918 		void CGColorSpaceRelease(CGColorSpaceRef cs);
15919 
15920 		void CGContextSetRGBStrokeColor(CGContextRef c,
15921 										double red, double green, double blue,
15922 										double alpha);
15923 		void CGContextSetRGBFillColor(CGContextRef c,
15924 									  double red, double green, double blue,
15925 									  double alpha);
15926 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
15927 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
15928 									  const(char)* str, size_t length);
15929 		void CGContextStrokeLineSegments(CGContextRef c,
15930 										 const(CGPoint)* points, size_t count);
15931 
15932 		void CGContextBeginPath(CGContextRef c);
15933 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
15934 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
15935 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
15936 							 double startAngle, double endAngle, long clockwise);
15937 		void CGContextAddRect(CGContextRef c, CGRect rect);
15938 		void CGContextAddLines(CGContextRef c,
15939 							   const(CGPoint)* points, size_t count);
15940 		void CGContextSaveGState(CGContextRef c);
15941 		void CGContextRestoreGState(CGContextRef c);
15942 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
15943 								 ulong textEncoding);
15944 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
15945 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
15946 
15947 		void CGImageRelease(CGImageRef image);
15948 	}
15949 
15950 private:
15951     // A convenient method to create a CFString (=NSString) from a D string.
15952     CFStringRef createCFString(string str) {
15953         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
15954                                              kCFStringEncodingUTF8, false);
15955     }
15956 
15957     // Objective-C calls.
15958     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
15959         auto _cmd = sel_registerName(selector.ptr);
15960         alias extern(C) RetType function(id, SEL, T) ExpectedType;
15961         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
15962     }
15963     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
15964         auto _cmd = sel_registerName(selector.ptr);
15965         auto cls = objc_getClass(className);
15966         alias extern(C) RetType function(id, SEL, T) ExpectedType;
15967         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
15968     }
15969     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
15970         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
15971     }
15972 
15973     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
15974     alias objc_msgSend_classMethod!("alloc", id) alloc;
15975     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
15976                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
15977     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
15978     alias objc_msgSend_specialized!("center", void) center;
15979     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
15980     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
15981     alias objc_msgSend_specialized!("release", void) release;
15982     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
15983     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
15984     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
15985     alias objc_msgSend_specialized!("invalidate", void) invalidate;
15986     alias objc_msgSend_specialized!("close", void) close;
15987     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
15988                                     id, double, id, SEL, id, BOOL) scheduledTimer;
15989     alias objc_msgSend_specialized!("run", void) run;
15990     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
15991                                     id) currentNSGraphicsContext;
15992     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
15993     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
15994     alias objc_msgSend_specialized!("superclass", Class) superclass;
15995     alias objc_msgSend_specialized!("init", id) init;
15996     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
15997     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
15998     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
15999                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
16000     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
16001     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
16002     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
16003                                     void, BOOL) activateIgnoringOtherApps;
16004     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
16005                                     id) sharedNSApplication;
16006     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
16007 } else static assert(0, "Unsupported operating system");
16008 
16009 
16010 version(OSXCocoa) {
16011 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
16012 	//
16013 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
16014 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
16015 	//
16016 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
16017 	// Probably won't even fully compile right now
16018 
16019     import std.math : PI;
16020     import std.algorithm : map;
16021     import std.array : array;
16022 
16023     alias SimpleWindow NativeWindowHandle;
16024     alias void delegate(id) NativeEventHandler;
16025 
16026     __gshared Ivar simpleWindowIvar;
16027 
16028     enum KEY_ESCAPE = 27;
16029 
16030     mixin template NativeImageImplementation() {
16031         CGContextRef context;
16032         ubyte* rawData;
16033     final:
16034 
16035 	void convertToRgbaBytes(ubyte[] where) {
16036 		assert(where.length == this.width * this.height * 4);
16037 
16038 		// if rawData had a length....
16039 		//assert(rawData.length == where.length);
16040 		for(long idx = 0; idx < where.length; idx += 4) {
16041 			auto alpha = rawData[idx + 3];
16042 			if(alpha == 255) {
16043 				where[idx + 0] = rawData[idx + 0]; // r
16044 				where[idx + 1] = rawData[idx + 1]; // g
16045 				where[idx + 2] = rawData[idx + 2]; // b
16046 				where[idx + 3] = rawData[idx + 3]; // a
16047 			} else {
16048 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
16049 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
16050 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
16051 				where[idx + 3] = rawData[idx + 3]; // a
16052 
16053 			}
16054 		}
16055 	}
16056 
16057 	void setFromRgbaBytes(in ubyte[] where) {
16058 		// FIXME: this is probably wrong
16059 		assert(where.length == this.width * this.height * 4);
16060 
16061 		// if rawData had a length....
16062 		//assert(rawData.length == where.length);
16063 		for(long idx = 0; idx < where.length; idx += 4) {
16064 			auto alpha = rawData[idx + 3];
16065 			if(alpha == 255) {
16066 				rawData[idx + 0] = where[idx + 0]; // r
16067 				rawData[idx + 1] = where[idx + 1]; // g
16068 				rawData[idx + 2] = where[idx + 2]; // b
16069 				rawData[idx + 3] = where[idx + 3]; // a
16070 			} else {
16071 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
16072 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
16073 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
16074 				rawData[idx + 3] = where[idx + 3]; // a
16075 
16076 			}
16077 		}
16078 	}
16079 
16080 
16081         void createImage(int width, int height, bool forcexshm=false) {
16082             auto colorSpace = CGColorSpaceCreateDeviceRGB();
16083             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
16084                                             colorSpace,
16085                                             kCGImageAlphaPremultipliedLast
16086                                                    |kCGBitmapByteOrder32Big);
16087             CGColorSpaceRelease(colorSpace);
16088             rawData = CGBitmapContextGetData(context);
16089         }
16090         void dispose() {
16091             CGContextRelease(context);
16092         }
16093 
16094         void setPixel(int x, int y, Color c) {
16095             auto offset = (y * width + x) * 4;
16096             if (c.a == 255) {
16097                 rawData[offset + 0] = c.r;
16098                 rawData[offset + 1] = c.g;
16099                 rawData[offset + 2] = c.b;
16100                 rawData[offset + 3] = c.a;
16101             } else {
16102                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
16103                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
16104                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
16105                 rawData[offset + 3] = c.a;
16106             }
16107         }
16108     }
16109 
16110     mixin template NativeScreenPainterImplementation() {
16111         CGContextRef context;
16112         ubyte[4] _outlineComponents;
16113 	id view;
16114 
16115         void create(NativeWindowHandle window) {
16116             context = window.drawingContext;
16117 	    view = window.view;
16118         }
16119 
16120         void dispose() {
16121             	setNeedsDisplay(view, true);
16122         }
16123 
16124 	// NotYetImplementedException
16125 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
16126 	void rasterOp(RasterOp op) {}
16127 	Pen _activePen;
16128 	Color _fillColor;
16129 	Rectangle _clipRectangle;
16130 	void setClipRectangle(int, int, int, int) {}
16131 	void setFont(OperatingSystemFont) {}
16132 	int fontHeight() { return 14; }
16133 
16134 	// end
16135 
16136         void pen(Pen pen) {
16137 	    _activePen = pen;
16138 	    auto color = pen.color; // FIXME
16139             double alphaComponent = color.a/255.0f;
16140             CGContextSetRGBStrokeColor(context,
16141                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
16142 
16143             if (color.a != 255) {
16144                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
16145                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
16146                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
16147                 _outlineComponents[3] = color.a;
16148             } else {
16149                 _outlineComponents[0] = color.r;
16150                 _outlineComponents[1] = color.g;
16151                 _outlineComponents[2] = color.b;
16152                 _outlineComponents[3] = color.a;
16153             }
16154         }
16155 
16156         @property void fillColor(Color color) {
16157             CGContextSetRGBFillColor(context,
16158                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
16159         }
16160 
16161         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
16162 		// NotYetImplementedException for upper left/width/height
16163             auto cgImage = CGBitmapContextCreateImage(image.context);
16164             auto size = CGSize(CGBitmapContextGetWidth(image.context),
16165                                CGBitmapContextGetHeight(image.context));
16166             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
16167             CGImageRelease(cgImage);
16168         }
16169 
16170 	version(OSXCocoa) {} else // NotYetImplementedException
16171         void drawPixmap(Sprite image, int x, int y) {
16172 		// FIXME: is this efficient?
16173             auto cgImage = CGBitmapContextCreateImage(image.context);
16174             auto size = CGSize(CGBitmapContextGetWidth(image.context),
16175                                CGBitmapContextGetHeight(image.context));
16176             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
16177             CGImageRelease(cgImage);
16178         }
16179 
16180 
16181         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
16182 		// FIXME: alignment
16183             if (_outlineComponents[3] != 0) {
16184                 CGContextSaveGState(context);
16185                 auto invAlpha = 1.0f/_outlineComponents[3];
16186                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
16187                                                   _outlineComponents[1]*invAlpha,
16188                                                   _outlineComponents[2]*invAlpha,
16189                                                   _outlineComponents[3]/255.0f);
16190                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
16191 // auto cfstr = cast(id)createCFString(text);
16192 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
16193 // NSPoint(x, y), null);
16194 // CFRelease(cfstr);
16195                 CGContextRestoreGState(context);
16196             }
16197         }
16198 
16199         void drawPixel(int x, int y) {
16200             auto rawData = CGBitmapContextGetData(context);
16201             auto width = CGBitmapContextGetWidth(context);
16202             auto height = CGBitmapContextGetHeight(context);
16203             auto offset = ((height - y - 1) * width + x) * 4;
16204             rawData[offset .. offset+4] = _outlineComponents;
16205         }
16206 
16207         void drawLine(int x1, int y1, int x2, int y2) {
16208             CGPoint[2] linePoints;
16209             linePoints[0] = CGPoint(x1, y1);
16210             linePoints[1] = CGPoint(x2, y2);
16211             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
16212         }
16213 
16214         void drawRectangle(int x, int y, int width, int height) {
16215             CGContextBeginPath(context);
16216             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
16217             CGContextAddRect(context, rect);
16218             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
16219         }
16220 
16221         void drawEllipse(int x1, int y1, int x2, int y2) {
16222             CGContextBeginPath(context);
16223             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
16224             CGContextAddEllipseInRect(context, rect);
16225             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
16226         }
16227 
16228         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
16229             // @@@BUG@@@ Does not support elliptic arc (width != height).
16230             CGContextBeginPath(context);
16231             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
16232                             start*PI/(180*64), finish*PI/(180*64), 0);
16233             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
16234         }
16235 
16236         void drawPolygon(Point[] intPoints) {
16237             CGContextBeginPath(context);
16238             auto points = array(map!(CGPoint.fromTuple)(intPoints));
16239             CGContextAddLines(context, points.ptr, points.length);
16240             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
16241         }
16242     }
16243 
16244     mixin template NativeSimpleWindowImplementation() {
16245         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
16246             synchronized {
16247                 if (NSApp == null) initializeApp();
16248             }
16249 
16250             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
16251 
16252             // create the window.
16253             window = initWithContentRect(alloc("NSWindow"),
16254                                          contentRect,
16255                                          NSTitledWindowMask
16256                                             |NSClosableWindowMask
16257                                             |NSMiniaturizableWindowMask
16258                                             |NSResizableWindowMask,
16259                                          NSBackingStoreBuffered,
16260                                          true);
16261 
16262             // set the title & move the window to center.
16263             auto windowTitle = createCFString(title);
16264             setTitle(window, windowTitle);
16265             CFRelease(windowTitle);
16266             center(window);
16267 
16268             // create area to draw on.
16269             auto colorSpace = CGColorSpaceCreateDeviceRGB();
16270             drawingContext = CGBitmapContextCreate(null, width, height,
16271                                                    8, 4*width, colorSpace,
16272                                                    kCGImageAlphaPremultipliedLast
16273                                                       |kCGBitmapByteOrder32Big);
16274             CGColorSpaceRelease(colorSpace);
16275             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
16276             auto matrix = CGContextGetTextMatrix(drawingContext);
16277             matrix.c = -matrix.c;
16278             matrix.d = -matrix.d;
16279             CGContextSetTextMatrix(drawingContext, matrix);
16280 
16281             // create the subview that things will be drawn on.
16282             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
16283             setContentView(window, view);
16284             object_setIvar(view, simpleWindowIvar, cast(id)this);
16285             release(view);
16286 
16287             setBackgroundColor(window, whiteNSColor);
16288             makeKeyAndOrderFront(window, null);
16289         }
16290         void dispose() {
16291             closeWindow();
16292             release(window);
16293         }
16294         void closeWindow() {
16295             invalidate(timer);
16296             .close(window);
16297         }
16298 
16299         ScreenPainter getPainter() {
16300 		return ScreenPainter(this, this);
16301 	}
16302 
16303         id window;
16304         id timer;
16305         id view;
16306         CGContextRef drawingContext;
16307     }
16308 
16309     extern(C) {
16310     private:
16311         BOOL returnTrue3(id self, SEL _cmd, id app) {
16312             return true;
16313         }
16314         BOOL returnTrue2(id self, SEL _cmd) {
16315             return true;
16316         }
16317 
16318         void pulse(id self, SEL _cmd) {
16319             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
16320             simpleWindow.handlePulse();
16321             setNeedsDisplay(self, true);
16322         }
16323         void drawRect(id self, SEL _cmd, NSRect rect) {
16324             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
16325             auto curCtx = graphicsPort(currentNSGraphicsContext);
16326             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
16327             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
16328                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
16329             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
16330             CGImageRelease(cgImage);
16331         }
16332         void keyDown(id self, SEL _cmd, id event) {
16333             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
16334 
16335             // the event may have multiple characters, and we send them all at
16336             // once.
16337             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
16338                 auto chars = characters(event);
16339                 auto range = CFRange(0, CFStringGetLength(chars));
16340                 auto buffer = new char[range.length*3];
16341                 long actualLength;
16342                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
16343                                  buffer.ptr, cast(int) buffer.length, &actualLength);
16344                 foreach (dchar dc; buffer[0..actualLength]) {
16345                     if (simpleWindow.handleCharEvent)
16346                         simpleWindow.handleCharEvent(dc);
16347 		    // NotYetImplementedException
16348                     //if (simpleWindow.handleKeyEvent)
16349                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
16350                 }
16351             }
16352 
16353             // the event's 'keyCode' is hardware-dependent. I don't think people
16354             // will like it. Let's leave it to the native handler.
16355 
16356             // perform the default action.
16357 
16358 	    // so the default action is to make a bomp sound and i dont want that
16359 	    // sooooooooo yeah not gonna do that.
16360 
16361             //auto superData = objc_super(self, superclass(self));
16362             //alias extern(C) void function(objc_super*, SEL, id) T;
16363             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
16364         }
16365     }
16366 
16367     // initialize the app so that it can be interacted with the user.
16368     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
16369     private void initializeApp() {
16370         // push an autorelease pool to avoid leaking.
16371         init(alloc("NSAutoreleasePool"));
16372 
16373         // create a new NSApp instance
16374         sharedNSApplication;
16375         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
16376 
16377         // create the "Quit" menu.
16378         auto menuBar = init(alloc("NSMenu"));
16379         auto appMenuItem = init(alloc("NSMenuItem"));
16380         addItem(menuBar, appMenuItem);
16381         setMainMenu(NSApp, menuBar);
16382         release(appMenuItem);
16383         release(menuBar);
16384 
16385         auto appMenu = init(alloc("NSMenu"));
16386         auto quitTitle = createCFString("Quit");
16387         auto q = createCFString("q");
16388         auto quitItem = initWithTitle(alloc("NSMenuItem"),
16389                                       quitTitle, sel_registerName("terminate:"), q);
16390         addItem(appMenu, quitItem);
16391         setSubmenu(appMenuItem, appMenu);
16392         release(quitItem);
16393         release(appMenu);
16394         CFRelease(q);
16395         CFRelease(quitTitle);
16396 
16397         // assign a delegate for the application, allow it to quit when the last
16398         // window is closed.
16399         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
16400                                                     "SDWindowCloseDelegate", 0);
16401         class_addMethod(delegateClass,
16402                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
16403                         &returnTrue3, "c@:@");
16404         objc_registerClassPair(delegateClass);
16405 
16406         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
16407         setDelegate(NSApp, appDelegate);
16408         activateIgnoringOtherApps(NSApp, true);
16409 
16410         // create a new view that draws the graphics and respond to keyDown
16411         // events.
16412         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
16413                                                 "SDGraphicsView", (void*).sizeof);
16414         class_addIvar(viewClass, "simpledisplay_simpleWindow",
16415                       (void*).sizeof, (void*).alignof, "^v");
16416         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
16417                         &pulse, "v@:");
16418         class_addMethod(viewClass, sel_registerName("drawRect:"),
16419                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
16420         class_addMethod(viewClass, sel_registerName("isFlipped"),
16421                         &returnTrue2, "c@:");
16422         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
16423                         &returnTrue2, "c@:");
16424         class_addMethod(viewClass, sel_registerName("keyDown:"),
16425                         &keyDown, "v@:@");
16426         objc_registerClassPair(viewClass);
16427         simpleWindowIvar = class_getInstanceVariable(viewClass,
16428                                                      "simpledisplay_simpleWindow");
16429     }
16430 }
16431 
16432 version(without_opengl) {} else
16433 extern(System) nothrow @nogc {
16434 	//enum uint GL_VERSION = 0x1F02;
16435 	//const(char)* glGetString (/*GLenum*/uint);
16436 	version(X11) {
16437 	static if (!SdpyIsUsingIVGLBinds) {
16438 
16439 		enum GLX_X_RENDERABLE = 0x8012;
16440 		enum GLX_DRAWABLE_TYPE = 0x8010;
16441 		enum GLX_RENDER_TYPE = 0x8011;
16442 		enum GLX_X_VISUAL_TYPE = 0x22;
16443 		enum GLX_TRUE_COLOR = 0x8002;
16444 		enum GLX_WINDOW_BIT = 0x00000001;
16445 		enum GLX_RGBA_BIT = 0x00000001;
16446 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
16447 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
16448 		enum GLX_SAMPLES = 0x186a1;
16449 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
16450 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
16451 	}
16452 
16453 		// GLX_EXT_swap_control
16454 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
16455 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
16456 
16457 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
16458 		extern(System) {
16459 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
16460 		}
16461 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
16462 
16463 		// this made public so we don't have to get it again and again
16464 		public bool glXCreateContextAttribsARB_present () {
16465 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
16466 				// get it
16467 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
16468 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
16469 			}
16470 			return (glXCreateContextAttribsARBFn !is null);
16471 		}
16472 
16473 		// this made public so we don't have to get it again and again
16474 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
16475 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
16476 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
16477 		}
16478 
16479 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
16480 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
16481 
16482 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
16483 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
16484 			if (_glx_swapInterval_fn is null) {
16485 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
16486 				if (_glx_swapInterval_fn is null) {
16487 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
16488 					return;
16489 				}
16490 				version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); }
16491 			}
16492 
16493 			if(glXSwapIntervalMESA is null) {
16494 				// it seems to require both to actually take effect on many computers
16495 				// idk why
16496 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
16497 				if(glXSwapIntervalMESA is null)
16498 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
16499 			}
16500 
16501 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
16502 				glXSwapIntervalMESA(wait ? 1 : 0);
16503 
16504 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
16505 		}
16506 	} else version(Windows) {
16507 	static if (!SdpyIsUsingIVGLBinds) {
16508 	enum GL_TRUE = 1;
16509 	enum GL_FALSE = 0;
16510 	alias int GLint;
16511 
16512 	public void* glbindGetProcAddress (const(char)* name) {
16513 		void* res = wglGetProcAddress(name);
16514 		if (res is null) {
16515 			/+
16516 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
16517 			import core.sys.windows.windef, core.sys.windows.winbase;
16518 			__gshared HINSTANCE dll = null;
16519 			if (dll is null) {
16520 				dll = LoadLibraryA("opengl32.dll");
16521 				if (dll is null) return null; // <32, but idc
16522 			}
16523 			res = GetProcAddress(dll, name);
16524 			+/
16525 			res = GetProcAddress(gl.libHandle, name);
16526 		}
16527 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
16528 		return res;
16529 	}
16530 	}
16531 
16532  
16533  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
16534         void wglSetVSync(bool wait) {
16535 		if(wglSwapIntervalEXT is null) {
16536 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
16537 			if(wglSwapIntervalEXT is null)
16538 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
16539 		}
16540 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
16541 			return;
16542 
16543 		wglSwapIntervalEXT(wait ? 1 : 0);
16544 	}
16545 
16546 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
16547 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
16548 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
16549 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
16550 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
16551 
16552 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
16553 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
16554 
16555 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
16556 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
16557 
16558 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
16559 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
16560 
16561 		void wglInitOtherFunctions () {
16562 			if (wglCreateContextAttribsARB is null) {
16563 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
16564 			}
16565 		}
16566 	}
16567 
16568 	static if (!SdpyIsUsingIVGLBinds) {
16569 
16570 	interface GL {
16571 		extern(System) @nogc nothrow:
16572 
16573 		void glGetIntegerv(int, void*);
16574 		void glMatrixMode(int);
16575 		void glPushMatrix();
16576 		void glLoadIdentity();
16577 		void glOrtho(double, double, double, double, double, double);
16578 		void glFrustum(double, double, double, double, double, double);
16579 
16580 		void glPopMatrix();
16581 		void glEnable(int);
16582 		void glDisable(int);
16583 		void glClear(int);
16584 		void glBegin(int);
16585 		void glVertex2f(float, float);
16586 		void glVertex3f(float, float, float);
16587 		void glEnd();
16588 		void glColor3b(byte, byte, byte);
16589 		void glColor3ub(ubyte, ubyte, ubyte);
16590 		void glColor4b(byte, byte, byte, byte);
16591 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
16592 		void glColor3i(int, int, int);
16593 		void glColor3ui(uint, uint, uint);
16594 		void glColor4i(int, int, int, int);
16595 		void glColor4ui(uint, uint, uint, uint);
16596 		void glColor3f(float, float, float);
16597 		void glColor4f(float, float, float, float);
16598 		void glTranslatef(float, float, float);
16599 		void glScalef(float, float, float);
16600 		version(X11) {
16601 			void glSecondaryColor3b(byte, byte, byte);
16602 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
16603 			void glSecondaryColor3i(int, int, int);
16604 			void glSecondaryColor3ui(uint, uint, uint);
16605 			void glSecondaryColor3f(float, float, float);
16606 		}
16607 
16608 		void glDrawElements(int, int, int, void*);
16609 
16610 		void glRotatef(float, float, float, float);
16611 
16612 		uint glGetError();
16613 
16614 		void glDeleteTextures(int, uint*);
16615 
16616 
16617 		void glRasterPos2i(int, int);
16618 		void glDrawPixels(int, int, uint, uint, void*);
16619 		void glClearColor(float, float, float, float);
16620 
16621 
16622 		void glPixelStorei(uint, int);
16623 
16624 		void glGenTextures(uint, uint*);
16625 		void glBindTexture(int, int);
16626 		void glTexParameteri(uint, uint, int);
16627 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
16628 		void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
16629 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
16630 			/*GLsizei*/int width, /*GLsizei*/int height,
16631 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
16632 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
16633 
16634 		void glLineWidth(int);
16635 
16636 
16637 		void glTexCoord2f(float, float);
16638 		void glVertex2i(int, int);
16639 		void glBlendFunc (int, int);
16640 		void glDepthFunc (int);
16641 		void glViewport(int, int, int, int);
16642 
16643 		void glClearDepth(double);
16644 
16645 		void glReadBuffer(uint);
16646 		void glReadPixels(int, int, int, int, int, int, void*);
16647 
16648 		void glFlush();
16649 		void glFinish();
16650 
16651 		version(Windows) {
16652 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
16653 			HGLRC wglCreateContext(HDC);
16654 			HGLRC wglCreateLayerContext(HDC, int);
16655 			BOOL wglDeleteContext(HGLRC);
16656 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
16657 			HGLRC wglGetCurrentContext();
16658 			HDC wglGetCurrentDC();
16659 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
16660 			PROC wglGetProcAddress(LPCSTR);
16661 			BOOL wglMakeCurrent(HDC, HGLRC);
16662 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
16663 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
16664 			BOOL wglShareLists(HGLRC, HGLRC);
16665 			BOOL wglSwapLayerBuffers(HDC, UINT);
16666 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
16667 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
16668 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
16669 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
16670 		}
16671 
16672 	}
16673 
16674 	interface GL3 {
16675 		extern(System) @nogc nothrow:
16676 
16677 		void glGenVertexArrays(GLsizei, GLuint*);
16678 		void glBindVertexArray(GLuint);
16679 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
16680 		void glGenerateMipmap(GLenum);
16681 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
16682 		void glStencilMask(GLuint);
16683 		void glStencilFunc(GLenum, GLint, GLuint);
16684 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
16685 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
16686 		GLuint glCreateProgram();
16687 		GLuint glCreateShader(GLenum);
16688 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
16689 		void glCompileShader(GLuint);
16690 		void glGetShaderiv(GLuint, GLenum, GLint*);
16691 		void glAttachShader(GLuint, GLuint);
16692 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
16693 		void glLinkProgram(GLuint);
16694 		void glGetProgramiv(GLuint, GLenum, GLint*);
16695 		void glDeleteProgram(GLuint);
16696 		void glDeleteShader(GLuint);
16697 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
16698 		void glGenBuffers(GLsizei, GLuint*);
16699 		void glUniform4fv(GLint, GLsizei, const(GLfloat)*);
16700 		void glUniform4f(GLint, float, float, float, float);
16701 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
16702 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
16703 		void glDrawArrays(GLenum, GLint, GLsizei);
16704 		void glStencilOp(GLenum, GLenum, GLenum);
16705 		void glUseProgram(GLuint);
16706 		void glCullFace(GLenum);
16707 		void glFrontFace(GLenum);
16708 		void glActiveTexture(GLenum);
16709 		void glBindBuffer(GLenum, GLuint);
16710 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
16711 		void glEnableVertexAttribArray(GLuint);
16712 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
16713 		void glUniform1i(GLint, GLint);
16714 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
16715 		void glDisableVertexAttribArray(GLuint);
16716 		void glDeleteBuffers(GLsizei, const(GLuint)*);
16717 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
16718 		void glLogicOp (GLenum opcode);
16719 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
16720 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
16721 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
16722 		GLenum glCheckFramebufferStatus (GLenum target);
16723 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
16724 	}
16725 
16726 	interface GL4 {
16727 		extern(System) @nogc nothrow:
16728 
16729 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
16730 			/*GLsizei*/int width, /*GLsizei*/int height,
16731 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
16732 	}
16733 
16734 	interface GLU {
16735 		extern(System) @nogc nothrow:
16736 
16737 		void gluLookAt(double, double, double, double, double, double, double, double, double);
16738 		void gluPerspective(double, double, double, double);
16739 
16740 		char* gluErrorString(uint);
16741 	}
16742 
16743 
16744 	enum GL_RED = 0x1903;
16745 	enum GL_ALPHA = 0x1906;
16746 
16747 	enum uint GL_FRONT = 0x0404;
16748 
16749 	enum uint GL_BLEND = 0x0be2;
16750 	enum uint GL_LEQUAL = 0x0203;
16751 
16752 
16753 	enum uint GL_RGB = 0x1907;
16754 	enum uint GL_BGRA = 0x80e1;
16755 	enum uint GL_RGBA = 0x1908;
16756 	enum uint GL_TEXTURE_2D =   0x0DE1;
16757 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
16758 	enum uint GL_NEAREST = 0x2600;
16759 	enum uint GL_LINEAR = 0x2601;
16760 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
16761 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
16762 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
16763 	enum uint GL_REPEAT = 0x2901;
16764 	enum uint GL_CLAMP = 0x2900;
16765 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
16766 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
16767 	enum uint GL_DECAL = 0x2101;
16768 	enum uint GL_MODULATE = 0x2100;
16769 	enum uint GL_TEXTURE_ENV = 0x2300;
16770 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
16771 	enum uint GL_REPLACE = 0x1E01;
16772 	enum uint GL_LIGHTING = 0x0B50;
16773 	enum uint GL_DITHER = 0x0BD0;
16774 
16775 	enum uint GL_NO_ERROR = 0;
16776 
16777 
16778 
16779 	enum int GL_VIEWPORT = 0x0BA2;
16780 	enum int GL_MODELVIEW = 0x1700;
16781 	enum int GL_TEXTURE = 0x1702;
16782 	enum int GL_PROJECTION = 0x1701;
16783 	enum int GL_DEPTH_TEST = 0x0B71;
16784 
16785 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
16786 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
16787 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
16788 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
16789 
16790 	enum int GL_POINTS = 0x0000;
16791 	enum int GL_LINES =  0x0001;
16792 	enum int GL_LINE_LOOP = 0x0002;
16793 	enum int GL_LINE_STRIP = 0x0003;
16794 	enum int GL_TRIANGLES = 0x0004;
16795 	enum int GL_TRIANGLE_STRIP = 5;
16796 	enum int GL_TRIANGLE_FAN = 6;
16797 	enum int GL_QUADS = 7;
16798 	enum int GL_QUAD_STRIP = 8;
16799 	enum int GL_POLYGON = 9;
16800 
16801 	alias GLvoid = void;
16802 	alias GLboolean = ubyte;
16803 	alias GLuint = uint;
16804 	alias GLenum = uint;
16805 	alias GLchar = char;
16806 	alias GLsizei = int;
16807 	alias GLfloat = float;
16808 	alias GLintptr = size_t;
16809 	alias GLsizeiptr = ptrdiff_t;
16810 
16811 
16812 	enum uint GL_INVALID_ENUM = 0x0500;
16813 
16814 	enum uint GL_ZERO = 0;
16815 	enum uint GL_ONE = 1;
16816 
16817 	enum uint GL_BYTE = 0x1400;
16818 	enum uint GL_UNSIGNED_BYTE = 0x1401;
16819 	enum uint GL_SHORT = 0x1402;
16820 	enum uint GL_UNSIGNED_SHORT = 0x1403;
16821 	enum uint GL_INT = 0x1404;
16822 	enum uint GL_UNSIGNED_INT = 0x1405;
16823 	enum uint GL_FLOAT = 0x1406;
16824 	enum uint GL_2_BYTES = 0x1407;
16825 	enum uint GL_3_BYTES = 0x1408;
16826 	enum uint GL_4_BYTES = 0x1409;
16827 	enum uint GL_DOUBLE = 0x140A;
16828 
16829 	enum uint GL_STREAM_DRAW = 0x88E0;
16830 
16831 	enum uint GL_CCW = 0x0901;
16832 
16833 	enum uint GL_STENCIL_TEST = 0x0B90;
16834 	enum uint GL_SCISSOR_TEST = 0x0C11;
16835 
16836 	enum uint GL_EQUAL = 0x0202;
16837 	enum uint GL_NOTEQUAL = 0x0205;
16838 
16839 	enum uint GL_ALWAYS = 0x0207;
16840 	enum uint GL_KEEP = 0x1E00;
16841 
16842 	enum uint GL_INCR = 0x1E02;
16843 
16844 	enum uint GL_INCR_WRAP = 0x8507;
16845 	enum uint GL_DECR_WRAP = 0x8508;
16846 
16847 	enum uint GL_CULL_FACE = 0x0B44;
16848 	enum uint GL_BACK = 0x0405;
16849 
16850 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
16851 	enum uint GL_VERTEX_SHADER = 0x8B31;
16852 
16853 	enum uint GL_COMPILE_STATUS = 0x8B81;
16854 	enum uint GL_LINK_STATUS = 0x8B82;
16855 
16856 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
16857 
16858 	enum uint GL_STATIC_DRAW = 0x88E4;
16859 
16860 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
16861 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
16862 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
16863 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
16864 
16865 	enum uint GL_GENERATE_MIPMAP = 0x8191;
16866 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
16867 
16868 	enum uint GL_TEXTURE0 = 0x84C0U;
16869 	enum uint GL_TEXTURE1 = 0x84C1U;
16870 
16871 	enum uint GL_ARRAY_BUFFER = 0x8892;
16872 
16873 	enum uint GL_SRC_COLOR = 0x0300;
16874 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
16875 	enum uint GL_SRC_ALPHA = 0x0302;
16876 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
16877 	enum uint GL_DST_ALPHA = 0x0304;
16878 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
16879 	enum uint GL_DST_COLOR = 0x0306;
16880 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
16881 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
16882 
16883 	enum uint GL_INVERT = 0x150AU;
16884 
16885 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
16886 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
16887 
16888 	enum uint GL_FRAMEBUFFER = 0x8D40U;
16889 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
16890 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
16891 
16892 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
16893 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
16894 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
16895 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
16896 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
16897 
16898 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
16899 	enum uint GL_CLEAR = 0x1500U;
16900 	enum uint GL_COPY = 0x1503U;
16901 	enum uint GL_XOR = 0x1506U;
16902 
16903 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
16904 
16905 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
16906 
16907 	}
16908 }
16909 
16910 version(without_opengl) {} else {
16911 static if(!SdpyIsUsingIVGLBinds) {
16912 	version(Windows) {
16913 		mixin DynamicLoad!(GL, "opengl32", 1, true) gl;
16914 		mixin DynamicLoad!(GLU, "glu32", 1, true) glu;
16915 	} else {
16916 		mixin DynamicLoad!(GL, "GL", 1, true) gl;
16917 		mixin DynamicLoad!(GLU, "GLU", 3, true) glu;
16918 	}
16919 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
16920 
16921 
16922 	shared static this() {
16923 		gl.loadDynamicLibrary();
16924 		glu.loadDynamicLibrary();
16925 	}
16926 }
16927 }
16928 
16929 /++
16930 	Convenience method for converting D arrays to opengl buffer data
16931 
16932 	I would LOVE to overload it with the original glBufferData, but D won't
16933 	let me since glBufferData is a function pointer :(
16934 
16935 	Added: August 25, 2020 (version 8.5)
16936 +/
16937 version(without_opengl) {} else
16938 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
16939 	glBufferData(target, data.length, data.ptr, usage);
16940 }
16941 
16942 /+
16943 /++
16944 	A matrix for simple uses that easily integrates with [OpenGlShader].
16945 
16946 	Might not be useful to you since it only as some simple functions and
16947 	probably isn't that fast.
16948 
16949 	Note it uses an inline static array for its storage, so copying it
16950 	may be expensive.
16951 +/
16952 struct BasicMatrix(int columns, int rows, T = float) {
16953 	import core.stdc.math;
16954 
16955 	T[columns * rows] data = 0.0;
16956 
16957 	/++
16958 		Basic operations that operate *in place*.
16959 	+/
16960 	void translate() {
16961 
16962 	}
16963 
16964 	/// ditto
16965 	void scale() {
16966 
16967 	}
16968 
16969 	/// ditto
16970 	void rotate() {
16971 
16972 	}
16973 
16974 	/++
16975 
16976 	+/
16977 	static if(columns == rows)
16978 	static BasicMatrix identity() {
16979 		BasicMatrix m;
16980 		foreach(i; 0 .. columns)
16981 			data[0 + i + i * columns] = 1.0;
16982 		return m;
16983 	}
16984 
16985 	static BasicMatrix ortho() {
16986 		return BasicMatrix.init;
16987 	}
16988 }
16989 +/
16990 
16991 /++
16992 	Convenience class for using opengl shaders.
16993 
16994 	Ensure that you've loaded opengl 3+ and set your active
16995 	context before trying to use this.
16996 
16997 	Added: August 25, 2020 (version 8.5)
16998 +/
16999 version(without_opengl) {} else
17000 final class OpenGlShader {
17001 	private int shaderProgram_;
17002 	private @property void shaderProgram(int a) {
17003 		shaderProgram_ = a;
17004 	}
17005 	/// Get the program ID for use in OpenGL functions.
17006 	public @property int shaderProgram() {
17007 		return shaderProgram_;
17008 	}
17009 
17010 	/++
17011 
17012 	+/
17013 	static struct Source {
17014 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
17015 		string code; ///
17016 	}
17017 
17018 	/++
17019 		Helper method to just compile some shader code and check for errors
17020 		while you do glCreateShader, etc. on the outside yourself.
17021 
17022 		This just does `glShaderSource` and `glCompileShader` for the given code.
17023 
17024 		If you the OpenGlShader class constructor, you never need to call this yourself.
17025 	+/
17026 	static void compile(int sid, Source code) {
17027 		const(char)*[1] buffer;
17028 		int[1] lengthBuffer;
17029 
17030 		buffer[0] = code.code.ptr;
17031 		lengthBuffer[0] = cast(int) code.code.length;
17032 
17033 		glShaderSource(sid, 1, buffer.ptr, lengthBuffer.ptr);
17034 		glCompileShader(sid);
17035 
17036 		int success;
17037 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
17038 		if(!success) {
17039 			char[512] info;
17040 			int len;
17041 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
17042 
17043 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
17044 		}
17045 	}
17046 
17047 	/++
17048 		Calls `glLinkProgram` and throws if error a occurs.
17049 
17050 		If you the OpenGlShader class constructor, you never need to call this yourself.
17051 	+/
17052 	static void link(int shaderProgram) {
17053 		glLinkProgram(shaderProgram);
17054 		int success;
17055 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
17056 		if(!success) {
17057 			char[512] info;
17058 			int len;
17059 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
17060 
17061 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
17062 		}
17063 	}
17064 
17065 	/++
17066 		Constructs the shader object by calling `glCreateProgram`, then
17067 		compiling each given [Source], and finally, linking them together.
17068 
17069 		Throws: on compile or link failure.
17070 	+/
17071 	this(Source[] codes...) {
17072 		shaderProgram = glCreateProgram();
17073 
17074 		int[16] shadersBufferStack;
17075 
17076 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 
17077 			shadersBufferStack[0 .. codes.length] :
17078 			new int[](codes.length);
17079 
17080 		foreach(idx, code; codes) {
17081 			shadersBuffer[idx] = glCreateShader(code.type);
17082 
17083 			compile(shadersBuffer[idx], code);
17084 
17085 			glAttachShader(shaderProgram, shadersBuffer[idx]);
17086 		}
17087 
17088 		link(shaderProgram);
17089 
17090 		foreach(s; shadersBuffer)
17091 			glDeleteShader(s);
17092 	}
17093 
17094 	/// Calls `glUseProgram(this.shaderProgram)`
17095 	void use() {
17096 		glUseProgram(this.shaderProgram);
17097 	}
17098 
17099 	/// Deletes the program.
17100 	void delete_() {
17101 		glDeleteProgram(shaderProgram);
17102 		shaderProgram = 0;
17103 	}
17104 
17105 	/++
17106 		[OpenGlShader.uniforms].name gives you one of these.
17107 
17108 		You can get the id out of it or just assign
17109 	+/
17110 	static struct Uniform {
17111 		/// the id passed to glUniform*
17112 		int id;
17113 
17114 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
17115 		void opAssign(float x, float y, float z, float w) {
17116 			glUniform4f(id, x, y, z, w);
17117 		}
17118 	}
17119 
17120 	static struct UniformsHelper {
17121 		OpenGlShader _shader;
17122 
17123 		@property Uniform opDispatch(string name)() {
17124 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
17125 			if(i == -1)
17126 				throw new Exception("Could not find uniform " ~ name);
17127 			return Uniform(i);
17128 		}
17129 	}
17130 
17131 	/++
17132 		Gives access to the uniforms through dot access.
17133 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
17134 	+/
17135 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
17136 }
17137 
17138 version(linux) {
17139 	version(with_eventloop) {} else {
17140 		private int epollFd = -1;
17141 		void prepareEventLoop() {
17142 			if(epollFd != -1)
17143 				return; // already initialized, no need to do it again
17144 			import ep = core.sys.linux.epoll;
17145 
17146 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
17147 			if(epollFd == -1)
17148 				throw new Exception("epoll create failure");
17149 		}
17150 	}
17151 } else version(Posix) {
17152 	void prepareEventLoop() {}
17153 }
17154 
17155 version(X11) {
17156 	import core.stdc.locale : LC_ALL; // rdmd fix
17157 	__gshared bool sdx_isUTF8Locale;
17158 
17159 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
17160 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
17161 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
17162 	// anal magic is here. I (Ketmar) hope you like it.
17163 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
17164 	// always return correct unicode symbols. The detection is here 'cause user can change locale
17165 	// later.
17166 
17167 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
17168 	shared static this () {
17169 		if(!librariesSuccessfullyLoaded)
17170 			return;
17171 
17172 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
17173 
17174 		// this doesn't hurt; it may add some locking, but the speed is still
17175 		// allows doing 60 FPS videogames; also, ignore the result, as most
17176 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
17177 		// never seen this failing).
17178 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
17179 
17180 		setlocale(LC_ALL, "");
17181 		// check if out locale is UTF-8
17182 		auto lct = setlocale(LC_CTYPE, null);
17183 		if (lct is null) {
17184 			sdx_isUTF8Locale = false;
17185 		} else {
17186 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
17187 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
17188 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
17189 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
17190 				{
17191 					sdx_isUTF8Locale = true;
17192 					break;
17193 				}
17194 			}
17195 		}
17196 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
17197 	}
17198 }
17199 
17200 class ExperimentalTextComponent2 {
17201 	/+
17202 		Stage 1: get it working monospace
17203 		Stage 2: use proportional font
17204 		Stage 3: allow changes in inline style
17205 		Stage 4: allow new fonts and sizes in the middle
17206 		Stage 5: optimize gap buffer
17207 		Stage 6: optimize layout
17208 		Stage 7: word wrap
17209 		Stage 8: justification
17210 		Stage 9: editing, selection, etc.
17211 
17212 			Operations:
17213 				insert text
17214 				overstrike text
17215 				select
17216 				cut
17217 				modify
17218 	+/
17219 
17220 	/++
17221 		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.
17222 	+/
17223 	this(SimpleWindow window) {
17224 		this.window = window;
17225 	}
17226 
17227 	private SimpleWindow window;
17228 
17229 
17230 	/++
17231 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
17232 		representing the internal parts. The first pass is focused on the x parameter, then the
17233 		renderer is responsible for going back to the parts in the current line and calling
17234 		adjustDownForAscent to change the y params.
17235 	+/
17236 	static interface ComponentRenderHelper {
17237 
17238 		/+
17239 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
17240 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
17241 			to move (adjust y to make room for new line) until you get back to the same position,
17242 			then you can stop - if one thing is unchanged, nothing after it is changed too.
17243 
17244 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
17245 			once you reach something that is unchanged, you can stop.
17246 		+/
17247 
17248 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
17249 
17250 		int ascent() const;
17251 		int descent() const;
17252 
17253 		int advance() const;
17254 
17255 		bool endsWithExplititLineBreak() const;
17256 	}
17257 
17258 	static interface RenderResult {
17259 		/++
17260 			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.
17261 		+/
17262 		void popFront();
17263 		@property bool empty() const;
17264 		@property ComponentRenderHelper front() const;
17265 
17266 		void repositionForNextLine(Point baseline, int availableWidth);
17267 	}
17268 
17269 	static interface ComponentInFlow {
17270 		void draw(ScreenPainter painter);
17271 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
17272 
17273 		bool startsWithExplicitLineBreak() const;
17274 	}
17275 
17276 	static class TextFlowComponent : ComponentInFlow {
17277 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
17278 
17279 		Color foreground;
17280 		Color background;
17281 
17282 		OperatingSystemFont font; // should NEVER be null
17283 
17284 		ubyte attributes; // underline, strike through, display on new block
17285 
17286 		version(Windows)
17287 			const(wchar)[] content;
17288 		else
17289 			const(char)[] content; // this should NEVER have a newline, except at the end
17290 
17291 		RenderedComponent[] rendered; // entirely controlled by [rerender]
17292 
17293 		// could prolly put some spacing around it too like margin / padding
17294 
17295 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
17296 			in { assert(font !is null);
17297 			     assert(!font.isNull); }
17298 			do
17299 		{
17300 			this.foreground = f;
17301 			this.background = b;
17302 			this.font = font;
17303 
17304 			this.attributes = attr;
17305 			version(Windows) {
17306 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
17307 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
17308 				auto buffer = new wchar[](sz);
17309 				this.content = makeWindowsString(c, buffer, conversionFlags);
17310 			} else {
17311 				this.content = c.dup;
17312 			}
17313 		}
17314 
17315 		void draw(ScreenPainter painter) {
17316 			painter.setFont(this.font);
17317 			painter.outlineColor = this.foreground;
17318 			painter.fillColor = Color.transparent;
17319 			foreach(rendered; this.rendered) {
17320 				// the component works in term of baseline,
17321 				// but the painter works in term of upper left bounding box
17322 				// so need to translate that
17323 
17324 				if(this.background.a) {
17325 					painter.fillColor = this.background;
17326 					painter.outlineColor = this.background;
17327 
17328 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
17329 
17330 					painter.outlineColor = this.foreground;
17331 					painter.fillColor = Color.transparent;
17332 				}
17333 
17334 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
17335 
17336 				// FIXME: strike through, underline, highlight selection, etc.
17337 			}
17338 		}
17339 	}
17340 
17341 	// I could split the parts into words on render
17342 	// for easier word-wrap, each one being an unbreakable "inline-block"
17343 	private TextFlowComponent[] parts;
17344 	private int needsRerenderFrom;
17345 
17346 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
17347 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
17348 		parts ~= new TextFlowComponent(f, b, font, attr, c);
17349 	}
17350 
17351 	static struct RenderedComponent {
17352 		int startX;
17353 		int startY;
17354 		short width;
17355 		// 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!
17356 		// for individual chars in here you've gotta process on demand
17357 		version(Windows)
17358 			const(wchar)[] slice;
17359 		else
17360 			const(char)[] slice;
17361 	}
17362 
17363 
17364 	void rerender(Rectangle boundingBox) {
17365 		Point baseline = boundingBox.upperLeft;
17366 
17367 		this.boundingBox.left = boundingBox.left;
17368 		this.boundingBox.top = boundingBox.top;
17369 
17370 		auto remainingParts = parts;
17371 
17372 		int largestX;
17373 
17374 
17375 		foreach(part; parts)
17376 			part.font.prepareContext(window);
17377 		scope(exit)
17378 		foreach(part; parts)
17379 			part.font.releaseContext();
17380 
17381 		calculateNextLine:
17382 
17383 		int nextLineHeight = 0;
17384 		int nextBiggestDescent = 0;
17385 
17386 		foreach(part; remainingParts) {
17387 			auto height = part.font.ascent;
17388 			if(height > nextLineHeight)
17389 				nextLineHeight = height;
17390 			if(part.font.descent > nextBiggestDescent)
17391 				nextBiggestDescent = part.font.descent;
17392 			if(part.content.length && part.content[$-1] == '\n')
17393 				break;
17394 		}
17395 
17396 		baseline.y += nextLineHeight;
17397 		auto lineStart = baseline;
17398 
17399 		while(remainingParts.length) {
17400 			remainingParts[0].rendered = null;
17401 
17402 			bool eol;
17403 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
17404 				eol = true;
17405 
17406 			// FIXME: word wrap
17407 			auto font = remainingParts[0].font;
17408 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
17409 			auto width = font.stringWidth(slice, window);
17410 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
17411 
17412 			remainingParts = remainingParts[1 .. $];
17413 			baseline.x += width;
17414 
17415 			if(eol) {
17416 				baseline.y += nextBiggestDescent;
17417 				if(baseline.x > largestX)
17418 					largestX = baseline.x;
17419 				baseline.x = lineStart.x;
17420 				goto calculateNextLine;
17421 			}
17422 		}
17423 
17424 		if(baseline.x > largestX)
17425 			largestX = baseline.x;
17426 
17427 		this.boundingBox.right = largestX;
17428 		this.boundingBox.bottom = baseline.y;
17429 	}
17430 
17431 	// you must call rerender first!
17432 	void draw(ScreenPainter painter) {
17433 		foreach(part; parts) {
17434 			part.draw(painter);
17435 		}
17436 	}
17437 
17438 	struct IdentifyResult {
17439 		TextFlowComponent part;
17440 		int charIndexInPart;
17441 		int totalCharIndex = -1; // if this is -1, it just means the end
17442 
17443 		Rectangle boundingBox;
17444 	}
17445 
17446 	IdentifyResult identify(Point pt, bool exact = false) {
17447 		if(parts.length == 0)
17448 			return IdentifyResult(null, 0);
17449 
17450 		if(pt.y < boundingBox.top) {
17451 			if(exact)
17452 				return IdentifyResult(null, 1);
17453 			return IdentifyResult(parts[0], 0);
17454 		}
17455 		if(pt.y > boundingBox.bottom) {
17456 			if(exact)
17457 				return IdentifyResult(null, 2);
17458 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
17459 		}
17460 
17461 		int tci = 0;
17462 
17463 		// I should probably like binary search this or something...
17464 		foreach(ref part; parts) {
17465 			foreach(rendered; part.rendered) {
17466 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
17467 				if(rect.contains(pt)) {
17468 					auto x = pt.x - rendered.startX;
17469 					auto estimatedIdx = x / part.font.averageWidth;
17470 
17471 					if(estimatedIdx < 0)
17472 						estimatedIdx = 0;
17473 
17474 					if(estimatedIdx > rendered.slice.length)
17475 						estimatedIdx = cast(int) rendered.slice.length;
17476 
17477 					int idx;
17478 					int x1, x2;
17479 					if(part.font.isMonospace) {
17480 						auto w = part.font.averageWidth;
17481 						if(!exact && x > (estimatedIdx + 1) * w)
17482 							return IdentifyResult(null, 4);
17483 						idx = estimatedIdx;
17484 						x1 = idx * w;
17485 						x2 = (idx + 1) * w;
17486 					} else {
17487 						idx = estimatedIdx;
17488 
17489 						part.font.prepareContext(window);
17490 						scope(exit) part.font.releaseContext();
17491 
17492 						// int iterations;
17493 
17494 						while(true) {
17495 							// iterations++;
17496 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
17497 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
17498 
17499 							x1 += rendered.startX;
17500 							x2 += rendered.startX;
17501 
17502 							if(pt.x < x1) {
17503 								if(idx == 0) {
17504 									if(exact)
17505 										return IdentifyResult(null, 6);
17506 									else
17507 										break;
17508 								}
17509 								idx--;
17510 							} else if(pt.x > x2) {
17511 								idx++;
17512 								if(idx > rendered.slice.length) {
17513 									if(exact)
17514 										return IdentifyResult(null, 5);
17515 									else
17516 										break;
17517 								}
17518 							} else if(pt.x >= x1 && pt.x <= x2) {
17519 								if(idx)
17520 									idx--; // point it at the original index
17521 								break; // we fit
17522 							}
17523 						}
17524 
17525 						// import std.stdio; writeln(iterations)
17526 					}
17527 
17528 
17529 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
17530 				}
17531 			}
17532 			tci += cast(int) part.content.length; // FIXME: utf-8?
17533 		}
17534 		return IdentifyResult(null, 3);
17535 	}
17536 
17537 	Rectangle boundingBox; // only set after [rerender]
17538 
17539 	// text will be positioned around the exclusion zone
17540 	static struct ExclusionZone {
17541 
17542 	}
17543 
17544 	ExclusionZone[] exclusionZones;
17545 }
17546 
17547 
17548 // Don't use this yet. When I'm happy with it, I will move it to the
17549 // regular module namespace.
17550 mixin template ExperimentalTextComponent() {
17551 
17552 static:
17553 
17554 	alias Rectangle = arsd.color.Rectangle;
17555 
17556 	struct ForegroundColor {
17557 		Color color;
17558 		alias color this;
17559 
17560 		this(Color c) {
17561 			color = c;
17562 		}
17563 
17564 		this(int r, int g, int b, int a = 255) {
17565 			color = Color(r, g, b, a);
17566 		}
17567 
17568 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
17569 			return ForegroundColor(mixin("Color." ~ s));
17570 		}
17571 	}
17572 
17573 	struct BackgroundColor {
17574 		Color color;
17575 		alias color this;
17576 
17577 		this(Color c) {
17578 			color = c;
17579 		}
17580 
17581 		this(int r, int g, int b, int a = 255) {
17582 			color = Color(r, g, b, a);
17583 		}
17584 
17585 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
17586 			return BackgroundColor(mixin("Color." ~ s));
17587 		}
17588 	}
17589 
17590 	static class InlineElement {
17591 		string text;
17592 
17593 		BlockElement containingBlock;
17594 
17595 		Color color = Color.black;
17596 		Color backgroundColor = Color.transparent;
17597 		ushort styles;
17598 
17599 		string font;
17600 		int fontSize;
17601 
17602 		int lineHeight;
17603 
17604 		void* identifier;
17605 
17606 		Rectangle boundingBox;
17607 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
17608 
17609 		bool isMergeCompatible(InlineElement other) {
17610 			return
17611 				containingBlock is other.containingBlock &&
17612 				color == other.color &&
17613 				backgroundColor == other.backgroundColor &&
17614 				styles == other.styles &&
17615 				font == other.font &&
17616 				fontSize == other.fontSize &&
17617 				lineHeight == other.lineHeight &&
17618 				true;
17619 		}
17620 
17621 		int xOfIndex(size_t index) {
17622 			if(index < letterXs.length)
17623 				return letterXs[index];
17624 			else
17625 				return boundingBox.right;
17626 		}
17627 
17628 		InlineElement clone() {
17629 			auto ie = new InlineElement();
17630 			ie.tupleof = this.tupleof;
17631 			return ie;
17632 		}
17633 
17634 		InlineElement getPreviousInlineElement() {
17635 			InlineElement prev = null;
17636 			foreach(ie; this.containingBlock.parts) {
17637 				if(ie is this)
17638 					break;
17639 				prev = ie;
17640 			}
17641 			if(prev is null) {
17642 				BlockElement pb;
17643 				BlockElement cb = this.containingBlock;
17644 				moar:
17645 				foreach(ie; this.containingBlock.containingLayout.blocks) {
17646 					if(ie is cb)
17647 						break;
17648 					pb = ie;
17649 				}
17650 				if(pb is null)
17651 					return null;
17652 				if(pb.parts.length == 0) {
17653 					cb = pb;
17654 					goto moar;
17655 				}
17656 
17657 				prev = pb.parts[$-1];
17658 
17659 			}
17660 			return prev;
17661 		}
17662 
17663 		InlineElement getNextInlineElement() {
17664 			InlineElement next = null;
17665 			foreach(idx, ie; this.containingBlock.parts) {
17666 				if(ie is this) {
17667 					if(idx + 1 < this.containingBlock.parts.length)
17668 						next = this.containingBlock.parts[idx + 1];
17669 					break;
17670 				}
17671 			}
17672 			if(next is null) {
17673 				BlockElement n;
17674 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
17675 					if(ie is this.containingBlock) {
17676 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
17677 							n = this.containingBlock.containingLayout.blocks[idx + 1];
17678 						break;
17679 					}
17680 				}
17681 				if(n is null)
17682 					return null;
17683 
17684 				if(n.parts.length)
17685 					next = n.parts[0];
17686 				else {} // FIXME
17687 
17688 			}
17689 			return next;
17690 		}
17691 
17692 	}
17693 
17694 	// Block elements are used entirely for positioning inline elements,
17695 	// which are the things that are actually drawn.
17696 	class BlockElement {
17697 		InlineElement[] parts;
17698 		uint alignment;
17699 
17700 		int whiteSpace; // pre, pre-wrap, wrap
17701 
17702 		TextLayout containingLayout;
17703 
17704 		// inputs
17705 		Point where;
17706 		Size minimumSize;
17707 		Size maximumSize;
17708 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
17709 		void* identifier;
17710 
17711 		Rectangle margin;
17712 		Rectangle padding;
17713 
17714 		// outputs
17715 		Rectangle[] boundingBoxes;
17716 	}
17717 
17718 	struct TextIdentifyResult {
17719 		InlineElement element;
17720 		int offset;
17721 
17722 		private TextIdentifyResult fixupNewline() {
17723 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
17724 				offset--;
17725 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
17726 				offset--;
17727 			}
17728 			return this;
17729 		}
17730 	}
17731 
17732 	class TextLayout {
17733 		BlockElement[] blocks;
17734 		Rectangle boundingBox_;
17735 		Rectangle boundingBox() { return boundingBox_; }
17736 		void boundingBox(Rectangle r) {
17737 			if(r != boundingBox_) {
17738 				boundingBox_ = r;
17739 				layoutInvalidated = true;
17740 			}
17741 		}
17742 
17743 		Rectangle contentBoundingBox() {
17744 			Rectangle r;
17745 			foreach(block; blocks)
17746 			foreach(ie; block.parts) {
17747 				if(ie.boundingBox.right > r.right)
17748 					r.right = ie.boundingBox.right;
17749 				if(ie.boundingBox.bottom > r.bottom)
17750 					r.bottom = ie.boundingBox.bottom;
17751 			}
17752 			return r;
17753 		}
17754 
17755 		BlockElement[] getBlocks() {
17756 			return blocks;
17757 		}
17758 
17759 		InlineElement[] getTexts() {
17760 			InlineElement[] elements;
17761 			foreach(block; blocks)
17762 				elements ~= block.parts;
17763 			return elements;
17764 		}
17765 
17766 		string getPlainText() {
17767 			string text;
17768 			foreach(block; blocks)
17769 				foreach(part; block.parts)
17770 					text ~= part.text;
17771 			return text;
17772 		}
17773 
17774 		string getHtml() {
17775 			return null; // FIXME
17776 		}
17777 
17778 		this(Rectangle boundingBox) {
17779 			this.boundingBox = boundingBox;
17780 		}
17781 
17782 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
17783 			auto be = new BlockElement();
17784 			be.containingLayout = this;
17785 			if(after is null)
17786 				blocks ~= be;
17787 			else {
17788 				foreach(idx, b; blocks) {
17789 					if(b is after.containingBlock) {
17790 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
17791 						break;
17792 					}
17793 				}
17794 			}
17795 			return be;
17796 		}
17797 
17798 		void clear() {
17799 			blocks = null;
17800 			selectionStart = selectionEnd = caret = Caret.init;
17801 		}
17802 
17803 		void addText(Args...)(Args args) {
17804 			if(blocks.length == 0)
17805 				addBlock();
17806 
17807 			InlineElement ie = new InlineElement();
17808 			foreach(idx, arg; args) {
17809 				static if(is(typeof(arg) == ForegroundColor))
17810 					ie.color = arg;
17811 				else static if(is(typeof(arg) == TextFormat)) {
17812 					if(arg & 0x8000) // ~TextFormat.something turns it off
17813 						ie.styles &= arg;
17814 					else
17815 						ie.styles |= arg;
17816 				} else static if(is(typeof(arg) == string)) {
17817 					static if(idx == 0 && args.length > 1)
17818 						static assert(0, "Put styles before the string.");
17819 					size_t lastLineIndex;
17820 					foreach(cidx, char a; arg) {
17821 						if(a == '\n') {
17822 							ie.text = arg[lastLineIndex .. cidx + 1];
17823 							lastLineIndex = cidx + 1;
17824 							ie.containingBlock = blocks[$-1];
17825 							blocks[$-1].parts ~= ie.clone;
17826 							ie.text = null;
17827 						} else {
17828 
17829 						}
17830 					}
17831 
17832 					ie.text = arg[lastLineIndex .. $];
17833 					ie.containingBlock = blocks[$-1];
17834 					blocks[$-1].parts ~= ie.clone;
17835 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
17836 				}
17837 			}
17838 
17839 			invalidateLayout();
17840 		}
17841 
17842 		void tryMerge(InlineElement into, InlineElement what) {
17843 			if(!into.isMergeCompatible(what)) {
17844 				return; // cannot merge, different configs
17845 			}
17846 
17847 			// cool, can merge, bring text together...
17848 			into.text ~= what.text;
17849 
17850 			// and remove what
17851 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
17852 				if(what.containingBlock.parts[a] is what) {
17853 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
17854 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
17855 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
17856 
17857 				}
17858 			}
17859 
17860 			// FIXME: ensure no other carets have a reference to it
17861 		}
17862 
17863 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
17864 		TextIdentifyResult identify(int x, int y, bool exact = false) {
17865 			TextIdentifyResult inexactMatch;
17866 			foreach(block; blocks) {
17867 				foreach(part; block.parts) {
17868 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
17869 
17870 						// FIXME binary search
17871 						int tidx;
17872 						int lastX;
17873 						foreach_reverse(idxo, lx; part.letterXs) {
17874 							int idx = cast(int) idxo;
17875 							if(lx <= x) {
17876 								if(lastX && lastX - x < x - lx)
17877 									tidx = idx + 1;
17878 								else
17879 									tidx = idx;
17880 								break;
17881 							}
17882 							lastX = lx;
17883 						}
17884 
17885 						return TextIdentifyResult(part, tidx).fixupNewline;
17886 					} else if(!exact) {
17887 						// we're not in the box, but are we on the same line?
17888 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
17889 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
17890 					}
17891 				}
17892 			}
17893 
17894 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
17895 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
17896 
17897 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
17898 		}
17899 
17900 		void moveCaretToPixelCoordinates(int x, int y) {
17901 			auto result = identify(x, y);
17902 			caret.inlineElement = result.element;
17903 			caret.offset = result.offset;
17904 		}
17905 
17906 		void selectToPixelCoordinates(int x, int y) {
17907 			auto result = identify(x, y);
17908 
17909 			if(y < caretLastDrawnY1) {
17910 				// on a previous line, carat is selectionEnd
17911 				selectionEnd = caret;
17912 
17913 				selectionStart = Caret(this, result.element, result.offset);
17914 			} else if(y > caretLastDrawnY2) {
17915 				// on a later line
17916 				selectionStart = caret;
17917 
17918 				selectionEnd = Caret(this, result.element, result.offset);
17919 			} else {
17920 				// on the same line...
17921 				if(x <= caretLastDrawnX) {
17922 					selectionEnd = caret;
17923 					selectionStart = Caret(this, result.element, result.offset);
17924 				} else {
17925 					selectionStart = caret;
17926 					selectionEnd = Caret(this, result.element, result.offset);
17927 				}
17928 
17929 			}
17930 		}
17931 
17932 
17933 		/// Call this if the inputs change. It will reflow everything
17934 		void redoLayout(ScreenPainter painter) {
17935 			//painter.setClipRectangle(boundingBox);
17936 			auto pos = Point(boundingBox.left, boundingBox.top);
17937 
17938 			int lastHeight;
17939 			void nl() {
17940 				pos.x = boundingBox.left;
17941 				pos.y += lastHeight;
17942 			}
17943 			foreach(block; blocks) {
17944 				nl();
17945 				foreach(part; block.parts) {
17946 					part.letterXs = null;
17947 
17948 					auto size = painter.textSize(part.text);
17949 					version(Windows)
17950 						if(part.text.length && part.text[$-1] == '\n')
17951 							size.height /= 2; // windows counts the new line at the end, but we don't want that
17952 
17953 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
17954 
17955 					foreach(idx, char c; part.text) {
17956 							// FIXME: unicode
17957 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
17958 					}
17959 
17960 					pos.x += size.width;
17961 					if(pos.x >= boundingBox.right) {
17962 						pos.y += size.height;
17963 						pos.x = boundingBox.left;
17964 						lastHeight = 0;
17965 					} else {
17966 						lastHeight = size.height;
17967 					}
17968 
17969 					if(part.text.length && part.text[$-1] == '\n')
17970 						nl();
17971 				}
17972 			}
17973 
17974 			layoutInvalidated = false;
17975 		}
17976 
17977 		bool layoutInvalidated = true;
17978 		void invalidateLayout() {
17979 			layoutInvalidated = true;
17980 		}
17981 
17982 // FIXME: caret can remain sometimes when inserting
17983 // FIXME: inserting at the beginning once you already have something can eff it up.
17984 		void drawInto(ScreenPainter painter, bool focused = false) {
17985 			if(layoutInvalidated)
17986 				redoLayout(painter);
17987 			foreach(block; blocks) {
17988 				foreach(part; block.parts) {
17989 					painter.outlineColor = part.color;
17990 					painter.fillColor = part.backgroundColor;
17991 
17992 					auto pos = part.boundingBox.upperLeft;
17993 					auto size = part.boundingBox.size;
17994 
17995 					painter.drawText(pos, part.text);
17996 					if(part.styles & TextFormat.underline)
17997 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
17998 					if(part.styles & TextFormat.strikethrough)
17999 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
18000 				}
18001 			}
18002 
18003 			// on every redraw, I will force the caret to be
18004 			// redrawn too, in order to eliminate perceived lag
18005 			// when moving around with the mouse.
18006 			eraseCaret(painter);
18007 
18008 			if(focused) {
18009 				highlightSelection(painter);
18010 				drawCaret(painter);
18011 			}
18012 		}
18013 
18014 		Color selectionXorColor = Color(255, 255, 127);
18015 
18016 		void highlightSelection(ScreenPainter painter) {
18017 			if(selectionStart is selectionEnd)
18018 				return; // no selection
18019 
18020 			if(selectionStart.inlineElement is null) return;
18021 			if(selectionEnd.inlineElement is null) return;
18022 
18023 			assert(selectionStart.inlineElement !is null);
18024 			assert(selectionEnd.inlineElement !is null);
18025 
18026 			painter.rasterOp = RasterOp.xor;
18027 			painter.outlineColor = Color.transparent;
18028 			painter.fillColor = selectionXorColor;
18029 
18030 			auto at = selectionStart.inlineElement;
18031 			auto atOffset = selectionStart.offset;
18032 			bool done;
18033 			while(at) {
18034 				auto box = at.boundingBox;
18035 				if(atOffset < at.letterXs.length)
18036 					box.left = at.letterXs[atOffset];
18037 
18038 				if(at is selectionEnd.inlineElement) {
18039 					if(selectionEnd.offset < at.letterXs.length)
18040 						box.right = at.letterXs[selectionEnd.offset];
18041 					done = true;
18042 				}
18043 
18044 				painter.drawRectangle(box.upperLeft, box.width, box.height);
18045 
18046 				if(done)
18047 					break;
18048 
18049 				at = at.getNextInlineElement();
18050 				atOffset = 0;
18051 			}
18052 		}
18053 
18054 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
18055 		bool caretShowingOnScreen = false;
18056 		void drawCaret(ScreenPainter painter) {
18057 			//painter.setClipRectangle(boundingBox);
18058 			int x, y1, y2;
18059 			if(caret.inlineElement is null) {
18060 				x = boundingBox.left;
18061 				y1 = boundingBox.top + 2;
18062 				y2 = boundingBox.top + painter.fontHeight;
18063 			} else {
18064 				x = caret.inlineElement.xOfIndex(caret.offset);
18065 				y1 = caret.inlineElement.boundingBox.top + 2;
18066 				y2 = caret.inlineElement.boundingBox.bottom - 2;
18067 			}
18068 
18069 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
18070 				eraseCaret(painter);
18071 
18072 			painter.pen = Pen(Color.white, 1);
18073 			painter.rasterOp = RasterOp.xor;
18074 			painter.drawLine(
18075 				Point(x, y1),
18076 				Point(x, y2)
18077 			);
18078 			painter.rasterOp = RasterOp.normal;
18079 			caretShowingOnScreen = !caretShowingOnScreen;
18080 
18081 			if(caretShowingOnScreen) {
18082 				caretLastDrawnX = x;
18083 				caretLastDrawnY1 = y1;
18084 				caretLastDrawnY2 = y2;
18085 			}
18086 		}
18087 
18088 		Rectangle caretBoundingBox() {
18089 			int x, y1, y2;
18090 			if(caret.inlineElement is null) {
18091 				x = boundingBox.left;
18092 				y1 = boundingBox.top + 2;
18093 				y2 = boundingBox.top + 16;
18094 			} else {
18095 				x = caret.inlineElement.xOfIndex(caret.offset);
18096 				y1 = caret.inlineElement.boundingBox.top + 2;
18097 				y2 = caret.inlineElement.boundingBox.bottom - 2;
18098 			}
18099 
18100 			return Rectangle(x, y1, x + 1, y2);
18101 		}
18102 
18103 		void eraseCaret(ScreenPainter painter) {
18104 			//painter.setClipRectangle(boundingBox);
18105 			if(!caretShowingOnScreen) return;
18106 			painter.pen = Pen(Color.white, 1);
18107 			painter.rasterOp = RasterOp.xor;
18108 			painter.drawLine(
18109 				Point(caretLastDrawnX, caretLastDrawnY1),
18110 				Point(caretLastDrawnX, caretLastDrawnY2)
18111 			);
18112 
18113 			caretShowingOnScreen = false;
18114 			painter.rasterOp = RasterOp.normal;
18115 		}
18116 
18117 		/// Caret movement api
18118 		/// These should give the user a logical result based on what they see on screen...
18119 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
18120 		void moveUp() {
18121 			if(caret.inlineElement is null) return;
18122 			auto x = caret.inlineElement.xOfIndex(caret.offset);
18123 			auto y = caret.inlineElement.boundingBox.top + 2;
18124 
18125 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
18126 			if(y < 0)
18127 				return;
18128 
18129 			auto i = identify(x, y);
18130 
18131 			if(i.element) {
18132 				caret.inlineElement = i.element;
18133 				caret.offset = i.offset;
18134 			}
18135 		}
18136 		void moveDown() {
18137 			if(caret.inlineElement is null) return;
18138 			auto x = caret.inlineElement.xOfIndex(caret.offset);
18139 			auto y = caret.inlineElement.boundingBox.bottom - 2;
18140 
18141 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
18142 
18143 			auto i = identify(x, y);
18144 			if(i.element) {
18145 				caret.inlineElement = i.element;
18146 				caret.offset = i.offset;
18147 			}
18148 		}
18149 		void moveLeft() {
18150 			if(caret.inlineElement is null) return;
18151 			if(caret.offset)
18152 				caret.offset--;
18153 			else {
18154 				auto p = caret.inlineElement.getPreviousInlineElement();
18155 				if(p) {
18156 					caret.inlineElement = p;
18157 					if(p.text.length && p.text[$-1] == '\n')
18158 						caret.offset = cast(int) p.text.length - 1;
18159 					else
18160 						caret.offset = cast(int) p.text.length;
18161 				}
18162 			}
18163 		}
18164 		void moveRight() {
18165 			if(caret.inlineElement is null) return;
18166 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
18167 				caret.offset++;
18168 			} else {
18169 				auto p = caret.inlineElement.getNextInlineElement();
18170 				if(p) {
18171 					caret.inlineElement = p;
18172 					caret.offset = 0;
18173 				}
18174 			}
18175 		}
18176 		void moveHome() {
18177 			if(caret.inlineElement is null) return;
18178 			auto x = 0;
18179 			auto y = caret.inlineElement.boundingBox.top + 2;
18180 
18181 			auto i = identify(x, y);
18182 
18183 			if(i.element) {
18184 				caret.inlineElement = i.element;
18185 				caret.offset = i.offset;
18186 			}
18187 		}
18188 		void moveEnd() {
18189 			if(caret.inlineElement is null) return;
18190 			auto x = int.max;
18191 			auto y = caret.inlineElement.boundingBox.top + 2;
18192 
18193 			auto i = identify(x, y);
18194 
18195 			if(i.element) {
18196 				caret.inlineElement = i.element;
18197 				caret.offset = i.offset;
18198 			}
18199 
18200 		}
18201 		void movePageUp(ref Caret caret) {}
18202 		void movePageDown(ref Caret caret) {}
18203 
18204 		void moveDocumentStart(ref Caret caret) {
18205 			if(blocks.length && blocks[0].parts.length)
18206 				caret = Caret(this, blocks[0].parts[0], 0);
18207 			else
18208 				caret = Caret.init;
18209 		}
18210 
18211 		void moveDocumentEnd(ref Caret caret) {
18212 			if(blocks.length) {
18213 				auto parts = blocks[$-1].parts;
18214 				if(parts.length) {
18215 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
18216 				} else {
18217 					caret = Caret.init;
18218 				}
18219 			} else
18220 				caret = Caret.init;
18221 		}
18222 
18223 		void deleteSelection() {
18224 			if(selectionStart is selectionEnd)
18225 				return;
18226 
18227 			if(selectionStart.inlineElement is null) return;
18228 			if(selectionEnd.inlineElement is null) return;
18229 
18230 			assert(selectionStart.inlineElement !is null);
18231 			assert(selectionEnd.inlineElement !is null);
18232 
18233 			auto at = selectionStart.inlineElement;
18234 
18235 			if(selectionEnd.inlineElement is at) {
18236 				// same element, need to chop out
18237 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
18238 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
18239 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
18240 			} else {
18241 				// different elements, we can do it with slicing
18242 				at.text = at.text[0 .. selectionStart.offset];
18243 				if(selectionStart.offset < at.letterXs.length)
18244 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
18245 
18246 				at = at.getNextInlineElement();
18247 
18248 				while(at) {
18249 					if(at is selectionEnd.inlineElement) {
18250 						at.text = at.text[selectionEnd.offset .. $];
18251 						if(selectionEnd.offset < at.letterXs.length)
18252 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
18253 						selectionEnd.offset = 0;
18254 						break;
18255 					} else {
18256 						auto cfd = at;
18257 						cfd.text = null; // delete the whole thing
18258 
18259 						at = at.getNextInlineElement();
18260 
18261 						if(cfd.text.length == 0) {
18262 							// and remove cfd
18263 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
18264 								if(cfd.containingBlock.parts[a] is cfd) {
18265 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
18266 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
18267 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
18268 
18269 								}
18270 							}
18271 						}
18272 					}
18273 				}
18274 			}
18275 
18276 			caret = selectionEnd;
18277 			selectNone();
18278 
18279 			invalidateLayout();
18280 
18281 		}
18282 
18283 		/// Plain text editing api. These work at the current caret inside the selected inline element.
18284 		void insert(in char[] text) {
18285 			foreach(dchar ch; text)
18286 				insert(ch);
18287 		}
18288 		/// ditto
18289 		void insert(dchar ch) {
18290 
18291 			bool selectionDeleted = false;
18292 			if(selectionStart !is selectionEnd) {
18293 				deleteSelection();
18294 				selectionDeleted = true;
18295 			}
18296 
18297 			if(ch == 127) {
18298 				delete_();
18299 				return;
18300 			}
18301 			if(ch == 8) {
18302 				if(!selectionDeleted)
18303 					backspace();
18304 				return;
18305 			}
18306 
18307 			invalidateLayout();
18308 
18309 			if(ch == 13) ch = 10;
18310 			auto e = caret.inlineElement;
18311 			if(e is null) {
18312 				addText("" ~ cast(char) ch) ; // FIXME
18313 				return;
18314 			}
18315 
18316 			if(caret.offset == e.text.length) {
18317 				e.text ~= cast(char) ch; // FIXME
18318 				caret.offset++;
18319 				if(ch == 10) {
18320 					auto c = caret.inlineElement.clone;
18321 					c.text = null;
18322 					c.letterXs = null;
18323 					insertPartAfter(c,e);
18324 					caret = Caret(this, c, 0);
18325 				}
18326 			} else {
18327 				// FIXME cast char sucks
18328 				if(ch == 10) {
18329 					auto c = caret.inlineElement.clone;
18330 					c.text = e.text[caret.offset .. $];
18331 					if(caret.offset < c.letterXs.length)
18332 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
18333 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
18334 					if(caret.offset <= e.letterXs.length) {
18335 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
18336 					}
18337 					insertPartAfter(c,e);
18338 					caret = Caret(this, c, 0);
18339 				} else {
18340 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
18341 					caret.offset++;
18342 				}
18343 			}
18344 		}
18345 
18346 		void insertPartAfter(InlineElement what, InlineElement where) {
18347 			foreach(idx, p; where.containingBlock.parts) {
18348 				if(p is where) {
18349 					if(idx + 1 == where.containingBlock.parts.length)
18350 						where.containingBlock.parts ~= what;
18351 					else
18352 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
18353 					return;
18354 				}
18355 			}
18356 		}
18357 
18358 		void cleanupStructures() {
18359 			for(size_t i = 0; i < blocks.length; i++) {
18360 				auto block = blocks[i];
18361 				for(size_t a = 0; a < block.parts.length; a++) {
18362 					auto part = block.parts[a];
18363 					if(part.text.length == 0) {
18364 						for(size_t b = a; b < block.parts.length - 1; b++)
18365 							block.parts[b] = block.parts[b+1];
18366 						block.parts = block.parts[0 .. $-1];
18367 					}
18368 				}
18369 				if(block.parts.length == 0) {
18370 					for(size_t a = i; a < blocks.length - 1; a++)
18371 						blocks[a] = blocks[a+1];
18372 					blocks = blocks[0 .. $-1];
18373 				}
18374 			}
18375 		}
18376 
18377 		void backspace() {
18378 			try_again:
18379 			auto e = caret.inlineElement;
18380 			if(e is null)
18381 				return;
18382 			if(caret.offset == 0) {
18383 				auto prev = e.getPreviousInlineElement();
18384 				if(prev is null)
18385 					return;
18386 				auto newOffset = cast(int) prev.text.length;
18387 				tryMerge(prev, e);
18388 				caret.inlineElement = prev;
18389 				caret.offset = prev is null ? 0 : newOffset;
18390 
18391 				goto try_again;
18392 			} else if(caret.offset == e.text.length) {
18393 				e.text = e.text[0 .. $-1];
18394 				caret.offset--;
18395 			} else {
18396 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
18397 				caret.offset--;
18398 			}
18399 			//cleanupStructures();
18400 
18401 			invalidateLayout();
18402 		}
18403 		void delete_() {
18404 			if(selectionStart !is selectionEnd)
18405 				deleteSelection();
18406 			else {
18407 				auto before = caret;
18408 				moveRight();
18409 				if(caret != before) {
18410 					backspace();
18411 				}
18412 			}
18413 
18414 			invalidateLayout();
18415 		}
18416 		void overstrike() {}
18417 
18418 		/// Selection API. See also: caret movement.
18419 		void selectAll() {
18420 			moveDocumentStart(selectionStart);
18421 			moveDocumentEnd(selectionEnd);
18422 		}
18423 		bool selectNone() {
18424 			if(selectionStart != selectionEnd) {
18425 				selectionStart = selectionEnd = Caret.init;
18426 				return true;
18427 			}
18428 			return false;
18429 		}
18430 
18431 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
18432 		/// They will modify the current selection if there is one and will splice one in if needed.
18433 		void changeAttributes() {}
18434 
18435 
18436 		/// Text search api. They manipulate the selection and/or caret.
18437 		void findText(string text) {}
18438 		void findIndex(size_t textIndex) {}
18439 
18440 		// sample event handlers
18441 
18442 		void handleEvent(KeyEvent event) {
18443 			//if(event.type == KeyEvent.Type.KeyPressed) {
18444 
18445 			//}
18446 		}
18447 
18448 		void handleEvent(dchar ch) {
18449 
18450 		}
18451 
18452 		void handleEvent(MouseEvent event) {
18453 
18454 		}
18455 
18456 		bool contentEditable; // can it be edited?
18457 		bool contentCaretable; // is there a caret/cursor that moves around in there?
18458 		bool contentSelectable; // selectable?
18459 
18460 		Caret caret;
18461 		Caret selectionStart;
18462 		Caret selectionEnd;
18463 
18464 		bool insertMode;
18465 	}
18466 
18467 	struct Caret {
18468 		TextLayout layout;
18469 		InlineElement inlineElement;
18470 		int offset;
18471 	}
18472 
18473 	enum TextFormat : ushort {
18474 		// decorations
18475 		underline = 1,
18476 		strikethrough = 2,
18477 
18478 		// font selectors
18479 
18480 		bold = 0x4000 | 1, // weight 700
18481 		light = 0x4000 | 2, // weight 300
18482 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
18483 		// bold | light is really invalid but should give weight 500
18484 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
18485 
18486 		italic = 0x4000 | 8,
18487 		smallcaps = 0x4000 | 16,
18488 	}
18489 
18490 	void* findFont(string family, int weight, TextFormat formats) {
18491 		return null;
18492 	}
18493 
18494 }
18495 
18496 /++
18497 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18498 
18499 	History:
18500 		Added February 19, 2021
18501 +/
18502 /// Group: drag_and_drop
18503 interface DropHandler {
18504 	/++
18505 		Called when the drag enters the handler's area.
18506 	+/
18507 	DragAndDropAction dragEnter(DropPackage*);
18508 	/++
18509 		Called when the drag leaves the handler's area or is
18510 		cancelled. You should free your resources when this is called.
18511 	+/
18512 	void dragLeave();
18513 	/++
18514 		Called continually as the drag moves over the handler's area.
18515 
18516 		Returns: feedback to the dragger
18517 	+/
18518 	DropParameters dragOver(Point pt);
18519 	/++
18520 		The user dropped the data and you should process it now. You can
18521 		access the data through the given [DropPackage].
18522 	+/
18523 	void drop(scope DropPackage*);
18524 	/++
18525 		Called when the drop is complete. You should free whatever temporary
18526 		resources you were using. It is often reasonable to simply forward
18527 		this call to [dragLeave].
18528 	+/
18529 	void finish();
18530 
18531 	/++
18532 		Parameters returned by [DropHandler.drop].
18533 	+/
18534 	static struct DropParameters {
18535 		/++
18536 			Acceptable action over this area.
18537 		+/
18538 		DragAndDropAction action;
18539 		/++
18540 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
18541 
18542 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
18543 		+/
18544 		Rectangle consistentWithin;
18545 	}
18546 }
18547 
18548 /++
18549 	History:
18550 		Added February 19, 2021
18551 +/
18552 /// Group: drag_and_drop
18553 enum DragAndDropAction {
18554 	none = 0,
18555 	copy,
18556 	move,
18557 	link,
18558 	ask,
18559 	custom
18560 }
18561 
18562 /++
18563 	An opaque structure representing dropped data. It contains
18564 	private, platform-specific data that your `drop` function
18565 	should simply forward.
18566 
18567 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18568 
18569 	History:
18570 		Added February 19, 2021
18571 +/
18572 /// Group: drag_and_drop
18573 struct DropPackage {
18574 	/++
18575 		Lists the available formats as magic numbers. You should compare these
18576 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
18577 		understand the passed data.
18578 	+/
18579 	DraggableData.FormatId[] availableFormats() {
18580 		version(X11) {
18581 			return xFormats;
18582 		} else version(Windows) {
18583 			if(pDataObj is null)
18584 				return null;
18585 
18586 			typeof(return) ret;
18587 
18588 			IEnumFORMATETC ef;
18589 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
18590 				FORMATETC fmt;
18591 				ULONG fetched;
18592 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
18593 					if(fetched == 0)
18594 						break;
18595 
18596 					if(fmt.lindex != -1)
18597 						continue;
18598 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
18599 						continue;
18600 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
18601 						continue;
18602 
18603 					ret ~= fmt.cfFormat;
18604 				}
18605 			}
18606 
18607 			return ret;
18608 		}
18609 	}
18610 
18611 	/++
18612 		Gets data from the drop and optionally accepts it.
18613 
18614 		Returns:
18615 			void because the data is fed asynchronously through the `dg` parameter.
18616 
18617 		Params:
18618 			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.
18619 
18620 			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.
18621 
18622 			Calling `getData` again after accepting a drop is not permitted.
18623 
18624 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
18625 
18626 			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.
18627 
18628 		Throws:
18629 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
18630 
18631 		History:
18632 			Included in first release of [DropPackage].
18633 	+/
18634 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
18635 		version(X11) {
18636 
18637 			auto display = XDisplayConnection.get();
18638 			auto selectionAtom = GetAtom!"XdndSelection"(display);
18639 			auto best = format;
18640 
18641 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
18642 
18643 				XDisplay* display;
18644 				Atom selectionAtom;
18645 				DraggableData.FormatId best;
18646 				DraggableData.FormatId format;
18647 				void delegate(scope ubyte[] data) dg;
18648 				DragAndDropAction acceptedAction;
18649 				Window sourceWindow;
18650 				SimpleWindow win;
18651 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
18652 					this.display = display;
18653 					this.win = win;
18654 					this.sourceWindow = sourceWindow;
18655 					this.format = format;
18656 					this.selectionAtom = selectionAtom;
18657 					this.best = best;
18658 					this.dg = dg;
18659 					this.acceptedAction = acceptedAction;
18660 				}
18661 
18662 
18663 				mixin X11GetSelectionHandler_Basics;
18664 
18665 				void handleData(Atom target, in ubyte[] data) {
18666 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
18667 
18668 					dg(cast(ubyte[]) data);
18669 
18670 					if(acceptedAction != DragAndDropAction.none) {
18671 						auto display = XDisplayConnection.get;
18672 
18673 						XClientMessageEvent xclient;
18674 
18675 						xclient.type = EventType.ClientMessage;
18676 						xclient.window = sourceWindow;
18677 						xclient.message_type = GetAtom!"XdndFinished"(display);
18678 						xclient.format = 32;
18679 						xclient.data.l[0] = win.impl.window;
18680 						xclient.data.l[1] = 1; // drop successful
18681 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
18682 
18683 						XSendEvent(
18684 							display,
18685 							sourceWindow,
18686 							false,
18687 							EventMask.NoEventMask,
18688 							cast(XEvent*) &xclient
18689 						);
18690 
18691 						XFlush(display);
18692 					}
18693 				}
18694 
18695 				Atom findBestFormat(Atom[] answer) {
18696 					Atom best = None;
18697 					foreach(option; answer) {
18698 						if(option == format) {
18699 							best = option;
18700 							break;
18701 						}
18702 						/*
18703 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
18704 							best = option;
18705 							break;
18706 						} else if(option == XA_STRING) {
18707 							best = option;
18708 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
18709 							best = option;
18710 						}
18711 						*/
18712 					}
18713 					return best;
18714 				}
18715 			}
18716 
18717 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
18718 
18719 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
18720 
18721 		} else version(Windows) {
18722 
18723 			// clean up like DragLeave
18724 			// pass effect back up
18725 
18726 			FORMATETC t;
18727 			assert(format >= 0 && format <= ushort.max);
18728 			t.cfFormat = cast(ushort) format;
18729 			t.lindex = -1;
18730 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
18731 			t.tymed = TYMED.TYMED_HGLOBAL;
18732 
18733 			STGMEDIUM m;
18734 
18735 			if(pDataObj.GetData(&t, &m) != S_OK) {
18736 				// fail
18737 			} else {
18738 				// succeed, take the data and clean up
18739 
18740 				// FIXME: ensure it is legit HGLOBAL
18741 				auto handle = m.hGlobal;
18742 
18743 				if(handle) {
18744 					auto sz = GlobalSize(handle);
18745 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
18746 						scope(exit) GlobalUnlock(handle);
18747 						scope(exit) GlobalFree(handle);
18748 
18749 						auto data = ptr[0 .. sz];
18750 
18751 						dg(data);
18752 					}
18753 				}
18754 			}
18755 		}
18756 	}
18757 
18758 	private:
18759 
18760 	version(X11) {
18761 		SimpleWindow win;
18762 		Window sourceWindow;
18763 		Time dataTimestamp;
18764 
18765 		Atom[] xFormats;
18766 	}
18767 	version(Windows) {
18768 		IDataObject pDataObj;
18769 	}
18770 }
18771 
18772 /++
18773 	A generic helper base class for making a drop handler with a preference list of custom types.
18774 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
18775 	droppers too.
18776 
18777 	It assumes the whole window it used, but you can subclass to change that.
18778 
18779 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18780 
18781 	History:
18782 		Added February 19, 2021
18783 +/
18784 /// Group: drag_and_drop
18785 class GenericDropHandlerBase : DropHandler {
18786 	// no fancy state here so no need to do anything here
18787 	void finish() { }
18788 	void dragLeave() { }
18789 
18790 	private DragAndDropAction acceptedAction;
18791 	private DraggableData.FormatId acceptedFormat;
18792 	private void delegate(scope ubyte[]) acceptedHandler;
18793 
18794 	struct FormatHandler {
18795 		DraggableData.FormatId format;
18796 		void delegate(scope ubyte[]) handler;
18797 	}
18798 
18799 	protected abstract FormatHandler[] formatHandlers();
18800 
18801 	DragAndDropAction dragEnter(DropPackage* pkg) {
18802 		debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
18803 		foreach(fmt; formatHandlers())
18804 		foreach(f; pkg.availableFormats())
18805 			if(f == fmt.format) {
18806 				acceptedFormat = f;
18807 				acceptedHandler = fmt.handler;
18808 				return acceptedAction = DragAndDropAction.copy;
18809 			}
18810 		return acceptedAction = DragAndDropAction.none;
18811 	}
18812 	DropParameters dragOver(Point pt) {
18813 		return DropParameters(acceptedAction);
18814 	}
18815 
18816 	void drop(scope DropPackage* dropPackage) {
18817 		if(!acceptedFormat || acceptedHandler is null) {
18818 			debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
18819 			return; // prolly shouldn't happen anyway...
18820 		}
18821 
18822 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
18823 	}
18824 }
18825 
18826 /++
18827 	A simple handler for making your window accept drops of plain text.
18828 
18829 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18830 
18831 	History:
18832 		Added February 22, 2021
18833 +/
18834 /// Group: drag_and_drop
18835 class TextDropHandler : GenericDropHandlerBase {
18836 	private void delegate(in char[] text) dg;
18837 
18838 	/++
18839 
18840 	+/
18841 	this(void delegate(in char[] text) dg) {
18842 		this.dg = dg;
18843 	}
18844 
18845 	protected override FormatHandler[] formatHandlers() {
18846 		version(X11)
18847 			return [
18848 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
18849 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
18850 			];
18851 		else version(Windows)
18852 			return [
18853 				FormatHandler(CF_UNICODETEXT, &translator),
18854 			];
18855 	}
18856 
18857 	private void translator(scope ubyte[] data) {
18858 		version(X11)
18859 			dg(cast(char[]) data);
18860 		else version(Windows)
18861 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
18862 	}
18863 }
18864 
18865 /++
18866 	A simple handler for making your window accept drops of files, issued to you as file names.
18867 
18868 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18869 
18870 	History:
18871 		Added February 22, 2021
18872 +/
18873 /// Group: drag_and_drop
18874 
18875 class FilesDropHandler : GenericDropHandlerBase {
18876 	private void delegate(in char[][]) dg;
18877 
18878 	/++
18879 
18880 	+/
18881 	this(void delegate(in char[][] fileNames) dg) {
18882 		this.dg = dg;
18883 	}
18884 
18885 	protected override FormatHandler[] formatHandlers() {
18886 		version(X11)
18887 			return [
18888 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
18889 			];
18890 		else version(Windows)
18891 			return [
18892 				FormatHandler(CF_HDROP, &translator),
18893 			];
18894 	}
18895 
18896 	private void translator(scope ubyte[] data) {
18897 		version(X11) {
18898 			char[] listString = cast(char[]) data;
18899 			char[][16] buffer;
18900 			int count;
18901 			char[][] result = buffer[];
18902 
18903 			void commit(char[] s) {
18904 				if(count == result.length)
18905 					result.length += 16;
18906 				if(s.length > 7 && s[0 ..7] == "file://")
18907 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
18908 				result[count++] = s;
18909 			}
18910 
18911 			size_t last;
18912 			foreach(idx, char c; listString) {
18913 				if(c == '\n') {
18914 					commit(listString[last .. idx - 1]); // a \r
18915 					last = idx + 1; // a \n
18916 				}
18917 			}
18918 
18919 			if(last < listString.length) {
18920 				commit(listString[last .. $]);
18921 			}
18922 
18923 			// FIXME: they are uris now, should I translate it to local file names?
18924 			// of course the host name is supposed to be there cuz of X rokking...
18925 
18926 			dg(result[0 .. count]);
18927 		} else version(Windows) {
18928 
18929 			static struct DROPFILES {
18930 				DWORD pFiles;
18931 				POINT pt;
18932 				BOOL  fNC;
18933 				BOOL  fWide;
18934 			}
18935 
18936 
18937 			const(char)[][16] buffer;
18938 			int count;
18939 			const(char)[][] result = buffer[];
18940 			size_t last;
18941 
18942 			void commitA(in char[] stuff) {
18943 				if(count == result.length)
18944 					result.length += 16;
18945 				result[count++] = stuff;
18946 			}
18947 
18948 			void commitW(in wchar[] stuff) {
18949 				commitA(makeUtf8StringFromWindowsString(stuff));
18950 			}
18951 
18952 			void magic(T)(T chars) {
18953 				size_t idx;
18954 				while(chars[idx]) {
18955 					last = idx;
18956 					while(chars[idx]) {
18957 						idx++;
18958 					}
18959 					static if(is(T == char*))
18960 						commitA(chars[last .. idx]);
18961 					else
18962 						commitW(chars[last .. idx]);
18963 					idx++;
18964 				}
18965 			}
18966 
18967 			auto df = cast(DROPFILES*) data.ptr;
18968 			if(df.fWide) {
18969 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
18970 				magic(chars);
18971 			} else {
18972 				char* chars = cast(char*) (data.ptr + df.pFiles);
18973 				magic(chars);
18974 			}
18975 			dg(result[0 .. count]);
18976 		}
18977 	}
18978 }
18979 
18980 /++
18981 	Interface to describe data being dragged. See also [draggable] helper function.
18982 
18983 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18984 
18985 	History:
18986 		Added February 19, 2021
18987 +/
18988 interface DraggableData {
18989 	version(X11)
18990 		alias FormatId = Atom;
18991 	else
18992 		alias FormatId = uint;
18993 	/++
18994 		Gets the platform-specific FormatId associated with the given named format.
18995 
18996 		This may be a MIME type, but may also be other various strings defined by the
18997 		programs you want to interoperate with.
18998 
18999 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
19000 		and convert it to some particular type for you.
19001 	+/
19002 	static FormatId getFormatId(string name)() {
19003 		version(X11)
19004 			return GetAtom!name(XDisplayConnection.get);
19005 		else version(Windows) {
19006 			static UINT cache;
19007 			if(!cache)
19008 				cache = RegisterClipboardFormatA(name);
19009 			return cache;
19010 		} else
19011 			throw new NotYetImplementedException();
19012 	}
19013 
19014 	/++
19015 		Looks up a string to represent the name for the given format, if there is one.
19016 
19017 		You should avoid using this function because it is slow. It is provided more for
19018 		debugging than for primary use.
19019 	+/
19020 	static string getFormatName(FormatId format) {
19021 		version(X11) {
19022 			if(format == 0)
19023 				return "None";
19024 			else
19025 				return getAtomName(format, XDisplayConnection.get);
19026 		} else version(Windows) {
19027 			switch(format) {
19028 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
19029 				case CF_DIBV5: return "CF_DIBV5";
19030 				case CF_RIFF: return "CF_RIFF";
19031 				case CF_WAVE: return "CF_WAVE";
19032 				case CF_HDROP: return "CF_HDROP";
19033 				default:
19034 					char[1024] name;
19035 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
19036 					return name[0 .. count].idup;
19037 			}
19038 		}
19039 	}
19040 
19041 	FormatId[] availableFormats();
19042 	// Return the slice of data you filled, empty slice if done.
19043 	// this is to support the incremental thing
19044 	ubyte[] getData(FormatId format, return scope ubyte[] data);
19045 
19046 	size_t dataLength(FormatId format);
19047 }
19048 
19049 /++
19050 	$(PITFALL this is not yet stable and may break in future versions without notice.)
19051 
19052 	History:
19053 		Added February 19, 2021
19054 +/
19055 DraggableData draggable(string s) {
19056 	version(X11)
19057 	return new class X11SetSelectionHandler_Text, DraggableData {
19058 		this() {
19059 			super(s);
19060 		}
19061 
19062 		override FormatId[] availableFormats() {
19063 			return X11SetSelectionHandler_Text.availableFormats();
19064 		}
19065 
19066 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
19067 			return X11SetSelectionHandler_Text.getData(format, data);
19068 		}
19069 
19070 		size_t dataLength(FormatId format) {
19071 			return s.length;
19072 		}
19073 	};
19074 	version(Windows)
19075 	return new class DraggableData {
19076 		FormatId[] availableFormats() {
19077 			return [CF_UNICODETEXT];
19078 		}
19079 
19080 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
19081 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
19082 		}
19083 
19084 		size_t dataLength(FormatId format) {
19085 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
19086 		}
19087 	};
19088 }
19089 
19090 /++
19091 	$(PITFALL this is not yet stable and may break in future versions without notice.)
19092 
19093 	History:
19094 		Added February 19, 2021
19095 +/
19096 /// Group: drag_and_drop
19097 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
19098 in {
19099 	assert(window !is null);
19100 	assert(handler !is null);
19101 }
19102 do
19103 {
19104 	version(X11) {
19105 		auto sh = cast(X11SetSelectionHandler) handler;
19106 		if(sh is null) {
19107 			// gotta make my own adapter.
19108 			sh = new class X11SetSelectionHandler {
19109 				mixin X11SetSelectionHandler_Basics;
19110 
19111 				Atom[] availableFormats() { return handler.availableFormats(); }
19112 				ubyte[] getData(Atom format, return scope ubyte[] data) {
19113 					return handler.getData(format, data);
19114 				}
19115 
19116 				// since the drop selection is only ever used once it isn't important
19117 				// to reset it.
19118 				void done() {}
19119 			};
19120 		}
19121 		return doDragDropX11(window, sh, action);
19122 	} else version(Windows) {
19123 		return doDragDropWindows(window, handler, action);
19124 	} else throw new NotYetImplementedException();
19125 }
19126 
19127 version(Windows)
19128 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
19129 	IDataObject obj = new class IDataObject {
19130 		ULONG refCount;
19131 		ULONG AddRef() {
19132 			return ++refCount;
19133 		}
19134 		ULONG Release() {
19135 			return --refCount;
19136 		}
19137 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
19138 			if (IID_IUnknown == *riid) {
19139 				*ppv = cast(void*) cast(IUnknown) this;
19140 			}
19141 			else if (IID_IDataObject == *riid) {
19142 				*ppv = cast(void*) cast(IDataObject) this;
19143 			}
19144 			else {
19145 				*ppv = null;
19146 				return E_NOINTERFACE;
19147 			}
19148 
19149 			AddRef();
19150 			return NOERROR;
19151 		}
19152 
19153 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
19154 			// import std.stdio; writeln("Advise");
19155 			return E_NOTIMPL;
19156 		}
19157 		HRESULT DUnadvise(DWORD dwConnection) {
19158 			return E_NOTIMPL;
19159 		}
19160 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
19161 			// import std.stdio; writeln("EnumDAdvise");
19162 			return OLE_E_ADVISENOTSUPPORTED;
19163 		}
19164 		// tell what formats it supports
19165 
19166 		FORMATETC[] types;
19167 		this() {
19168 			FORMATETC t;
19169 			foreach(ty; handler.availableFormats()) {
19170 				assert(ty <= ushort.max && ty >= 0);
19171 				t.cfFormat = cast(ushort) ty;
19172 				t.lindex = -1;
19173 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
19174 				t.tymed = TYMED.TYMED_HGLOBAL;
19175 			}
19176 			types ~= t;
19177 		}
19178 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
19179 			if(dwDirection == DATADIR.DATADIR_GET) {
19180 				*ppenumFormatEtc = new class IEnumFORMATETC {
19181 					ULONG refCount;
19182 					ULONG AddRef() {
19183 						return ++refCount;
19184 					}
19185 					ULONG Release() {
19186 						return --refCount;
19187 					}
19188 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
19189 						if (IID_IUnknown == *riid) {
19190 							*ppv = cast(void*) cast(IUnknown) this;
19191 						}
19192 						else if (IID_IEnumFORMATETC == *riid) {
19193 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
19194 						}
19195 						else {
19196 							*ppv = null;
19197 							return E_NOINTERFACE;
19198 						}
19199 
19200 						AddRef();
19201 						return NOERROR;
19202 					}
19203 
19204 
19205 					int pos;
19206 					this() {
19207 						pos = 0;
19208 					}
19209 
19210 					HRESULT Clone(IEnumFORMATETC* ppenum) {
19211 						// import std.stdio; writeln("clone");
19212 						return E_NOTIMPL; // FIXME
19213 					}
19214 
19215 					// Caller is responsible for freeing memory
19216 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
19217 						// fetched may be null if celt is one
19218 						if(celt != 1)
19219 							return E_NOTIMPL; // FIXME
19220 
19221 						if(celt + pos > types.length)
19222 							return S_FALSE;
19223 
19224 						*rgelt = types[pos++];
19225 
19226 						if(pceltFetched !is null)
19227 							*pceltFetched = 1;
19228 
19229 						// import std.stdio; writeln("ok celt ", celt);
19230 						return S_OK;
19231 					}
19232 
19233 					HRESULT Reset() {
19234 						pos = 0;
19235 						return S_OK;
19236 					}
19237 
19238 					HRESULT Skip(ULONG celt) {
19239 						if(celt + pos <= types.length) {
19240 							pos += celt;
19241 							return S_OK;
19242 						}
19243 						return S_FALSE;
19244 					}
19245 				};
19246 
19247 				return S_OK;
19248 			} else
19249 				return E_NOTIMPL;
19250 		}
19251 		// given a format, return the format you'd prefer to use cuz it is identical
19252 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
19253 			// FIXME: prolly could be better but meh
19254 			// import std.stdio; writeln("gcf: ", *pformatectIn);
19255 			*pformatetcOut = *pformatectIn;
19256 			return S_OK;
19257 		}
19258 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
19259 			foreach(ty; types) {
19260 				if(ty == *pformatetcIn) {
19261 					auto format = ty.cfFormat;
19262 					// import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty);
19263 					STGMEDIUM medium;
19264 					medium.tymed = TYMED.TYMED_HGLOBAL;
19265 
19266 					auto sz = handler.dataLength(format);
19267 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
19268 					if(handle is null) throw new Exception("GlobalAlloc");
19269 					if(auto data = cast(wchar*) GlobalLock(handle)) {
19270 						auto slice = data[0 .. sz];
19271 						scope(exit)
19272 							GlobalUnlock(handle);
19273 
19274 						handler.getData(format, cast(ubyte[]) slice[]);
19275 					}
19276 
19277 
19278 					medium.hGlobal = handle; // FIXME
19279 					*pmedium = medium;
19280 					return S_OK;
19281 				}
19282 			}
19283 			return DV_E_FORMATETC;
19284 		}
19285 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
19286 			// import std.stdio; writeln("GDH: ", *pformatetcIn);
19287 			return E_NOTIMPL; // FIXME
19288 		}
19289 		HRESULT QueryGetData(FORMATETC* pformatetc) {
19290 			auto search = *pformatetc;
19291 			search.tymed &= TYMED.TYMED_HGLOBAL;
19292 			foreach(ty; types)
19293 				if(ty == search) {
19294 					// import std.stdio; writeln("QueryGetData ", search, " ", types[0]);
19295 					return S_OK;
19296 				}
19297 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
19298 				//import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]);
19299 			}
19300 			return S_FALSE;
19301 		}
19302 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
19303 			// import std.stdio; writeln("SetData: ");
19304 			return E_NOTIMPL;
19305 		}
19306 	};
19307 
19308 
19309 	IDropSource src = new class IDropSource {
19310 		ULONG refCount;
19311 		ULONG AddRef() {
19312 			return ++refCount;
19313 		}
19314 		ULONG Release() {
19315 			return --refCount;
19316 		}
19317 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
19318 			if (IID_IUnknown == *riid) {
19319 				*ppv = cast(void*) cast(IUnknown) this;
19320 			}
19321 			else if (IID_IDropSource == *riid) {
19322 				*ppv = cast(void*) cast(IDropSource) this;
19323 			}
19324 			else {
19325 				*ppv = null;
19326 				return E_NOINTERFACE;
19327 			}
19328 
19329 			AddRef();
19330 			return NOERROR;
19331 		}
19332 
19333 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
19334 			if(fEscapePressed)
19335 				return DRAGDROP_S_CANCEL;
19336 			if(!(grfKeyState & MK_LBUTTON))
19337 				return DRAGDROP_S_DROP;
19338 			return S_OK;
19339 		}
19340 
19341 		int GiveFeedback(uint dwEffect) {
19342 			return DRAGDROP_S_USEDEFAULTCURSORS;
19343 		}
19344 	};
19345 
19346 	DWORD effect;
19347 
19348 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
19349 
19350 	DROPEFFECT de = win32DragAndDropAction(action);
19351 
19352 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
19353 	// but still prolly a FIXME
19354 
19355 	auto ret = DoDragDrop(obj, src, de, &effect);
19356 	/+
19357 	import std.stdio;
19358 	if(ret == DRAGDROP_S_DROP)
19359 		writeln("drop ", effect);
19360 	else if(ret == DRAGDROP_S_CANCEL)
19361 		writeln("cancel");
19362 	else if(ret == S_OK)
19363 		writeln("ok");
19364 	else writeln(ret);
19365 	+/
19366 
19367 	return ret;
19368 }
19369 
19370 version(Windows)
19371 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
19372 	DROPEFFECT de;
19373 
19374 	with(DragAndDropAction)
19375 	with(DROPEFFECT)
19376 	final switch(action) {
19377 		case none: de = DROPEFFECT_NONE; break;
19378 		case copy: de = DROPEFFECT_COPY; break;
19379 		case move: de = DROPEFFECT_MOVE; break;
19380 		case link: de = DROPEFFECT_LINK; break;
19381 		case ask: throw new Exception("ask not implemented yet");
19382 		case custom: throw new Exception("custom not implemented yet");
19383 	}
19384 
19385 	return de;
19386 }
19387 
19388 
19389 /++
19390 	History:
19391 		Added February 19, 2021
19392 +/
19393 /// Group: drag_and_drop
19394 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
19395 	version(X11) {
19396 		auto display = XDisplayConnection.get;
19397 
19398 		Atom atom = 5; // right???
19399 
19400 		XChangeProperty(
19401 			display,
19402 			window.impl.window,
19403 			GetAtom!"XdndAware"(display),
19404 			XA_ATOM,
19405 			32 /* bits */,
19406 			PropModeReplace,
19407 			&atom,
19408 			1);
19409 
19410 		window.dropHandler = handler;
19411 	} else version(Windows) {
19412 
19413 		initDnd();
19414 
19415 		auto dropTarget = new class (handler) IDropTarget {
19416 			DropHandler handler;
19417 			this(DropHandler handler) {
19418 				this.handler = handler;
19419 			}
19420 			ULONG refCount;
19421 			ULONG AddRef() {
19422 				return ++refCount;
19423 			}
19424 			ULONG Release() {
19425 				return --refCount;
19426 			}
19427 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
19428 				if (IID_IUnknown == *riid) {
19429 					*ppv = cast(void*) cast(IUnknown) this;
19430 				}
19431 				else if (IID_IDropTarget == *riid) {
19432 					*ppv = cast(void*) cast(IDropTarget) this;
19433 				}
19434 				else {
19435 					*ppv = null;
19436 					return E_NOINTERFACE;
19437 				}
19438 
19439 				AddRef();
19440 				return NOERROR;
19441 			}
19442 
19443 
19444 			// ///////////////////
19445 
19446 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
19447 				DropPackage dropPackage = DropPackage(pDataObj);
19448 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
19449 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
19450 			}
19451 
19452 			HRESULT DragLeave() {
19453 				handler.dragLeave();
19454 				// release the IDataObject if needed
19455 				return S_OK;
19456 			}
19457 
19458 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
19459 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
19460 
19461 				*pdwEffect = win32DragAndDropAction(res.action);
19462 				// same as DragEnter basically
19463 				return S_OK;
19464 			}
19465 
19466 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
19467 				DropPackage pkg = DropPackage(pDataObj);
19468 				handler.drop(&pkg);
19469 
19470 				return S_OK;
19471 			}
19472 		};
19473 		// Windows can hold on to the handler and try to call it
19474 		// during which time the GC can't see it. so important to
19475 		// manually manage this. At some point i'll FIXME and make
19476 		// all my com instances manually managed since they supposed
19477 		// to respect the refcount.
19478 		import core.memory;
19479 		GC.addRoot(cast(void*) dropTarget);
19480 
19481 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
19482 			throw new Exception("register");
19483 
19484 		window.dropHandler = handler;
19485 	} else throw new NotYetImplementedException();
19486 }
19487 
19488 
19489 
19490 static if(UsingSimpledisplayX11) {
19491 
19492 enum _NET_WM_STATE_ADD = 1;
19493 enum _NET_WM_STATE_REMOVE = 0;
19494 enum _NET_WM_STATE_TOGGLE = 2;
19495 
19496 /// X-specific. Use [SimpleWindow.requestAttention] instead for most casesl
19497 void demandAttention(SimpleWindow window, bool needs = true) {
19498 	demandAttention(window.impl.window, needs);
19499 }
19500 
19501 /// ditto
19502 void demandAttention(Window window, bool needs = true) {
19503 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
19504 }
19505 
19506 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
19507 	auto display = XDisplayConnection.get();
19508 	if(atom == None)
19509 		return; // non-failure error
19510 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
19511 
19512 	XClientMessageEvent xclient;
19513 
19514 	xclient.type = EventType.ClientMessage;
19515 	xclient.window = window;
19516 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
19517 	xclient.format = 32;
19518 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
19519 	xclient.data.l[1] = atom;
19520 	xclient.data.l[2] = atom2;
19521 	xclient.data.l[3] = 1;
19522 	// [3] == source. 0 == unknown, 1 == app, 2 == else
19523 
19524 	XSendEvent(
19525 		display,
19526 		RootWindow(display, DefaultScreen(display)),
19527 		false,
19528 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
19529 		cast(XEvent*) &xclient
19530 	);
19531 
19532 	/+
19533 	XChangeProperty(
19534 		display,
19535 		window.impl.window,
19536 		GetAtom!"_NET_WM_STATE"(display),
19537 		XA_ATOM,
19538 		32 /* bits */,
19539 		PropModeAppend,
19540 		&atom,
19541 		1);
19542 	+/
19543 }
19544 
19545 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
19546 	Atom actionAtom;
19547 	with(DragAndDropAction)
19548 	final switch(action) {
19549 		case none: actionAtom = None; break;
19550 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
19551 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
19552 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
19553 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
19554 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
19555 	}
19556 
19557 	return actionAtom;
19558 }
19559 
19560 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
19561 	// FIXME: I need to show user feedback somehow.
19562 	auto display = XDisplayConnection.get;
19563 
19564 	auto actionAtom = dndActionAtom(display, action);
19565 	assert(actionAtom, "Don't use action none to accept a drop");
19566 
19567 	setX11Selection!"XdndSelection"(window, handler, null);
19568 
19569 	auto oldKeyHandler = window.handleKeyEvent;
19570 	scope(exit) window.handleKeyEvent = oldKeyHandler;
19571 
19572 	auto oldCharHandler = window.handleCharEvent;
19573 	scope(exit) window.handleCharEvent = oldCharHandler;
19574 
19575 	auto oldMouseHandler = window.handleMouseEvent;
19576 	scope(exit) window.handleMouseEvent = oldMouseHandler;
19577 
19578 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
19579 
19580 	import core.sys.posix.sys.time;
19581 	timeval tv;
19582 	gettimeofday(&tv, null);
19583 
19584 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
19585 
19586 	Time lastMouseTimestamp;
19587 
19588 	bool dnding = true;
19589 	Window lastIn = None;
19590 
19591 	void leave() {
19592 		if(lastIn == None)
19593 			return;
19594 
19595 		XEvent ev;
19596 		ev.xclient.type = EventType.ClientMessage;
19597 		ev.xclient.window = lastIn;
19598 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
19599 		ev.xclient.format = 32;
19600 		ev.xclient.data.l[0] = window.impl.window;
19601 
19602 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
19603 		XFlush(display);
19604 
19605 		lastIn = None;
19606 	}
19607 
19608 	void enter(Window w) {
19609 		assert(lastIn == None);
19610 
19611 		lastIn = w;
19612 
19613 		XEvent ev;
19614 		ev.xclient.type = EventType.ClientMessage;
19615 		ev.xclient.window = lastIn;
19616 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
19617 		ev.xclient.format = 32;
19618 		ev.xclient.data.l[0] = window.impl.window;
19619 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
19620 
19621 		auto types = handler.availableFormats();
19622 		assert(types.length > 0);
19623 
19624 		ev.xclient.data.l[2] = types[0];
19625 		if(types.length > 1)
19626 			ev.xclient.data.l[3] = types[1];
19627 		if(types.length > 2)
19628 			ev.xclient.data.l[4] = types[2];
19629 
19630 		// FIXME: other types?!?!? and make sure we skip TARGETS
19631 
19632 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
19633 		XFlush(display);
19634 	}
19635 
19636 	void position(int rootX, int rootY) {
19637 		assert(lastIn != None);
19638 
19639 		XEvent ev;
19640 		ev.xclient.type = EventType.ClientMessage;
19641 		ev.xclient.window = lastIn;
19642 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
19643 		ev.xclient.format = 32;
19644 		ev.xclient.data.l[0] = window.impl.window;
19645 		ev.xclient.data.l[1] = 0; // reserved
19646 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
19647 		ev.xclient.data.l[3] = dataTimestamp;
19648 		ev.xclient.data.l[4] = actionAtom;
19649 
19650 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
19651 		XFlush(display);
19652 
19653 	}
19654 
19655 	void drop() {
19656 		XEvent ev;
19657 		ev.xclient.type = EventType.ClientMessage;
19658 		ev.xclient.window = lastIn;
19659 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
19660 		ev.xclient.format = 32;
19661 		ev.xclient.data.l[0] = window.impl.window;
19662 		ev.xclient.data.l[1] = 0; // reserved
19663 		ev.xclient.data.l[2] = dataTimestamp;
19664 
19665 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
19666 		XFlush(display);
19667 
19668 		lastIn = None;
19669 		dnding = false;
19670 	}
19671 
19672 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
19673 	// but idk if i should...
19674 
19675 	window.setEventHandlers(
19676 		delegate(KeyEvent ev) {
19677 			if(ev.pressed == true && ev.key == Key.Escape) {
19678 				// cancel
19679 				dnding = false;
19680 			}
19681 		},
19682 		delegate(MouseEvent ev) {
19683 			if(ev.timestamp < lastMouseTimestamp)
19684 				return;
19685 
19686 			lastMouseTimestamp = ev.timestamp;
19687 
19688 			if(ev.type == MouseEventType.motion) {
19689 				auto display = XDisplayConnection.get;
19690 				auto root = RootWindow(display, DefaultScreen(display));
19691 
19692 				Window topWindow;
19693 				int rootX, rootY;
19694 
19695 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
19696 
19697 				if(topWindow == None)
19698 					return;
19699 
19700 				top:
19701 				if(auto result = topWindow in eligibility) {
19702 					auto dropWindow = *result;
19703 					if(dropWindow == None) {
19704 						leave();
19705 						return;
19706 					}
19707 
19708 					if(dropWindow != lastIn) {
19709 						leave();
19710 						enter(dropWindow);
19711 						position(rootX, rootY);
19712 					} else {
19713 						position(rootX, rootY);
19714 					}
19715 				} else {
19716 					// determine eligibility
19717 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
19718 					if(data.length == 1) {
19719 						// in case there is no WM or it isn't reparenting
19720 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
19721 					} else {
19722 
19723 						Window tryScanChildren(Window search, int maxRecurse) {
19724 							// could be reparenting window manager, so gotta check the next few children too
19725 							Window child;
19726 							int x;
19727 							int y;
19728 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
19729 
19730 							if(child == None)
19731 								return None;
19732 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
19733 							if(data.length == 1) {
19734 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
19735 							} else {
19736 								if(maxRecurse)
19737 									return tryScanChildren(child, maxRecurse - 1);
19738 								else
19739 									return None;
19740 							}
19741 
19742 						}
19743 
19744 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
19745 						auto topResult = tryScanChildren(topWindow, 3);
19746 						// it is easy to have a false negative due to the mouse going over a WM
19747 						// child window like the close button if separate from the frame... so I
19748 						// can't really cache negatives, :(
19749 						if(topResult != None) {
19750 							eligibility[topWindow] = topResult;
19751 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
19752 						}
19753 					}
19754 
19755 				}
19756 
19757 			} else if(ev.type == MouseEventType.buttonReleased) {
19758 				drop();
19759 				dnding = false;
19760 			}
19761 		}
19762 	);
19763 
19764 	window.grabInput();
19765 	scope(exit)
19766 		window.releaseInputGrab();
19767 
19768 
19769 	EventLoop.get.run(() => dnding);
19770 
19771 	return 0;
19772 }
19773 
19774 /// X-specific
19775 TrueColorImage getWindowNetWmIcon(Window window) {
19776 	try {
19777 		auto display = XDisplayConnection.get;
19778 
19779 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
19780 
19781 		if (data.length > arch_ulong.sizeof * 2) {
19782 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
19783 			// these are an array of rgba images that we have to convert into pixmaps ourself
19784 
19785 			int width = cast(int) meta[0];
19786 			int height = cast(int) meta[1];
19787 
19788 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
19789 
19790 			static if(arch_ulong.sizeof == 4) {
19791 				bytes = bytes[0 .. width * height * 4];
19792 				alias imageData = bytes;
19793 			} else static if(arch_ulong.sizeof == 8) {
19794 				bytes = bytes[0 .. width * height * 8];
19795 				auto imageData = new ubyte[](4 * width * height);
19796 			} else static assert(0);
19797 
19798 
19799 
19800 			// this returns ARGB. Remember it is little-endian so
19801 			//                                         we have BGRA
19802 			// our thing uses RGBA, which in little endian, is ABGR
19803 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
19804 				auto r = bytes[idx + 2];
19805 				auto g = bytes[idx + 1];
19806 				auto b = bytes[idx + 0];
19807 				auto a = bytes[idx + 3];
19808 
19809 				imageData[idx2 + 0] = r;
19810 				imageData[idx2 + 1] = g;
19811 				imageData[idx2 + 2] = b;
19812 				imageData[idx2 + 3] = a;
19813 			}
19814 
19815 			return new TrueColorImage(width, height, imageData);
19816 		}
19817 
19818 		return null;
19819 	} catch(Exception e) {
19820 		return null;
19821 	}
19822 }
19823 
19824 } /* UsingSimpledisplayX11 */
19825 
19826 
19827 void loadBinNameToWindowClassName () {
19828 	import core.stdc.stdlib : realloc;
19829 	version(linux) {
19830 		// args[0] MAY be empty, so we'll just use this
19831 		import core.sys.posix.unistd : readlink;
19832 		char[1024] ebuf = void; // 1KB should be enough for everyone!
19833 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
19834 		if (len < 1) return;
19835 	} else /*version(Windows)*/ {
19836 		import core.runtime : Runtime;
19837 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
19838 		auto ebuf = Runtime.args[0];
19839 		auto len = ebuf.length;
19840 	}
19841 	auto pos = len;
19842 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
19843 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
19844 	if (sdpyWindowClassStr is null) return; // oops
19845 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
19846 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
19847 }
19848 
19849 /++
19850 	An interface representing a font.
19851 
19852 	This is still MAJOR work in progress.
19853 +/
19854 interface DrawableFont {
19855 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
19856 }
19857 
19858 /++
19859 	Loads a true type font using [arsd.ttf]. That module must be compiled
19860 	in if you choose to use this function.
19861 
19862 	Be warned: this can be slow and memory hungry, especially on remote connections
19863 	to the X server.
19864 
19865 	This is still MAJOR work in progress.
19866 +/
19867 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
19868 	import arsd.ttf;
19869 	static class ArsdTtfFont : DrawableFont {
19870 		TtfFont font;
19871 		int size;
19872 		this(in ubyte[] data, int size) {
19873 			font = TtfFont(data);
19874 			this.size = size;
19875 		}
19876 
19877 		Sprite[string] cache;
19878 
19879 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
19880 			Sprite sprite = (text in cache) ? *(text in cache) : null;
19881 
19882 			auto fg = painter.impl._outlineColor;
19883 			auto bg = painter.impl._fillColor;
19884 
19885 			if(sprite is null) {
19886 				int width, height;
19887 				auto data = font.renderString(text, size, width, height);
19888 				auto image = new TrueColorImage(width, height);
19889 				int pos = 0;
19890 				foreach(y; 0 .. height)
19891 				foreach(x; 0 .. width) {
19892 					fg.a = data[0];
19893 					bg.a = 255;
19894 					auto color = alphaBlend(fg, bg);
19895 					image.imageData.bytes[pos++] = color.r;
19896 					image.imageData.bytes[pos++] = color.g;
19897 					image.imageData.bytes[pos++] = color.b;
19898 					image.imageData.bytes[pos++] = data[0];
19899 					data = data[1 .. $];
19900 				}
19901 				assert(data.length == 0);
19902 
19903 				sprite = new Sprite(painter.window, Image.fromMemoryImage(image));
19904 				cache[text.idup] = sprite;
19905 			}
19906 
19907 			sprite.drawAt(painter, upperLeft);
19908 		}
19909 	}
19910 
19911 	return new ArsdTtfFont(data, size);
19912 }
19913 
19914 class NotYetImplementedException : Exception {
19915 	this(string file = __FILE__, size_t line = __LINE__) {
19916 		super("Not yet implemented", file, line);
19917 	}
19918 }
19919 
19920 ///
19921 __gshared bool librariesSuccessfullyLoaded = true;
19922 ///
19923 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
19924 
19925 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
19926 	mixin(staticForeachReplacement!Iface);
19927 
19928 	void loadDynamicLibrary() @nogc {
19929 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
19930 	}
19931 
19932         void loadDynamicLibraryForReal() {
19933                 foreach(name; __traits(derivedMembers, Iface)) {
19934                         mixin("alias tmp = " ~ name ~ ";");
19935                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
19936                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
19937                 }
19938         }
19939 }
19940 
19941 private const(char)[] staticForeachReplacement(Iface)() pure {
19942 /*
19943 	// just this for gdc 9....
19944 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
19945 
19946         static foreach(name; __traits(derivedMembers, Iface))
19947                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
19948 */
19949 
19950 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
19951 	size_t pos;
19952 
19953 	void append(in char[] what) {
19954 		if(pos + what.length > code.length)
19955 			code.length = (code.length * 3) / 2;
19956 		code[pos .. pos + what.length] = what[];
19957 		pos += what.length;
19958 	}
19959 
19960         foreach(name; __traits(derivedMembers, Iface)) {
19961                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
19962 		append(name);
19963 		append(`")) `);
19964 		append(name);
19965 		append(";");
19966 	}
19967 
19968 	return code[0 .. pos];
19969 }
19970 
19971 private mixin template DynamicLoad(Iface, string library, int majorVersion, bool openGLRelated = false, bool optional = false) {
19972 	mixin(staticForeachReplacement!Iface);
19973 
19974         private void* libHandle;
19975 	private bool attempted;
19976 
19977         void loadDynamicLibrary() @nogc {
19978 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
19979 	}
19980 
19981 	bool loadAttempted() {
19982 		return attempted;
19983 	}
19984 	bool loadSuccessful() {
19985 		return libHandle !is null;
19986 	}
19987 
19988         void loadDynamicLibraryForReal() {
19989 		attempted = true;
19990                 version(Posix) {
19991                         import core.sys.posix.dlfcn;
19992 			version(OSX) {
19993 				version(X11)
19994                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
19995 				else
19996                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
19997 			} else {
19998                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
19999 				if(libHandle is null)
20000                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
20001 			}
20002 
20003 			static void* loadsym(void* l, const char* name) {
20004 				import core.stdc.stdlib;
20005 				if(l is null)
20006 					return &abort;
20007 				return dlsym(l, name);
20008 			}
20009                 } else version(Windows) {
20010                         import core.sys.windows.windows;
20011                         libHandle = LoadLibrary(library ~ ".dll");
20012 			static void* loadsym(void* l, const char* name) {
20013 				import core.stdc.stdlib;
20014 				if(l is null)
20015 					return &abort;
20016 				return GetProcAddress(l, name);
20017 			}
20018                 }
20019                 if(libHandle is null && !optional) {
20020 			if(openGLRelated)
20021 				openGlLibrariesSuccessfullyLoaded = false;
20022 			else
20023 				librariesSuccessfullyLoaded = false;
20024                         //throw new Exception("load failure of library " ~ library);
20025 		}
20026                 foreach(name; __traits(derivedMembers, Iface)) {
20027                         mixin("alias tmp = " ~ name ~ ";");
20028                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
20029                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
20030                 }
20031         }
20032 
20033         void unloadDynamicLibrary() {
20034                 version(Posix) {
20035                         import core.sys.posix.dlfcn;
20036                         dlclose(libHandle);
20037                 } else version(Windows) {
20038                         import core.sys.windows.windows;
20039                         FreeLibrary(libHandle);
20040                 }
20041                 foreach(name; __traits(derivedMembers, Iface))
20042                         mixin(name ~ " = null;");
20043         }
20044 }
20045 
20046 void guiAbortProcess(string msg) {
20047 	import core.stdc.stdlib;
20048 	version(Windows) {
20049 		WCharzBuffer t = WCharzBuffer(msg);
20050 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
20051 	} else {
20052 		import std.stdio;
20053 		stderr.writeln(msg);
20054 		stderr.flush();
20055 	}
20056 
20057 	abort();
20058 }
20059 
20060 private alias scriptable = arsd_jsvar_compatible;