1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 /+
4 	To share some stuff between two opengl threads:
5 	windows
6 	https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
7 	linux
8 	https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
9 +/
10 
11 
12 // Search for: FIXME: leaks if multithreaded gc
13 
14 // https://freedesktop.org/wiki/Specifications/XDND/
15 
16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
17 
18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
20 
21 
22 // on Mac with X11: -L-L/usr/X11/lib
23 
24 /+
25 
26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
27 
28 	Progress bar in taskbar
29 		- i can probably just set a property on the window...
30 		  it sets that prop to an integer 0 .. 100. Taskbar
31 		  deletes it or window deletes it when it is handled.
32 		- prolly display it as a nice little line at the bottom.
33 
34 
35 from gtk:
36 
37 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
38 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
39 
40 >+  if (cardinal > 0)
41 >+  {
42 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
45 >+                     XA_CARDINAL, 32,
46 >+                     PropModeReplace,
47 >+                     (guchar *) &cardinal, 1);
48 >+  }
49 >+  else
50 >+  {
51 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
52 >+                     xid,
53 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
54 >+  }
55 
56 from Windows:
57 
58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
59 
60 interface
61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
63 listen for msg, return TRUE
64 interface->SetProgressState(hwnd, TBPF_NORMAL);
65 interface->SetProgressValue(hwnd, 40, 100);
66 
67 
68 	My new notification system.
69 		- use a unix socket? or a x property? or a udp port?
70 		- could of course also get on the dbus train but ugh.
71 		- it could also reply with the info as a string for easy remote examination.
72 
73 +/
74 
75 /*
76 	Event Loop would be nices:
77 
78 	* add on idle - runs when nothing else happens
79 		* which can specify how long to yield for
80 	* send messages without a recipient window
81 	* setTimeout
82 	* setInterval
83 */
84 
85 /*
86 	Classic games I want to add:
87 		* my tetris clone
88 		* pac man
89 */
90 
91 /*
92 	Text layout needs a lot of work. Plain drawText is useful but too
93 	limited. It will need some kind of text context thing which it will
94 	update and you can pass it on and get more details out of it.
95 
96 	It will need a bounding box, a current cursor location that is updated
97 	as drawing continues, and various changable facts (which can also be
98 	changed on the painter i guess) like font, color, size, background,
99 	etc.
100 
101 	We can also fetch the caret location from it somehow.
102 
103 	Should prolly be an overload of drawText
104 
105 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
106 
107 		WS_EX_NOACTIVATE
108 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
109 		full screen windows. Can just set the atom on X. Windows will be harder.
110 
111 		moving windows. resizing windows.
112 
113 		hide cursor, capture cursor, change cursor.
114 
115 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
116 	sure the pieces are there to do its job easily and make other jobs possible.
117 */
118 
119 /++
120 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
121 	including creating windows, drawing on them, working with the clipboard,
122 	timers, OpenGL, and more. However, it does NOT provide high level GUI
123 	widgets. See my minigui.d, an extension to this module, for that
124 	functionality.
125 
126 	simpledisplay provides cross-platform wrapping for Windows and Linux
127 	(and perhaps other OSes that use X11), but also does not prevent you
128 	from using the underlying facilities if you need them. It has a goal
129 	of working efficiently over a remote X link (at least as far as Xlib
130 	reasonably allows.)
131 
132 	simpledisplay depends on [arsd.color|color.d], which should be available from the
133 	same place where you got this file. Other than that, however, it has
134 	very few dependencies and ones that don't come with the OS and/or the
135 	compiler are all opt-in.
136 
137 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
138 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
139 
140 	simpledisplay is basically stable. I plan to refactor the internals,
141 	and may add new features and fix bugs, but It do not expect to
142 	significantly change the API. It has been stable a few years already now.
143 
144 	Installation_instructions:
145 
146 	`simpledisplay.d` does not have any dependencies outside the
147 	operating system and `color.d`, so it should just work most the
148 	time, but there are a few caveats on some systems:
149 
150 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
151 	console to be automatically allocated.
152 
153 	Please note when compiling on Win64, you need to explicitly list
154 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
155 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
156 
157 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
158 	note the "w".
159 
160 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
161 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
162 	See [EnableWindowsSubsystem] for more information.
163 
164 	$(PITFALL
165 		With the Windows subsystem, there is no console, so standard writeln will throw!
166 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
167 		create a console as needed.
168 	)
169 
170 	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.
171 
172 	On Ubuntu, you might need to install X11 development libraries to
173 	successfully link.
174 
175 	$(CONSOLE
176 		$ sudo apt-get install libglc-dev
177 		$ sudo apt-get install libx11-dev
178 	)
179 
180 
181 	Jump_list:
182 
183 	Don't worry, you don't have to read this whole documentation file!
184 
185 	Check out the [#event-example] and [#Pong-example] to get started quickly.
186 
187 	The main classes you may want to create are [SimpleWindow], [Timer],
188 	[Image], and [Sprite].
189 
190 	The main functions you'll want are [setClipboardText] and [getClipboardText].
191 
192 	There are also platform-specific functions available such as [XDisplayConnection]
193 	and [GetAtom] for X11, among others.
194 
195 	See the examples and topics list below to learn more.
196 
197 	$(WARNING
198 		There should only be one GUI thread per application,
199 		and all windows should be created in it and your
200 		event loop should run there.
201 
202 		To do otherwise is undefined behavior and has no
203 		cross platform guarantees.
204 	)
205 
206 	$(H2 About this documentation)
207 
208 	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.
209 
210 	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!
211 
212 	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.
213 
214 	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.
215 
216 	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.
217 
218 	At points, I will talk about implementation details in the documentation. These are sometimes
219 	subject to change, but nevertheless useful to understand what is really going on. You can learn
220 	more about some of the referenced things by searching the web for info about using them from C.
221 	You can always look at the source of simpledisplay.d too for the most authoritative source on
222 	its specific implementation. If you disagree with how I did something, please contact me so we
223 	can discuss it!
224 
225 	$(H2 Using with fibers)
226 
227 	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).
228 
229 	$(H2 Topics)
230 
231 	$(H3 $(ID topic-windows) Windows)
232 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
233 		window on the user's screen.
234 
235 		You may create multiple windows, if the underlying platform supports it. You may check
236 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
237 		SimpleWindow's constructor at runtime to handle those cases.
238 
239 		A single running event loop will handle as many windows as needed.
240 
241 	$(H3 $(ID topic-event-loops) Event loops)
242 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
243 
244 		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:
245 
246 		---
247 		// dmd example.d simpledisplay.d color.d
248 		import arsd.simpledisplay;
249 		void main() {
250 			auto window = new SimpleWindow(200, 200);
251 			window.eventLoop(0,
252 			  delegate (dchar) { /* got a character key press */ }
253 			);
254 		}
255 		---
256 
257 		$(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.)
258 
259 		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.
260 
261 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
262 
263 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
264 
265 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
266 
267 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
268 		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.
269 
270 		See the [NotificationAreaIcon] class.
271 
272 	$(H3 $(ID topic-input-handling) Input handling)
273 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
274 
275 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
276 
277 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
278 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
279 
280 		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:
281 
282 		---
283 		// dmd example.d simpledisplay.d color.d
284 		import arsd.simpledisplay;
285 		void main() {
286 			auto window = new SimpleWindow(200, 200);
287 			{ // introduce sub-scope
288 				auto painter = window.draw(); // begin drawing
289 				/* draw here */
290 				painter.outlineColor = Color.red;
291 				painter.fillColor = Color.black;
292 				painter.drawRectangle(Point(0, 0), 200, 200);
293 			} // end scope, calling `painter`'s destructor, drawing to the screen.
294 			window.eventLoop(0); // handle events
295 		}
296 		---
297 
298 		Painting is done based on two color properties, a pen and a brush.
299 
300 		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.
301 
302 		FIXME Add example of 2d opengl drawing here.
303 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
304 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
305 
306 		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.
307 
308 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
309 
310 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
311 
312 		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].
313 
314 		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.
315 
316 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
317 
318 		---
319 		import arsd.simpledisplay;
320 
321 		void main() {
322 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
323 
324 			float otherColor = 0.0;
325 			float colorDelta = 0.05;
326 
327 			window.redrawOpenGlScene = delegate() {
328 				glLoadIdentity();
329 				glBegin(GL_QUADS);
330 
331 				glColor3f(1.0, otherColor, 0);
332 				glVertex3f(-0.8, -0.8, 0);
333 
334 				glColor3f(1.0, otherColor, 1.0);
335 				glVertex3f(0.8, -0.8, 0);
336 
337 				glColor3f(0, 1.0, otherColor);
338 				glVertex3f(0.8, 0.8, 0);
339 
340 				glColor3f(otherColor, 0, 1.0);
341 				glVertex3f(-0.8, 0.8, 0);
342 
343 				glEnd();
344 			};
345 
346 			window.eventLoop(50, () {
347 				otherColor += colorDelta;
348 				if(otherColor > 1.0) {
349 					otherColor = 1.0;
350 					colorDelta = -0.05;
351 				}
352 				if(otherColor < 0) {
353 					otherColor = 0;
354 					colorDelta = 0.05;
355 				}
356 				// at the end of the timer, we have to request a redraw
357 				// or we won't see the changes.
358 				window.redrawOpenGlSceneSoon();
359 			});
360 		}
361 		---
362 
363 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
364 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
365 		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 provides helpers for this too.
366 
367 		This example program shows how you can set up a shader to draw a rectangle:
368 
369 		---
370 		module opengl3test;
371 		import arsd.simpledisplay;
372 
373 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
374 
375 		void main() {
376 			// First thing we do, before creating the window, is declare what version we want.
377 			setOpenGLContextVersion(3, 3);
378 			// turning off legacy compat is required to use version 3.3 and newer
379 			openGLContextCompatible = false;
380 
381 			uint VAO;
382 			OpenGlShader shader;
383 
384 			// then we can create the window.
385 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
386 
387 			// additional setup needs to be done when it is visible, simpledisplay offers a property
388 			// for exactly that:
389 			window.visibleForTheFirstTime = delegate() {
390 				// now with the window loaded, we can start loading the modern opengl functions.
391 
392 				// you MUST set the context first.
393 				window.setAsCurrentOpenGlContext;
394 				// then load the remainder of the library
395 				gl3.loadDynamicLibrary();
396 
397 				// now you can create the shaders, etc.
398 				shader = new OpenGlShader(
399 					OpenGlShader.Source(GL_VERTEX_SHADER, `
400 						#version 330 core
401 						layout (location = 0) in vec3 aPos;
402 						void main() {
403 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
404 						}
405 					`),
406 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
407 						#version 330 core
408 						out vec4 FragColor;
409 						uniform vec4 mycolor;
410 						void main() {
411 							FragColor = mycolor;
412 						}
413 					`),
414 				);
415 
416 				// and do whatever other setup you want.
417 
418 				float[] vertices = [
419 					0.5f,  0.5f, 0.0f,  // top right
420 					0.5f, -0.5f, 0.0f,  // bottom right
421 					-0.5f, -0.5f, 0.0f,  // bottom left
422 					-0.5f,  0.5f, 0.0f   // top left
423 				];
424 				uint[] indices = [  // note that we start from 0!
425 					0, 1, 3,  // first Triangle
426 					1, 2, 3   // second Triangle
427 				];
428 				uint VBO, EBO;
429 				glGenVertexArrays(1, &VAO);
430 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
431 				glBindVertexArray(VAO);
432 
433 				glGenBuffers(1, &VBO);
434 				glGenBuffers(1, &EBO);
435 
436 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
437 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
438 
439 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
440 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
441 
442 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
443 				glEnableVertexAttribArray(0);
444 
445 				// the library will set the initial viewport and trigger our first draw,
446 				// so these next two lines are NOT needed. they are just here as comments
447 				// to show what would happen next.
448 
449 				// glViewport(0, 0, window.width, window.height);
450 				// window.redrawOpenGlSceneNow();
451 			};
452 
453 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
454 			// it is our render method.
455 			window.redrawOpenGlScene = delegate() {
456 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
457 				glClear(GL_COLOR_BUFFER_BIT);
458 
459 				glUseProgram(shader.shaderProgram);
460 
461 				// the shader helper class has methods to set uniforms too
462 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
463 
464 				glBindVertexArray(VAO);
465 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
466 			};
467 
468 			window.eventLoop(0);
469 		}
470 		---
471 
472 	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.
473 
474 
475 	$(H3 $(ID topic-images) Displaying images)
476 		You can also load PNG images using [arsd.png].
477 
478 		---
479 		// dmd example.d simpledisplay.d color.d png.d
480 		import arsd.simpledisplay;
481 		import arsd.png;
482 
483 		void main() {
484 			auto image = Image.fromMemoryImage(readPng("image.png"));
485 			displayImage(image);
486 		}
487 		---
488 
489 		Compile with `dmd example.d simpledisplay.d png.d`.
490 
491 		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.
492 
493 	$(H3 $(ID topic-sprites) Sprites)
494 		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.
495 
496 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
497 
498 	$(H3 $(ID topic-clipboard) Clipboard)
499 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
500 
501 		It also has helpers for handling X-specific events.
502 
503 	$(H3 $(ID topic-dnd) Drag and Drop)
504 		See [enableDragAndDrop] and [draggable].
505 
506 	$(H3 $(ID topic-timers) Timers)
507 		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].
508 
509 		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.
510 
511 		---
512 			import arsd.simpledisplay;
513 
514 			void main() {
515 				auto window = new SimpleWindow(400, 400);
516 				// every 100 ms, it will draw a random line
517 				// on the window.
518 				window.eventLoop(100, {
519 					auto painter = window.draw();
520 
521 					import std.random;
522 					// random color
523 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
524 					// random line
525 					painter.drawLine(
526 						Point(uniform(0, window.width), uniform(0, window.height)),
527 						Point(uniform(0, window.width), uniform(0, window.height)));
528 
529 				});
530 			}
531 		---
532 
533 		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.
534 
535 		The pulse timer and instances of the [Timer] class may be combined at will.
536 
537 		---
538 			import arsd.simpledisplay;
539 
540 			void main() {
541 				auto window = new SimpleWindow(400, 400);
542 				auto timer = new Timer(1000, delegate {
543 					auto painter = window.draw();
544 					painter.clear();
545 				});
546 
547 				window.eventLoop(0);
548 			}
549 		---
550 
551 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
552 
553 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
554 		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.
555 
556 		See also: `xwindows.d` from my github.
557 
558 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
559 		`handleNativeEvent` and `handleNativeGlobalEvent`.
560 
561 	$(H3 $(ID topic-integration) Integration with other libraries)
562 		Integration with a third-party event loop is possible.
563 
564 		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.
565 
566 	$(H3 $(ID topic-guis) GUI widgets)
567 		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!
568 
569 		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.
570 
571 		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.)
572 
573 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
574 
575 	$(H2 Platform-specific tips and tricks)
576 
577 	X_tips:
578 
579 	On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release.
580 
581 	Windows_tips:
582 
583 	You can add icons or manifest files to your exe using a resource file.
584 
585 	To create a Windows .ico file, use the gimp or something. I'll write a helper
586 	program later.
587 
588 	Create `yourapp.rc`:
589 
590 	```rc
591 		1 ICON filename.ico
592 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
593 	```
594 
595 	And `yourapp.exe.manifest`:
596 
597 	```xml
598 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
599 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
600 		<assemblyIdentity
601 		    version="1.0.0.0"
602 		    processorArchitecture="*"
603 		    name="CompanyName.ProductName.YourApplication"
604 		    type="win32"
605 		/>
606 		<description>Your application description here.</description>
607 		<dependency>
608 		    <dependentAssembly>
609 			<assemblyIdentity
610 			    type="win32"
611 			    name="Microsoft.Windows.Common-Controls"
612 			    version="6.0.0.0"
613 			    processorArchitecture="*"
614 			    publicKeyToken="6595b64144ccf1df"
615 			    language="*"
616 			/>
617 		    </dependentAssembly>
618 		</dependency>
619 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
620 			<windowsSettings>
621 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
622 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
623 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
624 				<!-- to render crisply in DPI-unaware contexts -->
625 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
626 			</windowsSettings>
627 		</application>
628 		</assembly>
629 	```
630 
631 	You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`.
632 
633 	Doing this lets you opt into various new things since Windows XP.
634 
635 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
636 
637 	$(H2 Tips)
638 
639 	$(H3 Name conflicts)
640 
641 	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:
642 
643 	---
644 	static import sdpy = arsd.simpledisplay;
645 	import arsd.simpledisplay : SimpleWindow;
646 
647 	void main() {
648 		auto window = new SimpleWindow();
649 		sdpy.EventLoop.get.run();
650 	}
651 	---
652 
653 	$(H2 $(ID developer-notes) Developer notes)
654 
655 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
656 	implementation though.
657 
658 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
659 	suck. If I was rewriting it, I wouldn't do it that way again.
660 
661 	This file must not have any more required dependencies. If you need bindings, add
662 	them right to this file. Once it gets into druntime and is there for a while, remove
663 	bindings from here to avoid conflicts (or put them in an appropriate version block
664 	so it continues to just work on old dmd), but wait a couple releases before making the
665 	transition so this module remains usable with older versions of dmd.
666 
667 	You may have optional dependencies if needed by putting them in version blocks or
668 	template functions. You may also extend the module with other modules with UFCS without
669 	actually editing this - that is nice to do if you can.
670 
671 	Try to make functions work the same way across operating systems. I typically make
672 	it thinly wrap Windows, then emulate that on Linux.
673 
674 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
675 	Phobos! So try to avoid it.
676 
677 	See more comments throughout the source.
678 
679 	I realize this file is fairly large, but over half that is just bindings at the bottom
680 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
681 	to understand. I suggest you jump around the source by looking for a particular
682 	declaration you're interested in, like `class SimpleWindow` using your editor's search
683 	function, then look at one piece at a time.
684 
685 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
686 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
687 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
688 
689 	I live in the eastern United States, so I will most likely not be around at night in
690 	that US east timezone.
691 
692 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
693 
694 	Building documentation: use my adrdox generator, `dub run adrdox`.
695 
696 	Examples:
697 
698 	$(DIV $(ID Event-example))
699 	$(H3 $(ID event-example) Event example)
700 	This program creates a window and draws events inside them as they
701 	happen, scrolling the text in the window as needed. Run this program
702 	and experiment to get a feel for where basic input events take place
703 	in the library.
704 
705 	---
706 	// dmd example.d simpledisplay.d color.d
707 	import arsd.simpledisplay;
708 	import std.conv;
709 
710 	void main() {
711 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
712 
713 		int y = 0;
714 
715 		void addLine(string text) {
716 			auto painter = window.draw();
717 
718 			if(y + painter.fontHeight >= window.height) {
719 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
720 				y -= painter.fontHeight;
721 			}
722 
723 			painter.outlineColor = Color.red;
724 			painter.fillColor = Color.black;
725 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
726 
727 			painter.outlineColor = Color.white;
728 
729 			painter.drawText(Point(10, y), text);
730 
731 			y += painter.fontHeight;
732 		}
733 
734 		window.eventLoop(1000,
735 		  () {
736 			addLine("Timer went off!");
737 		  },
738 		  (KeyEvent event) {
739 			addLine(to!string(event));
740 		  },
741 		  (MouseEvent event) {
742 			addLine(to!string(event));
743 		  },
744 		  (dchar ch) {
745 			addLine(to!string(ch));
746 		  }
747 		);
748 	}
749 	---
750 
751 	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.
752 
753 	$(COMMENT
754 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
755 
756 	---
757 
758 	---
759 	)
760 
761 	History:
762 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
763 
764 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
765 +/
766 module arsd.simpledisplay;
767 
768 import arsd.core;
769 
770 // FIXME: tetris demo
771 // FIXME: space invaders demo
772 // FIXME: asteroids demo
773 
774 /++ $(ID Pong-example)
775 	$(H3 Pong)
776 
777 	This program creates a little Pong-like game. Player one is controlled
778 	with the keyboard.  Player two is controlled with the mouse. It demos
779 	the pulse timer, event handling, and some basic drawing.
780 +/
781 version(demos)
782 unittest {
783 	// dmd example.d simpledisplay.d color.d
784 	import arsd.simpledisplay;
785 
786 	enum paddleMovementSpeed = 8;
787 	enum paddleHeight = 48;
788 
789 	void main() {
790 		auto window = new SimpleWindow(600, 400, "Pong game!");
791 
792 		int playerOnePosition, playerTwoPosition;
793 		int playerOneMovement, playerTwoMovement;
794 		int playerOneScore, playerTwoScore;
795 
796 		int ballX, ballY;
797 		int ballDx, ballDy;
798 
799 		void serve() {
800 			import std.random;
801 
802 			ballX = window.width / 2;
803 			ballY = window.height / 2;
804 			ballDx = uniform(-4, 4) * 3;
805 			ballDy = uniform(-4, 4) * 3;
806 			if(ballDx == 0)
807 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
808 		}
809 
810 		serve();
811 
812 		window.eventLoop(50, // set a 50 ms timer pulls
813 			// This runs once per timer pulse
814 			delegate () {
815 				auto painter = window.draw();
816 
817 				painter.clear();
818 
819 				// Update everyone's motion
820 				playerOnePosition += playerOneMovement;
821 				playerTwoPosition += playerTwoMovement;
822 
823 				ballX += ballDx;
824 				ballY += ballDy;
825 
826 				// Bounce off the top and bottom edges of the window
827 				if(ballY + 7 >= window.height)
828 					ballDy = -ballDy;
829 				if(ballY - 8 <= 0)
830 					ballDy = -ballDy;
831 
832 				// Bounce off the paddle, if it is in position
833 				if(ballX - 8 <= 16) {
834 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
835 						ballDx = -ballDx + 1; // add some speed to keep it interesting
836 						ballDy += playerOneMovement; // and y movement based on your controls too
837 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
838 					} else {
839 						// Missed it
840 						playerTwoScore ++;
841 						serve();
842 					}
843 				}
844 
845 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
846 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
847 						ballDx = -ballDx - 1;
848 						ballDy += playerTwoMovement;
849 						ballX = window.width - 24;
850 					} else {
851 						// Missed it
852 						playerOneScore ++;
853 						serve();
854 					}
855 				}
856 
857 				// Draw the paddles
858 				painter.outlineColor = Color.black;
859 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
860 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
861 
862 				// Draw the ball
863 				painter.fillColor = Color.red;
864 				painter.outlineColor = Color.yellow;
865 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
866 
867 				// Draw the score
868 				painter.outlineColor = Color.blue;
869 				import std.conv;
870 				painter.drawText(Point(64, 4), to!string(playerOneScore));
871 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
872 
873 			},
874 			delegate (KeyEvent event) {
875 				// Player 1's controls are the arrow keys on the keyboard
876 				if(event.key == Key.Down)
877 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
878 				if(event.key == Key.Up)
879 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
880 
881 			},
882 			delegate (MouseEvent event) {
883 				// Player 2's controls are mouse movement while the left button is held down
884 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
885 					if(event.dy > 0)
886 						playerTwoMovement = paddleMovementSpeed;
887 					else if(event.dy < 0)
888 						playerTwoMovement = -paddleMovementSpeed;
889 				} else {
890 					playerTwoMovement = 0;
891 				}
892 			}
893 		);
894 	}
895 }
896 
897 /++ $(H3 $(ID example-minesweeper) Minesweeper)
898 
899 	This minesweeper demo shows how we can implement another classic
900 	game with simpledisplay and shows some mouse input and basic output
901 	code.
902 +/
903 version(demos)
904 unittest {
905 	import arsd.simpledisplay;
906 
907 	enum GameSquare {
908 		mine = 0,
909 		clear,
910 		m1, m2, m3, m4, m5, m6, m7, m8
911 	}
912 
913 	enum UserSquare {
914 		unknown,
915 		revealed,
916 		flagged,
917 		questioned
918 	}
919 
920 	enum GameState {
921 		inProgress,
922 		lose,
923 		win
924 	}
925 
926 	GameSquare[] board;
927 	UserSquare[] userState;
928 	GameState gameState;
929 	int boardWidth;
930 	int boardHeight;
931 
932 	bool isMine(int x, int y) {
933 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
934 			return false;
935 		return board[y * boardWidth + x] == GameSquare.mine;
936 	}
937 
938 	GameState reveal(int x, int y) {
939 		if(board[y * boardWidth + x] == GameSquare.clear) {
940 			floodFill(userState, boardWidth, boardHeight,
941 				UserSquare.unknown, UserSquare.revealed,
942 				x, y,
943 				(x, y) {
944 					if(board[y * boardWidth + x] == GameSquare.clear)
945 						return true;
946 					else {
947 						userState[y * boardWidth + x] = UserSquare.revealed;
948 						return false;
949 					}
950 				});
951 		} else {
952 			userState[y * boardWidth + x] = UserSquare.revealed;
953 			if(isMine(x, y))
954 				return GameState.lose;
955 		}
956 
957 		foreach(state; userState) {
958 			if(state == UserSquare.unknown || state == UserSquare.questioned)
959 				return GameState.inProgress;
960 		}
961 
962 		return GameState.win;
963 	}
964 
965 	void initializeBoard(int width, int height, int numberOfMines) {
966 		boardWidth = width;
967 		boardHeight = height;
968 		board.length = width * height;
969 
970 		userState.length = width * height;
971 		userState[] = UserSquare.unknown;
972 
973 		import std.algorithm, std.random, std.range;
974 
975 		board[] = GameSquare.clear;
976 
977 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
978 			board[minePosition] = GameSquare.mine;
979 
980 		int x;
981 		int y;
982 		foreach(idx, ref square; board) {
983 			if(square == GameSquare.clear) {
984 				int danger = 0;
985 				danger += isMine(x-1, y-1)?1:0;
986 				danger += isMine(x-1, y)?1:0;
987 				danger += isMine(x-1, y+1)?1:0;
988 				danger += isMine(x, y-1)?1:0;
989 				danger += isMine(x, y+1)?1:0;
990 				danger += isMine(x+1, y-1)?1:0;
991 				danger += isMine(x+1, y)?1:0;
992 				danger += isMine(x+1, y+1)?1:0;
993 
994 				square = cast(GameSquare) (danger + 1);
995 			}
996 
997 			x++;
998 			if(x == width) {
999 				x = 0;
1000 				y++;
1001 			}
1002 		}
1003 	}
1004 
1005 	void redraw(SimpleWindow window) {
1006 		import std.conv;
1007 
1008 		auto painter = window.draw();
1009 
1010 		painter.clear();
1011 
1012 		final switch(gameState) with(GameState) {
1013 			case inProgress:
1014 				break;
1015 			case win:
1016 				painter.fillColor = Color.green;
1017 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1018 				return;
1019 			case lose:
1020 				painter.fillColor = Color.red;
1021 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1022 				return;
1023 		}
1024 
1025 		int x = 0;
1026 		int y = 0;
1027 
1028 		foreach(idx, square; board) {
1029 			auto state = userState[idx];
1030 
1031 			final switch(state) with(UserSquare) {
1032 				case unknown:
1033 					painter.outlineColor = Color.black;
1034 					painter.fillColor = Color(128,128,128);
1035 
1036 					painter.drawRectangle(
1037 						Point(x * 20, y * 20),
1038 						20, 20
1039 					);
1040 				break;
1041 				case revealed:
1042 					if(square == GameSquare.clear) {
1043 						painter.outlineColor = Color.white;
1044 						painter.fillColor = Color.white;
1045 
1046 						painter.drawRectangle(
1047 							Point(x * 20, y * 20),
1048 							20, 20
1049 						);
1050 					} else {
1051 						painter.outlineColor = Color.black;
1052 						painter.fillColor = Color.white;
1053 
1054 						painter.drawText(
1055 							Point(x * 20, y * 20),
1056 							to!string(square)[1..2],
1057 							Point(x * 20 + 20, y * 20 + 20),
1058 							TextAlignment.Center | TextAlignment.VerticalCenter);
1059 					}
1060 				break;
1061 				case flagged:
1062 					painter.outlineColor = Color.black;
1063 					painter.fillColor = Color.red;
1064 					painter.drawRectangle(
1065 						Point(x * 20, y * 20),
1066 						20, 20
1067 					);
1068 				break;
1069 				case questioned:
1070 					painter.outlineColor = Color.black;
1071 					painter.fillColor = Color.yellow;
1072 					painter.drawRectangle(
1073 						Point(x * 20, y * 20),
1074 						20, 20
1075 					);
1076 				break;
1077 			}
1078 
1079 			x++;
1080 			if(x == boardWidth) {
1081 				x = 0;
1082 				y++;
1083 			}
1084 		}
1085 
1086 	}
1087 
1088 	void main() {
1089 		auto window = new SimpleWindow(200, 200);
1090 
1091 		initializeBoard(10, 10, 10);
1092 
1093 		redraw(window);
1094 		window.eventLoop(0,
1095 			delegate (MouseEvent me) {
1096 				if(me.type != MouseEventType.buttonPressed)
1097 					return;
1098 				auto x = me.x / 20;
1099 				auto y = me.y / 20;
1100 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1101 					if(me.button == MouseButton.left) {
1102 						gameState = reveal(x, y);
1103 					} else {
1104 						userState[y*boardWidth+x] = UserSquare.flagged;
1105 					}
1106 					redraw(window);
1107 				}
1108 			}
1109 		);
1110 	}
1111 }
1112 
1113 import arsd.core;
1114 
1115 // FIXME: tetris demo
1116 // FIXME: space invaders demo
1117 // FIXME: asteroids demo
1118 
1119 
1120 /*
1121 version(OSX) {
1122 	version=without_opengl;
1123 	version=allow_unimplemented_features;
1124 	version=OSXCocoa;
1125 	pragma(linkerDirective, "-framework Cocoa");
1126 }
1127 */
1128 
1129 version(without_opengl) {
1130 	enum SdpyIsUsingIVGLBinds = false;
1131 } else /*version(Posix)*/ {
1132 	static if (__traits(compiles, (){import iv.glbinds;})) {
1133 		enum SdpyIsUsingIVGLBinds = true;
1134 		public import iv.glbinds;
1135 		//pragma(msg, "SDPY: using iv.glbinds");
1136 	} else {
1137 		enum SdpyIsUsingIVGLBinds = false;
1138 	}
1139 //} else {
1140 //	enum SdpyIsUsingIVGLBinds = false;
1141 }
1142 
1143 
1144 version(Windows) {
1145 	//import core.sys.windows.windows;
1146 	import core.sys.windows.winnls;
1147 	import core.sys.windows.windef;
1148 	import core.sys.windows.basetyps;
1149 	import core.sys.windows.winbase;
1150 	import core.sys.windows.winuser;
1151 	import core.sys.windows.shellapi;
1152 	import core.sys.windows.wingdi;
1153 	static import gdi = core.sys.windows.wingdi; // so i
1154 
1155 	pragma(lib, "gdi32");
1156 	pragma(lib, "user32");
1157 
1158 	// for AlphaBlend... a breaking change....
1159 	version(CRuntime_DigitalMars) { } else
1160 		pragma(lib, "msimg32");
1161 } else version (linux) {
1162 	//k8: this is hack for rdmd. sorry.
1163 	static import core.sys.linux.epoll;
1164 	static import core.sys.linux.timerfd;
1165 }
1166 
1167 
1168 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1169 
1170 // http://wiki.dlang.org/Simpledisplay.d
1171 
1172 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1173 
1174 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1175 // but can i control the scroll lock led
1176 
1177 
1178 // Note: if you are using Image on X, you might want to do:
1179 /*
1180 	static if(UsingSimpledisplayX11) {
1181 		if(!Image.impl.xshmAvailable) {
1182 			// the images will use the slower XPutImage, you might
1183 			// want to consider an alternative method to get better speed
1184 		}
1185 	}
1186 
1187 	If the shared memory extension is available though, simpledisplay uses it
1188 	for a significant speed boost whenever you draw large Images.
1189 */
1190 
1191 // 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.
1192 
1193 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1194 
1195 /*
1196 	Biggest FIXME:
1197 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1198 
1199 		clean up opengl contexts when their windows close
1200 
1201 		fix resizing the bitmaps/pixmaps
1202 */
1203 
1204 // BTW on Windows:
1205 // -L/SUBSYSTEM:WINDOWS:5.0
1206 // to dmd will make a nice windows binary w/o a console if you want that.
1207 
1208 /*
1209 	Stuff to add:
1210 
1211 	use multibyte functions everywhere we can
1212 
1213 	OpenGL windows
1214 	more event stuff
1215 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1216 
1217 
1218 	resizeEvent
1219 		and make the windows non-resizable by default,
1220 		or perhaps stretched (if I can find something in X like StretchBlt)
1221 
1222 	take a screenshot function!
1223 
1224 	Pens and brushes?
1225 	Maybe a global event loop?
1226 
1227 	Mouse deltas
1228 	Key items
1229 */
1230 
1231 /*
1232 From MSDN:
1233 
1234 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1235 
1236 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.
1237 
1238 */
1239 
1240 version(linux) {
1241 	version = X11;
1242 	version(without_libnotify) {
1243 		// we cool
1244 	}
1245 	else
1246 		version = libnotify;
1247 }
1248 
1249 version(libnotify) {
1250 	pragma(lib, "dl");
1251 	import core.sys.posix.dlfcn;
1252 
1253 	void delegate()[int] libnotify_action_delegates;
1254 	int libnotify_action_delegates_count;
1255 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1256 		auto idx = cast(int) user_data;
1257 		if(auto dgptr = idx in libnotify_action_delegates) {
1258 			(*dgptr)();
1259 			libnotify_action_delegates.remove(idx);
1260 		}
1261 	}
1262 
1263 	struct C_DynamicLibrary {
1264 		void* handle;
1265 		this(string name) {
1266 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1267 			if(handle is null)
1268 				throw new Exception("dlopen");
1269 		}
1270 
1271 		void close() {
1272 			dlclose(handle);
1273 		}
1274 
1275 		~this() {
1276 			// close
1277 		}
1278 
1279 		// FIXME: this looks up by name every time....
1280 		template call(string func, Ret, Args...) {
1281 			extern(C) Ret function(Args) fptr;
1282 			typeof(fptr) call() {
1283 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1284 				return fptr;
1285 			}
1286 		}
1287 	}
1288 
1289 	C_DynamicLibrary* libnotify;
1290 }
1291 
1292 version(OSX) {
1293 	version(OSXCocoa) {}
1294 	else { version = X11; }
1295 }
1296 	//version = OSXCocoa; // this was written by KennyTM
1297 version(FreeBSD)
1298 	version = X11;
1299 version(Solaris)
1300 	version = X11;
1301 
1302 version(X11) {
1303 	version(without_xft) {}
1304 	else version=with_xft;
1305 }
1306 
1307 void featureNotImplemented()() {
1308 	version(allow_unimplemented_features)
1309 		throw new NotYetImplementedException();
1310 	else
1311 		static assert(0);
1312 }
1313 
1314 // these are so the static asserts don't trigger unless you want to
1315 // add support to it for an OS
1316 version(Windows)
1317 	version = with_timer;
1318 version(linux)
1319 	version = with_timer;
1320 
1321 version(with_timer)
1322 	enum bool SimpledisplayTimerAvailable = true;
1323 else
1324 	enum bool SimpledisplayTimerAvailable = false;
1325 
1326 /// 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.
1327 version(Windows)
1328 	enum bool UsingSimpledisplayWindows = true;
1329 else
1330 	enum bool UsingSimpledisplayWindows = false;
1331 
1332 /// 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.
1333 version(X11)
1334 	enum bool UsingSimpledisplayX11 = true;
1335 else
1336 	enum bool UsingSimpledisplayX11 = false;
1337 
1338 /// 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.
1339 version(OSXCocoa)
1340 	enum bool UsingSimpledisplayCocoa = true;
1341 else
1342 	enum bool UsingSimpledisplayCocoa = false;
1343 
1344 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1345 version(Windows)
1346 	enum multipleWindowsSupported = true;
1347 else version(X11)
1348 	enum multipleWindowsSupported = true;
1349 else version(OSXCocoa)
1350 	enum multipleWindowsSupported = true;
1351 else
1352 	static assert(0);
1353 
1354 version(without_opengl)
1355 	enum bool OpenGlEnabled = false;
1356 else
1357 	enum bool OpenGlEnabled = true;
1358 
1359 /++
1360 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1361 	If you mix this in above your `main` function, you no longer need to use the linker
1362 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1363 
1364 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1365 
1366 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1367 	stderr writeln. It will fail and throw an exception.
1368 
1369 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1370 
1371 	History:
1372 		Added November 24, 2021 (dub v10.4)
1373 +/
1374 mixin template EnableWindowsSubsystem() {
1375 	version(Windows)
1376 	version(CRuntime_Microsoft) {
1377 		pragma(linkerDirective, "/subsystem:windows");
1378 		version(LDC)
1379 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1380 		else
1381 			pragma(linkerDirective, "/entry:mainCRTStartup");
1382 	}
1383 }
1384 
1385 
1386 /++
1387 	After selecting a type from [WindowTypes], you may further customize
1388 	its behavior by setting one or more of these flags.
1389 
1390 
1391 	The different window types have different meanings of `normal`. If the
1392 	window type already is a good match for what you want to do, you should
1393 	just use [WindowFlags.normal], the default, which will do the right thing
1394 	for your users.
1395 
1396 	The window flags will not always be honored by the operating system
1397 	and window managers; they are hints, not commands.
1398 +/
1399 enum WindowFlags : int {
1400 	normal = 0, ///
1401 	skipTaskbar = 1, ///
1402 	alwaysOnTop = 2, ///
1403 	alwaysOnBottom = 4, ///
1404 	cannotBeActivated = 8, ///
1405 	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.
1406 	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.
1407 	/++
1408 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1409 		it is still a top-level window. This should NOT be set separately for most window types.
1410 
1411 		A transient window will not keep the application open if its main window closes.
1412 
1413 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1414 
1415 
1416 		From the ICCM:
1417 
1418 		$(BLOCKQUOTE
1419 			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.
1420 
1421 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1422 		)
1423 
1424 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1425 
1426 		History:
1427 			Added February 23, 2021 but not yet stabilized.
1428 	+/
1429 	transient = 64,
1430 	/++
1431 		This indicates that the window manages its own platform-specific child window input focus. You must use a delegate, [SimpleWindow.setRequestedInputFocus], to set the input when requested. This delegate returns the handle to the window that should receive the focus.
1432 
1433 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1434 
1435 		History:
1436 			Added April 1, 2022
1437 	+/
1438 	managesChildWindowFocus = 128,
1439 
1440 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1441 }
1442 
1443 /++
1444 	When creating a window, you can pass a type to SimpleWindow's constructor,
1445 	then further customize the window by changing `WindowFlags`.
1446 
1447 
1448 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1449 	use. The others are there to build a foundation for a higher level GUI toolkit,
1450 	but are themselves not as high level as you might think from their names.
1451 
1452 	This list is based on the EMWH spec for X11.
1453 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1454 +/
1455 enum WindowTypes : int {
1456 	/// An ordinary application window.
1457 	normal,
1458 	/// 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.
1459 	undecorated,
1460 	/// 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.
1461 	eventOnly,
1462 	/// A drop down menu, such as from a menu bar
1463 	dropdownMenu,
1464 	/// A popup menu, such as from a right click
1465 	popupMenu,
1466 	/// A popup bubble notification
1467 	notification,
1468 	/*
1469 	menu, /// a tearable menu bar
1470 	splashScreen, /// a loading splash screen for your application
1471 	tooltip, /// A tiny window showing temporary help text or something.
1472 	comboBoxDropdown,
1473 	dialog,
1474 	toolbar
1475 	*/
1476 	/// a child nested inside the parent. You must pass a parent window to the ctor
1477 	nestedChild,
1478 
1479 	/++
1480 		The type you get when you pass in an existing browser handle, which means most
1481 		of simpledisplay's fancy things will not be done since they were never set up.
1482 
1483 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1484 		failure; you should use the existing handle constructor.
1485 
1486 		History:
1487 			Added November 17, 2022 (previously it would have type `normal`)
1488 	+/
1489 	minimallyWrapped
1490 }
1491 
1492 
1493 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1494 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1495 private __gshared char* sdpyWindowClassStr = null;
1496 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1497 
1498 /**
1499 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1500 	You may want to change context version if you want to use advanced shaders or
1501 	other modern OpenGL techinques. This setting doesn't affect already created
1502 	windows. You may use version 2.1 as your default, which should be supported
1503 	by any box since 2006, so seems to be a reasonable choice.
1504 
1505 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1506 	old context creation code without any version specified. This is the safest
1507 	way to init OpenGL, but it may not give you access to advanced features.
1508 
1509 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1510 */
1511 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1512 
1513 /**
1514 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1515 	pipeline functions, and without "compatible" mode you won't be able to use
1516 	your old non-shader-based code with such contexts. By default SimpleDisplay
1517 	creates compatible context, so you can gradually upgrade your OpenGL code if
1518 	you want to (or leave it as is, as it should "just work").
1519 */
1520 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1521 
1522 /**
1523 	Set to `true` to allow creating OpenGL context with lower version than requested
1524 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1525 	`openGLContextFallbackActivated()` will return `true`.
1526 	*/
1527 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1528 
1529 /**
1530 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1531 	*/
1532 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1533 
1534 /++
1535 	History:
1536 		Added April 24, 2023  (dub v11.0)
1537 +/
1538 auto openGLCurrentContext() {
1539 	version(Windows)
1540 		return wglGetCurrentContext();
1541 	else
1542 		return glXGetCurrentContext();
1543 }
1544 
1545 
1546 /**
1547 	Set window class name for all following `new SimpleWindow()` calls.
1548 
1549 	WARNING! For Windows, you should set your class name before creating any
1550 	window, and NEVER change it after that!
1551 */
1552 void sdpyWindowClass (const(char)[] v) {
1553 	import core.stdc.stdlib : realloc;
1554 	if (v.length == 0) v = "SimpleDisplayWindow";
1555 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1556 	if (sdpyWindowClassStr is null) return; // oops
1557 	sdpyWindowClassStr[0..v.length+1] = 0;
1558 	sdpyWindowClassStr[0..v.length] = v[];
1559 }
1560 
1561 /**
1562 	Get current window class name.
1563 */
1564 string sdpyWindowClass () {
1565 	if (sdpyWindowClassStr is null) return null;
1566 	foreach (immutable idx; 0..size_t.max-1) {
1567 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1568 	}
1569 	return null;
1570 }
1571 
1572 /++
1573 	Returns the logical 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. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor.
1574 
1575 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1576 +/
1577 float[2] getDpi() {
1578 	float[2] dpi;
1579 	version(Windows) {
1580 		HDC screen = GetDC(null);
1581 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1582 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1583 	} else version(X11) {
1584 		auto display = XDisplayConnection.get;
1585 		auto screen = DefaultScreen(display);
1586 
1587 		void fallback() {
1588 			/+
1589 			// 25.4 millimeters in an inch...
1590 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1591 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1592 			+/
1593 
1594 			// the physical size isn't actually as important as the logical size since this is
1595 			// all about scaling really
1596 			dpi[0] = 96;
1597 			dpi[1] = 96;
1598 		}
1599 
1600 		auto xft = getXftDpi();
1601 		if(xft is float.init)
1602 			fallback();
1603 		else {
1604 			dpi[0] = xft;
1605 			dpi[1] = xft;
1606 		}
1607 	}
1608 
1609 	return dpi;
1610 }
1611 
1612 version(X11)
1613 float getXftDpi() {
1614 	auto display = XDisplayConnection.get;
1615 
1616 	char* resourceString = XResourceManagerString(display);
1617 	XrmInitialize();
1618 
1619 	if (resourceString) {
1620 		auto db = XrmGetStringDatabase(resourceString);
1621 		XrmValue value;
1622 		char* type;
1623 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1624 			if (value.addr) {
1625 				import core.stdc.stdlib;
1626 				return atof(cast(char*) value.addr);
1627 			}
1628 		}
1629 	}
1630 
1631 	return float.init;
1632 }
1633 
1634 /++
1635 	Implementation used by [SimpleWindow.takeScreenshot].
1636 
1637 	Params:
1638 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1639 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1640 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1641 		x = the x-offset of the image to capture, from the left.
1642 		y = the y-offset of the image to capture, from the top.
1643 
1644 	History:
1645 		Added on March 14, 2021
1646 
1647 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1648 
1649 +/
1650 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1651 	TrueColorImage got;
1652 	version(X11) {
1653 		auto display = XDisplayConnection.get;
1654 		if(handle == 0)
1655 			handle = RootWindow(display, DefaultScreen(display));
1656 
1657 		if(width == 0 || height == 0) {
1658 			Window root;
1659 			int xpos, ypos;
1660 			uint widthret, heightret, borderret, depthret;
1661 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1662 
1663 			if(width == 0)
1664 				width = widthret;
1665 			if(height == 0)
1666 				height = heightret;
1667 		}
1668 
1669 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1670 
1671 		// https://github.com/adamdruppe/arsd/issues/98
1672 
1673 		auto i = new Image(image);
1674 		got = i.toTrueColorImage();
1675 
1676 		XDestroyImage(image);
1677 	} else version(Windows) {
1678 		auto hdc = GetDC(handle);
1679 		scope(exit) ReleaseDC(handle, hdc);
1680 
1681 		if(width == 0 || height == 0) {
1682 			BITMAP bmHeader;
1683 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1684 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1685 			if(width == 0)
1686 				width = bmHeader.bmWidth;
1687 			if(height == 0)
1688 				height = bmHeader.bmHeight;
1689 		}
1690 
1691 		auto i = new Image(width, height);
1692 		HDC hdcMem = CreateCompatibleDC(hdc);
1693 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1694 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1695 		SelectObject(hdcMem, hbmOld);
1696 		DeleteDC(hdcMem);
1697 
1698 		got = i.toTrueColorImage();
1699 	} else featureNotImplemented();
1700 
1701 	return got;
1702 }
1703 
1704 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1705 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1706 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1707 
1708 version(Windows)
1709 shared static this() {
1710 	auto lib = LoadLibrary("User32.dll");
1711 	if(lib is null)
1712 		return;
1713 	//scope(exit)
1714 		//FreeLibrary(lib);
1715 
1716 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1717 
1718 	if(SetProcessDpiAwarenessContext is null)
1719 		return;
1720 
1721 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1722 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1723 		//writeln(GetLastError());
1724 	}
1725 
1726 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1727 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1728 }
1729 
1730 /++
1731 	Blocking mode for event loop calls associated with a window instance.
1732 
1733 	History:
1734 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1735 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1736 		is, all would block until the application quit.
1737 
1738 		That behavior can still be achieved here with `untilApplicationQuits`,
1739 		or explicitly calling the top-level `EventLoop.get.run` function.
1740 +/
1741 enum BlockingMode {
1742 	/++
1743 		The event loop call will block until the whole application is ready
1744 		to quit if it is the only one running, but if it is nested inside
1745 		another one, it will only block until the window you're calling it on
1746 		closes.
1747 	+/
1748 	automatic             = 0x00,
1749 	/++
1750 		The event loop call will only return when the whole application
1751 		is ready to quit. This usually means all windows have been closed.
1752 
1753 		This is appropriate for your main application event loop.
1754 	+/
1755 	untilApplicationQuits = 0x01,
1756 	/++
1757 		The event loop will return when the window you're calling it on
1758 		closes. If there are other windows still open, they may be destroyed
1759 		unless you have another event loop running later.
1760 
1761 		This might be appropriate for a modal dialog box loop. Remember that
1762 		other windows are still processing input though, so you can end up
1763 		with a lengthy call stack if this happens in a loop, similar to a
1764 		recursive function (well, it literally is a recursive function, just
1765 		not an obvious looking one).
1766 	+/
1767 	untilWindowCloses     = 0x02,
1768 	/++
1769 		If an event loop is already running, this call will immediately
1770 		return, allowing the existing loop to handle it. If not, this call
1771 		will block until the condition you bitwise-or into the flag.
1772 
1773 		The default is to block until the application quits, same as with
1774 		the `automatic` setting (since if it were nested, which triggers until
1775 		window closes in automatic, this flag would instead not block at all),
1776 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1777 		it will only nest until the window closes. You might want that if you are
1778 		going to open two windows simultaneously and want closing just one of them
1779 		to trigger the event loop return.
1780 	+/
1781 	onlyIfNotNested       = 0x10,
1782 }
1783 
1784 /++
1785 	The flagship window class.
1786 
1787 
1788 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1789 	out of more advanced or complex features of the underlying windowing system.
1790 
1791 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1792 	and get a suitable window to work with.
1793 
1794 	From there, you can opt into additional features, like custom resizability and OpenGL support
1795 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1796 	and customization flags with the final two constructor arguments.
1797 
1798 	If none of that works for you, you can also create a window using native function calls, then
1799 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1800 	though, if you do this, managing the window is still your own responsibility! Notably, you
1801 	will need to destroy it yourself.
1802 +/
1803 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1804 
1805 	/++
1806 		Copies the window's current state into a [TrueColorImage].
1807 
1808 		Be warned: this can be a very slow operation
1809 
1810 		History:
1811 			Actually implemented on March 14, 2021
1812 	+/
1813 	TrueColorImage takeScreenshot() {
1814 		version(Windows)
1815 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1816 		else version(OSXCocoa)
1817 			throw new NotYetImplementedException();
1818 		else
1819 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1820 	}
1821 
1822 	/++
1823 		Returns the actual logical DPI for the window on its current display monitor. If the window
1824 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1825 
1826 		Please note this function may return zero if it doesn't know the answer!
1827 
1828 
1829 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1830 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1831 
1832 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1833 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1834 		window primarily resides on by checking the center point of the window against the monitor map.
1835 
1836 		Returns:
1837 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1838 			assumes the X and Y dpi are the same.
1839 
1840 		History:
1841 			Added November 26, 2021 (dub v10.4)
1842 
1843 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1844 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1845 			that.
1846 
1847 		Bugs:
1848 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1849 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1850 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1851 			and 1.5 on the secondary monitor.
1852 
1853 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1854 			is a historical misnomer - the real thing of interest is the scale factor and due to
1855 			compatibility concerns the scale would modify dpi values to trick applications. But since
1856 			that's the terminology common out there, I used it too.
1857 
1858 		See_Also:
1859 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1860 			as this since the window many be on a different monitor, but it is a reasonable fallback
1861 			to use if `actualDpi` returns 0.
1862 
1863 			[onDpiChanged] is changed when `actualDpi` has changed.
1864 	+/
1865 	int actualDpi() {
1866 		if(!actualDpiLoadAttempted) {
1867 			// FIXME: do the actual monitor we are on
1868 			// and on X this is a good chance to load the monitor map.
1869 			version(Windows) {
1870 				if(GetDpiForWindow)
1871 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1872 			} else version(X11) {
1873 				if(!xRandrInfoLoadAttemped) {
1874 					xRandrInfoLoadAttemped = true;
1875 					if(!XRandrLibrary.attempted) {
1876 						XRandrLibrary.loadDynamicLibrary();
1877 					}
1878 
1879 					if(XRandrLibrary.loadSuccessful) {
1880 						auto display = XDisplayConnection.get;
1881 						int scratch;
1882 						int major, minor;
1883 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1884 							goto fallback;
1885 
1886 						XRRQueryVersion(display, &major, &minor);
1887 						if(major <= 1 && minor < 5)
1888 							goto fallback;
1889 
1890 						int count;
1891 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1892 						if(monitors is null)
1893 							goto fallback;
1894 						scope(exit) XRRFreeMonitors(monitors);
1895 
1896 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1897 						MonitorInfo.info.assumeSafeAppend();
1898 						foreach(idx, monitor; monitors[0 .. count]) {
1899 							MonitorInfo.info ~= MonitorInfo(
1900 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1901 								Size(monitor.mwidth, monitor.mheight),
1902 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1903 							);
1904 
1905 							/+
1906 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1907 							// unknown physical size, just guess 96 to avoid divide by zero
1908 							MonitorInfo.info ~= MonitorInfo(
1909 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1910 								Size(monitor.mwidth, monitor.mheight),
1911 								96
1912 							);
1913 							else
1914 							// and actual thing
1915 							MonitorInfo.info ~= MonitorInfo(
1916 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1917 								Size(monitor.mwidth, monitor.mheight),
1918 								minInternal(
1919 									// millimeter to int then rounding up.
1920 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1921 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1922 								)
1923 							);
1924 							+/
1925 						}
1926 					// writeln("Here", MonitorInfo.info);
1927 					}
1928 				}
1929 
1930 				if(XRandrLibrary.loadSuccessful) {
1931 					updateActualDpi(true);
1932 					// writeln("updated");
1933 
1934 					if(!requestedInput) {
1935 						// this is what requests live updates should the configuration change
1936 						// each time you select input, it sends an initial event, so very important
1937 						// to not get into a loop of selecting input, getting event, updating data,
1938 						// and reselecting input...
1939 						requestedInput = true;
1940 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1941 						// writeln("requested input");
1942 					}
1943 				} else {
1944 					fallback:
1945 					// make sure we disable events that aren't coming
1946 					xrrEventBase = -1;
1947 					// best guess... respect the custom scaling user command to some extent at least though
1948 					actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1949 				}
1950 			}
1951 			actualDpiLoadAttempted = true;
1952 		}
1953 		return actualDpi_;
1954 	}
1955 
1956 	private int actualDpi_;
1957 	private bool actualDpiLoadAttempted;
1958 
1959 	version(X11) private {
1960 		bool requestedInput;
1961 		static bool xRandrInfoLoadAttemped;
1962 		struct MonitorInfo {
1963 			Rectangle position;
1964 			Size size;
1965 			int dpi;
1966 
1967 			static MonitorInfo[] info;
1968 		}
1969 		bool screenPositionKnown;
1970 		int screenPositionX;
1971 		int screenPositionY;
1972 		void updateActualDpi(bool loadingNow = false) {
1973 			if(!loadingNow && !actualDpiLoadAttempted)
1974 				actualDpi(); // just to make it do the load
1975 			foreach(idx, m; MonitorInfo.info) {
1976 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1977 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1978 					actualDpi_ = m.dpi;
1979 					// writeln("monitor ", idx);
1980 					if(changed && onDpiChanged)
1981 						onDpiChanged();
1982 					break;
1983 				}
1984 			}
1985 		}
1986 	}
1987 
1988 	/++
1989 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
1990 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
1991 
1992 		History:
1993 			Added November 26, 2021 (dub v10.4)
1994 
1995 		See_Also:
1996 			[actualDpi]
1997 	+/
1998 	void delegate() onDpiChanged;
1999 
2000 	version(X11) {
2001 		void recreateAfterDisconnect() {
2002 			if(!stateDiscarded) return;
2003 
2004 			if(_parent !is null && _parent.stateDiscarded)
2005 				_parent.recreateAfterDisconnect();
2006 
2007 			bool wasHidden = hidden;
2008 
2009 			activeScreenPainter = null; // should already be done but just to confirm
2010 
2011 			actualDpi_ = 0;
2012 			actualDpiLoadAttempted = false;
2013 			xRandrInfoLoadAttemped = false;
2014 
2015 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2016 
2017 			if(auto dh = dropHandler) {
2018 				dropHandler = null;
2019 				enableDragAndDrop(this, dh);
2020 			}
2021 
2022 			if(recreateAdditionalConnectionState)
2023 				recreateAdditionalConnectionState();
2024 
2025 			hidden = wasHidden;
2026 			stateDiscarded = false;
2027 		}
2028 
2029 		bool stateDiscarded;
2030 		void discardConnectionState() {
2031 			if(XDisplayConnection.display)
2032 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2033 			if(discardAdditionalConnectionState)
2034 				discardAdditionalConnectionState();
2035 			stateDiscarded = true;
2036 		}
2037 
2038 		void delegate() discardAdditionalConnectionState;
2039 		void delegate() recreateAdditionalConnectionState;
2040 
2041 	}
2042 
2043 	private DropHandler dropHandler;
2044 
2045 	SimpleWindow _parent;
2046 	bool beingOpenKeepsAppOpen = true;
2047 	/++
2048 		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.
2049 
2050 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2051 
2052 		Params:
2053 
2054 		width = the width of the window's client area, in pixels
2055 		height = the height of the window's client area, in pixels
2056 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2057 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2058 		resizable = [Resizability] has three options:
2059 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2060 			$(P `fixedSize` will not allow the user to resize the window.)
2061 			$(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.)
2062 		windowType = The type of window you want to make.
2063 		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.
2064 		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".
2065 	+/
2066 	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) {
2067 		claimGuiThread();
2068 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2069 		this._width = this._virtualWidth = width;
2070 		this._height = this._virtualHeight = height;
2071 		this.openglMode = opengl;
2072 		version(X11) {
2073 			// auto scale not implemented except with opengl and even there it is kinda weird
2074 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2075 				resizable = Resizability.fixedSize;
2076 		}
2077 		this.resizability = resizable;
2078 		this.windowType = windowType;
2079 		this.customizationFlags = customizationFlags;
2080 		this._title = (title is null ? "D Application" : title);
2081 		this._parent = parent;
2082 		impl.createWindow(width, height, this._title, opengl, parent);
2083 
2084 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2085 			beingOpenKeepsAppOpen = false;
2086 	}
2087 
2088 	/// ditto
2089 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2090 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2091 	}
2092 
2093 	/// Same as above, except using the `Size` struct instead of separate width and height.
2094 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2095 		this(size.width, size.height, title, opengl, resizable);
2096 	}
2097 
2098 	/// ditto
2099 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2100 		this(size, title, opengl, resizable);
2101 	}
2102 
2103 
2104 	/++
2105 		Creates a window based on the given [Image]. It's client area
2106 		width and height is equal to the image. (A window's client area
2107 		is the drawable space inside; it excludes the title bar, etc.)
2108 
2109 		Windows based on images will not be resizable and do not use OpenGL.
2110 
2111 		It will draw the image in upon creation, but this will be overwritten
2112 		upon any draws, including the initial window visible event.
2113 
2114 		You probably do not want to use this and it may be removed from
2115 		the library eventually, or I might change it to be a "permanent"
2116 		background image; one that is automatically drawn on it before any
2117 		other drawing event. idk.
2118 	+/
2119 	this(Image image, string title = null) {
2120 		this(image.width, image.height, title);
2121 		this.image = image;
2122 	}
2123 
2124 	/++
2125 		Wraps a native window handle with very little additional processing - notably no destruction
2126 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2127 		windows created through the low level API (so you can use platform-specific options and
2128 		other details SimpleWindow does not expose) available to the event loop wrappers.
2129 	+/
2130 	this(NativeWindowHandle nativeWindow) {
2131 		windowType = WindowTypes.minimallyWrapped;
2132 		version(Windows)
2133 			impl.hwnd = nativeWindow;
2134 		else version(X11) {
2135 			impl.window = nativeWindow;
2136 			if(nativeWindow)
2137 				display = XDisplayConnection.get(); // get initial display to not segfault
2138 		} else version(OSXCocoa)
2139 			throw new NotYetImplementedException();
2140 		else featureNotImplemented();
2141 		// FIXME: set the size correctly
2142 		_width = 1;
2143 		_height = 1;
2144 		if(nativeWindow)
2145 			nativeMapping[nativeWindow] = this;
2146 
2147 		beingOpenKeepsAppOpen = false;
2148 
2149 		if(nativeWindow)
2150 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2151 		_suppressDestruction = true; // so it doesn't try to close
2152 	}
2153 
2154 	/++
2155 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2156 		The delegate will be called when the window manager asks you to take focus.
2157 
2158 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2159 
2160 		History:
2161 			Added April 1, 2022 (dub v10.8)
2162 	+/
2163 	SimpleWindow delegate() setRequestedInputFocus;
2164 
2165 	/// Experimental, do not use yet
2166 	/++
2167 		Grabs exclusive input from the user until you release it with
2168 		[releaseInputGrab].
2169 
2170 
2171 		Note: it is extremely rude to do this without good reason.
2172 		Reasons may include doing some kind of mouse drag operation
2173 		or popping up a temporary menu that should get events and will
2174 		be dismissed at ease by the user clicking away.
2175 
2176 		Params:
2177 			keyboard = do you want to grab keyboard input?
2178 			mouse = grab mouse input?
2179 			confine = confine the mouse cursor to inside this window?
2180 
2181 		History:
2182 			Prior to March 11, 2021, grabbing the keyboard would always also
2183 			set the X input focus. Now, it only focuses if it is a non-transient
2184 			window and otherwise manages the input direction internally.
2185 
2186 			This means spurious focus/blur events will no longer be sent and the
2187 			application will not steal focus from other applications (which the
2188 			window manager may have rejected anyway).
2189 	+/
2190 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2191 		static if(UsingSimpledisplayX11) {
2192 			XSync(XDisplayConnection.get, 0);
2193 			if(keyboard) {
2194 				if(isTransient && _parent) {
2195 					/*
2196 					FIXME:
2197 						setting the keyboard focus is not actually that helpful, what I more likely want
2198 						is the events from the parent window to be sent over here if we're transient.
2199 					*/
2200 
2201 					_parent.inputProxy = this;
2202 				} else {
2203 
2204 					SimpleWindow setTo;
2205 					if(setRequestedInputFocus !is null)
2206 						setTo = setRequestedInputFocus();
2207 					if(setTo is null)
2208 						setTo = this;
2209 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2210 				}
2211 			}
2212 			if(mouse) {
2213 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2214 				EventMask.PointerMotionMask // FIXME: not efficient
2215 				| EventMask.ButtonPressMask
2216 				| EventMask.ButtonReleaseMask
2217 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2218 				)
2219 			{
2220 				XSync(XDisplayConnection.get, 0);
2221 				import core.stdc.stdio;
2222 				printf("Grab input failed %d\n", res);
2223 				//throw new Exception("Grab input failed");
2224 			} else {
2225 				// cool
2226 			}
2227 			}
2228 
2229 		} else version(Windows) {
2230 			// FIXME: keyboard?
2231 			SetCapture(impl.hwnd);
2232 			if(confine) {
2233 				RECT rcClip;
2234 				//RECT rcOldClip;
2235 				//GetClipCursor(&rcOldClip);
2236 				GetWindowRect(hwnd, &rcClip);
2237 				ClipCursor(&rcClip);
2238 			}
2239 		} else version(OSXCocoa) {
2240 			throw new NotYetImplementedException();
2241 		} else static assert(0);
2242 	}
2243 
2244 	private Point imePopupLocation = Point(0, 0);
2245 
2246 	/++
2247 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2248 
2249 		Bugs:
2250 			Not implemented outside X11.
2251 	+/
2252 	void setIMEPopupLocation(Point location) {
2253 		static if(UsingSimpledisplayX11) {
2254 			imePopupLocation = location;
2255 			updateIMEPopupLocation();
2256 		} else {
2257 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2258 			// throw new NotYetImplementedException();
2259 		}
2260 	}
2261 
2262 	/// ditto
2263 	void setIMEPopupLocation(int x, int y) {
2264 		return setIMEPopupLocation(Point(x, y));
2265 	}
2266 
2267 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2268 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2269 	// receives a ConfigureNotify event
2270 	private void updateIMEPopupLocation() {
2271 		static if(UsingSimpledisplayX11) {
2272 			if (xic is null) {
2273 				return;
2274 			}
2275 
2276 			XPoint nspot;
2277 			nspot.x = cast(short) imePopupLocation.x;
2278 			nspot.y = cast(short) imePopupLocation.y;
2279 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2280 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2281 			XFree(preeditAttr);
2282 		}
2283 	}
2284 
2285 	private bool imeFocused = true;
2286 
2287 	/++
2288 		Tells the IME whether or not an input field is currently focused in the window.
2289 
2290 		Bugs:
2291 			Not implemented outside X11.
2292 	+/
2293 	void setIMEFocused(bool value) {
2294 		imeFocused = value;
2295 		updateIMEFocused();
2296 	}
2297 
2298 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2299 	private void updateIMEFocused() {
2300 		static if(UsingSimpledisplayX11) {
2301 			if (xic is null) {
2302 				return;
2303 			}
2304 
2305 			if (focused && imeFocused) {
2306 				XSetICFocus(xic);
2307 			} else {
2308 				XUnsetICFocus(xic);
2309 			}
2310 		}
2311 	}
2312 
2313 	/++
2314 		Returns the native window.
2315 
2316 		History:
2317 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2318 			to access it through the `impl` member (which is semi-supported
2319 			but platform specific and here it is simple enough to offer an accessor).
2320 
2321 		Bugs:
2322 			Not implemented outside Windows or X11.
2323 	+/
2324 	NativeWindowHandle nativeWindowHandle() {
2325 		version(X11)
2326 			return impl.window;
2327 		else version(Windows)
2328 			return impl.hwnd;
2329 		else
2330 			throw new NotYetImplementedException();
2331 	}
2332 
2333 	private bool isTransient() {
2334 		with(WindowTypes)
2335 		final switch(windowType) {
2336 			case normal, undecorated, eventOnly:
2337 			case nestedChild, minimallyWrapped:
2338 				return (customizationFlags & WindowFlags.transient) ? true : false;
2339 			case dropdownMenu, popupMenu, notification:
2340 				return true;
2341 		}
2342 	}
2343 
2344 	private SimpleWindow inputProxy;
2345 
2346 	/++
2347 		Releases the grab acquired by [grabInput].
2348 	+/
2349 	void releaseInputGrab() {
2350 		static if(UsingSimpledisplayX11) {
2351 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2352 			if(_parent)
2353 				_parent.inputProxy = null;
2354 		} else version(Windows) {
2355 			ReleaseCapture();
2356 			ClipCursor(null);
2357 		} else version(OSXCocoa) {
2358 			throw new NotYetImplementedException();
2359 		} else static assert(0);
2360 	}
2361 
2362 	/++
2363 		Sets the input focus to this window.
2364 
2365 		You shouldn't call this very often - please let the user control the input focus.
2366 	+/
2367 	void focus() {
2368 		static if(UsingSimpledisplayX11) {
2369 			SimpleWindow setTo;
2370 			if(setRequestedInputFocus !is null)
2371 				setTo = setRequestedInputFocus();
2372 			if(setTo is null)
2373 				setTo = this;
2374 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2375 		} else version(Windows) {
2376 			SetFocus(this.impl.hwnd);
2377 		} else version(OSXCocoa) {
2378 			throw new NotYetImplementedException();
2379 		} else static assert(0);
2380 	}
2381 
2382 	/++
2383 		Requests attention from the user for this window.
2384 
2385 
2386 		The typical result of this function is to change the color
2387 		of the taskbar icon, though it may be tweaked on specific
2388 		platforms.
2389 
2390 		It is meant to unobtrusively tell the user that something
2391 		relevant to them happened in the background and they should
2392 		check the window when they get a chance. Upon receiving the
2393 		keyboard focus, the window will automatically return to its
2394 		natural state.
2395 
2396 		If the window already has the keyboard focus, this function
2397 		may do nothing, because the user is presumed to already be
2398 		giving the window attention.
2399 
2400 		Implementation_note:
2401 
2402 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2403 		atom on X11 and the FlashWindow function on Windows.
2404 	+/
2405 	void requestAttention() {
2406 		if(_focused)
2407 			return;
2408 
2409 		version(Windows) {
2410 			FLASHWINFO info;
2411 			info.cbSize = info.sizeof;
2412 			info.hwnd = impl.hwnd;
2413 			info.dwFlags = FLASHW_TRAY;
2414 			info.uCount = 1;
2415 
2416 			FlashWindowEx(&info);
2417 
2418 		} else version(X11) {
2419 			demandingAttention = true;
2420 			demandAttention(this, true);
2421 		} else version(OSXCocoa) {
2422 			throw new NotYetImplementedException();
2423 		} else static assert(0);
2424 	}
2425 
2426 	private bool _focused;
2427 
2428 	version(X11) private bool demandingAttention;
2429 
2430 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2431 	/// You'll have to call `close()` manually if you set this delegate.
2432 	void delegate () closeQuery;
2433 
2434 	/// This will be called when window visibility was changed.
2435 	void delegate (bool becomesVisible) visibilityChanged;
2436 
2437 	/// This will be called when window becomes visible for the first time.
2438 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2439 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2440 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2441 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2442 	private bool _visibleForTheFirstTimeCalled;
2443 	void delegate () visibleForTheFirstTime;
2444 
2445 	/// Returns true if the window has been closed.
2446 	final @property bool closed() { return _closed; }
2447 
2448 	private final @property bool notClosed() { return !_closed; }
2449 
2450 	/// Returns true if the window is focused.
2451 	final @property bool focused() { return _focused; }
2452 
2453 	private bool _visible;
2454 	/// Returns true if the window is visible (mapped).
2455 	final @property bool visible() { return _visible; }
2456 
2457 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2458 	void close() {
2459 		if (!_closed) {
2460 			runInGuiThread( {
2461 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2462 				if (onClosing !is null) onClosing();
2463 				impl.closeWindow();
2464 				_closed = true;
2465 			} );
2466 		}
2467 	}
2468 
2469 	/++
2470 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2471 
2472 		History:
2473 			Overload added on March 7, 2021.
2474 	+/
2475 	void close() shared {
2476 		(cast() this).close();
2477 	}
2478 
2479 	/++
2480 
2481 	+/
2482 	void maximize() {
2483 		version(Windows)
2484 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2485 		else version(X11) {
2486 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2487 
2488 			// also note _NET_WM_STATE_FULLSCREEN
2489 		}
2490 
2491 	}
2492 
2493 	private bool _fullscreen;
2494 	version(Windows)
2495 	private WINDOWPLACEMENT g_wpPrev;
2496 
2497 	/// not fully implemented but planned for a future release
2498 	void fullscreen(bool yes) {
2499 		version(Windows) {
2500 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2501 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2502 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2503 				MONITORINFO mi;
2504 				mi.cbSize = MONITORINFO.sizeof;
2505 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2506 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2507 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2508 					SetWindowLong(hwnd, GWL_STYLE,
2509 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2510 					SetWindowPos(hwnd, HWND_TOP,
2511 						     mi.rcMonitor.left, mi.rcMonitor.top,
2512 						     mi.rcMonitor.right - mi.rcMonitor.left,
2513 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2514 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2515 				}
2516 			} else {
2517 				SetWindowLong(hwnd, GWL_STYLE,
2518 					      dwStyle | WS_OVERLAPPEDWINDOW);
2519 				SetWindowPlacement(hwnd, &g_wpPrev);
2520 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2521 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2522 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2523 			}
2524 
2525 		} else version(X11) {
2526 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2527 		}
2528 
2529 		_fullscreen = yes;
2530 
2531 	}
2532 
2533 	bool fullscreen() {
2534 		return _fullscreen;
2535 	}
2536 
2537 	/++
2538 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2539 
2540 	+/
2541 	void minimize() {
2542 		version(Windows)
2543 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2544 		//else version(X11)
2545 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2546 	}
2547 
2548 	/// Alias for `hidden = false`
2549 	void show() {
2550 		hidden = false;
2551 	}
2552 
2553 	/// Alias for `hidden = true`
2554 	void hide() {
2555 		hidden = true;
2556 	}
2557 
2558 	/// Hide cursor when it enters the window.
2559 	void hideCursor() {
2560 		version(OSXCocoa) throw new NotYetImplementedException(); else
2561 		if (!_closed) impl.hideCursor();
2562 	}
2563 
2564 	/// Don't hide cursor when it enters the window.
2565 	void showCursor() {
2566 		version(OSXCocoa) throw new NotYetImplementedException(); else
2567 		if (!_closed) impl.showCursor();
2568 	}
2569 
2570 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2571 	 *
2572 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2573 	 * control. Try to think for other approaches before using this function.
2574 	 *
2575 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2576 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2577 	 *       receive "mouse moved here" event.
2578 	 */
2579 	bool warpMouse (int x, int y) {
2580 		version(X11) {
2581 			if (!_closed) { impl.warpMouse(x, y); return true; }
2582 		} else version(Windows) {
2583 			if (!_closed) {
2584 				POINT point;
2585 				point.x = x;
2586 				point.y = y;
2587 				if(ClientToScreen(impl.hwnd, &point)) {
2588 					SetCursorPos(point.x, point.y);
2589 					return true;
2590 				}
2591 			}
2592 		}
2593 		return false;
2594 	}
2595 
2596 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2597 	void sendDummyEvent () {
2598 		version(X11) {
2599 			if (!_closed) { impl.sendDummyEvent(); }
2600 		}
2601 	}
2602 
2603 	/// Set window minimal size.
2604 	void setMinSize (int minwidth, int minheight) {
2605 		version(OSXCocoa) throw new NotYetImplementedException(); else
2606 		if (!_closed) impl.setMinSize(minwidth, minheight);
2607 	}
2608 
2609 	/// Set window maximal size.
2610 	void setMaxSize (int maxwidth, int maxheight) {
2611 		version(OSXCocoa) throw new NotYetImplementedException(); else
2612 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2613 	}
2614 
2615 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2616 	/// Currently only supported on X11.
2617 	void setResizeGranularity (int granx, int grany) {
2618 		version(OSXCocoa) throw new NotYetImplementedException(); else
2619 		if (!_closed) impl.setResizeGranularity(granx, grany);
2620 	}
2621 
2622 	/// Move window.
2623 	void move(int x, int y) {
2624 		version(OSXCocoa) throw new NotYetImplementedException(); else
2625 		if (!_closed) impl.move(x, y);
2626 	}
2627 
2628 	/// ditto
2629 	void move(Point p) {
2630 		version(OSXCocoa) throw new NotYetImplementedException(); else
2631 		if (!_closed) impl.move(p.x, p.y);
2632 	}
2633 
2634 	/++
2635 		Resize window.
2636 
2637 		Note that the width and height of the window are NOT instantly
2638 		updated - it waits for the window manager to approve the resize
2639 		request, which means you must return to the event loop before the
2640 		width and height are actually changed.
2641 	+/
2642 	void resize(int w, int h) {
2643 		if(!_closed && _fullscreen) fullscreen = false;
2644 		version(OSXCocoa) throw new NotYetImplementedException(); else
2645 		if (!_closed) impl.resize(w, h);
2646 	}
2647 
2648 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2649 	void moveResize (int x, int y, int w, int h) {
2650 		if(!_closed && _fullscreen) fullscreen = false;
2651 		version(OSXCocoa) throw new NotYetImplementedException(); else
2652 		if (!_closed) impl.moveResize(x, y, w, h);
2653 	}
2654 
2655 	private bool _hidden;
2656 
2657 	/// Returns true if the window is hidden.
2658 	final @property bool hidden() {
2659 		return _hidden;
2660 	}
2661 
2662 	/// Shows or hides the window based on the bool argument.
2663 	final @property void hidden(bool b) {
2664 		_hidden = b;
2665 		version(Windows) {
2666 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2667 		} else version(X11) {
2668 			if(b)
2669 				//XUnmapWindow(impl.display, impl.window);
2670 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2671 			else
2672 				XMapWindow(impl.display, impl.window);
2673 		} else version(OSXCocoa) {
2674 			throw new NotYetImplementedException();
2675 		} else static assert(0);
2676 	}
2677 
2678 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2679 	void opacity(double opacity) @property
2680 	in {
2681 		assert(opacity >= 0 && opacity <= 1);
2682 	} do {
2683 		version (Windows) {
2684 			impl.setOpacity(cast(ubyte)(255 * opacity));
2685 		} else version (X11) {
2686 			impl.setOpacity(cast(uint)(uint.max * opacity));
2687 		} else throw new NotYetImplementedException();
2688 	}
2689 
2690 	/++
2691 		Sets your event handlers, without entering the event loop. Useful if you
2692 		have multiple windows - set the handlers on each window, then only do
2693 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2694 
2695 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2696 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2697 		delegate signatures.
2698 	+/
2699 	void setEventHandlers(T...)(T eventHandlers) {
2700 		// FIXME: add more events
2701 		foreach(handler; eventHandlers) {
2702 			static if(__traits(compiles, handleKeyEvent = handler)) {
2703 				handleKeyEvent = handler;
2704 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2705 				handleCharEvent = handler;
2706 			} else static if(__traits(compiles, handlePulse = handler)) {
2707 				handlePulse = handler;
2708 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2709 				handleMouseEvent = handler;
2710 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2711 		}
2712 	}
2713 
2714 	/++
2715 		The event loop automatically returns when the window is closed
2716 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2717 		pulse timer is created. The event loop will block until an event
2718 		arrives or the pulse timer goes off.
2719 
2720 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2721 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2722 		[handleMouseEvent], based on the signature of delegates you provide.
2723 
2724 		Give one with no parameters to set a timer pulse handler. Give one that
2725 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2726 		and one that takes `dchar` for a char event handler. You can use as many
2727 		or as few handlers as you need for your application.
2728 
2729 		Bugs:
2730 
2731 		$(PITFALL
2732 			You should always have one event loop live for your application.
2733 			If you make two windows in sequence, the second call to eventLoop
2734 			might fail:
2735 
2736 			---
2737 			// don't do this!
2738 			auto window = new SimpleWindow();
2739 			window.eventLoop(0);
2740 
2741 			auto window2 = new SimpleWindow();
2742 			window2.eventLoop(0); // problematic! might crash
2743 			---
2744 
2745 			simpledisplay's current implementation assumes that final cleanup is
2746 			done when the event loop refcount reaches zero. So after the first
2747 			eventLoop returns, when there isn't already another one active, it assumes
2748 			the program will exit soon and cleans up.
2749 
2750 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2751 			it eventually, but in the mean time, there's an easy solution:
2752 
2753 			---
2754 			// do this
2755 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2756 
2757 			auto window = new SimpleWindow();
2758 			window.eventLoop(0);
2759 
2760 			auto window2 = new SimpleWindow();
2761 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2762 			---
2763 
2764 			By adding a top-level reference to the event loop, it ensures the final cleanup
2765 			is not performed until it goes out of scope too, letting the individual window loops
2766 			work without trouble despite the bug.
2767 		)
2768 
2769 		History:
2770 			The overload without `pulseTimeout` was added on December 8, 2021.
2771 
2772 			On December 9, 2021, the default blocking mode (which is now configurable
2773 			because [eventLoopWithBlockingMode] was added) switched from
2774 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2775 			should almost never be noticeable to you since the typical simpledisplay
2776 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2777 
2778 		See_Also:
2779 			[eventLoopWithBlockingMode]
2780 	+/
2781 	final int eventLoop(T...)(
2782 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2783 		T eventHandlers) /// delegate list like std.concurrency.receive
2784 	{
2785 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2786 	}
2787 
2788 	/// ditto
2789 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2790 	{
2791 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2792 	}
2793 
2794 	/++
2795 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2796 
2797 		History:
2798 			Added December 8, 2021 (dub v10.5)
2799 
2800 			Previously, this implementation was right inside [eventLoop], but when I wanted
2801 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2802 			just renamed it instead of adding as an overload. Besides, the new name makes it
2803 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2804 
2805 		See_Also:
2806 			[SimpleWindow.eventLoop], [EventLoop]
2807 
2808 		Bugs:
2809 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2810 	+/
2811 	final int eventLoopWithBlockingMode(T...)(
2812 		BlockingMode blockingMode, /// when you want this function to block until
2813 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2814 		T eventHandlers) /// delegate list like std.concurrency.receive
2815 	{
2816 		setEventHandlers(eventHandlers);
2817 
2818 		version(with_eventloop) {
2819 			// delegates event loop to my other module
2820 			version(X11)
2821 				XFlush(display);
2822 
2823 			import arsd.eventloop;
2824 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2825 			scope(exit) clearInterval(handle);
2826 
2827 			loop();
2828 			return 0;
2829 		} else version(OSXCocoa) {
2830 			// FIXME
2831 			if (handlePulse !is null && pulseTimeout != 0) {
2832 				timer = scheduledTimer(pulseTimeout*1e-3,
2833 					view, sel_registerName("simpledisplay_pulse"),
2834 					null, true);
2835 			}
2836 
2837             		setNeedsDisplay(view, true);
2838             		run(NSApp);
2839             		return 0;
2840         	} else {
2841 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2842 
2843 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2844 				return 0;
2845 
2846 			return el.run(
2847 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2848 					null :
2849 					&this.notClosed
2850 			);
2851 		}
2852 	}
2853 
2854 	/++
2855 		This lets you draw on the window (or its backing buffer) using basic
2856 		2D primitives.
2857 
2858 		Be sure to call this in a limited scope because your changes will not
2859 		actually appear on the window until ScreenPainter's destructor runs.
2860 
2861 		Returns: an instance of [ScreenPainter], which has the drawing methods
2862 		on it to draw on this window.
2863 
2864 		Params:
2865 			manualInvalidations = if you set this to true, you will need to
2866 			set the invalid rectangle on the painter yourself. If false, it
2867 			assumes the whole window has been redrawn each time you draw.
2868 
2869 			Only invalidated rectangles are blitted back to the window when
2870 			the destructor runs. Doing this yourself can reduce flickering
2871 			of child windows.
2872 
2873 		History:
2874 			The `manualInvalidations` parameter overload was added on
2875 			December 30, 2021 (dub v10.5)
2876 	+/
2877 	ScreenPainter draw() {
2878 		return draw(false);
2879 	}
2880 	/// ditto
2881 	ScreenPainter draw(bool manualInvalidations) {
2882 		return impl.getPainter(manualInvalidations);
2883 	}
2884 
2885 	// This is here to implement the interface we use for various native handlers.
2886 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2887 
2888 	// maps native window handles to SimpleWindow instances, if there are any
2889 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2890 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2891 
2892 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
2893 	private int _virtualWidth;
2894 	private int _virtualHeight;
2895 
2896 	/// Width of the window's drawable client area, in pixels.
2897 	@scriptable
2898 	final @property int width() const pure nothrow @safe @nogc {
2899 		if(resizability == Resizability.automaticallyScaleIfPossible)
2900 			return _virtualWidth;
2901 		else
2902 			return _width;
2903 	}
2904 
2905 	/// Height of the window's drawable client area, in pixels.
2906 	@scriptable
2907 	final @property int height() const pure nothrow @safe @nogc {
2908 		if(resizability == Resizability.automaticallyScaleIfPossible)
2909 			return _virtualHeight;
2910 		else
2911 			return _height;
2912 	}
2913 
2914 	/++
2915 		Returns the actual size of the window, bypassing the logical
2916 		illusions of [Resizability.automaticallyScaleIfPossible].
2917 
2918 		History:
2919 			Added November 11, 2022 (dub v10.10)
2920 	+/
2921 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
2922 		return Size(_width, _height);
2923 	}
2924 
2925 
2926 	private int _width;
2927 	private int _height;
2928 
2929 	// HACK: making the best of some copy constructor woes with refcounting
2930 	private ScreenPainterImplementation* activeScreenPainter_;
2931 
2932 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2933 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2934 
2935 	private OpenGlOptions openglMode;
2936 	private Resizability resizability;
2937 	private WindowTypes windowType;
2938 	private int customizationFlags;
2939 
2940 	/// `true` if OpenGL was initialized for this window.
2941 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2942 		version(without_opengl)
2943 			return false;
2944 		else
2945 			return (openglMode == OpenGlOptions.yes);
2946 	}
2947 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2948 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2949 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2950 
2951 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2952 	/// to call this, as it's not recommended to share window between threads.
2953 	void mtLock () {
2954 		version(X11) {
2955 			XLockDisplay(this.display);
2956 		}
2957 	}
2958 
2959 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2960 	/// to call this, as it's not recommended to share window between threads.
2961 	void mtUnlock () {
2962 		version(X11) {
2963 			XUnlockDisplay(this.display);
2964 		}
2965 	}
2966 
2967 	/// Emit a beep to get user's attention.
2968 	void beep () {
2969 		version(X11) {
2970 			XBell(this.display, 100);
2971 		} else version(Windows) {
2972 			MessageBeep(0xFFFFFFFF);
2973 		}
2974 	}
2975 
2976 
2977 
2978 	version(without_opengl) {} else {
2979 
2980 		/// 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`.
2981 		void delegate() redrawOpenGlScene;
2982 
2983 		/// This will allow you to change OpenGL vsync state.
2984 		final @property void vsync (bool wait) {
2985 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2986 		  version(X11) {
2987 		    setAsCurrentOpenGlContext();
2988 		    glxSetVSync(display, impl.window, wait);
2989 		  } else version(Windows) {
2990 		    setAsCurrentOpenGlContext();
2991                     wglSetVSync(wait);
2992 		  }
2993 		}
2994 
2995 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
2996 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
2997 		/// enough without waiting 'em to finish their frame business.
2998 		bool useGLFinish = true;
2999 
3000 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3001 		/// 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.
3002 		void redrawOpenGlSceneNow() {
3003 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3004 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3005 			if(redrawOpenGlScene is null)
3006 				return;
3007 
3008 			this.mtLock();
3009 			scope(exit) this.mtUnlock();
3010 
3011 			this.setAsCurrentOpenGlContext();
3012 
3013 			redrawOpenGlScene();
3014 
3015 			this.swapOpenGlBuffers();
3016 			// 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.
3017 			if (useGLFinish) glFinish();
3018 		}
3019 
3020 		private bool redrawOpenGlSceneSoonSet = false;
3021 		private static class RedrawOpenGlSceneEvent {
3022 			SimpleWindow w;
3023 			this(SimpleWindow w) { this.w = w; }
3024 		}
3025 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3026 		/++
3027 			Queues an opengl redraw as soon as the other pending events are cleared.
3028 		+/
3029 		void redrawOpenGlSceneSoon() {
3030 			if(redrawOpenGlScene is null)
3031 				return;
3032 
3033 			if(!redrawOpenGlSceneSoonSet) {
3034 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3035 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3036 				redrawOpenGlSceneSoonSet = true;
3037 			}
3038 			this.postEvent(redrawOpenGlSceneEvent, true);
3039 		}
3040 
3041 
3042 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3043 		void setAsCurrentOpenGlContext() {
3044 			assert(openglMode == OpenGlOptions.yes);
3045 			version(X11) {
3046 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3047 					throw new Exception("glXMakeCurrent");
3048 			} else version(Windows) {
3049 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3050 				if (!wglMakeCurrent(ghDC, ghRC))
3051 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3052 			}
3053 		}
3054 
3055 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3056 		/// This doesn't throw, returning success flag instead.
3057 		bool setAsCurrentOpenGlContextNT() nothrow {
3058 			assert(openglMode == OpenGlOptions.yes);
3059 			version(X11) {
3060 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3061 			} else version(Windows) {
3062 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3063 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3064 			}
3065 		}
3066 
3067 		/// 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.
3068 		/// This doesn't throw, returning success flag instead.
3069 		bool releaseCurrentOpenGlContext() nothrow {
3070 			assert(openglMode == OpenGlOptions.yes);
3071 			version(X11) {
3072 				return (glXMakeCurrent(display, 0, null) != 0);
3073 			} else version(Windows) {
3074 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3075 				return wglMakeCurrent(ghDC, null) ? true : false;
3076 			}
3077 		}
3078 
3079 		/++
3080 			simpledisplay always uses double buffering, usually automatically. This
3081 			manually swaps the OpenGL buffers.
3082 
3083 
3084 			You should not need to call this yourself because simpledisplay will do it
3085 			for you after calling your `redrawOpenGlScene`.
3086 
3087 			Remember that this may throw an exception, which you can catch in a multithreaded
3088 			application to keep your thread from dying from an unhandled exception.
3089 		+/
3090 		void swapOpenGlBuffers() {
3091 			assert(openglMode == OpenGlOptions.yes);
3092 			version(X11) {
3093 				if (!this._visible) return; // no need to do this if window is invisible
3094 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3095 				glXSwapBuffers(display, impl.window);
3096 			} else version(Windows) {
3097 				SwapBuffers(ghDC);
3098 			}
3099 		}
3100 	}
3101 
3102 	/++
3103 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3104 
3105 
3106 		---
3107 			auto window = new SimpleWindow(100, 100, "First title");
3108 			window.title = "A new title";
3109 		---
3110 
3111 		You may call this function at any time.
3112 	+/
3113 	@property void title(string title) {
3114 		_title = title;
3115 		version(OSXCocoa) throw new NotYetImplementedException(); else
3116 		impl.setTitle(title);
3117 	}
3118 
3119 	private string _title;
3120 
3121 	/// Gets the title
3122 	@property string title() {
3123 		if(_title is null)
3124 			_title = getRealTitle();
3125 		return _title;
3126 	}
3127 
3128 	/++
3129 		Get the title as set by the window manager.
3130 		May not match what you attempted to set.
3131 	+/
3132 	string getRealTitle() {
3133 		static if(is(typeof(impl.getTitle())))
3134 			return impl.getTitle();
3135 		else
3136 			return null;
3137 	}
3138 
3139 	// don't use this generally it is not yet really released
3140 	version(X11)
3141 	@property Image secret_icon() {
3142 		return secret_icon_inner;
3143 	}
3144 	private Image secret_icon_inner;
3145 
3146 
3147 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3148 	@property void icon(MemoryImage icon) {
3149 		if(icon is null)
3150 			return;
3151 		auto tci = icon.getAsTrueColorImage();
3152 		version(Windows) {
3153 			winIcon = new WindowsIcon(icon);
3154 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3155 		} else version(X11) {
3156 			secret_icon_inner = Image.fromMemoryImage(icon);
3157 			// FIXME: ensure this is correct
3158 			auto display = XDisplayConnection.get;
3159 			arch_ulong[] buffer;
3160 			buffer ~= icon.width;
3161 			buffer ~= icon.height;
3162 			foreach(c; tci.imageData.colors) {
3163 				arch_ulong b;
3164 				b |= c.a << 24;
3165 				b |= c.r << 16;
3166 				b |= c.g << 8;
3167 				b |= c.b;
3168 				buffer ~= b;
3169 			}
3170 
3171 			XChangeProperty(
3172 				display,
3173 				impl.window,
3174 				GetAtom!("_NET_WM_ICON", true)(display),
3175 				GetAtom!"CARDINAL"(display),
3176 				32 /* bits */,
3177 				0 /*PropModeReplace*/,
3178 				buffer.ptr,
3179 				cast(int) buffer.length);
3180 		} else version(OSXCocoa) {
3181 			throw new NotYetImplementedException();
3182 		} else static assert(0);
3183 	}
3184 
3185 	version(Windows)
3186 		private WindowsIcon winIcon;
3187 
3188 	bool _suppressDestruction;
3189 
3190 	~this() {
3191 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3192 		if(_suppressDestruction)
3193 			return;
3194 		impl.dispose();
3195 	}
3196 
3197 	private bool _closed;
3198 
3199 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3200 	/*
3201 	ScreenPainter drawTransiently() {
3202 		return impl.getPainter();
3203 	}
3204 	*/
3205 
3206 	/// Draws an image on the window. This is meant to provide quick look
3207 	/// of a static image generated elsewhere.
3208 	@property void image(Image i) {
3209 	/+
3210 		version(Windows) {
3211 			BITMAP bm;
3212 			HDC hdc = GetDC(hwnd);
3213 			HDC hdcMem = CreateCompatibleDC(hdc);
3214 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3215 
3216 			GetObject(i.handle, bm.sizeof, &bm);
3217 
3218 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3219 
3220 			SelectObject(hdcMem, hbmOld);
3221 			DeleteDC(hdcMem);
3222 			ReleaseDC(hwnd, hdc);
3223 
3224 			/*
3225 			RECT r;
3226 			r.right = i.width;
3227 			r.bottom = i.height;
3228 			InvalidateRect(hwnd, &r, false);
3229 			*/
3230 		} else
3231 		version(X11) {
3232 			if(!destroyed) {
3233 				if(i.usingXshm)
3234 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3235 				else
3236 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3237 			}
3238 		} else
3239 		version(OSXCocoa) {
3240 			draw().drawImage(Point(0, 0), i);
3241 			setNeedsDisplay(view, true);
3242 		} else static assert(0);
3243 	+/
3244 		auto painter = this.draw;
3245 		painter.drawImage(Point(0, 0), i);
3246 	}
3247 
3248 	/++
3249 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3250 
3251 		---
3252 		window.cursor = GenericCursor.Help;
3253 		// now the window mouse cursor is set to a generic help
3254 		---
3255 
3256 	+/
3257 	@property void cursor(MouseCursor cursor) {
3258 		version(OSXCocoa)
3259 			featureNotImplemented();
3260 		else
3261 		if(this.impl.curHidden <= 0) {
3262 			static if(UsingSimpledisplayX11) {
3263 				auto ch = cursor.cursorHandle;
3264 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3265 			} else version(Windows) {
3266 				auto ch = cursor.cursorHandle;
3267 				impl.currentCursor = ch;
3268 				SetCursor(ch); // redraw without waiting for mouse movement to update
3269 			} else featureNotImplemented();
3270 		}
3271 
3272 	}
3273 
3274 	/// What follows are the event handlers. These are set automatically
3275 	/// by the eventLoop function, but are still public so you can change
3276 	/// them later. wasPressed == true means key down. false == key up.
3277 
3278 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3279 	void delegate(KeyEvent ke) handleKeyEvent;
3280 
3281 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3282 	void delegate(dchar c) handleCharEvent;
3283 
3284 	/// Handles a timer pulse. Settable through setEventHandlers.
3285 	void delegate() handlePulse;
3286 
3287 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3288 	void delegate(bool) onFocusChange;
3289 
3290 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3291 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3292 	void delegate() onClosing;
3293 
3294 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3295 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3296 	 * last minute cleanup. */
3297 	void delegate() onDestroyed;
3298 
3299 	static if (UsingSimpledisplayX11)
3300 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3301 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3302 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3303 	 *
3304 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3305 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3306 
3307 	//version(Windows)
3308 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3309 
3310 	private {
3311 		int lastMouseX = int.min;
3312 		int lastMouseY = int.min;
3313 		void mdx(ref MouseEvent ev) {
3314 			if(lastMouseX == int.min || lastMouseY == int.min) {
3315 				ev.dx = 0;
3316 				ev.dy = 0;
3317 			} else {
3318 				ev.dx = ev.x - lastMouseX;
3319 				ev.dy = ev.y - lastMouseY;
3320 			}
3321 
3322 			lastMouseX = ev.x;
3323 			lastMouseY = ev.y;
3324 		}
3325 	}
3326 
3327 	/// Mouse event handler. Settable through setEventHandlers.
3328 	void delegate(MouseEvent) handleMouseEvent;
3329 
3330 	/// use to redraw child widgets if you use system apis to add stuff
3331 	void delegate() paintingFinished;
3332 
3333 	void delegate() paintingFinishedDg() {
3334 		return paintingFinished;
3335 	}
3336 
3337 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3338 	/// for this to ever happen.
3339 	void delegate(int width, int height) windowResized;
3340 
3341 	/++
3342 		Platform specific - handle any native message this window gets.
3343 
3344 		Note: this is called *in addition to* other event handlers, unless you either:
3345 
3346 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3347 
3348 		2) On Windows, set the `mustReturn` parameter to 1 indicating you've done it and your return value should be forwarded to the operating system. If you do not set `mustReturn`, your return value will be discarded.
3349 
3350 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3351 
3352 		On X, it takes the form of `int delegate(XEvent)`.
3353 
3354 		History:
3355 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3356 
3357 			Prior to November 27, 2021, the `mustReturn` parameter was not present, and the Windows implementation would discard return values. There's still a deprecated shim with that signature, but since the return value is often important, you shouldn't use it.
3358 	+/
3359 	NativeEventHandler handleNativeEvent_;
3360 
3361 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3362 		return handleNativeEvent_;
3363 	}
3364 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3365 		handleNativeEvent_ = neh;
3366 	}
3367 
3368 	version(Windows)
3369 	// compatibility shim with the old deprecated way
3370 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3371 	deprecated("This old api ignored your non-zero return values and that hurt it a lot. Add an `out int pleaseReturn` param to your delegate and set it to one if you must return the result to Windows. Otherwise, leave it zero and processing will continue through to the default window message processor.") @property void handleNativeEvent(int delegate(HWND, UINT, WPARAM, LPARAM) dg) {
3372 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3373 			auto ret = dg(h, m, w, l);
3374 			if(ret == 0)
3375 				r = 1;
3376 			return ret;
3377 		};
3378 	}
3379 
3380 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3381 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3382 	/// this instead and it will work the same way.
3383 	__gshared NativeEventHandler handleNativeGlobalEvent;
3384 
3385 //  private:
3386 	/// The native implementation is available, but you shouldn't use it unless you are
3387 	/// familiar with the underlying operating system, don't mind depending on it, and
3388 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3389 	/// do what you need to do with handleNativeEvent instead.
3390 	///
3391 	/// This is likely to eventually change to be just a struct holding platform-specific
3392 	/// handles instead of a template mixin at some point because I'm not happy with the
3393 	/// code duplication here (ironically).
3394 	mixin NativeSimpleWindowImplementation!() impl;
3395 
3396 	/**
3397 		This is in-process one-way (from anything to window) event sending mechanics.
3398 		It is thread-safe, so it can be used in multi-threaded applications to send,
3399 		for example, "wake up and repaint" events when thread completed some operation.
3400 		This will allow to avoid using timer pulse to check events with synchronization,
3401 		'cause event handler will be called in UI thread. You can stop guessing which
3402 		pulse frequency will be enough for your app.
3403 		Note that events handlers may be called in arbitrary order, i.e. last registered
3404 		handler can be called first, and vice versa.
3405 	*/
3406 public:
3407 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3408 	 * "spamming" window with events it can't cope with.
3409 	 * It is safe to call this from non-UI threads.
3410 	 */
3411 	@property bool eventQueueEmpty() () {
3412 		synchronized(this) {
3413 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3414 		}
3415 		return true;
3416 	}
3417 
3418 	/** Does our custom event queue contains at least one with the given type?
3419 	 * Can be used in simple cases to prevent "spamming" window with events
3420 	 * it can't cope with.
3421 	 * It is safe to call this from non-UI threads.
3422 	 */
3423 	@property bool eventQueued(ET:Object) () {
3424 		synchronized(this) {
3425 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3426 				if (!o.doProcess) {
3427 					if (cast(ET)(o.evt)) return true;
3428 				}
3429 			}
3430 		}
3431 		return false;
3432 	}
3433 
3434 	/++
3435 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3436 
3437 		History:
3438 			Added May 12, 2021
3439 	+/
3440 	void delegate(Exception e) nothrow eventUncaughtException;
3441 
3442 	/** Add listener for custom event. Can be used like this:
3443 	 *
3444 	 * ---------------------
3445 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3446 	 *   ...
3447 	 *   win.removeEventListener(eid);
3448 	 * ---------------------
3449 	 *
3450 	 * Returns: 0 on failure (should never happen, so ignore it)
3451 	 *
3452 	 * $(WARNING Don't use this method in object destructors!)
3453 	 *
3454 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3455 	 *           'cause if event handler id counter will overflow, you won't be able
3456 	 *           to register any more events.)
3457 	 */
3458 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3459 		if (dg is null) return 0; // ignore empty handlers
3460 		synchronized(this) {
3461 			//FIXME: abort on overflow?
3462 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3463 			EventHandlerEntry e;
3464 			e.dg = delegate (Object o) {
3465 				if (auto co = cast(ET)o) {
3466 					try {
3467 						dg(co);
3468 					} catch (Exception e) {
3469 						// sorry!
3470 						if(eventUncaughtException)
3471 							eventUncaughtException(e);
3472 					}
3473 					return true;
3474 				}
3475 				return false;
3476 			};
3477 			e.id = lastUsedHandlerId;
3478 			auto optr = eventHandlers.ptr;
3479 			eventHandlers ~= e;
3480 			if (eventHandlers.ptr !is optr) {
3481 				import core.memory : GC;
3482 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3483 			}
3484 			return lastUsedHandlerId;
3485 		}
3486 	}
3487 
3488 	/// Remove event listener. It is safe to pass invalid event id here.
3489 	/// $(WARNING Don't use this method in object destructors!)
3490 	void removeEventListener() (uint id) {
3491 		if (id == 0 || id > lastUsedHandlerId) return;
3492 		synchronized(this) {
3493 			foreach (immutable idx; 0..eventHandlers.length) {
3494 				if (eventHandlers[idx].id == id) {
3495 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3496 					eventHandlers[$-1].dg = null;
3497 					eventHandlers.length -= 1;
3498 					eventHandlers.assumeSafeAppend;
3499 					return;
3500 				}
3501 			}
3502 		}
3503 	}
3504 
3505 	/// Post event to queue. It is safe to call this from non-UI threads.
3506 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3507 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3508 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3509 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3510 		if (this.closed) return false; // closed windows can't handle events
3511 
3512 		// remove all events of type `ET`
3513 		void removeAllET () {
3514 			uint eidx = 0, ec = eventQueueUsed;
3515 			auto eptr = eventQueue.ptr;
3516 			while (eidx < ec) {
3517 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3518 				if (cast(ET)eptr.evt !is null) {
3519 					// i found her!
3520 					if (inCustomEventProcessor) {
3521 						// if we're in custom event processing loop, processor will clear it for us
3522 						eptr.evt = null;
3523 						++eidx;
3524 						++eptr;
3525 					} else {
3526 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3527 						ec = --eventQueueUsed;
3528 						// clear last event (it is already copied)
3529 						eventQueue.ptr[ec].evt = null;
3530 					}
3531 				} else {
3532 					++eidx;
3533 					++eptr;
3534 				}
3535 			}
3536 		}
3537 
3538 		if (evt is null) {
3539 			if (replace) { synchronized(this) removeAllET(); }
3540 			// ignore empty events, they can't be handled anyway
3541 			return false;
3542 		}
3543 
3544 		// add events even if no event FD/event object created yet
3545 		synchronized(this) {
3546 			if (replace) removeAllET();
3547 			if (eventQueueUsed == uint.max) return false; // just in case
3548 			if (eventQueueUsed < eventQueue.length) {
3549 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3550 			} else {
3551 				if (eventQueue.capacity == eventQueue.length) {
3552 					// need to reallocate; do a trick to ensure that old array is cleared
3553 					auto oarr = eventQueue;
3554 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3555 					// just in case, do yet another check
3556 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3557 					import core.memory : GC;
3558 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3559 				} else {
3560 					auto optr = eventQueue.ptr;
3561 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3562 					assert(eventQueue.ptr is optr);
3563 				}
3564 				++eventQueueUsed;
3565 				assert(eventQueueUsed == eventQueue.length);
3566 			}
3567 			if (!eventWakeUp()) {
3568 				// can't wake up event processor, so there is no reason to keep the event
3569 				assert(eventQueueUsed > 0);
3570 				eventQueue[--eventQueueUsed].evt = null;
3571 				return false;
3572 			}
3573 			return true;
3574 		}
3575 	}
3576 
3577 	/// Post event to queue. It is safe to call this from non-UI threads.
3578 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3579 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3580 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3581 		return postTimeout!ET(evt, 0, replace);
3582 	}
3583 
3584 private:
3585 	private import core.time : MonoTime;
3586 
3587 	version(Posix) {
3588 		__gshared int customEventFDRead = -1;
3589 		__gshared int customEventFDWrite = -1;
3590 		__gshared int customSignalFD = -1;
3591 	} else version(Windows) {
3592 		__gshared HANDLE customEventH = null;
3593 	}
3594 
3595 	// wake up event processor
3596 	static bool eventWakeUp () {
3597 		version(X11) {
3598 			import core.sys.posix.unistd : write;
3599 			ulong n = 1;
3600 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3601 			return true;
3602 		} else version(Windows) {
3603 			if (customEventH !is null) SetEvent(customEventH);
3604 			return true;
3605 		} else {
3606 			// not implemented for other OSes
3607 			return false;
3608 		}
3609 	}
3610 
3611 	static struct QueuedEvent {
3612 		Object evt;
3613 		bool timed = false;
3614 		MonoTime hittime = MonoTime.zero;
3615 		bool doProcess = false; // process event at the current iteration (internal flag)
3616 
3617 		this (Object aevt, uint toutmsecs) {
3618 			evt = aevt;
3619 			if (toutmsecs > 0) {
3620 				import core.time : msecs;
3621 				timed = true;
3622 				hittime = MonoTime.currTime+toutmsecs.msecs;
3623 			}
3624 		}
3625 	}
3626 
3627 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3628 	static struct EventHandlerEntry {
3629 		CustomEventHandler dg;
3630 		uint id;
3631 	}
3632 
3633 	uint lastUsedHandlerId;
3634 	EventHandlerEntry[] eventHandlers;
3635 	QueuedEvent[] eventQueue = null;
3636 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3637 	bool inCustomEventProcessor = false; // required to properly remove events
3638 
3639 	// process queued events and call custom event handlers
3640 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3641 	void processCustomEvents () {
3642 		bool hasSomethingToDo = false;
3643 		uint ecount;
3644 		bool ocep;
3645 		synchronized(this) {
3646 			ocep = inCustomEventProcessor;
3647 			inCustomEventProcessor = true;
3648 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3649 			auto ctt = MonoTime.currTime;
3650 			bool hasEmpty = false;
3651 			// mark events to process (this is required for `eventQueued()`)
3652 			foreach (ref qe; eventQueue[0..ecount]) {
3653 				if (qe.evt is null) { hasEmpty = true; continue; }
3654 				if (qe.timed) {
3655 					qe.doProcess = (qe.hittime <= ctt);
3656 				} else {
3657 					qe.doProcess = true;
3658 				}
3659 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3660 			}
3661 			if (!hasSomethingToDo) {
3662 				// remove empty events
3663 				if (hasEmpty) {
3664 					uint eidx = 0, ec = eventQueueUsed;
3665 					auto eptr = eventQueue.ptr;
3666 					while (eidx < ec) {
3667 						if (eptr.evt is null) {
3668 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3669 							ec = --eventQueueUsed;
3670 							eventQueue.ptr[ec].evt = null; // make GC life easier
3671 						} else {
3672 							++eidx;
3673 							++eptr;
3674 						}
3675 					}
3676 				}
3677 				inCustomEventProcessor = ocep;
3678 				return;
3679 			}
3680 		}
3681 		// process marked events
3682 		uint efree = 0; // non-processed events will be put at this index
3683 		EventHandlerEntry[] eh;
3684 		Object evt;
3685 		foreach (immutable eidx; 0..ecount) {
3686 			synchronized(this) {
3687 				if (!eventQueue[eidx].doProcess) {
3688 					// skip this event
3689 					assert(efree <= eidx);
3690 					if (efree != eidx) {
3691 						// copy this event to queue start
3692 						eventQueue[efree] = eventQueue[eidx];
3693 						eventQueue[eidx].evt = null; // just in case
3694 					}
3695 					++efree;
3696 					continue;
3697 				}
3698 				evt = eventQueue[eidx].evt;
3699 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3700 				if (evt is null) continue; // just in case
3701 				// try all handlers; this can be slow, but meh...
3702 				eh = eventHandlers;
3703 			}
3704 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3705 			evt = null;
3706 			eh = null;
3707 		}
3708 		synchronized(this) {
3709 			// move all unprocessed events to queue top; efree holds first "free index"
3710 			foreach (immutable eidx; ecount..eventQueueUsed) {
3711 				assert(efree <= eidx);
3712 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3713 				++efree;
3714 			}
3715 			eventQueueUsed = efree;
3716 			// wake up event processor on next event loop iteration if we have more queued events
3717 			// also, remove empty events
3718 			bool awaken = false;
3719 			uint eidx = 0, ec = eventQueueUsed;
3720 			auto eptr = eventQueue.ptr;
3721 			while (eidx < ec) {
3722 				if (eptr.evt is null) {
3723 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3724 					ec = --eventQueueUsed;
3725 					eventQueue.ptr[ec].evt = null; // make GC life easier
3726 				} else {
3727 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3728 					++eidx;
3729 					++eptr;
3730 				}
3731 			}
3732 			inCustomEventProcessor = ocep;
3733 		}
3734 	}
3735 
3736 	// for all windows in nativeMapping
3737 	package static void processAllCustomEvents () {
3738 
3739 		cleanupQueue.process();
3740 
3741 		justCommunication.processCustomEvents();
3742 
3743 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3744 			if (sw is null || sw.closed) continue;
3745 			sw.processCustomEvents();
3746 		}
3747 
3748 		runPendingRunInGuiThreadDelegates();
3749 	}
3750 
3751 	// 0: infinite (i.e. no scheduled events in queue)
3752 	uint eventQueueTimeoutMSecs () {
3753 		synchronized(this) {
3754 			if (eventQueueUsed == 0) return 0;
3755 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3756 			uint res = int.max;
3757 			auto ctt = MonoTime.currTime;
3758 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3759 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3760 				if (qe.doProcess) continue; // just in case
3761 				if (!qe.timed) return 1; // minimal
3762 				if (qe.hittime <= ctt) return 1; // minimal
3763 				auto tms = (qe.hittime-ctt).total!"msecs";
3764 				if (tms < 1) tms = 1; // safety net
3765 				if (tms >= int.max) tms = int.max-1; // and another safety net
3766 				if (res > tms) res = cast(uint)tms;
3767 			}
3768 			return (res >= int.max ? 0 : res);
3769 		}
3770 	}
3771 
3772 	// for all windows in nativeMapping
3773 	static uint eventAllQueueTimeoutMSecs () {
3774 		uint res = uint.max;
3775 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3776 			if (sw is null || sw.closed) continue;
3777 			uint to = sw.eventQueueTimeoutMSecs();
3778 			if (to && to < res) {
3779 				res = to;
3780 				if (to == 1) break; // can't have less than this
3781 			}
3782 		}
3783 		return (res >= int.max ? 0 : res);
3784 	}
3785 
3786 	version(X11) {
3787 		ResizeEvent pendingResizeEvent;
3788 	}
3789 
3790 	/++
3791 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3792 
3793 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3794 		worth so you can disable it by setting this to `true`.
3795 
3796 		History:
3797 			Added November 13, 2022.
3798 	+/
3799 	public bool suppressAutoOpenglViewport = false;
3800 	private void updateOpenglViewportIfNeeded(int width, int height) {
3801 		if(suppressAutoOpenglViewport) return;
3802 
3803 		version(without_opengl) {} else
3804 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3805 		// writeln(width, " ", height);
3806 			setAsCurrentOpenGlContextNT();
3807 			glViewport(0, 0, width, height);
3808 		}
3809 	}
3810 }
3811 
3812 /++
3813 	Magic pseudo-window for just posting events to a global queue.
3814 
3815 	Not entirely supported, I might delete it at any time.
3816 
3817 	Added Nov 5, 2021.
3818 +/
3819 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init);
3820 
3821 /* Drag and drop support { */
3822 version(X11) {
3823 
3824 } else version(Windows) {
3825 	import core.sys.windows.uuid;
3826 	import core.sys.windows.ole2;
3827 	import core.sys.windows.oleidl;
3828 	import core.sys.windows.objidl;
3829 	import core.sys.windows.wtypes;
3830 
3831 	pragma(lib, "ole32");
3832 	void initDnd() {
3833 		auto err = OleInitialize(null);
3834 		if(err != S_OK && err != S_FALSE)
3835 			throw new Exception("init");//err);
3836 	}
3837 }
3838 /* } End drag and drop support */
3839 
3840 
3841 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3842 /// See [GenericCursor].
3843 class MouseCursor {
3844 	int osId;
3845 	bool isStockCursor;
3846 	private this(int osId) {
3847 		this.osId = osId;
3848 		this.isStockCursor = true;
3849 	}
3850 
3851 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3852 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3853 
3854 	version(Windows) {
3855 		HCURSOR cursor_;
3856 		HCURSOR cursorHandle() {
3857 			if(cursor_ is null)
3858 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3859 			return cursor_;
3860 		}
3861 
3862 	} else static if(UsingSimpledisplayX11) {
3863 		Cursor cursor_ = None;
3864 		int xDisplaySequence;
3865 
3866 		Cursor cursorHandle() {
3867 			if(this.osId == None)
3868 				return None;
3869 
3870 			// we need to reload if we on a new X connection
3871 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3872 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3873 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3874 			}
3875 			return cursor_;
3876 		}
3877 	}
3878 }
3879 
3880 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3881 // https://tronche.com/gui/x/xlib/appendix/b/
3882 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3883 /// 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.
3884 enum GenericCursorType {
3885 	Default, /// The default arrow pointer.
3886 	Wait, /// A cursor indicating something is loading and the user must wait.
3887 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3888 	Help, /// A cursor indicating the user can get help about the pointer location.
3889 	Cross, /// A crosshair.
3890 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3891 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3892 	UpArrow, /// An arrow pointing straight up.
3893 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3894 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3895 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3896 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3897 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3898 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3899 
3900 }
3901 
3902 /*
3903 	X_plus == css cell == Windows ?
3904 */
3905 
3906 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3907 static struct GenericCursor {
3908 	static:
3909 	///
3910 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3911 		static MouseCursor mc;
3912 
3913 		auto type = __traits(getMember, GenericCursorType, str);
3914 
3915 		if(mc is null) {
3916 
3917 			version(Windows) {
3918 				int osId;
3919 				final switch(type) {
3920 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3921 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3922 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3923 					case GenericCursorType.Help: osId = IDC_HELP; break;
3924 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3925 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3926 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3927 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3928 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3929 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3930 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3931 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3932 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3933 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3934 				}
3935 			} else static if(UsingSimpledisplayX11) {
3936 				int osId;
3937 				final switch(type) {
3938 					case GenericCursorType.Default: osId = None; break;
3939 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3940 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3941 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3942 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3943 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3944 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3945 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3946 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3947 
3948 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3949 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3950 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3951 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3952 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3953 				}
3954 
3955 			} else featureNotImplemented();
3956 
3957 			mc = new MouseCursor(osId);
3958 		}
3959 		return mc;
3960 	}
3961 }
3962 
3963 
3964 /++
3965 	If you want to get more control over the event loop, you can use this.
3966 
3967 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
3968 	to `EventLoop.get.run`.
3969 +/
3970 struct EventLoop {
3971 	@disable this();
3972 
3973 	/// Gets a reference to an existing event loop
3974 	static EventLoop get() {
3975 		return EventLoop(0, null);
3976 	}
3977 
3978 	static void quitApplication() {
3979 		EventLoop.get().exit();
3980 	}
3981 
3982 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3983 
3984 	/// Construct an application-global event loop for yourself
3985 	/// See_Also: [SimpleWindow.setEventHandlers]
3986 	this(long pulseTimeout, void delegate() handlePulse) {
3987 		synchronized(monitor) {
3988 			if(impl is null) {
3989 				claimGuiThread();
3990 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3991 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
3992 			} else {
3993 				if(pulseTimeout) {
3994 					impl.pulseTimeout = pulseTimeout;
3995 					impl.handlePulse = handlePulse;
3996 				}
3997 			}
3998 			impl.refcount++;
3999 		}
4000 	}
4001 
4002 	~this() {
4003 		if(impl is null)
4004 			return;
4005 		impl.refcount--;
4006 		if(impl.refcount == 0) {
4007 			impl.dispose();
4008 			if(thisIsGuiThread)
4009 				guiThreadFinalize();
4010 		}
4011 
4012 	}
4013 
4014 	this(this) {
4015 		if(impl is null)
4016 			return;
4017 		impl.refcount++;
4018 	}
4019 
4020 	/// Runs the event loop until the whileCondition, if present, returns false
4021 	int run(bool delegate() whileCondition = null) {
4022 		assert(impl !is null);
4023 		impl.notExited = true;
4024 		return impl.run(whileCondition);
4025 	}
4026 
4027 	/// Exits the event loop
4028 	void exit() {
4029 		assert(impl !is null);
4030 		impl.notExited = false;
4031 	}
4032 
4033 	version(linux)
4034 	ref void delegate(int) signalHandler() {
4035 		assert(impl !is null);
4036 		return impl.signalHandler;
4037 	}
4038 
4039 	__gshared static EventLoopImpl* impl;
4040 }
4041 
4042 version(linux)
4043 	void delegate(int, int) globalHupHandler;
4044 
4045 version(Posix)
4046 	void makeNonBlocking(int fd) {
4047 		import fcntl = core.sys.posix.fcntl;
4048 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4049 		if(flags == -1)
4050 			throw new Exception("fcntl get");
4051 		flags |= fcntl.O_NONBLOCK;
4052 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4053 		if(s == -1)
4054 			throw new Exception("fcntl set");
4055 	}
4056 
4057 struct EventLoopImpl {
4058 	int refcount;
4059 
4060 	bool notExited = true;
4061 
4062 	version(linux) {
4063 		static import ep = core.sys.linux.epoll;
4064 		static import unix = core.sys.posix.unistd;
4065 		static import err = core.stdc.errno;
4066 		import core.sys.linux.timerfd;
4067 
4068 		void delegate(int) signalHandler;
4069 	}
4070 
4071 	version(X11) {
4072 		int pulseFd = -1;
4073 		version(linux) ep.epoll_event[16] events = void;
4074 	} else version(Windows) {
4075 		Timer pulser;
4076 		HANDLE[] handles;
4077 	}
4078 
4079 
4080 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4081 	/// to call this, as it's not recommended to share window between threads.
4082 	void mtLock () {
4083 		version(X11) {
4084 			XLockDisplay(this.display);
4085 		}
4086 	}
4087 
4088 	version(X11)
4089 	auto display() { return XDisplayConnection.get; }
4090 
4091 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4092 	/// to call this, as it's not recommended to share window between threads.
4093 	void mtUnlock () {
4094 		version(X11) {
4095 			XUnlockDisplay(this.display);
4096 		}
4097 	}
4098 
4099 	version(with_eventloop)
4100 	void initialize(long pulseTimeout) {}
4101 	else
4102 	void initialize(long pulseTimeout) {
4103 		version(Windows) {
4104 			if(pulseTimeout && handlePulse !is null)
4105 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4106 
4107 			if (customEventH is null) {
4108 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4109 				if (customEventH !is null) {
4110 					handles ~= customEventH;
4111 				} else {
4112 					// this is something that should not be; better be safe than sorry
4113 					throw new Exception("can't create eventfd for custom event processing");
4114 				}
4115 			}
4116 
4117 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4118 		}
4119 
4120 		version(linux) {
4121 			prepareEventLoop();
4122 			{
4123 				auto display = XDisplayConnection.get;
4124 				// adding Xlib file
4125 				ep.epoll_event ev = void;
4126 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4127 				ev.events = ep.EPOLLIN;
4128 				ev.data.fd = display.fd;
4129 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4130 					throw new Exception("add x fd");// ~ to!string(epollFd));
4131 				displayFd = display.fd;
4132 			}
4133 
4134 			if(pulseTimeout && handlePulse !is null) {
4135 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4136 				if(pulseFd == -1)
4137 					throw new Exception("pulse timer create failed");
4138 
4139 				itimerspec value;
4140 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4141 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4142 
4143 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4144 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4145 
4146 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4147 					throw new Exception("couldn't make pulse timer");
4148 
4149 				ep.epoll_event ev = void;
4150 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4151 				ev.events = ep.EPOLLIN;
4152 				ev.data.fd = pulseFd;
4153 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4154 			}
4155 
4156 			// eventfd for custom events
4157 			if (customEventFDWrite == -1) {
4158 				customEventFDWrite = eventfd(0, 0);
4159 				customEventFDRead = customEventFDWrite;
4160 				if (customEventFDRead >= 0) {
4161 					ep.epoll_event ev = void;
4162 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4163 					ev.events = ep.EPOLLIN;
4164 					ev.data.fd = customEventFDRead;
4165 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4166 				} else {
4167 					// this is something that should not be; better be safe than sorry
4168 					throw new Exception("can't create eventfd for custom event processing");
4169 				}
4170 			}
4171 
4172 			if (customSignalFD == -1) {
4173 				import core.sys.linux.sys.signalfd;
4174 
4175 				sigset_t sigset;
4176 				auto err = sigemptyset(&sigset);
4177 				assert(!err);
4178 				err = sigaddset(&sigset, SIGINT);
4179 				assert(!err);
4180 				err = sigaddset(&sigset, SIGHUP);
4181 				assert(!err);
4182 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4183 				assert(!err);
4184 
4185 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4186 				assert(customSignalFD != -1);
4187 
4188 				ep.epoll_event ev = void;
4189 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4190 				ev.events = ep.EPOLLIN;
4191 				ev.data.fd = customSignalFD;
4192 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4193 			}
4194 		} else version(Posix) {
4195 			prepareEventLoop();
4196 			if (customEventFDRead == -1) {
4197 				int[2] bfr;
4198 				import core.sys.posix.unistd;
4199 				auto ret = pipe(bfr);
4200 				if(ret == -1) throw new Exception("pipe");
4201 				customEventFDRead = bfr[0];
4202 				customEventFDWrite = bfr[1];
4203 			}
4204 
4205 		}
4206 
4207 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4208 
4209 		version(linux) {
4210 			this.mtLock();
4211 			scope(exit) this.mtUnlock();
4212 			XPending(display); // no, really
4213 		}
4214 
4215 		disposed = false;
4216 	}
4217 
4218 	bool disposed = true;
4219 	version(X11)
4220 		int displayFd = -1;
4221 
4222 	version(with_eventloop)
4223 	void dispose() {}
4224 	else
4225 	void dispose() {
4226 		disposed = true;
4227 		version(X11) {
4228 			if(pulseFd != -1) {
4229 				import unix = core.sys.posix.unistd;
4230 				unix.close(pulseFd);
4231 				pulseFd = -1;
4232 			}
4233 
4234 				version(linux)
4235 				if(displayFd != -1) {
4236 					// 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
4237 					ep.epoll_event ev = void;
4238 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4239 					ev.events = ep.EPOLLIN;
4240 					ev.data.fd = displayFd;
4241 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4242 					displayFd = -1;
4243 				}
4244 
4245 		} else version(Windows) {
4246 			if(pulser !is null) {
4247 				pulser.destroy();
4248 				pulser = null;
4249 			}
4250 			if (customEventH !is null) {
4251 				CloseHandle(customEventH);
4252 				customEventH = null;
4253 			}
4254 		}
4255 	}
4256 
4257 	this(long pulseTimeout, void delegate() handlePulse) {
4258 		this.pulseTimeout = pulseTimeout;
4259 		this.handlePulse = handlePulse;
4260 		initialize(pulseTimeout);
4261 	}
4262 
4263 	private long pulseTimeout;
4264 	void delegate() handlePulse;
4265 
4266 	~this() {
4267 		dispose();
4268 	}
4269 
4270 	version(Posix)
4271 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4272 	version(Posix)
4273 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4274 	version(linux)
4275 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4276 	version(Windows)
4277 	ref auto customEventH() { return SimpleWindow.customEventH; }
4278 
4279 	version(with_eventloop) {
4280 		int loopHelper(bool delegate() whileCondition) {
4281 			// FIXME: whileCondition
4282 			import arsd.eventloop;
4283 			loop();
4284 			return 0;
4285 		}
4286 	} else
4287 	int loopHelper(bool delegate() whileCondition) {
4288 		version(X11) {
4289 			bool done = false;
4290 
4291 			XFlush(display);
4292 			insideXEventLoop = true;
4293 			scope(exit) insideXEventLoop = false;
4294 
4295 			version(linux) {
4296 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4297 					bool forceXPending = false;
4298 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4299 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4300 					{
4301 						this.mtLock();
4302 						scope(exit) this.mtUnlock();
4303 						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
4304 					}
4305 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4306 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4307 					if(nfds == -1) {
4308 						if(err.errno == err.EINTR) {
4309 							//if(forceXPending) goto xpending;
4310 							continue; // interrupted by signal, just try again
4311 						}
4312 						throw new Exception("epoll wait failure");
4313 					}
4314 					// writeln(nfds, " ", events[0].data.fd);
4315 
4316 					SimpleWindow.processAllCustomEvents(); // anyway
4317 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4318 					foreach(idx; 0 .. nfds) {
4319 						if(done) break;
4320 						auto fd = events[idx].data.fd;
4321 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4322 						auto flags = events[idx].events;
4323 						if(flags & ep.EPOLLIN) {
4324 							if (fd == customSignalFD) {
4325 								version(linux) {
4326 									import core.sys.linux.sys.signalfd;
4327 									import core.sys.posix.unistd : read;
4328 									signalfd_siginfo info;
4329 									read(customSignalFD, &info, info.sizeof);
4330 
4331 									auto sig = info.ssi_signo;
4332 
4333 									if(EventLoop.get.signalHandler !is null) {
4334 										EventLoop.get.signalHandler()(sig);
4335 									} else {
4336 										EventLoop.get.exit();
4337 									}
4338 								}
4339 							} else if(fd == display.fd) {
4340 								version(sdddd) { writeln("X EVENT PENDING!"); }
4341 								this.mtLock();
4342 								scope(exit) this.mtUnlock();
4343 								while(!done && XPending(display)) {
4344 									done = doXNextEvent(this.display);
4345 								}
4346 								forceXPending = false;
4347 							} else if(fd == pulseFd) {
4348 								long expirationCount;
4349 								// 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...
4350 
4351 								handlePulse();
4352 
4353 								// read just to clear the buffer so poll doesn't trigger again
4354 								// BTW I read AFTER the pulse because if the pulse handler takes
4355 								// a lot of time to execute, we don't want the app to get stuck
4356 								// in a loop of timer hits without a chance to do anything else
4357 								//
4358 								// IOW handlePulse happens at most once per pulse interval.
4359 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4360 								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
4361 							} else if (fd == customEventFDRead) {
4362 								// we have some custom events; process 'em
4363 								import core.sys.posix.unistd : read;
4364 								ulong n;
4365 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4366 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4367 								//SimpleWindow.processAllCustomEvents();
4368 
4369 								forceXPending = true;
4370 							} else {
4371 								// some other timer
4372 								version(sdddd) { writeln("unknown fd: ", fd); }
4373 
4374 								if(Timer* t = fd in Timer.mapping)
4375 									(*t).trigger();
4376 
4377 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4378 									(*pfr).ready(flags);
4379 
4380 								// or i might add support for other FDs too
4381 								// but for now it is just timer
4382 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4383 							}
4384 						}
4385 						if(flags & ep.EPOLLHUP) {
4386 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4387 								(*pfr).hup(flags);
4388 							if(globalHupHandler)
4389 								globalHupHandler(fd, flags);
4390 						}
4391 						/+
4392 						} else {
4393 							// not interested in OUT, we are just reading here.
4394 							//
4395 							// error or hup might also be reported
4396 							// but it shouldn't here since we are only
4397 							// using a few types of FD and Xlib will report
4398 							// if it dies.
4399 							// so instead of thoughtfully handling it, I'll
4400 							// just throw. for now at least
4401 
4402 							throw new Exception("epoll did something else");
4403 						}
4404 						+/
4405 					}
4406 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4407 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4408 					xpending:
4409 					if (!done && forceXPending) {
4410 						this.mtLock();
4411 						scope(exit) this.mtUnlock();
4412 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4413 						while(!done && XPending(display)) {
4414 							done = doXNextEvent(this.display);
4415 						}
4416 					}
4417 				}
4418 			} else {
4419 				// Generic fallback: yes to simple pulse support,
4420 				// but NO timer support!
4421 
4422 				// FIXME: we could probably support the POSIX timer_create
4423 				// signal-based option, but I'm in no rush to write it since
4424 				// I prefer the fd-based functions.
4425 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4426 
4427 					import core.sys.posix.poll;
4428 
4429 					pollfd[] pfds;
4430 					pollfd[32] pfdsBuffer;
4431 					auto len = PosixFdReader.mapping.length + 2;
4432 					// FIXME: i should just reuse the buffer
4433 					if(len < pfdsBuffer.length)
4434 						pfds = pfdsBuffer[0 .. len];
4435 					else
4436 						pfds = new pollfd[](len);
4437 
4438 					pfds[0].fd = display.fd;
4439 					pfds[0].events = POLLIN;
4440 					pfds[0].revents = 0;
4441 
4442 					int slot = 1;
4443 
4444 					if(customEventFDRead != -1) {
4445 						pfds[slot].fd = customEventFDRead;
4446 						pfds[slot].events = POLLIN;
4447 						pfds[slot].revents = 0;
4448 
4449 						slot++;
4450 					}
4451 
4452 					foreach(fd, obj; PosixFdReader.mapping) {
4453 						if(!obj.enabled) continue;
4454 						pfds[slot].fd = fd;
4455 						pfds[slot].events = POLLIN;
4456 						pfds[slot].revents = 0;
4457 
4458 						slot++;
4459 					}
4460 
4461 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4462 					if(ret == -1) throw new Exception("poll");
4463 
4464 					if(ret == 0) {
4465 						// FIXME it may not necessarily time out if events keep coming
4466 						if(handlePulse !is null)
4467 							handlePulse();
4468 					} else {
4469 						foreach(s; 0 .. slot) {
4470 							if(pfds[s].revents == 0) continue;
4471 
4472 							if(pfds[s].fd == display.fd) {
4473 								while(!done && XPending(display)) {
4474 									this.mtLock();
4475 									scope(exit) this.mtUnlock();
4476 									done = doXNextEvent(this.display);
4477 								}
4478 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4479 
4480 								import core.sys.posix.unistd : read;
4481 								ulong n;
4482 								read(customEventFDRead, &n, n.sizeof);
4483 								SimpleWindow.processAllCustomEvents();
4484 							} else {
4485 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4486 								if(pfds[s].revents & POLLNVAL) {
4487 									obj.dispose();
4488 								} else {
4489 									obj.ready(pfds[s].revents);
4490 								}
4491 							}
4492 
4493 							ret--;
4494 							if(ret == 0) break;
4495 						}
4496 					}
4497 				}
4498 			}
4499 		}
4500 
4501 		version(Windows) {
4502 			int ret = -1;
4503 			MSG message;
4504 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4505 				eventLoopRound++;
4506 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4507 				auto waitResult = MsgWaitForMultipleObjectsEx(
4508 					cast(int) handles.length, handles.ptr,
4509 					(wto == 0 ? INFINITE : wto), /* timeout */
4510 					0x04FF, /* QS_ALLINPUT */
4511 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4512 
4513 				SimpleWindow.processAllCustomEvents(); // anyway
4514 				enum WAIT_OBJECT_0 = 0;
4515 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4516 					auto h = handles[waitResult - WAIT_OBJECT_0];
4517 					if(auto e = h in WindowsHandleReader.mapping) {
4518 						(*e).ready();
4519 					}
4520 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4521 					// message ready
4522 					int count;
4523 					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
4524 						ret = GetMessage(&message, null, 0, 0);
4525 						if(ret == -1)
4526 							throw new WindowsApiException("GetMessage", GetLastError());
4527 						TranslateMessage(&message);
4528 						DispatchMessage(&message);
4529 
4530 						count++;
4531 						if(count > 10)
4532 							break; // take the opportunity to catch up on other events
4533 
4534 						if(ret == 0) { // WM_QUIT
4535 							EventLoop.quitApplication();
4536 							break;
4537 						}
4538 					}
4539 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4540 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4541 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4542 					// timeout, should never happen since we aren't using it
4543 				} else if(waitResult == 0xFFFFFFFF) {
4544 						// failed
4545 						throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4546 				} else {
4547 					// idk....
4548 				}
4549 			}
4550 
4551 			// return message.wParam;
4552 			return 0;
4553 		} else {
4554 			return 0;
4555 		}
4556 	}
4557 
4558 	int run(bool delegate() whileCondition = null) {
4559 		if(disposed)
4560 			initialize(this.pulseTimeout);
4561 
4562 		version(X11) {
4563 			try {
4564 				return loopHelper(whileCondition);
4565 			} catch(XDisconnectException e) {
4566 				if(e.userRequested) {
4567 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4568 						item.discardConnectionState();
4569 					XCloseDisplay(XDisplayConnection.display);
4570 				}
4571 
4572 				XDisplayConnection.display = null;
4573 
4574 				this.dispose();
4575 
4576 				throw e;
4577 			}
4578 		} else {
4579 			return loopHelper(whileCondition);
4580 		}
4581 	}
4582 }
4583 
4584 
4585 /++
4586 	Provides an icon on the system notification area (also known as the system tray).
4587 
4588 
4589 	If a notification area is not available with the NotificationIcon object is created,
4590 	it will silently succeed and simply attempt to create one when an area becomes available.
4591 
4592 
4593 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
4594 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
4595 	use the older version.
4596 +/
4597 version(OSXCocoa) {} else // NotYetImplementedException
4598 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4599 
4600 	version(X11) {
4601 		void recreateAfterDisconnect() {
4602 			stateDiscarded = false;
4603 			clippixmap = None;
4604 			throw new Exception("NOT IMPLEMENTED");
4605 		}
4606 
4607 		bool stateDiscarded;
4608 		void discardConnectionState() {
4609 			stateDiscarded = true;
4610 		}
4611 	}
4612 
4613 
4614 	version(X11) {
4615 		Image img;
4616 
4617 		NativeEventHandler getNativeEventHandler() {
4618 			return delegate int(XEvent e) {
4619 				switch(e.type) {
4620 					case EventType.Expose:
4621 					//case EventType.VisibilityNotify:
4622 						redraw();
4623 					break;
4624 					case EventType.ClientMessage:
4625 						version(sddddd) {
4626 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4627 						writeln("\t", e.xclient.format);
4628 						writeln("\t", e.xclient.data.l);
4629 						}
4630 					break;
4631 					case EventType.ButtonPress:
4632 						auto event = e.xbutton;
4633 						if (onClick !is null || onClickEx !is null) {
4634 							MouseButton mb = cast(MouseButton)0;
4635 							switch (event.button) {
4636 								case 1: mb = MouseButton.left; break; // left
4637 								case 2: mb = MouseButton.middle; break; // middle
4638 								case 3: mb = MouseButton.right; break; // right
4639 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4640 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4641 								case 6: break; // scroll left...
4642 								case 7: break; // scroll right...
4643 								case 8: mb = MouseButton.backButton; break;
4644 								case 9: mb = MouseButton.forwardButton; break;
4645 								default:
4646 							}
4647 							if (mb) {
4648 								try { onClick()(mb); } catch (Exception) {}
4649 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4650 							}
4651 						}
4652 					break;
4653 					case EventType.EnterNotify:
4654 						if (onEnter !is null) {
4655 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4656 						}
4657 						break;
4658 					case EventType.LeaveNotify:
4659 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4660 						break;
4661 					case EventType.DestroyNotify:
4662 						active = false;
4663 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4664 					break;
4665 					case EventType.ConfigureNotify:
4666 						auto event = e.xconfigure;
4667 						this.width = event.width;
4668 						this.height = event.height;
4669 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4670 						redraw();
4671 					break;
4672 					default: return 1;
4673 				}
4674 				return 1;
4675 			};
4676 		}
4677 
4678 		/* private */ void hideBalloon() {
4679 			balloon.close();
4680 			version(with_timer)
4681 				timer.destroy();
4682 			balloon = null;
4683 			version(with_timer)
4684 				timer = null;
4685 		}
4686 
4687 		void redraw() {
4688 			if (!active) return;
4689 
4690 			auto display = XDisplayConnection.get;
4691 			auto gc = DefaultGC(display, DefaultScreen(display));
4692 			XClearWindow(display, nativeHandle);
4693 
4694 			XSetClipMask(display, gc, clippixmap);
4695 
4696 			XSetForeground(display, gc,
4697 				cast(uint) 0 << 16 |
4698 				cast(uint) 0 << 8 |
4699 				cast(uint) 0);
4700 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4701 
4702 			if (img is null) {
4703 				XSetForeground(display, gc,
4704 					cast(uint) 0 << 16 |
4705 					cast(uint) 127 << 8 |
4706 					cast(uint) 0);
4707 				XFillArc(display, nativeHandle,
4708 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4709 			} else {
4710 				int dx = 0;
4711 				int dy = 0;
4712 				if(width > img.width)
4713 					dx = (width - img.width) / 2;
4714 				if(height > img.height)
4715 					dy = (height - img.height) / 2;
4716 				XSetClipOrigin(display, gc, dx, dy);
4717 
4718 				if (img.usingXshm)
4719 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
4720 				else
4721 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
4722 			}
4723 			XSetClipMask(display, gc, None);
4724 			flushGui();
4725 		}
4726 
4727 		static Window getTrayOwner() {
4728 			auto display = XDisplayConnection.get;
4729 			auto i = cast(int) DefaultScreen(display);
4730 			if(i < 10 && i >= 0) {
4731 				static Atom atom;
4732 				if(atom == None)
4733 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4734 				return XGetSelectionOwner(display, atom);
4735 			}
4736 			return None;
4737 		}
4738 
4739 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4740 			auto to = getTrayOwner();
4741 			auto display = XDisplayConnection.get;
4742 			XEvent ev;
4743 			ev.xclient.type = EventType.ClientMessage;
4744 			ev.xclient.window = to;
4745 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4746 			ev.xclient.format = 32;
4747 			ev.xclient.data.l[0] = CurrentTime;
4748 			ev.xclient.data.l[1] = message;
4749 			ev.xclient.data.l[2] = d1;
4750 			ev.xclient.data.l[3] = d2;
4751 			ev.xclient.data.l[4] = d3;
4752 
4753 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4754 		}
4755 
4756 		private static NotificationAreaIcon[] activeIcons;
4757 
4758 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4759 		private void newManager() {
4760 			close();
4761 			createXWin();
4762 
4763 			if(this.clippixmap)
4764 				XFreePixmap(XDisplayConnection.get, clippixmap);
4765 			if(this.originalMemoryImage)
4766 				this.icon = this.originalMemoryImage;
4767 			else if(this.img)
4768 				this.icon = this.img;
4769 		}
4770 
4771 		private void createXWin () {
4772 			// create window
4773 			auto display = XDisplayConnection.get;
4774 
4775 			// to check for MANAGER on root window to catch new/changed tray owners
4776 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4777 			// so if a thing does appear, we can handle it
4778 			foreach(ai; activeIcons)
4779 				if(ai is this)
4780 					goto alreadythere;
4781 			activeIcons ~= this;
4782 			alreadythere:
4783 
4784 			// and check for an existing tray
4785 			auto trayOwner = getTrayOwner();
4786 			if(trayOwner == None)
4787 				return;
4788 				//throw new Exception("No notification area found");
4789 
4790 			Visual* v = cast(Visual*) CopyFromParent;
4791 			/+
4792 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4793 			if(visualProp !is null) {
4794 				c_ulong[] info = cast(c_ulong[]) visualProp;
4795 				if(info.length == 1) {
4796 					auto vid = info[0];
4797 					int returned;
4798 					XVisualInfo t;
4799 					t.visualid = vid;
4800 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4801 					if(got !is null) {
4802 						if(returned == 1) {
4803 							v = got.visual;
4804 							writeln("using special visual ", *got);
4805 						}
4806 						XFree(got);
4807 					}
4808 				}
4809 			}
4810 			+/
4811 
4812 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
4813 			assert(nativeWindow);
4814 
4815 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4816 
4817 			nativeHandle = nativeWindow;
4818 
4819 			///+
4820 			arch_ulong[2] info;
4821 			info[0] = 0;
4822 			info[1] = 1;
4823 
4824 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4825 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4826 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4827 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4828 
4829 			XChangeProperty(
4830 				display,
4831 				nativeWindow,
4832 				GetAtom!("_XEMBED_INFO", true)(display),
4833 				GetAtom!("_XEMBED_INFO", true)(display),
4834 				32 /* bits */,
4835 				0 /*PropModeReplace*/,
4836 				info.ptr,
4837 				2);
4838 
4839 			import core.sys.posix.unistd;
4840 			arch_ulong pid = getpid();
4841 
4842 			XChangeProperty(
4843 				display,
4844 				nativeWindow,
4845 				GetAtom!("_NET_WM_PID", true)(display),
4846 				XA_CARDINAL,
4847 				32 /* bits */,
4848 				0 /*PropModeReplace*/,
4849 				&pid,
4850 				1);
4851 
4852 			updateNetWmIcon();
4853 
4854 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4855 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4856 				XClassHint klass;
4857 				XWMHints wh;
4858 				XSizeHints size;
4859 				klass.res_name = sdpyWindowClassStr;
4860 				klass.res_class = sdpyWindowClassStr;
4861 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4862 			}
4863 
4864 				// believe it or not, THIS is what xfce needed for the 9999 issue
4865 				XSizeHints sh;
4866 					c_long spr;
4867 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4868 					sh.flags |= PMaxSize | PMinSize;
4869 				// FIXME maybe nicer resizing
4870 				sh.min_width = 16;
4871 				sh.min_height = 16;
4872 				sh.max_width = 16;
4873 				sh.max_height = 16;
4874 				XSetWMNormalHints(display, nativeWindow, &sh);
4875 
4876 
4877 			//+/
4878 
4879 
4880 			XSelectInput(display, nativeWindow,
4881 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4882 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4883 
4884 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4885 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4886 			active = true;
4887 		}
4888 
4889 		void updateNetWmIcon() {
4890 			if(img is null) return;
4891 			auto display = XDisplayConnection.get;
4892 			// FIXME: ensure this is correct
4893 			arch_ulong[] buffer;
4894 			auto imgMi = img.toTrueColorImage;
4895 			buffer ~= imgMi.width;
4896 			buffer ~= imgMi.height;
4897 			foreach(c; imgMi.imageData.colors) {
4898 				arch_ulong b;
4899 				b |= c.a << 24;
4900 				b |= c.r << 16;
4901 				b |= c.g << 8;
4902 				b |= c.b;
4903 				buffer ~= b;
4904 			}
4905 
4906 			XChangeProperty(
4907 				display,
4908 				nativeHandle,
4909 				GetAtom!"_NET_WM_ICON"(display),
4910 				GetAtom!"CARDINAL"(display),
4911 				32 /* bits */,
4912 				0 /*PropModeReplace*/,
4913 				buffer.ptr,
4914 				cast(int) buffer.length);
4915 		}
4916 
4917 
4918 
4919 		private SimpleWindow balloon;
4920 		version(with_timer)
4921 		private Timer timer;
4922 
4923 		private Window nativeHandle;
4924 		private Pixmap clippixmap = None;
4925 		private int width = 16;
4926 		private int height = 16;
4927 		private bool active = false;
4928 
4929 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4930 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4931 		void delegate () onLeave; /// X11 only.
4932 
4933 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4934 
4935 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4936 		void getWindowRect (out int x, out int y, out int width, out int height) {
4937 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4938 			Window dummyw;
4939 			auto dpy = XDisplayConnection.get;
4940 			//XWindowAttributes xwa;
4941 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4942 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4943 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4944 			width = this.width;
4945 			height = this.height;
4946 		}
4947 	}
4948 
4949 	/+
4950 		What I actually want from this:
4951 
4952 		* set / change: icon, tooltip
4953 		* handle: mouse click, right click
4954 		* show: notification bubble.
4955 	+/
4956 
4957 	version(Windows) {
4958 		WindowsIcon win32Icon;
4959 		HWND hwnd;
4960 
4961 		NOTIFYICONDATAW data;
4962 
4963 		NativeEventHandler getNativeEventHandler() {
4964 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
4965 				if(msg == WM_USER) {
4966 					auto event = LOWORD(lParam);
4967 					auto iconId = HIWORD(lParam);
4968 					//auto x = GET_X_LPARAM(wParam);
4969 					//auto y = GET_Y_LPARAM(wParam);
4970 					switch(event) {
4971 						case WM_LBUTTONDOWN:
4972 							onClick()(MouseButton.left);
4973 						break;
4974 						case WM_RBUTTONDOWN:
4975 							onClick()(MouseButton.right);
4976 						break;
4977 						case WM_MBUTTONDOWN:
4978 							onClick()(MouseButton.middle);
4979 						break;
4980 						case WM_MOUSEMOVE:
4981 							// sent, we could use it.
4982 						break;
4983 						case WM_MOUSEWHEEL:
4984 							// NOT SENT
4985 						break;
4986 						//case NIN_KEYSELECT:
4987 						//case NIN_SELECT:
4988 						//break;
4989 						default: {}
4990 					}
4991 				}
4992 				return 0;
4993 			};
4994 		}
4995 
4996 		enum NIF_SHOWTIP = 0x00000080;
4997 
4998 		private static struct NOTIFYICONDATAW {
4999 			DWORD cbSize;
5000 			HWND  hWnd;
5001 			UINT  uID;
5002 			UINT  uFlags;
5003 			UINT  uCallbackMessage;
5004 			HICON hIcon;
5005 			WCHAR[128] szTip;
5006 			DWORD dwState;
5007 			DWORD dwStateMask;
5008 			WCHAR[256] szInfo;
5009 			union {
5010 				UINT uTimeout;
5011 				UINT uVersion;
5012 			}
5013 			WCHAR[64] szInfoTitle;
5014 			DWORD dwInfoFlags;
5015 			GUID  guidItem;
5016 			HICON hBalloonIcon;
5017 		}
5018 
5019 	}
5020 
5021 	/++
5022 		Note that on Windows, only left, right, and middle buttons are sent.
5023 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5024 		program is meant to be used on Windows too.
5025 	+/
5026 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5027 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5028 		// but on X, we need an Image, so its canonical ctor is there. They should
5029 		// forward to each other though.
5030 		version(X11) {
5031 			this.name = name;
5032 			this.onClick = onClick;
5033 			createXWin();
5034 			this.icon = icon;
5035 		} else version(Windows) {
5036 			this.onClick = onClick;
5037 			this.win32Icon = new WindowsIcon(icon);
5038 
5039 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5040 
5041 			static bool registered = false;
5042 			if(!registered) {
5043 				WNDCLASSEX wc;
5044 				wc.cbSize = wc.sizeof;
5045 				wc.hInstance = hInstance;
5046 				wc.lpfnWndProc = &WndProc;
5047 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5048 				if(!RegisterClassExW(&wc))
5049 					throw new WindowsApiException("RegisterClass", GetLastError());
5050 				registered = true;
5051 			}
5052 
5053 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5054 			if(hwnd is null)
5055 				throw new WindowsApiException("CreateWindow", GetLastError());
5056 
5057 			data.cbSize = data.sizeof;
5058 			data.hWnd = hwnd;
5059 			data.uID = cast(uint) cast(void*) this;
5060 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5061 				// NIF_INFO means show balloon
5062 			data.uCallbackMessage = WM_USER;
5063 			data.hIcon = this.win32Icon.hIcon;
5064 			data.szTip = ""; // FIXME
5065 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5066 			data.dwStateMask = NIS_HIDDEN; // windows vista
5067 
5068 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5069 
5070 
5071 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5072 
5073 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5074 		} else version(OSXCocoa) {
5075 			throw new NotYetImplementedException();
5076 		} else static assert(0);
5077 	}
5078 
5079 	/// ditto
5080 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5081 		version(X11) {
5082 			this.onClick = onClick;
5083 			this.name = name;
5084 			createXWin();
5085 			this.icon = icon;
5086 		} else version(Windows) {
5087 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5088 		} else version(OSXCocoa) {
5089 			throw new NotYetImplementedException();
5090 		} else static assert(0);
5091 	}
5092 
5093 	version(X11) {
5094 		/++
5095 			X-specific extension (for now at least)
5096 		+/
5097 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5098 			this.onClickEx = onClickEx;
5099 			createXWin();
5100 			if (icon !is null) this.icon = icon;
5101 		}
5102 
5103 		/// ditto
5104 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5105 			this.onClickEx = onClickEx;
5106 			createXWin();
5107 			this.icon = icon;
5108 		}
5109 	}
5110 
5111 	private void delegate (MouseButton button) onClick_;
5112 
5113 	///
5114 	@property final void delegate(MouseButton) onClick() {
5115 		if(onClick_ is null)
5116 			onClick_ = delegate void(MouseButton) {};
5117 		return onClick_;
5118 	}
5119 
5120 	/// ditto
5121 	@property final void onClick(void delegate(MouseButton) handler) {
5122 		// I made this a property setter so we can wrap smaller arg
5123 		// delegates and just forward all to onClickEx or something.
5124 		onClick_ = handler;
5125 	}
5126 
5127 
5128 	string name_;
5129 	@property void name(string n) {
5130 		name_ = n;
5131 	}
5132 
5133 	@property string name() {
5134 		return name_;
5135 	}
5136 
5137 	private MemoryImage originalMemoryImage;
5138 
5139 	///
5140 	@property void icon(MemoryImage i) {
5141 		version(X11) {
5142 			this.originalMemoryImage = i;
5143 			if (!active) return;
5144 			if (i !is null) {
5145 				this.img = Image.fromMemoryImage(i);
5146 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5147 				// writeln("using pixmap ", clippixmap);
5148 				updateNetWmIcon();
5149 				redraw();
5150 			} else {
5151 				if (this.img !is null) {
5152 					this.img = null;
5153 					redraw();
5154 				}
5155 			}
5156 		} else version(Windows) {
5157 			this.win32Icon = new WindowsIcon(i);
5158 
5159 			data.uFlags = NIF_ICON;
5160 			data.hIcon = this.win32Icon.hIcon;
5161 
5162 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5163 		} else version(OSXCocoa) {
5164 			throw new NotYetImplementedException();
5165 		} else static assert(0);
5166 	}
5167 
5168 	/// ditto
5169 	@property void icon (Image i) {
5170 		version(X11) {
5171 			if (!active) return;
5172 			if (i !is img) {
5173 				originalMemoryImage = null;
5174 				img = i;
5175 				redraw();
5176 			}
5177 		} else version(Windows) {
5178 			this.icon(i is null ? null : i.toTrueColorImage());
5179 		} else version(OSXCocoa) {
5180 			throw new NotYetImplementedException();
5181 		} else static assert(0);
5182 	}
5183 
5184 	/++
5185 		Shows a balloon notification. You can only show one balloon at a time, if you call
5186 		it twice while one is already up, the first balloon will be replaced.
5187 
5188 
5189 		The user is free to block notifications and they will automatically disappear after
5190 		a timeout period.
5191 
5192 		Params:
5193 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5194 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5195 			icon = the icon to display with the notification. If null, it uses your existing icon.
5196 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5197 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5198 	+/
5199 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5200 		bool useCustom = true;
5201 		version(libnotify) {
5202 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5203 			try {
5204 				if(!active) return;
5205 
5206 				if(libnotify is null) {
5207 					libnotify = new C_DynamicLibrary("libnotify.so");
5208 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5209 				}
5210 
5211 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5212 
5213 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5214 
5215 				if(onclick) {
5216 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5217 					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);
5218 					libnotify_action_delegates_count++;
5219 				}
5220 
5221 				// FIXME icon
5222 
5223 				// set hint image-data
5224 				// set default action for onclick
5225 
5226 				void* error;
5227 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5228 
5229 				useCustom = false;
5230 			} catch(Exception e) {
5231 
5232 			}
5233 		}
5234 
5235 		version(X11) {
5236 		if(useCustom) {
5237 			if(!active) return;
5238 			if(balloon) {
5239 				hideBalloon();
5240 			}
5241 			// I know there are two specs for this, but one is never
5242 			// implemented by any window manager I have ever seen, and
5243 			// the other is a bloated mess and too complicated for simpledisplay...
5244 			// so doing my own little window instead.
5245 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5246 
5247 			int x, y, width, height;
5248 			getWindowRect(x, y, width, height);
5249 
5250 			int bx = x - balloon.width;
5251 			int by = y - balloon.height;
5252 			if(bx < 0)
5253 				bx = x + width + balloon.width;
5254 			if(by < 0)
5255 				by = y + height;
5256 
5257 			// just in case, make sure it is actually on scren
5258 			if(bx < 0)
5259 				bx = 0;
5260 			if(by < 0)
5261 				by = 0;
5262 
5263 			balloon.move(bx, by);
5264 			auto painter = balloon.draw();
5265 			painter.fillColor = Color(220, 220, 220);
5266 			painter.outlineColor = Color.black;
5267 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5268 			auto iconWidth = icon is null ? 0 : icon.width;
5269 			if(icon)
5270 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5271 			iconWidth += 6; // margin around the icon
5272 
5273 			// draw a close button
5274 			painter.outlineColor = Color(44, 44, 44);
5275 			painter.fillColor = Color(255, 255, 255);
5276 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5277 			painter.pen = Pen(Color.black, 3);
5278 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5279 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5280 			painter.pen = Pen(Color.black, 1);
5281 			painter.fillColor = Color(220, 220, 220);
5282 
5283 			// Draw the title and message
5284 			painter.drawText(Point(4 + iconWidth, 4), title);
5285 			painter.drawLine(
5286 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5287 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5288 			);
5289 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5290 
5291 			balloon.setEventHandlers(
5292 				(MouseEvent ev) {
5293 					if(ev.type == MouseEventType.buttonPressed) {
5294 						if(ev.x > balloon.width - 16 && ev.y < 16)
5295 							hideBalloon();
5296 						else if(onclick)
5297 							onclick();
5298 					}
5299 				}
5300 			);
5301 			balloon.show();
5302 
5303 			version(with_timer)
5304 			timer = new Timer(timeout, &hideBalloon);
5305 			else {} // FIXME
5306 		}
5307 		} else version(Windows) {
5308 			enum NIF_INFO = 0x00000010;
5309 
5310 			data.uFlags = NIF_INFO;
5311 
5312 			// FIXME: go back to the last valid unicode code point
5313 			if(title.length > 40)
5314 				title = title[0 .. 40];
5315 			if(message.length > 220)
5316 				message = message[0 .. 220];
5317 
5318 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5319 			enum NIIF_LARGE_ICON  = 0x00000020;
5320 			enum NIIF_NOSOUND = 0x00000010;
5321 			enum NIIF_USER = 0x00000004;
5322 			enum NIIF_ERROR = 0x00000003;
5323 			enum NIIF_WARNING = 0x00000002;
5324 			enum NIIF_INFO = 0x00000001;
5325 			enum NIIF_NONE = 0;
5326 
5327 			WCharzBuffer t = WCharzBuffer(title);
5328 			WCharzBuffer m = WCharzBuffer(message);
5329 
5330 			t.copyInto(data.szInfoTitle);
5331 			m.copyInto(data.szInfo);
5332 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5333 
5334 			if(icon !is null) {
5335 				auto i = new WindowsIcon(icon);
5336 				data.hBalloonIcon = i.hIcon;
5337 				data.dwInfoFlags |= NIIF_USER;
5338 			}
5339 
5340 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5341 		} else version(OSXCocoa) {
5342 			throw new NotYetImplementedException();
5343 		} else static assert(0);
5344 	}
5345 
5346 	///
5347 	//version(Windows)
5348 	void show() {
5349 		version(X11) {
5350 			if(!hidden)
5351 				return;
5352 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5353 			hidden = false;
5354 		} else version(Windows) {
5355 			data.uFlags = NIF_STATE;
5356 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5357 			data.dwStateMask = NIS_HIDDEN; // windows vista
5358 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5359 		} else version(OSXCocoa) {
5360 			throw new NotYetImplementedException();
5361 		} else static assert(0);
5362 	}
5363 
5364 	version(X11)
5365 		bool hidden = false;
5366 
5367 	///
5368 	//version(Windows)
5369 	void hide() {
5370 		version(X11) {
5371 			if(hidden)
5372 				return;
5373 			hidden = true;
5374 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5375 		} else version(Windows) {
5376 			data.uFlags = NIF_STATE;
5377 			data.dwState = NIS_HIDDEN; // windows vista
5378 			data.dwStateMask = NIS_HIDDEN; // windows vista
5379 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5380 		} else version(OSXCocoa) {
5381 			throw new NotYetImplementedException();
5382 		} else static assert(0);
5383 	}
5384 
5385 	///
5386 	void close () {
5387 		version(X11) {
5388 			if (active) {
5389 				active = false; // event handler will set this too, but meh
5390 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5391 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5392 				flushGui();
5393 			}
5394 		} else version(Windows) {
5395 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5396 		} else version(OSXCocoa) {
5397 			throw new NotYetImplementedException();
5398 		} else static assert(0);
5399 	}
5400 
5401 	~this() {
5402 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5403 		version(X11)
5404 			if(clippixmap != None)
5405 				XFreePixmap(XDisplayConnection.get, clippixmap);
5406 		close();
5407 	}
5408 }
5409 
5410 version(X11)
5411 /// Call `XFreePixmap` on the return value.
5412 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5413 	char[] data = new char[](i.width * i.height / 8 + 2);
5414 	data[] = 0;
5415 
5416 	int bitOffset = 0;
5417 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5418 		ubyte v = c.a > 128 ? 1 : 0;
5419 		data[bitOffset / 8] |= v << (bitOffset%8);
5420 		bitOffset++;
5421 	}
5422 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5423 	return handle;
5424 }
5425 
5426 
5427 // basic functions to make timers
5428 /**
5429 	A timer that will trigger your function on a given interval.
5430 
5431 
5432 	You create a timer with an interval and a callback. It will continue
5433 	to fire on the interval until it is destroyed.
5434 
5435 	There are currently no one-off timers (instead, just create one and
5436 	destroy it when it is triggered) nor are there pause/resume functions -
5437 	the timer must again be destroyed and recreated if you want to pause it.
5438 
5439 	---
5440 	auto timer = new Timer(50, { it happened!; });
5441 	timer.destroy();
5442 	---
5443 
5444 	Timers can only be expected to fire when the event loop is running and only
5445 	once per iteration through the event loop.
5446 
5447 	History:
5448 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5449 		slow could lock up the event loop. It now guarantees other things will
5450 		get a chance to run between timer calls, even if that means not keeping up
5451 		with the requested interval.
5452 */
5453 version(with_timer) {
5454 class Timer {
5455 // FIXME: needs pause and unpause
5456 	// FIXME: I might add overloads for ones that take a count of
5457 	// how many elapsed since last time (on Windows, it will divide
5458 	// the ticks thing given, on Linux it is just available) and
5459 	// maybe one that takes an instance of the Timer itself too
5460 	/// Create a timer with a callback when it triggers.
5461 	this(int intervalInMilliseconds, void delegate() onPulse) {
5462 		assert(onPulse !is null);
5463 
5464 		this.intervalInMilliseconds = intervalInMilliseconds;
5465 		this.onPulse = onPulse;
5466 
5467 		version(Windows) {
5468 			/*
5469 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5470 			if(handle == 0)
5471 				throw new WindowsApiException("SetTimer", GetLastError());
5472 			*/
5473 
5474 			// thanks to Archival 998 for the WaitableTimer blocks
5475 			handle = CreateWaitableTimer(null, false, null);
5476 			long initialTime = -intervalInMilliseconds;
5477 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5478 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5479 
5480 			mapping[handle] = this;
5481 
5482 		} else version(linux) {
5483 			static import ep = core.sys.linux.epoll;
5484 
5485 			import core.sys.linux.timerfd;
5486 
5487 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5488 			if(fd == -1)
5489 				throw new Exception("timer create failed");
5490 
5491 			mapping[fd] = this;
5492 
5493 			itimerspec value;
5494 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5495 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5496 
5497 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5498 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5499 
5500 			if(timerfd_settime(fd, 0, &value, null) == -1)
5501 				throw new Exception("couldn't make pulse timer");
5502 
5503 			version(with_eventloop) {
5504 				import arsd.eventloop;
5505 				addFileEventListeners(fd, &trigger, null, null);
5506 			} else {
5507 				prepareEventLoop();
5508 
5509 				ep.epoll_event ev = void;
5510 				ev.events = ep.EPOLLIN;
5511 				ev.data.fd = fd;
5512 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5513 			}
5514 		} else featureNotImplemented();
5515 	}
5516 
5517 	private int intervalInMilliseconds;
5518 
5519 	// just cuz I sometimes call it this.
5520 	alias dispose = destroy;
5521 
5522 	/// Stop and destroy the timer object.
5523 	void destroy() {
5524 		version(Windows) {
5525 			staticDestroy(handle);
5526 			handle = null;
5527 		} else version(linux) {
5528 			staticDestroy(fd);
5529 			fd = -1;
5530 		} else featureNotImplemented();
5531 	}
5532 
5533 	version(Windows)
5534 	static void staticDestroy(HANDLE handle) {
5535 		if(handle) {
5536 			// KillTimer(null, handle);
5537 			CancelWaitableTimer(cast(void*)handle);
5538 			mapping.remove(handle);
5539 			CloseHandle(handle);
5540 		}
5541 	}
5542 	else version(linux)
5543 	static void staticDestroy(int fd) {
5544 		if(fd != -1) {
5545 			import unix = core.sys.posix.unistd;
5546 			static import ep = core.sys.linux.epoll;
5547 
5548 			version(with_eventloop) {
5549 				import arsd.eventloop;
5550 				removeFileEventListeners(fd);
5551 			} else {
5552 				ep.epoll_event ev = void;
5553 				ev.events = ep.EPOLLIN;
5554 				ev.data.fd = fd;
5555 
5556 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5557 			}
5558 			unix.close(fd);
5559 			mapping.remove(fd);
5560 		}
5561 	}
5562 
5563 	~this() {
5564 		version(Windows) { if(handle)
5565 			cleanupQueue.queue!staticDestroy(handle);
5566 		} else version(linux) { if(fd != -1)
5567 			cleanupQueue.queue!staticDestroy(fd);
5568 		}
5569 	}
5570 
5571 
5572 	void changeTime(int intervalInMilliseconds)
5573 	{
5574 		this.intervalInMilliseconds = intervalInMilliseconds;
5575 		version(Windows)
5576 		{
5577 			if(handle)
5578 			{
5579 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5580 				long initialTime = -intervalInMilliseconds;
5581 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5582 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
5583 			}
5584 		}
5585 	}
5586 
5587 
5588 	private:
5589 
5590 	void delegate() onPulse;
5591 
5592 	int lastEventLoopRoundTriggered;
5593 
5594 	void trigger() {
5595 		version(linux) {
5596 			import unix = core.sys.posix.unistd;
5597 			long val;
5598 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5599 		} else version(Windows) {
5600 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5601 				return; // never try to actually run faster than the event loop
5602 			lastEventLoopRoundTriggered = eventLoopRound;
5603 		} else featureNotImplemented();
5604 
5605 		onPulse();
5606 	}
5607 
5608 	version(Windows)
5609 	void rearm() {
5610 
5611 	}
5612 
5613 	version(Windows)
5614 		extern(Windows)
5615 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5616 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5617 			if(Timer* t = timer in mapping) {
5618 				try
5619 				(*t).trigger();
5620 				catch(Exception e) { sdpy_abort(e); assert(0); }
5621 			}
5622 		}
5623 
5624 	version(Windows) {
5625 		//UINT_PTR handle;
5626 		//static Timer[UINT_PTR] mapping;
5627 		HANDLE handle;
5628 		__gshared Timer[HANDLE] mapping;
5629 	} else version(linux) {
5630 		int fd = -1;
5631 		__gshared Timer[int] mapping;
5632 	} else static assert(0, "timer not supported");
5633 }
5634 }
5635 
5636 version(Windows)
5637 private int eventLoopRound;
5638 
5639 version(Windows)
5640 /// 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
5641 class WindowsHandleReader {
5642 	///
5643 	this(void delegate() onReady, HANDLE handle) {
5644 		this.onReady = onReady;
5645 		this.handle = handle;
5646 
5647 		mapping[handle] = this;
5648 
5649 		enable();
5650 	}
5651 
5652 	///
5653 	void enable() {
5654 		auto el = EventLoop.get().impl;
5655 		el.handles ~= handle;
5656 	}
5657 
5658 	///
5659 	void disable() {
5660 		auto el = EventLoop.get().impl;
5661 		for(int i = 0; i < el.handles.length; i++) {
5662 			if(el.handles[i] is handle) {
5663 				el.handles[i] = el.handles[$-1];
5664 				el.handles = el.handles[0 .. $-1];
5665 				return;
5666 			}
5667 		}
5668 	}
5669 
5670 	void dispose() {
5671 		disable();
5672 		if(handle)
5673 			mapping.remove(handle);
5674 		handle = null;
5675 	}
5676 
5677 	void ready() {
5678 		if(onReady)
5679 			onReady();
5680 	}
5681 
5682 	HANDLE handle;
5683 	void delegate() onReady;
5684 
5685 	__gshared WindowsHandleReader[HANDLE] mapping;
5686 }
5687 
5688 version(Posix)
5689 /// Lets you add files to the event loop for reading. Use at your own risk.
5690 class PosixFdReader {
5691 	///
5692 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5693 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5694 	}
5695 
5696 	///
5697 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5698 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5699 	}
5700 
5701 	///
5702 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5703 		this.onReady = onReady;
5704 		this.fd = fd;
5705 		this.captureWrites = captureWrites;
5706 		this.captureReads = captureReads;
5707 
5708 		mapping[fd] = this;
5709 
5710 		version(with_eventloop) {
5711 			import arsd.eventloop;
5712 			addFileEventListeners(fd, &readyel);
5713 		} else {
5714 			enable();
5715 		}
5716 	}
5717 
5718 	bool captureReads;
5719 	bool captureWrites;
5720 
5721 	version(with_eventloop) {} else
5722 	///
5723 	void enable() {
5724 		prepareEventLoop();
5725 
5726 		enabled = true;
5727 
5728 		version(linux) {
5729 			static import ep = core.sys.linux.epoll;
5730 			ep.epoll_event ev = void;
5731 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5732 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5733 			ev.data.fd = fd;
5734 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5735 		} else {
5736 
5737 		}
5738 	}
5739 
5740 	version(with_eventloop) {} else
5741 	///
5742 	void disable() {
5743 		prepareEventLoop();
5744 
5745 		enabled = false;
5746 
5747 		version(linux) {
5748 			static import ep = core.sys.linux.epoll;
5749 			ep.epoll_event ev = void;
5750 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5751 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5752 			ev.data.fd = fd;
5753 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5754 		}
5755 	}
5756 
5757 	version(with_eventloop) {} else
5758 	///
5759 	void dispose() {
5760 		if(enabled)
5761 			disable();
5762 		if(fd != -1)
5763 			mapping.remove(fd);
5764 		fd = -1;
5765 	}
5766 
5767 	void delegate(int, bool, bool) onReady;
5768 
5769 	version(with_eventloop)
5770 	void readyel() {
5771 		onReady(fd, true, true);
5772 	}
5773 
5774 	void ready(uint flags) {
5775 		version(linux) {
5776 			static import ep = core.sys.linux.epoll;
5777 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5778 		} else {
5779 			import core.sys.posix.poll;
5780 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5781 		}
5782 	}
5783 
5784 	void hup(uint flags) {
5785 		if(onHup)
5786 			onHup();
5787 	}
5788 
5789 	void delegate() onHup;
5790 
5791 	int fd = -1;
5792 	private bool enabled;
5793 	__gshared PosixFdReader[int] mapping;
5794 }
5795 
5796 // basic functions to access the clipboard
5797 /+
5798 
5799 
5800 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5801 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5802 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5803 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5804 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5805 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5806 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5807 
5808 +/
5809 
5810 /++
5811 	this does a delegate because it is actually an async call on X...
5812 	the receiver may never be called if the clipboard is empty or unavailable
5813 	gets plain text from the clipboard.
5814 +/
5815 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5816 	version(Windows) {
5817 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5818 		if(OpenClipboard(hwndOwner) == 0)
5819 			throw new WindowsApiException("OpenClipboard", GetLastError());
5820 		scope(exit)
5821 			CloseClipboard();
5822 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5823 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5824 
5825 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5826 				scope(exit)
5827 					GlobalUnlock(dataHandle);
5828 
5829 				// FIXME: CR/LF conversions
5830 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5831 				int len = 0;
5832 				auto d = data;
5833 				while(*d) {
5834 					d++;
5835 					len++;
5836 				}
5837 				string s;
5838 				s.reserve(len);
5839 				foreach(dchar ch; data[0 .. len]) {
5840 					s ~= ch;
5841 				}
5842 				receiver(s);
5843 			}
5844 		}
5845 	} else version(X11) {
5846 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5847 	} else version(OSXCocoa) {
5848 		throw new NotYetImplementedException();
5849 	} else static assert(0);
5850 }
5851 
5852 // FIXME: a clipboard listener might be cool btw
5853 
5854 /++
5855 	this does a delegate because it is actually an async call on X...
5856 	the receiver may never be called if the clipboard is empty or unavailable
5857 	gets image from the clipboard.
5858 
5859 	templated because it introduces an optional dependency on arsd.bmp
5860 +/
5861 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5862 	version(Windows) {
5863 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5864 		if(OpenClipboard(hwndOwner) == 0)
5865 			throw new WindowsApiException("OpenClipboard", GetLastError());
5866 		scope(exit)
5867 			CloseClipboard();
5868 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5869 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5870 				scope(exit)
5871 					GlobalUnlock(dataHandle);
5872 
5873 				auto len = GlobalSize(dataHandle);
5874 
5875 				import arsd.bmp;
5876 				auto img = readBmp(data[0 .. len], false);
5877 				receiver(img);
5878 			}
5879 		}
5880 	} else version(X11) {
5881 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5882 	} else version(OSXCocoa) {
5883 		throw new NotYetImplementedException();
5884 	} else static assert(0);
5885 }
5886 
5887 /// Copies some text to the clipboard.
5888 void setClipboardText(SimpleWindow clipboardOwner, string text) {
5889 	assert(clipboardOwner !is null);
5890 	version(Windows) {
5891 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5892 			throw new WindowsApiException("OpenClipboard", GetLastError());
5893 		scope(exit)
5894 			CloseClipboard();
5895 		EmptyClipboard();
5896 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5897 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
5898 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
5899 		if(auto data = cast(wchar*) GlobalLock(handle)) {
5900 			auto slice = data[0 .. sz];
5901 			scope(failure)
5902 				GlobalUnlock(handle);
5903 
5904 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5905 
5906 			GlobalUnlock(handle);
5907 			SetClipboardData(CF_UNICODETEXT, handle);
5908 		}
5909 	} else version(X11) {
5910 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
5911 	} else version(OSXCocoa) {
5912 		throw new NotYetImplementedException();
5913 	} else static assert(0);
5914 }
5915 
5916 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
5917 	assert(clipboardOwner !is null);
5918 	version(Windows) {
5919 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5920 			throw new WindowsApiException("OpenClipboard", GetLastError());
5921 		scope(exit)
5922 			CloseClipboard();
5923 		EmptyClipboard();
5924 
5925 
5926 		import arsd.bmp;
5927 		ubyte[] mdata;
5928 		mdata.reserve(img.width * img.height);
5929 		void sink(ubyte b) {
5930 			mdata ~= b;
5931 		}
5932 		writeBmpIndirect(img, &sink, false);
5933 
5934 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
5935 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
5936 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
5937 			auto slice = data[0 .. mdata.length];
5938 			scope(failure)
5939 				GlobalUnlock(handle);
5940 
5941 			slice[] = mdata[];
5942 
5943 			GlobalUnlock(handle);
5944 			SetClipboardData(CF_DIB, handle);
5945 		}
5946 	} else version(X11) {
5947 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
5948 			mixin X11SetSelectionHandler_Basics;
5949 			private const(ubyte)[] mdata;
5950 			private const(ubyte)[] mdata_original;
5951 			this(MemoryImage img) {
5952 				import arsd.bmp;
5953 
5954 				mdata.reserve(img.width * img.height);
5955 				void sink(ubyte b) {
5956 					mdata ~= b;
5957 				}
5958 				writeBmpIndirect(img, &sink, true);
5959 
5960 				mdata_original = mdata;
5961 			}
5962 
5963 			Atom[] availableFormats() {
5964 				auto display = XDisplayConnection.get;
5965 				return [
5966 					GetAtom!"image/bmp"(display),
5967 					GetAtom!"TARGETS"(display)
5968 				];
5969 			}
5970 
5971 			ubyte[] getData(Atom format, return scope ubyte[] data) {
5972 				if(mdata.length < data.length) {
5973 					data[0 .. mdata.length] = mdata[];
5974 					auto ret = data[0 .. mdata.length];
5975 					mdata = mdata[$..$];
5976 					return ret;
5977 				} else {
5978 					data[] = mdata[0 .. data.length];
5979 					mdata = mdata[data.length .. $];
5980 					return data[];
5981 				}
5982 			}
5983 
5984 			void done() {
5985 				mdata = mdata_original;
5986 			}
5987 		}
5988 
5989 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
5990 	} else version(OSXCocoa) {
5991 		throw new NotYetImplementedException();
5992 	} else static assert(0);
5993 }
5994 
5995 
5996 version(X11) {
5997 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
5998 
5999 	private Atom*[] interredAtoms; // for discardAndRecreate
6000 
6001 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6002 	/// Platform-specific for X11.
6003 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6004 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6005 		static Atom a;
6006 		if(!a) {
6007 			a = XInternAtom(display, name, !create);
6008 			interredAtoms ~= &a;
6009 		}
6010 		if(a == None)
6011 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6012 		return a;
6013 	}
6014 
6015 	/// Platform-specific for X11 - gets atom names as a string.
6016 	string getAtomName(Atom atom, Display* display) {
6017 		auto got = XGetAtomName(display, atom);
6018 		scope(exit) XFree(got);
6019 		import core.stdc.string;
6020 		string s = got[0 .. strlen(got)].idup;
6021 		return s;
6022 	}
6023 
6024 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6025 	void setPrimarySelection(SimpleWindow window, string text) {
6026 		setX11Selection!"PRIMARY"(window, text);
6027 	}
6028 
6029 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6030 	void setSecondarySelection(SimpleWindow window, string text) {
6031 		setX11Selection!"SECONDARY"(window, text);
6032 	}
6033 
6034 	interface X11SetSelectionHandler {
6035 		// should include TARGETS right now
6036 		Atom[] availableFormats();
6037 		// Return the slice of data you filled, empty slice if done.
6038 		// this is to support the incremental thing
6039 		ubyte[] getData(Atom format, return scope ubyte[] data);
6040 
6041 		void done();
6042 
6043 		void handleRequest(XEvent);
6044 
6045 		bool matchesIncr(Window, Atom);
6046 		void sendMoreIncr(XPropertyEvent*);
6047 	}
6048 
6049 	mixin template X11SetSelectionHandler_Basics() {
6050 		Window incrWindow;
6051 		Atom incrAtom;
6052 		Atom selectionAtom;
6053 		Atom formatAtom;
6054 		ubyte[] toSend;
6055 		bool matchesIncr(Window w, Atom a) {
6056 			return incrAtom && incrAtom == a && w == incrWindow;
6057 		}
6058 		void sendMoreIncr(XPropertyEvent* event) {
6059 			auto display = XDisplayConnection.get;
6060 
6061 			XChangeProperty (display,
6062 				incrWindow,
6063 				incrAtom,
6064 				formatAtom,
6065 				8 /* bits */, PropModeReplace,
6066 				toSend.ptr, cast(int) toSend.length);
6067 
6068 			if(toSend.length != 0) {
6069 				toSend = this.getData(formatAtom, toSend[]);
6070 			} else {
6071 				this.done();
6072 				incrWindow = None;
6073 				incrAtom = None;
6074 				selectionAtom = None;
6075 				formatAtom = None;
6076 				toSend = null;
6077 			}
6078 		}
6079 		void handleRequest(XEvent ev) {
6080 
6081 			auto display = XDisplayConnection.get;
6082 
6083 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6084 			XSelectionEvent selectionEvent;
6085 			selectionEvent.type = EventType.SelectionNotify;
6086 			selectionEvent.display = event.display;
6087 			selectionEvent.requestor = event.requestor;
6088 			selectionEvent.selection = event.selection;
6089 			selectionEvent.time = event.time;
6090 			selectionEvent.target = event.target;
6091 
6092 			bool supportedType() {
6093 				foreach(t; this.availableFormats())
6094 					if(t == event.target)
6095 						return true;
6096 				return false;
6097 			}
6098 
6099 			if(event.property == None) {
6100 				selectionEvent.property = event.target;
6101 
6102 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6103 				XFlush(display);
6104 			} if(event.target == GetAtom!"TARGETS"(display)) {
6105 				/* respond with the supported types */
6106 				auto tlist = this.availableFormats();
6107 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6108 				selectionEvent.property = event.property;
6109 
6110 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6111 				XFlush(display);
6112 			} else if(supportedType()) {
6113 				auto buffer = new ubyte[](1024 * 64);
6114 				auto toSend = this.getData(event.target, buffer[]);
6115 
6116 				if(toSend.length < 32 * 1024) {
6117 					// small enough to send directly...
6118 					selectionEvent.property = event.property;
6119 					XChangeProperty (display,
6120 						selectionEvent.requestor,
6121 						selectionEvent.property,
6122 						event.target,
6123 						8 /* bits */, 0 /* PropModeReplace */,
6124 						toSend.ptr, cast(int) toSend.length);
6125 
6126 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6127 					XFlush(display);
6128 				} else {
6129 					// large, let's send incrementally
6130 					arch_ulong l = toSend.length;
6131 
6132 					// if I wanted other events from this window don't want to clear that out....
6133 					XWindowAttributes xwa;
6134 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6135 
6136 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6137 
6138 					incrWindow = event.requestor;
6139 					incrAtom = event.property;
6140 					formatAtom = event.target;
6141 					selectionAtom = event.selection;
6142 					this.toSend = toSend;
6143 
6144 					selectionEvent.property = event.property;
6145 					XChangeProperty (display,
6146 						selectionEvent.requestor,
6147 						selectionEvent.property,
6148 						GetAtom!"INCR"(display),
6149 						32 /* bits */, PropModeReplace,
6150 						&l, 1);
6151 
6152 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6153 					XFlush(display);
6154 				}
6155 				//if(after)
6156 					//after();
6157 			} else {
6158 				debug(sdpy_clip) {
6159 					writeln("Unsupported data ", getAtomName(event.target, display));
6160 				}
6161 				selectionEvent.property = None; // I don't know how to handle this type...
6162 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6163 				XFlush(display);
6164 			}
6165 		}
6166 	}
6167 
6168 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6169 		mixin X11SetSelectionHandler_Basics;
6170 		private const(ubyte)[] text;
6171 		private const(ubyte)[] text_original;
6172 		this(string text) {
6173 			this.text = cast(const ubyte[]) text;
6174 			this.text_original = this.text;
6175 		}
6176 		Atom[] availableFormats() {
6177 			auto display = XDisplayConnection.get;
6178 			return [
6179 				GetAtom!"UTF8_STRING"(display),
6180 				GetAtom!"text/plain"(display),
6181 				XA_STRING,
6182 				GetAtom!"TARGETS"(display)
6183 			];
6184 		}
6185 
6186 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6187 			if(text.length < data.length) {
6188 				data[0 .. text.length] = text[];
6189 				return data[0 .. text.length];
6190 			} else {
6191 				data[] = text[0 .. data.length];
6192 				text = text[data.length .. $];
6193 				return data[];
6194 			}
6195 		}
6196 
6197 		void done() {
6198 			text = text_original;
6199 		}
6200 	}
6201 
6202 	/// 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?!)
6203 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6204 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6205 	}
6206 
6207 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6208 		assert(window !is null);
6209 
6210 		auto display = XDisplayConnection.get();
6211 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6212 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6213 		else Atom a = GetAtom!atomName(display);
6214 
6215 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6216 
6217 		window.impl.setSelectionHandlers[a] = data;
6218 	}
6219 
6220 	///
6221 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6222 		getX11Selection!"PRIMARY"(window, handler);
6223 	}
6224 
6225 	// added July 28, 2020
6226 	// undocumented as experimental tho
6227 	interface X11GetSelectionHandler {
6228 		void handleData(Atom target, in ubyte[] data);
6229 		Atom findBestFormat(Atom[] answer);
6230 
6231 		void prepareIncremental(Window, Atom);
6232 		bool matchesIncr(Window, Atom);
6233 		void handleIncrData(Atom, in ubyte[] data);
6234 	}
6235 
6236 	mixin template X11GetSelectionHandler_Basics() {
6237 		Window incrWindow;
6238 		Atom incrAtom;
6239 
6240 		void prepareIncremental(Window w, Atom a) {
6241 			incrWindow = w;
6242 			incrAtom = a;
6243 		}
6244 		bool matchesIncr(Window w, Atom a) {
6245 			return incrWindow == w && incrAtom == a;
6246 		}
6247 
6248 		Atom incrFormatAtom;
6249 		ubyte[] incrData;
6250 		void handleIncrData(Atom format, in ubyte[] data) {
6251 			incrFormatAtom = format;
6252 
6253 			if(data.length)
6254 				incrData ~= data;
6255 			else
6256 				handleData(incrFormatAtom, incrData);
6257 
6258 		}
6259 	}
6260 
6261 	///
6262 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6263 		assert(window !is null);
6264 
6265 		auto display = XDisplayConnection.get();
6266 		auto atom = GetAtom!atomName(display);
6267 
6268 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6269 			this(void delegate(in char[]) handler) {
6270 				this.handler = handler;
6271 			}
6272 
6273 			mixin X11GetSelectionHandler_Basics;
6274 
6275 			void delegate(in char[]) handler;
6276 
6277 			void handleData(Atom target, in ubyte[] data) {
6278 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6279 					handler(cast(const char[]) data);
6280 			}
6281 
6282 			Atom findBestFormat(Atom[] answer) {
6283 				Atom best = None;
6284 				foreach(option; answer) {
6285 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6286 						best = option;
6287 						break;
6288 					} else if(option == XA_STRING) {
6289 						best = option;
6290 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6291 						best = option;
6292 					}
6293 				}
6294 				return best;
6295 			}
6296 		}
6297 
6298 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6299 
6300 		auto target = GetAtom!"TARGETS"(display);
6301 
6302 		// SDD_DATA is "simpledisplay.d data"
6303 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6304 	}
6305 
6306 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6307 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6308 		assert(window !is null);
6309 
6310 		auto display = XDisplayConnection.get();
6311 		auto atom = GetAtom!atomName(display);
6312 
6313 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6314 			this(void delegate(MemoryImage) handler) {
6315 				this.handler = handler;
6316 			}
6317 
6318 			mixin X11GetSelectionHandler_Basics;
6319 
6320 			void delegate(MemoryImage) handler;
6321 
6322 			void handleData(Atom target, in ubyte[] data) {
6323 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6324 					import arsd.bmp;
6325 					handler(readBmp(data));
6326 				}
6327 			}
6328 
6329 			Atom findBestFormat(Atom[] answer) {
6330 				Atom best = None;
6331 				foreach(option; answer) {
6332 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6333 						best = option;
6334 					}
6335 				}
6336 				return best;
6337 			}
6338 
6339 		}
6340 
6341 
6342 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6343 
6344 		auto target = GetAtom!"TARGETS"(display);
6345 
6346 		// SDD_DATA is "simpledisplay.d data"
6347 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6348 	}
6349 
6350 
6351 	///
6352 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6353 		Atom actualType;
6354 		int actualFormat;
6355 		arch_ulong actualItems;
6356 		arch_ulong bytesRemaining;
6357 		void* data;
6358 
6359 		auto display = XDisplayConnection.get();
6360 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6361 			if(actualFormat == 0)
6362 				return null;
6363 			else {
6364 				int byteLength;
6365 				if(actualFormat == 32) {
6366 					// 32 means it is a C long... which is variable length
6367 					actualFormat = cast(int) arch_long.sizeof * 8;
6368 				}
6369 
6370 				// then it is just a bit count
6371 				byteLength = cast(int) (actualItems * actualFormat / 8);
6372 
6373 				auto d = new ubyte[](byteLength);
6374 				d[] = cast(ubyte[]) data[0 .. byteLength];
6375 				XFree(data);
6376 				return d;
6377 			}
6378 		}
6379 		return null;
6380 	}
6381 
6382 	/* defined in the systray spec */
6383 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6384 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6385 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6386 
6387 
6388 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6389 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6390 	public class GlobalHotkey {
6391 		KeyEvent key;
6392 		void delegate () handler;
6393 
6394 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6395 
6396 		/// Create from initialzed KeyEvent object
6397 		this (KeyEvent akey, void delegate () ahandler=null) {
6398 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6399 			key = akey;
6400 			handler = ahandler;
6401 		}
6402 
6403 		/// Create from emacs-like key name ("C-M-Y", etc.)
6404 		this (const(char)[] akey, void delegate () ahandler=null) {
6405 			key = KeyEvent.parse(akey);
6406 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6407 			handler = ahandler;
6408 		}
6409 
6410 	}
6411 
6412 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6413 		//conwriteln("failed to grab key");
6414 		GlobalHotkeyManager.ghfailed = true;
6415 		return 0;
6416 	}
6417 
6418 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6419 		Image.impl.xshmfailed = true;
6420 		return 0;
6421 	}
6422 
6423 	private __gshared int errorHappened;
6424 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6425 		import core.stdc.stdio;
6426 		char[265] buffer;
6427 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6428 		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);
6429 		errorHappened = true;
6430 		return 0;
6431 	}
6432 
6433 	/++
6434 		Global hotkey manager. It contains static methods to manage global hotkeys.
6435 
6436 		---
6437 		 try {
6438 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6439 		} catch (Exception e) {
6440 			conwriteln("ERROR registering hotkey!");
6441 		}
6442 		EventLoop.get.run();
6443 		---
6444 
6445 		The key strings are based on Emacs. In practical terms,
6446 		`M` means `alt` and `H` means the Windows logo key. `C`
6447 		is `ctrl`.
6448 
6449 		$(WARNING
6450 			This is X-specific right now. If you are on
6451 			Windows, try [registerHotKey] instead.
6452 
6453 			We will probably merge these into a single
6454 			interface later.
6455 		)
6456 	+/
6457 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6458 		version(X11) {
6459 			void recreateAfterDisconnect() {
6460 				throw new Exception("NOT IMPLEMENTED");
6461 			}
6462 			void discardConnectionState() {
6463 				throw new Exception("NOT IMPLEMENTED");
6464 			}
6465 		}
6466 
6467 		private static immutable uint[8] masklist = [ 0,
6468 			KeyOrButtonMask.LockMask,
6469 			KeyOrButtonMask.Mod2Mask,
6470 			KeyOrButtonMask.Mod3Mask,
6471 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6472 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6473 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6474 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6475 		];
6476 		private __gshared GlobalHotkeyManager ghmanager;
6477 		private __gshared bool ghfailed = false;
6478 
6479 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6480 			if (modmask == 0) return false;
6481 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6482 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6483 			return true;
6484 		}
6485 
6486 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6487 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6488 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6489 			return modmask;
6490 		}
6491 
6492 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6493 			uint keycode = cast(uint)ke.key;
6494 			auto dpy = XDisplayConnection.get;
6495 			return XKeysymToKeycode(dpy, keycode);
6496 		}
6497 
6498 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6499 
6500 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6501 
6502 		NativeEventHandler getNativeEventHandler () {
6503 			return delegate int (XEvent e) {
6504 				if (e.type != EventType.KeyPress) return 1;
6505 				auto kev = cast(const(XKeyEvent)*)&e;
6506 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6507 				if (auto ghkp = hash in globalHotkeyList) {
6508 					try {
6509 						ghkp.doHandle();
6510 					} catch (Exception e) {
6511 						import core.stdc.stdio : stderr, fprintf;
6512 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6513 					}
6514 				}
6515 				return 1;
6516 			};
6517 		}
6518 
6519 		private this () {
6520 			auto dpy = XDisplayConnection.get;
6521 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6522 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6523 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6524 		}
6525 
6526 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6527 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6528 		static void register (GlobalHotkey gh) {
6529 			if (gh is null) return;
6530 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6531 
6532 			auto dpy = XDisplayConnection.get;
6533 			immutable keycode = keyEvent2KeyCode(gh.key);
6534 
6535 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6536 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6537 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6538 			XSync(dpy, 0/*False*/);
6539 
6540 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6541 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6542 			ghfailed = false;
6543 			foreach (immutable uint ormask; masklist[]) {
6544 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6545 			}
6546 			XSync(dpy, 0/*False*/);
6547 			XSetErrorHandler(savedErrorHandler);
6548 
6549 			if (ghfailed) {
6550 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6551 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6552 				XSync(dpy, 0/*False*/);
6553 				XSetErrorHandler(savedErrorHandler);
6554 				throw new Exception("cannot register global hotkey");
6555 			}
6556 
6557 			globalHotkeyList[hash] = gh;
6558 		}
6559 
6560 		/// Ditto
6561 		static void register (const(char)[] akey, void delegate () ahandler) {
6562 			register(new GlobalHotkey(akey, ahandler));
6563 		}
6564 
6565 		private static void removeByHash (ulong hash) {
6566 			if (auto ghp = hash in globalHotkeyList) {
6567 				auto dpy = XDisplayConnection.get;
6568 				immutable keycode = keyEvent2KeyCode(ghp.key);
6569 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6570 				XSync(dpy, 0/*False*/);
6571 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6572 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6573 				XSync(dpy, 0/*False*/);
6574 				XSetErrorHandler(savedErrorHandler);
6575 				globalHotkeyList.remove(hash);
6576 			}
6577 		}
6578 
6579 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6580 		/// It is safe to unregister unknown or invalid hotkey.
6581 		static void unregister (GlobalHotkey gh) {
6582 			//TODO: add second AA for faster search? prolly doesn't worth it.
6583 			if (gh is null) return;
6584 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6585 				if (kv.value is gh) {
6586 					removeByHash(kv.key);
6587 					return;
6588 				}
6589 			}
6590 		}
6591 
6592 		/// Ditto.
6593 		static void unregister (const(char)[] key) {
6594 			auto kev = KeyEvent.parse(key);
6595 			immutable keycode = keyEvent2KeyCode(kev);
6596 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6597 		}
6598 	}
6599 }
6600 
6601 version(Windows) {
6602 	/++
6603 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6604 
6605 		This is platform-specific UTF-16 function for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application).
6606 	+/
6607 	void sendSyntheticInput(wstring s) {
6608 			INPUT[] inputs;
6609 			inputs.reserve(s.length * 2);
6610 
6611 			foreach(wchar c; s) {
6612 				INPUT input;
6613 				input.type = INPUT_KEYBOARD;
6614 				input.ki.wScan = c;
6615 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6616 				inputs ~= input;
6617 
6618 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6619 				inputs ~= input;
6620 			}
6621 
6622 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6623 				throw new WindowsApiException("SendInput", GetLastError());
6624 			}
6625 
6626 	}
6627 
6628 
6629 	// global hotkey helper function
6630 
6631 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6632 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6633 		__gshared int hotkeyId = 0;
6634 		int id = ++hotkeyId;
6635 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6636 			throw new Exception("RegisterHotKey");
6637 
6638 		__gshared void delegate()[WPARAM][HWND] handlers;
6639 
6640 		handlers[window.impl.hwnd][id] = handler;
6641 
6642 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6643 
6644 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6645 			switch(msg) {
6646 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6647 				case WM_HOTKEY:
6648 					if(auto list = hwnd in handlers) {
6649 						if(auto h = wParam in *list) {
6650 							(*h)();
6651 							return 0;
6652 						}
6653 					}
6654 				goto default;
6655 				default:
6656 			}
6657 			if(oldHandler)
6658 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6659 			return 1; // pass it on
6660 		};
6661 
6662 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6663 			oldHandler = window.handleNativeEvent;
6664 			window.handleNativeEvent = nativeEventHandler;
6665 		}
6666 
6667 		return id;
6668 	}
6669 
6670 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6671 	void unregisterHotKey(SimpleWindow window, int id) {
6672 		if(!UnregisterHotKey(window.impl.hwnd, id))
6673 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6674 	}
6675 }
6676 
6677 version (X11) {
6678 	pragma(lib, "dl");
6679 	import core.sys.posix.dlfcn;
6680 }
6681 
6682 /++
6683 	Allows for sending synthetic input to the X server via the Xtst
6684 	extension or on Windows using SendInput.
6685 
6686 	Please remember user input is meant to be user - don't use this
6687 	if you have some other alternative!
6688 
6689 	History:
6690 		Added May 17, 2020 with the X implementation.
6691 
6692 		Added unified implementation for Windows on April 3, 2022. (Prior to that, you had to use the top-level [sendSyntheticInput] or the Windows SendInput call directly.)
6693 	Bugs:
6694 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6695 +/
6696 struct SyntheticInput {
6697 	@disable this();
6698 
6699 	private int* refcount;
6700 
6701 	version(X11) {
6702 		private void* lib;
6703 
6704 		private extern(C) {
6705 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6706 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6707 		}
6708 	}
6709 
6710 	/// The dummy param must be 0.
6711 	this(int dummy) {
6712 		version(X11) {
6713 			lib = dlopen("libXtst.so", RTLD_NOW);
6714 			if(lib is null)
6715 				throw new Exception("cannot load xtest lib extension");
6716 			scope(failure)
6717 				dlclose(lib);
6718 
6719 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6720 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6721 
6722 			if(XTestFakeKeyEvent is null)
6723 				throw new Exception("No XTestFakeKeyEvent");
6724 			if(XTestFakeButtonEvent is null)
6725 				throw new Exception("No XTestFakeButtonEvent");
6726 		}
6727 
6728 		refcount = new int;
6729 		*refcount = 1;
6730 	}
6731 
6732 	this(this) {
6733 		if(refcount)
6734 			*refcount += 1;
6735 	}
6736 
6737 	~this() {
6738 		if(refcount) {
6739 			*refcount -= 1;
6740 			if(*refcount == 0)
6741 				// I commented this because if I close the lib before
6742 				// XCloseDisplay, it is liable to segfault... so just
6743 				// gonna keep it loaded if it is loaded, no big deal
6744 				// anyway.
6745 				{} // dlclose(lib);
6746 		}
6747 	}
6748 
6749 	/++
6750 		Simulates typing a string into the keyboard.
6751 
6752 		Bugs:
6753 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6754 
6755 			Not implemented except on Windows and X11.
6756 	+/
6757 	void sendSyntheticInput(string s) {
6758 		version(Windows) {
6759 			INPUT[] inputs;
6760 			inputs.reserve(s.length * 2);
6761 
6762 			auto ei = GetMessageExtraInfo();
6763 
6764 			foreach(wchar c; s) {
6765 				INPUT input;
6766 				input.type = INPUT_KEYBOARD;
6767 				input.ki.wScan = c;
6768 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6769 				input.ki.dwExtraInfo = ei;
6770 				inputs ~= input;
6771 
6772 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6773 				inputs ~= input;
6774 			}
6775 
6776 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6777 				throw new WindowsApiException("SendInput", GetLastError());
6778 			}
6779 		} else version(X11) {
6780 			int delay = 0;
6781 			foreach(ch; s) {
6782 				pressKey(cast(Key) ch, true, delay);
6783 				pressKey(cast(Key) ch, false, delay);
6784 				delay += 5;
6785 			}
6786 		} else throw new NotYetImplementedException();
6787 	}
6788 
6789 	/++
6790 		Sends a fake press or release key event.
6791 
6792 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6793 
6794 		Bugs:
6795 			The `delay` parameter is not implemented yet on Windows.
6796 
6797 			Not implemented except on Windows and X11.
6798 	+/
6799 	void pressKey(Key key, bool pressed, int delay = 0) {
6800 		version(Windows) {
6801 			INPUT input;
6802 			input.type = INPUT_KEYBOARD;
6803 			input.ki.wVk = cast(ushort) key;
6804 
6805 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6806 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6807 
6808 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6809 				throw new WindowsApiException("SendInput", GetLastError());
6810 			}
6811 		} else version(X11) {
6812 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6813 		} else throw new NotYetImplementedException();
6814 	}
6815 
6816 	/++
6817 		Sends a fake mouse button press or release event.
6818 
6819 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6820 
6821 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6822 
6823 		Bugs:
6824 			The `delay` parameter is not implemented yet on Windows.
6825 
6826 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6827 
6828 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6829 	+/
6830 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6831 		version(Windows) {
6832 			INPUT input;
6833 			input.type = INPUT_MOUSE;
6834 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6835 
6836 			// input.mi.mouseData for a wheel event
6837 
6838 			switch(button) {
6839 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6840 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6841 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6842 				case MouseButton.wheelUp:
6843 				case MouseButton.wheelDown:
6844 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6845 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6846 				break;
6847 				case MouseButton.backButton: throw new NotYetImplementedException();
6848 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6849 				default:
6850 			}
6851 
6852 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6853 				throw new WindowsApiException("SendInput", GetLastError());
6854 			}
6855 		} else version(X11) {
6856 			int btn;
6857 
6858 			switch(button) {
6859 				case MouseButton.left: btn = 1; break;
6860 				case MouseButton.middle: btn = 2; break;
6861 				case MouseButton.right: btn = 3; break;
6862 				case MouseButton.wheelUp: btn = 4; break;
6863 				case MouseButton.wheelDown: btn = 5; break;
6864 				case MouseButton.backButton: btn = 8; break;
6865 				case MouseButton.forwardButton: btn = 9; break;
6866 				default:
6867 			}
6868 
6869 			assert(btn);
6870 
6871 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6872 		} else throw new NotYetImplementedException();
6873 	}
6874 
6875 	///
6876 	static void moveMouseArrowBy(int dx, int dy) {
6877 		version(Windows) {
6878 			INPUT input;
6879 			input.type = INPUT_MOUSE;
6880 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6881 			input.mi.dx = dx;
6882 			input.mi.dy = dy;
6883 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
6884 
6885 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6886 				throw new WindowsApiException("SendInput", GetLastError());
6887 			}
6888 		} else version(X11) {
6889 			auto disp = XDisplayConnection.get();
6890 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
6891 			XFlush(disp);
6892 		} else throw new NotYetImplementedException();
6893 	}
6894 
6895 	///
6896 	static void moveMouseArrowTo(int x, int y) {
6897 		version(Windows) {
6898 			INPUT input;
6899 			input.type = INPUT_MOUSE;
6900 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6901 			input.mi.dx = x;
6902 			input.mi.dy = y;
6903 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
6904 
6905 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6906 				throw new WindowsApiException("SendInput", GetLastError());
6907 			}
6908 		} else version(X11) {
6909 			auto disp = XDisplayConnection.get();
6910 			auto root = RootWindow(disp, DefaultScreen(disp));
6911 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
6912 			XFlush(disp);
6913 		} else throw new NotYetImplementedException();
6914 	}
6915 }
6916 
6917 
6918 
6919 /++
6920 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
6921 
6922 	See_Also:
6923 	$(LIST
6924 		*[ScreenPainter]
6925 		*[ScreenPainter.rasterOp]
6926 	)
6927 +/
6928 enum RasterOp {
6929 	normal, /// Replaces the pixel.
6930 	xor, /// Uses bitwise xor to draw.
6931 }
6932 
6933 // being phobos-free keeps the size WAY down
6934 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
6935 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
6936 package(arsd) const(wchar)* toWStringz(string s) {
6937 	wstring r;
6938 	foreach(dchar c; s)
6939 		r ~= c;
6940 	r ~= '\0';
6941 	return r.ptr;
6942 }
6943 private string[] split(in void[] a, char c) {
6944 		string[] ret;
6945 		size_t previous = 0;
6946 		foreach(i, char ch; cast(ubyte[]) a) {
6947 			if(ch == c) {
6948 				ret ~= cast(string) a[previous .. i];
6949 				previous = i + 1;
6950 			}
6951 		}
6952 		if(previous != a.length)
6953 			ret ~= cast(string) a[previous .. $];
6954 		return ret;
6955 	}
6956 
6957 version(without_opengl) {
6958 	enum OpenGlOptions {
6959 		no,
6960 	}
6961 } else {
6962 	/++
6963 		Determines if you want an OpenGL context created on the new window.
6964 
6965 
6966 		See more: [#topics-3d|in the 3d topic].
6967 
6968 		---
6969 		import arsd.simpledisplay;
6970 		void main() {
6971 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
6972 
6973 			// Set up the matrix
6974 			window.setAsCurrentOpenGlContext(); // make this window active
6975 
6976 			// This is called on each frame, we will draw our scene
6977 			window.redrawOpenGlScene = delegate() {
6978 
6979 			};
6980 
6981 			window.eventLoop(0);
6982 		}
6983 		---
6984 	+/
6985 	enum OpenGlOptions {
6986 		no, /// No OpenGL context is created
6987 		yes, /// Yes, create an OpenGL context
6988 	}
6989 
6990 	version(X11) {
6991 		static if (!SdpyIsUsingIVGLBinds) {
6992 
6993 
6994 			struct __GLXFBConfigRec {}
6995 			alias GLXFBConfig = __GLXFBConfigRec*;
6996 
6997 			//pragma(lib, "GL");
6998 			//pragma(lib, "GLU");
6999 			interface GLX {
7000 			extern(C) nothrow @nogc {
7001 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7002 						const int *attrib_list);
7003 
7004 				 void glXCopyContext(Display *dpy, GLXContext src,
7005 						GLXContext dst, arch_ulong mask);
7006 
7007 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7008 						GLXContext share_list, Bool direct);
7009 
7010 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7011 						Pixmap pixmap);
7012 
7013 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7014 
7015 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7016 
7017 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7018 						int attrib, int *value);
7019 
7020 				 GLXContext glXGetCurrentContext();
7021 
7022 				 GLXDrawable glXGetCurrentDrawable();
7023 
7024 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7025 
7026 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7027 						GLXContext ctx);
7028 
7029 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7030 
7031 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7032 
7033 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7034 
7035 				 void glXUseXFont(Font font, int first, int count, int list_base);
7036 
7037 				 void glXWaitGL();
7038 
7039 				 void glXWaitX();
7040 
7041 
7042 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7043 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7044 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7045 
7046 				char* glXQueryExtensionsString (Display*, int);
7047 				void* glXGetProcAddress (const(char)*);
7048 
7049 			}
7050 			}
7051 
7052 			version(OSX)
7053 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7054 			else
7055 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7056 			shared static this() {
7057 				glx.loadDynamicLibrary();
7058 			}
7059 
7060 			alias glbindGetProcAddress = glXGetProcAddress;
7061 		}
7062 	} else version(Windows) {
7063 		/* it is done below by interface GL */
7064 	} else
7065 		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.");
7066 }
7067 
7068 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7069 alias Resizablity = Resizability;
7070 
7071 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7072 enum Resizability {
7073 	fixedSize, /// the window cannot be resized. If it is resized anyway, simpledisplay will position and truncate your drawn content without necessarily informing your program, maintaining the API illusion of a non-resizable window.
7074 	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. This allows most control for both user and you as the library consumer, but you also have to do the most work to handle it well.
7075 	/++
7076 		$(PITFALL
7077 			Planned for the future but not implemented.
7078 		)
7079 
7080 		Allow the user to resize the window, but try to maintain the original aspect ratio of the client area. The simpledisplay library may letterbox your content if necessary but will not stretch it. The windowResized delegate and width and height members will be updated with the size.
7081 
7082 		History:
7083 			Added November 11, 2022, but not yet implemented and may not be for some time.
7084 	+/
7085 	/*@__future*/ allowResizingMaintainingAspectRatio,
7086 	/++
7087 		If possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size, letterboxing if needed to keep the aspect ratio. If this is impossible, it will fallback to [fixedSize]. The simpledisplay library will always provide the illusion that your window is the same size you requested, even if it scales things for you, meaning [width] and [height] will never change.
7088 
7089 		History:
7090 			Prior to November 11, 2022, width and height would change, which made this mode harder to use than intended. While I had documented this as a possiblity, I still considered it a bug, a leaky abstraction, and changed the code to tighten it up. After that date, the width and height members, as well as mouse coordinates, are always scaled to maintain the illusion of a fixed canvas size.
7091 
7092 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7093 	+/
7094 	automaticallyScaleIfPossible,
7095 }
7096 
7097 
7098 /++
7099 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7100 +/
7101 enum TextAlignment : uint {
7102 	Left = 0, ///
7103 	Center = 1, ///
7104 	Right = 2, ///
7105 
7106 	VerticalTop = 0, ///
7107 	VerticalCenter = 4, ///
7108 	VerticalBottom = 8, ///
7109 }
7110 
7111 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7112 alias Rectangle = arsd.color.Rectangle;
7113 
7114 
7115 /++
7116 	Keyboard press and release events.
7117 +/
7118 struct KeyEvent {
7119 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7120 	Key key;
7121 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7122 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7123 
7124 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7125 
7126 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7127 
7128 	SimpleWindow window; /// associated Window
7129 
7130 	/++
7131 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7132 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7133 		to predict if char events are actually coming..
7134 
7135 		Only available on X systems since this information is not given ahead of time elsewhere.
7136 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7137 
7138 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7139 		and potential quirks I'd recommend avoiding it.
7140 
7141 		History:
7142 			Added April 26, 2021 (dub v9.5)
7143 	+/
7144 	version(X11)
7145 		dchar[] charsPossible;
7146 
7147 	// convert key event to simplified string representation a-la emacs
7148 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7149 		uint dpos = 0;
7150 		void put (const(char)[] s...) nothrow @trusted {
7151 			static if (growdest) {
7152 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7153 			} else {
7154 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7155 			}
7156 		}
7157 
7158 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7159 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7160 		}
7161 
7162 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7163 
7164 		// put modifiers
7165 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7166 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7167 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7168 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7169 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7170 
7171 		if (this.key) {
7172 			foreach (string kn; __traits(allMembers, Key)) {
7173 				if (this.key == __traits(getMember, Key, kn)) {
7174 					// HACK!
7175 					static if (kn == "N0") put("0");
7176 					else static if (kn == "N1") put("1");
7177 					else static if (kn == "N2") put("2");
7178 					else static if (kn == "N3") put("3");
7179 					else static if (kn == "N4") put("4");
7180 					else static if (kn == "N5") put("5");
7181 					else static if (kn == "N6") put("6");
7182 					else static if (kn == "N7") put("7");
7183 					else static if (kn == "N8") put("8");
7184 					else static if (kn == "N9") put("9");
7185 					else put(kn);
7186 					return dest[0..dpos];
7187 				}
7188 			}
7189 			put("Unknown");
7190 		} else {
7191 			if (dpos && dest[dpos-1] == '+') --dpos;
7192 		}
7193 		return dest[0..dpos];
7194 	}
7195 
7196 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7197 
7198 	/** Parse string into key name with modifiers. It accepts things like:
7199 	 *
7200 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7201 	 *
7202 	 * Ctrl+Win+1 -- windows style
7203 	 *
7204 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7205 	 *
7206 	 * Ctrl Win 1 -- and space
7207 	 *
7208 	 * and even "Win + 1 + Ctrl".
7209 	 */
7210 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7211 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7212 
7213 		// remove trailing spaces
7214 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7215 
7216 		// tokens delimited by blank, '+', or '-'
7217 		// null on eol
7218 		const(char)[] getToken () nothrow @trusted @nogc {
7219 			// remove leading spaces and delimiters
7220 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7221 			if (name.length == 0) return null; // oops, no more tokens
7222 			// get token
7223 			size_t epos = 0;
7224 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7225 			assert(epos > 0 && epos <= name.length);
7226 			auto res = name[0..epos];
7227 			name = name[epos..$];
7228 			return res;
7229 		}
7230 
7231 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7232 			if (s0.length != s1.length) return false;
7233 			foreach (immutable ci, char c0; s0) {
7234 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7235 				char c1 = s1[ci];
7236 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7237 				if (c0 != c1) return false;
7238 			}
7239 			return true;
7240 		}
7241 
7242 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7243 		if (updown !is null) *updown = -1;
7244 		KeyEvent res;
7245 		res.key = cast(Key)0; // just in case
7246 		const(char)[] tk, tkn; // last token
7247 		bool allowEmascStyle = true;
7248 		bool ignoreModifiers = false;
7249 		tokenloop: for (;;) {
7250 			tk = tkn;
7251 			tkn = getToken();
7252 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7253 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7254 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7255 			if (allowEmascStyle && tkn.length != 0) {
7256 				if (tk.length == 1) {
7257 					char mdc = tk[0];
7258 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7259 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7260 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7261 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7262 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7263 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7264 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7265 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7266 				}
7267 			}
7268 			allowEmascStyle = false;
7269 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7270 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7271 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7272 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7273 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7274 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7275 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7276 			if (tk.length == 0) continue;
7277 			// try key name
7278 			if (res.key == 0) {
7279 				// little hack
7280 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7281 					final switch (tk[0]) {
7282 						case '0': tk = "N0"; break;
7283 						case '1': tk = "N1"; break;
7284 						case '2': tk = "N2"; break;
7285 						case '3': tk = "N3"; break;
7286 						case '4': tk = "N4"; break;
7287 						case '5': tk = "N5"; break;
7288 						case '6': tk = "N6"; break;
7289 						case '7': tk = "N7"; break;
7290 						case '8': tk = "N8"; break;
7291 						case '9': tk = "N9"; break;
7292 					}
7293 				}
7294 				foreach (string kn; __traits(allMembers, Key)) {
7295 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7296 				}
7297 			}
7298 			// unknown or duplicate key name, get out of here
7299 			break;
7300 		}
7301 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7302 		return res; // something
7303 	}
7304 
7305 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7306 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7307 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7308 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7309 		}
7310 		bool ignoreMods;
7311 		int updown;
7312 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7313 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7314 		if (this.key != ke.key) {
7315 			// things like "ctrl+alt" are complicated
7316 			uint tkm = this.modifierState&modmask;
7317 			uint kkm = ke.modifierState&modmask;
7318 			Key tk = this.key;
7319 			// ke
7320 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7321 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7322 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7323 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7324 			// this
7325 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7326 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7327 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7328 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7329 			return (tk == ke.key && tkm == kkm);
7330 		}
7331 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7332 	}
7333 }
7334 
7335 /// Sets the application name.
7336 @property string ApplicationName(string name) {
7337 	return _applicationName = name;
7338 }
7339 
7340 string _applicationName;
7341 
7342 /// ditto
7343 @property string ApplicationName() {
7344 	if(_applicationName is null) {
7345 		import core.runtime;
7346 		return Runtime.args[0];
7347 	}
7348 	return _applicationName;
7349 }
7350 
7351 
7352 /// Type of a [MouseEvent].
7353 enum MouseEventType : int {
7354 	motion = 0, /// The mouse moved inside the window
7355 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7356 	buttonReleased = 2, /// A mouse button was released
7357 }
7358 
7359 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7360 /++
7361 	Listen for this on your event listeners if you are interested in mouse action.
7362 
7363 	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.
7364 
7365 	Examples:
7366 
7367 	This will draw boxes on the window with the mouse as you hold the left button.
7368 	---
7369 	import arsd.simpledisplay;
7370 
7371 	void main() {
7372 		auto window = new SimpleWindow();
7373 
7374 		window.eventLoop(0,
7375 			(MouseEvent ev) {
7376 				if(ev.modifierState & ModifierState.leftButtonDown) {
7377 					auto painter = window.draw();
7378 					painter.fillColor = Color.red;
7379 					painter.outlineColor = Color.black;
7380 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7381 				}
7382 			}
7383 		);
7384 	}
7385 	---
7386 +/
7387 struct MouseEvent {
7388 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7389 
7390 	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.
7391 	int y; /// Current Y position of the cursor when the event fired.
7392 
7393 	int dx; /// Change in X position since last report
7394 	int dy; /// Change in Y position since last report
7395 
7396 	MouseButton button; /// See [MouseButton]
7397 	int modifierState; /// See [ModifierState]
7398 
7399 	version(X11)
7400 		private Time timestamp;
7401 
7402 	/// Returns a linear representation of mouse button,
7403 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7404 	///
7405 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7406 	@property ubyte buttonLinear() const {
7407 		import core.bitop;
7408 		if(button == 0)
7409 			return 0;
7410 		return (bsf(button) + 1) & 0b1111;
7411 	}
7412 
7413 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7414 
7415 	SimpleWindow window; /// The window in which the event happened.
7416 
7417 	Point globalCoordinates() {
7418 		Point p;
7419 		if(window is null)
7420 			throw new Exception("wtf");
7421 		static if(UsingSimpledisplayX11) {
7422 			Window child;
7423 			XTranslateCoordinates(
7424 				XDisplayConnection.get,
7425 				window.impl.window,
7426 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7427 				x, y, &p.x, &p.y, &child);
7428 			return p;
7429 		} else version(Windows) {
7430 			POINT[1] points;
7431 			points[0].x = x;
7432 			points[0].y = y;
7433 			MapWindowPoints(
7434 				window.impl.hwnd,
7435 				null,
7436 				points.ptr,
7437 				points.length
7438 			);
7439 			p.x = points[0].x;
7440 			p.y = points[0].y;
7441 
7442 			return p;
7443 		} else version(OSXCocoa) {
7444 			throw new NotYetImplementedException();
7445 		} else static assert(0);
7446 	}
7447 
7448 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7449 
7450 	/**
7451 	can contain emacs-like modifier prefix
7452 	case-insensitive names:
7453 		lmbX/leftX
7454 		rmbX/rightX
7455 		mmbX/middleX
7456 		wheelX
7457 		motion (no prefix allowed)
7458 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7459 	*/
7460 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7461 		if (str.length == 0) return false; // just in case
7462 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7463 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7464 		auto anchor = str;
7465 		uint mods = 0; // uint.max == any
7466 		// interesting bits in kmod
7467 		uint kmodmask =
7468 			ModifierState.shift|
7469 			ModifierState.ctrl|
7470 			ModifierState.alt|
7471 			ModifierState.windows|
7472 			ModifierState.leftButtonDown|
7473 			ModifierState.middleButtonDown|
7474 			ModifierState.rightButtonDown|
7475 			0;
7476 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7477 		bool wasButtons = false;
7478 		while (str.length) {
7479 			if (str.ptr[0] <= ' ') {
7480 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7481 				continue;
7482 			}
7483 			// one-letter modifier?
7484 			if (str.length >= 2 && str.ptr[1] == '-') {
7485 				switch (str.ptr[0]) {
7486 					case '*': // "any" modifier (cannot be undone)
7487 						mods = mods.max;
7488 						break;
7489 					case 'C': case 'c': // emacs "ctrl"
7490 						if (mods != mods.max) mods |= ModifierState.ctrl;
7491 						break;
7492 					case 'M': case 'm': // emacs "meta"
7493 						if (mods != mods.max) mods |= ModifierState.alt;
7494 						break;
7495 					case 'S': case 's': // emacs "shift"
7496 						if (mods != mods.max) mods |= ModifierState.shift;
7497 						break;
7498 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7499 						if (mods != mods.max) mods |= ModifierState.windows;
7500 						break;
7501 					default:
7502 						return false; // unknown modifier
7503 				}
7504 				str = str[2..$];
7505 				continue;
7506 			}
7507 			// word
7508 			char[16] buf = void; // locased
7509 			auto wep = 0;
7510 			while (str.length) {
7511 				immutable char ch = str.ptr[0];
7512 				if (ch <= ' ' || ch == '-') break;
7513 				str = str[1..$];
7514 				if (wep > buf.length) return false; // too long
7515 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7516 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7517 				else return false; // invalid char
7518 			}
7519 			if (wep == 0) return false; // just in case
7520 			uint bnum;
7521 			enum UpDown { None = -1, Up, Down, Any }
7522 			auto updown = UpDown.None; // 0: up; 1: down
7523 			switch (buf[0..wep]) {
7524 				// left button
7525 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7526 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7527 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7528 				case "lmb": case "left": bnum = 0; break;
7529 				// middle button
7530 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7531 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7532 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7533 				case "mmb": case "middle": bnum = 1; break;
7534 				// right button
7535 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7536 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7537 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7538 				case "rmb": case "right": bnum = 2; break;
7539 				// wheel
7540 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7541 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7542 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7543 				case "wheel": bnum = 3; break;
7544 				// motion
7545 				case "motion": bnum = 7; break;
7546 				// unknown
7547 				default: return false;
7548 			}
7549 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7550 			// parse possible "-up" or "-down"
7551 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7552 				wep = 0;
7553 				foreach (immutable idx, immutable char ch; str[1..$]) {
7554 					if (ch <= ' ' || ch == '-') break;
7555 					assert(idx == wep); // for now; trick
7556 					if (wep > buf.length) { wep = 0; break; } // too long
7557 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7558 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7559 					else { wep = 0; break; } // invalid char
7560 				}
7561 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7562 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7563 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7564 				// remove parsed part
7565 				if (updown != UpDown.None) str = str[wep+1..$];
7566 			}
7567 			if (updown == UpDown.None) {
7568 				updown = UpDown.Down;
7569 			}
7570 			wasButtons = wasButtons || (bnum <= 2);
7571 			//assert(updown != UpDown.None);
7572 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7573 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7574 			if (lastButt != lastButt.max) {
7575 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7576 				if (mods != mods.max) {
7577 					uint butbit = 0;
7578 					final switch (lastButt&0x03) {
7579 						case 0: butbit = ModifierState.leftButtonDown; break;
7580 						case 1: butbit = ModifierState.middleButtonDown; break;
7581 						case 2: butbit = ModifierState.rightButtonDown; break;
7582 					}
7583 					     if (lastButt&Flag.Down) mods |= butbit;
7584 					else if (lastButt&Flag.Up) mods &= ~butbit;
7585 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7586 				}
7587 			}
7588 			// remember last button
7589 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7590 		}
7591 		// no button -- nothing to do
7592 		if (lastButt == lastButt.max) return false;
7593 		// done parsing, check if something's left
7594 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7595 		// remove action button from mask
7596 		if ((lastButt&0xff) < 3) {
7597 			final switch (lastButt&0x03) {
7598 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7599 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7600 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7601 			}
7602 		}
7603 		// special case: "Motion" means "ignore buttons"
7604 		if ((lastButt&0xff) == 7 && !wasButtons) {
7605 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7606 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7607 		}
7608 		uint kmod = event.modifierState&kmodmask;
7609 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7610 		// check modifier state
7611 		if (mods != mods.max) {
7612 			if (kmod != mods) return false;
7613 		}
7614 		// now check type
7615 		if ((lastButt&0xff) == 7) {
7616 			// motion
7617 			if (event.type != MouseEventType.motion) return false;
7618 		} else if ((lastButt&0xff) == 3) {
7619 			// wheel
7620 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7621 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7622 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7623 			return false;
7624 		} else {
7625 			// buttons
7626 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7627 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7628 			{
7629 				return false;
7630 			}
7631 			// button number
7632 			switch (lastButt&0x03) {
7633 				case 0: if (event.button != MouseButton.left) return false; break;
7634 				case 1: if (event.button != MouseButton.middle) return false; break;
7635 				case 2: if (event.button != MouseButton.right) return false; break;
7636 				default: return false;
7637 			}
7638 		}
7639 		return true;
7640 	}
7641 }
7642 
7643 version(arsd_mevent_strcmp_test) unittest {
7644 	MouseEvent event;
7645 	event.type = MouseEventType.buttonPressed;
7646 	event.button = MouseButton.left;
7647 	event.modifierState = ModifierState.ctrl;
7648 	assert(event == "C-LMB");
7649 	assert(event != "C-LMBUP");
7650 	assert(event != "C-LMB-UP");
7651 	assert(event != "C-S-LMB");
7652 	assert(event == "*-LMB");
7653 	assert(event != "*-LMB-UP");
7654 
7655 	event.type = MouseEventType.buttonReleased;
7656 	assert(event != "C-LMB");
7657 	assert(event == "C-LMBUP");
7658 	assert(event == "C-LMB-UP");
7659 	assert(event != "C-S-LMB");
7660 	assert(event != "*-LMB");
7661 	assert(event == "*-LMB-UP");
7662 
7663 	event.button = MouseButton.right;
7664 	event.modifierState |= ModifierState.shift;
7665 	event.type = MouseEventType.buttonPressed;
7666 	assert(event != "C-LMB");
7667 	assert(event != "C-LMBUP");
7668 	assert(event != "C-LMB-UP");
7669 	assert(event != "C-S-LMB");
7670 	assert(event != "*-LMB");
7671 	assert(event != "*-LMB-UP");
7672 
7673 	assert(event != "C-RMB");
7674 	assert(event != "C-RMBUP");
7675 	assert(event != "C-RMB-UP");
7676 	assert(event == "C-S-RMB");
7677 	assert(event == "*-RMB");
7678 	assert(event != "*-RMB-UP");
7679 }
7680 
7681 /// This gives a few more options to drawing lines and such
7682 struct Pen {
7683 	Color color; /// the foreground color
7684 	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.
7685 	Style style; /// See [Style]
7686 /+
7687 // From X.h
7688 
7689 #define LineSolid		0
7690 #define LineOnOffDash		1
7691 #define LineDoubleDash		2
7692        LineDou-        The full path of the line is drawn, but the
7693        bleDash         even dashes are filled differently from the
7694                        odd dashes (see fill-style) with CapButt
7695                        style used where even and odd dashes meet.
7696 
7697 
7698 
7699 /* capStyle */
7700 
7701 #define CapNotLast		0
7702 #define CapButt			1
7703 #define CapRound		2
7704 #define CapProjecting		3
7705 
7706 /* joinStyle */
7707 
7708 #define JoinMiter		0
7709 #define JoinRound		1
7710 #define JoinBevel		2
7711 
7712 /* fillStyle */
7713 
7714 #define FillSolid		0
7715 #define FillTiled		1
7716 #define FillStippled		2
7717 #define FillOpaqueStippled	3
7718 
7719 
7720 +/
7721 	/// Style of lines drawn
7722 	enum Style {
7723 		Solid, /// a solid line
7724 		Dashed, /// a dashed line
7725 		Dotted, /// a dotted line
7726 	}
7727 }
7728 
7729 
7730 /++
7731 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7732 
7733 
7734 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7735 
7736 	$(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.)
7737 
7738 	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.
7739 
7740 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7741 
7742 	$(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.
7743 
7744 	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!
7745 
7746 	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!)
7747 
7748 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7749 
7750 	---
7751 		auto image = new Image(256, 256);
7752 		scope(exit) destroy(image);
7753 	---
7754 
7755 	As long as you don't hold on to it outside the scope.
7756 
7757 	I might change it to be an owned pointer at some point in the future.
7758 
7759 	)
7760 
7761 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7762 	you can also often get a fair amount of speedup by getting the raw data format and
7763 	writing some custom code.
7764 
7765 	FIXME INSERT EXAMPLES HERE
7766 
7767 
7768 +/
7769 final class Image {
7770 	///
7771 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7772 		this.width = width;
7773 		this.height = height;
7774 		this.enableAlpha = enableAlpha;
7775 
7776 		impl.createImage(width, height, forcexshm, enableAlpha);
7777 	}
7778 
7779 	///
7780 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7781 		this(size.width, size.height, forcexshm, enableAlpha);
7782 	}
7783 
7784 	private bool suppressDestruction;
7785 
7786 	version(X11)
7787 	this(XImage* handle) {
7788 		this.handle = handle;
7789 		this.rawData = cast(ubyte*) handle.data;
7790 		this.width = handle.width;
7791 		this.height = handle.height;
7792 		this.enableAlpha = handle.depth == 32;
7793 		suppressDestruction = true;
7794 	}
7795 
7796 	~this() {
7797 		if(suppressDestruction) return;
7798 		impl.dispose();
7799 	}
7800 
7801 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7802 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7803 	pure const @system nothrow {
7804 		/*
7805 			To use these to draw a blue rectangle with size WxH at position X,Y...
7806 
7807 			// make certain that it will fit before we proceed
7808 			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!
7809 
7810 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7811 			// (though calculating them isn't really that expensive).
7812 			auto nextLineAdjustment = img.adjustmentForNextLine();
7813 			auto offR = img.redByteOffset();
7814 			auto offB = img.blueByteOffset();
7815 			auto offG = img.greenByteOffset();
7816 			auto bpp = img.bytesPerPixel();
7817 
7818 			auto data = img.getDataPointer();
7819 
7820 			// figure out the starting byte offset
7821 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7822 
7823 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7824 
7825 			// and now our drawing loop for the rectangle
7826 			foreach(y; 0 .. H) {
7827 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7828 				foreach(x; 0 .. W) {
7829 					// write our color
7830 					data[offR] = 0;
7831 					data[offG] = 0;
7832 					data[offB] = 255;
7833 
7834 					data += bpp; // moving to the next pixel is just an addition...
7835 				}
7836 				startOfLine += nextLineAdjustment;
7837 			}
7838 
7839 
7840 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7841 
7842 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7843 			can be made into a bitmask or something so we can write them as *uint...
7844 		*/
7845 
7846 		///
7847 		int offsetForTopLeftPixel() {
7848 			version(X11) {
7849 				return 0;
7850 			} else version(Windows) {
7851 				if(enableAlpha) {
7852 					return (width * 4) * (height - 1);
7853 				} else {
7854 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7855 				}
7856 			} else version(OSXCocoa) {
7857 				return 0 ; //throw new NotYetImplementedException();
7858 			} else static assert(0, "fill in this info for other OSes");
7859 		}
7860 
7861 		///
7862 		int offsetForPixel(int x, int y) {
7863 			version(X11) {
7864 				auto offset = (y * width + x) * 4;
7865 				return offset;
7866 			} else version(Windows) {
7867 				if(enableAlpha) {
7868 					auto itemsPerLine = width * 4;
7869 					// remember, bmps are upside down
7870 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7871 					return offset;
7872 				} else {
7873 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7874 					// remember, bmps are upside down
7875 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7876 					return offset;
7877 				}
7878 			} else version(OSXCocoa) {
7879 				return 0 ; //throw new NotYetImplementedException();
7880 			} else static assert(0, "fill in this info for other OSes");
7881 		}
7882 
7883 		///
7884 		int adjustmentForNextLine() {
7885 			version(X11) {
7886 				return width * 4;
7887 			} else version(Windows) {
7888 				// windows bmps are upside down, so the adjustment is actually negative
7889 				if(enableAlpha)
7890 					return - (cast(int) width * 4);
7891 				else
7892 					return -((cast(int) width * 3 + 3) / 4) * 4;
7893 			} else version(OSXCocoa) {
7894 				return 0 ; //throw new NotYetImplementedException();
7895 			} else static assert(0, "fill in this info for other OSes");
7896 		}
7897 
7898 		/// once you have the position of a pixel, use these to get to the proper color
7899 		int redByteOffset() {
7900 			version(X11) {
7901 				return 2;
7902 			} else version(Windows) {
7903 				return 2;
7904 			} else version(OSXCocoa) {
7905 				return 0 ; //throw new NotYetImplementedException();
7906 			} else static assert(0, "fill in this info for other OSes");
7907 		}
7908 
7909 		///
7910 		int greenByteOffset() {
7911 			version(X11) {
7912 				return 1;
7913 			} else version(Windows) {
7914 				return 1;
7915 			} else version(OSXCocoa) {
7916 				return 0 ; //throw new NotYetImplementedException();
7917 			} else static assert(0, "fill in this info for other OSes");
7918 		}
7919 
7920 		///
7921 		int blueByteOffset() {
7922 			version(X11) {
7923 				return 0;
7924 			} else version(Windows) {
7925 				return 0;
7926 			} else version(OSXCocoa) {
7927 				return 0 ; //throw new NotYetImplementedException();
7928 			} else static assert(0, "fill in this info for other OSes");
7929 		}
7930 
7931 		/// Only valid if [enableAlpha] is true
7932 		int alphaByteOffset() {
7933 			version(X11) {
7934 				return 3;
7935 			} else version(Windows) {
7936 				return 3;
7937 			} else version(OSXCocoa) {
7938 				return 3; //throw new NotYetImplementedException();
7939 			} else static assert(0, "fill in this info for other OSes");
7940 		}
7941 	}
7942 
7943 	///
7944 	final void putPixel(int x, int y, Color c) {
7945 		if(x < 0 || x >= width)
7946 			return;
7947 		if(y < 0 || y >= height)
7948 			return;
7949 
7950 		impl.setPixel(x, y, c);
7951 	}
7952 
7953 	///
7954 	final Color getPixel(int x, int y) {
7955 		if(x < 0 || x >= width)
7956 			return Color.transparent;
7957 		if(y < 0 || y >= height)
7958 			return Color.transparent;
7959 
7960 		version(OSXCocoa) throw new NotYetImplementedException(); else
7961 		return impl.getPixel(x, y);
7962 	}
7963 
7964 	///
7965 	final void opIndexAssign(Color c, int x, int y) {
7966 		putPixel(x, y, c);
7967 	}
7968 
7969 	///
7970 	TrueColorImage toTrueColorImage() {
7971 		auto tci = new TrueColorImage(width, height);
7972 		convertToRgbaBytes(tci.imageData.bytes);
7973 		return tci;
7974 	}
7975 
7976 	///
7977 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) {
7978 		auto tci = i.getAsTrueColorImage();
7979 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
7980 		img.setRgbaBytes(tci.imageData.bytes);
7981 		return img;
7982 	}
7983 
7984 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
7985 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
7986 	/// if you pass null, it will allocate a new one.
7987 	ubyte[] getRgbaBytes(ubyte[] where = null) {
7988 		if(where is null)
7989 			where = new ubyte[this.width*this.height*4];
7990 		convertToRgbaBytes(where);
7991 		return where;
7992 	}
7993 
7994 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
7995 	void setRgbaBytes(in ubyte[] from ) {
7996 		assert(from.length == this.width * this.height * 4);
7997 		setFromRgbaBytes(from);
7998 	}
7999 
8000 	// FIXME: make properly cross platform by getting rgba right
8001 
8002 	/// warning: this is not portable across platforms because the data format can change
8003 	ubyte* getDataPointer() {
8004 		return impl.rawData;
8005 	}
8006 
8007 	/// for use with getDataPointer
8008 	final int bytesPerLine() const pure @safe nothrow {
8009 		version(Windows)
8010 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8011 		else version(X11)
8012 			return 4 * width;
8013 		else version(OSXCocoa)
8014 			return 4 * width;
8015 		else static assert(0);
8016 	}
8017 
8018 	/// for use with getDataPointer
8019 	final int bytesPerPixel() const pure @safe nothrow {
8020 		version(Windows)
8021 			return enableAlpha ? 4 : 3;
8022 		else version(X11)
8023 			return 4;
8024 		else version(OSXCocoa)
8025 			return 4;
8026 		else static assert(0);
8027 	}
8028 
8029 	///
8030 	immutable int width;
8031 
8032 	///
8033 	immutable int height;
8034 
8035 	///
8036 	immutable bool enableAlpha;
8037     //private:
8038 	mixin NativeImageImplementation!() impl;
8039 }
8040 
8041 /++
8042 	A convenience function to pop up a window displaying the image.
8043 	If you pass a win, it will draw the image in it. Otherwise, it will
8044 	create a window with the size of the image and run its event loop, closing
8045 	when a key is pressed.
8046 
8047 	History:
8048 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8049 		always block until the application quit which could cause bizarre behavior
8050 		inside a more complex application. Now, the default is to block until
8051 		this window closes if it is the only event loop running, and otherwise,
8052 		not to block at all and just pop up the display window asynchronously.
8053 +/
8054 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8055 	if(win is null) {
8056 		win = new SimpleWindow(image);
8057 		{
8058 			auto p = win.draw;
8059 			p.drawImage(Point(0, 0), image);
8060 		}
8061 		win.eventLoopWithBlockingMode(
8062 			bm, 0,
8063 			(KeyEvent ev) {
8064 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8065 			} );
8066 	} else {
8067 		win.image = image;
8068 	}
8069 }
8070 
8071 enum FontWeight : int {
8072 	dontcare = 0,
8073 	thin = 100,
8074 	extralight = 200,
8075 	light = 300,
8076 	regular = 400,
8077 	medium = 500,
8078 	semibold = 600,
8079 	bold = 700,
8080 	extrabold = 800,
8081 	heavy = 900
8082 }
8083 
8084 /++
8085 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8086 
8087 	History:
8088 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8089 +/
8090 interface MeasurableFont {
8091 	/++
8092 		Returns true if it is a monospace font, meaning each of the
8093 		glyphs (at least the ascii characters) have matching width
8094 		and no kerning, so you can determine the display width of some
8095 		strings by simply multiplying the string width by [averageWidth].
8096 
8097 		(Please note that multiply doesn't $(I actually) work in general,
8098 		consider characters like tab and newline, but it does sometimes.)
8099 	+/
8100 	bool isMonospace();
8101 
8102 	/++
8103 		The average width of glyphs in the font, traditionally equal to the
8104 		width of the lowercase x. Can be used to estimate bounding boxes,
8105 		especially if the font [isMonospace].
8106 
8107 		Given in pixels.
8108 	+/
8109 	int averageWidth();
8110 	/++
8111 		The height of the bounding box of a line.
8112 	+/
8113 	int height();
8114 	/++
8115 		The maximum ascent of a glyph above the baseline.
8116 
8117 		Given in pixels.
8118 	+/
8119 	int ascent();
8120 	/++
8121 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8122 
8123 		Given in pixels.
8124 	+/
8125 	int descent();
8126 	/++
8127 		The display width of the given string, and if you provide a window, it will use it to
8128 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8129 
8130 		Given in pixels.
8131 	+/
8132 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8133 
8134 }
8135 
8136 // FIXME: i need a font cache and it needs to handle disconnects.
8137 
8138 /++
8139 	Represents a font loaded off the operating system or the X server.
8140 
8141 
8142 	While the api here is unified cross platform, the fonts are not necessarily
8143 	available, even across machines of the same platform, so be sure to always check
8144 	for null (using [isNull]) and have a fallback plan.
8145 
8146 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8147 
8148 	Worst case, a null font will automatically fall back to the default font loaded
8149 	for your system.
8150 +/
8151 class OperatingSystemFont : MeasurableFont {
8152 	// FIXME: when the X Connection is lost, these need to be invalidated!
8153 	// that means I need to store the original stuff again to reconstruct it too.
8154 
8155 	version(X11) {
8156 		XFontStruct* font;
8157 		XFontSet fontset;
8158 
8159 		version(with_xft) {
8160 			XftFont* xftFont;
8161 			bool isXft;
8162 		}
8163 	} else version(Windows) {
8164 		HFONT font;
8165 		int width_;
8166 		int height_;
8167 	} else version(OSXCocoa) {
8168 		// FIXME
8169 	} else static assert(0);
8170 
8171 	/++
8172 		Constructs the class and immediately calls [load].
8173 	+/
8174 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8175 		load(name, size, weight, italic);
8176 	}
8177 
8178 	/++
8179 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8180 
8181 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8182 
8183 		History:
8184 			Added January 24, 2021.
8185 	+/
8186 	this() {
8187 		// this space intentionally left blank
8188 	}
8189 
8190 	/++
8191 		Constructs a copy of the given font object.
8192 
8193 		History:
8194 			Added January 7, 2023.
8195 	+/
8196 	this(OperatingSystemFont font) {
8197 		if(font is null || font.loadedInfo is LoadedInfo.init)
8198 			loadDefault();
8199 		else
8200 			load(font.loadedInfo.tupleof);
8201 	}
8202 
8203 	/++
8204 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8205 
8206 		History:
8207 			Added November 13, 2020.
8208 	+/
8209 	version(with_xft)
8210 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8211 		unload();
8212 
8213 		if(!XftLibrary.attempted) {
8214 			XftLibrary.loadDynamicLibrary();
8215 		}
8216 
8217 		if(!XftLibrary.loadSuccessful)
8218 			return false;
8219 
8220 		auto display = XDisplayConnection.get;
8221 
8222 		char[256] nameBuffer = void;
8223 		int nbp = 0;
8224 
8225 		void add(in char[] a) {
8226 			nameBuffer[nbp .. nbp + a.length] = a[];
8227 			nbp += a.length;
8228 		}
8229 		add(name);
8230 
8231 		if(size) {
8232 			add(":size=");
8233 			add(toInternal!string(size));
8234 		}
8235 		if(weight != FontWeight.dontcare) {
8236 			add(":weight=");
8237 			add(weightToString(weight));
8238 		}
8239 		if(italic)
8240 			add(":slant=100");
8241 
8242 		nameBuffer[nbp] = 0;
8243 
8244 		this.xftFont = XftFontOpenName(
8245 			display,
8246 			DefaultScreen(display),
8247 			nameBuffer.ptr
8248 		);
8249 
8250 		this.isXft = true;
8251 
8252 		if(xftFont !is null) {
8253 			isMonospace_ = stringWidth("x") == stringWidth("M");
8254 			ascent_ = xftFont.ascent;
8255 			descent_ = xftFont.descent;
8256 		}
8257 
8258 		return !isNull();
8259 	}
8260 
8261 	/++
8262 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8263 
8264 
8265 		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.
8266 
8267 		If `pattern` is null, it returns all available font families.
8268 
8269 		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.
8270 
8271 		The format of the pattern is platform-specific.
8272 
8273 		History:
8274 			Added May 1, 2021 (dub v9.5)
8275 	+/
8276 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8277 		version(Windows) {
8278 			auto hdc = GetDC(null);
8279 			scope(exit) ReleaseDC(null, hdc);
8280 			LOGFONT logfont;
8281 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8282 				auto localHandler = *(cast(typeof(handler)*) p);
8283 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8284 			}
8285 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8286 		} else version(X11) {
8287 			//import core.stdc.stdio;
8288 			bool done = false;
8289 			version(with_xft) {
8290 				if(!XftLibrary.attempted) {
8291 					XftLibrary.loadDynamicLibrary();
8292 				}
8293 
8294 				if(!XftLibrary.loadSuccessful)
8295 					goto skipXft;
8296 
8297 				if(!FontConfigLibrary.attempted)
8298 					FontConfigLibrary.loadDynamicLibrary();
8299 				if(!FontConfigLibrary.loadSuccessful)
8300 					goto skipXft;
8301 
8302 				{
8303 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8304 					if(got is null)
8305 						goto skipXft;
8306 					scope(exit) FcFontSetDestroy(got);
8307 
8308 					auto fontPatterns = got.fonts[0 .. got.nfont];
8309 					foreach(candidate; fontPatterns) {
8310 						char* where, whereStyle;
8311 
8312 						char* pmg = FcNameUnparse(candidate);
8313 
8314 						//FcPatternGetString(candidate, "family", 0, &where);
8315 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8316 						//if(where && whereStyle) {
8317 						if(pmg) {
8318 							if(!handler(pmg.sliceCString))
8319 								return;
8320 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8321 						}
8322 					}
8323 				}
8324 			}
8325 
8326 			skipXft:
8327 
8328 			if(pattern is null)
8329 				pattern = "*";
8330 
8331 			int count;
8332 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8333 			scope(exit) XFreeFontNames(coreFontsRaw);
8334 
8335 			auto coreFonts = coreFontsRaw[0 .. count];
8336 
8337 			foreach(font; coreFonts) {
8338 				char[128] tmp;
8339 				tmp[0 ..5] = "core:";
8340 				auto cf = font.sliceCString;
8341 				if(5 + cf.length > tmp.length)
8342 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8343 				tmp[5 .. 5 + cf.length] = cf;
8344 				if(!handler(tmp[0 .. 5 + cf.length]))
8345 					return;
8346 			}
8347 		}
8348 	}
8349 
8350 	/++
8351 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8352 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8353 
8354 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8355 		underlying system doesn't support returning the raw bytes.
8356 
8357 		History:
8358 			Added September 10, 2021 (dub v10.3)
8359 	+/
8360 	ubyte[] getTtfBytes() {
8361 		if(isNull)
8362 			return null;
8363 
8364 		version(Windows) {
8365 			auto dc = GetDC(null);
8366 			auto orig = SelectObject(dc, font);
8367 
8368 			scope(exit) {
8369 				SelectObject(dc, orig);
8370 				ReleaseDC(null, dc);
8371 			}
8372 
8373 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8374 			if(res == GDI_ERROR)
8375 				return null;
8376 
8377 			ubyte[] buffer = new ubyte[](res);
8378 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8379 			if(res == GDI_ERROR)
8380 				return null; // wtf really tbh
8381 
8382 			return buffer;
8383 		} else version(with_xft) {
8384 			if(isXft && xftFont) {
8385 				if(!FontConfigLibrary.attempted)
8386 					FontConfigLibrary.loadDynamicLibrary();
8387 				if(!FontConfigLibrary.loadSuccessful)
8388 					return null;
8389 
8390 				char* file;
8391 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8392 					if (file !is null && file[0]) {
8393 						import core.stdc.stdio;
8394 						auto fp = fopen(file, "rb");
8395 						if(fp is null)
8396 							return null;
8397 						scope(exit)
8398 							fclose(fp);
8399 						fseek(fp, 0, SEEK_END);
8400 						ubyte[] buffer = new ubyte[](ftell(fp));
8401 						fseek(fp, 0, SEEK_SET);
8402 
8403 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8404 						if(got != buffer.length)
8405 							return null;
8406 
8407 						return buffer;
8408 					}
8409 				}
8410 			}
8411 			return null;
8412 		}
8413 	}
8414 
8415 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8416 
8417 	private string weightToString(FontWeight weight) {
8418 		with(FontWeight)
8419 		final switch(weight) {
8420 			case dontcare: return "*";
8421 			case thin: return "extralight";
8422 			case extralight: return "extralight";
8423 			case light: return "light";
8424 			case regular: return "regular";
8425 			case medium: return "medium";
8426 			case semibold: return "demibold";
8427 			case bold: return "bold";
8428 			case extrabold: return "demibold";
8429 			case heavy: return "black";
8430 		}
8431 	}
8432 
8433 	/++
8434 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8435 
8436 		History:
8437 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8438 	+/
8439 	version(X11)
8440 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8441 		unload();
8442 
8443 		string xfontstr;
8444 
8445 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8446 			// this is kinda a disgusting hack but if the user sends an exact
8447 			// string I'd like to honor it...
8448 			xfontstr = name;
8449 		} else {
8450 			string weightstr = weightToString(weight);
8451 			string sizestr;
8452 			if(size == 0)
8453 				sizestr = "*";
8454 			else
8455 				sizestr = toInternal!string(size);
8456 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8457 		}
8458 
8459 		// writeln(xfontstr);
8460 
8461 		auto display = XDisplayConnection.get;
8462 
8463 		font = XLoadQueryFont(display, xfontstr.ptr);
8464 		if(font is null)
8465 			return false;
8466 
8467 		char** lol;
8468 		int lol2;
8469 		char* lol3;
8470 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8471 
8472 		prepareFontInfo();
8473 
8474 		return !isNull();
8475 	}
8476 
8477 	version(X11)
8478 	private void prepareFontInfo() {
8479 		if(font !is null) {
8480 			isMonospace_ = stringWidth("l") == stringWidth("M");
8481 			ascent_ = font.max_bounds.ascent;
8482 			descent_ = font.max_bounds.descent;
8483 		}
8484 	}
8485 
8486 	/++
8487 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8488 
8489 		History:
8490 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8491 	+/
8492 	version(Windows)
8493 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8494 		unload();
8495 
8496 		WCharzBuffer buffer = WCharzBuffer(name);
8497 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8498 
8499 		prepareFontInfo(hdc);
8500 
8501 		return !isNull();
8502 	}
8503 
8504 	version(Windows)
8505 	void prepareFontInfo(HDC hdc = null) {
8506 		if(font is null)
8507 			return;
8508 
8509 		TEXTMETRIC tm;
8510 		auto dc = hdc ? hdc : GetDC(null);
8511 		auto orig = SelectObject(dc, font);
8512 		GetTextMetrics(dc, &tm);
8513 		SelectObject(dc, orig);
8514 		if(hdc is null)
8515 			ReleaseDC(null, dc);
8516 
8517 		width_ = tm.tmAveCharWidth;
8518 		height_ = tm.tmHeight;
8519 		ascent_ = tm.tmAscent;
8520 		descent_ = tm.tmDescent;
8521 		// 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.
8522 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8523 	}
8524 
8525 
8526 	/++
8527 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8528 
8529 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8530 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8531 
8532 		On Windows, it forwards directly to [loadWin32].
8533 
8534 		Params:
8535 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8536 			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.
8537 			weight = approximate boldness, results may vary.
8538 			italic = try to get a slanted version of the given font.
8539 
8540 		History:
8541 			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.
8542 	+/
8543 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8544 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8545 		version(X11) {
8546 			version(with_xft) {
8547 				if(name.length > 5 && name[0 .. 5] == "core:") {
8548 					goto core;
8549 				}
8550 
8551 				if(loadXft(name, size, weight, italic))
8552 					return true;
8553 				// if xft fails, fallback to core to avoid breaking
8554 				// code that already depended on this.
8555 			}
8556 
8557 			core:
8558 
8559 			if(name.length > 5 && name[0 .. 5] == "core:") {
8560 				name = name[5 .. $];
8561 			}
8562 
8563 			return loadCoreX(name, size, weight, italic);
8564 		} else version(Windows) {
8565 			return loadWin32(name, size, weight, italic);
8566 		} else version(OSXCocoa) {
8567 			// FIXME
8568 			return false;
8569 		} else static assert(0);
8570 	}
8571 
8572 	private struct LoadedInfo {
8573 		string name;
8574 		int size;
8575 		FontWeight weight;
8576 		bool italic;
8577 	}
8578 	private LoadedInfo loadedInfo;
8579 
8580 	///
8581 	void unload() {
8582 		if(isNull())
8583 			return;
8584 
8585 		version(X11) {
8586 			auto display = XDisplayConnection.display;
8587 
8588 			if(display is null)
8589 				return;
8590 
8591 			version(with_xft) {
8592 				if(isXft) {
8593 					if(xftFont)
8594 						XftFontClose(display, xftFont);
8595 					isXft = false;
8596 					xftFont = null;
8597 					return;
8598 				}
8599 			}
8600 
8601 			if(font && font !is ScreenPainterImplementation.defaultfont)
8602 				XFreeFont(display, font);
8603 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8604 				XFreeFontSet(display, fontset);
8605 
8606 			font = null;
8607 			fontset = null;
8608 		} else version(Windows) {
8609 			DeleteObject(font);
8610 			font = null;
8611 		} else version(OSXCocoa) {
8612 			// FIXME
8613 		} else static assert(0);
8614 	}
8615 
8616 	private bool isMonospace_;
8617 
8618 	/++
8619 		History:
8620 			Added January 16, 2021
8621 	+/
8622 	bool isMonospace() {
8623 		return isMonospace_;
8624 	}
8625 
8626 	/++
8627 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8628 
8629 		History:
8630 			Added March 26, 2020
8631 			Documented January 16, 2021
8632 	+/
8633 	int averageWidth() {
8634 		version(X11) {
8635 			return stringWidth("x");
8636 		} else version(Windows)
8637 			return width_;
8638 		else assert(0);
8639 	}
8640 
8641 	/++
8642 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8643 
8644 		History:
8645 			Added January 16, 2021
8646 	+/
8647 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8648 	// FIXME: what about tab?
8649 		if(isNull)
8650 			return 0;
8651 
8652 		version(X11) {
8653 			version(with_xft)
8654 				if(isXft && xftFont !is null) {
8655 					//return xftFont.max_advance_width;
8656 					XGlyphInfo extents;
8657 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8658 					// writeln(extents);
8659 					return extents.xOff;
8660 				}
8661 			if(font is null)
8662 				return 0;
8663 			else if(fontset) {
8664 				XRectangle rect;
8665 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8666 
8667 				return rect.width;
8668 			} else {
8669 				return XTextWidth(font, s.ptr, cast(int) s.length);
8670 			}
8671 		} else version(Windows) {
8672 			WCharzBuffer buffer = WCharzBuffer(s);
8673 
8674 			return stringWidth(buffer.slice, window);
8675 		}
8676 		else assert(0);
8677 	}
8678 
8679 	version(Windows)
8680 	/// ditto
8681 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8682 		if(isNull)
8683 			return 0;
8684 		version(Windows) {
8685 			SIZE size;
8686 
8687 			prepareContext(window);
8688 			scope(exit) releaseContext();
8689 
8690 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8691 
8692 			return size.cx;
8693 		} else {
8694 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8695 			static assert(0, "not implemented yet");
8696 			//return stringWidth(s, window);
8697 		}
8698 	}
8699 
8700 	private {
8701 		int prepRefcount;
8702 
8703 		version(Windows) {
8704 			HDC dc;
8705 			HANDLE orig;
8706 			HWND hwnd;
8707 		}
8708 	}
8709 	/++
8710 		[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.
8711 
8712 		History:
8713 			Added January 23, 2021
8714 	+/
8715 	void prepareContext(SimpleWindow window = null) {
8716 		prepRefcount++;
8717 		if(prepRefcount == 1) {
8718 			version(Windows) {
8719 				hwnd = window is null ? null : window.impl.hwnd;
8720 				dc = GetDC(hwnd);
8721 				orig = SelectObject(dc, font);
8722 			}
8723 		}
8724 	}
8725 	/// ditto
8726 	void releaseContext() {
8727 		prepRefcount--;
8728 		if(prepRefcount == 0) {
8729 			version(Windows) {
8730 				SelectObject(dc, orig);
8731 				ReleaseDC(hwnd, dc);
8732 				hwnd = null;
8733 				dc = null;
8734 				orig = null;
8735 			}
8736 		}
8737 	}
8738 
8739 	/+
8740 		FIXME: I think I need advance and kerning pair
8741 
8742 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8743 	+/
8744 
8745 	/++
8746 		Returns the height of the font.
8747 
8748 		History:
8749 			Added March 26, 2020
8750 			Documented January 16, 2021
8751 	+/
8752 	int height() {
8753 		version(X11) {
8754 			version(with_xft)
8755 				if(isXft && xftFont !is null) {
8756 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8757 				}
8758 			if(font is null)
8759 				return 0;
8760 			return font.max_bounds.ascent + font.max_bounds.descent;
8761 		} else version(Windows)
8762 			return height_;
8763 		else assert(0);
8764 	}
8765 
8766 	private int ascent_;
8767 	private int descent_;
8768 
8769 	/++
8770 		Max ascent above the baseline.
8771 
8772 		History:
8773 			Added January 22, 2021
8774 	+/
8775 	int ascent() {
8776 		return ascent_;
8777 	}
8778 
8779 	/++
8780 		Max descent below the baseline.
8781 
8782 		History:
8783 			Added January 22, 2021
8784 	+/
8785 	int descent() {
8786 		return descent_;
8787 	}
8788 
8789 	/++
8790 		Loads the default font used by [ScreenPainter] if none others are loaded.
8791 
8792 		Returns:
8793 			This method mutates the `this` object, but then returns `this` for
8794 			easy chaining like:
8795 
8796 			---
8797 			auto font = foo.isNull ? foo : foo.loadDefault
8798 			---
8799 
8800 		History:
8801 			Added previously, but left unimplemented until January 24, 2021.
8802 	+/
8803 	OperatingSystemFont loadDefault() {
8804 		unload();
8805 
8806 		loadedInfo = LoadedInfo.init;
8807 
8808 		version(X11) {
8809 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8810 			// but meh since sdpy does its own thing, this should be ok too
8811 
8812 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8813 			this.font = ScreenPainterImplementation.defaultfont;
8814 			this.fontset = ScreenPainterImplementation.defaultfontset;
8815 
8816 			prepareFontInfo();
8817 		} else version(Windows) {
8818 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8819 			this.font = ScreenPainterImplementation.defaultGuiFont;
8820 
8821 			prepareFontInfo();
8822 		} else throw new NotYetImplementedException();
8823 
8824 		return this;
8825 	}
8826 
8827 	///
8828 	bool isNull() {
8829 		version(OSXCocoa) throw new NotYetImplementedException(); else {
8830 			version(with_xft)
8831 				if(isXft)
8832 					return xftFont is null;
8833 			return font is null;
8834 		}
8835 	}
8836 
8837 	/* Metrics */
8838 	/+
8839 		GetABCWidth
8840 		GetKerningPairs
8841 
8842 		if I do it right, I can size it all here, and match
8843 		what happens when I draw the full string with the OS functions.
8844 
8845 		subclasses might do the same thing while getting the glyphs on images
8846 	struct GlyphInfo {
8847 		int glyph;
8848 
8849 		size_t stringIdxStart;
8850 		size_t stringIdxEnd;
8851 
8852 		Rectangle boundingBox;
8853 	}
8854 	GlyphInfo[] getCharBoxes() {
8855 		// XftTextExtentsUtf8
8856 		return null;
8857 
8858 	}
8859 	+/
8860 
8861 	~this() {
8862 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
8863 		unload();
8864 	}
8865 }
8866 
8867 version(Windows)
8868 private string sliceCString(const(wchar)[] w) {
8869 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
8870 }
8871 
8872 private inout(char)[] sliceCString(inout(char)* s) {
8873 	import core.stdc.string;
8874 	auto len = strlen(s);
8875 	return s[0 .. len];
8876 }
8877 
8878 /**
8879 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
8880 	than constructing it directly. Then, it is reference counted so you can pass it
8881 	at around and when the last ref goes out of scope, the buffered drawing activities
8882 	are all carried out.
8883 
8884 
8885 	Most functions use the outlineColor instead of taking a color themselves.
8886 	ScreenPainter is reference counted and draws its buffer to the screen when its
8887 	final reference goes out of scope.
8888 */
8889 struct ScreenPainter {
8890 	CapableOfBeingDrawnUpon window;
8891 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) {
8892 		this.window = window;
8893 		if(window.closed)
8894 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
8895 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
8896 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
8897 		if(window.activeScreenPainter !is null) {
8898 			impl = window.activeScreenPainter;
8899 			if(impl.referenceCount == 0) {
8900 				impl.window = window;
8901 				impl.create(handle);
8902 			}
8903 			impl.manualInvalidations = manualInvalidations;
8904 			impl.referenceCount++;
8905 		//	writeln("refcount ++ ", impl.referenceCount);
8906 		} else {
8907 			impl = new ScreenPainterImplementation;
8908 			impl.window = window;
8909 			impl.create(handle);
8910 			impl.referenceCount = 1;
8911 			impl.manualInvalidations = manualInvalidations;
8912 			window.activeScreenPainter = impl;
8913 			// writeln("constructed");
8914 		}
8915 
8916 		copyActiveOriginals();
8917 	}
8918 
8919 	/++
8920 		EXPERIMENTAL. subject to change.
8921 
8922 		When you draw a cursor, you can draw this to notify your window of where it is,
8923 		for IME systems to use.
8924 	+/
8925 	void notifyCursorPosition(int x, int y, int width, int height) {
8926 		if(auto w = cast(SimpleWindow) window) {
8927 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
8928 		}
8929 	}
8930 
8931 	/++
8932 		If you are using manual invalidations, this informs the
8933 		window system that a section needs to be redrawn.
8934 
8935 		If you didn't opt into manual invalidation, you don't
8936 		have to call this.
8937 
8938 		History:
8939 			Added December 30, 2021 (dub v10.5)
8940 	+/
8941 	void invalidateRect(Rectangle rect) {
8942 		if(impl is null) return;
8943 
8944 		// transform(rect)
8945 		rect.left += _originX;
8946 		rect.right += _originX;
8947 		rect.top += _originY;
8948 		rect.bottom += _originY;
8949 
8950 		impl.invalidateRect(rect);
8951 	}
8952 
8953 	private Pen originalPen;
8954 	private Color originalFillColor;
8955 	private arsd.color.Rectangle originalClipRectangle;
8956 	private OperatingSystemFont originalFont;
8957 	void copyActiveOriginals() {
8958 		if(impl is null) return;
8959 		originalPen = impl._activePen;
8960 		originalFillColor = impl._fillColor;
8961 		originalClipRectangle = impl._clipRectangle;
8962 		originalFont = impl._activeFont;
8963 	}
8964 
8965 	~this() {
8966 		if(impl is null) return;
8967 		impl.referenceCount--;
8968 		//writeln("refcount -- ", impl.referenceCount);
8969 		if(impl.referenceCount == 0) {
8970 			// writeln("destructed");
8971 			impl.dispose();
8972 			*window.activeScreenPainter = ScreenPainterImplementation.init;
8973 			// writeln("paint finished");
8974 		} else {
8975 			// there is still an active reference, reset stuff so the
8976 			// next user doesn't get weirdness via the reference
8977 			this.rasterOp = RasterOp.normal;
8978 			pen = originalPen;
8979 			fillColor = originalFillColor;
8980 			if(originalFont)
8981 				setFont(originalFont);
8982 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
8983 		}
8984 	}
8985 
8986 	this(this) {
8987 		if(impl is null) return;
8988 		impl.referenceCount++;
8989 		//writeln("refcount ++ ", impl.referenceCount);
8990 
8991 		copyActiveOriginals();
8992 	}
8993 
8994 	private int _originX;
8995 	private int _originY;
8996 	@property int originX() { return _originX; }
8997 	@property int originY() { return _originY; }
8998 	@property int originX(int a) {
8999 		_originX = a;
9000 		return _originX;
9001 	}
9002 	@property int originY(int a) {
9003 		_originY = a;
9004 		return _originY;
9005 	}
9006 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9007 	private void transform(ref Point p) {
9008 		if(impl is null) return;
9009 		p.x += _originX;
9010 		p.y += _originY;
9011 	}
9012 
9013 	// this needs to be checked BEFORE the originX/Y transformation
9014 	private bool isClipped(Point p) {
9015 		return !currentClipRectangle.contains(p);
9016 	}
9017 	private bool isClipped(Point p, int width, int height) {
9018 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9019 	}
9020 	private bool isClipped(Point p, Size s) {
9021 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9022 	}
9023 	private bool isClipped(Point p, Point p2) {
9024 		// need to ensure the end points are actually included inside, so the +1 does that
9025 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9026 	}
9027 
9028 
9029 	/++
9030 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9031 
9032 		Returns:
9033 			The old clip rectangle.
9034 
9035 		History:
9036 			Return value was `void` prior to May 10, 2021.
9037 
9038 	+/
9039 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9040 		if(impl is null) return currentClipRectangle;
9041 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9042 			return currentClipRectangle; // no need to do anything
9043 		auto old = currentClipRectangle;
9044 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9045 		transform(pt);
9046 
9047 		impl.setClipRectangle(pt.x, pt.y, width, height);
9048 
9049 		return old;
9050 	}
9051 
9052 	/// ditto
9053 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9054 		if(impl is null) return currentClipRectangle;
9055 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9056 	}
9057 
9058 	///
9059 	void setFont(OperatingSystemFont font) {
9060 		if(impl is null) return;
9061 		impl.setFont(font);
9062 	}
9063 
9064 	///
9065 	int fontHeight() {
9066 		if(impl is null) return 0;
9067 		return impl.fontHeight();
9068 	}
9069 
9070 	private Pen activePen;
9071 
9072 	///
9073 	@property void pen(Pen p) {
9074 		if(impl is null) return;
9075 		activePen = p;
9076 		impl.pen(p);
9077 	}
9078 
9079 	///
9080 	@scriptable
9081 	@property void outlineColor(Color c) {
9082 		if(impl is null) return;
9083 		if(activePen.color == c)
9084 			return;
9085 		activePen.color = c;
9086 		impl.pen(activePen);
9087 	}
9088 
9089 	///
9090 	@scriptable
9091 	@property void fillColor(Color c) {
9092 		if(impl is null) return;
9093 		impl.fillColor(c);
9094 	}
9095 
9096 	///
9097 	@property void rasterOp(RasterOp op) {
9098 		if(impl is null) return;
9099 		impl.rasterOp(op);
9100 	}
9101 
9102 
9103 	void updateDisplay() {
9104 		// FIXME this should do what the dtor does
9105 	}
9106 
9107 	/// 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)
9108 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9109 		if(impl is null) return;
9110 		if(isClipped(upperLeft, width, height)) return;
9111 		transform(upperLeft);
9112 		version(Windows) {
9113 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9114 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9115 			RECT clip = scroll;
9116 			RECT uncovered;
9117 			HRGN hrgn;
9118 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9119 				throw new WindowsApiException("ScrollDC", GetLastError());
9120 
9121 		} else version(X11) {
9122 			// FIXME: clip stuff outside this rectangle
9123 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9124 		} else version(OSXCocoa) {
9125 			throw new NotYetImplementedException();
9126 		} else static assert(0);
9127 	}
9128 
9129 	///
9130 	void clear(Color color = Color.white()) {
9131 		if(impl is null) return;
9132 		fillColor = color;
9133 		outlineColor = color;
9134 		drawRectangle(Point(0, 0), window.width, window.height);
9135 	}
9136 
9137 	/++
9138 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9139 
9140 		Params:
9141 			upperLeft = point on the window where the upper left corner of the image will be drawn
9142 			imageUpperLeft = point on the image to start the slice to draw
9143 			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.
9144 		History:
9145 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9146 	+/
9147 	version(OSXCocoa) {} else // NotYetImplementedException
9148 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9149 		if(impl is null) return;
9150 		if(isClipped(upperLeft, s.width, s.height)) return;
9151 		transform(upperLeft);
9152 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9153 	}
9154 
9155 	///
9156 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9157 		if(impl is null) return;
9158 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9159 		transform(upperLeft);
9160 		if(w == 0 || w > i.width)
9161 			w = i.width;
9162 		if(h == 0 || h > i.height)
9163 			h = i.height;
9164 		if(upperLeftOfImage.x < 0)
9165 			upperLeftOfImage.x = 0;
9166 		if(upperLeftOfImage.y < 0)
9167 			upperLeftOfImage.y = 0;
9168 
9169 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9170 	}
9171 
9172 	///
9173 	Size textSize(in char[] text) {
9174 		if(impl is null) return Size(0, 0);
9175 		return impl.textSize(text);
9176 	}
9177 
9178 	/++
9179 		Draws a string in the window with the set font (see [setFont] to change it).
9180 
9181 		Params:
9182 			upperLeft = the upper left point of the bounding box of the text
9183 			text = the string to draw
9184 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9185 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9186 	+/
9187 	@scriptable
9188 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9189 		if(impl is null) return;
9190 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9191 			if(isClipped(upperLeft, lowerRight)) return;
9192 			transform(lowerRight);
9193 		} else {
9194 			if(isClipped(upperLeft, textSize(text))) return;
9195 		}
9196 		transform(upperLeft);
9197 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9198 	}
9199 
9200 	/++
9201 		Draws text using a custom font.
9202 
9203 		This is still MAJOR work in progress.
9204 
9205 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9206 	+/
9207 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9208 		if(impl is null) return;
9209 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9210 		transform(upperLeft);
9211 		font.drawString(this, upperLeft, text);
9212 	}
9213 
9214 	version(Windows)
9215 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9216 		if(impl is null) return;
9217 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9218 		transform(upperLeft);
9219 
9220 		if(text.length && text[$-1] == '\n')
9221 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9222 
9223 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9224 	}
9225 
9226 	static struct TextDrawingContext {
9227 		Point boundingBoxUpperLeft;
9228 		Point boundingBoxLowerRight;
9229 
9230 		Point currentLocation;
9231 
9232 		Point lastDrewUpperLeft;
9233 		Point lastDrewLowerRight;
9234 
9235 		// how do i do right aligned rich text?
9236 		// i kinda want to do a pre-made drawing then right align
9237 		// draw the whole block.
9238 		//
9239 		// That's exactly the diff: inline vs block stuff.
9240 
9241 		// I need to get coordinates of an inline section out too,
9242 		// not just a bounding box, but a series of bounding boxes
9243 		// should be ok. Consider what's needed to detect a click
9244 		// on a link in the middle of a paragraph breaking a line.
9245 		//
9246 		// Generally, we should be able to get the rectangles of
9247 		// any portion we draw.
9248 		//
9249 		// It also needs to tell what text is left if it overflows
9250 		// out of the box, so we can do stuff like float images around
9251 		// it. It should not attempt to draw a letter that would be
9252 		// clipped.
9253 		//
9254 		// I might also turn off word wrap stuff.
9255 	}
9256 
9257 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9258 		if(impl is null) return;
9259 		// FIXME
9260 	}
9261 
9262 	/// Drawing an individual pixel is slow. Avoid it if possible.
9263 	void drawPixel(Point where) {
9264 		if(impl is null) return;
9265 		if(isClipped(where)) return;
9266 		transform(where);
9267 		impl.drawPixel(where.x, where.y);
9268 	}
9269 
9270 
9271 	/// Draws a pen using the current pen / outlineColor
9272 	@scriptable
9273 	void drawLine(Point starting, Point ending) {
9274 		if(impl is null) return;
9275 		if(isClipped(starting, ending)) return;
9276 		transform(starting);
9277 		transform(ending);
9278 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9279 	}
9280 
9281 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9282 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9283 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9284 	@scriptable
9285 	void drawRectangle(Point upperLeft, int width, int height) {
9286 		if(impl is null) return;
9287 		if(isClipped(upperLeft, width, height)) return;
9288 		transform(upperLeft);
9289 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9290 	}
9291 
9292 	/// ditto
9293 	void drawRectangle(Point upperLeft, Size size) {
9294 		if(impl is null) return;
9295 		if(isClipped(upperLeft, size.width, size.height)) return;
9296 		transform(upperLeft);
9297 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9298 	}
9299 
9300 	/// ditto
9301 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9302 		if(impl is null) return;
9303 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9304 		transform(upperLeft);
9305 		transform(lowerRightInclusive);
9306 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9307 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9308 	}
9309 
9310 	// overload added on May 12, 2021
9311 	/// ditto
9312 	void drawRectangle(Rectangle rect) {
9313 		drawRectangle(rect.upperLeft, rect.size);
9314 	}
9315 
9316 	/// Arguments are the points of the bounding rectangle
9317 	void drawEllipse(Point upperLeft, Point lowerRight) {
9318 		if(impl is null) return;
9319 		if(isClipped(upperLeft, lowerRight)) return;
9320 		transform(upperLeft);
9321 		transform(lowerRight);
9322 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9323 	}
9324 
9325 	/++
9326 		start and finish are units of degrees * 64
9327 
9328 		History:
9329 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9330 
9331 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9332 	+/
9333 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9334 		if(impl is null) return;
9335 		// FIXME: not actually implemented
9336 		if(isClipped(upperLeft, width, height)) return;
9337 		transform(upperLeft);
9338 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9339 	}
9340 
9341 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9342 	void drawCircle(Point upperLeft, int diameter) {
9343 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9344 	}
9345 
9346 	/// .
9347 	void drawPolygon(Point[] vertexes) {
9348 		if(impl is null) return;
9349 		assert(vertexes.length);
9350 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9351 		foreach(ref vertex; vertexes) {
9352 			if(vertex.x < minX)
9353 				minX = vertex.x;
9354 			if(vertex.y < minY)
9355 				minY = vertex.y;
9356 			if(vertex.x > maxX)
9357 				maxX = vertex.x;
9358 			if(vertex.y > maxY)
9359 				maxY = vertex.y;
9360 			transform(vertex);
9361 		}
9362 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9363 		impl.drawPolygon(vertexes);
9364 	}
9365 
9366 	/// ditto
9367 	void drawPolygon(Point[] vertexes...) {
9368 		if(impl is null) return;
9369 		drawPolygon(vertexes);
9370 	}
9371 
9372 
9373 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9374 
9375 	//mixin NativeScreenPainterImplementation!() impl;
9376 
9377 
9378 	// HACK: if I mixin the impl directly, it won't let me override the copy
9379 	// constructor! The linker complains about there being multiple definitions.
9380 	// I'll make the best of it and reference count it though.
9381 	ScreenPainterImplementation* impl;
9382 }
9383 
9384 	// HACK: I need a pointer to the implementation so it's separate
9385 	struct ScreenPainterImplementation {
9386 		CapableOfBeingDrawnUpon window;
9387 		int referenceCount;
9388 		mixin NativeScreenPainterImplementation!();
9389 	}
9390 
9391 // FIXME: i haven't actually tested the sprite class on MS Windows
9392 
9393 /**
9394 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9395 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9396 
9397 
9398 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9399 	though I'm not sure that's ideal and the implementation might change.
9400 
9401 	You create one by giving a window and an image. It optimizes for that window,
9402 	and copies the image into it to use as the initial picture. Creating a sprite
9403 	can be quite slow (especially over a network connection) so you should do it
9404 	as little as possible and just hold on to your sprite handles after making them.
9405 	simpledisplay does try to do its best though, using the XSHM extension if available,
9406 	but you should still write your code as if it will always be slow.
9407 
9408 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9409 	a fast operation - much faster than drawing the Image itself every time.
9410 
9411 	`Sprite` represents a scarce resource which should be freed when you
9412 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9413 	after it has been disposed. If you are unsure about this, don't take chances,
9414 	just let the garbage collector do it for you. But ideally, you can manage its
9415 	lifetime more efficiently.
9416 
9417 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9418 	support alpha blending in its drawing at this time. That might change in the
9419 	future, but if you need alpha blending right now, use OpenGL instead. See
9420 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9421 
9422 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9423 	in by setting the enableAlpha = true in the constructor.
9424 */
9425 version(OSXCocoa) {} else // NotYetImplementedException
9426 class Sprite : CapableOfBeingDrawnUpon {
9427 
9428 	///
9429 	ScreenPainter draw() {
9430 		return ScreenPainter(this, handle, false);
9431 	}
9432 
9433 	/++
9434 		Copies the sprite's current state into a [TrueColorImage].
9435 
9436 		Be warned: this can be a very slow operation
9437 
9438 		History:
9439 			Actually implemented on March 14, 2021
9440 	+/
9441 	TrueColorImage takeScreenshot() {
9442 		return trueColorImageFromNativeHandle(handle, width, height);
9443 	}
9444 
9445 	void delegate() paintingFinishedDg() { return null; }
9446 	bool closed() { return false; }
9447 	ScreenPainterImplementation* activeScreenPainter_;
9448 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9449 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9450 
9451 	version(Windows)
9452 		private ubyte* rawData;
9453 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9454 	// ditto on the XPicture stuff
9455 
9456 	version(X11) {
9457 		private static XRenderPictFormat* RGB24;
9458 		private static XRenderPictFormat* ARGB32;
9459 
9460 		private Picture xrenderPicture;
9461 	}
9462 
9463 	version(X11)
9464 	private static void requireXRender() {
9465 		if(!XRenderLibrary.loadAttempted) {
9466 			XRenderLibrary.loadDynamicLibrary();
9467 		}
9468 
9469 		if(!XRenderLibrary.loadSuccessful)
9470 			throw new Exception("XRender library load failure");
9471 
9472 		auto display = XDisplayConnection.get;
9473 
9474 		// FIXME: if we migrate X displays, these need to be changed
9475 		if(RGB24 is null)
9476 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9477 		if(ARGB32 is null)
9478 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9479 	}
9480 
9481 	protected this() {}
9482 
9483 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9484 		this._width = width;
9485 		this._height = height;
9486 		this.enableAlpha = enableAlpha;
9487 
9488 		version(X11) {
9489 			auto display = XDisplayConnection.get();
9490 
9491 			if(enableAlpha) {
9492 				requireXRender();
9493 			}
9494 
9495 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9496 
9497 			if(enableAlpha) {
9498 				XRenderPictureAttributes attrs;
9499 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9500 			}
9501 		} else version(Windows) {
9502 			version(CRuntime_DigitalMars) {
9503 				//if(enableAlpha)
9504 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9505 			}
9506 
9507 			BITMAPINFO infoheader;
9508 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9509 			infoheader.bmiHeader.biWidth = width;
9510 			infoheader.bmiHeader.biHeight = height;
9511 			infoheader.bmiHeader.biPlanes = 1;
9512 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9513 			infoheader.bmiHeader.biCompression = BI_RGB;
9514 
9515 			// FIXME: this should prolly be a device dependent bitmap...
9516 			handle = CreateDIBSection(
9517 				null,
9518 				&infoheader,
9519 				DIB_RGB_COLORS,
9520 				cast(void**) &rawData,
9521 				null,
9522 				0);
9523 
9524 			if(handle is null)
9525 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9526 		}
9527 	}
9528 
9529 	/// Makes a sprite based on the image with the initial contents from the Image
9530 	this(SimpleWindow win, Image i) {
9531 		this(win, i.width, i.height, i.enableAlpha);
9532 
9533 		version(X11) {
9534 			auto display = XDisplayConnection.get();
9535 			auto gc = XCreateGC(display, this.handle, 0, null);
9536 			scope(exit) XFreeGC(display, gc);
9537 			if(i.usingXshm)
9538 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9539 			else
9540 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9541 		} else version(Windows) {
9542 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9543 			auto arrLength = itemsPerLine * height;
9544 			rawData[0..arrLength] = i.rawData[0..arrLength];
9545 		} else version(OSXCocoa) {
9546 			// FIXME: I have no idea if this is even any good
9547 			ubyte* rawData;
9548 
9549 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9550 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
9551 				colorSpace,
9552 				kCGImageAlphaPremultipliedLast
9553 				|kCGBitmapByteOrder32Big);
9554 			CGColorSpaceRelease(colorSpace);
9555 			rawData = CGBitmapContextGetData(context);
9556 
9557 			auto rdl = (width * height * 4);
9558 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9559 		} else static assert(0);
9560 	}
9561 
9562 	/++
9563 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9564 
9565 		Params:
9566 			where = point on the window where the upper left corner of the image will be drawn
9567 			imageUpperLeft = point on the image to start the slice to draw
9568 			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.
9569 		History:
9570 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9571 	+/
9572 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9573 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9574 	}
9575 
9576 	/// Call this when you're ready to get rid of it
9577 	void dispose() {
9578 		version(X11) {
9579 			staticDispose(xrenderPicture, handle);
9580 			xrenderPicture = None;
9581 			handle = None;
9582 		} else version(Windows) {
9583 			staticDispose(handle);
9584 			handle = null;
9585 		} else version(OSXCocoa) {
9586 			staticDispose(context);
9587 			context = null;
9588 		} else static assert(0);
9589 
9590 	}
9591 
9592 	version(X11)
9593 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9594 		if(xrenderPicture)
9595 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9596 		if(handle)
9597 			XFreePixmap(XDisplayConnection.get(), handle);
9598 	}
9599 	else version(Windows)
9600 	static void staticDispose(HBITMAP handle) {
9601 		if(handle)
9602 			DeleteObject(handle);
9603 	}
9604 	else version(OSXCocoa)
9605 	static void staticDispose(CGContextRef context) {
9606 		if(context)
9607 			CGContextRelease(context);
9608 	}
9609 
9610 	~this() {
9611 		version(X11) { if(xrenderPicture || handle)
9612 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9613 		} else version(Windows) { if(handle)
9614 			cleanupQueue.queue!staticDispose(handle);
9615 		} else version(OSXCocoa) { if(context)
9616 			cleanupQueue.queue!staticDispose(context);
9617 		} else static assert(0);
9618 	}
9619 
9620 	///
9621 	final @property int width() { return _width; }
9622 
9623 	///
9624 	final @property int height() { return _height; }
9625 
9626 	///
9627 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9628 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9629 	}
9630 
9631 	auto nativeHandle() {
9632 		return handle;
9633 	}
9634 
9635 	private:
9636 
9637 	int _width;
9638 	int _height;
9639 	bool enableAlpha;
9640 	version(X11)
9641 		Pixmap handle;
9642 	else version(Windows)
9643 		HBITMAP handle;
9644 	else version(OSXCocoa)
9645 		CGContextRef context;
9646 	else static assert(0);
9647 }
9648 
9649 /++
9650 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9651 
9652 	History:
9653 		Added November 20, 2021 (dub v10.4)
9654 +/
9655 abstract class Gradient : Sprite {
9656 	protected this(int w, int h) {
9657 		version(X11) {
9658 			Sprite.requireXRender();
9659 
9660 			super();
9661 			enableAlpha = true;
9662 			_width = w;
9663 			_height = h;
9664 		} else version(Windows) {
9665 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9666 		}
9667 	}
9668 
9669 	version(Windows)
9670 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9671 		auto ptr = rawData;
9672 		foreach(j; 0 .. _height)
9673 		foreach(i; 0 .. _width) {
9674 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9675 			*rawData = (color.a * color.b) / 255; rawData++;
9676 			*rawData = (color.a * color.g) / 255; rawData++;
9677 			*rawData = (color.a * color.r) / 255; rawData++;
9678 			*rawData = color.a; rawData++;
9679 		}
9680 	}
9681 
9682 	version(X11)
9683 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9684 		assert(stops.length > 0);
9685 		assert(stops.length <= 16, "I got lazy with buffers");
9686 
9687 		XFixed[16] stopsPositions = void;
9688 		XRenderColor[16] colors = void;
9689 
9690 		foreach(idx, stop; stops) {
9691 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9692 			auto c = stop.c;
9693 			colors[idx] = XRenderColor(
9694 				cast(ushort)(c.r * ushort.max / 255),
9695 				cast(ushort)(c.g * ushort.max / 255),
9696 				cast(ushort)(c.b * ushort.max / 255),
9697 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9698 			);
9699 		}
9700 
9701 		xrenderPicture = dg(stopsPositions, colors);
9702 	}
9703 
9704 	///
9705 	static struct Stop {
9706 		float percentage; /// between 0 and 1.0
9707 		Color c;
9708 	}
9709 }
9710 
9711 /++
9712 	Creates a linear gradient between p1 and p2.
9713 
9714 	X ONLY RIGHT NOW
9715 
9716 	History:
9717 		Added November 20, 2021 (dub v10.4)
9718 
9719 	Bugs:
9720 		Not yet implemented on Windows.
9721 +/
9722 class LinearGradient : Gradient {
9723 	/++
9724 
9725 	+/
9726 	this(Point p1, Point p2, Stop[] stops...) {
9727 		super(p2.x, p2.y);
9728 
9729 		version(X11) {
9730 			XLinearGradient gradient;
9731 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9732 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9733 
9734 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9735 				return XRenderCreateLinearGradient(
9736 					XDisplayConnection.get,
9737 					&gradient,
9738 					stopsPositions.ptr,
9739 					colors.ptr,
9740 					cast(int) stops.length);
9741 			});
9742 		} else version(Windows) {
9743 			// FIXME
9744 			forEachPixel((int x, int y) {
9745 				import core.stdc.math;
9746 
9747 				//sqrtf(
9748 
9749 				return Color.transparent;
9750 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9751 			});
9752 		}
9753 	}
9754 }
9755 
9756 /++
9757 	A conical gradient goes from color to color around a circumference from a center point.
9758 
9759 	X ONLY RIGHT NOW
9760 
9761 	History:
9762 		Added November 20, 2021 (dub v10.4)
9763 
9764 	Bugs:
9765 		Not yet implemented on Windows.
9766 +/
9767 class ConicalGradient : Gradient {
9768 	/++
9769 
9770 	+/
9771 	this(Point center, float angleInDegrees, Stop[] stops...) {
9772 		super(center.x * 2, center.y * 2);
9773 
9774 		version(X11) {
9775 			XConicalGradient gradient;
9776 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9777 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9778 
9779 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9780 				return XRenderCreateConicalGradient(
9781 					XDisplayConnection.get,
9782 					&gradient,
9783 					stopsPositions.ptr,
9784 					colors.ptr,
9785 					cast(int) stops.length);
9786 			});
9787 		} else version(Windows) {
9788 			// FIXME
9789 			forEachPixel((int x, int y) {
9790 				import core.stdc.math;
9791 
9792 				//sqrtf(
9793 
9794 				return Color.transparent;
9795 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9796 			});
9797 
9798 		}
9799 	}
9800 }
9801 
9802 /++
9803 	A radial gradient goes from color to color based on distance from the center.
9804 	It is like rings of color.
9805 
9806 	X ONLY RIGHT NOW
9807 
9808 
9809 	More specifically, you create two circles: an inner circle and an outer circle.
9810 	The gradient is only drawn in the area outside the inner circle but inside the outer
9811 	circle. The closest line between those two circles forms the line for the gradient
9812 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9813 
9814 	History:
9815 		Added November 20, 2021 (dub v10.4)
9816 
9817 	Bugs:
9818 		Not yet implemented on Windows.
9819 +/
9820 class RadialGradient : Gradient {
9821 	/++
9822 
9823 	+/
9824 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
9825 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
9826 
9827 		version(X11) {
9828 			XRadialGradient gradient;
9829 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
9830 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
9831 
9832 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9833 				return XRenderCreateRadialGradient(
9834 					XDisplayConnection.get,
9835 					&gradient,
9836 					stopsPositions.ptr,
9837 					colors.ptr,
9838 					cast(int) stops.length);
9839 			});
9840 		} else version(Windows) {
9841 			// FIXME
9842 			forEachPixel((int x, int y) {
9843 				import core.stdc.math;
9844 
9845 				//sqrtf(
9846 
9847 				return Color.transparent;
9848 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9849 			});
9850 		}
9851 	}
9852 }
9853 
9854 
9855 
9856 /+
9857 	NOT IMPLEMENTED
9858 
9859 	A display-stored image optimized for relatively quick drawing, like
9860 	[Sprite], but this one supports alpha channel blending and does NOT
9861 	support direct drawing upon it with a [ScreenPainter].
9862 
9863 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
9864 	plain [ScreenPainter]... sort of.
9865 
9866 	On X11, it requires the Xrender extension and library. This is available
9867 	almost everywhere though.
9868 
9869 	History:
9870 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
9871 +/
9872 version(none)
9873 class AlphaSprite {
9874 	/++
9875 		Copies the given image into it.
9876 	+/
9877 	this(MemoryImage img) {
9878 
9879 		if(!XRenderLibrary.loadAttempted) {
9880 			XRenderLibrary.loadDynamicLibrary();
9881 
9882 			// FIXME: this needs to be reconstructed when the X server changes
9883 			repopulateX();
9884 		}
9885 		if(!XRenderLibrary.loadSuccessful)
9886 			throw new Exception("XRender library load failure");
9887 
9888 		// I probably need to put the alpha mask in a separate Picture
9889 		// ugh
9890 		// maybe the Sprite itself can have an alpha bitmask anyway
9891 
9892 
9893 		auto display = XDisplayConnection.get();
9894 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
9895 
9896 
9897 		XRenderPictureAttributes attrs;
9898 
9899 		handle = XRenderCreatePicture(
9900 			XDisplayConnection.get,
9901 			pixmap,
9902 			RGBA,
9903 			0,
9904 			&attrs
9905 		);
9906 
9907 	}
9908 
9909 	// maybe i'll use the create gradient functions too with static factories..
9910 
9911 	void drawAt(ScreenPainter painter, Point where) {
9912 		//painter.drawPixmap(this, where);
9913 
9914 		XRenderPictureAttributes attrs;
9915 
9916 		auto pic = XRenderCreatePicture(
9917 			XDisplayConnection.get,
9918 			painter.impl.d,
9919 			RGB,
9920 			0,
9921 			&attrs
9922 		);
9923 
9924 		XRenderComposite(
9925 			XDisplayConnection.get,
9926 			3, // PictOpOver
9927 			handle,
9928 			None,
9929 			pic,
9930 			0, // src
9931 			0,
9932 			0, // mask
9933 			0,
9934 			10, // dest
9935 			10,
9936 			100, // width
9937 			100
9938 		);
9939 
9940 		/+
9941 		XRenderFreePicture(
9942 			XDisplayConnection.get,
9943 			pic
9944 		);
9945 
9946 		XRenderFreePicture(
9947 			XDisplayConnection.get,
9948 			fill
9949 		);
9950 		+/
9951 		// on Windows you can stretch but Xrender still can't :(
9952 	}
9953 
9954 	static XRenderPictFormat* RGB;
9955 	static XRenderPictFormat* RGBA;
9956 	static void repopulateX() {
9957 		auto display = XDisplayConnection.get;
9958 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
9959 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
9960 	}
9961 
9962 	XPixmap pixmap;
9963 	Picture handle;
9964 }
9965 
9966 ///
9967 interface CapableOfBeingDrawnUpon {
9968 	///
9969 	ScreenPainter draw();
9970 	///
9971 	int width();
9972 	///
9973 	int height();
9974 	protected ScreenPainterImplementation* activeScreenPainter();
9975 	protected void activeScreenPainter(ScreenPainterImplementation*);
9976 	bool closed();
9977 
9978 	void delegate() paintingFinishedDg();
9979 
9980 	/// Be warned: this can be a very slow operation
9981 	TrueColorImage takeScreenshot();
9982 }
9983 
9984 /// Flushes any pending gui buffers. Necessary if you are using with_eventloop with X - flush after you create your windows but before you call [arsd.eventloop.loop].
9985 void flushGui() {
9986 	version(X11) {
9987 		auto dpy = XDisplayConnection.get();
9988 		XLockDisplay(dpy);
9989 		scope(exit) XUnlockDisplay(dpy);
9990 		XFlush(dpy);
9991 	}
9992 }
9993 
9994 /++
9995 	Runs the given code in the GUI thread when its event loop
9996 	is available, blocking until it completes. This allows you
9997 	to create and manipulate windows from another thread without
9998 	invoking undefined behavior.
9999 
10000 	If this is the gui thread, it runs the code immediately.
10001 
10002 	If no gui thread exists yet, the current thread is assumed
10003 	to be it. Attempting to create windows or run the event loop
10004 	in any other thread will cause an assertion failure.
10005 
10006 
10007 	$(TIP
10008 		Did you know you can use UFCS on delegate literals?
10009 
10010 		() {
10011 			// code here
10012 		}.runInGuiThread;
10013 	)
10014 
10015 	Returns:
10016 		`true` if the function was called, `false` if it was not.
10017 		The function may not be called because the gui thread had
10018 		already terminated by the time you called this.
10019 
10020 	History:
10021 		Added April 10, 2020 (v7.2.0)
10022 
10023 		Return value added and implementation tweaked to avoid locking
10024 		at program termination on February 24, 2021 (v9.2.1).
10025 +/
10026 bool runInGuiThread(scope void delegate() dg) @trusted {
10027 	claimGuiThread();
10028 
10029 	if(thisIsGuiThread) {
10030 		dg();
10031 		return true;
10032 	}
10033 
10034 	if(guiThreadTerminating)
10035 		return false;
10036 
10037 	import core.sync.semaphore;
10038 	static Semaphore sc;
10039 	if(sc is null)
10040 		sc = new Semaphore();
10041 
10042 	static RunQueueMember* rqm;
10043 	if(rqm is null)
10044 		rqm = new RunQueueMember;
10045 	rqm.dg = cast(typeof(rqm.dg)) dg;
10046 	rqm.signal = sc;
10047 	rqm.thrown = null;
10048 
10049 	synchronized(runInGuiThreadLock) {
10050 		runInGuiThreadQueue ~= rqm;
10051 	}
10052 
10053 	if(!SimpleWindow.eventWakeUp())
10054 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10055 
10056 	rqm.signal.wait();
10057 	auto t = rqm.thrown;
10058 
10059 	if(t)
10060 		throw t;
10061 
10062 	return true;
10063 }
10064 
10065 // note it runs sync if this is the gui thread....
10066 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10067 	claimGuiThread();
10068 
10069 	try {
10070 
10071 		if(thisIsGuiThread) {
10072 			dg();
10073 			return;
10074 		}
10075 
10076 		if(guiThreadTerminating)
10077 			return;
10078 
10079 		RunQueueMember* rqm = new RunQueueMember;
10080 		rqm.dg = cast(typeof(rqm.dg)) dg;
10081 		rqm.signal = null;
10082 		rqm.thrown = null;
10083 
10084 		synchronized(runInGuiThreadLock) {
10085 			runInGuiThreadQueue ~= rqm;
10086 		}
10087 
10088 		if(!SimpleWindow.eventWakeUp())
10089 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10090 	} catch(Exception e) {
10091 		if(handleError)
10092 			handleError(e);
10093 	}
10094 }
10095 
10096 private void runPendingRunInGuiThreadDelegates() {
10097 	more:
10098 	RunQueueMember* next;
10099 	synchronized(runInGuiThreadLock) {
10100 		if(runInGuiThreadQueue.length) {
10101 			next = runInGuiThreadQueue[0];
10102 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10103 		} else {
10104 			next = null;
10105 		}
10106 	}
10107 
10108 	if(next) {
10109 		try {
10110 			next.dg();
10111 			next.thrown = null;
10112 		} catch(Throwable t) {
10113 			next.thrown = t;
10114 		}
10115 
10116 		if(next.signal)
10117 			next.signal.notify();
10118 
10119 		goto more;
10120 	}
10121 }
10122 
10123 private void claimGuiThread() nothrow {
10124 	import core.atomic;
10125 	if(cas(&guiThreadExists_, false, true))
10126 		thisIsGuiThread = true;
10127 }
10128 
10129 private struct RunQueueMember {
10130 	void delegate() dg;
10131 	import core.sync.semaphore;
10132 	Semaphore signal;
10133 	Throwable thrown;
10134 }
10135 
10136 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10137 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10138 private bool thisIsGuiThread = false;
10139 private shared bool guiThreadExists_ = false;
10140 private shared bool guiThreadTerminating = false;
10141 
10142 /++
10143 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10144 	event loop. All windows must be exclusively created and managed by a single thread.
10145 
10146 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10147 	when you call one of its constructors.
10148 
10149 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10150 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10151 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10152 
10153 	The reason this function is available is in case you want to message pass between a gui
10154 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10155 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10156 
10157 	History:
10158 		Added December 3, 2021 (dub v10.5)
10159 +/
10160 public bool guiThreadExists() {
10161 	return guiThreadExists_;
10162 }
10163 
10164 /++
10165 	Returns `true` if this thread is either running or set to be running the
10166 	simpledisplay.d gui core event loop because it owns windows.
10167 
10168 	It is important to keep gui-related functionality in the right thread, so you will
10169 	want to `runInGuiThread` when you call them (with some specific exceptions called
10170 	out in those specific functions' documentation). Notably, all windows must be
10171 	created and managed only from the gui thread.
10172 
10173 	Will return false if simpledisplay's other functions haven't been called
10174 	yet; check [guiThreadExists] in addition to this.
10175 
10176 	History:
10177 		Added December 3, 2021 (dub v10.5)
10178 +/
10179 public bool thisThreadRunningGui() {
10180 	return thisIsGuiThread;
10181 }
10182 
10183 /++
10184 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10185 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10186 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10187 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10188 	file instead if you are in one of those situations).
10189 
10190 	It does not support outputting very many types; just strings and ints are likely to actually work.
10191 
10192 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10193 	is unspecified meaning I can change it at any time. The only point of this function is to help
10194 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10195 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10196 	in those contexts.
10197 
10198 	$(WARNING
10199 		I reserve the right to change this function at any time. You can use it if it helps you
10200 		but do not rely on it for anything permanent.
10201 	)
10202 
10203 	History:
10204 		Added December 3, 2021. Not formally supported under any stable tag.
10205 +/
10206 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10207 	try {
10208 		version(Windows) {
10209 			import core.sys.windows.wincon;
10210 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10211 				AllocConsole();
10212 			const(char)* fn = "CONOUT$";
10213 		} else version(Posix) {
10214 			const(char)* fn = "/dev/tty";
10215 		} else static assert(0, "Function not implemented for your system");
10216 
10217 		if(fileOverride.length)
10218 			fn = fileOverride.ptr;
10219 
10220 		import core.stdc.stdio;
10221 		auto fp = fopen(fn, "wt");
10222 		if(fp is null) return;
10223 		scope(exit) fclose(fp);
10224 
10225 		string str;
10226 		foreach(item; t) {
10227 			static if(is(typeof(item) : const(char)[]))
10228 				str ~= item;
10229 			else
10230 				str ~= toInternal!string(item);
10231 			str ~= " ";
10232 		}
10233 		str ~= "\n";
10234 
10235 		fwrite(str.ptr, 1, str.length, fp);
10236 		fflush(fp);
10237 	} catch(Exception e) {
10238 		// sorry no hope
10239 	}
10240 }
10241 
10242 private void guiThreadFinalize() {
10243 	assert(thisIsGuiThread);
10244 
10245 	guiThreadTerminating = true; // don't add any more from this point on
10246 	runPendingRunInGuiThreadDelegates();
10247 }
10248 
10249 /+
10250 interface IPromise {
10251 	void reportProgress(int current, int max, string message);
10252 
10253 	/+ // not formally in cuz of templates but still
10254 	IPromise Then();
10255 	IPromise Catch();
10256 	IPromise Finally();
10257 	+/
10258 }
10259 
10260 /+
10261 	auto promise = async({ ... });
10262 	promise.Then(whatever).
10263 		Then(whateverelse).
10264 		Catch((exception) { });
10265 
10266 
10267 	A promise is run inside a fiber and it looks something like:
10268 
10269 	try {
10270 		auto res = whatever();
10271 		auto res2 = whateverelse(res);
10272 	} catch(Exception e) {
10273 		{ }(e);
10274 	}
10275 
10276 	When a thing succeeds, it is passed as an arg to the next
10277 +/
10278 class Promise(T) : IPromise {
10279 	auto Then() { return null; }
10280 	auto Catch() { return null; }
10281 	auto Finally() { return null; }
10282 
10283 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10284 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10285 	T await();
10286 }
10287 
10288 interface Task {
10289 }
10290 
10291 interface Resolvable(T) : Task {
10292 	void run();
10293 
10294 	void resolve(T);
10295 
10296 	Resolvable!T then(void delegate(T)); // returns a new promise
10297 	Resolvable!T error(Throwable); // js catch
10298 	Resolvable!T completed(); // js finally
10299 
10300 }
10301 
10302 /++
10303 	Runs `work` in a helper thread and sends its return value back to the main gui
10304 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10305 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10306 	kill the program.
10307 
10308 	You can call reportProgress(position, max, message) to update your parent window
10309 	on your progress.
10310 
10311 	I should also use `shared` methods. FIXME
10312 
10313 	History:
10314 		Added March 6, 2021 (dub version 9.3).
10315 +/
10316 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10317 	uponCompletion(work(null));
10318 }
10319 
10320 +/
10321 
10322 /// Used internal to dispatch events to various classes.
10323 interface CapableOfHandlingNativeEvent {
10324 	NativeEventHandler getNativeEventHandler();
10325 
10326 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10327 
10328 	version(X11) {
10329 		// if this is impossible, you are allowed to just throw from it
10330 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10331 		void recreateAfterDisconnect();
10332 		// discard any *connection specific* state, but keep enough that you
10333 		// can be recreated if possible. discardConnectionState() is always called immediately
10334 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10335 		// you need initialization order
10336 		void discardConnectionState();
10337 	}
10338 }
10339 
10340 version(X11)
10341 /++
10342 	State of keys on mouse events, especially motion.
10343 
10344 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10345 +/
10346 enum ModifierState : uint {
10347 	shift = 1, ///
10348 	capsLock = 2, ///
10349 	ctrl = 4, ///
10350 	alt = 8, /// Not always available on Windows
10351 	windows = 64, /// ditto
10352 	numLock = 16, ///
10353 
10354 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10355 	middleButtonDown = 512, /// ditto
10356 	rightButtonDown = 1024, /// ditto
10357 }
10358 else version(Windows)
10359 /// ditto
10360 enum ModifierState : uint {
10361 	shift = 4, ///
10362 	ctrl = 8, ///
10363 
10364 	// i'm not sure if the next two are available
10365 	alt = 256, /// not always available on Windows
10366 	windows = 512, /// ditto
10367 
10368 	capsLock = 1024, ///
10369 	numLock = 2048, ///
10370 
10371 	leftButtonDown = 1, /// not available on key events
10372 	middleButtonDown = 16, /// ditto
10373 	rightButtonDown = 2, /// ditto
10374 
10375 	backButtonDown = 0x20, /// not available on X
10376 	forwardButtonDown = 0x40, /// ditto
10377 }
10378 else version(OSXCocoa)
10379 // FIXME FIXME NotYetImplementedException
10380 enum ModifierState : uint {
10381 	shift = 1, ///
10382 	capsLock = 2, ///
10383 	ctrl = 4, ///
10384 	alt = 8, /// Not always available on Windows
10385 	windows = 64, /// ditto
10386 	numLock = 16, ///
10387 
10388 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10389 	middleButtonDown = 512, /// ditto
10390 	rightButtonDown = 1024, /// ditto
10391 }
10392 
10393 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10394 enum MouseButton : int {
10395 	none = 0,
10396 	left = 1, ///
10397 	right = 2, ///
10398 	middle = 4, ///
10399 	wheelUp = 8, ///
10400 	wheelDown = 16, ///
10401 	backButton = 32, /// often found on the thumb and used for back in browsers
10402 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10403 }
10404 
10405 version(X11) {
10406 	// FIXME: match ASCII whenever we can. Most of it is already there,
10407 	// but there's a few exceptions and mismatches with Windows
10408 
10409 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10410 	enum Key {
10411 		Escape = 0xff1b, ///
10412 		F1 = 0xffbe, ///
10413 		F2 = 0xffbf, ///
10414 		F3 = 0xffc0, ///
10415 		F4 = 0xffc1, ///
10416 		F5 = 0xffc2, ///
10417 		F6 = 0xffc3, ///
10418 		F7 = 0xffc4, ///
10419 		F8 = 0xffc5, ///
10420 		F9 = 0xffc6, ///
10421 		F10 = 0xffc7, ///
10422 		F11 = 0xffc8, ///
10423 		F12 = 0xffc9, ///
10424 		PrintScreen = 0xff61, ///
10425 		ScrollLock = 0xff14, ///
10426 		Pause = 0xff13, ///
10427 		Grave = 0x60, /// The $(BACKTICK) ~ key
10428 		// number keys across the top of the keyboard
10429 		N1 = 0x31, /// Number key atop the keyboard
10430 		N2 = 0x32, ///
10431 		N3 = 0x33, ///
10432 		N4 = 0x34, ///
10433 		N5 = 0x35, ///
10434 		N6 = 0x36, ///
10435 		N7 = 0x37, ///
10436 		N8 = 0x38, ///
10437 		N9 = 0x39, ///
10438 		N0 = 0x30, ///
10439 		Dash = 0x2d, ///
10440 		Equals = 0x3d, ///
10441 		Backslash = 0x5c, /// The \ | key
10442 		Backspace = 0xff08, ///
10443 		Insert = 0xff63, ///
10444 		Home = 0xff50, ///
10445 		PageUp = 0xff55, ///
10446 		Delete = 0xffff, ///
10447 		End = 0xff57, ///
10448 		PageDown = 0xff56, ///
10449 		Up = 0xff52, ///
10450 		Down = 0xff54, ///
10451 		Left = 0xff51, ///
10452 		Right = 0xff53, ///
10453 
10454 		Tab = 0xff09, ///
10455 		Q = 0x71, ///
10456 		W = 0x77, ///
10457 		E = 0x65, ///
10458 		R = 0x72, ///
10459 		T = 0x74, ///
10460 		Y = 0x79, ///
10461 		U = 0x75, ///
10462 		I = 0x69, ///
10463 		O = 0x6f, ///
10464 		P = 0x70, ///
10465 		LeftBracket = 0x5b, /// the [ { key
10466 		RightBracket = 0x5d, /// the ] } key
10467 		CapsLock = 0xffe5, ///
10468 		A = 0x61, ///
10469 		S = 0x73, ///
10470 		D = 0x64, ///
10471 		F = 0x66, ///
10472 		G = 0x67, ///
10473 		H = 0x68, ///
10474 		J = 0x6a, ///
10475 		K = 0x6b, ///
10476 		L = 0x6c, ///
10477 		Semicolon = 0x3b, ///
10478 		Apostrophe = 0x27, ///
10479 		Enter = 0xff0d, ///
10480 		Shift = 0xffe1, ///
10481 		Z = 0x7a, ///
10482 		X = 0x78, ///
10483 		C = 0x63, ///
10484 		V = 0x76, ///
10485 		B = 0x62, ///
10486 		N = 0x6e, ///
10487 		M = 0x6d, ///
10488 		Comma = 0x2c, ///
10489 		Period = 0x2e, ///
10490 		Slash = 0x2f, /// the / ? key
10491 		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
10492 		Ctrl = 0xffe3, ///
10493 		Windows = 0xffeb, ///
10494 		Alt = 0xffe9, ///
10495 		Space = 0x20, ///
10496 		Alt_r = 0xffea, /// ditto of shift_r
10497 		Windows_r = 0xffec, ///
10498 		Menu = 0xff67, ///
10499 		Ctrl_r = 0xffe4, ///
10500 
10501 		NumLock = 0xff7f, ///
10502 		Divide = 0xffaf, /// The / key on the number pad
10503 		Multiply = 0xffaa, /// The * key on the number pad
10504 		Minus = 0xffad, /// The - key on the number pad
10505 		Plus = 0xffab, /// The + key on the number pad
10506 		PadEnter = 0xff8d, /// Numberpad enter key
10507 		Pad1 = 0xff9c, /// Numberpad keys
10508 		Pad2 = 0xff99, ///
10509 		Pad3 = 0xff9b, ///
10510 		Pad4 = 0xff96, ///
10511 		Pad5 = 0xff9d, ///
10512 		Pad6 = 0xff98, ///
10513 		Pad7 = 0xff95, ///
10514 		Pad8 = 0xff97, ///
10515 		Pad9 = 0xff9a, ///
10516 		Pad0 = 0xff9e, ///
10517 		PadDot = 0xff9f, ///
10518 	}
10519 } else version(Windows) {
10520 	// the character here is for en-us layouts and for illustration only
10521 	// if you actually want to get characters, wait for character events
10522 	// (the argument to your event handler is simply a dchar)
10523 	// those will be converted by the OS for the right locale.
10524 
10525 	enum Key {
10526 		Escape = 0x1b,
10527 		F1 = 0x70,
10528 		F2 = 0x71,
10529 		F3 = 0x72,
10530 		F4 = 0x73,
10531 		F5 = 0x74,
10532 		F6 = 0x75,
10533 		F7 = 0x76,
10534 		F8 = 0x77,
10535 		F9 = 0x78,
10536 		F10 = 0x79,
10537 		F11 = 0x7a,
10538 		F12 = 0x7b,
10539 		PrintScreen = 0x2c,
10540 		ScrollLock = 0x91,
10541 		Pause = 0x13,
10542 		Grave = 0xc0,
10543 		// number keys across the top of the keyboard
10544 		N1 = 0x31,
10545 		N2 = 0x32,
10546 		N3 = 0x33,
10547 		N4 = 0x34,
10548 		N5 = 0x35,
10549 		N6 = 0x36,
10550 		N7 = 0x37,
10551 		N8 = 0x38,
10552 		N9 = 0x39,
10553 		N0 = 0x30,
10554 		Dash = 0xbd,
10555 		Equals = 0xbb,
10556 		Backslash = 0xdc,
10557 		Backspace = 0x08,
10558 		Insert = 0x2d,
10559 		Home = 0x24,
10560 		PageUp = 0x21,
10561 		Delete = 0x2e,
10562 		End = 0x23,
10563 		PageDown = 0x22,
10564 		Up = 0x26,
10565 		Down = 0x28,
10566 		Left = 0x25,
10567 		Right = 0x27,
10568 
10569 		Tab = 0x09,
10570 		Q = 0x51,
10571 		W = 0x57,
10572 		E = 0x45,
10573 		R = 0x52,
10574 		T = 0x54,
10575 		Y = 0x59,
10576 		U = 0x55,
10577 		I = 0x49,
10578 		O = 0x4f,
10579 		P = 0x50,
10580 		LeftBracket = 0xdb,
10581 		RightBracket = 0xdd,
10582 		CapsLock = 0x14,
10583 		A = 0x41,
10584 		S = 0x53,
10585 		D = 0x44,
10586 		F = 0x46,
10587 		G = 0x47,
10588 		H = 0x48,
10589 		J = 0x4a,
10590 		K = 0x4b,
10591 		L = 0x4c,
10592 		Semicolon = 0xba,
10593 		Apostrophe = 0xde,
10594 		Enter = 0x0d,
10595 		Shift = 0x10,
10596 		Z = 0x5a,
10597 		X = 0x58,
10598 		C = 0x43,
10599 		V = 0x56,
10600 		B = 0x42,
10601 		N = 0x4e,
10602 		M = 0x4d,
10603 		Comma = 0xbc,
10604 		Period = 0xbe,
10605 		Slash = 0xbf,
10606 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10607 		Ctrl = 0x11,
10608 		Windows = 0x5b,
10609 		Alt = -5, // FIXME
10610 		Space = 0x20,
10611 		Alt_r = 0xffea, // ditto of shift_r
10612 		Windows_r = 0x5c, // ditto of shift_r
10613 		Menu = 0x5d,
10614 		Ctrl_r = 0xa3, // ditto of shift_r
10615 
10616 		NumLock = 0x90,
10617 		Divide = 0x6f,
10618 		Multiply = 0x6a,
10619 		Minus = 0x6d,
10620 		Plus = 0x6b,
10621 		PadEnter = -8, // FIXME
10622 		Pad1 = 0x61,
10623 		Pad2 = 0x62,
10624 		Pad3 = 0x63,
10625 		Pad4 = 0x64,
10626 		Pad5 = 0x65,
10627 		Pad6 = 0x66,
10628 		Pad7 = 0x67,
10629 		Pad8 = 0x68,
10630 		Pad9 = 0x69,
10631 		Pad0 = 0x60,
10632 		PadDot = 0x6e,
10633 	}
10634 
10635 	// I'm keeping this around for reference purposes
10636 	// ideally all these buttons will be listed for all platforms,
10637 	// but now now I'm just focusing on my US keyboard
10638 	version(none)
10639 	enum Key {
10640 		LBUTTON = 0x01,
10641 		RBUTTON = 0x02,
10642 		CANCEL = 0x03,
10643 		MBUTTON = 0x04,
10644 		//static if (_WIN32_WINNT > =  0x500) {
10645 		XBUTTON1 = 0x05,
10646 		XBUTTON2 = 0x06,
10647 		//}
10648 		BACK = 0x08,
10649 		TAB = 0x09,
10650 		CLEAR = 0x0C,
10651 		RETURN = 0x0D,
10652 		SHIFT = 0x10,
10653 		CONTROL = 0x11,
10654 		MENU = 0x12,
10655 		PAUSE = 0x13,
10656 		CAPITAL = 0x14,
10657 		KANA = 0x15,
10658 		HANGEUL = 0x15,
10659 		HANGUL = 0x15,
10660 		JUNJA = 0x17,
10661 		FINAL = 0x18,
10662 		HANJA = 0x19,
10663 		KANJI = 0x19,
10664 		ESCAPE = 0x1B,
10665 		CONVERT = 0x1C,
10666 		NONCONVERT = 0x1D,
10667 		ACCEPT = 0x1E,
10668 		MODECHANGE = 0x1F,
10669 		SPACE = 0x20,
10670 		PRIOR = 0x21,
10671 		NEXT = 0x22,
10672 		END = 0x23,
10673 		HOME = 0x24,
10674 		LEFT = 0x25,
10675 		UP = 0x26,
10676 		RIGHT = 0x27,
10677 		DOWN = 0x28,
10678 		SELECT = 0x29,
10679 		PRINT = 0x2A,
10680 		EXECUTE = 0x2B,
10681 		SNAPSHOT = 0x2C,
10682 		INSERT = 0x2D,
10683 		DELETE = 0x2E,
10684 		HELP = 0x2F,
10685 		LWIN = 0x5B,
10686 		RWIN = 0x5C,
10687 		APPS = 0x5D,
10688 		SLEEP = 0x5F,
10689 		NUMPAD0 = 0x60,
10690 		NUMPAD1 = 0x61,
10691 		NUMPAD2 = 0x62,
10692 		NUMPAD3 = 0x63,
10693 		NUMPAD4 = 0x64,
10694 		NUMPAD5 = 0x65,
10695 		NUMPAD6 = 0x66,
10696 		NUMPAD7 = 0x67,
10697 		NUMPAD8 = 0x68,
10698 		NUMPAD9 = 0x69,
10699 		MULTIPLY = 0x6A,
10700 		ADD = 0x6B,
10701 		SEPARATOR = 0x6C,
10702 		SUBTRACT = 0x6D,
10703 		DECIMAL = 0x6E,
10704 		DIVIDE = 0x6F,
10705 		F1 = 0x70,
10706 		F2 = 0x71,
10707 		F3 = 0x72,
10708 		F4 = 0x73,
10709 		F5 = 0x74,
10710 		F6 = 0x75,
10711 		F7 = 0x76,
10712 		F8 = 0x77,
10713 		F9 = 0x78,
10714 		F10 = 0x79,
10715 		F11 = 0x7A,
10716 		F12 = 0x7B,
10717 		F13 = 0x7C,
10718 		F14 = 0x7D,
10719 		F15 = 0x7E,
10720 		F16 = 0x7F,
10721 		F17 = 0x80,
10722 		F18 = 0x81,
10723 		F19 = 0x82,
10724 		F20 = 0x83,
10725 		F21 = 0x84,
10726 		F22 = 0x85,
10727 		F23 = 0x86,
10728 		F24 = 0x87,
10729 		NUMLOCK = 0x90,
10730 		SCROLL = 0x91,
10731 		LSHIFT = 0xA0,
10732 		RSHIFT = 0xA1,
10733 		LCONTROL = 0xA2,
10734 		RCONTROL = 0xA3,
10735 		LMENU = 0xA4,
10736 		RMENU = 0xA5,
10737 		//static if (_WIN32_WINNT > =  0x500) {
10738 		BROWSER_BACK = 0xA6,
10739 		BROWSER_FORWARD = 0xA7,
10740 		BROWSER_REFRESH = 0xA8,
10741 		BROWSER_STOP = 0xA9,
10742 		BROWSER_SEARCH = 0xAA,
10743 		BROWSER_FAVORITES = 0xAB,
10744 		BROWSER_HOME = 0xAC,
10745 		VOLUME_MUTE = 0xAD,
10746 		VOLUME_DOWN = 0xAE,
10747 		VOLUME_UP = 0xAF,
10748 		MEDIA_NEXT_TRACK = 0xB0,
10749 		MEDIA_PREV_TRACK = 0xB1,
10750 		MEDIA_STOP = 0xB2,
10751 		MEDIA_PLAY_PAUSE = 0xB3,
10752 		LAUNCH_MAIL = 0xB4,
10753 		LAUNCH_MEDIA_SELECT = 0xB5,
10754 		LAUNCH_APP1 = 0xB6,
10755 		LAUNCH_APP2 = 0xB7,
10756 		//}
10757 		OEM_1 = 0xBA,
10758 		//static if (_WIN32_WINNT > =  0x500) {
10759 		OEM_PLUS = 0xBB,
10760 		OEM_COMMA = 0xBC,
10761 		OEM_MINUS = 0xBD,
10762 		OEM_PERIOD = 0xBE,
10763 		//}
10764 		OEM_2 = 0xBF,
10765 		OEM_3 = 0xC0,
10766 		OEM_4 = 0xDB,
10767 		OEM_5 = 0xDC,
10768 		OEM_6 = 0xDD,
10769 		OEM_7 = 0xDE,
10770 		OEM_8 = 0xDF,
10771 		//static if (_WIN32_WINNT > =  0x500) {
10772 		OEM_102 = 0xE2,
10773 		//}
10774 		PROCESSKEY = 0xE5,
10775 		//static if (_WIN32_WINNT > =  0x500) {
10776 		PACKET = 0xE7,
10777 		//}
10778 		ATTN = 0xF6,
10779 		CRSEL = 0xF7,
10780 		EXSEL = 0xF8,
10781 		EREOF = 0xF9,
10782 		PLAY = 0xFA,
10783 		ZOOM = 0xFB,
10784 		NONAME = 0xFC,
10785 		PA1 = 0xFD,
10786 		OEM_CLEAR = 0xFE,
10787 	}
10788 
10789 } else version(OSXCocoa) {
10790 	// FIXME
10791 	enum Key {
10792 		Escape = 0x1b,
10793 		F1 = 0x70,
10794 		F2 = 0x71,
10795 		F3 = 0x72,
10796 		F4 = 0x73,
10797 		F5 = 0x74,
10798 		F6 = 0x75,
10799 		F7 = 0x76,
10800 		F8 = 0x77,
10801 		F9 = 0x78,
10802 		F10 = 0x79,
10803 		F11 = 0x7a,
10804 		F12 = 0x7b,
10805 		PrintScreen = 0x2c,
10806 		ScrollLock = -2, // FIXME
10807 		Pause = -3, // FIXME
10808 		Grave = 0xc0,
10809 		// number keys across the top of the keyboard
10810 		N1 = 0x31,
10811 		N2 = 0x32,
10812 		N3 = 0x33,
10813 		N4 = 0x34,
10814 		N5 = 0x35,
10815 		N6 = 0x36,
10816 		N7 = 0x37,
10817 		N8 = 0x38,
10818 		N9 = 0x39,
10819 		N0 = 0x30,
10820 		Dash = 0xbd,
10821 		Equals = 0xbb,
10822 		Backslash = 0xdc,
10823 		Backspace = 0x08,
10824 		Insert = 0x2d,
10825 		Home = 0x24,
10826 		PageUp = 0x21,
10827 		Delete = 0x2e,
10828 		End = 0x23,
10829 		PageDown = 0x22,
10830 		Up = 0x26,
10831 		Down = 0x28,
10832 		Left = 0x25,
10833 		Right = 0x27,
10834 
10835 		Tab = 0x09,
10836 		Q = 0x51,
10837 		W = 0x57,
10838 		E = 0x45,
10839 		R = 0x52,
10840 		T = 0x54,
10841 		Y = 0x59,
10842 		U = 0x55,
10843 		I = 0x49,
10844 		O = 0x4f,
10845 		P = 0x50,
10846 		LeftBracket = 0xdb,
10847 		RightBracket = 0xdd,
10848 		CapsLock = 0x14,
10849 		A = 0x41,
10850 		S = 0x53,
10851 		D = 0x44,
10852 		F = 0x46,
10853 		G = 0x47,
10854 		H = 0x48,
10855 		J = 0x4a,
10856 		K = 0x4b,
10857 		L = 0x4c,
10858 		Semicolon = 0xba,
10859 		Apostrophe = 0xde,
10860 		Enter = 0x0d,
10861 		Shift = 0x10,
10862 		Z = 0x5a,
10863 		X = 0x58,
10864 		C = 0x43,
10865 		V = 0x56,
10866 		B = 0x42,
10867 		N = 0x4e,
10868 		M = 0x4d,
10869 		Comma = 0xbc,
10870 		Period = 0xbe,
10871 		Slash = 0xbf,
10872 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10873 		Ctrl = 0x11,
10874 		Windows = 0x5b,
10875 		Alt = -5, // FIXME
10876 		Space = 0x20,
10877 		Alt_r = 0xffea, // ditto of shift_r
10878 		Windows_r = -6, // FIXME
10879 		Menu = 0x5d,
10880 		Ctrl_r = -7, // FIXME
10881 
10882 		NumLock = 0x90,
10883 		Divide = 0x6f,
10884 		Multiply = 0x6a,
10885 		Minus = 0x6d,
10886 		Plus = 0x6b,
10887 		PadEnter = -8, // FIXME
10888 		// FIXME for the rest of these:
10889 		Pad1 = 0xff9c,
10890 		Pad2 = 0xff99,
10891 		Pad3 = 0xff9b,
10892 		Pad4 = 0xff96,
10893 		Pad5 = 0xff9d,
10894 		Pad6 = 0xff98,
10895 		Pad7 = 0xff95,
10896 		Pad8 = 0xff97,
10897 		Pad9 = 0xff9a,
10898 		Pad0 = 0xff9e,
10899 		PadDot = 0xff9f,
10900 	}
10901 
10902 }
10903 
10904 /* Additional utilities */
10905 
10906 
10907 Color fromHsl(real h, real s, real l) {
10908 	return arsd.color.fromHsl([h,s,l]);
10909 }
10910 
10911 
10912 
10913 /* ********** What follows is the system-specific implementations *********/
10914 version(Windows) {
10915 
10916 
10917 	// helpers for making HICONs from MemoryImages
10918 	class WindowsIcon {
10919 		struct Win32Icon(int colorCount) {
10920 		align(1):
10921 			uint biSize;
10922 			int biWidth;
10923 			int biHeight;
10924 			ushort biPlanes;
10925 			ushort biBitCount;
10926 			uint biCompression;
10927 			uint biSizeImage;
10928 			int biXPelsPerMeter;
10929 			int biYPelsPerMeter;
10930 			uint biClrUsed;
10931 			uint biClrImportant;
10932 			RGBQUAD[colorCount] biColors;
10933 			/* Pixels:
10934 			Uint8 pixels[]
10935 			*/
10936 			/* Mask:
10937 			Uint8 mask[]
10938 			*/
10939 
10940 			// FIXME: this sux, needs to be dynamic
10941 			ubyte[/*4096*/ 256*256*4 + 256*256/8] data;
10942 
10943 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
10944 				width = mi.width;
10945 				height = mi.height;
10946 
10947 				auto indexedImage = cast(IndexedImage) mi;
10948 				if(indexedImage is null)
10949 					indexedImage = quantize(mi.getAsTrueColorImage());
10950 
10951 				assert(width <= 256, "image too wide");
10952 				assert(height <= 256, "image too tall");
10953 				assert(width %8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy
10954 				assert(height %4 == 0, "image not multiple of 4 height");
10955 
10956 				int icon_plen = height*((width+3)&~3);
10957 				int icon_mlen = height*((((width+7)/8)+3)&~3);
10958 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
10959 
10960 				biSize = 40;
10961 				biWidth = width;
10962 				biHeight = height*2;
10963 				biPlanes = 1;
10964 				biBitCount = 8;
10965 				biSizeImage = icon_plen+icon_mlen;
10966 
10967 				int offset = 0;
10968 				int andOff = icon_plen * 8; // the and offset is in bits
10969 				for(int y = height - 1; y >= 0; y--) {
10970 					int off2 = y * width;
10971 					foreach(x; 0 .. width) {
10972 						const b = indexedImage.data[off2 + x];
10973 						data[offset] = b;
10974 						offset++;
10975 
10976 						const andBit = andOff % 8;
10977 						const andIdx = andOff / 8;
10978 						assert(b < indexedImage.palette.length);
10979 						// this is anded to the destination, since and 0 means erase,
10980 						// we want that to  be opaque, and 1 for transparent
10981 						auto transparent = (indexedImage.palette[b].a <= 127);
10982 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
10983 
10984 						andOff++;
10985 					}
10986 
10987 					andOff += andOff % 32;
10988 				}
10989 
10990 				foreach(idx, entry; indexedImage.palette) {
10991 					if(entry.a > 127) {
10992 						biColors[idx].rgbBlue = entry.b;
10993 						biColors[idx].rgbGreen = entry.g;
10994 						biColors[idx].rgbRed = entry.r;
10995 					} else {
10996 						biColors[idx].rgbBlue = 255;
10997 						biColors[idx].rgbGreen = 255;
10998 						biColors[idx].rgbRed = 255;
10999 					}
11000 				}
11001 
11002 				/*
11003 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
11004 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
11005 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
11006 				auto pngMap = fetchPaletteWin32(png);
11007 				biColors[0..pngMap.length] = pngMap[];
11008 				*/
11009 			}
11010 		}
11011 
11012 
11013 		Win32Icon!(256) icon_win32;
11014 
11015 
11016 		this(MemoryImage mi) {
11017 			int icon_len, width, height;
11018 
11019 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
11020 
11021 			/*
11022 			PNG* png = readPnpngData);
11023 			PNGHeader pngh = getHeader(png);
11024 			void* icon_win32;
11025 			if(pngh.depth == 4) {
11026 				auto i = new Win32Icon!(16);
11027 				i.fromPNG(png, pngh, icon_len, width, height);
11028 				icon_win32 = i;
11029 			}
11030 			else if(pngh.depth == 8) {
11031 				auto i = new Win32Icon!(256);
11032 				i.fromPNG(png, pngh, icon_len, width, height);
11033 				icon_win32 = i;
11034 			} else assert(0);
11035 			*/
11036 
11037 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
11038 
11039 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11040 		}
11041 
11042 		~this() {
11043 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11044 			DestroyIcon(hIcon);
11045 		}
11046 
11047 		HICON hIcon;
11048 	}
11049 
11050 
11051 
11052 
11053 
11054 
11055 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11056 	alias HWND NativeWindowHandle;
11057 
11058 	extern(Windows)
11059 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11060 		try {
11061 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11062 				// it returns zero if the message is handled, so we won't do anything more there
11063 				// do I like that though?
11064 				int mustReturn;
11065 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11066 				if(mustReturn)
11067 					return ret;
11068 			}
11069 
11070 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11071 				if(window.getNativeEventHandler !is null) {
11072 					int mustReturn;
11073 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11074 					if(mustReturn)
11075 						return ret;
11076 				}
11077 				if(auto w = cast(SimpleWindow) (*window))
11078 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11079 				else
11080 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11081 			} else {
11082 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11083 			}
11084 		} catch (Exception e) {
11085 			try {
11086 				sdpy_abort(e);
11087 				return 0;
11088 			} catch(Exception e) { assert(0); }
11089 		}
11090 	}
11091 
11092 	void sdpy_abort(Throwable e) nothrow {
11093 		try
11094 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11095 		catch(Exception e)
11096 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11097 		ExitProcess(1);
11098 	}
11099 
11100 	mixin template NativeScreenPainterImplementation() {
11101 		HDC hdc;
11102 		HWND hwnd;
11103 		//HDC windowHdc;
11104 		HBITMAP oldBmp;
11105 
11106 		void create(NativeWindowHandle window) {
11107 			hwnd = window;
11108 
11109 			if(auto sw = cast(SimpleWindow) this.window) {
11110 				// drawing on a window, double buffer
11111 				auto windowHdc = GetDC(hwnd);
11112 
11113 				auto buffer = sw.impl.buffer;
11114 				if(buffer is null) {
11115 					hdc = windowHdc;
11116 					windowDc = true;
11117 				} else {
11118 					hdc = CreateCompatibleDC(windowHdc);
11119 
11120 					ReleaseDC(hwnd, windowHdc);
11121 
11122 					oldBmp = SelectObject(hdc, buffer);
11123 				}
11124 			} else {
11125 				// drawing on something else, draw directly
11126 				hdc = CreateCompatibleDC(null);
11127 				SelectObject(hdc, window);
11128 			}
11129 
11130 			// X doesn't draw a text background, so neither should we
11131 			SetBkMode(hdc, TRANSPARENT);
11132 
11133 			ensureDefaultFontLoaded();
11134 
11135 			if(defaultGuiFont) {
11136 				SelectObject(hdc, defaultGuiFont);
11137 				// DeleteObject(defaultGuiFont);
11138 			}
11139 		}
11140 
11141 		static HFONT defaultGuiFont;
11142 		static void ensureDefaultFontLoaded() {
11143 			static bool triedDefaultGuiFont = false;
11144 			if(!triedDefaultGuiFont) {
11145 				NONCLIENTMETRICS params;
11146 				params.cbSize = params.sizeof;
11147 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11148 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11149 				}
11150 				triedDefaultGuiFont = true;
11151 			}
11152 		}
11153 
11154 		private OperatingSystemFont _activeFont;
11155 
11156 		void setFont(OperatingSystemFont font) {
11157 			_activeFont = font;
11158 			if(font && font.font) {
11159 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11160 					// error... how to handle tho?
11161 				} else {
11162 
11163 				}
11164 			}
11165 			else if(defaultGuiFont)
11166 				SelectObject(hdc, defaultGuiFont);
11167 		}
11168 
11169 		arsd.color.Rectangle _clipRectangle;
11170 
11171 		void setClipRectangle(int x, int y, int width, int height) {
11172 			auto old = _clipRectangle;
11173 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11174 			if(old == _clipRectangle)
11175 				return;
11176 
11177 			if(width == 0 || height == 0) {
11178 				SelectClipRgn(hdc, null);
11179 			} else {
11180 				auto region = CreateRectRgn(x, y, x + width, y + height);
11181 				SelectClipRgn(hdc, region);
11182 				DeleteObject(region);
11183 			}
11184 		}
11185 
11186 
11187 		// just because we can on Windows...
11188 		//void create(Image image);
11189 
11190 		void invalidateRect(Rectangle invalidRect) {
11191 			RECT rect;
11192 			rect.left = invalidRect.left;
11193 			rect.right = invalidRect.right;
11194 			rect.top = invalidRect.top;
11195 			rect.bottom = invalidRect.bottom;
11196 			InvalidateRect(hwnd, &rect, false);
11197 		}
11198 		bool manualInvalidations;
11199 
11200 		void dispose() {
11201 			// FIXME: this.window.width/height is probably wrong
11202 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11203 			// ReleaseDC(hwnd, windowHdc);
11204 
11205 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11206 			if(cast(SimpleWindow) this.window) {
11207 				if(!manualInvalidations)
11208 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11209 			}
11210 
11211 			if(originalPen !is null)
11212 				SelectObject(hdc, originalPen);
11213 			if(currentPen !is null)
11214 				DeleteObject(currentPen);
11215 			if(originalBrush !is null)
11216 				SelectObject(hdc, originalBrush);
11217 			if(currentBrush !is null)
11218 				DeleteObject(currentBrush);
11219 
11220 			SelectObject(hdc, oldBmp);
11221 
11222 			if(windowDc)
11223 				ReleaseDC(hwnd, hdc);
11224 			else
11225 				DeleteDC(hdc);
11226 
11227 			if(window.paintingFinishedDg !is null)
11228 				window.paintingFinishedDg()();
11229 		}
11230 
11231 		bool windowDc;
11232 		HPEN originalPen;
11233 		HPEN currentPen;
11234 
11235 		Pen _activePen;
11236 
11237 		Color _outlineColor;
11238 
11239 		@property void pen(Pen p) {
11240 			_activePen = p;
11241 			_outlineColor = p.color;
11242 
11243 			HPEN pen;
11244 			if(p.color.a == 0) {
11245 				pen = GetStockObject(NULL_PEN);
11246 			} else {
11247 				int style = PS_SOLID;
11248 				final switch(p.style) {
11249 					case Pen.Style.Solid:
11250 						style = PS_SOLID;
11251 					break;
11252 					case Pen.Style.Dashed:
11253 						style = PS_DASH;
11254 					break;
11255 					case Pen.Style.Dotted:
11256 						style = PS_DOT;
11257 					break;
11258 				}
11259 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11260 			}
11261 			auto orig = SelectObject(hdc, pen);
11262 			if(originalPen is null)
11263 				originalPen = orig;
11264 
11265 			if(currentPen !is null)
11266 				DeleteObject(currentPen);
11267 
11268 			currentPen = pen;
11269 
11270 			// the outline is like a foreground since it's done that way on X
11271 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11272 
11273 		}
11274 
11275 		@property void rasterOp(RasterOp op) {
11276 			int mode;
11277 			final switch(op) {
11278 				case RasterOp.normal:
11279 					mode = R2_COPYPEN;
11280 				break;
11281 				case RasterOp.xor:
11282 					mode = R2_XORPEN;
11283 				break;
11284 			}
11285 			SetROP2(hdc, mode);
11286 		}
11287 
11288 		HBRUSH originalBrush;
11289 		HBRUSH currentBrush;
11290 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11291 		@property void fillColor(Color c) {
11292 			if(c == _fillColor)
11293 				return;
11294 			_fillColor = c;
11295 			HBRUSH brush;
11296 			if(c.a == 0) {
11297 				brush = GetStockObject(HOLLOW_BRUSH);
11298 			} else {
11299 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11300 			}
11301 			auto orig = SelectObject(hdc, brush);
11302 			if(originalBrush is null)
11303 				originalBrush = orig;
11304 
11305 			if(currentBrush !is null)
11306 				DeleteObject(currentBrush);
11307 
11308 			currentBrush = brush;
11309 
11310 			// background color is NOT set because X doesn't draw text backgrounds
11311 			//   SetBkColor(hdc, RGB(255, 255, 255));
11312 		}
11313 
11314 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11315 			BITMAP bm;
11316 
11317 			HDC hdcMem = CreateCompatibleDC(hdc);
11318 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11319 
11320 			GetObject(i.handle, bm.sizeof, &bm);
11321 
11322 			// or should I AlphaBlend!??!?!
11323 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11324 
11325 			SelectObject(hdcMem, hbmOld);
11326 			DeleteDC(hdcMem);
11327 		}
11328 
11329 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11330 			BITMAP bm;
11331 
11332 			HDC hdcMem = CreateCompatibleDC(hdc);
11333 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11334 
11335 			GetObject(s.handle, bm.sizeof, &bm);
11336 
11337 			version(CRuntime_DigitalMars) goto noalpha;
11338 
11339 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11340 			if(s.enableAlpha) {
11341 				auto dw = w ? w : bm.bmWidth;
11342 				auto dh = h ? h : bm.bmHeight;
11343 				BLENDFUNCTION bf;
11344 				bf.BlendOp = AC_SRC_OVER;
11345 				bf.SourceConstantAlpha = 255;
11346 				bf.AlphaFormat = AC_SRC_ALPHA;
11347 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11348 			} else {
11349 				noalpha:
11350 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11351 			}
11352 
11353 			SelectObject(hdcMem, hbmOld);
11354 			DeleteDC(hdcMem);
11355 		}
11356 
11357 		Size textSize(scope const(char)[] text) {
11358 			bool dummyX;
11359 			if(text.length == 0) {
11360 				text = " ";
11361 				dummyX = true;
11362 			}
11363 			RECT rect;
11364 			WCharzBuffer buffer = WCharzBuffer(text);
11365 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11366 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11367 		}
11368 
11369 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11370 			if(text.length && text[$-1] == '\n')
11371 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11372 			if(text.length && text[$-1] == '\r')
11373 				text = text[0 .. $-1];
11374 
11375 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11376 			if(x2 == 0 && y2 == 0) {
11377 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11378 			} else {
11379 				RECT rect;
11380 				rect.left = x;
11381 				rect.top = y;
11382 				rect.right = x2;
11383 				rect.bottom = y2;
11384 
11385 				uint mode = DT_LEFT;
11386 				if(alignment & TextAlignment.Right)
11387 					mode = DT_RIGHT;
11388 				else if(alignment & TextAlignment.Center)
11389 					mode = DT_CENTER;
11390 
11391 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11392 				if(alignment & TextAlignment.VerticalCenter)
11393 					mode |= DT_VCENTER | DT_SINGLELINE;
11394 
11395 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11396 			}
11397 
11398 			/*
11399 			uint mode;
11400 
11401 			if(alignment & TextAlignment.Center)
11402 				mode = TA_CENTER;
11403 
11404 			SetTextAlign(hdc, mode);
11405 			*/
11406 		}
11407 
11408 		int fontHeight() {
11409 			TEXTMETRIC metric;
11410 			if(GetTextMetricsW(hdc, &metric)) {
11411 				return metric.tmHeight;
11412 			}
11413 
11414 			return 16; // idk just guessing here, maybe we should throw
11415 		}
11416 
11417 		void drawPixel(int x, int y) {
11418 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11419 		}
11420 
11421 		// The basic shapes, outlined
11422 
11423 		void drawLine(int x1, int y1, int x2, int y2) {
11424 			MoveToEx(hdc, x1, y1, null);
11425 			LineTo(hdc, x2, y2);
11426 		}
11427 
11428 		void drawRectangle(int x, int y, int width, int height) {
11429 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11430 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11431 		}
11432 
11433 		/// Arguments are the points of the bounding rectangle
11434 		void drawEllipse(int x1, int y1, int x2, int y2) {
11435 			Ellipse(hdc, x1, y1, x2, y2);
11436 		}
11437 
11438 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11439 			if((start % (360*64)) == (finish % (360*64)))
11440 				drawEllipse(x1, y1, x1 + width, y1 + height);
11441 			else {
11442 				import core.stdc.math;
11443 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11444 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11445 
11446 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11447 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11448 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11449 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11450 
11451 				if(_activePen.color.a)
11452 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11453 				if(_fillColor.a)
11454 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11455 			}
11456 		}
11457 
11458 		void drawPolygon(Point[] vertexes) {
11459 			POINT[] points;
11460 			points.length = vertexes.length;
11461 
11462 			foreach(i, p; vertexes) {
11463 				points[i].x = p.x;
11464 				points[i].y = p.y;
11465 			}
11466 
11467 			Polygon(hdc, points.ptr, cast(int) points.length);
11468 		}
11469 	}
11470 
11471 
11472 	// Mix this into the SimpleWindow class
11473 	mixin template NativeSimpleWindowImplementation() {
11474 		int curHidden = 0; // counter
11475 		__gshared static bool[string] knownWinClasses;
11476 		static bool altPressed = false;
11477 
11478 		HANDLE oldCursor;
11479 
11480 		void hideCursor () {
11481 			if(curHidden == 0)
11482 				oldCursor = SetCursor(null);
11483 			++curHidden;
11484 		}
11485 
11486 		void showCursor () {
11487 			--curHidden;
11488 			if(curHidden == 0) {
11489 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11490 			}
11491 		}
11492 
11493 
11494 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11495 
11496 		void setMinSize (int minwidth, int minheight) {
11497 			minWidth = minwidth;
11498 			minHeight = minheight;
11499 		}
11500 		void setMaxSize (int maxwidth, int maxheight) {
11501 			maxWidth = maxwidth;
11502 			maxHeight = maxheight;
11503 		}
11504 
11505 		// FIXME i'm not sure that Windows has this functionality
11506 		// though it is nonessential anyway.
11507 		void setResizeGranularity (int granx, int grany) {}
11508 
11509 		ScreenPainter getPainter(bool manualInvalidations) {
11510 			return ScreenPainter(this, hwnd, manualInvalidations);
11511 		}
11512 
11513 		HBITMAP buffer;
11514 
11515 		void setTitle(string title) {
11516 			WCharzBuffer bfr = WCharzBuffer(title);
11517 			SetWindowTextW(hwnd, bfr.ptr);
11518 		}
11519 
11520 		string getTitle() {
11521 			auto len = GetWindowTextLengthW(hwnd);
11522 			if (!len)
11523 				return null;
11524 			wchar[256] tmpBuffer;
11525 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11526 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11527 			auto str = buffer[0 .. len2];
11528 			return makeUtf8StringFromWindowsString(str);
11529 		}
11530 
11531 		void move(int x, int y) {
11532 			RECT rect;
11533 			GetWindowRect(hwnd, &rect);
11534 			// move it while maintaining the same size...
11535 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11536 		}
11537 
11538 		void resize(int w, int h) {
11539 			RECT rect;
11540 			GetWindowRect(hwnd, &rect);
11541 
11542 			RECT client;
11543 			GetClientRect(hwnd, &client);
11544 
11545 			rect.right = rect.right - client.right + w;
11546 			rect.bottom = rect.bottom - client.bottom + h;
11547 
11548 			// same position, new size for the client rectangle
11549 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11550 
11551 			updateOpenglViewportIfNeeded(w, h);
11552 		}
11553 
11554 		void moveResize (int x, int y, int w, int h) {
11555 			// what's given is the client rectangle, we need to adjust
11556 
11557 			RECT rect;
11558 			rect.left = x;
11559 			rect.top = y;
11560 			rect.right = w + x;
11561 			rect.bottom = h + y;
11562 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11563 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11564 
11565 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11566 			updateOpenglViewportIfNeeded(w, h);
11567 			if (windowResized !is null) windowResized(w, h);
11568 		}
11569 
11570 		version(without_opengl) {} else {
11571 			HGLRC ghRC;
11572 			HDC ghDC;
11573 		}
11574 
11575 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11576 			string cnamec;
11577 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11578 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11579 				cnamec = "DSimpleWindow";
11580 			} else {
11581 				cnamec = sdpyWindowClass;
11582 			}
11583 
11584 			WCharzBuffer cn = WCharzBuffer(cnamec);
11585 
11586 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11587 
11588 			if(cnamec !in knownWinClasses) {
11589 				WNDCLASSEX wc;
11590 
11591 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11592 				// to the object. Maybe.
11593 				wc.cbSize = wc.sizeof;
11594 				wc.cbClsExtra = 0;
11595 				wc.cbWndExtra = 0;
11596 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11597 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11598 				wc.hIcon = LoadIcon(hInstance, null);
11599 				wc.hInstance = hInstance;
11600 				wc.lpfnWndProc = &WndProc;
11601 				wc.lpszClassName = cn.ptr;
11602 				wc.hIconSm = null;
11603 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11604 				if(!RegisterClassExW(&wc))
11605 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11606 				knownWinClasses[cnamec] = true;
11607 			}
11608 
11609 			int style;
11610 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11611 
11612 			// FIXME: windowType and customizationFlags
11613 			final switch(windowType) {
11614 				case WindowTypes.normal:
11615 					if(resizability == Resizability.fixedSize) {
11616 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11617 					} else {
11618 						style = WS_OVERLAPPEDWINDOW;
11619 					}
11620 				break;
11621 				case WindowTypes.undecorated:
11622 					style = WS_POPUP | WS_SYSMENU;
11623 				break;
11624 				case WindowTypes.eventOnly:
11625 					_hidden = true;
11626 				break;
11627 				case WindowTypes.dropdownMenu:
11628 				case WindowTypes.popupMenu:
11629 				case WindowTypes.notification:
11630 					style = WS_POPUP;
11631 					flags |= WS_EX_NOACTIVATE;
11632 				break;
11633 				case WindowTypes.nestedChild:
11634 					style = WS_CHILD;
11635 				break;
11636 				case WindowTypes.minimallyWrapped:
11637 					assert(0, "construct minimally wrapped through the other ctor overlad");
11638 			}
11639 
11640 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11641 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11642 
11643 			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
11644 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11645 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11646 
11647 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11648 				setOpacity(255);
11649 
11650 			SimpleWindow.nativeMapping[hwnd] = this;
11651 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11652 
11653 			if(windowType == WindowTypes.eventOnly)
11654 				return;
11655 
11656 			HDC hdc = GetDC(hwnd);
11657 
11658 
11659 			version(without_opengl) {}
11660 			else {
11661 				if(opengl == OpenGlOptions.yes) {
11662 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11663 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11664 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11665 					ghDC = hdc;
11666 					PIXELFORMATDESCRIPTOR pfd;
11667 
11668 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11669 					pfd.nVersion = 1;
11670 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11671 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11672 					pfd.iPixelType = PFD_TYPE_RGBA;
11673 					pfd.cColorBits = 24;
11674 					pfd.cDepthBits = 24;
11675 					pfd.cAccumBits = 0;
11676 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11677 
11678 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11679 
11680 					if (pixelformat == 0)
11681 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11682 
11683 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11684 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11685 
11686 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11687 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11688 						// so we will create fake context to get that stupid address
11689 						auto tmpcc = wglCreateContext(ghDC);
11690 						if (tmpcc !is null) {
11691 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11692 							wglMakeCurrent(ghDC, tmpcc);
11693 							wglInitOtherFunctions();
11694 						}
11695 					}
11696 
11697 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11698 						int[9] contextAttribs = [
11699 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11700 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11701 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11702 							// for modern context, set "forward compatibility" flag too
11703 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11704 							0/*None*/,
11705 						];
11706 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11707 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11708 							// activate fallback mode
11709 							// 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;
11710 							ghRC = wglCreateContext(ghDC);
11711 						}
11712 						if (ghRC is null)
11713 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11714 					} else {
11715 						// try to do at least something
11716 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11717 							sdpyOpenGLContextVersion = 0;
11718 							ghRC = wglCreateContext(ghDC);
11719 						}
11720 						if (ghRC is null)
11721 							throw new WindowsApiException("wglCreateContext", GetLastError());
11722 					}
11723 				}
11724 			}
11725 
11726 			if(opengl == OpenGlOptions.no) {
11727 				buffer = CreateCompatibleBitmap(hdc, width, height);
11728 
11729 				auto hdcBmp = CreateCompatibleDC(hdc);
11730 				// make sure it's filled with a blank slate
11731 				auto oldBmp = SelectObject(hdcBmp, buffer);
11732 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11733 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11734 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11735 				SelectObject(hdcBmp, oldBmp);
11736 				SelectObject(hdcBmp, oldBrush);
11737 				SelectObject(hdcBmp, oldPen);
11738 				DeleteDC(hdcBmp);
11739 
11740 				bmpWidth = width;
11741 				bmpHeight = height;
11742 
11743 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11744 			}
11745 
11746 			// We want the window's client area to match the image size
11747 			RECT rcClient, rcWindow;
11748 			POINT ptDiff;
11749 			GetClientRect(hwnd, &rcClient);
11750 			GetWindowRect(hwnd, &rcWindow);
11751 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11752 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11753 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11754 
11755 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11756 				ShowWindow(hwnd, SW_SHOWNORMAL);
11757 			} else {
11758 				_hidden = true;
11759 			}
11760 			this._visibleForTheFirstTimeCalled = false; // hack!
11761 		}
11762 
11763 
11764 		void dispose() {
11765 			if(buffer)
11766 				DeleteObject(buffer);
11767 		}
11768 
11769 		void closeWindow() {
11770 			if(ghRC) {
11771 				wglDeleteContext(ghRC);
11772 				ghRC = null;
11773 			}
11774 			DestroyWindow(hwnd);
11775 		}
11776 
11777 		bool setOpacity(ubyte alpha) {
11778 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11779 		}
11780 
11781 		HANDLE currentCursor;
11782 
11783 		// returns zero if it recognized the event
11784 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11785 			MouseEvent mouse;
11786 
11787 			void mouseEvent(bool isScreen, ulong mods) {
11788 				auto x = LOWORD(lParam);
11789 				auto y = HIWORD(lParam);
11790 				if(isScreen) {
11791 					POINT p;
11792 					p.x = x;
11793 					p.y = y;
11794 					ScreenToClient(hwnd, &p);
11795 					x = cast(ushort) p.x;
11796 					y = cast(ushort) p.y;
11797 				}
11798 
11799 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11800 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11801 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11802 				}
11803 
11804 				mouse.x = x + offsetX;
11805 				mouse.y = y + offsetY;
11806 
11807 				wind.mdx(mouse);
11808 				mouse.modifierState = cast(int) mods;
11809 				mouse.window = wind;
11810 
11811 				if(wind.handleMouseEvent)
11812 					wind.handleMouseEvent(mouse);
11813 			}
11814 
11815 			switch(msg) {
11816 				case WM_GETMINMAXINFO:
11817 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11818 
11819 					if(wind.minWidth > 0) {
11820 						RECT rect;
11821 						rect.left = 100;
11822 						rect.top = 100;
11823 						rect.right = wind.minWidth + 100;
11824 						rect.bottom = wind.minHeight + 100;
11825 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11826 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11827 
11828 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11829 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11830 					}
11831 
11832 					if(wind.maxWidth < int.max) {
11833 						RECT rect;
11834 						rect.left = 100;
11835 						rect.top = 100;
11836 						rect.right = wind.maxWidth + 100;
11837 						rect.bottom = wind.maxHeight + 100;
11838 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11839 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11840 
11841 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11842 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11843 					}
11844 				break;
11845 				case WM_CHAR:
11846 					wchar c = cast(wchar) wParam;
11847 					if(wind.handleCharEvent)
11848 						wind.handleCharEvent(cast(dchar) c);
11849 				break;
11850 				  case WM_SETFOCUS:
11851 				  case WM_KILLFOCUS:
11852 					wind._focused = (msg == WM_SETFOCUS);
11853 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
11854 					if(wind.onFocusChange)
11855 						wind.onFocusChange(msg == WM_SETFOCUS);
11856 				  break;
11857 
11858 				case WM_SYSKEYDOWN:
11859 					goto case;
11860 				case WM_SYSKEYUP:
11861 					if(lParam & (1 << 29)) {
11862 						goto case;
11863 					} else {
11864 						// no window has keyboard focus
11865 						goto default;
11866 					}
11867 				case WM_KEYDOWN:
11868 				case WM_KEYUP:
11869 					KeyEvent ev;
11870 					ev.key = cast(Key) wParam;
11871 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
11872 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
11873 
11874 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
11875 
11876 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
11877 						ev.modifierState |= ModifierState.shift;
11878 					//k8: this doesn't work; thanks for nothing, windows
11879 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
11880 						ev.modifierState |= ModifierState.alt;*/
11881 					// this never seems to actually be set
11882 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11883 
11884 					if (wParam == 0x12) {
11885 						altPressed = (msg == WM_SYSKEYDOWN);
11886 					}
11887 
11888 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
11889 						altPressed = false;
11890 					}
11891 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
11892 
11893 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11894 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
11895 						ev.modifierState |= ModifierState.ctrl;
11896 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
11897 						ev.modifierState |= ModifierState.windows;
11898 					if(GetKeyState(Key.NumLock))
11899 						ev.modifierState |= ModifierState.numLock;
11900 					if(GetKeyState(Key.CapsLock))
11901 						ev.modifierState |= ModifierState.capsLock;
11902 
11903 					/+
11904 					// we always want to send the character too, so let's convert it
11905 					ubyte[256] state;
11906 					wchar[16] buffer;
11907 					GetKeyboardState(state.ptr);
11908 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
11909 
11910 					foreach(dchar d; buffer) {
11911 						ev.character = d;
11912 						break;
11913 					}
11914 					+/
11915 
11916 					ev.window = wind;
11917 					if(wind.handleKeyEvent)
11918 						wind.handleKeyEvent(ev);
11919 				break;
11920 				case 0x020a /*WM_MOUSEWHEEL*/:
11921 					// send click
11922 					mouse.type = cast(MouseEventType) 1;
11923 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11924 					mouseEvent(true, LOWORD(wParam));
11925 
11926 					// also send release
11927 					mouse.type = cast(MouseEventType) 2;
11928 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11929 					mouseEvent(true, LOWORD(wParam));
11930 				break;
11931 				case WM_MOUSEMOVE:
11932 					mouse.type = cast(MouseEventType) 0;
11933 					mouseEvent(false, wParam);
11934 				break;
11935 				case WM_LBUTTONDOWN:
11936 				case WM_LBUTTONDBLCLK:
11937 					mouse.type = cast(MouseEventType) 1;
11938 					mouse.button = MouseButton.left;
11939 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
11940 					mouseEvent(false, wParam);
11941 				break;
11942 				case WM_LBUTTONUP:
11943 					mouse.type = cast(MouseEventType) 2;
11944 					mouse.button = MouseButton.left;
11945 					mouseEvent(false, wParam);
11946 				break;
11947 				case WM_RBUTTONDOWN:
11948 				case WM_RBUTTONDBLCLK:
11949 					mouse.type = cast(MouseEventType) 1;
11950 					mouse.button = MouseButton.right;
11951 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
11952 					mouseEvent(false, wParam);
11953 				break;
11954 				case WM_RBUTTONUP:
11955 					mouse.type = cast(MouseEventType) 2;
11956 					mouse.button = MouseButton.right;
11957 					mouseEvent(false, wParam);
11958 				break;
11959 				case WM_MBUTTONDOWN:
11960 				case WM_MBUTTONDBLCLK:
11961 					mouse.type = cast(MouseEventType) 1;
11962 					mouse.button = MouseButton.middle;
11963 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
11964 					mouseEvent(false, wParam);
11965 				break;
11966 				case WM_MBUTTONUP:
11967 					mouse.type = cast(MouseEventType) 2;
11968 					mouse.button = MouseButton.middle;
11969 					mouseEvent(false, wParam);
11970 				break;
11971 				case WM_XBUTTONDOWN:
11972 				case WM_XBUTTONDBLCLK:
11973 					mouse.type = cast(MouseEventType) 1;
11974 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11975 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
11976 					mouseEvent(false, wParam);
11977 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
11978 				case WM_XBUTTONUP:
11979 					mouse.type = cast(MouseEventType) 2;
11980 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11981 					mouseEvent(false, wParam);
11982 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
11983 
11984 				default: return 1;
11985 			}
11986 			return 0;
11987 		}
11988 
11989 		HWND hwnd;
11990 		private int oldWidth;
11991 		private int oldHeight;
11992 		private bool inSizeMove;
11993 
11994 		/++
11995 			If this is true, the live resize events will trigger all the size things as they drag. If false, those events only come when the size is complete; when the user lets go of the mouse button.
11996 
11997 			History:
11998 				Added November 23, 2021
11999 
12000 				Not fully stable, may be moved out of the impl struct.
12001 
12002 				Default value changed to `true` on February 15, 2021
12003 		+/
12004 		bool doLiveResizing = true;
12005 
12006 		package int bmpWidth;
12007 		package int bmpHeight;
12008 
12009 		// the extern(Windows) wndproc should just forward to this
12010 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12011 		try {
12012 			assert(hwnd is this.hwnd);
12013 
12014 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12015 			switch(msg) {
12016 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12017 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12018 					// The main things we can do are select, execute, close, or ignore
12019 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12020 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12021 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12022 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12023 
12024 					// returns the value in the *high order word* of the return value
12025 					// hence the << 16
12026 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12027 				case WM_SETCURSOR:
12028 					if(cast(HWND) wParam !is hwnd)
12029 						return 0; // further processing elsewhere
12030 
12031 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12032 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12033 						return 1;
12034 					} else {
12035 						return DefWindowProc(hwnd, msg, wParam, lParam);
12036 					}
12037 				//break;
12038 
12039 				case WM_CLOSE:
12040 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12041 				break;
12042 				case WM_DESTROY:
12043 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12044 					SimpleWindow.nativeMapping.remove(hwnd);
12045 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12046 
12047 					bool anyImportant = false;
12048 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12049 						if(w.beingOpenKeepsAppOpen) {
12050 							anyImportant = true;
12051 							break;
12052 						}
12053 					if(!anyImportant) {
12054 						PostQuitMessage(0);
12055 					}
12056 				break;
12057 				case 0x02E0 /*WM_DPICHANGED*/:
12058 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12059 
12060 					RECT* prcNewWindow = cast(RECT*)lParam;
12061 					// docs say this is the recommended position and we should honor it
12062 					SetWindowPos(hwnd,
12063 							null,
12064 							prcNewWindow.left,
12065 							prcNewWindow.top,
12066 							prcNewWindow.right - prcNewWindow.left,
12067 							prcNewWindow.bottom - prcNewWindow.top,
12068 							SWP_NOZORDER | SWP_NOACTIVATE);
12069 
12070 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12071 					// im not sure it is completely correct
12072 					// but without it the tabs and such do look weird as things change.
12073 					if(SystemParametersInfoForDpi) {
12074 						LOGFONT lfText;
12075 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12076 						HFONT hFontNew = CreateFontIndirect(&lfText);
12077 						if (hFontNew)
12078 						{
12079 							//DeleteObject(hFontOld);
12080 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12081 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12082 								return TRUE;
12083 							}
12084 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12085 						}
12086 					}
12087 
12088 					if(this.onDpiChanged)
12089 						this.onDpiChanged();
12090 				break;
12091 				case WM_ENTERIDLE:
12092 					// when a menu is up, it stops normal event processing (modal message loop)
12093 					// but this at least gives us a chance to SOMETIMES catch up
12094 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12095 					SimpleWindow.processAllCustomEvents;
12096 					SimpleWindow.processAllCustomEvents;
12097 					SleepEx(0, true);
12098 					break;
12099 				case WM_SIZE:
12100 					if(wParam == 1 /* SIZE_MINIMIZED */)
12101 						break;
12102 					_width = LOWORD(lParam);
12103 					_height = HIWORD(lParam);
12104 
12105 					// I want to avoid tearing in the windows (my code is inefficient
12106 					// so this is a hack around that) so while sizing, we don't trigger,
12107 					// but we do want to trigger on events like mazimize.
12108 					if(!inSizeMove || doLiveResizing)
12109 						goto size_changed;
12110 				break;
12111 				/+
12112 				case WM_SIZING:
12113 					writeln("size");
12114 				break;
12115 				+/
12116 				// I don't like the tearing I get when redrawing on WM_SIZE
12117 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12118 				// so instead it is going to redraw only at the end of a size.
12119 				case 0x0231: /* WM_ENTERSIZEMOVE */
12120 					inSizeMove = true;
12121 				break;
12122 				case 0x0232: /* WM_EXITSIZEMOVE */
12123 					inSizeMove = false;
12124 
12125 					size_changed:
12126 
12127 					// nothing relevant changed, don't bother redrawing
12128 					if(oldWidth == _width && oldHeight == _height) {
12129 						break;
12130 					}
12131 
12132 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12133 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12134 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12135 						// gotta get the double buffer bmp to match the window
12136 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12137 
12138 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12139 						if(resizability != Resizability.automaticallyScaleIfPossible)
12140 						if(_width > bmpWidth || _height > bmpHeight) {
12141 							auto hdc = GetDC(hwnd);
12142 							auto oldBuffer = buffer;
12143 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12144 
12145 							auto hdcBmp = CreateCompatibleDC(hdc);
12146 							auto oldBmp = SelectObject(hdcBmp, buffer);
12147 
12148 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12149 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12150 
12151 							/+
12152 							RECT r;
12153 							r.left = 0;
12154 							r.top = 0;
12155 							r.right = width;
12156 							r.bottom = height;
12157 							auto c = Color.green;
12158 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12159 							FillRect(hdcBmp, &r, brush);
12160 							DeleteObject(brush);
12161 							+/
12162 
12163 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12164 
12165 							bmpWidth = _width;
12166 							bmpHeight = _height;
12167 
12168 							SelectObject(hdcOldBmp, oldOldBmp);
12169 							DeleteDC(hdcOldBmp);
12170 
12171 							SelectObject(hdcBmp, oldBmp);
12172 							DeleteDC(hdcBmp);
12173 
12174 							ReleaseDC(hwnd, hdc);
12175 
12176 							DeleteObject(oldBuffer);
12177 						}
12178 					}
12179 
12180 					updateOpenglViewportIfNeeded(_width, _height);
12181 
12182 					if(resizability != Resizability.automaticallyScaleIfPossible)
12183 					if(windowResized !is null)
12184 						windowResized(_width, _height);
12185 
12186 					if(inSizeMove) {
12187 						SimpleWindow.processAllCustomEvents();
12188 						SimpleWindow.processAllCustomEvents();
12189 					} else {
12190 						// when it is all done, make sure everything is freshly drawn or there might be
12191 						// weird bugs left.
12192 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12193 					}
12194 
12195 					oldWidth = this._width;
12196 					oldHeight = this._height;
12197 				break;
12198 				case WM_ERASEBKGND:
12199 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12200 					if (!this._visibleForTheFirstTimeCalled) {
12201 						this._visibleForTheFirstTimeCalled = true;
12202 						if (this.visibleForTheFirstTime !is null) {
12203 							this.visibleForTheFirstTime();
12204 						}
12205 					}
12206 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12207 					version(without_opengl) {} else {
12208 						if (openglMode == OpenGlOptions.yes) return 1;
12209 					}
12210 					// call windows default handler, so it can paint standard controls
12211 					goto default;
12212 				case WM_CTLCOLORBTN:
12213 				case WM_CTLCOLORSTATIC:
12214 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12215 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12216 					GetSysColorBrush(COLOR_3DFACE);
12217 				//break;
12218 				case WM_SHOWWINDOW:
12219 					this._visible = (wParam != 0);
12220 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12221 						this._visibleForTheFirstTimeCalled = true;
12222 						if (this.visibleForTheFirstTime !is null) {
12223 							this.visibleForTheFirstTime();
12224 						}
12225 					}
12226 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12227 					break;
12228 				case WM_PAINT: {
12229 					if (!this._visibleForTheFirstTimeCalled) {
12230 						this._visibleForTheFirstTimeCalled = true;
12231 						if (this.visibleForTheFirstTime !is null) {
12232 							this.visibleForTheFirstTime();
12233 						}
12234 					}
12235 
12236 					BITMAP bm;
12237 					PAINTSTRUCT ps;
12238 
12239 					HDC hdc = BeginPaint(hwnd, &ps);
12240 
12241 					if(openglMode == OpenGlOptions.no) {
12242 
12243 						HDC hdcMem = CreateCompatibleDC(hdc);
12244 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12245 
12246 						GetObject(buffer, bm.sizeof, &bm);
12247 
12248 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12249 						if(resizability == Resizability.automaticallyScaleIfPossible)
12250 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12251 						else
12252 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12253 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12254 
12255 						SelectObject(hdcMem, hbmOld);
12256 						DeleteDC(hdcMem);
12257 						EndPaint(hwnd, &ps);
12258 					} else {
12259 						EndPaint(hwnd, &ps);
12260 						version(without_opengl) {} else
12261 							redrawOpenGlSceneSoon();
12262 					}
12263 				} break;
12264 				  default:
12265 					return DefWindowProc(hwnd, msg, wParam, lParam);
12266 			}
12267 			 return 0;
12268 
12269 		}
12270 		catch(Throwable t) {
12271 			sdpyPrintDebugString(t.toString);
12272 			return 0;
12273 		}
12274 		}
12275 	}
12276 
12277 	mixin template NativeImageImplementation() {
12278 		HBITMAP handle;
12279 		ubyte* rawData;
12280 
12281 	final:
12282 
12283 		Color getPixel(int x, int y) {
12284 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12285 			// remember, bmps are upside down
12286 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12287 
12288 			Color c;
12289 			if(enableAlpha)
12290 				c.a = rawData[offset + 3];
12291 			else
12292 				c.a = 255;
12293 			c.b = rawData[offset + 0];
12294 			c.g = rawData[offset + 1];
12295 			c.r = rawData[offset + 2];
12296 			c.unPremultiply();
12297 			return c;
12298 		}
12299 
12300 		void setPixel(int x, int y, Color c) {
12301 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12302 			// remember, bmps are upside down
12303 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12304 
12305 			if(enableAlpha)
12306 				c.premultiply();
12307 
12308 			rawData[offset + 0] = c.b;
12309 			rawData[offset + 1] = c.g;
12310 			rawData[offset + 2] = c.r;
12311 			if(enableAlpha)
12312 				rawData[offset + 3] = c.a;
12313 		}
12314 
12315 		void convertToRgbaBytes(ubyte[] where) {
12316 			assert(where.length == this.width * this.height * 4);
12317 
12318 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12319 			int idx = 0;
12320 			int offset = itemsPerLine * (height - 1);
12321 			// remember, bmps are upside down
12322 			for(int y = height - 1; y >= 0; y--) {
12323 				auto offsetStart = offset;
12324 				for(int x = 0; x < width; x++) {
12325 					where[idx + 0] = rawData[offset + 2]; // r
12326 					where[idx + 1] = rawData[offset + 1]; // g
12327 					where[idx + 2] = rawData[offset + 0]; // b
12328 					if(enableAlpha) {
12329 						where[idx + 3] = rawData[offset + 3]; // a
12330 						unPremultiplyRgba(where[idx .. idx + 4]);
12331 						offset++;
12332 					} else
12333 						where[idx + 3] = 255; // a
12334 					idx += 4;
12335 					offset += 3;
12336 				}
12337 
12338 				offset = offsetStart - itemsPerLine;
12339 			}
12340 		}
12341 
12342 		void setFromRgbaBytes(in ubyte[] what) {
12343 			assert(what.length == this.width * this.height * 4);
12344 
12345 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12346 			int idx = 0;
12347 			int offset = itemsPerLine * (height - 1);
12348 			// remember, bmps are upside down
12349 			for(int y = height - 1; y >= 0; y--) {
12350 				auto offsetStart = offset;
12351 				for(int x = 0; x < width; x++) {
12352 					if(enableAlpha) {
12353 						auto a = what[idx + 3];
12354 
12355 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12356 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12357 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12358 						rawData[offset + 3] = a; // a
12359 						//premultiplyBgra(rawData[offset .. offset + 4]);
12360 						offset++;
12361 					} else {
12362 						rawData[offset + 2] = what[idx + 0]; // r
12363 						rawData[offset + 1] = what[idx + 1]; // g
12364 						rawData[offset + 0] = what[idx + 2]; // b
12365 					}
12366 					idx += 4;
12367 					offset += 3;
12368 				}
12369 
12370 				offset = offsetStart - itemsPerLine;
12371 			}
12372 		}
12373 
12374 
12375 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12376 			BITMAPINFO infoheader;
12377 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12378 			infoheader.bmiHeader.biWidth = width;
12379 			infoheader.bmiHeader.biHeight = height;
12380 			infoheader.bmiHeader.biPlanes = 1;
12381 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12382 			infoheader.bmiHeader.biCompression = BI_RGB;
12383 
12384 			handle = CreateDIBSection(
12385 				null,
12386 				&infoheader,
12387 				DIB_RGB_COLORS,
12388 				cast(void**) &rawData,
12389 				null,
12390 				0);
12391 			if(handle is null)
12392 				throw new WindowsApiException("create image failed", GetLastError());
12393 
12394 		}
12395 
12396 		void dispose() {
12397 			DeleteObject(handle);
12398 		}
12399 	}
12400 
12401 	enum KEY_ESCAPE = 27;
12402 }
12403 version(X11) {
12404 	/// This is the default font used. You might change this before doing anything else with
12405 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12406 	/// for cross-platform compatibility.
12407 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12408 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12409 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12410 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12411 
12412 	alias int delegate(XEvent) NativeEventHandler;
12413 	alias Window NativeWindowHandle;
12414 
12415 	enum KEY_ESCAPE = 9;
12416 
12417 	mixin template NativeScreenPainterImplementation() {
12418 		Display* display;
12419 		Drawable d;
12420 		Drawable destiny;
12421 
12422 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12423 		GC gc;
12424 
12425 		__gshared bool fontAttempted;
12426 
12427 		__gshared XFontStruct* defaultfont;
12428 		__gshared XFontSet defaultfontset;
12429 
12430 		XFontStruct* font;
12431 		XFontSet fontset;
12432 
12433 		void create(NativeWindowHandle window) {
12434 			this.display = XDisplayConnection.get();
12435 
12436 			Drawable buffer = None;
12437 			if(auto sw = cast(SimpleWindow) this.window) {
12438 				buffer = sw.impl.buffer;
12439 				this.destiny = cast(Drawable) window;
12440 			} else {
12441 				buffer = cast(Drawable) window;
12442 				this.destiny = None;
12443 			}
12444 
12445 			this.d = cast(Drawable) buffer;
12446 
12447 			auto dgc = DefaultGC(display, DefaultScreen(display));
12448 
12449 			this.gc = XCreateGC(display, d, 0, null);
12450 
12451 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12452 
12453 			ensureDefaultFontLoaded();
12454 
12455 			font = defaultfont;
12456 			fontset = defaultfontset;
12457 
12458 			if(font) {
12459 				XSetFont(display, gc, font.fid);
12460 			}
12461 		}
12462 
12463 		static void ensureDefaultFontLoaded() {
12464 			if(!fontAttempted) {
12465 				auto display = XDisplayConnection.get;
12466 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12467 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12468 				if(font is null) {
12469 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12470 					font = XLoadQueryFont(display, xfontstr.ptr);
12471 				}
12472 
12473 				char** lol;
12474 				int lol2;
12475 				char* lol3;
12476 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12477 
12478 				fontAttempted = true;
12479 
12480 				defaultfont = font;
12481 				defaultfontset = fontset;
12482 			}
12483 		}
12484 
12485 		arsd.color.Rectangle _clipRectangle;
12486 		void setClipRectangle(int x, int y, int width, int height) {
12487 			auto old = _clipRectangle;
12488 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12489 			if(old == _clipRectangle)
12490 				return;
12491 
12492 			if(width == 0 || height == 0) {
12493 				XSetClipMask(display, gc, None);
12494 
12495 				if(xrenderPicturePainter) {
12496 
12497 					XRectangle[1] rects;
12498 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12499 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12500 				}
12501 
12502 				version(with_xft) {
12503 					if(xftFont is null || xftDraw is null)
12504 						return;
12505 					XftDrawSetClip(xftDraw, null);
12506 				}
12507 			} else {
12508 				XRectangle[1] rects;
12509 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12510 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12511 
12512 				if(xrenderPicturePainter)
12513 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12514 
12515 				version(with_xft) {
12516 					if(xftFont is null || xftDraw is null)
12517 						return;
12518 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12519 				}
12520 			}
12521 		}
12522 
12523 		version(with_xft) {
12524 			XftFont* xftFont;
12525 			XftDraw* xftDraw;
12526 
12527 			XftColor xftColor;
12528 
12529 			void updateXftColor() {
12530 				if(xftFont is null)
12531 					return;
12532 
12533 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12534 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12535 
12536 				XftColorAllocValue(
12537 					display,
12538 					DefaultVisual(display, DefaultScreen(display)),
12539 					DefaultColormap(display, 0),
12540 					&colorIn,
12541 					&xftColor
12542 				);
12543 			}
12544 		}
12545 
12546 		private OperatingSystemFont _activeFont;
12547 		void setFont(OperatingSystemFont font) {
12548 			_activeFont = font;
12549 			version(with_xft) {
12550 				if(font && font.isXft && font.xftFont)
12551 					this.xftFont = font.xftFont;
12552 				else
12553 					this.xftFont = null;
12554 
12555 				if(this.xftFont) {
12556 					if(xftDraw is null) {
12557 						xftDraw = XftDrawCreate(
12558 							display,
12559 							d,
12560 							DefaultVisual(display, DefaultScreen(display)),
12561 							DefaultColormap(display, 0)
12562 						);
12563 
12564 						updateXftColor();
12565 					}
12566 
12567 					return;
12568 				}
12569 			}
12570 
12571 			if(font && font.font) {
12572 				this.font = font.font;
12573 				this.fontset = font.fontset;
12574 				XSetFont(display, gc, font.font.fid);
12575 			} else {
12576 				this.font = defaultfont;
12577 				this.fontset = defaultfontset;
12578 			}
12579 
12580 		}
12581 
12582 		private Picture xrenderPicturePainter;
12583 
12584 		bool manualInvalidations;
12585 		void invalidateRect(Rectangle invalidRect) {
12586 			// FIXME if manualInvalidations
12587 		}
12588 
12589 		void dispose() {
12590 			this.rasterOp = RasterOp.normal;
12591 
12592 			if(xrenderPicturePainter) {
12593 				XRenderFreePicture(display, xrenderPicturePainter);
12594 				xrenderPicturePainter = None;
12595 			}
12596 
12597 			// FIXME: this.window.width/height is probably wrong
12598 
12599 			// src x,y     then dest x, y
12600 			if(destiny != None) {
12601 				// FIXME: if manual invalidations we can actually only copy some of the area.
12602 				// if(manualInvalidations)
12603 				XSetClipMask(display, gc, None);
12604 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12605 			}
12606 
12607 			XFreeGC(display, gc);
12608 
12609 			version(with_xft)
12610 			if(xftDraw) {
12611 				XftDrawDestroy(xftDraw);
12612 				xftDraw = null;
12613 			}
12614 
12615 			/+
12616 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12617 			if(font && font !is defaultfont) {
12618 				XFreeFont(display, font);
12619 				font = null;
12620 			}
12621 			if(fontset && fontset !is defaultfontset) {
12622 				XFreeFontSet(display, fontset);
12623 				fontset = null;
12624 			}
12625 			+/
12626 			XFlush(display);
12627 
12628 			if(window.paintingFinishedDg !is null)
12629 				window.paintingFinishedDg()();
12630 		}
12631 
12632 		bool backgroundIsNotTransparent = true;
12633 		bool foregroundIsNotTransparent = true;
12634 
12635 		bool _penInitialized = false;
12636 		Pen _activePen;
12637 
12638 		Color _outlineColor;
12639 		Color _fillColor;
12640 
12641 		@property void pen(Pen p) {
12642 			if(_penInitialized && p == _activePen) {
12643 				return;
12644 			}
12645 			_penInitialized = true;
12646 			_activePen = p;
12647 			_outlineColor = p.color;
12648 
12649 			int style;
12650 
12651 			byte dashLength;
12652 
12653 			final switch(p.style) {
12654 				case Pen.Style.Solid:
12655 					style = 0 /*LineSolid*/;
12656 				break;
12657 				case Pen.Style.Dashed:
12658 					style = 1 /*LineOnOffDash*/;
12659 					dashLength = 4;
12660 				break;
12661 				case Pen.Style.Dotted:
12662 					style = 1 /*LineOnOffDash*/;
12663 					dashLength = 1;
12664 				break;
12665 			}
12666 
12667 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
12668 			if(dashLength)
12669 				XSetDashes(display, gc, 0, &dashLength, 1);
12670 
12671 			if(p.color.a == 0) {
12672 				foregroundIsNotTransparent = false;
12673 				return;
12674 			}
12675 
12676 			foregroundIsNotTransparent = true;
12677 
12678 			XSetForeground(display, gc, colorToX(p.color, display));
12679 
12680 			version(with_xft)
12681 				updateXftColor();
12682 		}
12683 
12684 		RasterOp _currentRasterOp;
12685 		bool _currentRasterOpInitialized = false;
12686 		@property void rasterOp(RasterOp op) {
12687 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12688 				return;
12689 			_currentRasterOp = op;
12690 			_currentRasterOpInitialized = true;
12691 			int mode;
12692 			final switch(op) {
12693 				case RasterOp.normal:
12694 					mode = GXcopy;
12695 				break;
12696 				case RasterOp.xor:
12697 					mode = GXxor;
12698 				break;
12699 			}
12700 			XSetFunction(display, gc, mode);
12701 		}
12702 
12703 
12704 		bool _fillColorInitialized = false;
12705 
12706 		@property void fillColor(Color c) {
12707 			if(_fillColorInitialized && _fillColor == c)
12708 				return; // already good, no need to waste time calling it
12709 			_fillColor = c;
12710 			_fillColorInitialized = true;
12711 			if(c.a == 0) {
12712 				backgroundIsNotTransparent = false;
12713 				return;
12714 			}
12715 
12716 			backgroundIsNotTransparent = true;
12717 
12718 			XSetBackground(display, gc, colorToX(c, display));
12719 
12720 		}
12721 
12722 		void swapColors() {
12723 			auto tmp = _fillColor;
12724 			fillColor = _outlineColor;
12725 			auto newPen = _activePen;
12726 			newPen.color = tmp;
12727 			pen(newPen);
12728 		}
12729 
12730 		uint colorToX(Color c, Display* display) {
12731 			auto visual = DefaultVisual(display, DefaultScreen(display));
12732 			import core.bitop;
12733 			uint color = 0;
12734 			{
12735 			auto startBit = bsf(visual.red_mask);
12736 			auto lastBit = bsr(visual.red_mask);
12737 			auto r = cast(uint) c.r;
12738 			r >>= 7 - (lastBit - startBit);
12739 			r <<= startBit;
12740 			color |= r;
12741 			}
12742 			{
12743 			auto startBit = bsf(visual.green_mask);
12744 			auto lastBit = bsr(visual.green_mask);
12745 			auto g = cast(uint) c.g;
12746 			g >>= 7 - (lastBit - startBit);
12747 			g <<= startBit;
12748 			color |= g;
12749 			}
12750 			{
12751 			auto startBit = bsf(visual.blue_mask);
12752 			auto lastBit = bsr(visual.blue_mask);
12753 			auto b = cast(uint) c.b;
12754 			b >>= 7 - (lastBit - startBit);
12755 			b <<= startBit;
12756 			color |= b;
12757 			}
12758 
12759 
12760 
12761 			return color;
12762 		}
12763 
12764 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12765 			// source x, source y
12766 			if(ix >= i.width) return;
12767 			if(iy >= i.height) return;
12768 			if(ix + w > i.width) w = i.width - ix;
12769 			if(iy + h > i.height) h = i.height - iy;
12770 			if(i.usingXshm)
12771 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12772 			else
12773 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12774 		}
12775 
12776 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12777 			if(s.enableAlpha) {
12778 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12779 				if(this.xrenderPicturePainter == None) {
12780 					XRenderPictureAttributes attrs;
12781 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12782 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12783 
12784 					// need to initialize the clip
12785 					XRectangle[1] rects;
12786 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12787 
12788 					if(_clipRectangle != Rectangle.init)
12789 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12790 				}
12791 
12792 				XRenderComposite(
12793 					display,
12794 					3, // PicOpOver
12795 					s.xrenderPicture,
12796 					None,
12797 					this.xrenderPicturePainter,
12798 					ix,
12799 					iy,
12800 					0,
12801 					0,
12802 					x,
12803 					y,
12804 					w ? w : s.width,
12805 					h ? h : s.height
12806 				);
12807 			} else {
12808 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12809 			}
12810 		}
12811 
12812 		int fontHeight() {
12813 			version(with_xft)
12814 				if(xftFont !is null)
12815 					return xftFont.height;
12816 			if(font)
12817 				return font.max_bounds.ascent + font.max_bounds.descent;
12818 			return 12; // pretty common default...
12819 		}
12820 
12821 		int textWidth(in char[] line) {
12822 			version(with_xft)
12823 			if(xftFont) {
12824 				if(line.length == 0)
12825 					return 0;
12826 				XGlyphInfo extents;
12827 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12828 				return extents.width;
12829 			}
12830 
12831 			if(fontset) {
12832 				if(line.length == 0)
12833 					return 0;
12834 				XRectangle rect;
12835 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12836 
12837 				return rect.width;
12838 			}
12839 
12840 			if(font)
12841 				// FIXME: unicode
12842 				return XTextWidth( font, line.ptr, cast(int) line.length);
12843 			else
12844 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
12845 		}
12846 
12847 		Size textSize(in char[] text) {
12848 			auto maxWidth = 0;
12849 			auto lineHeight = fontHeight;
12850 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
12851 			foreach(line; text.split('\n')) {
12852 				int textWidth = this.textWidth(line);
12853 				if(textWidth > maxWidth)
12854 					maxWidth = textWidth;
12855 				h += lineHeight + 4;
12856 			}
12857 			return Size(maxWidth, h);
12858 		}
12859 
12860 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
12861 			const(char)[] text;
12862 			version(with_xft)
12863 			if(xftFont) {
12864 				text = originalText;
12865 				goto loaded;
12866 			}
12867 
12868 			if(fontset)
12869 				text = originalText;
12870 			else {
12871 				text.reserve(originalText.length);
12872 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
12873 				// then strip the rest so there isn't garbage
12874 				foreach(dchar ch; originalText)
12875 					if(ch < 256)
12876 						text ~= cast(ubyte) ch;
12877 					else
12878 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
12879 			}
12880 			loaded:
12881 			if(text.length == 0)
12882 				return;
12883 
12884 			// FIXME: should we clip it to the bounding box?
12885 			int textHeight = fontHeight;
12886 
12887 			auto lines = text.split('\n');
12888 
12889 			const lineHeight = textHeight;
12890 			textHeight *= lines.length;
12891 
12892 			int cy = y;
12893 
12894 			if(alignment & TextAlignment.VerticalBottom) {
12895 				if(y2 <= 0)
12896 					return;
12897 				auto h = y2 - y;
12898 				if(h > textHeight) {
12899 					cy += h - textHeight;
12900 					cy -= lineHeight / 2;
12901 				}
12902 			} else if(alignment & TextAlignment.VerticalCenter) {
12903 				if(y2 <= 0)
12904 					return;
12905 				auto h = y2 - y;
12906 				if(textHeight < h) {
12907 					cy += (h - textHeight) / 2;
12908 					//cy -= lineHeight / 4;
12909 				}
12910 			}
12911 
12912 			foreach(line; text.split('\n')) {
12913 				int textWidth = this.textWidth(line);
12914 
12915 				int px = x, py = cy;
12916 
12917 				if(alignment & TextAlignment.Center) {
12918 					if(x2 <= 0)
12919 						return;
12920 					auto w = x2 - x;
12921 					if(w > textWidth)
12922 						px += (w - textWidth) / 2;
12923 				} else if(alignment & TextAlignment.Right) {
12924 					if(x2 <= 0)
12925 						return;
12926 					auto pos = x2 - textWidth;
12927 					if(pos > x)
12928 						px = pos;
12929 				}
12930 
12931 				version(with_xft)
12932 				if(xftFont) {
12933 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
12934 
12935 					goto carry_on;
12936 				}
12937 
12938 				if(fontset)
12939 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12940 				else
12941 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12942 				carry_on:
12943 				cy += lineHeight + 4;
12944 			}
12945 		}
12946 
12947 		void drawPixel(int x, int y) {
12948 			XDrawPoint(display, d, gc, x, y);
12949 		}
12950 
12951 		// The basic shapes, outlined
12952 
12953 		void drawLine(int x1, int y1, int x2, int y2) {
12954 			if(foregroundIsNotTransparent)
12955 				XDrawLine(display, d, gc, x1, y1, x2, y2);
12956 		}
12957 
12958 		void drawRectangle(int x, int y, int width, int height) {
12959 			if(backgroundIsNotTransparent) {
12960 				swapColors();
12961 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
12962 				swapColors();
12963 			}
12964 			// 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
12965 			if(foregroundIsNotTransparent)
12966 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
12967 		}
12968 
12969 		/// Arguments are the points of the bounding rectangle
12970 		void drawEllipse(int x1, int y1, int x2, int y2) {
12971 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
12972 		}
12973 
12974 		// NOTE: start and finish are in units of degrees * 64
12975 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
12976 			if(backgroundIsNotTransparent) {
12977 				swapColors();
12978 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
12979 				swapColors();
12980 			}
12981 			if(foregroundIsNotTransparent) {
12982 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
12983 				// Windows draws the straight lines on the edges too so FIXME sort of
12984 			}
12985 		}
12986 
12987 		void drawPolygon(Point[] vertexes) {
12988 			XPoint[16] pointsBuffer;
12989 			XPoint[] points;
12990 			if(vertexes.length <= pointsBuffer.length)
12991 				points = pointsBuffer[0 .. vertexes.length];
12992 			else
12993 				points.length = vertexes.length;
12994 
12995 			foreach(i, p; vertexes) {
12996 				points[i].x = cast(short) p.x;
12997 				points[i].y = cast(short) p.y;
12998 			}
12999 
13000 			if(backgroundIsNotTransparent) {
13001 				swapColors();
13002 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13003 				swapColors();
13004 			}
13005 			if(foregroundIsNotTransparent) {
13006 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13007 			}
13008 		}
13009 	}
13010 
13011 	/* XRender { */
13012 
13013 	struct XRenderColor {
13014 		ushort red;
13015 		ushort green;
13016 		ushort blue;
13017 		ushort alpha;
13018 	}
13019 
13020 	alias Picture = XID;
13021 	alias PictFormat = XID;
13022 
13023 	struct XGlyphInfo {
13024 		ushort width;
13025 		ushort height;
13026 		short x;
13027 		short y;
13028 		short xOff;
13029 		short yOff;
13030 	}
13031 
13032 struct XRenderDirectFormat {
13033     short   red;
13034     short   redMask;
13035     short   green;
13036     short   greenMask;
13037     short   blue;
13038     short   blueMask;
13039     short   alpha;
13040     short   alphaMask;
13041 }
13042 
13043 struct XRenderPictFormat {
13044     PictFormat		id;
13045     int			type;
13046     int			depth;
13047     XRenderDirectFormat	direct;
13048     Colormap		colormap;
13049 }
13050 
13051 enum PictFormatID	=   (1 << 0);
13052 enum PictFormatType	=   (1 << 1);
13053 enum PictFormatDepth	=   (1 << 2);
13054 enum PictFormatRed	=   (1 << 3);
13055 enum PictFormatRedMask  =(1 << 4);
13056 enum PictFormatGreen	=   (1 << 5);
13057 enum PictFormatGreenMask=(1 << 6);
13058 enum PictFormatBlue	=   (1 << 7);
13059 enum PictFormatBlueMask =(1 << 8);
13060 enum PictFormatAlpha	=   (1 << 9);
13061 enum PictFormatAlphaMask=(1 << 10);
13062 enum PictFormatColormap =(1 << 11);
13063 
13064 struct XRenderPictureAttributes {
13065 	int 		repeat;
13066 	Picture		alpha_map;
13067 	int			alpha_x_origin;
13068 	int			alpha_y_origin;
13069 	int			clip_x_origin;
13070 	int			clip_y_origin;
13071 	Pixmap		clip_mask;
13072 	Bool		graphics_exposures;
13073 	int			subwindow_mode;
13074 	int			poly_edge;
13075 	int			poly_mode;
13076 	Atom		dither;
13077 	Bool		component_alpha;
13078 }
13079 
13080 alias int XFixed;
13081 
13082 struct XPointFixed {
13083     XFixed  x, y;
13084 }
13085 
13086 struct XCircle {
13087     XFixed x;
13088     XFixed y;
13089     XFixed radius;
13090 }
13091 
13092 struct XTransform {
13093     XFixed[3][3]  matrix;
13094 }
13095 
13096 struct XFilters {
13097     int	    nfilter;
13098     char    **filter;
13099     int	    nalias;
13100     short   *alias_;
13101 }
13102 
13103 struct XIndexValue {
13104     c_ulong    pixel;
13105     ushort   red, green, blue, alpha;
13106 }
13107 
13108 struct XAnimCursor {
13109     Cursor	    cursor;
13110     c_ulong   delay;
13111 }
13112 
13113 struct XLinearGradient {
13114     XPointFixed p1;
13115     XPointFixed p2;
13116 }
13117 
13118 struct XRadialGradient {
13119     XCircle inner;
13120     XCircle outer;
13121 }
13122 
13123 struct XConicalGradient {
13124     XPointFixed center;
13125     XFixed angle; /* in degrees */
13126 }
13127 
13128 enum PictStandardARGB32  = 0;
13129 enum PictStandardRGB24   = 1;
13130 enum PictStandardA8	 =  2;
13131 enum PictStandardA4	 =  3;
13132 enum PictStandardA1	 =  4;
13133 enum PictStandardNUM	 =  5;
13134 
13135 interface XRender {
13136 extern(C) @nogc:
13137 
13138 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13139 
13140 	Status XRenderQueryVersion (Display *dpy,
13141 			int     *major_versionp,
13142 			int     *minor_versionp);
13143 
13144 	Status XRenderQueryFormats (Display *dpy);
13145 
13146 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13147 
13148 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13149 
13150 	XRenderPictFormat *
13151 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13152 
13153 	XRenderPictFormat *
13154 		XRenderFindFormat (Display			*dpy,
13155 				c_ulong		mask,
13156 				const XRenderPictFormat	*templ,
13157 				int				count);
13158 	XRenderPictFormat *
13159 		XRenderFindStandardFormat (Display		*dpy,
13160 				int			format);
13161 
13162 	XIndexValue *
13163 		XRenderQueryPictIndexValues(Display			*dpy,
13164 				const XRenderPictFormat	*format,
13165 				int				*num);
13166 
13167 	Picture XRenderCreatePicture(
13168 		Display *dpy,
13169 		Drawable drawable,
13170 		const XRenderPictFormat *format,
13171 		c_ulong valuemask,
13172 		const XRenderPictureAttributes *attributes);
13173 
13174 	void XRenderChangePicture (Display				*dpy,
13175 				Picture				picture,
13176 				c_ulong			valuemask,
13177 				const XRenderPictureAttributes  *attributes);
13178 
13179 	void
13180 		XRenderSetPictureClipRectangles (Display	    *dpy,
13181 				Picture	    picture,
13182 				int		    xOrigin,
13183 				int		    yOrigin,
13184 				const XRectangle *rects,
13185 				int		    n);
13186 
13187 	void
13188 		XRenderSetPictureClipRegion (Display	    *dpy,
13189 				Picture	    picture,
13190 				Region	    r);
13191 
13192 	void
13193 		XRenderSetPictureTransform (Display	    *dpy,
13194 				Picture	    picture,
13195 				XTransform	    *transform);
13196 
13197 	void
13198 		XRenderFreePicture (Display                   *dpy,
13199 				Picture                   picture);
13200 
13201 	void
13202 		XRenderComposite (Display   *dpy,
13203 				int	    op,
13204 				Picture   src,
13205 				Picture   mask,
13206 				Picture   dst,
13207 				int	    src_x,
13208 				int	    src_y,
13209 				int	    mask_x,
13210 				int	    mask_y,
13211 				int	    dst_x,
13212 				int	    dst_y,
13213 				uint	width,
13214 				uint	height);
13215 
13216 
13217 	Picture XRenderCreateSolidFill (Display *dpy,
13218 			const XRenderColor *color);
13219 
13220 	Picture XRenderCreateLinearGradient (Display *dpy,
13221 			const XLinearGradient *gradient,
13222 			const XFixed *stops,
13223 			const XRenderColor *colors,
13224 			int nstops);
13225 
13226 	Picture XRenderCreateRadialGradient (Display *dpy,
13227 			const XRadialGradient *gradient,
13228 			const XFixed *stops,
13229 			const XRenderColor *colors,
13230 			int nstops);
13231 
13232 	Picture XRenderCreateConicalGradient (Display *dpy,
13233 			const XConicalGradient *gradient,
13234 			const XFixed *stops,
13235 			const XRenderColor *colors,
13236 			int nstops);
13237 
13238 
13239 
13240 	Cursor
13241 		XRenderCreateCursor (Display	    *dpy,
13242 				Picture	    source,
13243 				uint   x,
13244 				uint   y);
13245 
13246 	XFilters *
13247 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13248 
13249 	void
13250 		XRenderSetPictureFilter (Display    *dpy,
13251 				Picture    picture,
13252 				const char *filter,
13253 				XFixed	    *params,
13254 				int	    nparams);
13255 
13256 	Cursor
13257 		XRenderCreateAnimCursor (Display	*dpy,
13258 				int		ncursor,
13259 				XAnimCursor	*cursors);
13260 }
13261 
13262 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13263 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13264 
13265 	/* XRender } */
13266 
13267 	/* Xrandr { */
13268 
13269 struct XRRMonitorInfo {
13270     Atom name;
13271     Bool primary;
13272     Bool automatic;
13273     int noutput;
13274     int x;
13275     int y;
13276     int width;
13277     int height;
13278     int mwidth;
13279     int mheight;
13280     /*RROutput*/ void *outputs;
13281 }
13282 
13283 struct XRRScreenChangeNotifyEvent {
13284     int type;                   /* event base */
13285     c_ulong serial;       /* # of last request processed by server */
13286     Bool send_event;            /* true if this came from a SendEvent request */
13287     Display *display;           /* Display the event was read from */
13288     Window window;              /* window which selected for this event */
13289     Window root;                /* Root window for changed screen */
13290     Time timestamp;             /* when the screen change occurred */
13291     Time config_timestamp;      /* when the last configuration change */
13292     ushort/*SizeID*/ size_index;
13293     ushort/*SubpixelOrder*/ subpixel_order;
13294     ushort/*Rotation*/ rotation;
13295     int width;
13296     int height;
13297     int mwidth;
13298     int mheight;
13299 }
13300 
13301 enum RRScreenChangeNotify = 0;
13302 
13303 enum RRScreenChangeNotifyMask = 1;
13304 
13305 __gshared int xrrEventBase = -1;
13306 
13307 
13308 interface XRandr {
13309 extern(C) @nogc:
13310 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13311 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13312 
13313 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13314 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13315 
13316 	void XRRSelectInput(Display *dpy, Window window, int mask);
13317 }
13318 
13319 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13320 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13321 	/* Xrandr } */
13322 
13323 	/* Xft { */
13324 
13325 	// actually freetype
13326 	alias void FT_Face;
13327 
13328 	// actually fontconfig
13329 	private alias FcBool = int;
13330 	alias void FcCharSet;
13331 	alias void FcPattern;
13332 	alias void FcResult;
13333 	enum FcEndian { FcEndianBig, FcEndianLittle }
13334 	struct FcFontSet {
13335 		int nfont;
13336 		int sfont;
13337 		FcPattern** fonts;
13338 	}
13339 
13340 	// actually XRegion
13341 	struct BOX {
13342 		short x1, x2, y1, y2;
13343 	}
13344 	struct _XRegion {
13345 		c_long size;
13346 		c_long numRects;
13347 		BOX* rects;
13348 		BOX extents;
13349 	}
13350 
13351 	alias Region = _XRegion*;
13352 
13353 	// ok actually Xft
13354 
13355 	struct XftFontInfo;
13356 
13357 	struct XftFont {
13358 		int         ascent;
13359 		int         descent;
13360 		int         height;
13361 		int         max_advance_width;
13362 		FcCharSet*  charset;
13363 		FcPattern*  pattern;
13364 	}
13365 
13366 	struct XftDraw;
13367 
13368 	struct XftColor {
13369 		c_ulong pixel;
13370 		XRenderColor color;
13371 	}
13372 
13373 	struct XftCharSpec {
13374 		dchar           ucs4;
13375 		short           x;
13376 		short           y;
13377 	}
13378 
13379 	struct XftCharFontSpec {
13380 		XftFont         *font;
13381 		dchar           ucs4;
13382 		short           x;
13383 		short           y;
13384 	}
13385 
13386 	struct XftGlyphSpec {
13387 		uint            glyph;
13388 		short           x;
13389 		short           y;
13390 	}
13391 
13392 	struct XftGlyphFontSpec {
13393 		XftFont         *font;
13394 		uint            glyph;
13395 		short           x;
13396 		short           y;
13397 	}
13398 
13399 	interface Xft {
13400 	extern(C) @nogc pure:
13401 
13402 	Bool XftColorAllocName (Display  *dpy,
13403 				const Visual   *visual,
13404 				Colormap cmap,
13405 				const char     *name,
13406 				XftColor *result);
13407 
13408 	Bool XftColorAllocValue (Display         *dpy,
13409 				Visual          *visual,
13410 				Colormap        cmap,
13411 				const XRenderColor    *color,
13412 				XftColor        *result);
13413 
13414 	void XftColorFree (Display   *dpy,
13415 				Visual    *visual,
13416 				Colormap  cmap,
13417 				XftColor  *color);
13418 
13419 	Bool XftDefaultHasRender (Display *dpy);
13420 
13421 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13422 
13423 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13424 
13425 	XftDraw * XftDrawCreate (Display   *dpy,
13426 		       Drawable  drawable,
13427 		       Visual    *visual,
13428 		       Colormap  colormap);
13429 
13430 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13431 			     Pixmap   bitmap);
13432 
13433 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13434 			    Pixmap  pixmap,
13435 			    int     depth);
13436 
13437 	void XftDrawChange (XftDraw  *draw,
13438 		       Drawable drawable);
13439 
13440 	Display * XftDrawDisplay (XftDraw *draw);
13441 
13442 	Drawable XftDrawDrawable (XftDraw *draw);
13443 
13444 	Colormap XftDrawColormap (XftDraw *draw);
13445 
13446 	Visual * XftDrawVisual (XftDraw *draw);
13447 
13448 	void XftDrawDestroy (XftDraw *draw);
13449 
13450 	Picture XftDrawPicture (XftDraw *draw);
13451 
13452 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13453 
13454 	void XftDrawGlyphs (XftDraw          *draw,
13455 				const XftColor *color,
13456 				XftFont          *pub,
13457 				int              x,
13458 				int              y,
13459 				const uint  *glyphs,
13460 				int              nglyphs);
13461 
13462 	void XftDrawString8 (XftDraw             *draw,
13463 				const XftColor    *color,
13464 				XftFont             *pub,
13465 				int                 x,
13466 				int                 y,
13467 				const char     *string,
13468 				int                 len);
13469 
13470 	void XftDrawString16 (XftDraw            *draw,
13471 				const XftColor   *color,
13472 				XftFont            *pub,
13473 				int                x,
13474 				int                y,
13475 				const wchar   *string,
13476 				int                len);
13477 
13478 	void XftDrawString32 (XftDraw            *draw,
13479 				const XftColor   *color,
13480 				XftFont            *pub,
13481 				int                x,
13482 				int                y,
13483 				const dchar   *string,
13484 				int                len);
13485 
13486 	void XftDrawStringUtf8 (XftDraw          *draw,
13487 				const XftColor *color,
13488 				XftFont          *pub,
13489 				int              x,
13490 				int              y,
13491 				const char  *string,
13492 				int              len);
13493 	void XftDrawStringUtf16 (XftDraw             *draw,
13494 				const XftColor    *color,
13495 				XftFont             *pub,
13496 				int                 x,
13497 				int                 y,
13498 				const char     *string,
13499 				FcEndian            endian,
13500 				int                 len);
13501 
13502 	void XftDrawCharSpec (XftDraw                *draw,
13503 				const XftColor       *color,
13504 				XftFont                *pub,
13505 				const XftCharSpec    *chars,
13506 				int                    len);
13507 
13508 	void XftDrawCharFontSpec (XftDraw                    *draw,
13509 				const XftColor           *color,
13510 				const XftCharFontSpec    *chars,
13511 				int                        len);
13512 
13513 	void XftDrawGlyphSpec (XftDraw               *draw,
13514 				const XftColor      *color,
13515 				XftFont               *pub,
13516 				const XftGlyphSpec  *glyphs,
13517 				int                   len);
13518 
13519 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13520 				const XftColor          *color,
13521 				const XftGlyphFontSpec  *glyphs,
13522 				int                       len);
13523 
13524 	void XftDrawRect (XftDraw            *draw,
13525 				const XftColor   *color,
13526 				int                x,
13527 				int                y,
13528 				uint       width,
13529 				uint       height);
13530 
13531 	Bool XftDrawSetClip (XftDraw     *draw,
13532 				Region      r);
13533 
13534 
13535 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13536 				int                   xOrigin,
13537 				int                   yOrigin,
13538 				const XRectangle    *rects,
13539 				int                   n);
13540 
13541 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13542 				int        mode);
13543 
13544 	void XftGlyphExtents (Display            *dpy,
13545 				XftFont            *pub,
13546 				const uint    *glyphs,
13547 				int                nglyphs,
13548 				XGlyphInfo         *extents);
13549 
13550 	void XftTextExtents8 (Display            *dpy,
13551 				XftFont            *pub,
13552 				const char    *string,
13553 				int                len,
13554 				XGlyphInfo         *extents);
13555 
13556 	void XftTextExtents16 (Display           *dpy,
13557 				XftFont           *pub,
13558 				const wchar  *string,
13559 				int               len,
13560 				XGlyphInfo        *extents);
13561 
13562 	void XftTextExtents32 (Display           *dpy,
13563 				XftFont           *pub,
13564 				const dchar  *string,
13565 				int               len,
13566 				XGlyphInfo        *extents);
13567 
13568 	void XftTextExtentsUtf8 (Display         *dpy,
13569 				XftFont         *pub,
13570 				const char *string,
13571 				int             len,
13572 				XGlyphInfo      *extents);
13573 
13574 	void XftTextExtentsUtf16 (Display            *dpy,
13575 				XftFont            *pub,
13576 				const char    *string,
13577 				FcEndian           endian,
13578 				int                len,
13579 				XGlyphInfo         *extents);
13580 
13581 	FcPattern * XftFontMatch (Display           *dpy,
13582 				int               screen,
13583 				const FcPattern *pattern,
13584 				FcResult          *result);
13585 
13586 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13587 
13588 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13589 
13590 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13591 
13592 	FT_Face XftLockFace (XftFont *pub);
13593 
13594 	void XftUnlockFace (XftFont *pub);
13595 
13596 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13597 
13598 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13599 
13600 	dchar XftFontInfoHash (const XftFontInfo *fi);
13601 
13602 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13603 
13604 	XftFont * XftFontOpenInfo (Display        *dpy,
13605 				FcPattern      *pattern,
13606 				XftFontInfo    *fi);
13607 
13608 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13609 
13610 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13611 
13612 	void XftFontClose (Display *dpy, XftFont *pub);
13613 
13614 	FcBool XftInitFtLibrary();
13615 	void XftFontLoadGlyphs (Display          *dpy,
13616 				XftFont          *pub,
13617 				FcBool           need_bitmaps,
13618 				const uint  *glyphs,
13619 				int              nglyph);
13620 
13621 	void XftFontUnloadGlyphs (Display            *dpy,
13622 				XftFont            *pub,
13623 				const uint    *glyphs,
13624 				int                nglyph);
13625 
13626 	FcBool XftFontCheckGlyph (Display  *dpy,
13627 				XftFont  *pub,
13628 				FcBool   need_bitmaps,
13629 				uint  glyph,
13630 				uint  *missing,
13631 				int      *nmissing);
13632 
13633 	FcBool XftCharExists (Display      *dpy,
13634 				XftFont      *pub,
13635 				dchar    ucs4);
13636 
13637 	uint XftCharIndex (Display       *dpy,
13638 				XftFont       *pub,
13639 				dchar      ucs4);
13640 	FcBool XftInit (const char *config);
13641 
13642 	int XftGetVersion ();
13643 
13644 	FcFontSet * XftListFonts (Display   *dpy,
13645 				int       screen,
13646 				...);
13647 
13648 	FcPattern *XftNameParse (const char *name);
13649 
13650 	void XftGlyphRender (Display         *dpy,
13651 				int             op,
13652 				Picture         src,
13653 				XftFont         *pub,
13654 				Picture         dst,
13655 				int             srcx,
13656 				int             srcy,
13657 				int             x,
13658 				int             y,
13659 				const uint *glyphs,
13660 				int             nglyphs);
13661 
13662 	void XftGlyphSpecRender (Display                 *dpy,
13663 				int                     op,
13664 				Picture                 src,
13665 				XftFont                 *pub,
13666 				Picture                 dst,
13667 				int                     srcx,
13668 				int                     srcy,
13669 				const XftGlyphSpec    *glyphs,
13670 				int                     nglyphs);
13671 
13672 	void XftCharSpecRender (Display              *dpy,
13673 				int                  op,
13674 				Picture              src,
13675 				XftFont              *pub,
13676 				Picture              dst,
13677 				int                  srcx,
13678 				int                  srcy,
13679 				const XftCharSpec  *chars,
13680 				int                  len);
13681 	void XftGlyphFontSpecRender (Display                     *dpy,
13682 				int                         op,
13683 				Picture                     src,
13684 				Picture                     dst,
13685 				int                         srcx,
13686 				int                         srcy,
13687 				const XftGlyphFontSpec    *glyphs,
13688 				int                         nglyphs);
13689 
13690 	void XftCharFontSpecRender (Display                  *dpy,
13691 				int                      op,
13692 				Picture                  src,
13693 				Picture                  dst,
13694 				int                      srcx,
13695 				int                      srcy,
13696 				const XftCharFontSpec  *chars,
13697 				int                      len);
13698 
13699 	void XftTextRender8 (Display         *dpy,
13700 				int             op,
13701 				Picture         src,
13702 				XftFont         *pub,
13703 				Picture         dst,
13704 				int             srcx,
13705 				int             srcy,
13706 				int             x,
13707 				int             y,
13708 				const char *string,
13709 				int             len);
13710 	void XftTextRender16 (Display            *dpy,
13711 				int                op,
13712 				Picture            src,
13713 				XftFont            *pub,
13714 				Picture            dst,
13715 				int                srcx,
13716 				int                srcy,
13717 				int                x,
13718 				int                y,
13719 				const wchar   *string,
13720 				int                len);
13721 
13722 	void XftTextRender16BE (Display          *dpy,
13723 				int              op,
13724 				Picture          src,
13725 				XftFont          *pub,
13726 				Picture          dst,
13727 				int              srcx,
13728 				int              srcy,
13729 				int              x,
13730 				int              y,
13731 				const char  *string,
13732 				int              len);
13733 
13734 	void XftTextRender16LE (Display          *dpy,
13735 				int              op,
13736 				Picture          src,
13737 				XftFont          *pub,
13738 				Picture          dst,
13739 				int              srcx,
13740 				int              srcy,
13741 				int              x,
13742 				int              y,
13743 				const char  *string,
13744 				int              len);
13745 
13746 	void XftTextRender32 (Display            *dpy,
13747 				int                op,
13748 				Picture            src,
13749 				XftFont            *pub,
13750 				Picture            dst,
13751 				int                srcx,
13752 				int                srcy,
13753 				int                x,
13754 				int                y,
13755 				const dchar   *string,
13756 				int                len);
13757 
13758 	void XftTextRender32BE (Display          *dpy,
13759 				int              op,
13760 				Picture          src,
13761 				XftFont          *pub,
13762 				Picture          dst,
13763 				int              srcx,
13764 				int              srcy,
13765 				int              x,
13766 				int              y,
13767 				const char  *string,
13768 				int              len);
13769 
13770 	void XftTextRender32LE (Display          *dpy,
13771 				int              op,
13772 				Picture          src,
13773 				XftFont          *pub,
13774 				Picture          dst,
13775 				int              srcx,
13776 				int              srcy,
13777 				int              x,
13778 				int              y,
13779 				const char  *string,
13780 				int              len);
13781 
13782 	void XftTextRenderUtf8 (Display          *dpy,
13783 				int              op,
13784 				Picture          src,
13785 				XftFont          *pub,
13786 				Picture          dst,
13787 				int              srcx,
13788 				int              srcy,
13789 				int              x,
13790 				int              y,
13791 				const char  *string,
13792 				int              len);
13793 
13794 	void XftTextRenderUtf16 (Display         *dpy,
13795 				int             op,
13796 				Picture         src,
13797 				XftFont         *pub,
13798 				Picture         dst,
13799 				int             srcx,
13800 				int             srcy,
13801 				int             x,
13802 				int             y,
13803 				const char *string,
13804 				FcEndian        endian,
13805 				int             len);
13806 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13807 
13808 	}
13809 
13810 	interface FontConfig {
13811 	extern(C) @nogc pure:
13812 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13813 		void FcFontSetDestroy(FcFontSet*);
13814 		char* FcNameUnparse(const FcPattern *);
13815 	}
13816 
13817 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13818 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13819 
13820 
13821 	/* Xft } */
13822 
13823 	class XDisconnectException : Exception {
13824 		bool userRequested;
13825 		this(bool userRequested = true) {
13826 			this.userRequested = userRequested;
13827 			super("X disconnected");
13828 		}
13829 	}
13830 
13831 	/++
13832 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
13833 
13834 		Please note that it returns
13835 	+/
13836 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
13837 
13838 		static XErrorEvent[] errorBuffer;
13839 
13840 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
13841 			errorBuffer ~= *evt;
13842 			return 0;
13843 		}
13844 
13845 		auto savedErrorHandler = XSetErrorHandler(&handler);
13846 
13847 		try {
13848 			dg();
13849 		} finally {
13850 			XSync(XDisplayConnection.get, 0/*False*/);
13851 			XSetErrorHandler(savedErrorHandler);
13852 		}
13853 
13854 		auto bfr = errorBuffer;
13855 		errorBuffer = null;
13856 
13857 		return bfr;
13858 	}
13859 
13860 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
13861 	class XDisplayConnection {
13862 		private __gshared Display* display;
13863 		private __gshared XIM xim;
13864 		private __gshared char* displayName;
13865 
13866 		private __gshared int connectionSequence_;
13867 		private __gshared bool isLocal_;
13868 
13869 		/// use this for lazy caching when reconnection
13870 		static int connectionSequenceNumber() { return connectionSequence_; }
13871 
13872 		/++
13873 			Guesses if the connection appears to be local.
13874 
13875 			History:
13876 				Added June 3, 2021
13877 		+/
13878 		static @property bool isLocal() nothrow @trusted @nogc {
13879 			return isLocal_;
13880 		}
13881 
13882 		/// Attempts recreation of state, may require application assistance
13883 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
13884 		/// then call this, and if successful, reenter the loop.
13885 		static void discardAndRecreate(string newDisplayString = null) {
13886 			if(insideXEventLoop)
13887 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
13888 
13889 			// 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
13890 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
13891 
13892 			foreach(handle; chnenhm) {
13893 				handle.discardConnectionState();
13894 			}
13895 
13896 			discardState();
13897 
13898 			if(newDisplayString !is null)
13899 				setDisplayName(newDisplayString);
13900 
13901 			auto display = get();
13902 
13903 			foreach(handle; chnenhm) {
13904 				handle.recreateAfterDisconnect();
13905 			}
13906 		}
13907 
13908 		private __gshared EventMask rootEventMask;
13909 
13910 		/++
13911 			Requests the specified input from the root window on the connection, in addition to any other request.
13912 
13913 
13914 			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.
13915 
13916 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
13917 		+/
13918 		static void addRootInput(EventMask mask) {
13919 			auto old = rootEventMask;
13920 			rootEventMask |= mask;
13921 			get(); // to ensure display connected
13922 			if(display !is null && rootEventMask != old)
13923 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
13924 		}
13925 
13926 		static void discardState() {
13927 			freeImages();
13928 
13929 			foreach(atomPtr; interredAtoms)
13930 				*atomPtr = 0;
13931 			interredAtoms = null;
13932 			interredAtoms.assumeSafeAppend();
13933 
13934 			ScreenPainterImplementation.fontAttempted = false;
13935 			ScreenPainterImplementation.defaultfont = null;
13936 			ScreenPainterImplementation.defaultfontset = null;
13937 
13938 			Image.impl.xshmQueryCompleted = false;
13939 			Image.impl._xshmAvailable = false;
13940 
13941 			SimpleWindow.nativeMapping = null;
13942 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
13943 			// GlobalHotkeyManager
13944 
13945 			display = null;
13946 			xim = null;
13947 		}
13948 
13949 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
13950 		private static void createXIM () {
13951 			import core.stdc.locale : setlocale, LC_ALL;
13952 			import core.stdc.stdio : stderr, fprintf;
13953 			import core.stdc.stdlib : free;
13954 			import core.stdc.string : strdup;
13955 
13956 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
13957 
13958 			auto olocale = strdup(setlocale(LC_ALL, null));
13959 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
13960 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
13961 
13962 			//fprintf(stderr, "opening IM...\n");
13963 			foreach (string s; mtry) {
13964 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
13965 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
13966 			}
13967 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
13968 		}
13969 
13970 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
13971 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
13972 		static struct ImgList {
13973 			size_t img; // class; hide it from GC
13974 			ImgList* next;
13975 		}
13976 
13977 		static __gshared ImgList* imglist = null;
13978 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
13979 
13980 		static void registerImage (Image img) {
13981 			if (!imglistLocked && img !is null) {
13982 				import core.stdc.stdlib : malloc;
13983 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
13984 				assert(it !is null); // do proper checks
13985 				it.img = cast(size_t)cast(void*)img;
13986 				it.next = imglist;
13987 				imglist = it;
13988 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
13989 			}
13990 		}
13991 
13992 		static void unregisterImage (Image img) {
13993 			if (!imglistLocked && img !is null) {
13994 				import core.stdc.stdlib : free;
13995 				ImgList* prev = null;
13996 				ImgList* cur = imglist;
13997 				while (cur !is null) {
13998 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
13999 					prev = cur;
14000 					cur = cur.next;
14001 				}
14002 				if (cur !is null) {
14003 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14004 					free(cur);
14005 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14006 				} else {
14007 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14008 				}
14009 			}
14010 		}
14011 
14012 		static void freeImages () { // needed for discardAndRecreate
14013 			imglistLocked = true;
14014 			scope(exit) imglistLocked = false;
14015 			ImgList* cur = imglist;
14016 			ImgList* next = null;
14017 			while (cur !is null) {
14018 				import core.stdc.stdlib : free;
14019 				next = cur.next;
14020 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14021 				(cast(Image)cast(void*)cur.img).dispose();
14022 				free(cur);
14023 				cur = next;
14024 			}
14025 			imglist = null;
14026 		}
14027 
14028 		/// can be used to override normal handling of display name
14029 		/// from environment and/or command line
14030 		static setDisplayName(string newDisplayName) {
14031 			displayName = cast(char*) (newDisplayName ~ '\0');
14032 		}
14033 
14034 		/// resets to the default display string
14035 		static resetDisplayName() {
14036 			displayName = null;
14037 		}
14038 
14039 		///
14040 		static Display* get() {
14041 			if(display is null) {
14042 				if(!librariesSuccessfullyLoaded)
14043 					throw new Exception("Unable to load X11 client libraries");
14044 				display = XOpenDisplay(displayName);
14045 
14046 				isLocal_ = false;
14047 
14048 				connectionSequence_++;
14049 				if(display is null)
14050 					throw new Exception("Unable to open X display");
14051 
14052 				auto str = display.display_name;
14053 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14054 				// and otherwise it probably isn't
14055 				if(str is null || (str[0] != ':' && str[0] != '/'))
14056 					isLocal_ = false;
14057 				else
14058 					isLocal_ = true;
14059 
14060 				debug(sdpy_x_errors) {
14061 					XSetErrorHandler(&adrlogger);
14062 					XSynchronize(display, true);
14063 
14064 					extern(C) int wtf() {
14065 						if(errorHappened) {
14066 							asm { int 3; }
14067 							errorHappened = false;
14068 						}
14069 						return 0;
14070 					}
14071 					XSetAfterFunction(display, &wtf);
14072 				}
14073 
14074 
14075 				XSetIOErrorHandler(&x11ioerrCB);
14076 				Bool sup;
14077 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14078 				createXIM();
14079 				version(with_eventloop) {
14080 					import arsd.eventloop;
14081 					addFileEventListeners(display.fd, &eventListener, null, null);
14082 				}
14083 			}
14084 
14085 			return display;
14086 		}
14087 
14088 		extern(C)
14089 		static int x11ioerrCB(Display* dpy) {
14090 			throw new XDisconnectException(false);
14091 		}
14092 
14093 		version(with_eventloop) {
14094 			import arsd.eventloop;
14095 			static void eventListener(OsFileHandle fd) {
14096 				//this.mtLock();
14097 				//scope(exit) this.mtUnlock();
14098 				while(XPending(display))
14099 					doXNextEvent(display);
14100 			}
14101 		}
14102 
14103 		// close connection on program exit -- we need this to properly free all images
14104 		static ~this () {
14105 			// the gui thread must clean up after itself or else Xlib might deadlock
14106 			// using this flag on any thread destruction is the easiest way i know of
14107 			// (shared static this is run by the LAST thread to exit, which may not be
14108 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14109 			if(thisIsGuiThread)
14110 				close();
14111 		}
14112 
14113 		///
14114 		static void close() {
14115 			if(display is null)
14116 				return;
14117 
14118 			version(with_eventloop) {
14119 				import arsd.eventloop;
14120 				removeFileEventListeners(display.fd);
14121 			}
14122 
14123 			// now remove all registered images to prevent shared memory leaks
14124 			freeImages();
14125 
14126 			// tbh I don't know why it is doing this but like if this happens to run
14127 			// from the other thread there's frequent hanging inside here.
14128 			if(thisIsGuiThread)
14129 				XCloseDisplay(display);
14130 			display = null;
14131 		}
14132 	}
14133 
14134 	mixin template NativeImageImplementation() {
14135 		XImage* handle;
14136 		ubyte* rawData;
14137 
14138 		XShmSegmentInfo shminfo;
14139 
14140 		__gshared bool xshmQueryCompleted;
14141 		__gshared bool _xshmAvailable;
14142 		public static @property bool xshmAvailable() {
14143 			if(!xshmQueryCompleted) {
14144 				int i1, i2, i3;
14145 				xshmQueryCompleted = true;
14146 
14147 				if(!XDisplayConnection.isLocal)
14148 					_xshmAvailable = false;
14149 				else
14150 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14151 			}
14152 			return _xshmAvailable;
14153 		}
14154 
14155 		bool usingXshm;
14156 	final:
14157 
14158 		private __gshared bool xshmfailed;
14159 
14160 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14161 			auto display = XDisplayConnection.get();
14162 			assert(display !is null);
14163 			auto screen = DefaultScreen(display);
14164 
14165 			// it will only use shared memory for somewhat largish images,
14166 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14167 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14168 
14169 
14170 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14171 				// the actual use still fails. For example, if the program is in a container and permission denied
14172 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14173 				//
14174 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14175 
14176 
14177 				// synchronize so preexisting buffers are clear
14178 				XSync(display, false);
14179 				xshmfailed = false;
14180 
14181 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14182 
14183 
14184 				usingXshm = true;
14185 				handle = XShmCreateImage(
14186 					display,
14187 					DefaultVisual(display, screen),
14188 					enableAlpha ? 32: 24,
14189 					ImageFormat.ZPixmap,
14190 					null,
14191 					&shminfo,
14192 					width, height);
14193 				if(handle is null)
14194 					goto abortXshm1;
14195 
14196 				if(handle.bytes_per_line != 4 * width)
14197 					goto abortXshm2;
14198 
14199 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14200 				if(shminfo.shmid < 0)
14201 					goto abortXshm3;
14202 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14203 				if(rawData == cast(ubyte*) -1)
14204 					goto abortXshm4;
14205 				shminfo.readOnly = 0;
14206 				XShmAttach(display, &shminfo);
14207 
14208 				// and now to the final error check to ensure it actually worked.
14209 				XSync(display, false);
14210 				if(xshmfailed)
14211 					goto abortXshm5;
14212 
14213 				XSetErrorHandler(oldErrorHandler);
14214 
14215 				XDisplayConnection.registerImage(this);
14216 				// if I don't flush here there's a chance the dtor will run before the
14217 				// ctor and lead to a bad value X error. While this hurts the efficiency
14218 				// it is local anyway so prolly better to keep it simple
14219 				XFlush(display);
14220 
14221 				return;
14222 
14223 				abortXshm5:
14224 					shmdt(shminfo.shmaddr);
14225 					rawData = null;
14226 
14227 				abortXshm4:
14228 					shmctl(shminfo.shmid, IPC_RMID, null);
14229 
14230 				abortXshm3:
14231 					// nothing needed, the shmget failed so there's nothing to free
14232 
14233 				abortXshm2:
14234 					XDestroyImage(handle);
14235 					handle = null;
14236 
14237 				abortXshm1:
14238 					XSetErrorHandler(oldErrorHandler);
14239 					usingXshm = false;
14240 					handle = null;
14241 
14242 					shminfo = typeof(shminfo).init;
14243 
14244 					_xshmAvailable = false; // don't try again in the future
14245 
14246 					// writeln("fallingback");
14247 
14248 					goto fallback;
14249 
14250 			} else {
14251 				fallback:
14252 
14253 				if (forcexshm) throw new Exception("can't create XShm Image");
14254 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14255 				import core.stdc.stdlib : malloc;
14256 				rawData = cast(ubyte*) malloc(width * height * 4);
14257 
14258 				handle = XCreateImage(
14259 					display,
14260 					DefaultVisual(display, screen),
14261 					enableAlpha ? 32 : 24, // bpp
14262 					ImageFormat.ZPixmap,
14263 					0, // offset
14264 					rawData,
14265 					width, height,
14266 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14267 			}
14268 		}
14269 
14270 		void dispose() {
14271 			// note: this calls free(rawData) for us
14272 			if(handle) {
14273 				if (usingXshm) {
14274 					XDisplayConnection.unregisterImage(this);
14275 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14276 				}
14277 				XDestroyImage(handle);
14278 				if(usingXshm) {
14279 					shmdt(shminfo.shmaddr);
14280 					shmctl(shminfo.shmid, IPC_RMID, null);
14281 				}
14282 				handle = null;
14283 			}
14284 		}
14285 
14286 		Color getPixel(int x, int y) {
14287 			auto offset = (y * width + x) * 4;
14288 			Color c;
14289 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14290 			c.b = rawData[offset + 0];
14291 			c.g = rawData[offset + 1];
14292 			c.r = rawData[offset + 2];
14293 			if(enableAlpha)
14294 				c.unPremultiply;
14295 			return c;
14296 		}
14297 
14298 		void setPixel(int x, int y, Color c) {
14299 			if(enableAlpha)
14300 				c.premultiply();
14301 			auto offset = (y * width + x) * 4;
14302 			rawData[offset + 0] = c.b;
14303 			rawData[offset + 1] = c.g;
14304 			rawData[offset + 2] = c.r;
14305 			if(enableAlpha)
14306 				rawData[offset + 3] = c.a;
14307 		}
14308 
14309 		void convertToRgbaBytes(ubyte[] where) {
14310 			assert(where.length == this.width * this.height * 4);
14311 
14312 			// if rawData had a length....
14313 			//assert(rawData.length == where.length);
14314 			for(int idx = 0; idx < where.length; idx += 4) {
14315 				where[idx + 0] = rawData[idx + 2]; // r
14316 				where[idx + 1] = rawData[idx + 1]; // g
14317 				where[idx + 2] = rawData[idx + 0]; // b
14318 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14319 
14320 				if(enableAlpha)
14321 					unPremultiplyRgba(where[idx .. idx + 4]);
14322 			}
14323 		}
14324 
14325 		void setFromRgbaBytes(in ubyte[] where) {
14326 			assert(where.length == this.width * this.height * 4);
14327 
14328 			// if rawData had a length....
14329 			//assert(rawData.length == where.length);
14330 			for(int idx = 0; idx < where.length; idx += 4) {
14331 				rawData[idx + 2] = where[idx + 0]; // r
14332 				rawData[idx + 1] = where[idx + 1]; // g
14333 				rawData[idx + 0] = where[idx + 2]; // b
14334 				if(enableAlpha) {
14335 					rawData[idx + 3] = where[idx + 3]; // a
14336 					premultiplyBgra(rawData[idx .. idx + 4]);
14337 				}
14338 			}
14339 		}
14340 
14341 	}
14342 
14343 	mixin template NativeSimpleWindowImplementation() {
14344 		GC gc;
14345 		Window window;
14346 		Display* display;
14347 
14348 		Pixmap buffer;
14349 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14350 		XIC xic; // input context
14351 		int curHidden = 0; // counter
14352 		Cursor blankCurPtr = 0;
14353 		int cursorSequenceNumber = 0;
14354 		int warpEventCount = 0; // number of mouse movement events to eat
14355 
14356 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14357 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14358 
14359 		version(without_opengl) {} else
14360 		GLXContext glc;
14361 
14362 		private void fixFixedSize(bool forced=false) (int width, int height) {
14363 			if (forced || this.resizability == Resizability.fixedSize) {
14364 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14365 				XSizeHints sh;
14366 				static if (!forced) {
14367 					c_long spr;
14368 					XGetWMNormalHints(display, window, &sh, &spr);
14369 					sh.flags |= PMaxSize | PMinSize;
14370 				} else {
14371 					sh.flags = PMaxSize | PMinSize;
14372 				}
14373 				sh.min_width = width;
14374 				sh.min_height = height;
14375 				sh.max_width = width;
14376 				sh.max_height = height;
14377 				XSetWMNormalHints(display, window, &sh);
14378 				//XFlush(display);
14379 			}
14380 		}
14381 
14382 		ScreenPainter getPainter(bool manualInvalidations) {
14383 			return ScreenPainter(this, window, manualInvalidations);
14384 		}
14385 
14386 		void move(int x, int y) {
14387 			XMoveWindow(display, window, x, y);
14388 		}
14389 
14390 		void resize(int w, int h) {
14391 			if (w < 1) w = 1;
14392 			if (h < 1) h = 1;
14393 			XResizeWindow(display, window, w, h);
14394 
14395 			// calling this now to avoid waiting for the server to
14396 			// acknowledge the resize; draws without returning to the
14397 			// event loop will thus actually work. the server's event
14398 			// btw might overrule this and resize it again
14399 			recordX11Resize(display, this, w, h);
14400 
14401 			updateOpenglViewportIfNeeded(w, h);
14402 		}
14403 
14404 		void moveResize (int x, int y, int w, int h) {
14405 			if (w < 1) w = 1;
14406 			if (h < 1) h = 1;
14407 			XMoveResizeWindow(display, window, x, y, w, h);
14408 			updateOpenglViewportIfNeeded(w, h);
14409 		}
14410 
14411 		void hideCursor () {
14412 			if (curHidden++ == 0) {
14413 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14414 					static const(char)[1] cmbmp = 0;
14415 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14416 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14417 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14418 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14419 					XFreePixmap(display, pm);
14420 				}
14421 				XDefineCursor(display, window, blankCurPtr);
14422 			}
14423 		}
14424 
14425 		void showCursor () {
14426 			if (--curHidden == 0) XUndefineCursor(display, window);
14427 		}
14428 
14429 		void warpMouse (int x, int y) {
14430 			// here i will send dummy "ignore next mouse motion" event,
14431 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14432 			// and we don't need to report it to the user (as warping is
14433 			// used when the user needs movement deltas).
14434 			//XClientMessageEvent xclient;
14435 			XEvent e;
14436 			e.xclient.type = EventType.ClientMessage;
14437 			e.xclient.window = window;
14438 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14439 			e.xclient.format = 32;
14440 			e.xclient.data.l[0] = 0;
14441 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14442 			//{ 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]); }
14443 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14444 			// now warp pointer...
14445 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14446 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14447 			// ...and flush
14448 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14449 			XFlush(display);
14450 		}
14451 
14452 		void sendDummyEvent () {
14453 			// here i will send dummy event to ping event queue
14454 			XEvent e;
14455 			e.xclient.type = EventType.ClientMessage;
14456 			e.xclient.window = window;
14457 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14458 			e.xclient.format = 32;
14459 			e.xclient.data.l[0] = 0;
14460 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14461 			XFlush(display);
14462 		}
14463 
14464 		void setTitle(string title) {
14465 			if (title.ptr is null) title = "";
14466 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14467 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14468 			XTextProperty windowName;
14469 			windowName.value = title.ptr;
14470 			windowName.encoding = XA_UTF8; //XA_STRING;
14471 			windowName.format = 8;
14472 			windowName.nitems = cast(uint)title.length;
14473 			XSetWMName(display, window, &windowName);
14474 			char[1024] namebuf = 0;
14475 			auto maxlen = namebuf.length-1;
14476 			if (maxlen > title.length) maxlen = title.length;
14477 			namebuf[0..maxlen] = title[0..maxlen];
14478 			XStoreName(display, window, namebuf.ptr);
14479 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14480 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14481 		}
14482 
14483 		string[] getTitles() {
14484 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14485 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14486 			XTextProperty textProp;
14487 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14488 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14489 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14490 				} else
14491 					return [];
14492 			} else
14493 				return null;
14494 		}
14495 
14496 		string getTitle() {
14497 			auto titles = getTitles();
14498 			return titles.length ? titles[0] : null;
14499 		}
14500 
14501 		void setMinSize (int minwidth, int minheight) {
14502 			import core.stdc.config : c_long;
14503 			if (minwidth < 1) minwidth = 1;
14504 			if (minheight < 1) minheight = 1;
14505 			XSizeHints sh;
14506 			c_long spr;
14507 			XGetWMNormalHints(display, window, &sh, &spr);
14508 			sh.min_width = minwidth;
14509 			sh.min_height = minheight;
14510 			sh.flags |= PMinSize;
14511 			XSetWMNormalHints(display, window, &sh);
14512 			flushGui();
14513 		}
14514 
14515 		void setMaxSize (int maxwidth, int maxheight) {
14516 			import core.stdc.config : c_long;
14517 			if (maxwidth < 1) maxwidth = 1;
14518 			if (maxheight < 1) maxheight = 1;
14519 			XSizeHints sh;
14520 			c_long spr;
14521 			XGetWMNormalHints(display, window, &sh, &spr);
14522 			sh.max_width = maxwidth;
14523 			sh.max_height = maxheight;
14524 			sh.flags |= PMaxSize;
14525 			XSetWMNormalHints(display, window, &sh);
14526 			flushGui();
14527 		}
14528 
14529 		void setResizeGranularity (int granx, int grany) {
14530 			import core.stdc.config : c_long;
14531 			if (granx < 1) granx = 1;
14532 			if (grany < 1) grany = 1;
14533 			XSizeHints sh;
14534 			c_long spr;
14535 			XGetWMNormalHints(display, window, &sh, &spr);
14536 			sh.width_inc = granx;
14537 			sh.height_inc = grany;
14538 			sh.flags |= PResizeInc;
14539 			XSetWMNormalHints(display, window, &sh);
14540 			flushGui();
14541 		}
14542 
14543 		void setOpacity (uint opacity) {
14544 			arch_ulong o = opacity;
14545 			if (opacity == uint.max)
14546 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14547 			else
14548 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14549 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14550 		}
14551 
14552 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14553 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14554 			display = XDisplayConnection.get();
14555 			auto screen = DefaultScreen(display);
14556 
14557 			bool overrideRedirect = false;
14558 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14559 				overrideRedirect = true;
14560 
14561 			version(without_opengl) {}
14562 			else {
14563 				if(opengl == OpenGlOptions.yes) {
14564 					GLXFBConfig fbconf = null;
14565 					XVisualInfo* vi = null;
14566 					bool useLegacy = false;
14567 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14568 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14569 						int[23] visualAttribs = [
14570 							GLX_X_RENDERABLE , 1/*True*/,
14571 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14572 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14573 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14574 							GLX_RED_SIZE     , 8,
14575 							GLX_GREEN_SIZE   , 8,
14576 							GLX_BLUE_SIZE    , 8,
14577 							GLX_ALPHA_SIZE   , 8,
14578 							GLX_DEPTH_SIZE   , 24,
14579 							GLX_STENCIL_SIZE , 8,
14580 							GLX_DOUBLEBUFFER , 1/*True*/,
14581 							0/*None*/,
14582 						];
14583 						int fbcount;
14584 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14585 						if (fbcount == 0) {
14586 							useLegacy = true; // try to do at least something
14587 						} else {
14588 							// pick the FB config/visual with the most samples per pixel
14589 							int bestidx = -1, bestns = -1;
14590 							foreach (int fbi; 0..fbcount) {
14591 								int sb, samples;
14592 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14593 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14594 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14595 							}
14596 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14597 							fbconf = fbc[bestidx];
14598 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14599 							XFree(fbc);
14600 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14601 						}
14602 					}
14603 					if (vi is null || useLegacy) {
14604 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14605 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14606 						useLegacy = true;
14607 					}
14608 					if (vi is null) throw new Exception("no open gl visual found");
14609 
14610 					XSetWindowAttributes swa;
14611 					auto root = RootWindow(display, screen);
14612 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14613 
14614 					swa.override_redirect = overrideRedirect;
14615 
14616 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14617 						0, 0, width, height,
14618 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14619 
14620 					// now try to use `glXCreateContextAttribsARB()` if it's here
14621 					if (!useLegacy) {
14622 						// request fairly advanced context, even with stencil buffer!
14623 						int[9] contextAttribs = [
14624 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14625 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14626 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14627 							// for modern context, set "forward compatibility" flag too
14628 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14629 							0/*None*/,
14630 						];
14631 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14632 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14633 							sdpyOpenGLContextVersion = 0;
14634 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14635 						}
14636 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14637 					} else {
14638 						// fallback to old GLX call
14639 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14640 							sdpyOpenGLContextVersion = 0;
14641 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14642 						}
14643 					}
14644 					// sync to ensure any errors generated are processed
14645 					XSync(display, 0/*False*/);
14646 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14647 					if(glc is null)
14648 						throw new Exception("glc");
14649 				}
14650 			}
14651 
14652 			if(opengl == OpenGlOptions.no) {
14653 
14654 				XSetWindowAttributes swa;
14655 				swa.background_pixel = WhitePixel(display, screen);
14656 				swa.border_pixel = BlackPixel(display, screen);
14657 				swa.override_redirect = overrideRedirect;
14658 				auto root = RootWindow(display, screen);
14659 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14660 
14661 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14662 					0, 0, width, height,
14663 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14664 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14665 
14666 
14667 
14668 				/*
14669 				window = XCreateSimpleWindow(
14670 					display,
14671 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14672 					0, 0, // x, y
14673 					width, height,
14674 					1, // border width
14675 					BlackPixel(display, screen), // border
14676 					WhitePixel(display, screen)); // background
14677 				*/
14678 
14679 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14680 				bufferw = width;
14681 				bufferh = height;
14682 
14683 				gc = DefaultGC(display, screen);
14684 
14685 				// clear out the buffer to get us started...
14686 				XSetForeground(display, gc, WhitePixel(display, screen));
14687 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14688 				XSetForeground(display, gc, BlackPixel(display, screen));
14689 			}
14690 
14691 			// input context
14692 			//TODO: create this only for top-level windows, and reuse that?
14693 			populateXic();
14694 
14695 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14696 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14697 			// window class
14698 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14699 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14700 				XClassHint klass;
14701 				XWMHints wh;
14702 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14703 					wh.input = true;
14704 					wh.flags |= InputHint;
14705 				}
14706 				XSizeHints size;
14707 				klass.res_name = sdpyWindowClassStr;
14708 				klass.res_class = sdpyWindowClassStr;
14709 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14710 			}
14711 
14712 			setTitle(title);
14713 			SimpleWindow.nativeMapping[window] = this;
14714 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14715 
14716 			// This gives our window a close button
14717 			if (windowType != WindowTypes.eventOnly) {
14718 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14719 				int useAtoms;
14720 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14721 					useAtoms = 2;
14722 				} else {
14723 					useAtoms = 1;
14724 				}
14725 				assert(useAtoms <= atoms.length);
14726 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14727 			}
14728 
14729 			// FIXME: windowType and customizationFlags
14730 			Atom[8] wsatoms; // here, due to goto
14731 			int wmsacount = 0; // here, due to goto
14732 
14733 			try
14734 			final switch(windowType) {
14735 				case WindowTypes.normal:
14736 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14737 				break;
14738 				case WindowTypes.undecorated:
14739 					motifHideDecorations();
14740 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14741 				break;
14742 				case WindowTypes.eventOnly:
14743 					_hidden = true;
14744 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14745 					goto hiddenWindow;
14746 				//break;
14747 				case WindowTypes.nestedChild:
14748 					// handled in XCreateWindow calls
14749 				break;
14750 
14751 				case WindowTypes.dropdownMenu:
14752 					motifHideDecorations();
14753 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14754 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14755 				break;
14756 				case WindowTypes.popupMenu:
14757 					motifHideDecorations();
14758 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14759 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14760 				break;
14761 				case WindowTypes.notification:
14762 					motifHideDecorations();
14763 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14764 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14765 				break;
14766 				case WindowTypes.minimallyWrapped:
14767 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14768 				/+
14769 				case WindowTypes.menu:
14770 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14771 					motifHideDecorations();
14772 				break;
14773 				case WindowTypes.desktop:
14774 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14775 				break;
14776 				case WindowTypes.dock:
14777 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14778 				break;
14779 				case WindowTypes.toolbar:
14780 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14781 				break;
14782 				case WindowTypes.menu:
14783 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14784 				break;
14785 				case WindowTypes.utility:
14786 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14787 				break;
14788 				case WindowTypes.splash:
14789 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14790 				break;
14791 				case WindowTypes.dialog:
14792 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14793 				break;
14794 				case WindowTypes.tooltip:
14795 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14796 				break;
14797 				case WindowTypes.notification:
14798 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14799 				break;
14800 				case WindowTypes.combo:
14801 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14802 				break;
14803 				case WindowTypes.dnd:
14804 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14805 				break;
14806 				+/
14807 			}
14808 			catch(Exception e) {
14809 				// XInternAtom failed, prolly a WM
14810 				// that doesn't support these things
14811 			}
14812 
14813 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14814 			// the two following flags may be ignored by WM
14815 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14816 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14817 
14818 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14819 
14820 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14821 
14822 			// What would be ideal here is if they only were
14823 			// selected if there was actually an event handler
14824 			// for them...
14825 
14826 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14827 
14828 			hiddenWindow:
14829 
14830 			// set the pid property for lookup later by window managers
14831 			// a standard convenience
14832 			import core.sys.posix.unistd;
14833 			arch_ulong pid = getpid();
14834 
14835 			XChangeProperty(
14836 				display,
14837 				impl.window,
14838 				GetAtom!("_NET_WM_PID", true)(display),
14839 				XA_CARDINAL,
14840 				32 /* bits */,
14841 				0 /*PropModeReplace*/,
14842 				&pid,
14843 				1);
14844 
14845 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
14846 				if(parent is null) assert(0);
14847 				XChangeProperty(
14848 					display,
14849 					impl.window,
14850 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
14851 					XA_WINDOW,
14852 					32 /* bits */,
14853 					0 /*PropModeReplace*/,
14854 					&parent.impl.window,
14855 					1);
14856 
14857 			}
14858 
14859 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
14860 				XMapWindow(display, window);
14861 			} else {
14862 				_hidden = true;
14863 			}
14864 		}
14865 
14866 		void populateXic() {
14867 			if (XDisplayConnection.xim !is null) {
14868 				xic = XCreateIC(XDisplayConnection.xim,
14869 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
14870 						/*XNClientWindow*/"clientWindow".ptr, window,
14871 						/*XNFocusWindow*/"focusWindow".ptr, window,
14872 						null);
14873 				if (xic is null) {
14874 					import core.stdc.stdio : stderr, fprintf;
14875 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
14876 				}
14877 			}
14878 		}
14879 
14880 		void selectDefaultInput(bool forceIncludeMouseMotion) {
14881 			auto mask = EventMask.ExposureMask |
14882 				EventMask.KeyPressMask |
14883 				EventMask.KeyReleaseMask |
14884 				EventMask.PropertyChangeMask |
14885 				EventMask.FocusChangeMask |
14886 				EventMask.StructureNotifyMask |
14887 				EventMask.SubstructureNotifyMask |
14888 				EventMask.VisibilityChangeMask
14889 				| EventMask.ButtonPressMask
14890 				| EventMask.ButtonReleaseMask
14891 			;
14892 
14893 			// xshm is our shortcut for local connections
14894 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
14895 				mask |= EventMask.PointerMotionMask;
14896 			else
14897 				mask |= EventMask.ButtonMotionMask;
14898 
14899 			XSelectInput(display, window, mask);
14900 		}
14901 
14902 
14903 		void setNetWMWindowType(Atom type) {
14904 			Atom[2] atoms;
14905 
14906 			atoms[0] = type;
14907 			// generic fallback
14908 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
14909 
14910 			XChangeProperty(
14911 				display,
14912 				impl.window,
14913 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
14914 				XA_ATOM,
14915 				32 /* bits */,
14916 				0 /*PropModeReplace*/,
14917 				atoms.ptr,
14918 				cast(int) atoms.length);
14919 		}
14920 
14921 		void motifHideDecorations(bool hide = true) {
14922 			MwmHints hints;
14923 			hints.flags = MWM_HINTS_DECORATIONS;
14924 			hints.decorations = hide ? 0 : 1;
14925 
14926 			XChangeProperty(
14927 				display,
14928 				impl.window,
14929 				GetAtom!"_MOTIF_WM_HINTS"(display),
14930 				GetAtom!"_MOTIF_WM_HINTS"(display),
14931 				32 /* bits */,
14932 				0 /*PropModeReplace*/,
14933 				&hints,
14934 				hints.sizeof / 4);
14935 		}
14936 
14937 		/*k8: unused
14938 		void createOpenGlContext() {
14939 
14940 		}
14941 		*/
14942 
14943 		void closeWindow() {
14944 			// I can't close this or a child window closing will
14945 			// break events for everyone. So I'm just leaking it right
14946 			// now and that is probably perfectly fine...
14947 			version(none)
14948 			if (customEventFDRead != -1) {
14949 				import core.sys.posix.unistd : close;
14950 				auto same = customEventFDRead == customEventFDWrite;
14951 
14952 				close(customEventFDRead);
14953 				if(!same)
14954 					close(customEventFDWrite);
14955 				customEventFDRead = -1;
14956 				customEventFDWrite = -1;
14957 			}
14958 
14959 			if(glc !is null) {
14960 				glXDestroyContext(display, glc);
14961 				glc = null;
14962 			}
14963 
14964 			if(buffer)
14965 				XFreePixmap(display, buffer);
14966 			bufferw = bufferh = 0;
14967 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
14968 			XDestroyWindow(display, window);
14969 			XFlush(display);
14970 		}
14971 
14972 		void dispose() {
14973 		}
14974 
14975 		bool destroyed = false;
14976 	}
14977 
14978 	bool insideXEventLoop;
14979 }
14980 
14981 version(X11) {
14982 
14983 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
14984 
14985 	private class ResizeEvent {
14986 		int width, height;
14987 	}
14988 
14989 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
14990 		if(win.windowType == WindowTypes.minimallyWrapped)
14991 			return;
14992 
14993 		if(win.pendingResizeEvent is null) {
14994 			win.pendingResizeEvent = new ResizeEvent();
14995 			win.addEventListener((ResizeEvent re) {
14996 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
14997 			});
14998 		}
14999 		win.pendingResizeEvent.width = width;
15000 		win.pendingResizeEvent.height = height;
15001 		if(!win.eventQueued!ResizeEvent) {
15002 			win.postEvent(win.pendingResizeEvent);
15003 		}
15004 	}
15005 
15006 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15007 		if(win.windowType == WindowTypes.minimallyWrapped)
15008 			return;
15009 		if(win.closed)
15010 			return;
15011 
15012 		if(width != win.width || height != win.height) {
15013 
15014 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15015 			win._width = width;
15016 			win._height = height;
15017 
15018 			if(win.openglMode == OpenGlOptions.no) {
15019 				// FIXME: could this be more efficient?
15020 
15021 				if (win.bufferw < width || win.bufferh < height) {
15022 					//{ 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); }
15023 					// grow the internal buffer to match the window...
15024 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15025 					{
15026 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15027 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15028 						scope(exit) XFreeGC(win.display, xgc);
15029 						XSetClipMask(win.display, xgc, None);
15030 						XSetForeground(win.display, xgc, 0);
15031 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15032 					}
15033 					XCopyArea(display,
15034 						cast(Drawable) win.buffer,
15035 						cast(Drawable) newPixmap,
15036 						win.gc, 0, 0,
15037 						win.bufferw < width ? win.bufferw : win.width,
15038 						win.bufferh < height ? win.bufferh : win.height,
15039 						0, 0);
15040 
15041 					XFreePixmap(display, win.buffer);
15042 					win.buffer = newPixmap;
15043 					win.bufferw = width;
15044 					win.bufferh = height;
15045 				}
15046 
15047 				// clear unused parts of the buffer
15048 				if (win.bufferw > width || win.bufferh > height) {
15049 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15050 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15051 					scope(exit) XFreeGC(win.display, xgc);
15052 					XSetClipMask(win.display, xgc, None);
15053 					XSetForeground(win.display, xgc, 0);
15054 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15055 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15056 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15057 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15058 				}
15059 
15060 			}
15061 
15062 			win.updateOpenglViewportIfNeeded(width, height);
15063 
15064 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15065 
15066 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15067 			if(win.windowResized !is null) {
15068 				XUnlockDisplay(display);
15069 				scope(exit) XLockDisplay(display);
15070 				win.windowResized(width, height);
15071 			}
15072 		}
15073 	}
15074 
15075 
15076 	/// Platform-specific, you might use it when doing a custom event loop.
15077 	bool doXNextEvent(Display* display) {
15078 		bool done;
15079 		XEvent e;
15080 		XNextEvent(display, &e);
15081 		version(sddddd) {
15082 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15083 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15084 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15085 			}
15086 		}
15087 
15088 		// filter out compose events
15089 		if (XFilterEvent(&e, None)) {
15090 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15091 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15092 			return false;
15093 		}
15094 		// process keyboard mapping changes
15095 		if (e.type == EventType.KeymapNotify) {
15096 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15097 			XRefreshKeyboardMapping(&e.xmapping);
15098 			return false;
15099 		}
15100 
15101 		version(with_eventloop)
15102 			import arsd.eventloop;
15103 
15104 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15105 			// see windows impl's comments
15106 			XUnlockDisplay(display);
15107 			scope(exit) XLockDisplay(display);
15108 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15109 			if(ret == 0)
15110 				return done;
15111 		}
15112 
15113 
15114 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15115 			if(win.getNativeEventHandler !is null) {
15116 				XUnlockDisplay(display);
15117 				scope(exit) XLockDisplay(display);
15118 				auto ret = win.getNativeEventHandler()(e);
15119 				if(ret == 0)
15120 					return done;
15121 			}
15122 		}
15123 
15124 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15125 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15126 				// we get this because of the RRScreenChangeNotifyMask
15127 
15128 				// this isn't actually an ideal way to do it since it wastes time
15129 				// but meh it is simple and it works.
15130 				win.actualDpiLoadAttempted = false;
15131 				SimpleWindow.xRandrInfoLoadAttemped = false;
15132 				win.updateActualDpi(); // trigger a reload
15133 			}
15134 		}
15135 
15136 		switch(e.type) {
15137 		  case EventType.SelectionClear:
15138 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15139 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15140 				// writeln("SelectionClear");
15141 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15142 			}
15143 		  break;
15144 		  case EventType.SelectionRequest:
15145 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15146 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15147 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15148 				XUnlockDisplay(display);
15149 				scope(exit) XLockDisplay(display);
15150 				(*ssh).handleRequest(e);
15151 			}
15152 		  break;
15153 		  case EventType.PropertyNotify:
15154 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15155 
15156 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15157 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15158 					ssh.sendMoreIncr(&e.xproperty);
15159 			}
15160 
15161 
15162 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15163 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15164 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15165 					Atom target;
15166 					int format;
15167 					arch_ulong bytesafter, length;
15168 					void* value;
15169 
15170 					ubyte[] s;
15171 					Atom targetToKeep;
15172 
15173 					XGetWindowProperty(
15174 						e.xproperty.display,
15175 						e.xproperty.window,
15176 						e.xproperty.atom,
15177 						0,
15178 						100000 /* length */,
15179 						true, /* erase it to signal we got it and want more */
15180 						0 /*AnyPropertyType*/,
15181 						&target, &format, &length, &bytesafter, &value);
15182 
15183 					if(!targetToKeep)
15184 						targetToKeep = target;
15185 
15186 					auto id = (cast(ubyte*) value)[0 .. length];
15187 
15188 					handler.handleIncrData(targetToKeep, id);
15189 
15190 					XFree(value);
15191 				}
15192 			}
15193 		  break;
15194 		  case EventType.SelectionNotify:
15195 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15196 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15197 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15198 					XUnlockDisplay(display);
15199 					scope(exit) XLockDisplay(display);
15200 					handler.handleData(None, null);
15201 				} else {
15202 					Atom target;
15203 					int format;
15204 					arch_ulong bytesafter, length;
15205 					void* value;
15206 					XGetWindowProperty(
15207 						e.xselection.display,
15208 						e.xselection.requestor,
15209 						e.xselection.property,
15210 						0,
15211 						100000 /* length */,
15212 						//false, /* don't erase it */
15213 						true, /* do erase it lol */
15214 						0 /*AnyPropertyType*/,
15215 						&target, &format, &length, &bytesafter, &value);
15216 
15217 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15218 
15219 					{
15220 						XUnlockDisplay(display);
15221 						scope(exit) XLockDisplay(display);
15222 
15223 						if(target == XA_ATOM) {
15224 							// initial request, see what they are able to work with and request the best one
15225 							// we can handle, if available
15226 
15227 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15228 							Atom best = handler.findBestFormat(answer);
15229 
15230 							/+
15231 							writeln("got ", answer);
15232 							foreach(a; answer)
15233 								printf("%s\n", XGetAtomName(display, a));
15234 							writeln("best ", best);
15235 							+/
15236 
15237 							if(best != None) {
15238 								// actually request the best format
15239 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15240 							}
15241 						} else if(target == GetAtom!"INCR"(display)) {
15242 							// incremental
15243 
15244 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15245 
15246 							// signal the sending program that we see
15247 							// the incr and are ready to receive more.
15248 							XDeleteProperty(
15249 								e.xselection.display,
15250 								e.xselection.requestor,
15251 								e.xselection.property);
15252 						} else {
15253 							// unsupported type... maybe, forward
15254 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15255 						}
15256 					}
15257 					XFree(value);
15258 					/*
15259 					XDeleteProperty(
15260 						e.xselection.display,
15261 						e.xselection.requestor,
15262 						e.xselection.property);
15263 					*/
15264 				}
15265 			}
15266 		  break;
15267 		  case EventType.ConfigureNotify:
15268 			auto event = e.xconfigure;
15269 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15270 				if(win.windowType == WindowTypes.minimallyWrapped)
15271 					break;
15272 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15273 
15274 				/+
15275 					The ICCCM says window managers must send a synthetic event when the window
15276 					is moved but NOT when it is resized. In the resize case, an event is sent
15277 					with position (0, 0) which can be wrong and break the dpi calculations.
15278 
15279 					So we only consider the synthetic events from the WM and otherwise
15280 					need to wait for some other event to get the position which... sucks.
15281 
15282 					I'd rather not have windows changing their layout on mouse motion after
15283 					switching monitors... might be forced to but for now just ignoring it.
15284 
15285 					Easiest way to switch monitors without sending a size position is by
15286 					maximize or fullscreen in a setup like mine, but on most setups those
15287 					work on the monitor it is already living on, so it should be ok most the
15288 					time.
15289 				+/
15290 				if(event.send_event) {
15291 					win.screenPositionKnown = true;
15292 					win.screenPositionX = event.x;
15293 					win.screenPositionY = event.y;
15294 					win.updateActualDpi();
15295 				}
15296 
15297 				win.updateIMEPopupLocation();
15298 				recordX11ResizeAsync(display, *win, event.width, event.height);
15299 			}
15300 		  break;
15301 		  case EventType.Expose:
15302 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15303 				if(win.windowType == WindowTypes.minimallyWrapped)
15304 					break;
15305 				// if it is closing from a popup menu, it can get
15306 				// an Expose event right by the end and trigger a
15307 				// BadDrawable error ... we'll just check
15308 				// closed to handle that.
15309 				if((*win).closed) break;
15310 				if((*win).openglMode == OpenGlOptions.no) {
15311 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15312 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15313 					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);
15314 				} else {
15315 					// need to redraw the scene somehow
15316 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15317 						XUnlockDisplay(display);
15318 						scope(exit) XLockDisplay(display);
15319 						version(without_opengl) {} else
15320 						win.redrawOpenGlSceneSoon();
15321 					}
15322 				}
15323 			}
15324 		  break;
15325 		  case EventType.FocusIn:
15326 		  case EventType.FocusOut:
15327 
15328 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15329 				/+
15330 
15331 				void info(string detail) {
15332 					string s;
15333 					// import std.conv;
15334 					// import std.datetime;
15335 					s ~= to!string(Clock.currTime);
15336 					s ~= " ";
15337 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15338 					s ~= " ";
15339 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15340 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15341 					s ~= detail;
15342 					s ~= " ";
15343 
15344 					sdpyPrintDebugString(s);
15345 
15346 				}
15347 
15348 				switch(e.xfocus.detail) {
15349 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15350 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15351 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15352 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15353 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15354 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15355 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15356 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15357 					default:
15358 
15359 				}
15360 				+/
15361 
15362 
15363 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15364 					break; // just ignore these they seem irrelevant
15365 
15366 				auto old = win._focused;
15367 				win._focused = e.type == EventType.FocusIn;
15368 
15369 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15370 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15371 					win._focused = true;
15372 
15373 				if(win.demandingAttention)
15374 					demandAttention(*win, false);
15375 
15376 				win.updateIMEFocused();
15377 
15378 				if(old != win._focused && win.onFocusChange) {
15379 					XUnlockDisplay(display);
15380 					scope(exit) XLockDisplay(display);
15381 					win.onFocusChange(win._focused);
15382 				}
15383 			}
15384 		  break;
15385 		  case EventType.VisibilityNotify:
15386 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15387 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15388 						if (win.visibilityChanged !is null) {
15389 								XUnlockDisplay(display);
15390 								scope(exit) XLockDisplay(display);
15391 								win.visibilityChanged(false);
15392 							}
15393 					} else {
15394 						if (win.visibilityChanged !is null) {
15395 							XUnlockDisplay(display);
15396 							scope(exit) XLockDisplay(display);
15397 							win.visibilityChanged(true);
15398 						}
15399 					}
15400 				}
15401 				break;
15402 		  case EventType.ClientMessage:
15403 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15404 					// "ignore next mouse motion" event, increment ignore counter for teh window
15405 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15406 						++(*win).warpEventCount;
15407 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15408 					} else {
15409 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15410 					}
15411 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15412 					// user clicked the close button on the window manager
15413 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15414 						XUnlockDisplay(display);
15415 						scope(exit) XLockDisplay(display);
15416 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15417 					}
15418 
15419 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15420 					// writeln("HAPPENED");
15421 					// user clicked the close button on the window manager
15422 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15423 						XUnlockDisplay(display);
15424 						scope(exit) XLockDisplay(display);
15425 
15426 						auto setTo = *win;
15427 
15428 						if(win.setRequestedInputFocus !is null) {
15429 							auto s = win.setRequestedInputFocus();
15430 							if(s !is null) {
15431 								setTo = s;
15432 							}
15433 						}
15434 
15435 						assert(setTo !is null);
15436 
15437 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15438 
15439 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15440 					}
15441 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15442 					foreach(nai; NotificationAreaIcon.activeIcons)
15443 						nai.newManager();
15444 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15445 
15446 					bool xDragWindow = true;
15447 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15448 						//XDefineCursor(display, xDragWindow.impl.window,
15449 							//writeln("XdndStatus ", e.xclient.data.l);
15450 					}
15451 					if(auto dh = win.dropHandler) {
15452 
15453 						static Atom[3] xFormatsBuffer;
15454 						static Atom[] xFormats;
15455 
15456 						void resetXFormats() {
15457 							xFormatsBuffer[] = 0;
15458 							xFormats = xFormatsBuffer[];
15459 						}
15460 
15461 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15462 							// on Windows it is supposed to return the effect you actually do FIXME
15463 
15464 							auto sourceWindow =  e.xclient.data.l[0];
15465 
15466 							xFormatsBuffer[0] = e.xclient.data.l[2];
15467 							xFormatsBuffer[1] = e.xclient.data.l[3];
15468 							xFormatsBuffer[2] = e.xclient.data.l[4];
15469 
15470 							if(e.xclient.data.l[1] & 1) {
15471 								// can just grab it all but like we don't necessarily need them...
15472 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15473 							} else {
15474 								int len;
15475 								foreach(fmt; xFormatsBuffer)
15476 									if(fmt) len++;
15477 								xFormats = xFormatsBuffer[0 .. len];
15478 							}
15479 
15480 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15481 
15482 							dh.dragEnter(&pkg);
15483 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15484 
15485 							auto pack = e.xclient.data.l[2];
15486 
15487 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15488 
15489 
15490 							XClientMessageEvent xclient;
15491 
15492 							xclient.type = EventType.ClientMessage;
15493 							xclient.window = e.xclient.data.l[0];
15494 							xclient.message_type = GetAtom!"XdndStatus"(display);
15495 							xclient.format = 32;
15496 							xclient.data.l[0] = win.impl.window;
15497 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15498 							auto r = result.consistentWithin;
15499 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15500 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15501 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15502 
15503 							XSendEvent(
15504 								display,
15505 								e.xclient.data.l[0],
15506 								false,
15507 								EventMask.NoEventMask,
15508 								cast(XEvent*) &xclient
15509 							);
15510 
15511 
15512 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15513 							//writeln("XdndLeave");
15514 							// drop cancelled.
15515 							// data.l[0] is the source window
15516 							dh.dragLeave();
15517 
15518 							resetXFormats();
15519 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15520 							// drop happening, should fetch data, then send finished
15521 							// writeln("XdndDrop");
15522 
15523 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15524 
15525 							dh.drop(&pkg);
15526 
15527 							resetXFormats();
15528 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15529 							// writeln("XdndFinished");
15530 
15531 							dh.finish();
15532 						}
15533 
15534 					}
15535 				}
15536 		  break;
15537 		  case EventType.MapNotify:
15538 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15539 					(*win)._visible = true;
15540 					if (!(*win)._visibleForTheFirstTimeCalled) {
15541 						(*win)._visibleForTheFirstTimeCalled = true;
15542 						if ((*win).visibleForTheFirstTime !is null) {
15543 							XUnlockDisplay(display);
15544 							scope(exit) XLockDisplay(display);
15545 							(*win).visibleForTheFirstTime();
15546 						}
15547 					}
15548 					if ((*win).visibilityChanged !is null) {
15549 						XUnlockDisplay(display);
15550 						scope(exit) XLockDisplay(display);
15551 						(*win).visibilityChanged(true);
15552 					}
15553 				}
15554 		  break;
15555 		  case EventType.UnmapNotify:
15556 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15557 					win._visible = false;
15558 					if (win.visibilityChanged !is null) {
15559 						XUnlockDisplay(display);
15560 						scope(exit) XLockDisplay(display);
15561 						win.visibilityChanged(false);
15562 					}
15563 			}
15564 		  break;
15565 		  case EventType.DestroyNotify:
15566 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15567 				if(win.destroyed)
15568 					break; // might get a notification both for itself and from its parent
15569 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15570 				win._closed = true; // just in case
15571 				win.destroyed = true;
15572 				if (win.xic !is null) {
15573 					XDestroyIC(win.xic);
15574 					win.xic = null; // just in case
15575 				}
15576 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15577 				bool anyImportant = false;
15578 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15579 					if(w.beingOpenKeepsAppOpen) {
15580 						anyImportant = true;
15581 						break;
15582 					}
15583 				if(!anyImportant) {
15584 					EventLoop.quitApplication();
15585 					done = true;
15586 				}
15587 			}
15588 			auto window = e.xdestroywindow.window;
15589 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15590 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15591 
15592 			version(with_eventloop) {
15593 				if(done) exit();
15594 			}
15595 		  break;
15596 
15597 		  case EventType.MotionNotify:
15598 			MouseEvent mouse;
15599 			auto event = e.xmotion;
15600 
15601 			mouse.type = MouseEventType.motion;
15602 			mouse.x = event.x;
15603 			mouse.y = event.y;
15604 			mouse.modifierState = event.state;
15605 
15606 			mouse.timestamp = event.time;
15607 
15608 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15609 				mouse.window = *win;
15610 				if (win.warpEventCount > 0) {
15611 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15612 					--(*win).warpEventCount;
15613 					(*win).mdx(mouse); // so deltas will be correctly updated
15614 				} else {
15615 					win.warpEventCount = 0; // just in case
15616 					(*win).mdx(mouse);
15617 					if((*win).handleMouseEvent) {
15618 						XUnlockDisplay(display);
15619 						scope(exit) XLockDisplay(display);
15620 						(*win).handleMouseEvent(mouse);
15621 					}
15622 				}
15623 			}
15624 
15625 		  	version(with_eventloop)
15626 				send(mouse);
15627 		  break;
15628 		  case EventType.ButtonPress:
15629 		  case EventType.ButtonRelease:
15630 			MouseEvent mouse;
15631 			auto event = e.xbutton;
15632 
15633 			mouse.timestamp = event.time;
15634 
15635 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15636 			mouse.x = event.x;
15637 			mouse.y = event.y;
15638 
15639 			static Time lastMouseDownTime = 0;
15640 			static int lastMouseDownButton = -1;
15641 
15642 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15643 			if(e.type == EventType.ButtonPress) {
15644 				lastMouseDownTime = event.time;
15645 				lastMouseDownButton = event.button;
15646 			}
15647 
15648 			switch(event.button) {
15649 				case 1: mouse.button = MouseButton.left; break; // left
15650 				case 2: mouse.button = MouseButton.middle; break; // middle
15651 				case 3: mouse.button = MouseButton.right; break; // right
15652 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15653 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15654 				case 6: break; // idk
15655 				case 7: break; // idk
15656 				case 8: mouse.button = MouseButton.backButton; break;
15657 				case 9: mouse.button = MouseButton.forwardButton; break;
15658 				default:
15659 			}
15660 
15661 			// FIXME: double check this
15662 			mouse.modifierState = event.state;
15663 
15664 			//mouse.modifierState = event.detail;
15665 
15666 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15667 				mouse.window = *win;
15668 				(*win).mdx(mouse);
15669 				if((*win).handleMouseEvent) {
15670 					XUnlockDisplay(display);
15671 					scope(exit) XLockDisplay(display);
15672 					(*win).handleMouseEvent(mouse);
15673 				}
15674 			}
15675 			version(with_eventloop)
15676 				send(mouse);
15677 		  break;
15678 
15679 		  case EventType.KeyPress:
15680 		  case EventType.KeyRelease:
15681 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15682 			KeyEvent ke;
15683 			ke.pressed = e.type == EventType.KeyPress;
15684 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15685 
15686 			auto sym = XKeycodeToKeysym(
15687 				XDisplayConnection.get(),
15688 				e.xkey.keycode,
15689 				0);
15690 
15691 			ke.key = cast(Key) sym;//e.xkey.keycode;
15692 
15693 			ke.modifierState = e.xkey.state;
15694 
15695 			// writefln("%x", sym);
15696 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15697 			int charbuflen = 0; // return value of XwcLookupString
15698 			if (ke.pressed) {
15699 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15700 				if (win !is null && win.xic !is null) {
15701 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15702 					Status status;
15703 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15704 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15705 				} else {
15706 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15707 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15708 					char[16] buffer;
15709 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15710 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15711 				}
15712 			}
15713 
15714 			// if there's no char, subst one
15715 			if (charbuflen == 0) {
15716 				switch (sym) {
15717 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15718 					case 0xff8d: // keypad enter
15719 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15720 					default : // ignore
15721 				}
15722 			}
15723 
15724 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15725 				ke.window = *win;
15726 
15727 
15728 				if(win.inputProxy)
15729 					win = &win.inputProxy;
15730 
15731 				// char events are separate since they are on Windows too
15732 				// also, xcompose can generate long char sequences
15733 				// don't send char events if Meta and/or Hyper is pressed
15734 				// TODO: ctrl+char should only send control chars; not yet
15735 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15736 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15737 				}
15738 
15739 				dchar[32] charsComingBuffer;
15740 				int charsComingPosition;
15741 				dchar[] charsComing = charsComingBuffer[];
15742 
15743 				if (ke.pressed && charbuflen > 0) {
15744 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15745 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15746 						if(charsComingPosition >= charsComing.length)
15747 							charsComing.length = charsComingPosition + 8;
15748 
15749 						charsComing[charsComingPosition++] = ch;
15750 					}
15751 
15752 					charsComing = charsComing[0 .. charsComingPosition];
15753 				} else {
15754 					charsComing = null;
15755 				}
15756 
15757 				ke.charsPossible = charsComing;
15758 
15759 				if (win.handleKeyEvent) {
15760 					XUnlockDisplay(display);
15761 					scope(exit) XLockDisplay(display);
15762 					win.handleKeyEvent(ke);
15763 				}
15764 
15765 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15766 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15767 					XUnlockDisplay(display);
15768 					scope(exit) XLockDisplay(display);
15769 					foreach(ch; charsComing)
15770 						win.handleCharEvent(ch);
15771 				}
15772 			}
15773 
15774 			version(with_eventloop)
15775 				send(ke);
15776 		  break;
15777 		  default:
15778 		}
15779 
15780 		return done;
15781 	}
15782 }
15783 
15784 /* *************************************** */
15785 /*      Done with simpledisplay stuff      */
15786 /* *************************************** */
15787 
15788 // Necessary C library bindings follow
15789 version(Windows) {} else
15790 version(X11) {
15791 
15792 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15793 
15794 // X11 bindings needed here
15795 /*
15796 	A little of this is from the bindings project on
15797 	D Source and some of it is copy/paste from the C
15798 	header.
15799 
15800 	The DSource listing consistently used D's long
15801 	where C used long. That's wrong - C long is 32 bit, so
15802 	it should be int in D. I changed that here.
15803 
15804 	Note:
15805 	This isn't complete, just took what I needed for myself.
15806 */
15807 
15808 import core.stdc.stddef : wchar_t;
15809 
15810 interface XLib {
15811 extern(C) nothrow @nogc {
15812 	char* XResourceManagerString(Display*);
15813 	void XrmInitialize();
15814 	XrmDatabase XrmGetStringDatabase(char* data);
15815 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15816 
15817 	Cursor XCreateFontCursor(Display*, uint shape);
15818 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15819 	int XUndefineCursor(Display* display, Window w);
15820 
15821 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15822 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15823 	int XFreeCursor(Display* display, Cursor cursor);
15824 
15825 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
15826 
15827 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
15828 
15829 	XVaNestedList XVaCreateNestedList(int unused, ...);
15830 
15831 	char *XKeysymToString(KeySym keysym);
15832 	KeySym XKeycodeToKeysym(
15833 		Display*		/* display */,
15834 		KeyCode		/* keycode */,
15835 		int			/* index */
15836 	);
15837 
15838 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
15839 
15840 	int XFree(void*);
15841 	int XDeleteProperty(Display *display, Window w, Atom property);
15842 
15843 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
15844 
15845 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
15846 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
15847 		*actual_type_return, int *actual_format_return, arch_ulong
15848 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
15849 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
15850 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
15851 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
15852 
15853 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
15854 
15855 	Window XGetSelectionOwner(Display *display, Atom selection);
15856 
15857 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
15858 
15859 	char** XListFonts(Display*, const char*, int, int*);
15860 	void XFreeFontNames(char**);
15861 
15862 	Display* XOpenDisplay(const char*);
15863 	int XCloseDisplay(Display*);
15864 
15865 	int function() XSynchronize(Display*, bool);
15866 	int function() XSetAfterFunction(Display*, int function() proc);
15867 
15868 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
15869 
15870 	Bool XSupportsLocale();
15871 	char* XSetLocaleModifiers(const(char)* modifier_list);
15872 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15873 	Status XCloseOM(XOM om);
15874 
15875 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15876 	Status XCloseIM(XIM im);
15877 
15878 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15879 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15880 	Display* XDisplayOfIM(XIM im);
15881 	char* XLocaleOfIM(XIM im);
15882 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
15883 	void XDestroyIC(XIC ic);
15884 	void XSetICFocus(XIC ic);
15885 	void XUnsetICFocus(XIC ic);
15886 	//wchar_t* XwcResetIC(XIC ic);
15887 	char* XmbResetIC(XIC ic);
15888 	char* Xutf8ResetIC(XIC ic);
15889 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15890 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15891 	XIM XIMOfIC(XIC ic);
15892 
15893 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
15894 
15895 
15896 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
15897 	int XFreeFont(Display *display, XFontStruct *font_struct);
15898 	int XSetFont(Display* display, GC gc, Font font);
15899 	int XTextWidth(XFontStruct*, scope const char*, int);
15900 
15901 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
15902 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
15903 
15904 	Window XCreateSimpleWindow(
15905 		Display*	/* display */,
15906 		Window		/* parent */,
15907 		int			/* x */,
15908 		int			/* y */,
15909 		uint		/* width */,
15910 		uint		/* height */,
15911 		uint		/* border_width */,
15912 		uint		/* border */,
15913 		uint		/* background */
15914 	);
15915 	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);
15916 
15917 	int XReparentWindow(Display*, Window, Window, int, int);
15918 	int XClearWindow(Display*, Window);
15919 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
15920 	int XMoveWindow(Display*, Window, int, int);
15921 	int XResizeWindow(Display *display, Window w, uint width, uint height);
15922 
15923 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
15924 
15925 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
15926 
15927 	XImage *XCreateImage(
15928 		Display*		/* display */,
15929 		Visual*		/* visual */,
15930 		uint	/* depth */,
15931 		int			/* format */,
15932 		int			/* offset */,
15933 		ubyte*		/* data */,
15934 		uint	/* width */,
15935 		uint	/* height */,
15936 		int			/* bitmap_pad */,
15937 		int			/* bytes_per_line */
15938 	);
15939 
15940 	Status XInitImage (XImage* image);
15941 
15942 	Atom XInternAtom(
15943 		Display*		/* display */,
15944 		const char*	/* atom_name */,
15945 		Bool		/* only_if_exists */
15946 	);
15947 
15948 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
15949 	char* XGetAtomName(Display*, Atom);
15950 	Status XGetAtomNames(Display*, Atom*, int count, char**);
15951 
15952 	int XPutImage(
15953 		Display*	/* display */,
15954 		Drawable	/* d */,
15955 		GC			/* gc */,
15956 		XImage*	/* image */,
15957 		int			/* src_x */,
15958 		int			/* src_y */,
15959 		int			/* dest_x */,
15960 		int			/* dest_y */,
15961 		uint		/* width */,
15962 		uint		/* height */
15963 	);
15964 
15965 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
15966 
15967 
15968 	int XDestroyWindow(
15969 		Display*	/* display */,
15970 		Window		/* w */
15971 	);
15972 
15973 	int XDestroyImage(XImage*);
15974 
15975 	int XSelectInput(
15976 		Display*	/* display */,
15977 		Window		/* w */,
15978 		EventMask	/* event_mask */
15979 	);
15980 
15981 	int XMapWindow(
15982 		Display*	/* display */,
15983 		Window		/* w */
15984 	);
15985 
15986 	Status XIconifyWindow(Display*, Window, int);
15987 	int XMapRaised(Display*, Window);
15988 	int XMapSubwindows(Display*, Window);
15989 
15990 	int XNextEvent(
15991 		Display*	/* display */,
15992 		XEvent*		/* event_return */
15993 	);
15994 
15995 	int XMaskEvent(Display*, arch_long, XEvent*);
15996 
15997 	Bool XFilterEvent(XEvent *event, Window window);
15998 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
15999 
16000 	Status XSetWMProtocols(
16001 		Display*	/* display */,
16002 		Window		/* w */,
16003 		Atom*		/* protocols */,
16004 		int			/* count */
16005 	);
16006 
16007 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16008 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16009 
16010 
16011 	Status XInitThreads();
16012 	void XLockDisplay (Display* display);
16013 	void XUnlockDisplay (Display* display);
16014 
16015 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16016 
16017 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16018 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16019 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16020 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16021 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16022 
16023 
16024 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16025 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16026 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16027 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16028 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16029 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16030 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16031 	int XDrawPoint(Display*, Drawable, GC, int, int);
16032 	int XSetForeground(Display*, GC, uint);
16033 	int XSetBackground(Display*, GC, uint);
16034 
16035 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16036 	void XFreeFontSet(Display*, XFontSet);
16037 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16038 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16039 
16040 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16041 
16042 
16043 //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);
16044 
16045 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16046 	int XSetFunction(Display*, GC, int);
16047 
16048 	GC XCreateGC(Display*, Drawable, uint, void*);
16049 	int XCopyGC(Display*, GC, uint, GC);
16050 	int XFreeGC(Display*, GC);
16051 
16052 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16053 	bool XCheckMaskEvent(Display*, int, XEvent*);
16054 
16055 	int XPending(Display*);
16056 	int XEventsQueued(Display* display, int mode);
16057 
16058 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16059 	int XFreePixmap(Display*, Pixmap);
16060 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16061 	int XFlush(Display*);
16062 	int XBell(Display*, int);
16063 	int XSync(Display*, bool);
16064 
16065 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16066 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16067 
16068 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16069 	int XUngrabKeyboard(Display*, Time);
16070 
16071 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16072 
16073 	KeySym XStringToKeysym(const char *string);
16074 
16075 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16076 
16077 	Window XDefaultRootWindow(Display*);
16078 
16079 	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);
16080 
16081 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16082 
16083 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16084 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16085 
16086 	Status XAllocColor(Display*, Colormap, XColor*);
16087 
16088 	int XWithdrawWindow(Display*, Window, int);
16089 	int XUnmapWindow(Display*, Window);
16090 	int XLowerWindow(Display*, Window);
16091 	int XRaiseWindow(Display*, Window);
16092 
16093 	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);
16094 	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);
16095 
16096 	int XGetInputFocus(Display*, Window*, int*);
16097 	int XSetInputFocus(Display*, Window, int, Time);
16098 
16099 	XErrorHandler XSetErrorHandler(XErrorHandler);
16100 
16101 	int XGetErrorText(Display*, int, char*, int);
16102 
16103 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16104 
16105 
16106 	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);
16107 	int XUngrabPointer(Display *display, Time time);
16108 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16109 
16110 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16111 
16112 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16113 	int XSetClipMask(Display*, GC, Pixmap);
16114 	int XSetClipOrigin(Display*, GC, int, int);
16115 
16116 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16117 
16118 	void XSetWMName(Display*, Window, XTextProperty*);
16119 	Status XGetWMName(Display*, Window, XTextProperty*);
16120 	int XStoreName(Display* display, Window w, const(char)* window_name);
16121 
16122 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16123 
16124 }
16125 }
16126 
16127 interface Xext {
16128 extern(C) nothrow @nogc {
16129 	Status XShmAttach(Display*, XShmSegmentInfo*);
16130 	Status XShmDetach(Display*, XShmSegmentInfo*);
16131 	Status XShmPutImage(
16132 		Display*            /* dpy */,
16133 		Drawable            /* d */,
16134 		GC                  /* gc */,
16135 		XImage*             /* image */,
16136 		int                 /* src_x */,
16137 		int                 /* src_y */,
16138 		int                 /* dst_x */,
16139 		int                 /* dst_y */,
16140 		uint        /* src_width */,
16141 		uint        /* src_height */,
16142 		Bool                /* send_event */
16143 	);
16144 
16145 	Status XShmQueryExtension(Display*);
16146 
16147 	XImage *XShmCreateImage(
16148 		Display*            /* dpy */,
16149 		Visual*             /* visual */,
16150 		uint        /* depth */,
16151 		int                 /* format */,
16152 		char*               /* data */,
16153 		XShmSegmentInfo*    /* shminfo */,
16154 		uint        /* width */,
16155 		uint        /* height */
16156 	);
16157 
16158 	Pixmap XShmCreatePixmap(
16159 		Display*            /* dpy */,
16160 		Drawable            /* d */,
16161 		char*               /* data */,
16162 		XShmSegmentInfo*    /* shminfo */,
16163 		uint        /* width */,
16164 		uint        /* height */,
16165 		uint        /* depth */
16166 	);
16167 
16168 }
16169 }
16170 
16171 	// this requires -lXpm
16172 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16173 
16174 
16175 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16176 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16177 shared static this() {
16178 	xlib.loadDynamicLibrary();
16179 	xext.loadDynamicLibrary();
16180 }
16181 
16182 
16183 extern(C) nothrow @nogc {
16184 
16185 alias XrmDatabase = void*;
16186 struct XrmValue {
16187 	uint size;
16188 	void* addr;
16189 }
16190 
16191 struct XVisualInfo {
16192 	Visual* visual;
16193 	VisualID visualid;
16194 	int screen;
16195 	uint depth;
16196 	int c_class;
16197 	c_ulong red_mask;
16198 	c_ulong green_mask;
16199 	c_ulong blue_mask;
16200 	int colormap_size;
16201 	int bits_per_rgb;
16202 }
16203 
16204 enum VisualNoMask=	0x0;
16205 enum VisualIDMask=	0x1;
16206 enum VisualScreenMask=0x2;
16207 enum VisualDepthMask=	0x4;
16208 enum VisualClassMask=	0x8;
16209 enum VisualRedMaskMask=0x10;
16210 enum VisualGreenMaskMask=0x20;
16211 enum VisualBlueMaskMask=0x40;
16212 enum VisualColormapSizeMask=0x80;
16213 enum VisualBitsPerRGBMask=0x100;
16214 enum VisualAllMask=	0x1FF;
16215 
16216 enum AnyKey = 0;
16217 enum AnyModifier = 1 << 15;
16218 
16219 // XIM and other crap
16220 struct _XOM {}
16221 struct _XIM {}
16222 struct _XIC {}
16223 alias XOM = _XOM*;
16224 alias XIM = _XIM*;
16225 alias XIC = _XIC*;
16226 
16227 alias XVaNestedList = void*;
16228 
16229 alias XIMStyle = arch_ulong;
16230 enum : arch_ulong {
16231 	XIMPreeditArea      = 0x0001,
16232 	XIMPreeditCallbacks = 0x0002,
16233 	XIMPreeditPosition  = 0x0004,
16234 	XIMPreeditNothing   = 0x0008,
16235 	XIMPreeditNone      = 0x0010,
16236 	XIMStatusArea       = 0x0100,
16237 	XIMStatusCallbacks  = 0x0200,
16238 	XIMStatusNothing    = 0x0400,
16239 	XIMStatusNone       = 0x0800,
16240 }
16241 
16242 
16243 /* X Shared Memory Extension functions */
16244 	//pragma(lib, "Xshm");
16245 	alias arch_ulong ShmSeg;
16246 	struct XShmSegmentInfo {
16247 		ShmSeg shmseg;
16248 		int shmid;
16249 		ubyte* shmaddr;
16250 		Bool readOnly;
16251 	}
16252 
16253 	// and the necessary OS functions
16254 	int shmget(int, size_t, int);
16255 	void* shmat(int, scope const void*, int);
16256 	int shmdt(scope const void*);
16257 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16258 
16259 	enum IPC_PRIVATE = 0;
16260 	enum IPC_CREAT = 512;
16261 	enum IPC_RMID = 0;
16262 
16263 /* MIT-SHM end */
16264 
16265 
16266 enum MappingType:int {
16267 	MappingModifier		=0,
16268 	MappingKeyboard		=1,
16269 	MappingPointer		=2
16270 }
16271 
16272 /* ImageFormat -- PutImage, GetImage */
16273 enum ImageFormat:int {
16274 	XYBitmap	=0,	/* depth 1, XYFormat */
16275 	XYPixmap	=1,	/* depth == drawable depth */
16276 	ZPixmap	=2	/* depth == drawable depth */
16277 }
16278 
16279 enum ModifierName:int {
16280 	ShiftMapIndex	=0,
16281 	LockMapIndex	=1,
16282 	ControlMapIndex	=2,
16283 	Mod1MapIndex	=3,
16284 	Mod2MapIndex	=4,
16285 	Mod3MapIndex	=5,
16286 	Mod4MapIndex	=6,
16287 	Mod5MapIndex	=7
16288 }
16289 
16290 enum ButtonMask:int {
16291 	Button1Mask	=1<<8,
16292 	Button2Mask	=1<<9,
16293 	Button3Mask	=1<<10,
16294 	Button4Mask	=1<<11,
16295 	Button5Mask	=1<<12,
16296 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16297 }
16298 
16299 enum KeyOrButtonMask:uint {
16300 	ShiftMask	=1<<0,
16301 	LockMask	=1<<1,
16302 	ControlMask	=1<<2,
16303 	Mod1Mask	=1<<3,
16304 	Mod2Mask	=1<<4,
16305 	Mod3Mask	=1<<5,
16306 	Mod4Mask	=1<<6,
16307 	Mod5Mask	=1<<7,
16308 	Button1Mask	=1<<8,
16309 	Button2Mask	=1<<9,
16310 	Button3Mask	=1<<10,
16311 	Button4Mask	=1<<11,
16312 	Button5Mask	=1<<12,
16313 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16314 }
16315 
16316 enum ButtonName:int {
16317 	Button1	=1,
16318 	Button2	=2,
16319 	Button3	=3,
16320 	Button4	=4,
16321 	Button5	=5
16322 }
16323 
16324 /* Notify modes */
16325 enum NotifyModes:int
16326 {
16327 	NotifyNormal		=0,
16328 	NotifyGrab			=1,
16329 	NotifyUngrab		=2,
16330 	NotifyWhileGrabbed	=3
16331 }
16332 enum NotifyHint = 1;	/* for MotionNotify events */
16333 
16334 /* Notify detail */
16335 enum NotifyDetail:int
16336 {
16337 	NotifyAncestor			=0,
16338 	NotifyVirtual			=1,
16339 	NotifyInferior			=2,
16340 	NotifyNonlinear			=3,
16341 	NotifyNonlinearVirtual	=4,
16342 	NotifyPointer			=5,
16343 	NotifyPointerRoot		=6,
16344 	NotifyDetailNone		=7
16345 }
16346 
16347 /* Visibility notify */
16348 
16349 enum VisibilityNotify:int
16350 {
16351 VisibilityUnobscured		=0,
16352 VisibilityPartiallyObscured	=1,
16353 VisibilityFullyObscured		=2
16354 }
16355 
16356 
16357 enum WindowStackingMethod:int
16358 {
16359 	Above		=0,
16360 	Below		=1,
16361 	TopIf		=2,
16362 	BottomIf	=3,
16363 	Opposite	=4
16364 }
16365 
16366 /* Circulation request */
16367 enum CirculationRequest:int
16368 {
16369 	PlaceOnTop		=0,
16370 	PlaceOnBottom	=1
16371 }
16372 
16373 enum PropertyNotification:int
16374 {
16375 	PropertyNewValue	=0,
16376 	PropertyDelete		=1
16377 }
16378 
16379 enum ColorMapNotification:int
16380 {
16381 	ColormapUninstalled	=0,
16382 	ColormapInstalled		=1
16383 }
16384 
16385 
16386 	struct _XPrivate {}
16387 	struct _XrmHashBucketRec {}
16388 
16389 	alias void* XPointer;
16390 	alias void* XExtData;
16391 
16392 	version( X86_64 ) {
16393 		alias ulong XID;
16394 		alias ulong arch_ulong;
16395 		alias long arch_long;
16396 	} else version (AArch64) {
16397 		alias ulong XID;
16398 		alias ulong arch_ulong;
16399 		alias long arch_long;
16400 	} else {
16401 		alias uint XID;
16402 		alias uint arch_ulong;
16403 		alias int arch_long;
16404 	}
16405 
16406 	alias XID Window;
16407 	alias XID Drawable;
16408 	alias XID Pixmap;
16409 
16410 	alias arch_ulong Atom;
16411 	alias int Bool;
16412 	alias Display XDisplay;
16413 
16414 	alias int ByteOrder;
16415 	alias arch_ulong Time;
16416 	alias void ScreenFormat;
16417 
16418 	struct XImage {
16419 		int width, height;			/* size of image */
16420 		int xoffset;				/* number of pixels offset in X direction */
16421 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16422 		void *data;					/* pointer to image data */
16423 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16424 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16425 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16426 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16427 		int depth;					/* depth of image */
16428 		int bytes_per_line;			/* accelarator to next line */
16429 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16430 		arch_ulong red_mask;	/* bits in z arrangment */
16431 		arch_ulong green_mask;
16432 		arch_ulong blue_mask;
16433 		XPointer obdata;			/* hook for the object routines to hang on */
16434 		static struct F {				/* image manipulation routines */
16435 			XImage* function(
16436 				XDisplay* 			/* display */,
16437 				Visual*				/* visual */,
16438 				uint				/* depth */,
16439 				int					/* format */,
16440 				int					/* offset */,
16441 				ubyte*				/* data */,
16442 				uint				/* width */,
16443 				uint				/* height */,
16444 				int					/* bitmap_pad */,
16445 				int					/* bytes_per_line */) create_image;
16446 			int function(XImage *) destroy_image;
16447 			arch_ulong function(XImage *, int, int) get_pixel;
16448 			int function(XImage *, int, int, arch_ulong) put_pixel;
16449 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16450 			int function(XImage *, arch_long) add_pixel;
16451 		}
16452 		F f;
16453 	}
16454 	version(X86_64) static assert(XImage.sizeof == 136);
16455 	else version(X86) static assert(XImage.sizeof == 88);
16456 
16457 struct XCharStruct {
16458 	short       lbearing;       /* origin to left edge of raster */
16459 	short       rbearing;       /* origin to right edge of raster */
16460 	short       width;          /* advance to next char's origin */
16461 	short       ascent;         /* baseline to top edge of raster */
16462 	short       descent;        /* baseline to bottom edge of raster */
16463 	ushort attributes;  /* per char flags (not predefined) */
16464 }
16465 
16466 /*
16467  * To allow arbitrary information with fonts, there are additional properties
16468  * returned.
16469  */
16470 struct XFontProp {
16471 	Atom name;
16472 	arch_ulong card32;
16473 }
16474 
16475 alias Atom Font;
16476 
16477 struct XFontStruct {
16478 	XExtData *ext_data;           /* Hook for extension to hang data */
16479 	Font fid;                     /* Font ID for this font */
16480 	uint direction;           /* Direction the font is painted */
16481 	uint min_char_or_byte2;   /* First character */
16482 	uint max_char_or_byte2;   /* Last character */
16483 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16484 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16485 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16486 	uint default_char;        /* Char to print for undefined character */
16487 	int n_properties;             /* How many properties there are */
16488 	XFontProp *properties;        /* Pointer to array of additional properties*/
16489 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16490 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16491 	XCharStruct *per_char;        /* first_char to last_char information */
16492 	int ascent;                   /* Max extent above baseline for spacing */
16493 	int descent;                  /* Max descent below baseline for spacing */
16494 }
16495 
16496 
16497 /*
16498  * Definitions of specific events.
16499  */
16500 struct XKeyEvent
16501 {
16502 	int type;			/* of event */
16503 	arch_ulong serial;		/* # of last request processed by server */
16504 	Bool send_event;	/* true if this came from a SendEvent request */
16505 	Display *display;	/* Display the event was read from */
16506 	Window window;	        /* "event" window it is reported relative to */
16507 	Window root;	        /* root window that the event occurred on */
16508 	Window subwindow;	/* child window */
16509 	Time time;		/* milliseconds */
16510 	int x, y;		/* pointer x, y coordinates in event window */
16511 	int x_root, y_root;	/* coordinates relative to root */
16512 	KeyOrButtonMask state;	/* key or button mask */
16513 	uint keycode;	/* detail */
16514 	Bool same_screen;	/* same screen flag */
16515 }
16516 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16517 alias XKeyEvent XKeyPressedEvent;
16518 alias XKeyEvent XKeyReleasedEvent;
16519 
16520 struct XButtonEvent
16521 {
16522 	int type;		/* of event */
16523 	arch_ulong serial;	/* # of last request processed by server */
16524 	Bool send_event;	/* true if this came from a SendEvent request */
16525 	Display *display;	/* Display the event was read from */
16526 	Window window;	        /* "event" window it is reported relative to */
16527 	Window root;	        /* root window that the event occurred on */
16528 	Window subwindow;	/* child window */
16529 	Time time;		/* milliseconds */
16530 	int x, y;		/* pointer x, y coordinates in event window */
16531 	int x_root, y_root;	/* coordinates relative to root */
16532 	KeyOrButtonMask state;	/* key or button mask */
16533 	uint button;	/* detail */
16534 	Bool same_screen;	/* same screen flag */
16535 }
16536 alias XButtonEvent XButtonPressedEvent;
16537 alias XButtonEvent XButtonReleasedEvent;
16538 
16539 struct XMotionEvent{
16540 	int type;		/* of event */
16541 	arch_ulong serial;	/* # of last request processed by server */
16542 	Bool send_event;	/* true if this came from a SendEvent request */
16543 	Display *display;	/* Display the event was read from */
16544 	Window window;	        /* "event" window reported relative to */
16545 	Window root;	        /* root window that the event occurred on */
16546 	Window subwindow;	/* child window */
16547 	Time time;		/* milliseconds */
16548 	int x, y;		/* pointer x, y coordinates in event window */
16549 	int x_root, y_root;	/* coordinates relative to root */
16550 	KeyOrButtonMask state;	/* key or button mask */
16551 	byte is_hint;		/* detail */
16552 	Bool same_screen;	/* same screen flag */
16553 }
16554 alias XMotionEvent XPointerMovedEvent;
16555 
16556 struct XCrossingEvent{
16557 	int type;		/* of event */
16558 	arch_ulong serial;	/* # of last request processed by server */
16559 	Bool send_event;	/* true if this came from a SendEvent request */
16560 	Display *display;	/* Display the event was read from */
16561 	Window window;	        /* "event" window reported relative to */
16562 	Window root;	        /* root window that the event occurred on */
16563 	Window subwindow;	/* child window */
16564 	Time time;		/* milliseconds */
16565 	int x, y;		/* pointer x, y coordinates in event window */
16566 	int x_root, y_root;	/* coordinates relative to root */
16567 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16568 	NotifyDetail detail;
16569 	/*
16570 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16571 	 * NotifyNonlinear,NotifyNonlinearVirtual
16572 	 */
16573 	Bool same_screen;	/* same screen flag */
16574 	Bool focus;		/* Boolean focus */
16575 	KeyOrButtonMask state;	/* key or button mask */
16576 }
16577 alias XCrossingEvent XEnterWindowEvent;
16578 alias XCrossingEvent XLeaveWindowEvent;
16579 
16580 struct XFocusChangeEvent{
16581 	int type;		/* FocusIn or FocusOut */
16582 	arch_ulong serial;	/* # of last request processed by server */
16583 	Bool send_event;	/* true if this came from a SendEvent request */
16584 	Display *display;	/* Display the event was read from */
16585 	Window window;		/* window of event */
16586 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16587 				   NotifyGrab, NotifyUngrab */
16588 	NotifyDetail detail;
16589 	/*
16590 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16591 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16592 	 * NotifyPointerRoot, NotifyDetailNone
16593 	 */
16594 }
16595 alias XFocusChangeEvent XFocusInEvent;
16596 alias XFocusChangeEvent XFocusOutEvent;
16597 
16598 enum CWBackPixmap              = (1L<<0);
16599 enum CWBackPixel               = (1L<<1);
16600 enum CWBorderPixmap            = (1L<<2);
16601 enum CWBorderPixel             = (1L<<3);
16602 enum CWBitGravity              = (1L<<4);
16603 enum CWWinGravity              = (1L<<5);
16604 enum CWBackingStore            = (1L<<6);
16605 enum CWBackingPlanes           = (1L<<7);
16606 enum CWBackingPixel            = (1L<<8);
16607 enum CWOverrideRedirect        = (1L<<9);
16608 enum CWSaveUnder               = (1L<<10);
16609 enum CWEventMask               = (1L<<11);
16610 enum CWDontPropagate           = (1L<<12);
16611 enum CWColormap                = (1L<<13);
16612 enum CWCursor                  = (1L<<14);
16613 
16614 struct XWindowAttributes {
16615 	int x, y;			/* location of window */
16616 	int width, height;		/* width and height of window */
16617 	int border_width;		/* border width of window */
16618 	int depth;			/* depth of window */
16619 	Visual *visual;			/* the associated visual structure */
16620 	Window root;			/* root of screen containing window */
16621 	int class_;			/* InputOutput, InputOnly*/
16622 	int bit_gravity;		/* one of the bit gravity values */
16623 	int win_gravity;		/* one of the window gravity values */
16624 	int backing_store;		/* NotUseful, WhenMapped, Always */
16625 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16626 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16627 	Bool save_under;		/* boolean, should bits under be saved? */
16628 	Colormap colormap;		/* color map to be associated with window */
16629 	Bool map_installed;		/* boolean, is color map currently installed*/
16630 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16631 	arch_long all_event_masks;		/* set of events all people have interest in*/
16632 	arch_long your_event_mask;		/* my event mask */
16633 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16634 	Bool override_redirect;		/* boolean value for override-redirect */
16635 	Screen *screen;			/* back pointer to correct screen */
16636 }
16637 
16638 enum IsUnmapped = 0;
16639 enum IsUnviewable = 1;
16640 enum IsViewable = 2;
16641 
16642 struct XSetWindowAttributes {
16643 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16644 	arch_ulong background_pixel;/* background pixel */
16645 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16646 	arch_ulong border_pixel;/* border pixel value */
16647 	int bit_gravity;         /* one of bit gravity values */
16648 	int win_gravity;         /* one of the window gravity values */
16649 	int backing_store;       /* NotUseful, WhenMapped, Always */
16650 	arch_ulong backing_planes;/* planes to be preserved if possible */
16651 	arch_ulong backing_pixel;/* value to use in restoring planes */
16652 	Bool save_under;         /* should bits under be saved? (popups) */
16653 	arch_long event_mask;         /* set of events that should be saved */
16654 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16655 	Bool override_redirect;  /* boolean value for override_redirect */
16656 	Colormap colormap;       /* color map to be associated with window */
16657 	Cursor cursor;           /* cursor to be displayed (or None) */
16658 }
16659 
16660 
16661 alias int Status;
16662 
16663 
16664 enum EventMask:int
16665 {
16666 	NoEventMask				=0,
16667 	KeyPressMask			=1<<0,
16668 	KeyReleaseMask			=1<<1,
16669 	ButtonPressMask			=1<<2,
16670 	ButtonReleaseMask		=1<<3,
16671 	EnterWindowMask			=1<<4,
16672 	LeaveWindowMask			=1<<5,
16673 	PointerMotionMask		=1<<6,
16674 	PointerMotionHintMask	=1<<7,
16675 	Button1MotionMask		=1<<8,
16676 	Button2MotionMask		=1<<9,
16677 	Button3MotionMask		=1<<10,
16678 	Button4MotionMask		=1<<11,
16679 	Button5MotionMask		=1<<12,
16680 	ButtonMotionMask		=1<<13,
16681 	KeymapStateMask		=1<<14,
16682 	ExposureMask			=1<<15,
16683 	VisibilityChangeMask	=1<<16,
16684 	StructureNotifyMask		=1<<17,
16685 	ResizeRedirectMask		=1<<18,
16686 	SubstructureNotifyMask	=1<<19,
16687 	SubstructureRedirectMask=1<<20,
16688 	FocusChangeMask			=1<<21,
16689 	PropertyChangeMask		=1<<22,
16690 	ColormapChangeMask		=1<<23,
16691 	OwnerGrabButtonMask		=1<<24
16692 }
16693 
16694 struct MwmHints {
16695 	c_ulong flags;
16696 	c_ulong functions;
16697 	c_ulong decorations;
16698 	c_long input_mode;
16699 	c_ulong status;
16700 }
16701 
16702 enum {
16703 	MWM_HINTS_FUNCTIONS = (1L << 0),
16704 	MWM_HINTS_DECORATIONS =  (1L << 1),
16705 
16706 	MWM_FUNC_ALL = (1L << 0),
16707 	MWM_FUNC_RESIZE = (1L << 1),
16708 	MWM_FUNC_MOVE = (1L << 2),
16709 	MWM_FUNC_MINIMIZE = (1L << 3),
16710 	MWM_FUNC_MAXIMIZE = (1L << 4),
16711 	MWM_FUNC_CLOSE = (1L << 5),
16712 
16713 	MWM_DECOR_ALL = (1L << 0),
16714 	MWM_DECOR_BORDER = (1L << 1),
16715 	MWM_DECOR_RESIZEH = (1L << 2),
16716 	MWM_DECOR_TITLE = (1L << 3),
16717 	MWM_DECOR_MENU = (1L << 4),
16718 	MWM_DECOR_MINIMIZE = (1L << 5),
16719 	MWM_DECOR_MAXIMIZE = (1L << 6),
16720 }
16721 
16722 import core.stdc.config : c_long, c_ulong;
16723 
16724 	/* Size hints mask bits */
16725 
16726 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16727 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16728 	enum   PPosition   = (1L << 2)          /* program specified position */;
16729 	enum   PSize       = (1L << 3)          /* program specified size */;
16730 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16731 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16732 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16733 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16734 	enum   PBaseSize   = (1L << 8);
16735 	enum   PWinGravity = (1L << 9);
16736 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16737 	struct XSizeHints {
16738 		arch_long flags;         /* marks which fields in this structure are defined */
16739 		int x, y;           /* Obsolete */
16740 		int width, height;  /* Obsolete */
16741 		int min_width, min_height;
16742 		int max_width, max_height;
16743 		int width_inc, height_inc;
16744 		struct Aspect {
16745 			int x;       /* numerator */
16746 			int y;       /* denominator */
16747 		}
16748 
16749 		Aspect min_aspect;
16750 		Aspect max_aspect;
16751 		int base_width, base_height;
16752 		int win_gravity;
16753 		/* this structure may be extended in the future */
16754 	}
16755 
16756 
16757 
16758 enum EventType:int
16759 {
16760 	KeyPress			=2,
16761 	KeyRelease			=3,
16762 	ButtonPress			=4,
16763 	ButtonRelease		=5,
16764 	MotionNotify		=6,
16765 	EnterNotify			=7,
16766 	LeaveNotify			=8,
16767 	FocusIn				=9,
16768 	FocusOut			=10,
16769 	KeymapNotify		=11,
16770 	Expose				=12,
16771 	GraphicsExpose		=13,
16772 	NoExpose			=14,
16773 	VisibilityNotify	=15,
16774 	CreateNotify		=16,
16775 	DestroyNotify		=17,
16776 	UnmapNotify		=18,
16777 	MapNotify			=19,
16778 	MapRequest			=20,
16779 	ReparentNotify		=21,
16780 	ConfigureNotify		=22,
16781 	ConfigureRequest	=23,
16782 	GravityNotify		=24,
16783 	ResizeRequest		=25,
16784 	CirculateNotify		=26,
16785 	CirculateRequest	=27,
16786 	PropertyNotify		=28,
16787 	SelectionClear		=29,
16788 	SelectionRequest	=30,
16789 	SelectionNotify		=31,
16790 	ColormapNotify		=32,
16791 	ClientMessage		=33,
16792 	MappingNotify		=34,
16793 	LASTEvent			=35	/* must be bigger than any event # */
16794 }
16795 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16796 struct XKeymapEvent
16797 {
16798 	int type;
16799 	arch_ulong serial;	/* # of last request processed by server */
16800 	Bool send_event;	/* true if this came from a SendEvent request */
16801 	Display *display;	/* Display the event was read from */
16802 	Window window;
16803 	byte[32] key_vector;
16804 }
16805 
16806 struct XExposeEvent
16807 {
16808 	int type;
16809 	arch_ulong serial;	/* # of last request processed by server */
16810 	Bool send_event;	/* true if this came from a SendEvent request */
16811 	Display *display;	/* Display the event was read from */
16812 	Window window;
16813 	int x, y;
16814 	int width, height;
16815 	int count;		/* if non-zero, at least this many more */
16816 }
16817 
16818 struct XGraphicsExposeEvent{
16819 	int type;
16820 	arch_ulong serial;	/* # of last request processed by server */
16821 	Bool send_event;	/* true if this came from a SendEvent request */
16822 	Display *display;	/* Display the event was read from */
16823 	Drawable drawable;
16824 	int x, y;
16825 	int width, height;
16826 	int count;		/* if non-zero, at least this many more */
16827 	int major_code;		/* core is CopyArea or CopyPlane */
16828 	int minor_code;		/* not defined in the core */
16829 }
16830 
16831 struct XNoExposeEvent{
16832 	int type;
16833 	arch_ulong serial;	/* # of last request processed by server */
16834 	Bool send_event;	/* true if this came from a SendEvent request */
16835 	Display *display;	/* Display the event was read from */
16836 	Drawable drawable;
16837 	int major_code;		/* core is CopyArea or CopyPlane */
16838 	int minor_code;		/* not defined in the core */
16839 }
16840 
16841 struct XVisibilityEvent{
16842 	int type;
16843 	arch_ulong serial;	/* # of last request processed by server */
16844 	Bool send_event;	/* true if this came from a SendEvent request */
16845 	Display *display;	/* Display the event was read from */
16846 	Window window;
16847 	VisibilityNotify state;		/* Visibility state */
16848 }
16849 
16850 struct XCreateWindowEvent{
16851 	int type;
16852 	arch_ulong serial;	/* # of last request processed by server */
16853 	Bool send_event;	/* true if this came from a SendEvent request */
16854 	Display *display;	/* Display the event was read from */
16855 	Window parent;		/* parent of the window */
16856 	Window window;		/* window id of window created */
16857 	int x, y;		/* window location */
16858 	int width, height;	/* size of window */
16859 	int border_width;	/* border width */
16860 	Bool override_redirect;	/* creation should be overridden */
16861 }
16862 
16863 struct XDestroyWindowEvent
16864 {
16865 	int type;
16866 	arch_ulong serial;		/* # of last request processed by server */
16867 	Bool send_event;	/* true if this came from a SendEvent request */
16868 	Display *display;	/* Display the event was read from */
16869 	Window event;
16870 	Window window;
16871 }
16872 
16873 struct XUnmapEvent
16874 {
16875 	int type;
16876 	arch_ulong serial;		/* # of last request processed by server */
16877 	Bool send_event;	/* true if this came from a SendEvent request */
16878 	Display *display;	/* Display the event was read from */
16879 	Window event;
16880 	Window window;
16881 	Bool from_configure;
16882 }
16883 
16884 struct XMapEvent
16885 {
16886 	int type;
16887 	arch_ulong serial;		/* # of last request processed by server */
16888 	Bool send_event;	/* true if this came from a SendEvent request */
16889 	Display *display;	/* Display the event was read from */
16890 	Window event;
16891 	Window window;
16892 	Bool override_redirect;	/* Boolean, is override set... */
16893 }
16894 
16895 struct XMapRequestEvent
16896 {
16897 	int type;
16898 	arch_ulong serial;	/* # of last request processed by server */
16899 	Bool send_event;	/* true if this came from a SendEvent request */
16900 	Display *display;	/* Display the event was read from */
16901 	Window parent;
16902 	Window window;
16903 }
16904 
16905 struct XReparentEvent
16906 {
16907 	int type;
16908 	arch_ulong serial;	/* # of last request processed by server */
16909 	Bool send_event;	/* true if this came from a SendEvent request */
16910 	Display *display;	/* Display the event was read from */
16911 	Window event;
16912 	Window window;
16913 	Window parent;
16914 	int x, y;
16915 	Bool override_redirect;
16916 }
16917 
16918 struct XConfigureEvent
16919 {
16920 	int type;
16921 	arch_ulong serial;	/* # of last request processed by server */
16922 	Bool send_event;	/* true if this came from a SendEvent request */
16923 	Display *display;	/* Display the event was read from */
16924 	Window event;
16925 	Window window;
16926 	int x, y;
16927 	int width, height;
16928 	int border_width;
16929 	Window above;
16930 	Bool override_redirect;
16931 }
16932 
16933 struct XGravityEvent
16934 {
16935 	int type;
16936 	arch_ulong serial;	/* # of last request processed by server */
16937 	Bool send_event;	/* true if this came from a SendEvent request */
16938 	Display *display;	/* Display the event was read from */
16939 	Window event;
16940 	Window window;
16941 	int x, y;
16942 }
16943 
16944 struct XResizeRequestEvent
16945 {
16946 	int type;
16947 	arch_ulong serial;	/* # of last request processed by server */
16948 	Bool send_event;	/* true if this came from a SendEvent request */
16949 	Display *display;	/* Display the event was read from */
16950 	Window window;
16951 	int width, height;
16952 }
16953 
16954 struct  XConfigureRequestEvent
16955 {
16956 	int type;
16957 	arch_ulong serial;	/* # of last request processed by server */
16958 	Bool send_event;	/* true if this came from a SendEvent request */
16959 	Display *display;	/* Display the event was read from */
16960 	Window parent;
16961 	Window window;
16962 	int x, y;
16963 	int width, height;
16964 	int border_width;
16965 	Window above;
16966 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
16967 	arch_ulong value_mask;
16968 }
16969 
16970 struct XCirculateEvent
16971 {
16972 	int type;
16973 	arch_ulong serial;	/* # of last request processed by server */
16974 	Bool send_event;	/* true if this came from a SendEvent request */
16975 	Display *display;	/* Display the event was read from */
16976 	Window event;
16977 	Window window;
16978 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16979 }
16980 
16981 struct XCirculateRequestEvent
16982 {
16983 	int type;
16984 	arch_ulong serial;	/* # of last request processed by server */
16985 	Bool send_event;	/* true if this came from a SendEvent request */
16986 	Display *display;	/* Display the event was read from */
16987 	Window parent;
16988 	Window window;
16989 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16990 }
16991 
16992 struct XPropertyEvent
16993 {
16994 	int type;
16995 	arch_ulong serial;	/* # of last request processed by server */
16996 	Bool send_event;	/* true if this came from a SendEvent request */
16997 	Display *display;	/* Display the event was read from */
16998 	Window window;
16999 	Atom atom;
17000 	Time time;
17001 	PropertyNotification state;		/* NewValue, Deleted */
17002 }
17003 
17004 struct XSelectionClearEvent
17005 {
17006 	int type;
17007 	arch_ulong serial;	/* # of last request processed by server */
17008 	Bool send_event;	/* true if this came from a SendEvent request */
17009 	Display *display;	/* Display the event was read from */
17010 	Window window;
17011 	Atom selection;
17012 	Time time;
17013 }
17014 
17015 struct XSelectionRequestEvent
17016 {
17017 	int type;
17018 	arch_ulong serial;	/* # of last request processed by server */
17019 	Bool send_event;	/* true if this came from a SendEvent request */
17020 	Display *display;	/* Display the event was read from */
17021 	Window owner;
17022 	Window requestor;
17023 	Atom selection;
17024 	Atom target;
17025 	Atom property;
17026 	Time time;
17027 }
17028 
17029 struct XSelectionEvent
17030 {
17031 	int type;
17032 	arch_ulong serial;	/* # of last request processed by server */
17033 	Bool send_event;	/* true if this came from a SendEvent request */
17034 	Display *display;	/* Display the event was read from */
17035 	Window requestor;
17036 	Atom selection;
17037 	Atom target;
17038 	Atom property;		/* ATOM or None */
17039 	Time time;
17040 }
17041 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17042 
17043 struct XColormapEvent
17044 {
17045 	int type;
17046 	arch_ulong serial;	/* # of last request processed by server */
17047 	Bool send_event;	/* true if this came from a SendEvent request */
17048 	Display *display;	/* Display the event was read from */
17049 	Window window;
17050 	Colormap colormap;	/* COLORMAP or None */
17051 	Bool new_;		/* C++ */
17052 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17053 }
17054 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17055 
17056 struct XClientMessageEvent
17057 {
17058 	int type;
17059 	arch_ulong serial;	/* # of last request processed by server */
17060 	Bool send_event;	/* true if this came from a SendEvent request */
17061 	Display *display;	/* Display the event was read from */
17062 	Window window;
17063 	Atom message_type;
17064 	int format;
17065 	union Data{
17066 		byte[20] b;
17067 		short[10] s;
17068 		arch_ulong[5] l;
17069 	}
17070 	Data data;
17071 
17072 }
17073 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17074 
17075 struct XMappingEvent
17076 {
17077 	int type;
17078 	arch_ulong serial;	/* # of last request processed by server */
17079 	Bool send_event;	/* true if this came from a SendEvent request */
17080 	Display *display;	/* Display the event was read from */
17081 	Window window;		/* unused */
17082 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17083 				   MappingPointer */
17084 	int first_keycode;	/* first keycode */
17085 	int count;		/* defines range of change w. first_keycode*/
17086 }
17087 
17088 struct XErrorEvent
17089 {
17090 	int type;
17091 	Display *display;	/* Display the event was read from */
17092 	XID resourceid;		/* resource id */
17093 	arch_ulong serial;	/* serial number of failed request */
17094 	ubyte error_code;	/* error code of failed request */
17095 	ubyte request_code;	/* Major op-code of failed request */
17096 	ubyte minor_code;	/* Minor op-code of failed request */
17097 }
17098 
17099 struct XAnyEvent
17100 {
17101 	int type;
17102 	arch_ulong serial;	/* # of last request processed by server */
17103 	Bool send_event;	/* true if this came from a SendEvent request */
17104 	Display *display;/* Display the event was read from */
17105 	Window window;	/* window on which event was requested in event mask */
17106 }
17107 
17108 union XEvent{
17109 	int type;		/* must not be changed; first element */
17110 	XAnyEvent xany;
17111 	XKeyEvent xkey;
17112 	XButtonEvent xbutton;
17113 	XMotionEvent xmotion;
17114 	XCrossingEvent xcrossing;
17115 	XFocusChangeEvent xfocus;
17116 	XExposeEvent xexpose;
17117 	XGraphicsExposeEvent xgraphicsexpose;
17118 	XNoExposeEvent xnoexpose;
17119 	XVisibilityEvent xvisibility;
17120 	XCreateWindowEvent xcreatewindow;
17121 	XDestroyWindowEvent xdestroywindow;
17122 	XUnmapEvent xunmap;
17123 	XMapEvent xmap;
17124 	XMapRequestEvent xmaprequest;
17125 	XReparentEvent xreparent;
17126 	XConfigureEvent xconfigure;
17127 	XGravityEvent xgravity;
17128 	XResizeRequestEvent xresizerequest;
17129 	XConfigureRequestEvent xconfigurerequest;
17130 	XCirculateEvent xcirculate;
17131 	XCirculateRequestEvent xcirculaterequest;
17132 	XPropertyEvent xproperty;
17133 	XSelectionClearEvent xselectionclear;
17134 	XSelectionRequestEvent xselectionrequest;
17135 	XSelectionEvent xselection;
17136 	XColormapEvent xcolormap;
17137 	XClientMessageEvent xclient;
17138 	XMappingEvent xmapping;
17139 	XErrorEvent xerror;
17140 	XKeymapEvent xkeymap;
17141 	arch_ulong[24] pad;
17142 }
17143 
17144 
17145 	struct Display {
17146 		XExtData *ext_data;	/* hook for extension to hang data */
17147 		_XPrivate *private1;
17148 		int fd;			/* Network socket. */
17149 		int private2;
17150 		int proto_major_version;/* major version of server's X protocol */
17151 		int proto_minor_version;/* minor version of servers X protocol */
17152 		char *vendor;		/* vendor of the server hardware */
17153 	    	XID private3;
17154 		XID private4;
17155 		XID private5;
17156 		int private6;
17157 		XID function(Display*)resource_alloc;/* allocator function */
17158 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17159 		int bitmap_unit;	/* padding and data requirements */
17160 		int bitmap_pad;		/* padding requirements on bitmaps */
17161 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17162 		int nformats;		/* number of pixmap formats in list */
17163 		ScreenFormat *pixmap_format;	/* pixmap format list */
17164 		int private8;
17165 		int release;		/* release of the server */
17166 		_XPrivate *private9;
17167 		_XPrivate *private10;
17168 		int qlen;		/* Length of input event queue */
17169 		arch_ulong last_request_read; /* seq number of last event read */
17170 		arch_ulong request;	/* sequence number of last request. */
17171 		XPointer private11;
17172 		XPointer private12;
17173 		XPointer private13;
17174 		XPointer private14;
17175 		uint max_request_size; /* maximum number 32 bit words in request*/
17176 		_XrmHashBucketRec *db;
17177 		int function  (Display*)private15;
17178 		char *display_name;	/* "host:display" string used on this connect*/
17179 		int default_screen;	/* default screen for operations */
17180 		int nscreens;		/* number of screens on this server*/
17181 		Screen *screens;	/* pointer to list of screens */
17182 		arch_ulong motion_buffer;	/* size of motion buffer */
17183 		arch_ulong private16;
17184 		int min_keycode;	/* minimum defined keycode */
17185 		int max_keycode;	/* maximum defined keycode */
17186 		XPointer private17;
17187 		XPointer private18;
17188 		int private19;
17189 		byte *xdefaults;	/* contents of defaults from server */
17190 		/* there is more to this structure, but it is private to Xlib */
17191 	}
17192 
17193 	// I got these numbers from a C program as a sanity test
17194 	version(X86_64) {
17195 		static assert(Display.sizeof == 296);
17196 		static assert(XPointer.sizeof == 8);
17197 		static assert(XErrorEvent.sizeof == 40);
17198 		static assert(XAnyEvent.sizeof == 40);
17199 		static assert(XMappingEvent.sizeof == 56);
17200 		static assert(XEvent.sizeof == 192);
17201     	} else version (AArch64) {
17202         	// omit check for aarch64
17203 	} else {
17204 		static assert(Display.sizeof == 176);
17205 		static assert(XPointer.sizeof == 4);
17206 		static assert(XEvent.sizeof == 96);
17207 	}
17208 
17209 struct Depth
17210 {
17211 	int depth;		/* this depth (Z) of the depth */
17212 	int nvisuals;		/* number of Visual types at this depth */
17213 	Visual *visuals;	/* list of visuals possible at this depth */
17214 }
17215 
17216 alias void* GC;
17217 alias c_ulong VisualID;
17218 alias XID Colormap;
17219 alias XID Cursor;
17220 alias XID KeySym;
17221 alias uint KeyCode;
17222 enum None = 0;
17223 }
17224 
17225 version(without_opengl) {}
17226 else {
17227 extern(C) nothrow @nogc {
17228 
17229 
17230 static if(!SdpyIsUsingIVGLBinds) {
17231 enum GLX_USE_GL=            1;       /* support GLX rendering */
17232 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17233 enum GLX_LEVEL=             3;       /* level in plane stacking */
17234 enum GLX_RGBA=              4;       /* true if RGBA mode */
17235 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17236 enum GLX_STEREO=            6;       /* stereo buffering supported */
17237 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17238 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17239 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17240 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17241 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17242 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17243 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17244 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17245 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17246 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17247 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17248 
17249 
17250 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17251 
17252 
17253 
17254 enum GL_TRUE = 1;
17255 enum GL_FALSE = 0;
17256 alias int GLint;
17257 }
17258 
17259 alias XID GLXContextID;
17260 alias XID GLXPixmap;
17261 alias XID GLXDrawable;
17262 alias XID GLXPbuffer;
17263 alias XID GLXWindow;
17264 alias XID GLXFBConfigID;
17265 alias void* GLXContext;
17266 
17267 }
17268 }
17269 
17270 enum AllocNone = 0;
17271 
17272 extern(C) {
17273 	/* WARNING, this type not in Xlib spec */
17274 	extern(C) alias XIOErrorHandler = int function (Display* display);
17275 }
17276 
17277 extern(C) nothrow
17278 alias XErrorHandler = int function(Display*, XErrorEvent*);
17279 
17280 extern(C) nothrow @nogc {
17281 struct Screen{
17282 	XExtData *ext_data;		/* hook for extension to hang data */
17283 	Display *display;		/* back pointer to display structure */
17284 	Window root;			/* Root window id. */
17285 	int width, height;		/* width and height of screen */
17286 	int mwidth, mheight;	/* width and height of  in millimeters */
17287 	int ndepths;			/* number of depths possible */
17288 	Depth *depths;			/* list of allowable depths on the screen */
17289 	int root_depth;			/* bits per pixel */
17290 	Visual *root_visual;	/* root visual */
17291 	GC default_gc;			/* GC for the root root visual */
17292 	Colormap cmap;			/* default color map */
17293 	uint white_pixel;
17294 	uint black_pixel;		/* White and Black pixel values */
17295 	int max_maps, min_maps;	/* max and min color maps */
17296 	int backing_store;		/* Never, WhenMapped, Always */
17297 	bool save_unders;
17298 	int root_input_mask;	/* initial root input mask */
17299 }
17300 
17301 struct Visual
17302 {
17303 	XExtData *ext_data;	/* hook for extension to hang data */
17304 	VisualID visualid;	/* visual id of this visual */
17305 	int class_;			/* class of screen (monochrome, etc.) */
17306 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17307 	int bits_per_rgb;	/* log base 2 of distinct color values */
17308 	int map_entries;	/* color map entries */
17309 }
17310 
17311 	alias Display* _XPrivDisplay;
17312 
17313 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17314 		assert(dpy !is null);
17315 		return &dpy.screens[scr];
17316 	}
17317 
17318 	extern(D) Window RootWindow(Display *dpy,int scr) {
17319 		return ScreenOfDisplay(dpy,scr).root;
17320 	}
17321 
17322 	struct XWMHints {
17323 		arch_long flags;
17324 		Bool input;
17325 		int initial_state;
17326 		Pixmap icon_pixmap;
17327 		Window icon_window;
17328 		int icon_x, icon_y;
17329 		Pixmap icon_mask;
17330 		XID window_group;
17331 	}
17332 
17333 	struct XClassHint {
17334 		char* res_name;
17335 		char* res_class;
17336 	}
17337 
17338 	extern(D) int DefaultScreen(Display *dpy) {
17339 		return dpy.default_screen;
17340 	}
17341 
17342 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17343 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17344 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17345 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17346 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17347 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17348 
17349 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17350 
17351 	enum int AnyPropertyType = 0;
17352 	enum int Success = 0;
17353 
17354 	enum int RevertToNone = None;
17355 	enum int PointerRoot = 1;
17356 	enum Time CurrentTime = 0;
17357 	enum int RevertToPointerRoot = PointerRoot;
17358 	enum int RevertToParent = 2;
17359 
17360 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17361 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17362 	}
17363 
17364 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17365 		return ScreenOfDisplay(dpy,scr).root_visual;
17366 	}
17367 
17368 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17369 		return ScreenOfDisplay(dpy,scr).default_gc;
17370 	}
17371 
17372 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17373 		return ScreenOfDisplay(dpy,scr).black_pixel;
17374 	}
17375 
17376 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17377 		return ScreenOfDisplay(dpy,scr).white_pixel;
17378 	}
17379 
17380 	alias void* XFontSet; // i think
17381 	struct XmbTextItem {
17382 		char* chars;
17383 		int nchars;
17384 		int delta;
17385 		XFontSet font_set;
17386 	}
17387 
17388 	struct XTextItem {
17389 		char* chars;
17390 		int nchars;
17391 		int delta;
17392 		Font font;
17393 	}
17394 
17395 	enum {
17396 		GXclear        = 0x0, /* 0 */
17397 		GXand          = 0x1, /* src AND dst */
17398 		GXandReverse   = 0x2, /* src AND NOT dst */
17399 		GXcopy         = 0x3, /* src */
17400 		GXandInverted  = 0x4, /* NOT src AND dst */
17401 		GXnoop         = 0x5, /* dst */
17402 		GXxor          = 0x6, /* src XOR dst */
17403 		GXor           = 0x7, /* src OR dst */
17404 		GXnor          = 0x8, /* NOT src AND NOT dst */
17405 		GXequiv        = 0x9, /* NOT src XOR dst */
17406 		GXinvert       = 0xa, /* NOT dst */
17407 		GXorReverse    = 0xb, /* src OR NOT dst */
17408 		GXcopyInverted = 0xc, /* NOT src */
17409 		GXorInverted   = 0xd, /* NOT src OR dst */
17410 		GXnand         = 0xe, /* NOT src OR NOT dst */
17411 		GXset          = 0xf, /* 1 */
17412 	}
17413 	enum QueueMode : int {
17414 		QueuedAlready,
17415 		QueuedAfterReading,
17416 		QueuedAfterFlush
17417 	}
17418 
17419 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17420 
17421 	struct XPoint {
17422 		short x;
17423 		short y;
17424 	}
17425 
17426 	enum CoordMode:int {
17427 		CoordModeOrigin = 0,
17428 		CoordModePrevious = 1
17429 	}
17430 
17431 	enum PolygonShape:int {
17432 		Complex = 0,
17433 		Nonconvex = 1,
17434 		Convex = 2
17435 	}
17436 
17437 	struct XTextProperty {
17438 		const(char)* value;		/* same as Property routines */
17439 		Atom encoding;			/* prop type */
17440 		int format;				/* prop data format: 8, 16, or 32 */
17441 		arch_ulong nitems;		/* number of data items in value */
17442 	}
17443 
17444 	version( X86_64 ) {
17445 		static assert(XTextProperty.sizeof == 32);
17446 	}
17447 
17448 
17449 	struct XGCValues {
17450 		int function_;           /* logical operation */
17451 		arch_ulong plane_mask;/* plane mask */
17452 		arch_ulong foreground;/* foreground pixel */
17453 		arch_ulong background;/* background pixel */
17454 		int line_width;         /* line width */
17455 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17456 		int cap_style;          /* CapNotLast, CapButt,
17457 					   CapRound, CapProjecting */
17458 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17459 		int fill_style;         /* FillSolid, FillTiled,
17460 					   FillStippled, FillOpaeueStippled */
17461 		int fill_rule;          /* EvenOddRule, WindingRule */
17462 		int arc_mode;           /* ArcChord, ArcPieSlice */
17463 		Pixmap tile;            /* tile pixmap for tiling operations */
17464 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17465 		int ts_x_origin;        /* offset for tile or stipple operations */
17466 		int ts_y_origin;
17467 		Font font;              /* default text font for text operations */
17468 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17469 		Bool graphics_exposures;/* boolean, should exposures be generated */
17470 		int clip_x_origin;      /* origin for clipping */
17471 		int clip_y_origin;
17472 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17473 		int dash_offset;        /* patterned/dashed line information */
17474 		char dashes;
17475 	}
17476 
17477 	struct XColor {
17478 		arch_ulong pixel;
17479 		ushort red, green, blue;
17480 		byte flags;
17481 		byte pad;
17482 	}
17483 
17484 	struct XRectangle {
17485 		short x;
17486 		short y;
17487 		ushort width;
17488 		ushort height;
17489 	}
17490 
17491 	enum ClipByChildren = 0;
17492 	enum IncludeInferiors = 1;
17493 
17494 	enum Atom XA_PRIMARY = 1;
17495 	enum Atom XA_SECONDARY = 2;
17496 	enum Atom XA_STRING = 31;
17497 	enum Atom XA_CARDINAL = 6;
17498 	enum Atom XA_WM_NAME = 39;
17499 	enum Atom XA_ATOM = 4;
17500 	enum Atom XA_WINDOW = 33;
17501 	enum Atom XA_WM_HINTS = 35;
17502 	enum int PropModeAppend = 2;
17503 	enum int PropModeReplace = 0;
17504 	enum int PropModePrepend = 1;
17505 
17506 	enum int CopyFromParent = 0;
17507 	enum int InputOutput = 1;
17508 
17509 	// XWMHints
17510 	enum InputHint = 1 << 0;
17511 	enum StateHint = 1 << 1;
17512 	enum IconPixmapHint = (1L << 2);
17513 	enum IconWindowHint = (1L << 3);
17514 	enum IconPositionHint = (1L << 4);
17515 	enum IconMaskHint = (1L << 5);
17516 	enum WindowGroupHint = (1L << 6);
17517 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17518 	enum XUrgencyHint = (1L << 8);
17519 
17520 	// GC Components
17521 	enum GCFunction           =   (1L<<0);
17522 	enum GCPlaneMask         =    (1L<<1);
17523 	enum GCForeground       =     (1L<<2);
17524 	enum GCBackground      =      (1L<<3);
17525 	enum GCLineWidth      =       (1L<<4);
17526 	enum GCLineStyle     =        (1L<<5);
17527 	enum GCCapStyle     =         (1L<<6);
17528 	enum GCJoinStyle   =          (1L<<7);
17529 	enum GCFillStyle  =           (1L<<8);
17530 	enum GCFillRule  =            (1L<<9);
17531 	enum GCTile     =             (1L<<10);
17532 	enum GCStipple           =    (1L<<11);
17533 	enum GCTileStipXOrigin  =     (1L<<12);
17534 	enum GCTileStipYOrigin =      (1L<<13);
17535 	enum GCFont               =   (1L<<14);
17536 	enum GCSubwindowMode     =    (1L<<15);
17537 	enum GCGraphicsExposures=     (1L<<16);
17538 	enum GCClipXOrigin     =      (1L<<17);
17539 	enum GCClipYOrigin    =       (1L<<18);
17540 	enum GCClipMask      =        (1L<<19);
17541 	enum GCDashOffset   =         (1L<<20);
17542 	enum GCDashList    =          (1L<<21);
17543 	enum GCArcMode    =           (1L<<22);
17544 	enum GCLastBit   =            22;
17545 
17546 
17547 	enum int WithdrawnState = 0;
17548 	enum int NormalState = 1;
17549 	enum int IconicState = 3;
17550 
17551 }
17552 } else version (OSXCocoa) {
17553 private:
17554 	alias void* id;
17555 	alias void* Class;
17556 	alias void* SEL;
17557 	alias void* IMP;
17558 	alias void* Ivar;
17559 	alias byte BOOL;
17560 	alias const(void)* CFStringRef;
17561 	alias const(void)* CFAllocatorRef;
17562 	alias const(void)* CFTypeRef;
17563 	alias const(void)* CGContextRef;
17564 	alias const(void)* CGColorSpaceRef;
17565 	alias const(void)* CGImageRef;
17566 	alias ulong CGBitmapInfo;
17567 
17568 	struct objc_super {
17569 		id self;
17570 		Class superclass;
17571 	}
17572 
17573 	struct CFRange {
17574 		long location, length;
17575 	}
17576 
17577 	struct NSPoint {
17578 		double x, y;
17579 
17580 		static fromTuple(T)(T tupl) {
17581 			return NSPoint(tupl.tupleof);
17582 		}
17583 	}
17584 	struct NSSize {
17585 		double width, height;
17586 	}
17587 	struct NSRect {
17588 		NSPoint origin;
17589 		NSSize size;
17590 	}
17591 	alias NSPoint CGPoint;
17592 	alias NSSize CGSize;
17593 	alias NSRect CGRect;
17594 
17595 	struct CGAffineTransform {
17596 		double a, b, c, d, tx, ty;
17597 	}
17598 
17599 	enum NSApplicationActivationPolicyRegular = 0;
17600 	enum NSBackingStoreBuffered = 2;
17601 	enum kCFStringEncodingUTF8 = 0x08000100;
17602 
17603 	enum : size_t {
17604 		NSBorderlessWindowMask = 0,
17605 		NSTitledWindowMask = 1 << 0,
17606 		NSClosableWindowMask = 1 << 1,
17607 		NSMiniaturizableWindowMask = 1 << 2,
17608 		NSResizableWindowMask = 1 << 3,
17609 		NSTexturedBackgroundWindowMask = 1 << 8
17610 	}
17611 
17612 	enum : ulong {
17613 		kCGImageAlphaNone,
17614 		kCGImageAlphaPremultipliedLast,
17615 		kCGImageAlphaPremultipliedFirst,
17616 		kCGImageAlphaLast,
17617 		kCGImageAlphaFirst,
17618 		kCGImageAlphaNoneSkipLast,
17619 		kCGImageAlphaNoneSkipFirst
17620 	}
17621 	enum : ulong {
17622 		kCGBitmapAlphaInfoMask = 0x1F,
17623 		kCGBitmapFloatComponents = (1 << 8),
17624 		kCGBitmapByteOrderMask = 0x7000,
17625 		kCGBitmapByteOrderDefault = (0 << 12),
17626 		kCGBitmapByteOrder16Little = (1 << 12),
17627 		kCGBitmapByteOrder32Little = (2 << 12),
17628 		kCGBitmapByteOrder16Big = (3 << 12),
17629 		kCGBitmapByteOrder32Big = (4 << 12)
17630 	}
17631 	enum CGPathDrawingMode {
17632 		kCGPathFill,
17633 		kCGPathEOFill,
17634 		kCGPathStroke,
17635 		kCGPathFillStroke,
17636 		kCGPathEOFillStroke
17637 	}
17638 	enum objc_AssociationPolicy : size_t {
17639 		OBJC_ASSOCIATION_ASSIGN = 0,
17640 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
17641 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
17642 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
17643 		OBJC_ASSOCIATION_COPY = 0x303 //01403
17644 	}
17645 
17646 	extern(C) {
17647 		id objc_msgSend(id receiver, SEL selector, ...);
17648 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
17649 		id objc_getClass(const(char)* name);
17650 		SEL sel_registerName(const(char)* str);
17651 		Class objc_allocateClassPair(Class superclass, const(char)* name,
17652 									 size_t extra_bytes);
17653 		void objc_registerClassPair(Class cls);
17654 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
17655 		id objc_getAssociatedObject(id object, void* key);
17656 		void objc_setAssociatedObject(id object, void* key, id value,
17657 									  objc_AssociationPolicy policy);
17658 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
17659 		id object_getIvar(id object, Ivar ivar);
17660 		void object_setIvar(id object, Ivar ivar, id value);
17661 		BOOL class_addIvar(Class cls, const(char)* name,
17662 						   size_t size, ubyte alignment, const(char)* types);
17663 
17664 		extern __gshared id NSApp;
17665 
17666 		void CFRelease(CFTypeRef obj);
17667 
17668 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
17669 											const(char)* bytes, long numBytes,
17670 											long encoding,
17671 											BOOL isExternalRepresentation);
17672 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
17673 							 char lossByte, bool isExternalRepresentation,
17674 							 char* buffer, long maxBufLen, long* usedBufLen);
17675 		long CFStringGetLength(CFStringRef theString);
17676 
17677 		CGContextRef CGBitmapContextCreate(void* data,
17678 										   size_t width, size_t height,
17679 										   size_t bitsPerComponent,
17680 										   size_t bytesPerRow,
17681 										   CGColorSpaceRef colorspace,
17682 										   CGBitmapInfo bitmapInfo);
17683 		void CGContextRelease(CGContextRef c);
17684 		ubyte* CGBitmapContextGetData(CGContextRef c);
17685 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
17686 		size_t CGBitmapContextGetWidth(CGContextRef c);
17687 		size_t CGBitmapContextGetHeight(CGContextRef c);
17688 
17689 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
17690 		void CGColorSpaceRelease(CGColorSpaceRef cs);
17691 
17692 		void CGContextSetRGBStrokeColor(CGContextRef c,
17693 										double red, double green, double blue,
17694 										double alpha);
17695 		void CGContextSetRGBFillColor(CGContextRef c,
17696 									  double red, double green, double blue,
17697 									  double alpha);
17698 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
17699 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
17700 									  const(char)* str, size_t length);
17701 		void CGContextStrokeLineSegments(CGContextRef c,
17702 										 const(CGPoint)* points, size_t count);
17703 
17704 		void CGContextBeginPath(CGContextRef c);
17705 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
17706 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
17707 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
17708 							 double startAngle, double endAngle, long clockwise);
17709 		void CGContextAddRect(CGContextRef c, CGRect rect);
17710 		void CGContextAddLines(CGContextRef c,
17711 							   const(CGPoint)* points, size_t count);
17712 		void CGContextSaveGState(CGContextRef c);
17713 		void CGContextRestoreGState(CGContextRef c);
17714 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
17715 								 ulong textEncoding);
17716 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
17717 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
17718 
17719 		void CGImageRelease(CGImageRef image);
17720 	}
17721 
17722 private:
17723     // A convenient method to create a CFString (=NSString) from a D string.
17724     CFStringRef createCFString(string str) {
17725         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
17726                                              kCFStringEncodingUTF8, false);
17727     }
17728 
17729     // Objective-C calls.
17730     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
17731         auto _cmd = sel_registerName(selector.ptr);
17732         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17733         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
17734     }
17735     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
17736         auto _cmd = sel_registerName(selector.ptr);
17737         auto cls = objc_getClass(className);
17738         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17739         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
17740     }
17741     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
17742         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
17743     }
17744 
17745     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
17746     alias objc_msgSend_classMethod!("alloc", id) alloc;
17747     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
17748                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
17749     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
17750     alias objc_msgSend_specialized!("center", void) center;
17751     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
17752     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
17753     alias objc_msgSend_specialized!("release", void) release;
17754     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
17755     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
17756     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
17757     alias objc_msgSend_specialized!("invalidate", void) invalidate;
17758     alias objc_msgSend_specialized!("close", void) close;
17759     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
17760                                     id, double, id, SEL, id, BOOL) scheduledTimer;
17761     alias objc_msgSend_specialized!("run", void) run;
17762     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
17763                                     id) currentNSGraphicsContext;
17764     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
17765     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
17766     alias objc_msgSend_specialized!("superclass", Class) superclass;
17767     alias objc_msgSend_specialized!("init", id) init;
17768     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
17769     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
17770     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
17771                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
17772     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
17773     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
17774     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
17775                                     void, BOOL) activateIgnoringOtherApps;
17776     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
17777                                     id) sharedNSApplication;
17778     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
17779 } else static assert(0, "Unsupported operating system");
17780 
17781 
17782 version(OSXCocoa) {
17783 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
17784 	//
17785 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
17786 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
17787 	//
17788 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
17789 	// Probably won't even fully compile right now
17790 
17791     import std.math : PI; // OSX Only
17792     import std.algorithm : map; // OSX Only
17793     import std.array : array; // OSX Only
17794 
17795     alias SimpleWindow NativeWindowHandle;
17796     alias void delegate(id) NativeEventHandler;
17797 
17798     __gshared Ivar simpleWindowIvar;
17799 
17800     enum KEY_ESCAPE = 27;
17801 
17802     mixin template NativeImageImplementation() {
17803         CGContextRef context;
17804         ubyte* rawData;
17805     final:
17806 
17807 	void convertToRgbaBytes(ubyte[] where) {
17808 		assert(where.length == this.width * this.height * 4);
17809 
17810 		// if rawData had a length....
17811 		//assert(rawData.length == where.length);
17812 		for(long idx = 0; idx < where.length; idx += 4) {
17813 			auto alpha = rawData[idx + 3];
17814 			if(alpha == 255) {
17815 				where[idx + 0] = rawData[idx + 0]; // r
17816 				where[idx + 1] = rawData[idx + 1]; // g
17817 				where[idx + 2] = rawData[idx + 2]; // b
17818 				where[idx + 3] = rawData[idx + 3]; // a
17819 			} else {
17820 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
17821 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
17822 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
17823 				where[idx + 3] = rawData[idx + 3]; // a
17824 
17825 			}
17826 		}
17827 	}
17828 
17829 	void setFromRgbaBytes(in ubyte[] where) {
17830 		// FIXME: this is probably wrong
17831 		assert(where.length == this.width * this.height * 4);
17832 
17833 		// if rawData had a length....
17834 		//assert(rawData.length == where.length);
17835 		for(long idx = 0; idx < where.length; idx += 4) {
17836 			auto alpha = rawData[idx + 3];
17837 			if(alpha == 255) {
17838 				rawData[idx + 0] = where[idx + 0]; // r
17839 				rawData[idx + 1] = where[idx + 1]; // g
17840 				rawData[idx + 2] = where[idx + 2]; // b
17841 				rawData[idx + 3] = where[idx + 3]; // a
17842 			} else {
17843 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
17844 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
17845 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
17846 				rawData[idx + 3] = where[idx + 3]; // a
17847 
17848 			}
17849 		}
17850 	}
17851 
17852 
17853         void createImage(int width, int height, bool forcexshm=false) {
17854             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17855             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
17856                                             colorSpace,
17857                                             kCGImageAlphaPremultipliedLast
17858                                                    |kCGBitmapByteOrder32Big);
17859             CGColorSpaceRelease(colorSpace);
17860             rawData = CGBitmapContextGetData(context);
17861         }
17862         void dispose() {
17863             CGContextRelease(context);
17864         }
17865 
17866         void setPixel(int x, int y, Color c) {
17867             auto offset = (y * width + x) * 4;
17868             if (c.a == 255) {
17869                 rawData[offset + 0] = c.r;
17870                 rawData[offset + 1] = c.g;
17871                 rawData[offset + 2] = c.b;
17872                 rawData[offset + 3] = c.a;
17873             } else {
17874                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
17875                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
17876                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
17877                 rawData[offset + 3] = c.a;
17878             }
17879         }
17880     }
17881 
17882     mixin template NativeScreenPainterImplementation() {
17883         CGContextRef context;
17884         ubyte[4] _outlineComponents;
17885 	id view;
17886 
17887         void create(NativeWindowHandle window) {
17888             context = window.drawingContext;
17889 	    view = window.view;
17890         }
17891 
17892         void dispose() {
17893             	setNeedsDisplay(view, true);
17894         }
17895 
17896 	bool manualInvalidations;
17897 	void invalidateRect(Rectangle invalidRect) { }
17898 
17899 	// NotYetImplementedException
17900 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
17901 	void rasterOp(RasterOp op) {}
17902 	Pen _activePen;
17903 	Color _fillColor;
17904 	Rectangle _clipRectangle;
17905 	void setClipRectangle(int, int, int, int) {}
17906 	void setFont(OperatingSystemFont) {}
17907 	int fontHeight() { return 14; }
17908 
17909 	// end
17910 
17911         void pen(Pen pen) {
17912 	    _activePen = pen;
17913 	    auto color = pen.color; // FIXME
17914             double alphaComponent = color.a/255.0f;
17915             CGContextSetRGBStrokeColor(context,
17916                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
17917 
17918             if (color.a != 255) {
17919                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
17920                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
17921                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
17922                 _outlineComponents[3] = color.a;
17923             } else {
17924                 _outlineComponents[0] = color.r;
17925                 _outlineComponents[1] = color.g;
17926                 _outlineComponents[2] = color.b;
17927                 _outlineComponents[3] = color.a;
17928             }
17929         }
17930 
17931         @property void fillColor(Color color) {
17932             CGContextSetRGBFillColor(context,
17933                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
17934         }
17935 
17936         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
17937 		// NotYetImplementedException for upper left/width/height
17938             auto cgImage = CGBitmapContextCreateImage(image.context);
17939             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17940                                CGBitmapContextGetHeight(image.context));
17941             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17942             CGImageRelease(cgImage);
17943         }
17944 
17945 	version(OSXCocoa) {} else // NotYetImplementedException
17946         void drawPixmap(Sprite image, int x, int y) {
17947 		// FIXME: is this efficient?
17948             auto cgImage = CGBitmapContextCreateImage(image.context);
17949             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17950                                CGBitmapContextGetHeight(image.context));
17951             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17952             CGImageRelease(cgImage);
17953         }
17954 
17955 
17956         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
17957 		// FIXME: alignment
17958             if (_outlineComponents[3] != 0) {
17959                 CGContextSaveGState(context);
17960                 auto invAlpha = 1.0f/_outlineComponents[3];
17961                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
17962                                                   _outlineComponents[1]*invAlpha,
17963                                                   _outlineComponents[2]*invAlpha,
17964                                                   _outlineComponents[3]/255.0f);
17965                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
17966 // auto cfstr = cast(id)createCFString(text);
17967 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
17968 // NSPoint(x, y), null);
17969 // CFRelease(cfstr);
17970                 CGContextRestoreGState(context);
17971             }
17972         }
17973 
17974         void drawPixel(int x, int y) {
17975             auto rawData = CGBitmapContextGetData(context);
17976             auto width = CGBitmapContextGetWidth(context);
17977             auto height = CGBitmapContextGetHeight(context);
17978             auto offset = ((height - y - 1) * width + x) * 4;
17979             rawData[offset .. offset+4] = _outlineComponents;
17980         }
17981 
17982         void drawLine(int x1, int y1, int x2, int y2) {
17983             CGPoint[2] linePoints;
17984             linePoints[0] = CGPoint(x1, y1);
17985             linePoints[1] = CGPoint(x2, y2);
17986             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
17987         }
17988 
17989         void drawRectangle(int x, int y, int width, int height) {
17990             CGContextBeginPath(context);
17991             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
17992             CGContextAddRect(context, rect);
17993             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17994         }
17995 
17996         void drawEllipse(int x1, int y1, int x2, int y2) {
17997             CGContextBeginPath(context);
17998             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
17999             CGContextAddEllipseInRect(context, rect);
18000             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18001         }
18002 
18003         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18004             // @@@BUG@@@ Does not support elliptic arc (width != height).
18005             CGContextBeginPath(context);
18006             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18007                             start*PI/(180*64), finish*PI/(180*64), 0);
18008             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18009         }
18010 
18011         void drawPolygon(Point[] intPoints) {
18012             CGContextBeginPath(context);
18013             auto points = array(map!(CGPoint.fromTuple)(intPoints));
18014             CGContextAddLines(context, points.ptr, points.length);
18015             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18016         }
18017     }
18018 
18019     mixin template NativeSimpleWindowImplementation() {
18020         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18021             synchronized {
18022                 if (NSApp == null) initializeApp();
18023             }
18024 
18025             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18026 
18027             // create the window.
18028             window = initWithContentRect(alloc("NSWindow"),
18029                                          contentRect,
18030                                          NSTitledWindowMask
18031                                             |NSClosableWindowMask
18032                                             |NSMiniaturizableWindowMask
18033                                             |NSResizableWindowMask,
18034                                          NSBackingStoreBuffered,
18035                                          true);
18036 
18037             // set the title & move the window to center.
18038             auto windowTitle = createCFString(title);
18039             setTitle(window, windowTitle);
18040             CFRelease(windowTitle);
18041             center(window);
18042 
18043             // create area to draw on.
18044             auto colorSpace = CGColorSpaceCreateDeviceRGB();
18045             drawingContext = CGBitmapContextCreate(null, width, height,
18046                                                    8, 4*width, colorSpace,
18047                                                    kCGImageAlphaPremultipliedLast
18048                                                       |kCGBitmapByteOrder32Big);
18049             CGColorSpaceRelease(colorSpace);
18050             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18051             auto matrix = CGContextGetTextMatrix(drawingContext);
18052             matrix.c = -matrix.c;
18053             matrix.d = -matrix.d;
18054             CGContextSetTextMatrix(drawingContext, matrix);
18055 
18056             // create the subview that things will be drawn on.
18057             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
18058             setContentView(window, view);
18059             object_setIvar(view, simpleWindowIvar, cast(id)this);
18060             release(view);
18061 
18062             setBackgroundColor(window, whiteNSColor);
18063             makeKeyAndOrderFront(window, null);
18064         }
18065         void dispose() {
18066             closeWindow();
18067             release(window);
18068         }
18069         void closeWindow() {
18070             invalidate(timer);
18071             .close(window);
18072         }
18073 
18074         ScreenPainter getPainter(bool manualInvalidations) {
18075 		return ScreenPainter(this, this, manualInvalidations);
18076 	}
18077 
18078         id window;
18079         id timer;
18080         id view;
18081         CGContextRef drawingContext;
18082     }
18083 
18084     extern(C) {
18085     private:
18086         BOOL returnTrue3(id self, SEL _cmd, id app) {
18087             return true;
18088         }
18089         BOOL returnTrue2(id self, SEL _cmd) {
18090             return true;
18091         }
18092 
18093         void pulse(id self, SEL _cmd) {
18094             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18095             simpleWindow.handlePulse();
18096             setNeedsDisplay(self, true);
18097         }
18098         void drawRect(id self, SEL _cmd, NSRect rect) {
18099             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18100             auto curCtx = graphicsPort(currentNSGraphicsContext);
18101             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18102             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
18103                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
18104             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18105             CGImageRelease(cgImage);
18106         }
18107         void keyDown(id self, SEL _cmd, id event) {
18108             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18109 
18110             // the event may have multiple characters, and we send them all at
18111             // once.
18112             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
18113                 auto chars = characters(event);
18114                 auto range = CFRange(0, CFStringGetLength(chars));
18115                 auto buffer = new char[range.length*3];
18116                 long actualLength;
18117                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
18118                                  buffer.ptr, cast(int) buffer.length, &actualLength);
18119                 foreach (dchar dc; buffer[0..actualLength]) {
18120                     if (simpleWindow.handleCharEvent)
18121                         simpleWindow.handleCharEvent(dc);
18122 		    // NotYetImplementedException
18123                     //if (simpleWindow.handleKeyEvent)
18124                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
18125                 }
18126             }
18127 
18128             // the event's 'keyCode' is hardware-dependent. I don't think people
18129             // will like it. Let's leave it to the native handler.
18130 
18131             // perform the default action.
18132 
18133 	    // so the default action is to make a bomp sound and i dont want that
18134 	    // sooooooooo yeah not gonna do that.
18135 
18136             //auto superData = objc_super(self, superclass(self));
18137             //alias extern(C) void function(objc_super*, SEL, id) T;
18138             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
18139         }
18140     }
18141 
18142     // initialize the app so that it can be interacted with the user.
18143     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
18144     private void initializeApp() {
18145         // push an autorelease pool to avoid leaking.
18146         init(alloc("NSAutoreleasePool"));
18147 
18148         // create a new NSApp instance
18149         sharedNSApplication;
18150         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
18151 
18152         // create the "Quit" menu.
18153         auto menuBar = init(alloc("NSMenu"));
18154         auto appMenuItem = init(alloc("NSMenuItem"));
18155         addItem(menuBar, appMenuItem);
18156         setMainMenu(NSApp, menuBar);
18157         release(appMenuItem);
18158         release(menuBar);
18159 
18160         auto appMenu = init(alloc("NSMenu"));
18161         auto quitTitle = createCFString("Quit");
18162         auto q = createCFString("q");
18163         auto quitItem = initWithTitle(alloc("NSMenuItem"),
18164                                       quitTitle, sel_registerName("terminate:"), q);
18165         addItem(appMenu, quitItem);
18166         setSubmenu(appMenuItem, appMenu);
18167         release(quitItem);
18168         release(appMenu);
18169         CFRelease(q);
18170         CFRelease(quitTitle);
18171 
18172         // assign a delegate for the application, allow it to quit when the last
18173         // window is closed.
18174         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
18175                                                     "SDWindowCloseDelegate", 0);
18176         class_addMethod(delegateClass,
18177                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
18178                         &returnTrue3, "c@:@");
18179         objc_registerClassPair(delegateClass);
18180 
18181         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
18182         setDelegate(NSApp, appDelegate);
18183         activateIgnoringOtherApps(NSApp, true);
18184 
18185         // create a new view that draws the graphics and respond to keyDown
18186         // events.
18187         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
18188                                                 "SDGraphicsView", (void*).sizeof);
18189         class_addIvar(viewClass, "simpledisplay_simpleWindow",
18190                       (void*).sizeof, (void*).alignof, "^v");
18191         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
18192                         &pulse, "v@:");
18193         class_addMethod(viewClass, sel_registerName("drawRect:"),
18194                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
18195         class_addMethod(viewClass, sel_registerName("isFlipped"),
18196                         &returnTrue2, "c@:");
18197         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
18198                         &returnTrue2, "c@:");
18199         class_addMethod(viewClass, sel_registerName("keyDown:"),
18200                         &keyDown, "v@:@");
18201         objc_registerClassPair(viewClass);
18202         simpleWindowIvar = class_getInstanceVariable(viewClass,
18203                                                      "simpledisplay_simpleWindow");
18204     }
18205 }
18206 
18207 version(without_opengl) {} else
18208 extern(System) nothrow @nogc {
18209 	//enum uint GL_VERSION = 0x1F02;
18210 	//const(char)* glGetString (/*GLenum*/uint);
18211 	version(X11) {
18212 	static if (!SdpyIsUsingIVGLBinds) {
18213 
18214 		enum GLX_X_RENDERABLE = 0x8012;
18215 		enum GLX_DRAWABLE_TYPE = 0x8010;
18216 		enum GLX_RENDER_TYPE = 0x8011;
18217 		enum GLX_X_VISUAL_TYPE = 0x22;
18218 		enum GLX_TRUE_COLOR = 0x8002;
18219 		enum GLX_WINDOW_BIT = 0x00000001;
18220 		enum GLX_RGBA_BIT = 0x00000001;
18221 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18222 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18223 		enum GLX_SAMPLES = 0x186a1;
18224 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18225 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18226 	}
18227 
18228 		// GLX_EXT_swap_control
18229 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18230 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18231 
18232 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18233 		extern(System) {
18234 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18235 		}
18236 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18237 
18238 		// this made public so we don't have to get it again and again
18239 		public bool glXCreateContextAttribsARB_present () {
18240 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18241 				// get it
18242 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18243 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18244 			}
18245 			return (glXCreateContextAttribsARBFn !is null);
18246 		}
18247 
18248 		// this made public so we don't have to get it again and again
18249 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18250 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18251 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18252 		}
18253 
18254 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18255 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18256 
18257 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18258 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18259 			if (_glx_swapInterval_fn is null) {
18260 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18261 				if (_glx_swapInterval_fn is null) {
18262 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18263 					return;
18264 				}
18265 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
18266 			}
18267 
18268 			if(glXSwapIntervalMESA is null) {
18269 				// it seems to require both to actually take effect on many computers
18270 				// idk why
18271 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18272 				if(glXSwapIntervalMESA is null)
18273 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18274 			}
18275 
18276 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18277 				glXSwapIntervalMESA(wait ? 1 : 0);
18278 
18279 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18280 		}
18281 	} else version(Windows) {
18282 	static if (!SdpyIsUsingIVGLBinds) {
18283 	enum GL_TRUE = 1;
18284 	enum GL_FALSE = 0;
18285 	alias int GLint;
18286 
18287 	public void* glbindGetProcAddress (const(char)* name) {
18288 		void* res = wglGetProcAddress(name);
18289 		if (res is null) {
18290 			/+
18291 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18292 			import core.sys.windows.windef, core.sys.windows.winbase;
18293 			__gshared HINSTANCE dll = null;
18294 			if (dll is null) {
18295 				dll = LoadLibraryA("opengl32.dll");
18296 				if (dll is null) return null; // <32, but idc
18297 			}
18298 			res = GetProcAddress(dll, name);
18299 			+/
18300 			res = GetProcAddress(gl.libHandle, name);
18301 		}
18302 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18303 		return res;
18304 	}
18305 	}
18306 
18307 
18308  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18309         void wglSetVSync(bool wait) {
18310 		if(wglSwapIntervalEXT is null) {
18311 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18312 			if(wglSwapIntervalEXT is null)
18313 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18314 		}
18315 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18316 			return;
18317 
18318 		wglSwapIntervalEXT(wait ? 1 : 0);
18319 	}
18320 
18321 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18322 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18323 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18324 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18325 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18326 
18327 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18328 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18329 
18330 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18331 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18332 
18333 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18334 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18335 
18336 		void wglInitOtherFunctions () {
18337 			if (wglCreateContextAttribsARB is null) {
18338 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18339 			}
18340 		}
18341 	}
18342 
18343 	static if (!SdpyIsUsingIVGLBinds) {
18344 
18345 	interface GL {
18346 		extern(System) @nogc nothrow:
18347 
18348 		void glGetIntegerv(int, void*);
18349 		void glMatrixMode(int);
18350 		void glPushMatrix();
18351 		void glLoadIdentity();
18352 		void glOrtho(double, double, double, double, double, double);
18353 		void glFrustum(double, double, double, double, double, double);
18354 
18355 		void glPopMatrix();
18356 		void glEnable(int);
18357 		void glDisable(int);
18358 		void glClear(int);
18359 		void glBegin(int);
18360 		void glVertex2f(float, float);
18361 		void glVertex3f(float, float, float);
18362 		void glEnd();
18363 		void glColor3b(byte, byte, byte);
18364 		void glColor3ub(ubyte, ubyte, ubyte);
18365 		void glColor4b(byte, byte, byte, byte);
18366 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18367 		void glColor3i(int, int, int);
18368 		void glColor3ui(uint, uint, uint);
18369 		void glColor4i(int, int, int, int);
18370 		void glColor4ui(uint, uint, uint, uint);
18371 		void glColor3f(float, float, float);
18372 		void glColor4f(float, float, float, float);
18373 		void glTranslatef(float, float, float);
18374 		void glScalef(float, float, float);
18375 		version(X11) {
18376 			void glSecondaryColor3b(byte, byte, byte);
18377 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18378 			void glSecondaryColor3i(int, int, int);
18379 			void glSecondaryColor3ui(uint, uint, uint);
18380 			void glSecondaryColor3f(float, float, float);
18381 		}
18382 
18383 		void glDrawElements(int, int, int, void*);
18384 
18385 		void glRotatef(float, float, float, float);
18386 
18387 		uint glGetError();
18388 
18389 		void glDeleteTextures(int, uint*);
18390 
18391 
18392 		void glRasterPos2i(int, int);
18393 		void glDrawPixels(int, int, uint, uint, void*);
18394 		void glClearColor(float, float, float, float);
18395 
18396 
18397 		void glPixelStorei(uint, int);
18398 
18399 		void glGenTextures(uint, uint*);
18400 		void glBindTexture(int, int);
18401 		void glTexParameteri(uint, uint, int);
18402 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18403 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
18404 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18405 			/*GLsizei*/int width, /*GLsizei*/int height,
18406 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18407 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18408 
18409 		void glLineWidth(int);
18410 
18411 
18412 		void glTexCoord2f(float, float);
18413 		void glVertex2i(int, int);
18414 		void glBlendFunc (int, int);
18415 		void glDepthFunc (int);
18416 		void glViewport(int, int, int, int);
18417 
18418 		void glClearDepth(double);
18419 
18420 		void glReadBuffer(uint);
18421 		void glReadPixels(int, int, int, int, int, int, void*);
18422 
18423 		void glFlush();
18424 		void glFinish();
18425 
18426 		version(Windows) {
18427 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18428 			HGLRC wglCreateContext(HDC);
18429 			HGLRC wglCreateLayerContext(HDC, int);
18430 			BOOL wglDeleteContext(HGLRC);
18431 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18432 			HGLRC wglGetCurrentContext();
18433 			HDC wglGetCurrentDC();
18434 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18435 			PROC wglGetProcAddress(LPCSTR);
18436 			BOOL wglMakeCurrent(HDC, HGLRC);
18437 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18438 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18439 			BOOL wglShareLists(HGLRC, HGLRC);
18440 			BOOL wglSwapLayerBuffers(HDC, UINT);
18441 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18442 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18443 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18444 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18445 		}
18446 
18447 	}
18448 
18449 	interface GL3 {
18450 		extern(System) @nogc nothrow:
18451 
18452 		void glGenVertexArrays(GLsizei, GLuint*);
18453 		void glBindVertexArray(GLuint);
18454 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18455 		void glGenerateMipmap(GLenum);
18456 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18457 		void glStencilMask(GLuint);
18458 		void glStencilFunc(GLenum, GLint, GLuint);
18459 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18460 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18461 		GLuint glCreateProgram();
18462 		GLuint glCreateShader(GLenum);
18463 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18464 		void glCompileShader(GLuint);
18465 		void glGetShaderiv(GLuint, GLenum, GLint*);
18466 		void glAttachShader(GLuint, GLuint);
18467 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18468 		void glLinkProgram(GLuint);
18469 		void glGetProgramiv(GLuint, GLenum, GLint*);
18470 		void glDeleteProgram(GLuint);
18471 		void glDeleteShader(GLuint);
18472 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18473 		void glGenBuffers(GLsizei, GLuint*);
18474 
18475 		void glUniform1f(GLint location, GLfloat v0);
18476 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18477 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18478 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18479 		void glUniform1i(GLint location, GLint v0);
18480 		void glUniform2i(GLint location, GLint v0, GLint v1);
18481 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18482 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18483 		void glUniform1ui(GLint location, GLuint v0);
18484 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18485 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18486 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18487 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18488 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18489 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18490 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18491 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18492 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18493 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18494 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18495 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18496 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18497 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18498 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18499 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18500 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18501 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18502 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18503 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18504 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18505 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18506 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18507 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18508 
18509 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18510 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18511 		void glDrawArrays(GLenum, GLint, GLsizei);
18512 		void glStencilOp(GLenum, GLenum, GLenum);
18513 		void glUseProgram(GLuint);
18514 		void glCullFace(GLenum);
18515 		void glFrontFace(GLenum);
18516 		void glActiveTexture(GLenum);
18517 		void glBindBuffer(GLenum, GLuint);
18518 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18519 		void glEnableVertexAttribArray(GLuint);
18520 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18521 		void glUniform1i(GLint, GLint);
18522 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18523 		void glDisableVertexAttribArray(GLuint);
18524 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18525 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18526 		void glLogicOp (GLenum opcode);
18527 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18528 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18529 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18530 		GLenum glCheckFramebufferStatus (GLenum target);
18531 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18532 	}
18533 
18534 	interface GL4 {
18535 		extern(System) @nogc nothrow:
18536 
18537 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18538 			/*GLsizei*/int width, /*GLsizei*/int height,
18539 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18540 	}
18541 
18542 	interface GLU {
18543 		extern(System) @nogc nothrow:
18544 
18545 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18546 		void gluPerspective(double, double, double, double);
18547 
18548 		char* gluErrorString(uint);
18549 	}
18550 
18551 
18552 	enum GL_RED = 0x1903;
18553 	enum GL_ALPHA = 0x1906;
18554 
18555 	enum uint GL_FRONT = 0x0404;
18556 
18557 	enum uint GL_BLEND = 0x0be2;
18558 	enum uint GL_LEQUAL = 0x0203;
18559 
18560 
18561 	enum uint GL_RGB = 0x1907;
18562 	enum uint GL_BGRA = 0x80e1;
18563 	enum uint GL_RGBA = 0x1908;
18564 	enum uint GL_TEXTURE_2D =   0x0DE1;
18565 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18566 	enum uint GL_NEAREST = 0x2600;
18567 	enum uint GL_LINEAR = 0x2601;
18568 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18569 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18570 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18571 	enum uint GL_REPEAT = 0x2901;
18572 	enum uint GL_CLAMP = 0x2900;
18573 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18574 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18575 	enum uint GL_DECAL = 0x2101;
18576 	enum uint GL_MODULATE = 0x2100;
18577 	enum uint GL_TEXTURE_ENV = 0x2300;
18578 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18579 	enum uint GL_REPLACE = 0x1E01;
18580 	enum uint GL_LIGHTING = 0x0B50;
18581 	enum uint GL_DITHER = 0x0BD0;
18582 
18583 	enum uint GL_NO_ERROR = 0;
18584 
18585 
18586 
18587 	enum int GL_VIEWPORT = 0x0BA2;
18588 	enum int GL_MODELVIEW = 0x1700;
18589 	enum int GL_TEXTURE = 0x1702;
18590 	enum int GL_PROJECTION = 0x1701;
18591 	enum int GL_DEPTH_TEST = 0x0B71;
18592 
18593 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18594 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18595 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18596 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18597 
18598 	enum int GL_POINTS = 0x0000;
18599 	enum int GL_LINES =  0x0001;
18600 	enum int GL_LINE_LOOP = 0x0002;
18601 	enum int GL_LINE_STRIP = 0x0003;
18602 	enum int GL_TRIANGLES = 0x0004;
18603 	enum int GL_TRIANGLE_STRIP = 5;
18604 	enum int GL_TRIANGLE_FAN = 6;
18605 	enum int GL_QUADS = 7;
18606 	enum int GL_QUAD_STRIP = 8;
18607 	enum int GL_POLYGON = 9;
18608 
18609 	alias GLvoid = void;
18610 	alias GLboolean = ubyte;
18611 	alias GLuint = uint;
18612 	alias GLenum = uint;
18613 	alias GLchar = char;
18614 	alias GLsizei = int;
18615 	alias GLfloat = float;
18616 	alias GLintptr = size_t;
18617 	alias GLsizeiptr = ptrdiff_t;
18618 
18619 
18620 	enum uint GL_INVALID_ENUM = 0x0500;
18621 
18622 	enum uint GL_ZERO = 0;
18623 	enum uint GL_ONE = 1;
18624 
18625 	enum uint GL_BYTE = 0x1400;
18626 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18627 	enum uint GL_SHORT = 0x1402;
18628 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18629 	enum uint GL_INT = 0x1404;
18630 	enum uint GL_UNSIGNED_INT = 0x1405;
18631 	enum uint GL_FLOAT = 0x1406;
18632 	enum uint GL_2_BYTES = 0x1407;
18633 	enum uint GL_3_BYTES = 0x1408;
18634 	enum uint GL_4_BYTES = 0x1409;
18635 	enum uint GL_DOUBLE = 0x140A;
18636 
18637 	enum uint GL_STREAM_DRAW = 0x88E0;
18638 
18639 	enum uint GL_CCW = 0x0901;
18640 
18641 	enum uint GL_STENCIL_TEST = 0x0B90;
18642 	enum uint GL_SCISSOR_TEST = 0x0C11;
18643 
18644 	enum uint GL_EQUAL = 0x0202;
18645 	enum uint GL_NOTEQUAL = 0x0205;
18646 
18647 	enum uint GL_ALWAYS = 0x0207;
18648 	enum uint GL_KEEP = 0x1E00;
18649 
18650 	enum uint GL_INCR = 0x1E02;
18651 
18652 	enum uint GL_INCR_WRAP = 0x8507;
18653 	enum uint GL_DECR_WRAP = 0x8508;
18654 
18655 	enum uint GL_CULL_FACE = 0x0B44;
18656 	enum uint GL_BACK = 0x0405;
18657 
18658 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18659 	enum uint GL_VERTEX_SHADER = 0x8B31;
18660 
18661 	enum uint GL_COMPILE_STATUS = 0x8B81;
18662 	enum uint GL_LINK_STATUS = 0x8B82;
18663 
18664 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18665 
18666 	enum uint GL_STATIC_DRAW = 0x88E4;
18667 
18668 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18669 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18670 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18671 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18672 
18673 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18674 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18675 
18676 	enum uint GL_TEXTURE0 = 0x84C0U;
18677 	enum uint GL_TEXTURE1 = 0x84C1U;
18678 
18679 	enum uint GL_ARRAY_BUFFER = 0x8892;
18680 
18681 	enum uint GL_SRC_COLOR = 0x0300;
18682 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18683 	enum uint GL_SRC_ALPHA = 0x0302;
18684 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18685 	enum uint GL_DST_ALPHA = 0x0304;
18686 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18687 	enum uint GL_DST_COLOR = 0x0306;
18688 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18689 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18690 
18691 	enum uint GL_INVERT = 0x150AU;
18692 
18693 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18694 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18695 
18696 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18697 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18698 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18699 
18700 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18701 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18702 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18703 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18704 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18705 
18706 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18707 	enum uint GL_CLEAR = 0x1500U;
18708 	enum uint GL_COPY = 0x1503U;
18709 	enum uint GL_XOR = 0x1506U;
18710 
18711 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18712 
18713 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18714 
18715 	}
18716 }
18717 
18718 /++
18719 	History:
18720 		Added September 10, 2021. Previously it would have listed openGlLibrariesSuccessfullyLoaded as false if it couldn't find GLU but really opengl3 works fine without it so I didn't want to keep it required anymore.
18721 +/
18722 __gshared bool gluSuccessfullyLoaded = true;
18723 
18724 version(without_opengl) {} else {
18725 static if(!SdpyIsUsingIVGLBinds) {
18726 	version(Windows) {
18727 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18728 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18729 	} else {
18730 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18731 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18732 	}
18733 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18734 
18735 
18736 	shared static this() {
18737 		gl.loadDynamicLibrary();
18738 
18739 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18740 		// unless those functions are actually used
18741 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18742 		glu.loadDynamicLibrary();
18743 	}
18744 }
18745 }
18746 
18747 /++
18748 	Convenience method for converting D arrays to opengl buffer data
18749 
18750 	I would LOVE to overload it with the original glBufferData, but D won't
18751 	let me since glBufferData is a function pointer :(
18752 
18753 	Added: August 25, 2020 (version 8.5)
18754 +/
18755 version(without_opengl) {} else
18756 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18757 	glBufferData(target, data.length, data.ptr, usage);
18758 }
18759 
18760 /+
18761 /++
18762 	A matrix for simple uses that easily integrates with [OpenGlShader].
18763 
18764 	Might not be useful to you since it only as some simple functions and
18765 	probably isn't that fast.
18766 
18767 	Note it uses an inline static array for its storage, so copying it
18768 	may be expensive.
18769 +/
18770 struct BasicMatrix(int columns, int rows, T = float) {
18771 	import core.stdc.math;
18772 
18773 	T[columns * rows] data = 0.0;
18774 
18775 	/++
18776 		Basic operations that operate *in place*.
18777 	+/
18778 	void translate() {
18779 
18780 	}
18781 
18782 	/// ditto
18783 	void scale() {
18784 
18785 	}
18786 
18787 	/// ditto
18788 	void rotate() {
18789 
18790 	}
18791 
18792 	/++
18793 
18794 	+/
18795 	static if(columns == rows)
18796 	static BasicMatrix identity() {
18797 		BasicMatrix m;
18798 		foreach(i; 0 .. columns)
18799 			data[0 + i + i * columns] = 1.0;
18800 		return m;
18801 	}
18802 
18803 	static BasicMatrix ortho() {
18804 		return BasicMatrix.init;
18805 	}
18806 }
18807 +/
18808 
18809 /++
18810 	Convenience class for using opengl shaders.
18811 
18812 	Ensure that you've loaded opengl 3+ and set your active
18813 	context before trying to use this.
18814 
18815 	Added: August 25, 2020 (version 8.5)
18816 +/
18817 version(without_opengl) {} else
18818 final class OpenGlShader {
18819 	private int shaderProgram_;
18820 	private @property void shaderProgram(int a) {
18821 		shaderProgram_ = a;
18822 	}
18823 	/// Get the program ID for use in OpenGL functions.
18824 	public @property int shaderProgram() {
18825 		return shaderProgram_;
18826 	}
18827 
18828 	/++
18829 
18830 	+/
18831 	static struct Source {
18832 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
18833 		string code; ///
18834 	}
18835 
18836 	/++
18837 		Helper method to just compile some shader code and check for errors
18838 		while you do glCreateShader, etc. on the outside yourself.
18839 
18840 		This just does `glShaderSource` and `glCompileShader` for the given code.
18841 
18842 		If you the OpenGlShader class constructor, you never need to call this yourself.
18843 	+/
18844 	static void compile(int sid, Source code) {
18845 		const(char)*[1] buffer;
18846 		int[1] lengthBuffer;
18847 
18848 		buffer[0] = code.code.ptr;
18849 		lengthBuffer[0] = cast(int) code.code.length;
18850 
18851 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
18852 		glCompileShader(sid);
18853 
18854 		int success;
18855 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
18856 		if(!success) {
18857 			char[512] info;
18858 			int len;
18859 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
18860 
18861 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
18862 		}
18863 	}
18864 
18865 	/++
18866 		Calls `glLinkProgram` and throws if error a occurs.
18867 
18868 		If you the OpenGlShader class constructor, you never need to call this yourself.
18869 	+/
18870 	static void link(int shaderProgram) {
18871 		glLinkProgram(shaderProgram);
18872 		int success;
18873 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
18874 		if(!success) {
18875 			char[512] info;
18876 			int len;
18877 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
18878 
18879 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
18880 		}
18881 	}
18882 
18883 	/++
18884 		Constructs the shader object by calling `glCreateProgram`, then
18885 		compiling each given [Source], and finally, linking them together.
18886 
18887 		Throws: on compile or link failure.
18888 	+/
18889 	this(Source[] codes...) {
18890 		shaderProgram = glCreateProgram();
18891 
18892 		int[16] shadersBufferStack;
18893 
18894 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
18895 			shadersBufferStack[0 .. codes.length] :
18896 			new int[](codes.length);
18897 
18898 		foreach(idx, code; codes) {
18899 			shadersBuffer[idx] = glCreateShader(code.type);
18900 
18901 			compile(shadersBuffer[idx], code);
18902 
18903 			glAttachShader(shaderProgram, shadersBuffer[idx]);
18904 		}
18905 
18906 		link(shaderProgram);
18907 
18908 		foreach(s; shadersBuffer)
18909 			glDeleteShader(s);
18910 	}
18911 
18912 	/// Calls `glUseProgram(this.shaderProgram)`
18913 	void use() {
18914 		glUseProgram(this.shaderProgram);
18915 	}
18916 
18917 	/// Deletes the program.
18918 	void delete_() {
18919 		glDeleteProgram(shaderProgram);
18920 		shaderProgram = 0;
18921 	}
18922 
18923 	/++
18924 		[OpenGlShader.uniforms].name gives you one of these.
18925 
18926 		You can get the id out of it or just assign
18927 	+/
18928 	static struct Uniform {
18929 		/// the id passed to glUniform*
18930 		int id;
18931 
18932 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
18933 		void opAssign(float x, float y, float z, float w) {
18934 			if(id != -1)
18935 			glUniform4f(id, x, y, z, w);
18936 		}
18937 
18938 		void opAssign(float x) {
18939 			if(id != -1)
18940 			glUniform1f(id, x);
18941 		}
18942 
18943 		void opAssign(float x, float y) {
18944 			if(id != -1)
18945 			glUniform2f(id, x, y);
18946 		}
18947 
18948 		void opAssign(T)(T t) {
18949 			t.glUniform(id);
18950 		}
18951 	}
18952 
18953 	static struct UniformsHelper {
18954 		OpenGlShader _shader;
18955 
18956 		@property Uniform opDispatch(string name)() {
18957 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
18958 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
18959 			//if(i == -1)
18960 				//throw new Exception("Could not find uniform " ~ name);
18961 			return Uniform(i);
18962 		}
18963 
18964 		@property void opDispatch(string name, T)(T t) {
18965 			Uniform f = this.opDispatch!name;
18966 			t.glUniform(f);
18967 		}
18968 	}
18969 
18970 	/++
18971 		Gives access to the uniforms through dot access.
18972 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
18973 	+/
18974 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
18975 }
18976 
18977 version(without_opengl) {} else {
18978 /++
18979 	A static container of experimental types and value constructors for opengl 3+ shaders.
18980 
18981 
18982 	You can declare variables like:
18983 
18984 	```
18985 	OGL.vec3f something;
18986 	```
18987 
18988 	But generally it would be used with [OpenGlShader]'s uniform helpers like
18989 
18990 	```
18991 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
18992 	```
18993 
18994 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
18995 
18996 
18997 	History:
18998 		Added December 7, 2021. Not yet stable.
18999 +/
19000 final class OGL {
19001 	static:
19002 
19003 	private template typeFromSpecifier(string specifier) {
19004 		static if(specifier == "f")
19005 			alias typeFromSpecifier = GLfloat;
19006 		else static if(specifier == "i")
19007 			alias typeFromSpecifier = GLint;
19008 		else static if(specifier == "ui")
19009 			alias typeFromSpecifier = GLuint;
19010 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19011 	}
19012 
19013 	private template CommonType(T...) {
19014 		static if(T.length == 1)
19015 			alias CommonType = T[0];
19016 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19017 			alias CommonType = CommonType!(C, T[2 .. $]);
19018 	}
19019 
19020 	private template typesToSpecifier(T...) {
19021 		static if(is(CommonType!T == float))
19022 			enum typesToSpecifier = "f";
19023 		else static if(is(CommonType!T == int))
19024 			enum typesToSpecifier = "i";
19025 		else static if(is(CommonType!T == uint))
19026 			enum typesToSpecifier = "ui";
19027 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19028 	}
19029 
19030 	private template genNames(size_t dim, size_t dim2 = 0) {
19031 		string helper() {
19032 			string s;
19033 			if(dim2) {
19034 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19035 			} else {
19036 				if(dim > 0) s ~= "type x = 0;";
19037 				if(dim > 1) s ~= "type y = 0;";
19038 				if(dim > 2) s ~= "type z = 0;";
19039 				if(dim > 3) s ~= "type w = 0;";
19040 			}
19041 			return s;
19042 		}
19043 
19044 		enum genNames = helper();
19045 	}
19046 
19047 	// there's vec, arrays of vec, mat, and arrays of mat
19048 	template opDispatch(string name)
19049 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19050 	{
19051 		static if(name[4] == 'x') {
19052 			enum dimX = cast(int) (name[3] - '0');
19053 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19054 
19055 			enum dimY = cast(int) (name[5] - '0');
19056 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19057 
19058 			enum isArray = name[$ - 1] == 'v';
19059 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19060 			alias type = typeFromSpecifier!typeSpecifier;
19061 		} else {
19062 			enum dim = cast(int) (name[3] - '0');
19063 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19064 			enum isArray = name[$ - 1] == 'v';
19065 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19066 			alias type = typeFromSpecifier!typeSpecifier;
19067 		}
19068 
19069 		align(1)
19070 		struct opDispatch {
19071 			align(1):
19072 			static if(name[4] == 'x')
19073 				mixin(genNames!(dimX, dimY));
19074 			else
19075 				mixin(genNames!dim);
19076 
19077 			private void glUniform(OpenGlShader.Uniform assignTo) {
19078 				glUniform(assignTo.id);
19079 			}
19080 			private void glUniform(int assignTo) {
19081 				static if(name[4] == 'x') {
19082 					// FIXME
19083 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19084 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19085 				} else
19086 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19087 			}
19088 		}
19089 	}
19090 
19091 	auto vec(T...)(T members) {
19092 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19093 	}
19094 }
19095 }
19096 
19097 version(linux) {
19098 	version(with_eventloop) {} else {
19099 		private int epollFd = -1;
19100 		void prepareEventLoop() {
19101 			if(epollFd != -1)
19102 				return; // already initialized, no need to do it again
19103 			import ep = core.sys.linux.epoll;
19104 
19105 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19106 			if(epollFd == -1)
19107 				throw new Exception("epoll create failure");
19108 		}
19109 	}
19110 } else version(Posix) {
19111 	void prepareEventLoop() {}
19112 }
19113 
19114 version(X11) {
19115 	import core.stdc.locale : LC_ALL; // rdmd fix
19116 	__gshared bool sdx_isUTF8Locale;
19117 
19118 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19119 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19120 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19121 	// anal magic is here. I (Ketmar) hope you like it.
19122 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19123 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19124 	// later.
19125 
19126 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19127 	shared static this () {
19128 		if(!librariesSuccessfullyLoaded)
19129 			return;
19130 
19131 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19132 
19133 		// this doesn't hurt; it may add some locking, but the speed is still
19134 		// allows doing 60 FPS videogames; also, ignore the result, as most
19135 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19136 		// never seen this failing).
19137 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19138 
19139 		setlocale(LC_ALL, "");
19140 		// check if out locale is UTF-8
19141 		auto lct = setlocale(LC_CTYPE, null);
19142 		if (lct is null) {
19143 			sdx_isUTF8Locale = false;
19144 		} else {
19145 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19146 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19147 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19148 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19149 				{
19150 					sdx_isUTF8Locale = true;
19151 					break;
19152 				}
19153 			}
19154 		}
19155 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19156 	}
19157 }
19158 
19159 class ExperimentalTextComponent2 {
19160 	/+
19161 		Stage 1: get it working monospace
19162 		Stage 2: use proportional font
19163 		Stage 3: allow changes in inline style
19164 		Stage 4: allow new fonts and sizes in the middle
19165 		Stage 5: optimize gap buffer
19166 		Stage 6: optimize layout
19167 		Stage 7: word wrap
19168 		Stage 8: justification
19169 		Stage 9: editing, selection, etc.
19170 
19171 			Operations:
19172 				insert text
19173 				overstrike text
19174 				select
19175 				cut
19176 				modify
19177 	+/
19178 
19179 	/++
19180 		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.
19181 	+/
19182 	this(SimpleWindow window) {
19183 		this.window = window;
19184 	}
19185 
19186 	private SimpleWindow window;
19187 
19188 
19189 	/++
19190 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19191 		representing the internal parts. The first pass is focused on the x parameter, then the
19192 		renderer is responsible for going back to the parts in the current line and calling
19193 		adjustDownForAscent to change the y params.
19194 	+/
19195 	static interface ComponentRenderHelper {
19196 
19197 		/+
19198 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19199 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19200 			to move (adjust y to make room for new line) until you get back to the same position,
19201 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19202 
19203 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19204 			once you reach something that is unchanged, you can stop.
19205 		+/
19206 
19207 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19208 
19209 		int ascent() const;
19210 		int descent() const;
19211 
19212 		int advance() const;
19213 
19214 		bool endsWithExplititLineBreak() const;
19215 	}
19216 
19217 	static interface RenderResult {
19218 		/++
19219 			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.
19220 		+/
19221 		void popFront();
19222 		@property bool empty() const;
19223 		@property ComponentRenderHelper front() const;
19224 
19225 		void repositionForNextLine(Point baseline, int availableWidth);
19226 	}
19227 
19228 	static interface ComponentInFlow {
19229 		void draw(ScreenPainter painter);
19230 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19231 
19232 		bool startsWithExplicitLineBreak() const;
19233 	}
19234 
19235 	static class TextFlowComponent : ComponentInFlow {
19236 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19237 
19238 		Color foreground;
19239 		Color background;
19240 
19241 		OperatingSystemFont font; // should NEVER be null
19242 
19243 		ubyte attributes; // underline, strike through, display on new block
19244 
19245 		version(Windows)
19246 			const(wchar)[] content;
19247 		else
19248 			const(char)[] content; // this should NEVER have a newline, except at the end
19249 
19250 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19251 
19252 		// could prolly put some spacing around it too like margin / padding
19253 
19254 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19255 			in { assert(font !is null);
19256 			     assert(!font.isNull); }
19257 			do
19258 		{
19259 			this.foreground = f;
19260 			this.background = b;
19261 			this.font = font;
19262 
19263 			this.attributes = attr;
19264 			version(Windows) {
19265 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19266 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19267 				auto buffer = new wchar[](sz);
19268 				this.content = makeWindowsString(c, buffer, conversionFlags);
19269 			} else {
19270 				this.content = c.dup;
19271 			}
19272 		}
19273 
19274 		void draw(ScreenPainter painter) {
19275 			painter.setFont(this.font);
19276 			painter.outlineColor = this.foreground;
19277 			painter.fillColor = Color.transparent;
19278 			foreach(rendered; this.rendered) {
19279 				// the component works in term of baseline,
19280 				// but the painter works in term of upper left bounding box
19281 				// so need to translate that
19282 
19283 				if(this.background.a) {
19284 					painter.fillColor = this.background;
19285 					painter.outlineColor = this.background;
19286 
19287 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19288 
19289 					painter.outlineColor = this.foreground;
19290 					painter.fillColor = Color.transparent;
19291 				}
19292 
19293 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19294 
19295 				// FIXME: strike through, underline, highlight selection, etc.
19296 			}
19297 		}
19298 	}
19299 
19300 	// I could split the parts into words on render
19301 	// for easier word-wrap, each one being an unbreakable "inline-block"
19302 	private TextFlowComponent[] parts;
19303 	private int needsRerenderFrom;
19304 
19305 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19306 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19307 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19308 	}
19309 
19310 	static struct RenderedComponent {
19311 		int startX;
19312 		int startY;
19313 		short width;
19314 		// 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!
19315 		// for individual chars in here you've gotta process on demand
19316 		version(Windows)
19317 			const(wchar)[] slice;
19318 		else
19319 			const(char)[] slice;
19320 	}
19321 
19322 
19323 	void rerender(Rectangle boundingBox) {
19324 		Point baseline = boundingBox.upperLeft;
19325 
19326 		this.boundingBox.left = boundingBox.left;
19327 		this.boundingBox.top = boundingBox.top;
19328 
19329 		auto remainingParts = parts;
19330 
19331 		int largestX;
19332 
19333 
19334 		foreach(part; parts)
19335 			part.font.prepareContext(window);
19336 		scope(exit)
19337 		foreach(part; parts)
19338 			part.font.releaseContext();
19339 
19340 		calculateNextLine:
19341 
19342 		int nextLineHeight = 0;
19343 		int nextBiggestDescent = 0;
19344 
19345 		foreach(part; remainingParts) {
19346 			auto height = part.font.ascent;
19347 			if(height > nextLineHeight)
19348 				nextLineHeight = height;
19349 			if(part.font.descent > nextBiggestDescent)
19350 				nextBiggestDescent = part.font.descent;
19351 			if(part.content.length && part.content[$-1] == '\n')
19352 				break;
19353 		}
19354 
19355 		baseline.y += nextLineHeight;
19356 		auto lineStart = baseline;
19357 
19358 		while(remainingParts.length) {
19359 			remainingParts[0].rendered = null;
19360 
19361 			bool eol;
19362 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19363 				eol = true;
19364 
19365 			// FIXME: word wrap
19366 			auto font = remainingParts[0].font;
19367 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19368 			auto width = font.stringWidth(slice, window);
19369 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19370 
19371 			remainingParts = remainingParts[1 .. $];
19372 			baseline.x += width;
19373 
19374 			if(eol) {
19375 				baseline.y += nextBiggestDescent;
19376 				if(baseline.x > largestX)
19377 					largestX = baseline.x;
19378 				baseline.x = lineStart.x;
19379 				goto calculateNextLine;
19380 			}
19381 		}
19382 
19383 		if(baseline.x > largestX)
19384 			largestX = baseline.x;
19385 
19386 		this.boundingBox.right = largestX;
19387 		this.boundingBox.bottom = baseline.y;
19388 	}
19389 
19390 	// you must call rerender first!
19391 	void draw(ScreenPainter painter) {
19392 		foreach(part; parts) {
19393 			part.draw(painter);
19394 		}
19395 	}
19396 
19397 	struct IdentifyResult {
19398 		TextFlowComponent part;
19399 		int charIndexInPart;
19400 		int totalCharIndex = -1; // if this is -1, it just means the end
19401 
19402 		Rectangle boundingBox;
19403 	}
19404 
19405 	IdentifyResult identify(Point pt, bool exact = false) {
19406 		if(parts.length == 0)
19407 			return IdentifyResult(null, 0);
19408 
19409 		if(pt.y < boundingBox.top) {
19410 			if(exact)
19411 				return IdentifyResult(null, 1);
19412 			return IdentifyResult(parts[0], 0);
19413 		}
19414 		if(pt.y > boundingBox.bottom) {
19415 			if(exact)
19416 				return IdentifyResult(null, 2);
19417 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19418 		}
19419 
19420 		int tci = 0;
19421 
19422 		// I should probably like binary search this or something...
19423 		foreach(ref part; parts) {
19424 			foreach(rendered; part.rendered) {
19425 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19426 				if(rect.contains(pt)) {
19427 					auto x = pt.x - rendered.startX;
19428 					auto estimatedIdx = x / part.font.averageWidth;
19429 
19430 					if(estimatedIdx < 0)
19431 						estimatedIdx = 0;
19432 
19433 					if(estimatedIdx > rendered.slice.length)
19434 						estimatedIdx = cast(int) rendered.slice.length;
19435 
19436 					int idx;
19437 					int x1, x2;
19438 					if(part.font.isMonospace) {
19439 						auto w = part.font.averageWidth;
19440 						if(!exact && x > (estimatedIdx + 1) * w)
19441 							return IdentifyResult(null, 4);
19442 						idx = estimatedIdx;
19443 						x1 = idx * w;
19444 						x2 = (idx + 1) * w;
19445 					} else {
19446 						idx = estimatedIdx;
19447 
19448 						part.font.prepareContext(window);
19449 						scope(exit) part.font.releaseContext();
19450 
19451 						// int iterations;
19452 
19453 						while(true) {
19454 							// iterations++;
19455 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19456 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19457 
19458 							x1 += rendered.startX;
19459 							x2 += rendered.startX;
19460 
19461 							if(pt.x < x1) {
19462 								if(idx == 0) {
19463 									if(exact)
19464 										return IdentifyResult(null, 6);
19465 									else
19466 										break;
19467 								}
19468 								idx--;
19469 							} else if(pt.x > x2) {
19470 								idx++;
19471 								if(idx > rendered.slice.length) {
19472 									if(exact)
19473 										return IdentifyResult(null, 5);
19474 									else
19475 										break;
19476 								}
19477 							} else if(pt.x >= x1 && pt.x <= x2) {
19478 								if(idx)
19479 									idx--; // point it at the original index
19480 								break; // we fit
19481 							}
19482 						}
19483 
19484 						// writeln(iterations)
19485 					}
19486 
19487 
19488 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19489 				}
19490 			}
19491 			tci += cast(int) part.content.length; // FIXME: utf-8?
19492 		}
19493 		return IdentifyResult(null, 3);
19494 	}
19495 
19496 	Rectangle boundingBox; // only set after [rerender]
19497 
19498 	// text will be positioned around the exclusion zone
19499 	static struct ExclusionZone {
19500 
19501 	}
19502 
19503 	ExclusionZone[] exclusionZones;
19504 }
19505 
19506 
19507 // Don't use this yet. When I'm happy with it, I will move it to the
19508 // regular module namespace.
19509 mixin template ExperimentalTextComponent() {
19510 
19511 static:
19512 
19513 	alias Rectangle = arsd.color.Rectangle;
19514 
19515 	struct ForegroundColor {
19516 		Color color;
19517 		alias color this;
19518 
19519 		this(Color c) {
19520 			color = c;
19521 		}
19522 
19523 		this(int r, int g, int b, int a = 255) {
19524 			color = Color(r, g, b, a);
19525 		}
19526 
19527 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19528 			return ForegroundColor(mixin("Color." ~ s));
19529 		}
19530 	}
19531 
19532 	struct BackgroundColor {
19533 		Color color;
19534 		alias color this;
19535 
19536 		this(Color c) {
19537 			color = c;
19538 		}
19539 
19540 		this(int r, int g, int b, int a = 255) {
19541 			color = Color(r, g, b, a);
19542 		}
19543 
19544 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19545 			return BackgroundColor(mixin("Color." ~ s));
19546 		}
19547 	}
19548 
19549 	static class InlineElement {
19550 		string text;
19551 
19552 		BlockElement containingBlock;
19553 
19554 		Color color = Color.black;
19555 		Color backgroundColor = Color.transparent;
19556 		ushort styles;
19557 
19558 		string font;
19559 		int fontSize;
19560 
19561 		int lineHeight;
19562 
19563 		void* identifier;
19564 
19565 		Rectangle boundingBox;
19566 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19567 
19568 		bool isMergeCompatible(InlineElement other) {
19569 			return
19570 				containingBlock is other.containingBlock &&
19571 				color == other.color &&
19572 				backgroundColor == other.backgroundColor &&
19573 				styles == other.styles &&
19574 				font == other.font &&
19575 				fontSize == other.fontSize &&
19576 				lineHeight == other.lineHeight &&
19577 				true;
19578 		}
19579 
19580 		int xOfIndex(size_t index) {
19581 			if(index < letterXs.length)
19582 				return letterXs[index];
19583 			else
19584 				return boundingBox.right;
19585 		}
19586 
19587 		InlineElement clone() {
19588 			auto ie = new InlineElement();
19589 			ie.tupleof = this.tupleof;
19590 			return ie;
19591 		}
19592 
19593 		InlineElement getPreviousInlineElement() {
19594 			InlineElement prev = null;
19595 			foreach(ie; this.containingBlock.parts) {
19596 				if(ie is this)
19597 					break;
19598 				prev = ie;
19599 			}
19600 			if(prev is null) {
19601 				BlockElement pb;
19602 				BlockElement cb = this.containingBlock;
19603 				moar:
19604 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19605 					if(ie is cb)
19606 						break;
19607 					pb = ie;
19608 				}
19609 				if(pb is null)
19610 					return null;
19611 				if(pb.parts.length == 0) {
19612 					cb = pb;
19613 					goto moar;
19614 				}
19615 
19616 				prev = pb.parts[$-1];
19617 
19618 			}
19619 			return prev;
19620 		}
19621 
19622 		InlineElement getNextInlineElement() {
19623 			InlineElement next = null;
19624 			foreach(idx, ie; this.containingBlock.parts) {
19625 				if(ie is this) {
19626 					if(idx + 1 < this.containingBlock.parts.length)
19627 						next = this.containingBlock.parts[idx + 1];
19628 					break;
19629 				}
19630 			}
19631 			if(next is null) {
19632 				BlockElement n;
19633 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19634 					if(ie is this.containingBlock) {
19635 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19636 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19637 						break;
19638 					}
19639 				}
19640 				if(n is null)
19641 					return null;
19642 
19643 				if(n.parts.length)
19644 					next = n.parts[0];
19645 				else {} // FIXME
19646 
19647 			}
19648 			return next;
19649 		}
19650 
19651 	}
19652 
19653 	// Block elements are used entirely for positioning inline elements,
19654 	// which are the things that are actually drawn.
19655 	class BlockElement {
19656 		InlineElement[] parts;
19657 		uint alignment;
19658 
19659 		int whiteSpace; // pre, pre-wrap, wrap
19660 
19661 		TextLayout containingLayout;
19662 
19663 		// inputs
19664 		Point where;
19665 		Size minimumSize;
19666 		Size maximumSize;
19667 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19668 		void* identifier;
19669 
19670 		Rectangle margin;
19671 		Rectangle padding;
19672 
19673 		// outputs
19674 		Rectangle[] boundingBoxes;
19675 	}
19676 
19677 	struct TextIdentifyResult {
19678 		InlineElement element;
19679 		int offset;
19680 
19681 		private TextIdentifyResult fixupNewline() {
19682 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19683 				offset--;
19684 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19685 				offset--;
19686 			}
19687 			return this;
19688 		}
19689 	}
19690 
19691 	class TextLayout {
19692 		BlockElement[] blocks;
19693 		Rectangle boundingBox_;
19694 		Rectangle boundingBox() { return boundingBox_; }
19695 		void boundingBox(Rectangle r) {
19696 			if(r != boundingBox_) {
19697 				boundingBox_ = r;
19698 				layoutInvalidated = true;
19699 			}
19700 		}
19701 
19702 		Rectangle contentBoundingBox() {
19703 			Rectangle r;
19704 			foreach(block; blocks)
19705 			foreach(ie; block.parts) {
19706 				if(ie.boundingBox.right > r.right)
19707 					r.right = ie.boundingBox.right;
19708 				if(ie.boundingBox.bottom > r.bottom)
19709 					r.bottom = ie.boundingBox.bottom;
19710 			}
19711 			return r;
19712 		}
19713 
19714 		BlockElement[] getBlocks() {
19715 			return blocks;
19716 		}
19717 
19718 		InlineElement[] getTexts() {
19719 			InlineElement[] elements;
19720 			foreach(block; blocks)
19721 				elements ~= block.parts;
19722 			return elements;
19723 		}
19724 
19725 		string getPlainText() {
19726 			string text;
19727 			foreach(block; blocks)
19728 				foreach(part; block.parts)
19729 					text ~= part.text;
19730 			return text;
19731 		}
19732 
19733 		string getHtml() {
19734 			return null; // FIXME
19735 		}
19736 
19737 		this(Rectangle boundingBox) {
19738 			this.boundingBox = boundingBox;
19739 		}
19740 
19741 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19742 			auto be = new BlockElement();
19743 			be.containingLayout = this;
19744 			if(after is null)
19745 				blocks ~= be;
19746 			else {
19747 				foreach(idx, b; blocks) {
19748 					if(b is after.containingBlock) {
19749 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19750 						break;
19751 					}
19752 				}
19753 			}
19754 			return be;
19755 		}
19756 
19757 		void clear() {
19758 			blocks = null;
19759 			selectionStart = selectionEnd = caret = Caret.init;
19760 		}
19761 
19762 		void addText(Args...)(Args args) {
19763 			if(blocks.length == 0)
19764 				addBlock();
19765 
19766 			InlineElement ie = new InlineElement();
19767 			foreach(idx, arg; args) {
19768 				static if(is(typeof(arg) == ForegroundColor))
19769 					ie.color = arg;
19770 				else static if(is(typeof(arg) == TextFormat)) {
19771 					if(arg & 0x8000) // ~TextFormat.something turns it off
19772 						ie.styles &= arg;
19773 					else
19774 						ie.styles |= arg;
19775 				} else static if(is(typeof(arg) == string)) {
19776 					static if(idx == 0 && args.length > 1)
19777 						static assert(0, "Put styles before the string.");
19778 					size_t lastLineIndex;
19779 					foreach(cidx, char a; arg) {
19780 						if(a == '\n') {
19781 							ie.text = arg[lastLineIndex .. cidx + 1];
19782 							lastLineIndex = cidx + 1;
19783 							ie.containingBlock = blocks[$-1];
19784 							blocks[$-1].parts ~= ie.clone;
19785 							ie.text = null;
19786 						} else {
19787 
19788 						}
19789 					}
19790 
19791 					ie.text = arg[lastLineIndex .. $];
19792 					ie.containingBlock = blocks[$-1];
19793 					blocks[$-1].parts ~= ie.clone;
19794 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
19795 				}
19796 			}
19797 
19798 			invalidateLayout();
19799 		}
19800 
19801 		void tryMerge(InlineElement into, InlineElement what) {
19802 			if(!into.isMergeCompatible(what)) {
19803 				return; // cannot merge, different configs
19804 			}
19805 
19806 			// cool, can merge, bring text together...
19807 			into.text ~= what.text;
19808 
19809 			// and remove what
19810 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
19811 				if(what.containingBlock.parts[a] is what) {
19812 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
19813 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
19814 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
19815 
19816 				}
19817 			}
19818 
19819 			// FIXME: ensure no other carets have a reference to it
19820 		}
19821 
19822 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
19823 		TextIdentifyResult identify(int x, int y, bool exact = false) {
19824 			TextIdentifyResult inexactMatch;
19825 			foreach(block; blocks) {
19826 				foreach(part; block.parts) {
19827 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
19828 
19829 						// FIXME binary search
19830 						int tidx;
19831 						int lastX;
19832 						foreach_reverse(idxo, lx; part.letterXs) {
19833 							int idx = cast(int) idxo;
19834 							if(lx <= x) {
19835 								if(lastX && lastX - x < x - lx)
19836 									tidx = idx + 1;
19837 								else
19838 									tidx = idx;
19839 								break;
19840 							}
19841 							lastX = lx;
19842 						}
19843 
19844 						return TextIdentifyResult(part, tidx).fixupNewline;
19845 					} else if(!exact) {
19846 						// we're not in the box, but are we on the same line?
19847 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
19848 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
19849 					}
19850 				}
19851 			}
19852 
19853 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
19854 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
19855 
19856 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
19857 		}
19858 
19859 		void moveCaretToPixelCoordinates(int x, int y) {
19860 			auto result = identify(x, y);
19861 			caret.inlineElement = result.element;
19862 			caret.offset = result.offset;
19863 		}
19864 
19865 		void selectToPixelCoordinates(int x, int y) {
19866 			auto result = identify(x, y);
19867 
19868 			if(y < caretLastDrawnY1) {
19869 				// on a previous line, carat is selectionEnd
19870 				selectionEnd = caret;
19871 
19872 				selectionStart = Caret(this, result.element, result.offset);
19873 			} else if(y > caretLastDrawnY2) {
19874 				// on a later line
19875 				selectionStart = caret;
19876 
19877 				selectionEnd = Caret(this, result.element, result.offset);
19878 			} else {
19879 				// on the same line...
19880 				if(x <= caretLastDrawnX) {
19881 					selectionEnd = caret;
19882 					selectionStart = Caret(this, result.element, result.offset);
19883 				} else {
19884 					selectionStart = caret;
19885 					selectionEnd = Caret(this, result.element, result.offset);
19886 				}
19887 
19888 			}
19889 		}
19890 
19891 
19892 		/// Call this if the inputs change. It will reflow everything
19893 		void redoLayout(ScreenPainter painter) {
19894 			//painter.setClipRectangle(boundingBox);
19895 			auto pos = Point(boundingBox.left, boundingBox.top);
19896 
19897 			int lastHeight;
19898 			void nl() {
19899 				pos.x = boundingBox.left;
19900 				pos.y += lastHeight;
19901 			}
19902 			foreach(block; blocks) {
19903 				nl();
19904 				foreach(part; block.parts) {
19905 					part.letterXs = null;
19906 
19907 					auto size = painter.textSize(part.text);
19908 					version(Windows)
19909 						if(part.text.length && part.text[$-1] == '\n')
19910 							size.height /= 2; // windows counts the new line at the end, but we don't want that
19911 
19912 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
19913 
19914 					foreach(idx, char c; part.text) {
19915 							// FIXME: unicode
19916 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
19917 					}
19918 
19919 					pos.x += size.width;
19920 					if(pos.x >= boundingBox.right) {
19921 						pos.y += size.height;
19922 						pos.x = boundingBox.left;
19923 						lastHeight = 0;
19924 					} else {
19925 						lastHeight = size.height;
19926 					}
19927 
19928 					if(part.text.length && part.text[$-1] == '\n')
19929 						nl();
19930 				}
19931 			}
19932 
19933 			layoutInvalidated = false;
19934 		}
19935 
19936 		bool layoutInvalidated = true;
19937 		void invalidateLayout() {
19938 			layoutInvalidated = true;
19939 		}
19940 
19941 // FIXME: caret can remain sometimes when inserting
19942 // FIXME: inserting at the beginning once you already have something can eff it up.
19943 		void drawInto(ScreenPainter painter, bool focused = false) {
19944 			if(layoutInvalidated)
19945 				redoLayout(painter);
19946 			foreach(block; blocks) {
19947 				foreach(part; block.parts) {
19948 					painter.outlineColor = part.color;
19949 					painter.fillColor = part.backgroundColor;
19950 
19951 					auto pos = part.boundingBox.upperLeft;
19952 					auto size = part.boundingBox.size;
19953 
19954 					painter.drawText(pos, part.text);
19955 					if(part.styles & TextFormat.underline)
19956 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
19957 					if(part.styles & TextFormat.strikethrough)
19958 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
19959 				}
19960 			}
19961 
19962 			// on every redraw, I will force the caret to be
19963 			// redrawn too, in order to eliminate perceived lag
19964 			// when moving around with the mouse.
19965 			eraseCaret(painter);
19966 
19967 			if(focused) {
19968 				highlightSelection(painter);
19969 				drawCaret(painter);
19970 			}
19971 		}
19972 
19973 		Color selectionXorColor = Color(255, 255, 127);
19974 
19975 		void highlightSelection(ScreenPainter painter) {
19976 			if(selectionStart is selectionEnd)
19977 				return; // no selection
19978 
19979 			if(selectionStart.inlineElement is null) return;
19980 			if(selectionEnd.inlineElement is null) return;
19981 
19982 			assert(selectionStart.inlineElement !is null);
19983 			assert(selectionEnd.inlineElement !is null);
19984 
19985 			painter.rasterOp = RasterOp.xor;
19986 			painter.outlineColor = Color.transparent;
19987 			painter.fillColor = selectionXorColor;
19988 
19989 			auto at = selectionStart.inlineElement;
19990 			auto atOffset = selectionStart.offset;
19991 			bool done;
19992 			while(at) {
19993 				auto box = at.boundingBox;
19994 				if(atOffset < at.letterXs.length)
19995 					box.left = at.letterXs[atOffset];
19996 
19997 				if(at is selectionEnd.inlineElement) {
19998 					if(selectionEnd.offset < at.letterXs.length)
19999 						box.right = at.letterXs[selectionEnd.offset];
20000 					done = true;
20001 				}
20002 
20003 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20004 
20005 				if(done)
20006 					break;
20007 
20008 				at = at.getNextInlineElement();
20009 				atOffset = 0;
20010 			}
20011 		}
20012 
20013 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20014 		bool caretShowingOnScreen = false;
20015 		void drawCaret(ScreenPainter painter) {
20016 			//painter.setClipRectangle(boundingBox);
20017 			int x, y1, y2;
20018 			if(caret.inlineElement is null) {
20019 				x = boundingBox.left;
20020 				y1 = boundingBox.top + 2;
20021 				y2 = boundingBox.top + painter.fontHeight;
20022 			} else {
20023 				x = caret.inlineElement.xOfIndex(caret.offset);
20024 				y1 = caret.inlineElement.boundingBox.top + 2;
20025 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20026 			}
20027 
20028 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20029 				eraseCaret(painter);
20030 
20031 			painter.pen = Pen(Color.white, 1);
20032 			painter.rasterOp = RasterOp.xor;
20033 			painter.drawLine(
20034 				Point(x, y1),
20035 				Point(x, y2)
20036 			);
20037 			painter.rasterOp = RasterOp.normal;
20038 			caretShowingOnScreen = !caretShowingOnScreen;
20039 
20040 			if(caretShowingOnScreen) {
20041 				caretLastDrawnX = x;
20042 				caretLastDrawnY1 = y1;
20043 				caretLastDrawnY2 = y2;
20044 			}
20045 		}
20046 
20047 		Rectangle caretBoundingBox() {
20048 			int x, y1, y2;
20049 			if(caret.inlineElement is null) {
20050 				x = boundingBox.left;
20051 				y1 = boundingBox.top + 2;
20052 				y2 = boundingBox.top + 16;
20053 			} else {
20054 				x = caret.inlineElement.xOfIndex(caret.offset);
20055 				y1 = caret.inlineElement.boundingBox.top + 2;
20056 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20057 			}
20058 
20059 			return Rectangle(x, y1, x + 1, y2);
20060 		}
20061 
20062 		void eraseCaret(ScreenPainter painter) {
20063 			//painter.setClipRectangle(boundingBox);
20064 			if(!caretShowingOnScreen) return;
20065 			painter.pen = Pen(Color.white, 1);
20066 			painter.rasterOp = RasterOp.xor;
20067 			painter.drawLine(
20068 				Point(caretLastDrawnX, caretLastDrawnY1),
20069 				Point(caretLastDrawnX, caretLastDrawnY2)
20070 			);
20071 
20072 			caretShowingOnScreen = false;
20073 			painter.rasterOp = RasterOp.normal;
20074 		}
20075 
20076 		/// Caret movement api
20077 		/// These should give the user a logical result based on what they see on screen...
20078 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20079 		void moveUp() {
20080 			if(caret.inlineElement is null) return;
20081 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20082 			auto y = caret.inlineElement.boundingBox.top + 2;
20083 
20084 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20085 			if(y < 0)
20086 				return;
20087 
20088 			auto i = identify(x, y);
20089 
20090 			if(i.element) {
20091 				caret.inlineElement = i.element;
20092 				caret.offset = i.offset;
20093 			}
20094 		}
20095 		void moveDown() {
20096 			if(caret.inlineElement is null) return;
20097 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20098 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20099 
20100 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20101 
20102 			auto i = identify(x, y);
20103 			if(i.element) {
20104 				caret.inlineElement = i.element;
20105 				caret.offset = i.offset;
20106 			}
20107 		}
20108 		void moveLeft() {
20109 			if(caret.inlineElement is null) return;
20110 			if(caret.offset)
20111 				caret.offset--;
20112 			else {
20113 				auto p = caret.inlineElement.getPreviousInlineElement();
20114 				if(p) {
20115 					caret.inlineElement = p;
20116 					if(p.text.length && p.text[$-1] == '\n')
20117 						caret.offset = cast(int) p.text.length - 1;
20118 					else
20119 						caret.offset = cast(int) p.text.length;
20120 				}
20121 			}
20122 		}
20123 		void moveRight() {
20124 			if(caret.inlineElement is null) return;
20125 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20126 				caret.offset++;
20127 			} else {
20128 				auto p = caret.inlineElement.getNextInlineElement();
20129 				if(p) {
20130 					caret.inlineElement = p;
20131 					caret.offset = 0;
20132 				}
20133 			}
20134 		}
20135 		void moveHome() {
20136 			if(caret.inlineElement is null) return;
20137 			auto x = 0;
20138 			auto y = caret.inlineElement.boundingBox.top + 2;
20139 
20140 			auto i = identify(x, y);
20141 
20142 			if(i.element) {
20143 				caret.inlineElement = i.element;
20144 				caret.offset = i.offset;
20145 			}
20146 		}
20147 		void moveEnd() {
20148 			if(caret.inlineElement is null) return;
20149 			auto x = int.max;
20150 			auto y = caret.inlineElement.boundingBox.top + 2;
20151 
20152 			auto i = identify(x, y);
20153 
20154 			if(i.element) {
20155 				caret.inlineElement = i.element;
20156 				caret.offset = i.offset;
20157 			}
20158 
20159 		}
20160 		void movePageUp(ref Caret caret) {}
20161 		void movePageDown(ref Caret caret) {}
20162 
20163 		void moveDocumentStart(ref Caret caret) {
20164 			if(blocks.length && blocks[0].parts.length)
20165 				caret = Caret(this, blocks[0].parts[0], 0);
20166 			else
20167 				caret = Caret.init;
20168 		}
20169 
20170 		void moveDocumentEnd(ref Caret caret) {
20171 			if(blocks.length) {
20172 				auto parts = blocks[$-1].parts;
20173 				if(parts.length) {
20174 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20175 				} else {
20176 					caret = Caret.init;
20177 				}
20178 			} else
20179 				caret = Caret.init;
20180 		}
20181 
20182 		void deleteSelection() {
20183 			if(selectionStart is selectionEnd)
20184 				return;
20185 
20186 			if(selectionStart.inlineElement is null) return;
20187 			if(selectionEnd.inlineElement is null) return;
20188 
20189 			assert(selectionStart.inlineElement !is null);
20190 			assert(selectionEnd.inlineElement !is null);
20191 
20192 			auto at = selectionStart.inlineElement;
20193 
20194 			if(selectionEnd.inlineElement is at) {
20195 				// same element, need to chop out
20196 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20197 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20198 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20199 			} else {
20200 				// different elements, we can do it with slicing
20201 				at.text = at.text[0 .. selectionStart.offset];
20202 				if(selectionStart.offset < at.letterXs.length)
20203 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20204 
20205 				at = at.getNextInlineElement();
20206 
20207 				while(at) {
20208 					if(at is selectionEnd.inlineElement) {
20209 						at.text = at.text[selectionEnd.offset .. $];
20210 						if(selectionEnd.offset < at.letterXs.length)
20211 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20212 						selectionEnd.offset = 0;
20213 						break;
20214 					} else {
20215 						auto cfd = at;
20216 						cfd.text = null; // delete the whole thing
20217 
20218 						at = at.getNextInlineElement();
20219 
20220 						if(cfd.text.length == 0) {
20221 							// and remove cfd
20222 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20223 								if(cfd.containingBlock.parts[a] is cfd) {
20224 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20225 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20226 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20227 
20228 								}
20229 							}
20230 						}
20231 					}
20232 				}
20233 			}
20234 
20235 			caret = selectionEnd;
20236 			selectNone();
20237 
20238 			invalidateLayout();
20239 
20240 		}
20241 
20242 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20243 		void insert(in char[] text) {
20244 			foreach(dchar ch; text)
20245 				insert(ch);
20246 		}
20247 		/// ditto
20248 		void insert(dchar ch) {
20249 
20250 			bool selectionDeleted = false;
20251 			if(selectionStart !is selectionEnd) {
20252 				deleteSelection();
20253 				selectionDeleted = true;
20254 			}
20255 
20256 			if(ch == 127) {
20257 				delete_();
20258 				return;
20259 			}
20260 			if(ch == 8) {
20261 				if(!selectionDeleted)
20262 					backspace();
20263 				return;
20264 			}
20265 
20266 			invalidateLayout();
20267 
20268 			if(ch == 13) ch = 10;
20269 			auto e = caret.inlineElement;
20270 			if(e is null) {
20271 				addText("" ~ cast(char) ch) ; // FIXME
20272 				return;
20273 			}
20274 
20275 			if(caret.offset == e.text.length) {
20276 				e.text ~= cast(char) ch; // FIXME
20277 				caret.offset++;
20278 				if(ch == 10) {
20279 					auto c = caret.inlineElement.clone;
20280 					c.text = null;
20281 					c.letterXs = null;
20282 					insertPartAfter(c,e);
20283 					caret = Caret(this, c, 0);
20284 				}
20285 			} else {
20286 				// FIXME cast char sucks
20287 				if(ch == 10) {
20288 					auto c = caret.inlineElement.clone;
20289 					c.text = e.text[caret.offset .. $];
20290 					if(caret.offset < c.letterXs.length)
20291 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20292 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20293 					if(caret.offset <= e.letterXs.length) {
20294 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20295 					}
20296 					insertPartAfter(c,e);
20297 					caret = Caret(this, c, 0);
20298 				} else {
20299 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20300 					caret.offset++;
20301 				}
20302 			}
20303 		}
20304 
20305 		void insertPartAfter(InlineElement what, InlineElement where) {
20306 			foreach(idx, p; where.containingBlock.parts) {
20307 				if(p is where) {
20308 					if(idx + 1 == where.containingBlock.parts.length)
20309 						where.containingBlock.parts ~= what;
20310 					else
20311 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20312 					return;
20313 				}
20314 			}
20315 		}
20316 
20317 		void cleanupStructures() {
20318 			for(size_t i = 0; i < blocks.length; i++) {
20319 				auto block = blocks[i];
20320 				for(size_t a = 0; a < block.parts.length; a++) {
20321 					auto part = block.parts[a];
20322 					if(part.text.length == 0) {
20323 						for(size_t b = a; b < block.parts.length - 1; b++)
20324 							block.parts[b] = block.parts[b+1];
20325 						block.parts = block.parts[0 .. $-1];
20326 					}
20327 				}
20328 				if(block.parts.length == 0) {
20329 					for(size_t a = i; a < blocks.length - 1; a++)
20330 						blocks[a] = blocks[a+1];
20331 					blocks = blocks[0 .. $-1];
20332 				}
20333 			}
20334 		}
20335 
20336 		void backspace() {
20337 			try_again:
20338 			auto e = caret.inlineElement;
20339 			if(e is null)
20340 				return;
20341 			if(caret.offset == 0) {
20342 				auto prev = e.getPreviousInlineElement();
20343 				if(prev is null)
20344 					return;
20345 				auto newOffset = cast(int) prev.text.length;
20346 				tryMerge(prev, e);
20347 				caret.inlineElement = prev;
20348 				caret.offset = prev is null ? 0 : newOffset;
20349 
20350 				goto try_again;
20351 			} else if(caret.offset == e.text.length) {
20352 				e.text = e.text[0 .. $-1];
20353 				caret.offset--;
20354 			} else {
20355 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20356 				caret.offset--;
20357 			}
20358 			//cleanupStructures();
20359 
20360 			invalidateLayout();
20361 		}
20362 		void delete_() {
20363 			if(selectionStart !is selectionEnd)
20364 				deleteSelection();
20365 			else {
20366 				auto before = caret;
20367 				moveRight();
20368 				if(caret != before) {
20369 					backspace();
20370 				}
20371 			}
20372 
20373 			invalidateLayout();
20374 		}
20375 		void overstrike() {}
20376 
20377 		/// Selection API. See also: caret movement.
20378 		void selectAll() {
20379 			moveDocumentStart(selectionStart);
20380 			moveDocumentEnd(selectionEnd);
20381 		}
20382 		bool selectNone() {
20383 			if(selectionStart != selectionEnd) {
20384 				selectionStart = selectionEnd = Caret.init;
20385 				return true;
20386 			}
20387 			return false;
20388 		}
20389 
20390 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20391 		/// They will modify the current selection if there is one and will splice one in if needed.
20392 		void changeAttributes() {}
20393 
20394 
20395 		/// Text search api. They manipulate the selection and/or caret.
20396 		void findText(string text) {}
20397 		void findIndex(size_t textIndex) {}
20398 
20399 		// sample event handlers
20400 
20401 		void handleEvent(KeyEvent event) {
20402 			//if(event.type == KeyEvent.Type.KeyPressed) {
20403 
20404 			//}
20405 		}
20406 
20407 		void handleEvent(dchar ch) {
20408 
20409 		}
20410 
20411 		void handleEvent(MouseEvent event) {
20412 
20413 		}
20414 
20415 		bool contentEditable; // can it be edited?
20416 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20417 		bool contentSelectable; // selectable?
20418 
20419 		Caret caret;
20420 		Caret selectionStart;
20421 		Caret selectionEnd;
20422 
20423 		bool insertMode;
20424 	}
20425 
20426 	struct Caret {
20427 		TextLayout layout;
20428 		InlineElement inlineElement;
20429 		int offset;
20430 	}
20431 
20432 	enum TextFormat : ushort {
20433 		// decorations
20434 		underline = 1,
20435 		strikethrough = 2,
20436 
20437 		// font selectors
20438 
20439 		bold = 0x4000 | 1, // weight 700
20440 		light = 0x4000 | 2, // weight 300
20441 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20442 		// bold | light is really invalid but should give weight 500
20443 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20444 
20445 		italic = 0x4000 | 8,
20446 		smallcaps = 0x4000 | 16,
20447 	}
20448 
20449 	void* findFont(string family, int weight, TextFormat formats) {
20450 		return null;
20451 	}
20452 
20453 }
20454 
20455 /++
20456 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20457 
20458 	History:
20459 		Added February 19, 2021
20460 +/
20461 /// Group: drag_and_drop
20462 interface DropHandler {
20463 	/++
20464 		Called when the drag enters the handler's area.
20465 	+/
20466 	DragAndDropAction dragEnter(DropPackage*);
20467 	/++
20468 		Called when the drag leaves the handler's area or is
20469 		cancelled. You should free your resources when this is called.
20470 	+/
20471 	void dragLeave();
20472 	/++
20473 		Called continually as the drag moves over the handler's area.
20474 
20475 		Returns: feedback to the dragger
20476 	+/
20477 	DropParameters dragOver(Point pt);
20478 	/++
20479 		The user dropped the data and you should process it now. You can
20480 		access the data through the given [DropPackage].
20481 	+/
20482 	void drop(scope DropPackage*);
20483 	/++
20484 		Called when the drop is complete. You should free whatever temporary
20485 		resources you were using. It is often reasonable to simply forward
20486 		this call to [dragLeave].
20487 	+/
20488 	void finish();
20489 
20490 	/++
20491 		Parameters returned by [DropHandler.drop].
20492 	+/
20493 	static struct DropParameters {
20494 		/++
20495 			Acceptable action over this area.
20496 		+/
20497 		DragAndDropAction action;
20498 		/++
20499 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20500 
20501 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20502 		+/
20503 		Rectangle consistentWithin;
20504 	}
20505 }
20506 
20507 /++
20508 	History:
20509 		Added February 19, 2021
20510 +/
20511 /// Group: drag_and_drop
20512 enum DragAndDropAction {
20513 	none = 0,
20514 	copy,
20515 	move,
20516 	link,
20517 	ask,
20518 	custom
20519 }
20520 
20521 /++
20522 	An opaque structure representing dropped data. It contains
20523 	private, platform-specific data that your `drop` function
20524 	should simply forward.
20525 
20526 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20527 
20528 	History:
20529 		Added February 19, 2021
20530 +/
20531 /// Group: drag_and_drop
20532 struct DropPackage {
20533 	/++
20534 		Lists the available formats as magic numbers. You should compare these
20535 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20536 		understand the passed data.
20537 	+/
20538 	DraggableData.FormatId[] availableFormats() {
20539 		version(X11) {
20540 			return xFormats;
20541 		} else version(Windows) {
20542 			if(pDataObj is null)
20543 				return null;
20544 
20545 			typeof(return) ret;
20546 
20547 			IEnumFORMATETC ef;
20548 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20549 				FORMATETC fmt;
20550 				ULONG fetched;
20551 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20552 					if(fetched == 0)
20553 						break;
20554 
20555 					if(fmt.lindex != -1)
20556 						continue;
20557 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20558 						continue;
20559 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20560 						continue;
20561 
20562 					ret ~= fmt.cfFormat;
20563 				}
20564 			}
20565 
20566 			return ret;
20567 		}
20568 	}
20569 
20570 	/++
20571 		Gets data from the drop and optionally accepts it.
20572 
20573 		Returns:
20574 			void because the data is fed asynchronously through the `dg` parameter.
20575 
20576 		Params:
20577 			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.
20578 
20579 			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.
20580 
20581 			Calling `getData` again after accepting a drop is not permitted.
20582 
20583 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20584 
20585 			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.
20586 
20587 		Throws:
20588 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20589 
20590 		History:
20591 			Included in first release of [DropPackage].
20592 	+/
20593 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20594 		version(X11) {
20595 
20596 			auto display = XDisplayConnection.get();
20597 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20598 			auto best = format;
20599 
20600 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20601 
20602 				XDisplay* display;
20603 				Atom selectionAtom;
20604 				DraggableData.FormatId best;
20605 				DraggableData.FormatId format;
20606 				void delegate(scope ubyte[] data) dg;
20607 				DragAndDropAction acceptedAction;
20608 				Window sourceWindow;
20609 				SimpleWindow win;
20610 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20611 					this.display = display;
20612 					this.win = win;
20613 					this.sourceWindow = sourceWindow;
20614 					this.format = format;
20615 					this.selectionAtom = selectionAtom;
20616 					this.best = best;
20617 					this.dg = dg;
20618 					this.acceptedAction = acceptedAction;
20619 				}
20620 
20621 
20622 				mixin X11GetSelectionHandler_Basics;
20623 
20624 				void handleData(Atom target, in ubyte[] data) {
20625 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20626 
20627 					dg(cast(ubyte[]) data);
20628 
20629 					if(acceptedAction != DragAndDropAction.none) {
20630 						auto display = XDisplayConnection.get;
20631 
20632 						XClientMessageEvent xclient;
20633 
20634 						xclient.type = EventType.ClientMessage;
20635 						xclient.window = sourceWindow;
20636 						xclient.message_type = GetAtom!"XdndFinished"(display);
20637 						xclient.format = 32;
20638 						xclient.data.l[0] = win.impl.window;
20639 						xclient.data.l[1] = 1; // drop successful
20640 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20641 
20642 						XSendEvent(
20643 							display,
20644 							sourceWindow,
20645 							false,
20646 							EventMask.NoEventMask,
20647 							cast(XEvent*) &xclient
20648 						);
20649 
20650 						XFlush(display);
20651 					}
20652 				}
20653 
20654 				Atom findBestFormat(Atom[] answer) {
20655 					Atom best = None;
20656 					foreach(option; answer) {
20657 						if(option == format) {
20658 							best = option;
20659 							break;
20660 						}
20661 						/*
20662 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20663 							best = option;
20664 							break;
20665 						} else if(option == XA_STRING) {
20666 							best = option;
20667 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20668 							best = option;
20669 						}
20670 						*/
20671 					}
20672 					return best;
20673 				}
20674 			}
20675 
20676 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20677 
20678 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20679 
20680 		} else version(Windows) {
20681 
20682 			// clean up like DragLeave
20683 			// pass effect back up
20684 
20685 			FORMATETC t;
20686 			assert(format >= 0 && format <= ushort.max);
20687 			t.cfFormat = cast(ushort) format;
20688 			t.lindex = -1;
20689 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20690 			t.tymed = TYMED.TYMED_HGLOBAL;
20691 
20692 			STGMEDIUM m;
20693 
20694 			if(pDataObj.GetData(&t, &m) != S_OK) {
20695 				// fail
20696 			} else {
20697 				// succeed, take the data and clean up
20698 
20699 				// FIXME: ensure it is legit HGLOBAL
20700 				auto handle = m.hGlobal;
20701 
20702 				if(handle) {
20703 					auto sz = GlobalSize(handle);
20704 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20705 						scope(exit) GlobalUnlock(handle);
20706 						scope(exit) GlobalFree(handle);
20707 
20708 						auto data = ptr[0 .. sz];
20709 
20710 						dg(data);
20711 					}
20712 				}
20713 			}
20714 		}
20715 	}
20716 
20717 	private:
20718 
20719 	version(X11) {
20720 		SimpleWindow win;
20721 		Window sourceWindow;
20722 		Time dataTimestamp;
20723 
20724 		Atom[] xFormats;
20725 	}
20726 	version(Windows) {
20727 		IDataObject pDataObj;
20728 	}
20729 }
20730 
20731 /++
20732 	A generic helper base class for making a drop handler with a preference list of custom types.
20733 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20734 	droppers too.
20735 
20736 	It assumes the whole window it used, but you can subclass to change that.
20737 
20738 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20739 
20740 	History:
20741 		Added February 19, 2021
20742 +/
20743 /// Group: drag_and_drop
20744 class GenericDropHandlerBase : DropHandler {
20745 	// no fancy state here so no need to do anything here
20746 	void finish() { }
20747 	void dragLeave() { }
20748 
20749 	private DragAndDropAction acceptedAction;
20750 	private DraggableData.FormatId acceptedFormat;
20751 	private void delegate(scope ubyte[]) acceptedHandler;
20752 
20753 	struct FormatHandler {
20754 		DraggableData.FormatId format;
20755 		void delegate(scope ubyte[]) handler;
20756 	}
20757 
20758 	protected abstract FormatHandler[] formatHandlers();
20759 
20760 	DragAndDropAction dragEnter(DropPackage* pkg) {
20761 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20762 		foreach(fmt; formatHandlers())
20763 		foreach(f; pkg.availableFormats())
20764 			if(f == fmt.format) {
20765 				acceptedFormat = f;
20766 				acceptedHandler = fmt.handler;
20767 				return acceptedAction = DragAndDropAction.copy;
20768 			}
20769 		return acceptedAction = DragAndDropAction.none;
20770 	}
20771 	DropParameters dragOver(Point pt) {
20772 		return DropParameters(acceptedAction);
20773 	}
20774 
20775 	void drop(scope DropPackage* dropPackage) {
20776 		if(!acceptedFormat || acceptedHandler is null) {
20777 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20778 			return; // prolly shouldn't happen anyway...
20779 		}
20780 
20781 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20782 	}
20783 }
20784 
20785 /++
20786 	A simple handler for making your window accept drops of plain text.
20787 
20788 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20789 
20790 	History:
20791 		Added February 22, 2021
20792 +/
20793 /// Group: drag_and_drop
20794 class TextDropHandler : GenericDropHandlerBase {
20795 	private void delegate(in char[] text) dg;
20796 
20797 	/++
20798 
20799 	+/
20800 	this(void delegate(in char[] text) dg) {
20801 		this.dg = dg;
20802 	}
20803 
20804 	protected override FormatHandler[] formatHandlers() {
20805 		version(X11)
20806 			return [
20807 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
20808 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
20809 			];
20810 		else version(Windows)
20811 			return [
20812 				FormatHandler(CF_UNICODETEXT, &translator),
20813 			];
20814 	}
20815 
20816 	private void translator(scope ubyte[] data) {
20817 		version(X11)
20818 			dg(cast(char[]) data);
20819 		else version(Windows)
20820 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
20821 	}
20822 }
20823 
20824 /++
20825 	A simple handler for making your window accept drops of files, issued to you as file names.
20826 
20827 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20828 
20829 	History:
20830 		Added February 22, 2021
20831 +/
20832 /// Group: drag_and_drop
20833 
20834 class FilesDropHandler : GenericDropHandlerBase {
20835 	private void delegate(in char[][]) dg;
20836 
20837 	/++
20838 
20839 	+/
20840 	this(void delegate(in char[][] fileNames) dg) {
20841 		this.dg = dg;
20842 	}
20843 
20844 	protected override FormatHandler[] formatHandlers() {
20845 		version(X11)
20846 			return [
20847 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
20848 			];
20849 		else version(Windows)
20850 			return [
20851 				FormatHandler(CF_HDROP, &translator),
20852 			];
20853 	}
20854 
20855 	private void translator(scope ubyte[] data) {
20856 		version(X11) {
20857 			char[] listString = cast(char[]) data;
20858 			char[][16] buffer;
20859 			int count;
20860 			char[][] result = buffer[];
20861 
20862 			void commit(char[] s) {
20863 				if(count == result.length)
20864 					result.length += 16;
20865 				if(s.length > 7 && s[0 ..7] == "file://")
20866 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
20867 				result[count++] = s;
20868 			}
20869 
20870 			size_t last;
20871 			foreach(idx, char c; listString) {
20872 				if(c == '\n') {
20873 					commit(listString[last .. idx - 1]); // a \r
20874 					last = idx + 1; // a \n
20875 				}
20876 			}
20877 
20878 			if(last < listString.length) {
20879 				commit(listString[last .. $]);
20880 			}
20881 
20882 			// FIXME: they are uris now, should I translate it to local file names?
20883 			// of course the host name is supposed to be there cuz of X rokking...
20884 
20885 			dg(result[0 .. count]);
20886 		} else version(Windows) {
20887 
20888 			static struct DROPFILES {
20889 				DWORD pFiles;
20890 				POINT pt;
20891 				BOOL  fNC;
20892 				BOOL  fWide;
20893 			}
20894 
20895 
20896 			const(char)[][16] buffer;
20897 			int count;
20898 			const(char)[][] result = buffer[];
20899 			size_t last;
20900 
20901 			void commitA(in char[] stuff) {
20902 				if(count == result.length)
20903 					result.length += 16;
20904 				result[count++] = stuff;
20905 			}
20906 
20907 			void commitW(in wchar[] stuff) {
20908 				commitA(makeUtf8StringFromWindowsString(stuff));
20909 			}
20910 
20911 			void magic(T)(T chars) {
20912 				size_t idx;
20913 				while(chars[idx]) {
20914 					last = idx;
20915 					while(chars[idx]) {
20916 						idx++;
20917 					}
20918 					static if(is(T == char*))
20919 						commitA(chars[last .. idx]);
20920 					else
20921 						commitW(chars[last .. idx]);
20922 					idx++;
20923 				}
20924 			}
20925 
20926 			auto df = cast(DROPFILES*) data.ptr;
20927 			if(df.fWide) {
20928 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
20929 				magic(chars);
20930 			} else {
20931 				char* chars = cast(char*) (data.ptr + df.pFiles);
20932 				magic(chars);
20933 			}
20934 			dg(result[0 .. count]);
20935 		}
20936 	}
20937 }
20938 
20939 /++
20940 	Interface to describe data being dragged. See also [draggable] helper function.
20941 
20942 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20943 
20944 	History:
20945 		Added February 19, 2021
20946 +/
20947 interface DraggableData {
20948 	version(X11)
20949 		alias FormatId = Atom;
20950 	else
20951 		alias FormatId = uint;
20952 	/++
20953 		Gets the platform-specific FormatId associated with the given named format.
20954 
20955 		This may be a MIME type, but may also be other various strings defined by the
20956 		programs you want to interoperate with.
20957 
20958 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
20959 		and convert it to some particular type for you.
20960 	+/
20961 	static FormatId getFormatId(string name)() {
20962 		version(X11)
20963 			return GetAtom!name(XDisplayConnection.get);
20964 		else version(Windows) {
20965 			static UINT cache;
20966 			if(!cache)
20967 				cache = RegisterClipboardFormatA(name);
20968 			return cache;
20969 		} else
20970 			throw new NotYetImplementedException();
20971 	}
20972 
20973 	/++
20974 		Looks up a string to represent the name for the given format, if there is one.
20975 
20976 		You should avoid using this function because it is slow. It is provided more for
20977 		debugging than for primary use.
20978 	+/
20979 	static string getFormatName(FormatId format) {
20980 		version(X11) {
20981 			if(format == 0)
20982 				return "None";
20983 			else
20984 				return getAtomName(format, XDisplayConnection.get);
20985 		} else version(Windows) {
20986 			switch(format) {
20987 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
20988 				case CF_DIBV5: return "CF_DIBV5";
20989 				case CF_RIFF: return "CF_RIFF";
20990 				case CF_WAVE: return "CF_WAVE";
20991 				case CF_HDROP: return "CF_HDROP";
20992 				default:
20993 					char[1024] name;
20994 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
20995 					return name[0 .. count].idup;
20996 			}
20997 		}
20998 	}
20999 
21000 	FormatId[] availableFormats();
21001 	// Return the slice of data you filled, empty slice if done.
21002 	// this is to support the incremental thing
21003 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21004 
21005 	size_t dataLength(FormatId format);
21006 }
21007 
21008 /++
21009 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21010 
21011 	History:
21012 		Added February 19, 2021
21013 +/
21014 DraggableData draggable(string s) {
21015 	version(X11)
21016 	return new class X11SetSelectionHandler_Text, DraggableData {
21017 		this() {
21018 			super(s);
21019 		}
21020 
21021 		override FormatId[] availableFormats() {
21022 			return X11SetSelectionHandler_Text.availableFormats();
21023 		}
21024 
21025 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21026 			return X11SetSelectionHandler_Text.getData(format, data);
21027 		}
21028 
21029 		size_t dataLength(FormatId format) {
21030 			return s.length;
21031 		}
21032 	};
21033 	version(Windows)
21034 	return new class DraggableData {
21035 		FormatId[] availableFormats() {
21036 			return [CF_UNICODETEXT];
21037 		}
21038 
21039 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21040 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21041 		}
21042 
21043 		size_t dataLength(FormatId format) {
21044 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21045 		}
21046 	};
21047 }
21048 
21049 /++
21050 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21051 
21052 	History:
21053 		Added February 19, 2021
21054 +/
21055 /// Group: drag_and_drop
21056 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21057 in {
21058 	assert(window !is null);
21059 	assert(handler !is null);
21060 }
21061 do
21062 {
21063 	version(X11) {
21064 		auto sh = cast(X11SetSelectionHandler) handler;
21065 		if(sh is null) {
21066 			// gotta make my own adapter.
21067 			sh = new class X11SetSelectionHandler {
21068 				mixin X11SetSelectionHandler_Basics;
21069 
21070 				Atom[] availableFormats() { return handler.availableFormats(); }
21071 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21072 					return handler.getData(format, data);
21073 				}
21074 
21075 				// since the drop selection is only ever used once it isn't important
21076 				// to reset it.
21077 				void done() {}
21078 			};
21079 		}
21080 		return doDragDropX11(window, sh, action);
21081 	} else version(Windows) {
21082 		return doDragDropWindows(window, handler, action);
21083 	} else throw new NotYetImplementedException();
21084 }
21085 
21086 version(Windows)
21087 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21088 	IDataObject obj = new class IDataObject {
21089 		ULONG refCount;
21090 		ULONG AddRef() {
21091 			return ++refCount;
21092 		}
21093 		ULONG Release() {
21094 			return --refCount;
21095 		}
21096 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21097 			if (IID_IUnknown == *riid) {
21098 				*ppv = cast(void*) cast(IUnknown) this;
21099 			}
21100 			else if (IID_IDataObject == *riid) {
21101 				*ppv = cast(void*) cast(IDataObject) this;
21102 			}
21103 			else {
21104 				*ppv = null;
21105 				return E_NOINTERFACE;
21106 			}
21107 
21108 			AddRef();
21109 			return NOERROR;
21110 		}
21111 
21112 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21113 			//  writeln("Advise");
21114 			return E_NOTIMPL;
21115 		}
21116 		HRESULT DUnadvise(DWORD dwConnection) {
21117 			return E_NOTIMPL;
21118 		}
21119 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21120 			//  writeln("EnumDAdvise");
21121 			return OLE_E_ADVISENOTSUPPORTED;
21122 		}
21123 		// tell what formats it supports
21124 
21125 		FORMATETC[] types;
21126 		this() {
21127 			FORMATETC t;
21128 			foreach(ty; handler.availableFormats()) {
21129 				assert(ty <= ushort.max && ty >= 0);
21130 				t.cfFormat = cast(ushort) ty;
21131 				t.lindex = -1;
21132 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21133 				t.tymed = TYMED.TYMED_HGLOBAL;
21134 			}
21135 			types ~= t;
21136 		}
21137 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21138 			if(dwDirection == DATADIR.DATADIR_GET) {
21139 				*ppenumFormatEtc = new class IEnumFORMATETC {
21140 					ULONG refCount;
21141 					ULONG AddRef() {
21142 						return ++refCount;
21143 					}
21144 					ULONG Release() {
21145 						return --refCount;
21146 					}
21147 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21148 						if (IID_IUnknown == *riid) {
21149 							*ppv = cast(void*) cast(IUnknown) this;
21150 						}
21151 						else if (IID_IEnumFORMATETC == *riid) {
21152 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21153 						}
21154 						else {
21155 							*ppv = null;
21156 							return E_NOINTERFACE;
21157 						}
21158 
21159 						AddRef();
21160 						return NOERROR;
21161 					}
21162 
21163 
21164 					int pos;
21165 					this() {
21166 						pos = 0;
21167 					}
21168 
21169 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21170 						// writeln("clone");
21171 						return E_NOTIMPL; // FIXME
21172 					}
21173 
21174 					// Caller is responsible for freeing memory
21175 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21176 						// fetched may be null if celt is one
21177 						if(celt != 1)
21178 							return E_NOTIMPL; // FIXME
21179 
21180 						if(celt + pos > types.length)
21181 							return S_FALSE;
21182 
21183 						*rgelt = types[pos++];
21184 
21185 						if(pceltFetched !is null)
21186 							*pceltFetched = 1;
21187 
21188 						// writeln("ok celt ", celt);
21189 						return S_OK;
21190 					}
21191 
21192 					HRESULT Reset() {
21193 						pos = 0;
21194 						return S_OK;
21195 					}
21196 
21197 					HRESULT Skip(ULONG celt) {
21198 						if(celt + pos <= types.length) {
21199 							pos += celt;
21200 							return S_OK;
21201 						}
21202 						return S_FALSE;
21203 					}
21204 				};
21205 
21206 				return S_OK;
21207 			} else
21208 				return E_NOTIMPL;
21209 		}
21210 		// given a format, return the format you'd prefer to use cuz it is identical
21211 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21212 			// FIXME: prolly could be better but meh
21213 			// writeln("gcf: ", *pformatectIn);
21214 			*pformatetcOut = *pformatectIn;
21215 			return S_OK;
21216 		}
21217 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21218 			foreach(ty; types) {
21219 				if(ty == *pformatetcIn) {
21220 					auto format = ty.cfFormat;
21221 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21222 					STGMEDIUM medium;
21223 					medium.tymed = TYMED.TYMED_HGLOBAL;
21224 
21225 					auto sz = handler.dataLength(format);
21226 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21227 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21228 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21229 						auto slice = data[0 .. sz];
21230 						scope(exit)
21231 							GlobalUnlock(handle);
21232 
21233 						handler.getData(format, cast(ubyte[]) slice[]);
21234 					}
21235 
21236 
21237 					medium.hGlobal = handle; // FIXME
21238 					*pmedium = medium;
21239 					return S_OK;
21240 				}
21241 			}
21242 			return DV_E_FORMATETC;
21243 		}
21244 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21245 			// writeln("GDH: ", *pformatetcIn);
21246 			return E_NOTIMPL; // FIXME
21247 		}
21248 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21249 			auto search = *pformatetc;
21250 			search.tymed &= TYMED.TYMED_HGLOBAL;
21251 			foreach(ty; types)
21252 				if(ty == search) {
21253 					// writeln("QueryGetData ", search, " ", types[0]);
21254 					return S_OK;
21255 				}
21256 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21257 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21258 			}
21259 			return S_FALSE;
21260 		}
21261 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21262 			//  writeln("SetData: ");
21263 			return E_NOTIMPL;
21264 		}
21265 	};
21266 
21267 
21268 	IDropSource src = new class IDropSource {
21269 		ULONG refCount;
21270 		ULONG AddRef() {
21271 			return ++refCount;
21272 		}
21273 		ULONG Release() {
21274 			return --refCount;
21275 		}
21276 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21277 			if (IID_IUnknown == *riid) {
21278 				*ppv = cast(void*) cast(IUnknown) this;
21279 			}
21280 			else if (IID_IDropSource == *riid) {
21281 				*ppv = cast(void*) cast(IDropSource) this;
21282 			}
21283 			else {
21284 				*ppv = null;
21285 				return E_NOINTERFACE;
21286 			}
21287 
21288 			AddRef();
21289 			return NOERROR;
21290 		}
21291 
21292 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21293 			if(fEscapePressed)
21294 				return DRAGDROP_S_CANCEL;
21295 			if(!(grfKeyState & MK_LBUTTON))
21296 				return DRAGDROP_S_DROP;
21297 			return S_OK;
21298 		}
21299 
21300 		int GiveFeedback(uint dwEffect) {
21301 			return DRAGDROP_S_USEDEFAULTCURSORS;
21302 		}
21303 	};
21304 
21305 	DWORD effect;
21306 
21307 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21308 
21309 	DROPEFFECT de = win32DragAndDropAction(action);
21310 
21311 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21312 	// but still prolly a FIXME
21313 
21314 	auto ret = DoDragDrop(obj, src, de, &effect);
21315 	/+
21316 	if(ret == DRAGDROP_S_DROP)
21317 		writeln("drop ", effect);
21318 	else if(ret == DRAGDROP_S_CANCEL)
21319 		writeln("cancel");
21320 	else if(ret == S_OK)
21321 		writeln("ok");
21322 	else writeln(ret);
21323 	+/
21324 
21325 	return ret;
21326 }
21327 
21328 version(Windows)
21329 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21330 	DROPEFFECT de;
21331 
21332 	with(DragAndDropAction)
21333 	with(DROPEFFECT)
21334 	final switch(action) {
21335 		case none: de = DROPEFFECT_NONE; break;
21336 		case copy: de = DROPEFFECT_COPY; break;
21337 		case move: de = DROPEFFECT_MOVE; break;
21338 		case link: de = DROPEFFECT_LINK; break;
21339 		case ask: throw new Exception("ask not implemented yet");
21340 		case custom: throw new Exception("custom not implemented yet");
21341 	}
21342 
21343 	return de;
21344 }
21345 
21346 
21347 /++
21348 	History:
21349 		Added February 19, 2021
21350 +/
21351 /// Group: drag_and_drop
21352 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21353 	version(X11) {
21354 		auto display = XDisplayConnection.get;
21355 
21356 		Atom atom = 5; // right???
21357 
21358 		XChangeProperty(
21359 			display,
21360 			window.impl.window,
21361 			GetAtom!"XdndAware"(display),
21362 			XA_ATOM,
21363 			32 /* bits */,
21364 			PropModeReplace,
21365 			&atom,
21366 			1);
21367 
21368 		window.dropHandler = handler;
21369 	} else version(Windows) {
21370 
21371 		initDnd();
21372 
21373 		auto dropTarget = new class (handler) IDropTarget {
21374 			DropHandler handler;
21375 			this(DropHandler handler) {
21376 				this.handler = handler;
21377 			}
21378 			ULONG refCount;
21379 			ULONG AddRef() {
21380 				return ++refCount;
21381 			}
21382 			ULONG Release() {
21383 				return --refCount;
21384 			}
21385 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21386 				if (IID_IUnknown == *riid) {
21387 					*ppv = cast(void*) cast(IUnknown) this;
21388 				}
21389 				else if (IID_IDropTarget == *riid) {
21390 					*ppv = cast(void*) cast(IDropTarget) this;
21391 				}
21392 				else {
21393 					*ppv = null;
21394 					return E_NOINTERFACE;
21395 				}
21396 
21397 				AddRef();
21398 				return NOERROR;
21399 			}
21400 
21401 
21402 			// ///////////////////
21403 
21404 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21405 				DropPackage dropPackage = DropPackage(pDataObj);
21406 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21407 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21408 			}
21409 
21410 			HRESULT DragLeave() {
21411 				handler.dragLeave();
21412 				// release the IDataObject if needed
21413 				return S_OK;
21414 			}
21415 
21416 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21417 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21418 
21419 				*pdwEffect = win32DragAndDropAction(res.action);
21420 				// same as DragEnter basically
21421 				return S_OK;
21422 			}
21423 
21424 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21425 				DropPackage pkg = DropPackage(pDataObj);
21426 				handler.drop(&pkg);
21427 
21428 				return S_OK;
21429 			}
21430 		};
21431 		// Windows can hold on to the handler and try to call it
21432 		// during which time the GC can't see it. so important to
21433 		// manually manage this. At some point i'll FIXME and make
21434 		// all my com instances manually managed since they supposed
21435 		// to respect the refcount.
21436 		import core.memory;
21437 		GC.addRoot(cast(void*) dropTarget);
21438 
21439 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21440 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21441 
21442 		window.dropHandler = handler;
21443 	} else throw new NotYetImplementedException();
21444 }
21445 
21446 
21447 
21448 static if(UsingSimpledisplayX11) {
21449 
21450 enum _NET_WM_STATE_ADD = 1;
21451 enum _NET_WM_STATE_REMOVE = 0;
21452 enum _NET_WM_STATE_TOGGLE = 2;
21453 
21454 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21455 void demandAttention(SimpleWindow window, bool needs = true) {
21456 	demandAttention(window.impl.window, needs);
21457 }
21458 
21459 /// ditto
21460 void demandAttention(Window window, bool needs = true) {
21461 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21462 }
21463 
21464 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21465 	auto display = XDisplayConnection.get();
21466 	if(atom == None)
21467 		return; // non-failure error
21468 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21469 
21470 	XClientMessageEvent xclient;
21471 
21472 	xclient.type = EventType.ClientMessage;
21473 	xclient.window = window;
21474 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21475 	xclient.format = 32;
21476 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21477 	xclient.data.l[1] = atom;
21478 	xclient.data.l[2] = atom2;
21479 	xclient.data.l[3] = 1;
21480 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21481 
21482 	XSendEvent(
21483 		display,
21484 		RootWindow(display, DefaultScreen(display)),
21485 		false,
21486 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21487 		cast(XEvent*) &xclient
21488 	);
21489 
21490 	/+
21491 	XChangeProperty(
21492 		display,
21493 		window.impl.window,
21494 		GetAtom!"_NET_WM_STATE"(display),
21495 		XA_ATOM,
21496 		32 /* bits */,
21497 		PropModeAppend,
21498 		&atom,
21499 		1);
21500 	+/
21501 }
21502 
21503 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21504 	Atom actionAtom;
21505 	with(DragAndDropAction)
21506 	final switch(action) {
21507 		case none: actionAtom = None; break;
21508 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21509 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21510 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21511 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21512 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21513 	}
21514 
21515 	return actionAtom;
21516 }
21517 
21518 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21519 	// FIXME: I need to show user feedback somehow.
21520 	auto display = XDisplayConnection.get;
21521 
21522 	auto actionAtom = dndActionAtom(display, action);
21523 	assert(actionAtom, "Don't use action none to accept a drop");
21524 
21525 	setX11Selection!"XdndSelection"(window, handler, null);
21526 
21527 	auto oldKeyHandler = window.handleKeyEvent;
21528 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21529 
21530 	auto oldCharHandler = window.handleCharEvent;
21531 	scope(exit) window.handleCharEvent = oldCharHandler;
21532 
21533 	auto oldMouseHandler = window.handleMouseEvent;
21534 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21535 
21536 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21537 
21538 	import core.sys.posix.sys.time;
21539 	timeval tv;
21540 	gettimeofday(&tv, null);
21541 
21542 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21543 
21544 	Time lastMouseTimestamp;
21545 
21546 	bool dnding = true;
21547 	Window lastIn = None;
21548 
21549 	void leave() {
21550 		if(lastIn == None)
21551 			return;
21552 
21553 		XEvent ev;
21554 		ev.xclient.type = EventType.ClientMessage;
21555 		ev.xclient.window = lastIn;
21556 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21557 		ev.xclient.format = 32;
21558 		ev.xclient.data.l[0] = window.impl.window;
21559 
21560 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21561 		XFlush(display);
21562 
21563 		lastIn = None;
21564 	}
21565 
21566 	void enter(Window w) {
21567 		assert(lastIn == None);
21568 
21569 		lastIn = w;
21570 
21571 		XEvent ev;
21572 		ev.xclient.type = EventType.ClientMessage;
21573 		ev.xclient.window = lastIn;
21574 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21575 		ev.xclient.format = 32;
21576 		ev.xclient.data.l[0] = window.impl.window;
21577 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21578 
21579 		auto types = handler.availableFormats();
21580 		assert(types.length > 0);
21581 
21582 		ev.xclient.data.l[2] = types[0];
21583 		if(types.length > 1)
21584 			ev.xclient.data.l[3] = types[1];
21585 		if(types.length > 2)
21586 			ev.xclient.data.l[4] = types[2];
21587 
21588 		// FIXME: other types?!?!? and make sure we skip TARGETS
21589 
21590 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21591 		XFlush(display);
21592 	}
21593 
21594 	void position(int rootX, int rootY) {
21595 		assert(lastIn != None);
21596 
21597 		XEvent ev;
21598 		ev.xclient.type = EventType.ClientMessage;
21599 		ev.xclient.window = lastIn;
21600 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21601 		ev.xclient.format = 32;
21602 		ev.xclient.data.l[0] = window.impl.window;
21603 		ev.xclient.data.l[1] = 0; // reserved
21604 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21605 		ev.xclient.data.l[3] = dataTimestamp;
21606 		ev.xclient.data.l[4] = actionAtom;
21607 
21608 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21609 		XFlush(display);
21610 
21611 	}
21612 
21613 	void drop() {
21614 		XEvent ev;
21615 		ev.xclient.type = EventType.ClientMessage;
21616 		ev.xclient.window = lastIn;
21617 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21618 		ev.xclient.format = 32;
21619 		ev.xclient.data.l[0] = window.impl.window;
21620 		ev.xclient.data.l[1] = 0; // reserved
21621 		ev.xclient.data.l[2] = dataTimestamp;
21622 
21623 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21624 		XFlush(display);
21625 
21626 		lastIn = None;
21627 		dnding = false;
21628 	}
21629 
21630 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21631 	// but idk if i should...
21632 
21633 	window.setEventHandlers(
21634 		delegate(KeyEvent ev) {
21635 			if(ev.pressed == true && ev.key == Key.Escape) {
21636 				// cancel
21637 				dnding = false;
21638 			}
21639 		},
21640 		delegate(MouseEvent ev) {
21641 			if(ev.timestamp < lastMouseTimestamp)
21642 				return;
21643 
21644 			lastMouseTimestamp = ev.timestamp;
21645 
21646 			if(ev.type == MouseEventType.motion) {
21647 				auto display = XDisplayConnection.get;
21648 				auto root = RootWindow(display, DefaultScreen(display));
21649 
21650 				Window topWindow;
21651 				int rootX, rootY;
21652 
21653 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21654 
21655 				if(topWindow == None)
21656 					return;
21657 
21658 				top:
21659 				if(auto result = topWindow in eligibility) {
21660 					auto dropWindow = *result;
21661 					if(dropWindow == None) {
21662 						leave();
21663 						return;
21664 					}
21665 
21666 					if(dropWindow != lastIn) {
21667 						leave();
21668 						enter(dropWindow);
21669 						position(rootX, rootY);
21670 					} else {
21671 						position(rootX, rootY);
21672 					}
21673 				} else {
21674 					// determine eligibility
21675 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21676 					if(data.length == 1) {
21677 						// in case there is no WM or it isn't reparenting
21678 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21679 					} else {
21680 
21681 						Window tryScanChildren(Window search, int maxRecurse) {
21682 							// could be reparenting window manager, so gotta check the next few children too
21683 							Window child;
21684 							int x;
21685 							int y;
21686 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21687 
21688 							if(child == None)
21689 								return None;
21690 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21691 							if(data.length == 1) {
21692 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21693 							} else {
21694 								if(maxRecurse)
21695 									return tryScanChildren(child, maxRecurse - 1);
21696 								else
21697 									return None;
21698 							}
21699 
21700 						}
21701 
21702 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21703 						auto topResult = tryScanChildren(topWindow, 3);
21704 						// it is easy to have a false negative due to the mouse going over a WM
21705 						// child window like the close button if separate from the frame... so I
21706 						// can't really cache negatives, :(
21707 						if(topResult != None) {
21708 							eligibility[topWindow] = topResult;
21709 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21710 						}
21711 					}
21712 
21713 				}
21714 
21715 			} else if(ev.type == MouseEventType.buttonReleased) {
21716 				drop();
21717 				dnding = false;
21718 			}
21719 		}
21720 	);
21721 
21722 	window.grabInput();
21723 	scope(exit)
21724 		window.releaseInputGrab();
21725 
21726 
21727 	EventLoop.get.run(() => dnding);
21728 
21729 	return 0;
21730 }
21731 
21732 /// X-specific
21733 TrueColorImage getWindowNetWmIcon(Window window) {
21734 	try {
21735 		auto display = XDisplayConnection.get;
21736 
21737 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21738 
21739 		if (data.length > arch_ulong.sizeof * 2) {
21740 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21741 			// these are an array of rgba images that we have to convert into pixmaps ourself
21742 
21743 			int width = cast(int) meta[0];
21744 			int height = cast(int) meta[1];
21745 
21746 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21747 
21748 			static if(arch_ulong.sizeof == 4) {
21749 				bytes = bytes[0 .. width * height * 4];
21750 				alias imageData = bytes;
21751 			} else static if(arch_ulong.sizeof == 8) {
21752 				bytes = bytes[0 .. width * height * 8];
21753 				auto imageData = new ubyte[](4 * width * height);
21754 			} else static assert(0);
21755 
21756 
21757 
21758 			// this returns ARGB. Remember it is little-endian so
21759 			//                                         we have BGRA
21760 			// our thing uses RGBA, which in little endian, is ABGR
21761 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21762 				auto r = bytes[idx + 2];
21763 				auto g = bytes[idx + 1];
21764 				auto b = bytes[idx + 0];
21765 				auto a = bytes[idx + 3];
21766 
21767 				imageData[idx2 + 0] = r;
21768 				imageData[idx2 + 1] = g;
21769 				imageData[idx2 + 2] = b;
21770 				imageData[idx2 + 3] = a;
21771 			}
21772 
21773 			return new TrueColorImage(width, height, imageData);
21774 		}
21775 
21776 		return null;
21777 	} catch(Exception e) {
21778 		return null;
21779 	}
21780 }
21781 
21782 } /* UsingSimpledisplayX11 */
21783 
21784 
21785 void loadBinNameToWindowClassName () {
21786 	import core.stdc.stdlib : realloc;
21787 	version(linux) {
21788 		// args[0] MAY be empty, so we'll just use this
21789 		import core.sys.posix.unistd : readlink;
21790 		char[1024] ebuf = void; // 1KB should be enough for everyone!
21791 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
21792 		if (len < 1) return;
21793 	} else /*version(Windows)*/ {
21794 		import core.runtime : Runtime;
21795 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
21796 		auto ebuf = Runtime.args[0];
21797 		auto len = ebuf.length;
21798 	}
21799 	auto pos = len;
21800 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
21801 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
21802 	if (sdpyWindowClassStr is null) return; // oops
21803 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
21804 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
21805 }
21806 
21807 /++
21808 	An interface representing a font that is drawn with custom facilities.
21809 
21810 	You might want [OperatingSystemFont] instead, which represents
21811 	a font loaded and drawn by functions native to the operating system.
21812 
21813 	WARNING: I might still change this.
21814 +/
21815 interface DrawableFont : MeasurableFont {
21816 	/++
21817 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
21818 
21819 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
21820 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
21821 		fill color, but that's up to the implementation.
21822 	+/
21823 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
21824 
21825 	/++
21826 		Requests that the given string is added to the image cache. You should only do this rarely, but
21827 		if you have a string that you know will be used over and over again, adding it to a cache can
21828 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
21829 		to implement this as a do-nothing method).
21830 	+/
21831 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
21832 }
21833 
21834 /++
21835 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
21836 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
21837 
21838 	You should also consider [OperatingSystemFont], which loads and draws a font with
21839 	facilities native to the user's operating system. You might also consider
21840 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
21841 	of game, as they have their own ways to draw text too.
21842 
21843 	Be warned: this can be slow, especially on remote connections to the X server, since
21844 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
21845 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
21846 	experiment in your specific case.
21847 
21848 	Please note that the return type of [DrawableFont] also includes an implementation of
21849 	[MeasurableFont].
21850 +/
21851 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
21852 	import arsd.ttf;
21853 	static class ArsdTtfFont : DrawableFont {
21854 		TtfFont font;
21855 		int size;
21856 		this(in ubyte[] data, int size) {
21857 			font = TtfFont(data);
21858 			this.size = size;
21859 
21860 
21861 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
21862 			int ascent_, descent_, line_gap;
21863 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
21864 
21865 			int advance, lsb;
21866 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
21867 			xWidth = cast(int) (advance * scale);
21868 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
21869 			MWidth = cast(int) (advance * scale);
21870 		}
21871 
21872 		private int ascent_;
21873 		private int descent_;
21874 		private int xWidth;
21875 		private int MWidth;
21876 
21877 		bool isMonospace() {
21878 			return xWidth == MWidth;
21879 		}
21880 		int averageWidth() {
21881 			return xWidth;
21882 		}
21883 		int height() {
21884 			return size;
21885 		}
21886 		int ascent() {
21887 			return ascent_;
21888 		}
21889 		int descent() {
21890 			return descent_;
21891 		}
21892 
21893 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
21894 			int width, height;
21895 			font.getStringSize(s, size, width, height);
21896 			return width;
21897 		}
21898 
21899 
21900 
21901 		Sprite[string] cache;
21902 
21903 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
21904 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
21905 			cache[text] = sprite;
21906 		}
21907 
21908 		Image stringToImage(Color fg, Color bg, in char[] text) {
21909 			int width, height;
21910 			auto data = font.renderString(text, size, width, height);
21911 			auto image = new TrueColorImage(width, height);
21912 			int pos = 0;
21913 			foreach(y; 0 .. height)
21914 			foreach(x; 0 .. width) {
21915 				fg.a = data[0];
21916 				bg.a = 255;
21917 				auto color = alphaBlend(fg, bg);
21918 				image.imageData.bytes[pos++] = color.r;
21919 				image.imageData.bytes[pos++] = color.g;
21920 				image.imageData.bytes[pos++] = color.b;
21921 				image.imageData.bytes[pos++] = data[0];
21922 				data = data[1 .. $];
21923 			}
21924 			assert(data.length == 0);
21925 
21926 			return Image.fromMemoryImage(image);
21927 		}
21928 
21929 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
21930 			Sprite sprite = (text in cache) ? *(text in cache) : null;
21931 
21932 			auto fg = painter.impl._outlineColor;
21933 			auto bg = painter.impl._fillColor;
21934 
21935 			if(sprite !is null) {
21936 				auto w = cast(SimpleWindow) painter.window;
21937 				assert(w !is null);
21938 
21939 				sprite.drawAt(painter, upperLeft);
21940 			} else {
21941 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
21942 			}
21943 		}
21944 	}
21945 
21946 	return new ArsdTtfFont(data, size);
21947 }
21948 
21949 class NotYetImplementedException : Exception {
21950 	this(string file = __FILE__, size_t line = __LINE__) {
21951 		super("Not yet implemented", file, line);
21952 	}
21953 }
21954 
21955 ///
21956 __gshared bool librariesSuccessfullyLoaded = true;
21957 ///
21958 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
21959 
21960 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
21961 	mixin(staticForeachReplacement!Iface);
21962 
21963 	void loadDynamicLibrary() @nogc {
21964 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
21965 	}
21966 
21967         void loadDynamicLibraryForReal() {
21968                 foreach(name; __traits(derivedMembers, Iface)) {
21969                         mixin("alias tmp = " ~ name ~ ";");
21970                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
21971                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
21972                 }
21973         }
21974 }
21975 
21976 private const(char)[] staticForeachReplacement(Iface)() pure {
21977 /*
21978 	// just this for gdc 9....
21979 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
21980 
21981         static foreach(name; __traits(derivedMembers, Iface))
21982                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
21983 */
21984 
21985 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
21986 	size_t pos;
21987 
21988 	void append(in char[] what) {
21989 		if(pos + what.length > code.length)
21990 			code.length = (code.length * 3) / 2;
21991 		code[pos .. pos + what.length] = what[];
21992 		pos += what.length;
21993 	}
21994 
21995         foreach(name; __traits(derivedMembers, Iface)) {
21996                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
21997 		append(name);
21998 		append(`")) `);
21999 		append(name);
22000 		append(";");
22001 	}
22002 
22003 	return code[0 .. pos];
22004 }
22005 
22006 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22007 	mixin(staticForeachReplacement!Iface);
22008 
22009 	private __gshared void* libHandle;
22010 	private __gshared bool attempted;
22011 
22012         void loadDynamicLibrary() @nogc {
22013 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22014 	}
22015 
22016 	bool loadAttempted() {
22017 		return attempted;
22018 	}
22019 	bool loadSuccessful() {
22020 		return libHandle !is null;
22021 	}
22022 
22023         void loadDynamicLibraryForReal() {
22024 		attempted = true;
22025                 version(Posix) {
22026                         import core.sys.posix.dlfcn;
22027 			version(OSX) {
22028 				version(X11)
22029                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22030 				else
22031                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22032 			} else {
22033                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22034 				if(libHandle is null)
22035                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22036 			}
22037 
22038 			static void* loadsym(void* l, const char* name) {
22039 				import core.stdc.stdlib;
22040 				if(l is null)
22041 					return &abort;
22042 				return dlsym(l, name);
22043 			}
22044                 } else version(Windows) {
22045                         import core.sys.windows.winbase;
22046                         libHandle = LoadLibrary(library ~ ".dll");
22047 			static void* loadsym(void* l, const char* name) {
22048 				import core.stdc.stdlib;
22049 				if(l is null)
22050 					return &abort;
22051 				return GetProcAddress(l, name);
22052 			}
22053                 }
22054                 if(libHandle is null) {
22055 			success = false;
22056                         //throw new Exception("load failure of library " ~ library);
22057 		}
22058                 foreach(name; __traits(derivedMembers, Iface)) {
22059                         mixin("alias tmp = " ~ name ~ ";");
22060                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22061                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22062                 }
22063         }
22064 
22065         void unloadDynamicLibrary() {
22066                 version(Posix) {
22067                         import core.sys.posix.dlfcn;
22068                         dlclose(libHandle);
22069                 } else version(Windows) {
22070                         import core.sys.windows.winbase;
22071                         FreeLibrary(libHandle);
22072                 }
22073                 foreach(name; __traits(derivedMembers, Iface))
22074                         mixin(name ~ " = null;");
22075         }
22076 }
22077 
22078 /+
22079 	The GC can be called from any thread, and a lot of cleanup must be done
22080 	on the gui thread. Since the GC can interrupt any locks - including being
22081 	triggered inside a critical section - it is vital to avoid deadlocks to get
22082 	these functions called from the right place.
22083 
22084 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22085 	right now.
22086 
22087 	The cleanup function is run when the event loop gets around to it, which is just
22088 	whenever there's something there after it has been woken up for other work. It does
22089 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22090 	(Well actually it might be ok but i don't wanna mess with it right now.)
22091 +/
22092 private struct CleanupQueue {
22093 	import core.stdc.stdlib;
22094 
22095 	void queue(alias func, T...)(T args) {
22096 		static struct Args {
22097 			T args;
22098 		}
22099 		static struct RealJob {
22100 			Job j;
22101 			Args a;
22102 		}
22103 		static void call(Job* data) {
22104 			auto rj = cast(RealJob*) data;
22105 			func(rj.a.args);
22106 		}
22107 
22108 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22109 		thing.j.call = &call;
22110 		thing.a.args = args;
22111 
22112 		buffer[tail++] = cast(Job*) thing;
22113 
22114 		// FIXME: set overflowed
22115 	}
22116 
22117 	void process() {
22118 		const tail = this.tail;
22119 
22120 		while(tail != head) {
22121 			Job* job = cast(Job*) buffer[head++];
22122 			job.call(job);
22123 			free(job);
22124 		}
22125 
22126 		if(overflowed)
22127 			throw new Exception("cleanup overflowed");
22128 	}
22129 
22130 	private:
22131 
22132 	ubyte tail; // must ONLY be written by queue
22133 	ubyte head; // must ONLY be written by process
22134 	bool overflowed;
22135 
22136 	static struct Job {
22137 		void function(Job*) call;
22138 	}
22139 
22140 	void*[256] buffer;
22141 }
22142 private __gshared CleanupQueue cleanupQueue;
22143 
22144 version(X11)
22145 /++
22146 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22147 
22148 	$(WARNING
22149 		This function is exempted from stability guarantees.
22150 	)
22151 +/
22152 float customScalingFactorForMonitor(int monitorNumber) {
22153 	import core.stdc.stdlib;
22154 	auto val = getenv("ARSD_SCALING_FACTOR");
22155 
22156 	if(val is null)
22157 		return 1.0;
22158 
22159 	char[16] buffer = 0;
22160 	int pos;
22161 
22162 	const(char)* at = val;
22163 
22164 	foreach(item; 0 .. monitorNumber + 1) {
22165 		if(*at == 0)
22166 			break; // reuse the last number when we at the end of the string
22167 		pos = 0;
22168 		while(pos + 1 < buffer.length && *at && *at != ';') {
22169 			buffer[pos++] = *at;
22170 			at++;
22171 		}
22172 		if(*at)
22173 			at++; // skip the semicolon
22174 		buffer[pos] = 0;
22175 	}
22176 
22177 	//sdpyPrintDebugString(buffer[0 .. pos]);
22178 
22179 	import core.stdc.math;
22180 	auto f = atof(buffer.ptr);
22181 
22182 	if(f <= 0.0 || isnan(f) || isinf(f))
22183 		return 1.0;
22184 
22185 	return f;
22186 }
22187 
22188 void guiAbortProcess(string msg) {
22189 	import core.stdc.stdlib;
22190 	version(Windows) {
22191 		WCharzBuffer t = WCharzBuffer(msg);
22192 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22193 	} else {
22194 		import core.stdc.stdio;
22195 		fwrite(msg.ptr, 1, msg.length, stderr);
22196 		msg = "\n";
22197 		fwrite(msg.ptr, 1, msg.length, stderr);
22198 		fflush(stderr);
22199 	}
22200 
22201 	abort();
22202 }
22203 
22204 private int minInternal(int a, int b) {
22205 	return (a < b) ? a : b;
22206 }
22207 
22208 private alias scriptable = arsd_jsvar_compatible;