1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 // Search for: FIXME: leaks if multithreaded gc
4 
5 // https://freedesktop.org/wiki/Specifications/XDND/
6 
7 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
8 
9 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
10 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
11 
12 
13 // on Mac with X11: -L-L/usr/X11/lib 
14 
15 /+
16 
17 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
18 
19 	Progress bar in taskbar
20 		- i can probably just set a property on the window...
21 		  it sets that prop to an integer 0 .. 100. Taskbar
22 		  deletes it or window deletes it when it is handled.
23 		- prolly display it as a nice little line at the bottom.
24 
25 
26 from gtk:
27 
28 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
29 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
30 
31 >+  if (cardinal > 0)
32 >+  {
33 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
34 >+                     xid,
35 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
36 >+                     XA_CARDINAL, 32,
37 >+                     PropModeReplace,
38 >+                     (guchar *) &cardinal, 1);
39 >+  }
40 >+  else
41 >+  {
42 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
45 >+  }
46 
47 from Windows:
48 
49 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
50 
51 interface
52 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 
53 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 
54 listen for msg, return TRUE
55 interface->SetProgressState(hwnd, TBPF_NORMAL); 
56 interface->SetProgressValue(hwnd, 40, 100); 
57 
58 
59 	My new notification system.
60 		- use a unix socket? or a x property? or a udp port?
61 		- could of course also get on the dbus train but ugh.
62 		- it could also reply with the info as a string for easy remote examination.
63 
64 +/
65 
66 /*
67 	Event Loop would be nices:
68 
69 	* add on idle - runs when nothing else happens
70 		* which can specify how long to yield for
71 	* send messages without a recipient window
72 	* setTimeout
73 	* setInterval
74 */
75 
76 /*
77 	Classic games I want to add:
78 		* my tetris clone
79 		* pac man
80 */
81 
82 /*
83 	Text layout needs a lot of work. Plain drawText is useful but too
84 	limited. It will need some kind of text context thing which it will
85 	update and you can pass it on and get more details out of it.
86 
87 	It will need a bounding box, a current cursor location that is updated
88 	as drawing continues, and various changable facts (which can also be
89 	changed on the painter i guess) like font, color, size, background,
90 	etc.
91 
92 	We can also fetch the caret location from it somehow.
93 
94 	Should prolly be an overload of drawText
95 
96 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
97 
98 		WS_EX_NOACTIVATE
99 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
100 		full screen windows. Can just set the atom on X. Windows will be harder.
101 
102 		moving windows. resizing windows.
103 
104 		hide cursor, capture cursor, change cursor.
105 
106 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
107 	sure the pieces are there to do its job easily and make other jobs possible.
108 */
109 
110 /++
111 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
112 	including creating windows, drawing on them, working with the clipboard,
113 	timers, OpenGL, and more. However, it does NOT provide high level GUI
114 	widgets. See my minigui.d, an extension to this module, for that
115 	functionality.
116 
117 	simpledisplay provides cross-platform wrapping for Windows and Linux
118 	(and perhaps other OSes that use X11), but also does not prevent you
119 	from using the underlying facilities if you need them. It has a goal
120 	of working efficiently over a remote X link (at least as far as Xlib
121 	reasonably allows.)
122 
123 	simpledisplay depends on [arsd.color|color.d], which should be available from the
124 	same place where you got this file. Other than that, however, it has
125 	very few dependencies and ones that don't come with the OS and/or the
126 	compiler are all opt-in.
127 
128 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
129 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
130 
131 	simpledisplay is basically stable. I plan to refactor the internals,
132 	and may add new features and fix bugs, but It do not expect to
133 	significantly change the API. It has been stable a few years already now.
134 
135 	Installation_instructions:
136 
137 	`simpledisplay.d` does not have any dependencies outside the
138 	operating system and `color.d`, so it should just work most the
139 	time, but there are a few caveats on some systems:
140 
141 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
142 	console to be automatically allocated.
143 
144 	Please note when compiling on Win64, you need to explicitly list
145 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
146 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
147 
148 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
149 	note the "w".
150 
151 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
152 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
153 	See [EnableWindowsSubsystem] for more information.
154 
155 	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.
156 
157 	On Ubuntu, you might need to install X11 development libraries to
158 	successfully link.
159 
160 	$(CONSOLE
161 		$ sudo apt-get install libglc-dev
162 		$ sudo apt-get install libx11-dev
163 	)
164 
165 
166 	Jump_list:
167 
168 	Don't worry, you don't have to read this whole documentation file!
169 
170 	Check out the [#event-example] and [#Pong-example] to get started quickly.
171 
172 	The main classes you may want to create are [SimpleWindow], [Timer],
173 	[Image], and [Sprite].
174 
175 	The main functions you'll want are [setClipboardText] and [getClipboardText].
176 
177 	There are also platform-specific functions available such as [XDisplayConnection]
178 	and [GetAtom] for X11, among others.
179 
180 	See the examples and topics list below to learn more.
181 
182 	$(WARNING
183 		There should only be one GUI thread per application,
184 		and all windows should be created in it and your
185 		event loop should run there.
186 
187 		To do otherwise is undefined behavior and has no
188 		cross platform guarantees.
189 	)
190 
191 	$(H2 About this documentation)
192 
193 	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.
194 
195 	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!
196 
197 	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.
198 
199 	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.
200 
201 	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.
202 
203 	At points, I will talk about implementation details in the documentation. These are sometimes
204 	subject to change, but nevertheless useful to understand what is really going on. You can learn
205 	more about some of the referenced things by searching the web for info about using them from C.
206 	You can always look at the source of simpledisplay.d too for the most authoritative source on
207 	its specific implementation. If you disagree with how I did something, please contact me so we
208 	can discuss it!
209 
210 	$(H2 Using with fibers)
211 
212 	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).
213 
214 	$(H2 Topics)
215 
216 	$(H3 $(ID topic-windows) Windows)
217 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
218 		window on the user's screen.
219 
220 		You may create multiple windows, if the underlying platform supports it. You may check
221 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
222 		SimpleWindow's constructor at runtime to handle those cases.
223 
224 		A single running event loop will handle as many windows as needed.
225 
226 	$(H3 $(ID topic-event-loops) Event loops)
227 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
228 
229 		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:
230 
231 		---
232 		// dmd example.d simpledisplay.d color.d
233 		import arsd.simpledisplay;
234 		void main() {
235 			auto window = new SimpleWindow(200, 200);
236 			window.eventLoop(0,
237 			  delegate (dchar) { /* got a character key press */ }
238 			);
239 		}
240 		---
241 
242 		$(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.)
243 
244 		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.
245 
246 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
247 
248 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
249 
250 		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.
251 
252 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
253 		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.
254 
255 		See the [NotificationAreaIcon] class.
256 
257 	$(H3 $(ID topic-input-handling) Input handling)
258 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
259 
260 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
261 
262 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
263 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
264 
265 		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:
266 
267 		---
268 		// dmd example.d simpledisplay.d color.d
269 		import arsd.simpledisplay;
270 		void main() {
271 			auto window = new SimpleWindow(200, 200);
272 			{ // introduce sub-scope
273 				auto painter = window.draw(); // begin drawing
274 				/* draw here */
275 				painter.outlineColor = Color.red;
276 				painter.fillColor = Color.black;
277 				painter.drawRectangle(Point(0, 0), 200, 200);
278 			} // end scope, calling `painter`'s destructor, drawing to the screen.
279 			window.eventLoop(0); // handle events
280 		}
281 		---
282 
283 		Painting is done based on two color properties, a pen and a brush.
284 
285 		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.
286 
287 		FIXME Add example of 2d opengl drawing here.
288 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
289 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
290 
291 		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.
292 
293 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
294 
295 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
296 
297 		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].
298 
299 		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.
300 
301 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
302 
303 		---
304 		import arsd.simpledisplay;
305 
306 		void main() {
307 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
308 
309 			float otherColor = 0.0;
310 			float colorDelta = 0.05;
311 
312 			window.redrawOpenGlScene = delegate() {
313 				glLoadIdentity();
314 				glBegin(GL_QUADS);
315 
316 				glColor3f(1.0, otherColor, 0);
317 				glVertex3f(-0.8, -0.8, 0);
318 
319 				glColor3f(1.0, otherColor, 1.0);
320 				glVertex3f(0.8, -0.8, 0);
321 
322 				glColor3f(0, 1.0, otherColor);
323 				glVertex3f(0.8, 0.8, 0);
324 
325 				glColor3f(otherColor, 0, 1.0);
326 				glVertex3f(-0.8, 0.8, 0);
327 
328 				glEnd();
329 			};
330 
331 			window.eventLoop(50, () {
332 				otherColor += colorDelta;
333 				if(otherColor > 1.0) {
334 					otherColor = 1.0;
335 					colorDelta = -0.05;
336 				}
337 				if(otherColor < 0) {
338 					otherColor = 0;
339 					colorDelta = 0.05;
340 				}
341 				// at the end of the timer, we have to request a redraw
342 				// or we won't see the changes.
343 				window.redrawOpenGlSceneSoon();
344 			});
345 		}
346 		---
347 
348 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
349 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
350 		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.
351 
352 		This example program shows how you can set up a shader to draw a rectangle:
353 
354 		---
355 		module opengl3test;
356 		import arsd.simpledisplay;
357 
358 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
359 
360 		void main() {
361 			// First thing we do, before creating the window, is declare what version we want.
362 			setOpenGLContextVersion(3, 3);
363 			// turning off legacy compat is required to use version 3.3 and newer
364 			openGLContextCompatible = false;
365 
366 			uint VAO;
367 			OpenGlShader shader;
368 
369 			// then we can create the window.
370 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
371 
372 			// additional setup needs to be done when it is visible, simpledisplay offers a property
373 			// for exactly that:
374 			window.visibleForTheFirstTime = delegate() {
375 				// now with the window loaded, we can start loading the modern opengl functions.
376 
377 				// you MUST set the context first.
378 				window.setAsCurrentOpenGlContext;
379 				// then load the remainder of the library
380 				gl3.loadDynamicLibrary();
381 
382 				// now you can create the shaders, etc.
383 				shader = new OpenGlShader(
384 					OpenGlShader.Source(GL_VERTEX_SHADER, `
385 						#version 330 core
386 						layout (location = 0) in vec3 aPos;
387 						void main() {
388 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
389 						}
390 					`),
391 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
392 						#version 330 core
393 						out vec4 FragColor;
394 						uniform vec4 mycolor;
395 						void main() {
396 							FragColor = mycolor;
397 						}
398 					`),
399 				);
400 
401 				// and do whatever other setup you want.
402 
403 				float[] vertices = [
404 					0.5f,  0.5f, 0.0f,  // top right
405 					0.5f, -0.5f, 0.0f,  // bottom right
406 					-0.5f, -0.5f, 0.0f,  // bottom left
407 					-0.5f,  0.5f, 0.0f   // top left 
408 				];
409 				uint[] indices = [  // note that we start from 0!
410 					0, 1, 3,  // first Triangle
411 					1, 2, 3   // second Triangle
412 				];
413 				uint VBO, EBO;
414 				glGenVertexArrays(1, &VAO);
415 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
416 				glBindVertexArray(VAO);
417 
418 				glGenBuffers(1, &VBO);
419 				glGenBuffers(1, &EBO);
420 
421 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
422 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
423 
424 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
425 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
426 
427 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
428 				glEnableVertexAttribArray(0);
429 
430 				// the library will set the initial viewport and trigger our first draw,
431 				// so these next two lines are NOT needed. they are just here as comments
432 				// to show what would happen next.
433 
434 				// glViewport(0, 0, window.width, window.height);
435 				// window.redrawOpenGlSceneNow();
436 			};
437 
438 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
439 			// it is our render method.
440 			window.redrawOpenGlScene = delegate() {
441 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
442 				glClear(GL_COLOR_BUFFER_BIT);
443 
444 				glUseProgram(shader.shaderProgram);
445 
446 				// the shader helper class has methods to set uniforms too
447 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
448 
449 				glBindVertexArray(VAO);
450 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
451 			};
452 
453 			window.eventLoop(0);
454 		}
455 		---
456 
457 	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.
458 
459 
460 	$(H3 $(ID topic-images) Displaying images)
461 		You can also load PNG images using [arsd.png].
462 
463 		---
464 		// dmd example.d simpledisplay.d color.d png.d
465 		import arsd.simpledisplay;
466 		import arsd.png;
467 
468 		void main() {
469 			auto image = Image.fromMemoryImage(readPng("image.png"));
470 			displayImage(image);
471 		}
472 		---
473 
474 		Compile with `dmd example.d simpledisplay.d png.d`.
475 
476 		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.
477 
478 	$(H3 $(ID topic-sprites) Sprites)
479 		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.
480 
481 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
482 
483 	$(H3 $(ID topic-clipboard) Clipboard)
484 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
485 
486 		It also has helpers for handling X-specific events.
487 
488 	$(H3 $(ID topic-dnd) Drag and Drop)
489 		See [enableDragAndDrop] and [draggable].
490 
491 	$(H3 $(ID topic-timers) Timers)
492 		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].
493 
494 		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.
495 
496 		---
497 			import arsd.simpledisplay;
498 
499 			void main() {
500 				auto window = new SimpleWindow(400, 400);
501 				// every 100 ms, it will draw a random line
502 				// on the window.
503 				window.eventLoop(100, {
504 					auto painter = window.draw();
505 
506 					import std.random;
507 					// random color
508 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
509 					// random line
510 					painter.drawLine(
511 						Point(uniform(0, window.width), uniform(0, window.height)),
512 						Point(uniform(0, window.width), uniform(0, window.height)));
513 
514 				});
515 			}
516 		---
517 
518 		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.
519 
520 		The pulse timer and instances of the [Timer] class may be combined at will.
521 
522 		---
523 			import arsd.simpledisplay;
524 
525 			void main() {
526 				auto window = new SimpleWindow(400, 400);
527 				auto timer = new Timer(1000, delegate {
528 					auto painter = window.draw();
529 					painter.clear();
530 				});
531 
532 				window.eventLoop(0);
533 			}
534 		---
535 
536 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
537 
538 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
539 		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.
540 
541 		See also: `xwindows.d` from my github.
542 
543 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
544 		`handleNativeEvent` and `handleNativeGlobalEvent`.
545 
546 	$(H3 $(ID topic-integration) Integration with other libraries)
547 		Integration with a third-party event loop is possible.
548 
549 		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.
550 
551 	$(H3 $(ID topic-guis) GUI widgets)
552 		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!
553 
554 		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.
555 
556 		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.)
557 
558 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
559 
560 	$(H2 Platform-specific tips and tricks)
561 
562 	X_tips:
563 
564 	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.
565 
566 	Windows_tips:
567 
568 	You can add icons or manifest files to your exe using a resource file.
569 
570 	To create a Windows .ico file, use the gimp or something. I'll write a helper
571 	program later.
572 
573 	Create `yourapp.rc`:
574 
575 	```rc
576 		1 ICON filename.ico
577 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
578 	```
579 
580 	And `yourapp.exe.manifest`:
581 
582 	```xml
583 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
584 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
585 		<assemblyIdentity
586 		    version="1.0.0.0"
587 		    processorArchitecture="*"
588 		    name="CompanyName.ProductName.YourApplication"
589 		    type="win32"
590 		/>
591 		<description>Your application description here.</description>
592 		<dependency>
593 		    <dependentAssembly>
594 			<assemblyIdentity
595 			    type="win32"
596 			    name="Microsoft.Windows.Common-Controls"
597 			    version="6.0.0.0"
598 			    processorArchitecture="*"
599 			    publicKeyToken="6595b64144ccf1df"
600 			    language="*"
601 			/>
602 		    </dependentAssembly>
603 		</dependency>
604 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
605 			<windowsSettings>
606 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
607 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
608 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
609 				<!-- to render crisply in DPI-unaware contexts --> 
610 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
611 			</windowsSettings>
612 		</application>
613 		</assembly>
614 	```
615 
616 	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`.
617 
618 	Doing this lets you opt into various new things since Windows XP.
619 
620 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
621 
622 	$(H2 Tips)
623 
624 	$(H3 Name conflicts)
625 
626 	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:
627 
628 	---
629 	static import sdpy = arsd.simpledisplay;
630 	import arsd.simpledisplay : SimpleWindow;
631 
632 	void main() {
633 		auto window = new SimpleWindow();
634 		sdpy.EventLoop.get.run();
635 	}
636 	---
637 
638 	$(H2 $(ID developer-notes) Developer notes)
639 
640 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
641 	implementation though.
642 
643 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
644 	suck. If I was rewriting it, I wouldn't do it that way again.
645 
646 	This file must not have any more required dependencies. If you need bindings, add
647 	them right to this file. Once it gets into druntime and is there for a while, remove
648 	bindings from here to avoid conflicts (or put them in an appropriate version block
649 	so it continues to just work on old dmd), but wait a couple releases before making the
650 	transition so this module remains usable with older versions of dmd.
651 
652 	You may have optional dependencies if needed by putting them in version blocks or
653 	template functions. You may also extend the module with other modules with UFCS without
654 	actually editing this - that is nice to do if you can.
655 
656 	Try to make functions work the same way across operating systems. I typically make
657 	it thinly wrap Windows, then emulate that on Linux.
658 
659 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
660 	Phobos! So try to avoid it.
661 
662 	See more comments throughout the source.
663 
664 	I realize this file is fairly large, but over half that is just bindings at the bottom
665 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
666 	to understand. I suggest you jump around the source by looking for a particular
667 	declaration you're interested in, like `class SimpleWindow` using your editor's search
668 	function, then look at one piece at a time.
669 
670 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
671 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
672 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
673 
674 	I live in the eastern United States, so I will most likely not be around at night in
675 	that US east timezone.
676 
677 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
678 
679 	Building documentation: use my adrdox generator, `dub run adrdox`.
680 
681 	Examples:
682 
683 	$(DIV $(ID Event-example))
684 	$(H3 $(ID event-example) Event example)
685 	This program creates a window and draws events inside them as they
686 	happen, scrolling the text in the window as needed. Run this program
687 	and experiment to get a feel for where basic input events take place
688 	in the library.
689 
690 	---
691 	// dmd example.d simpledisplay.d color.d
692 	import arsd.simpledisplay;
693 	import std.conv;
694 
695 	void main() {
696 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
697 
698 		int y = 0;
699 
700 		void addLine(string text) {
701 			auto painter = window.draw();
702 
703 			if(y + painter.fontHeight >= window.height) {
704 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
705 				y -= painter.fontHeight;
706 			}
707 
708 			painter.outlineColor = Color.red;
709 			painter.fillColor = Color.black;
710 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
711 
712 			painter.outlineColor = Color.white;
713 
714 			painter.drawText(Point(10, y), text);
715 
716 			y += painter.fontHeight;
717 		}
718 
719 		window.eventLoop(1000,
720 		  () {
721 			addLine("Timer went off!");
722 		  },
723 		  (KeyEvent event) {
724 			addLine(to!string(event));
725 		  },
726 		  (MouseEvent event) {
727 			addLine(to!string(event));
728 		  },
729 		  (dchar ch) {
730 			addLine(to!string(ch));
731 		  }
732 		);
733 	}
734 	---
735 
736 	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.
737 
738 	$(COMMENT
739 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
740 
741 	---
742 
743 	---
744 	)
745 
746 
747 +/
748 module arsd.simpledisplay;
749 
750 // FIXME: tetris demo
751 // FIXME: space invaders demo
752 // FIXME: asteroids demo
753 
754 /++ $(ID Pong-example)
755 	$(H3 Pong)
756 
757 	This program creates a little Pong-like game. Player one is controlled
758 	with the keyboard.  Player two is controlled with the mouse. It demos
759 	the pulse timer, event handling, and some basic drawing.
760 +/
761 version(demos)
762 unittest {
763 	// dmd example.d simpledisplay.d color.d
764 	import arsd.simpledisplay;
765 
766 	enum paddleMovementSpeed = 8;
767 	enum paddleHeight = 48;
768 
769 	void main() {
770 		auto window = new SimpleWindow(600, 400, "Pong game!");
771 
772 		int playerOnePosition, playerTwoPosition;
773 		int playerOneMovement, playerTwoMovement;
774 		int playerOneScore, playerTwoScore;
775 
776 		int ballX, ballY;
777 		int ballDx, ballDy;
778 
779 		void serve() {
780 			import std.random;
781 
782 			ballX = window.width / 2;
783 			ballY = window.height / 2;
784 			ballDx = uniform(-4, 4) * 3;
785 			ballDy = uniform(-4, 4) * 3;
786 			if(ballDx == 0)
787 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
788 		}
789 
790 		serve();
791 
792 		window.eventLoop(50, // set a 50 ms timer pulls
793 			// This runs once per timer pulse
794 			delegate () {
795 				auto painter = window.draw();
796 
797 				painter.clear();
798 
799 				// Update everyone's motion
800 				playerOnePosition += playerOneMovement;
801 				playerTwoPosition += playerTwoMovement;
802 
803 				ballX += ballDx;
804 				ballY += ballDy;
805 
806 				// Bounce off the top and bottom edges of the window
807 				if(ballY + 7 >= window.height)
808 					ballDy = -ballDy;
809 				if(ballY - 8 <= 0)
810 					ballDy = -ballDy;
811 
812 				// Bounce off the paddle, if it is in position
813 				if(ballX - 8 <= 16) {
814 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
815 						ballDx = -ballDx + 1; // add some speed to keep it interesting
816 						ballDy += playerOneMovement; // and y movement based on your controls too
817 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
818 					} else {
819 						// Missed it
820 						playerTwoScore ++;
821 						serve();
822 					}
823 				}
824 
825 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
826 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
827 						ballDx = -ballDx - 1;
828 						ballDy += playerTwoMovement;
829 						ballX = window.width - 24;
830 					} else {
831 						// Missed it
832 						playerOneScore ++;
833 						serve();
834 					}
835 				}
836 
837 				// Draw the paddles
838 				painter.outlineColor = Color.black;
839 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
840 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
841 
842 				// Draw the ball
843 				painter.fillColor = Color.red;
844 				painter.outlineColor = Color.yellow;
845 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
846 
847 				// Draw the score
848 				painter.outlineColor = Color.blue;
849 				import std.conv;
850 				painter.drawText(Point(64, 4), to!string(playerOneScore));
851 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
852 
853 			},
854 			delegate (KeyEvent event) {
855 				// Player 1's controls are the arrow keys on the keyboard
856 				if(event.key == Key.Down)
857 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
858 				if(event.key == Key.Up)
859 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
860 
861 			},
862 			delegate (MouseEvent event) {
863 				// Player 2's controls are mouse movement while the left button is held down
864 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
865 					if(event.dy > 0)
866 						playerTwoMovement = paddleMovementSpeed;
867 					else if(event.dy < 0)
868 						playerTwoMovement = -paddleMovementSpeed;
869 				} else {
870 					playerTwoMovement = 0;
871 				}
872 			}
873 		);
874 	}
875 }
876 
877 /++ $(H3 $(ID example-minesweeper) Minesweeper)
878 
879 	This minesweeper demo shows how we can implement another classic
880 	game with simpledisplay and shows some mouse input and basic output
881 	code.
882 +/
883 version(demos)
884 unittest {
885 	import arsd.simpledisplay;
886 
887 	enum GameSquare {
888 		mine = 0,
889 		clear,
890 		m1, m2, m3, m4, m5, m6, m7, m8
891 	}
892 
893 	enum UserSquare {
894 		unknown,
895 		revealed,
896 		flagged,
897 		questioned
898 	}
899 
900 	enum GameState {
901 		inProgress,
902 		lose,
903 		win
904 	}
905 
906 	GameSquare[] board;
907 	UserSquare[] userState;
908 	GameState gameState;
909 	int boardWidth;
910 	int boardHeight;
911 
912 	bool isMine(int x, int y) {
913 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
914 			return false;
915 		return board[y * boardWidth + x] == GameSquare.mine;
916 	}
917 
918 	GameState reveal(int x, int y) {
919 		if(board[y * boardWidth + x] == GameSquare.clear) {
920 			floodFill(userState, boardWidth, boardHeight,
921 				UserSquare.unknown, UserSquare.revealed,
922 				x, y,
923 				(x, y) {
924 					if(board[y * boardWidth + x] == GameSquare.clear)
925 						return true;
926 					else {
927 						userState[y * boardWidth + x] = UserSquare.revealed;
928 						return false;
929 					}
930 				});
931 		} else {
932 			userState[y * boardWidth + x] = UserSquare.revealed;
933 			if(isMine(x, y))
934 				return GameState.lose;
935 		}
936 
937 		foreach(state; userState) {
938 			if(state == UserSquare.unknown || state == UserSquare.questioned)
939 				return GameState.inProgress;
940 		}
941 
942 		return GameState.win;
943 	}
944 
945 	void initializeBoard(int width, int height, int numberOfMines) {
946 		boardWidth = width;
947 		boardHeight = height;
948 		board.length = width * height;
949 
950 		userState.length = width * height;
951 		userState[] = UserSquare.unknown; 
952 
953 		import std.algorithm, std.random, std.range;
954 
955 		board[] = GameSquare.clear;
956 
957 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
958 			board[minePosition] = GameSquare.mine;
959 
960 		int x;
961 		int y;
962 		foreach(idx, ref square; board) {
963 			if(square == GameSquare.clear) {
964 				int danger = 0;
965 				danger += isMine(x-1, y-1)?1:0;
966 				danger += isMine(x-1, y)?1:0;
967 				danger += isMine(x-1, y+1)?1:0;
968 				danger += isMine(x, y-1)?1:0;
969 				danger += isMine(x, y+1)?1:0;
970 				danger += isMine(x+1, y-1)?1:0;
971 				danger += isMine(x+1, y)?1:0;
972 				danger += isMine(x+1, y+1)?1:0;
973 
974 				square = cast(GameSquare) (danger + 1);
975 			}
976 
977 			x++;
978 			if(x == width) {
979 				x = 0;
980 				y++;
981 			}
982 		}
983 	}
984 
985 	void redraw(SimpleWindow window) {
986 		import std.conv;
987 
988 		auto painter = window.draw();
989 
990 		painter.clear();
991 
992 		final switch(gameState) with(GameState) {
993 			case inProgress:
994 				break;
995 			case win:
996 				painter.fillColor = Color.green;
997 				painter.drawRectangle(Point(0, 0), window.width, window.height);
998 				return;
999 			case lose:
1000 				painter.fillColor = Color.red;
1001 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1002 				return;
1003 		}
1004 
1005 		int x = 0;
1006 		int y = 0;
1007 
1008 		foreach(idx, square; board) {
1009 			auto state = userState[idx];
1010 
1011 			final switch(state) with(UserSquare) {
1012 				case unknown:
1013 					painter.outlineColor = Color.black;
1014 					painter.fillColor = Color(128,128,128);
1015 
1016 					painter.drawRectangle(
1017 						Point(x * 20, y * 20),
1018 						20, 20
1019 					);
1020 				break;
1021 				case revealed:
1022 					if(square == GameSquare.clear) {
1023 						painter.outlineColor = Color.white;
1024 						painter.fillColor = Color.white;
1025 
1026 						painter.drawRectangle(
1027 							Point(x * 20, y * 20),
1028 							20, 20
1029 						);
1030 					} else {
1031 						painter.outlineColor = Color.black;
1032 						painter.fillColor = Color.white;
1033 
1034 						painter.drawText(
1035 							Point(x * 20, y * 20),
1036 							to!string(square)[1..2],
1037 							Point(x * 20 + 20, y * 20 + 20),
1038 							TextAlignment.Center | TextAlignment.VerticalCenter);
1039 					}
1040 				break;
1041 				case flagged:
1042 					painter.outlineColor = Color.black;
1043 					painter.fillColor = Color.red;
1044 					painter.drawRectangle(
1045 						Point(x * 20, y * 20),
1046 						20, 20
1047 					);
1048 				break;
1049 				case questioned:
1050 					painter.outlineColor = Color.black;
1051 					painter.fillColor = Color.yellow;
1052 					painter.drawRectangle(
1053 						Point(x * 20, y * 20),
1054 						20, 20
1055 					);
1056 				break;
1057 			}
1058 
1059 			x++;
1060 			if(x == boardWidth) {
1061 				x = 0;
1062 				y++;
1063 			}
1064 		}
1065 
1066 	}
1067 
1068 	void main() {
1069 		auto window = new SimpleWindow(200, 200);
1070 
1071 		initializeBoard(10, 10, 10);
1072 
1073 		redraw(window);
1074 		window.eventLoop(0,
1075 			delegate (MouseEvent me) {
1076 				if(me.type != MouseEventType.buttonPressed)
1077 					return;
1078 				auto x = me.x / 20;
1079 				auto y = me.y / 20;
1080 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1081 					if(me.button == MouseButton.left) {
1082 						gameState = reveal(x, y);
1083 					} else {
1084 						userState[y*boardWidth+x] = UserSquare.flagged;
1085 					}
1086 					redraw(window);
1087 				}
1088 			}
1089 		);
1090 	}
1091 }
1092 
1093 /*
1094 version(OSX) {
1095 	version=without_opengl;
1096 	version=allow_unimplemented_features;
1097 	version=OSXCocoa;
1098 	pragma(linkerDirective, "-framework Cocoa");
1099 }
1100 */
1101 
1102 version(without_opengl) {
1103 	enum SdpyIsUsingIVGLBinds = false;
1104 } else /*version(Posix)*/ {
1105 	static if (__traits(compiles, (){import iv.glbinds;})) {
1106 		enum SdpyIsUsingIVGLBinds = true;
1107 		public import iv.glbinds;
1108 		//pragma(msg, "SDPY: using iv.glbinds");
1109 	} else {
1110 		enum SdpyIsUsingIVGLBinds = false;
1111 	}
1112 //} else {
1113 //	enum SdpyIsUsingIVGLBinds = false;
1114 }
1115 
1116 
1117 version(Windows) {
1118 	//import core.sys.windows.windows;
1119 	import core.sys.windows.winnls;
1120 	import core.sys.windows.windef;
1121 	import core.sys.windows.basetyps;
1122 	import core.sys.windows.winbase;
1123 	import core.sys.windows.winuser;
1124 	import core.sys.windows.shellapi;
1125 	import core.sys.windows.wingdi;
1126 	static import gdi = core.sys.windows.wingdi; // so i
1127 
1128 	pragma(lib, "gdi32");
1129 	pragma(lib, "user32");
1130 
1131 	// for AlphaBlend... a breaking change....
1132 	version(CRuntime_DigitalMars) { } else
1133 		pragma(lib, "msimg32");
1134 } else version (linux) {
1135 	//k8: this is hack for rdmd. sorry.
1136 	static import core.sys.linux.epoll;
1137 	static import core.sys.linux.timerfd;
1138 }
1139 
1140 
1141 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1142 
1143 // http://wiki.dlang.org/Simpledisplay.d
1144 
1145 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1146 
1147 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1148 // but can i control the scroll lock led
1149 
1150 
1151 // Note: if you are using Image on X, you might want to do:
1152 /*
1153 	static if(UsingSimpledisplayX11) {
1154 		if(!Image.impl.xshmAvailable) {
1155 			// the images will use the slower XPutImage, you might
1156 			// want to consider an alternative method to get better speed
1157 		}
1158 	}
1159 
1160 	If the shared memory extension is available though, simpledisplay uses it
1161 	for a significant speed boost whenever you draw large Images.
1162 */
1163 
1164 // 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.
1165 
1166 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1167 
1168 /*
1169 	Biggest FIXME:
1170 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1171 
1172 		clean up opengl contexts when their windows close
1173 
1174 		fix resizing the bitmaps/pixmaps
1175 */
1176 
1177 // BTW on Windows:
1178 // -L/SUBSYSTEM:WINDOWS:5.0
1179 // to dmd will make a nice windows binary w/o a console if you want that.
1180 
1181 /*
1182 	Stuff to add:
1183 
1184 	use multibyte functions everywhere we can
1185 
1186 	OpenGL windows
1187 	more event stuff
1188 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1189 
1190 
1191 	resizeEvent
1192 		and make the windows non-resizable by default,
1193 		or perhaps stretched (if I can find something in X like StretchBlt)
1194 
1195 	take a screenshot function!
1196 
1197 	Pens and brushes?
1198 	Maybe a global event loop?
1199 
1200 	Mouse deltas
1201 	Key items
1202 */
1203 
1204 /*
1205 From MSDN:
1206 
1207 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1208 
1209 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.
1210 
1211 */
1212 
1213 version(linux) {
1214 	version = X11;
1215 	version(without_libnotify) {
1216 		// we cool
1217 	}
1218 	else
1219 		version = libnotify;
1220 }
1221 
1222 version(libnotify) {
1223 	pragma(lib, "dl");
1224 	import core.sys.posix.dlfcn;
1225 
1226 	void delegate()[int] libnotify_action_delegates;
1227 	int libnotify_action_delegates_count;
1228 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1229 		auto idx = cast(int) user_data;
1230 		if(auto dgptr = idx in libnotify_action_delegates) {
1231 			(*dgptr)();
1232 			libnotify_action_delegates.remove(idx);
1233 		}
1234 	}
1235 
1236 	struct C_DynamicLibrary {
1237 		void* handle;
1238 		this(string name) {
1239 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1240 			if(handle is null)
1241 				throw new Exception("dlopen");
1242 		}
1243 
1244 		void close() {
1245 			dlclose(handle);
1246 		}
1247 
1248 		~this() {
1249 			// close
1250 		}
1251 
1252 		// FIXME: this looks up by name every time.... 
1253 		template call(string func, Ret, Args...) {
1254 			extern(C) Ret function(Args) fptr;
1255 			typeof(fptr) call() {
1256 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1257 				return fptr;
1258 			}
1259 		}
1260 	}
1261 
1262 	C_DynamicLibrary* libnotify;
1263 }
1264 
1265 version(OSX) {
1266 	version(OSXCocoa) {}
1267 	else { version = X11; }
1268 }
1269 	//version = OSXCocoa; // this was written by KennyTM
1270 version(FreeBSD)
1271 	version = X11;
1272 version(Solaris)
1273 	version = X11;
1274 
1275 version(X11) {
1276 	version(without_xft) {}
1277 	else version=with_xft;
1278 }
1279 
1280 void featureNotImplemented()() {
1281 	version(allow_unimplemented_features)
1282 		throw new NotYetImplementedException();
1283 	else
1284 		static assert(0);
1285 }
1286 
1287 // these are so the static asserts don't trigger unless you want to
1288 // add support to it for an OS
1289 version(Windows)
1290 	version = with_timer;
1291 version(linux)
1292 	version = with_timer;
1293 
1294 version(with_timer)
1295 	enum bool SimpledisplayTimerAvailable = true;
1296 else
1297 	enum bool SimpledisplayTimerAvailable = false;
1298 
1299 /// 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.
1300 version(Windows)
1301 	enum bool UsingSimpledisplayWindows = true;
1302 else
1303 	enum bool UsingSimpledisplayWindows = false;
1304 
1305 /// 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.
1306 version(X11)
1307 	enum bool UsingSimpledisplayX11 = true;
1308 else
1309 	enum bool UsingSimpledisplayX11 = false;
1310 
1311 /// 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.
1312 version(OSXCocoa)
1313 	enum bool UsingSimpledisplayCocoa = true;
1314 else
1315 	enum bool UsingSimpledisplayCocoa = false;
1316 
1317 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1318 version(Windows)
1319 	enum multipleWindowsSupported = true;
1320 else version(X11)
1321 	enum multipleWindowsSupported = true;
1322 else version(OSXCocoa)
1323 	enum multipleWindowsSupported = true;
1324 else
1325 	static assert(0);
1326 
1327 version(without_opengl)
1328 	enum bool OpenGlEnabled = false;
1329 else
1330 	enum bool OpenGlEnabled = true;
1331 
1332 /++
1333 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1334 	If you mix this in above your `main` function, you no longer need to use the linker
1335 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1336 
1337 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1338 
1339 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1340 	stderr writeln. It will fail and throw an exception.
1341 
1342 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1343 
1344 	History:
1345 		Added November 24, 2021 (dub v10.4)
1346 +/
1347 mixin template EnableWindowsSubsystem() {
1348 	version(Windows)
1349 	version(CRuntime_Microsoft) {
1350 		pragma(linkerDirective, "/subsystem:windows");
1351 		version(LDC)
1352 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1353 		else
1354 			pragma(linkerDirective, "/entry:mainCRTStartup");
1355 	}
1356 }
1357 
1358 
1359 /++
1360 	After selecting a type from [WindowTypes], you may further customize
1361 	its behavior by setting one or more of these flags.
1362 
1363 
1364 	The different window types have different meanings of `normal`. If the
1365 	window type already is a good match for what you want to do, you should
1366 	just use [WindowFlags.normal], the default, which will do the right thing
1367 	for your users.
1368 
1369 	The window flags will not always be honored by the operating system
1370 	and window managers; they are hints, not commands.
1371 +/
1372 enum WindowFlags : int {
1373 	normal = 0, ///
1374 	skipTaskbar = 1, ///
1375 	alwaysOnTop = 2, ///
1376 	alwaysOnBottom = 4, ///
1377 	cannotBeActivated = 8, ///
1378 	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.
1379 	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.
1380 	/++
1381 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1382 		it is still a top-level window. This should NOT be set separately for most window types.
1383 
1384 		A transient window will not keep the application open if its main window closes.
1385 
1386 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1387 
1388 
1389 		From the ICCM:
1390 
1391 		$(BLOCKQUOTE
1392 			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. 
1393 
1394 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1395 		)
1396 
1397 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1398 
1399 		History:
1400 			Added February 23, 2021 but not yet stabilized.
1401 	+/
1402 	transient = 64,
1403 	/++
1404 		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.
1405 
1406 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1407 
1408 		History:
1409 			Added April 1, 2022
1410 	+/
1411 	managesChildWindowFocus = 128,
1412 
1413 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1414 }
1415 
1416 /++
1417 	When creating a window, you can pass a type to SimpleWindow's constructor,
1418 	then further customize the window by changing `WindowFlags`.
1419 
1420 
1421 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1422 	use. The others are there to build a foundation for a higher level GUI toolkit,
1423 	but are themselves not as high level as you might think from their names.
1424 
1425 	This list is based on the EMWH spec for X11.
1426 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1427 +/
1428 enum WindowTypes : int {
1429 	/// An ordinary application window.
1430 	normal,
1431 	/// 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.
1432 	undecorated,
1433 	/// 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.
1434 	eventOnly,
1435 	/// A drop down menu, such as from a menu bar
1436 	dropdownMenu,
1437 	/// A popup menu, such as from a right click
1438 	popupMenu,
1439 	/// A popup bubble notification
1440 	notification,
1441 	/*
1442 	menu, /// a tearable menu bar
1443 	splashScreen, /// a loading splash screen for your application
1444 	tooltip, /// A tiny window showing temporary help text or something.
1445 	comboBoxDropdown,
1446 	dialog,
1447 	toolbar
1448 	*/
1449 	/// a child nested inside the parent. You must pass a parent window to the ctor
1450 	nestedChild,
1451 }
1452 
1453 
1454 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1455 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1456 private __gshared char* sdpyWindowClassStr = null;
1457 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1458 
1459 /**
1460 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1461 	You may want to change context version if you want to use advanced shaders or
1462 	other modern OpenGL techinques. This setting doesn't affect already created
1463 	windows. You may use version 2.1 as your default, which should be supported
1464 	by any box since 2006, so seems to be a reasonable choice.
1465 
1466 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1467 	old context creation code without any version specified. This is the safest
1468 	way to init OpenGL, but it may not give you access to advanced features.
1469 
1470 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1471 */
1472 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1473 
1474 /**
1475 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1476 	pipeline functions, and without "compatible" mode you won't be able to use
1477 	your old non-shader-based code with such contexts. By default SimpleDisplay
1478 	creates compatible context, so you can gradually upgrade your OpenGL code if
1479 	you want to (or leave it as is, as it should "just work").
1480 */
1481 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1482 
1483 /**
1484 	Set to `true` to allow creating OpenGL context with lower version than requested
1485 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1486 	`openGLContextFallbackActivated()` will return `true`.
1487 	*/
1488 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1489 
1490 /**
1491 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1492 	*/
1493 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1494 
1495 
1496 /**
1497 	Set window class name for all following `new SimpleWindow()` calls.
1498 
1499 	WARNING! For Windows, you should set your class name before creating any
1500 	window, and NEVER change it after that!
1501 */
1502 void sdpyWindowClass (const(char)[] v) {
1503 	import core.stdc.stdlib : realloc;
1504 	if (v.length == 0) v = "SimpleDisplayWindow";
1505 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1506 	if (sdpyWindowClassStr is null) return; // oops
1507 	sdpyWindowClassStr[0..v.length+1] = 0;
1508 	sdpyWindowClassStr[0..v.length] = v[];
1509 }
1510 
1511 /**
1512 	Get current window class name.
1513 */
1514 string sdpyWindowClass () {
1515 	if (sdpyWindowClassStr is null) return null;
1516 	foreach (immutable idx; 0..size_t.max-1) {
1517 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1518 	}
1519 	return null;
1520 }
1521 
1522 /++
1523 	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.
1524 
1525 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1526 +/
1527 float[2] getDpi() {
1528 	float[2] dpi;
1529 	version(Windows) {
1530 		HDC screen = GetDC(null);
1531 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1532 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1533 	} else version(X11) {
1534 		auto display = XDisplayConnection.get;
1535 		auto screen = DefaultScreen(display);
1536 
1537 		void fallback() {
1538 			/+
1539 			// 25.4 millimeters in an inch...
1540 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1541 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1542 			+/
1543 
1544 			// the physical size isn't actually as important as the logical size since this is
1545 			// all about scaling really
1546 			dpi[0] = 96;
1547 			dpi[1] = 96;
1548 		}
1549 
1550 		auto xft = getXftDpi();
1551 		if(xft is float.init)
1552 			fallback();
1553 		else {
1554 			dpi[0] = xft;
1555 			dpi[1] = xft;
1556 		}
1557 	}
1558 
1559 	return dpi;
1560 }
1561 
1562 version(X11)
1563 float getXftDpi() {
1564 	auto display = XDisplayConnection.get;
1565 
1566 	char* resourceString = XResourceManagerString(display);
1567 	XrmInitialize();
1568 
1569 	if (resourceString) {
1570 		auto db = XrmGetStringDatabase(resourceString);
1571 		XrmValue value;
1572 		char* type;
1573 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1574 			if (value.addr) {
1575 				import core.stdc.stdlib;
1576 				return atof(cast(char*) value.addr);
1577 			}
1578 		}
1579 	}
1580 
1581 	return float.init;
1582 }
1583 
1584 /++
1585 	Implementation used by [SimpleWindow.takeScreenshot].
1586 
1587 	Params:
1588 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1589 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1590 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1591 		x = the x-offset of the image to capture, from the left.
1592 		y = the y-offset of the image to capture, from the top.
1593 
1594 	History:
1595 		Added on March 14, 2021
1596 
1597 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1598 		
1599 +/
1600 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1601 	TrueColorImage got;
1602 	version(X11) {
1603 		auto display = XDisplayConnection.get;
1604 		if(handle == 0)
1605 			handle = RootWindow(display, DefaultScreen(display));
1606 
1607 		if(width == 0 || height == 0) {
1608 			Window root;
1609 			int xpos, ypos;
1610 			uint widthret, heightret, borderret, depthret;
1611 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1612 
1613 			if(width == 0)
1614 				width = widthret;
1615 			if(height == 0)
1616 				height = heightret;
1617 		}
1618 
1619 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1620 
1621 		// https://github.com/adamdruppe/arsd/issues/98
1622 
1623 		auto i = new Image(image);
1624 		got = i.toTrueColorImage();
1625 
1626 		XDestroyImage(image);
1627 	} else version(Windows) {
1628 		auto hdc = GetDC(handle);
1629 		scope(exit) ReleaseDC(handle, hdc);
1630 
1631 		if(width == 0 || height == 0) {
1632 			BITMAP bmHeader;
1633 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1634 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1635 			if(width == 0)
1636 				width = bmHeader.bmWidth;
1637 			if(height == 0)
1638 				height = bmHeader.bmHeight;
1639 		}
1640 
1641 		auto i = new Image(width, height);
1642 		HDC hdcMem = CreateCompatibleDC(hdc);
1643 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1644 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1645 		SelectObject(hdcMem, hbmOld);
1646 		DeleteDC(hdcMem);
1647 
1648 		got = i.toTrueColorImage();
1649 	} else featureNotImplemented();
1650 
1651 	return got;
1652 }
1653 
1654 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1655 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1656 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1657 
1658 version(Windows)
1659 shared static this() {
1660 	auto lib = LoadLibrary("User32.dll");
1661 	if(lib is null)
1662 		return;
1663 	//scope(exit)
1664 		//FreeLibrary(lib);
1665 
1666 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1667 
1668 	if(SetProcessDpiAwarenessContext is null)
1669 		return;
1670 
1671 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1672 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1673 		//writeln(GetLastError());
1674 	}
1675 
1676 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1677 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1678 }
1679 
1680 /++
1681 	Blocking mode for event loop calls associated with a window instance.
1682 
1683 	History:
1684 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1685 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1686 		is, all would block until the application quit.
1687 
1688 		That behavior can still be achieved here with `untilApplicationQuits`,
1689 		or explicitly calling the top-level `EventLoop.get.run` function.
1690 +/
1691 enum BlockingMode {
1692 	/++
1693 		The event loop call will block until the whole application is ready
1694 		to quit if it is the only one running, but if it is nested inside
1695 		another one, it will only block until the window you're calling it on
1696 		closes.
1697 	+/
1698 	automatic             = 0x00,
1699 	/++
1700 		The event loop call will only return when the whole application
1701 		is ready to quit. This usually means all windows have been closed.
1702 
1703 		This is appropriate for your main application event loop.
1704 	+/
1705 	untilApplicationQuits = 0x01,
1706 	/++
1707 		The event loop will return when the window you're calling it on
1708 		closes. If there are other windows still open, they may be destroyed
1709 		unless you have another event loop running later.
1710 
1711 		This might be appropriate for a modal dialog box loop. Remember that
1712 		other windows are still processing input though, so you can end up
1713 		with a lengthy call stack if this happens in a loop, similar to a
1714 		recursive function (well, it literally is a recursive function, just
1715 		not an obvious looking one).
1716 	+/
1717 	untilWindowCloses     = 0x02,
1718 	/++
1719 		If an event loop is already running, this call will immediately
1720 		return, allowing the existing loop to handle it. If not, this call
1721 		will block until the condition you bitwise-or into the flag.
1722 
1723 		The default is to block until the application quits, same as with
1724 		the `automatic` setting (since if it were nested, which triggers until
1725 		window closes in automatic, this flag would instead not block at all),
1726 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1727 		it will only nest until the window closes. You might want that if you are
1728 		going to open two windows simultaneously and want closing just one of them
1729 		to trigger the event loop return.
1730 	+/
1731 	onlyIfNotNested       = 0x10,
1732 }
1733 
1734 /++
1735 	The flagship window class.
1736 
1737 
1738 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1739 	out of more advanced or complex features of the underlying windowing system.
1740 
1741 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1742 	and get a suitable window to work with.
1743 
1744 	From there, you can opt into additional features, like custom resizability and OpenGL support
1745 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1746 	and customization flags with the final two constructor arguments.
1747 
1748 	If none of that works for you, you can also create a window using native function calls, then
1749 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1750 	though, if you do this, managing the window is still your own responsibility! Notably, you
1751 	will need to destroy it yourself.
1752 +/
1753 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1754 
1755 	/++
1756 		Copies the window's current state into a [TrueColorImage].
1757 
1758 		Be warned: this can be a very slow operation
1759 
1760 		History:
1761 			Actually implemented on March 14, 2021
1762 	+/
1763 	TrueColorImage takeScreenshot() {
1764 		version(Windows)
1765 			return trueColorImageFromNativeHandle(impl.hwnd, width, height);
1766 		else version(OSXCocoa)
1767 			throw new NotYetImplementedException();
1768 		else
1769 			return trueColorImageFromNativeHandle(impl.window, width, height);
1770 	}
1771 
1772 	/++
1773 		Returns the actual logical DPI for the window on its current display monitor. If the window
1774 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1775 
1776 		Please note this function may return zero if it doesn't know the answer!
1777 
1778 
1779 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1780 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1781 
1782 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1783 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1784 		window primarily resides on by checking the center point of the window against the monitor map.
1785 
1786 		Returns:
1787 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1788 			assumes the X and Y dpi are the same.
1789 
1790 		History:
1791 			Added November 26, 2021 (dub v10.4)
1792 
1793 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1794 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1795 			that.
1796 
1797 		Bugs:
1798 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1799 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1800 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1801 			and 1.5 on the secondary monitor.
1802 
1803 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1804 			is a historical misnomer - the real thing of interest is the scale factor and due to
1805 			compatibility concerns the scale would modify dpi values to trick applications. But since
1806 			that's the terminology common out there, I used it too.
1807 
1808 		See_Also:
1809 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1810 			as this since the window many be on a different monitor, but it is a reasonable fallback
1811 			to use if `actualDpi` returns 0.
1812 
1813 			[onDpiChanged] is changed when `actualDpi` has changed.
1814 	+/
1815 	int actualDpi() {
1816 		if(!actualDpiLoadAttempted) {
1817 			// FIXME: do the actual monitor we are on
1818 			// and on X this is a good chance to load the monitor map.
1819 			version(Windows) {
1820 				if(GetDpiForWindow)
1821 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1822 			} else version(X11) {
1823 				if(!xRandrInfoLoadAttemped) {
1824 					xRandrInfoLoadAttemped = true;
1825 					if(!XRandrLibrary.attempted) {
1826 						XRandrLibrary.loadDynamicLibrary();
1827 					}
1828 
1829 					if(XRandrLibrary.loadSuccessful) {
1830 						auto display = XDisplayConnection.get;
1831 						int scratch;
1832 						int major, minor;
1833 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1834 							goto fallback;
1835 
1836 						XRRQueryVersion(display, &major, &minor);
1837 						if(major <= 1 && minor < 5)
1838 							goto fallback;
1839 
1840 						int count;
1841 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1842 						if(monitors is null)
1843 							goto fallback;
1844 						scope(exit) XRRFreeMonitors(monitors);
1845 
1846 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1847 						MonitorInfo.info.assumeSafeAppend();
1848 						foreach(idx, monitor; monitors[0 .. count]) {
1849 							MonitorInfo.info ~= MonitorInfo(
1850 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1851 								Size(monitor.mwidth, monitor.mheight),
1852 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1853 							);
1854 
1855 							/+
1856 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1857 							// unknown physical size, just guess 96 to avoid divide by zero
1858 							MonitorInfo.info ~= MonitorInfo(
1859 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1860 								Size(monitor.mwidth, monitor.mheight),
1861 								96
1862 							);
1863 							else
1864 							// and actual thing
1865 							MonitorInfo.info ~= MonitorInfo(
1866 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1867 								Size(monitor.mwidth, monitor.mheight),
1868 								minInternal(
1869 									// millimeter to int then rounding up.
1870 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1871 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1872 								)
1873 							);
1874 							+/
1875 						}
1876 					// import std.stdio; writeln("Here", MonitorInfo.info);
1877 					}
1878 				}
1879 
1880 				if(XRandrLibrary.loadSuccessful) {
1881 					updateActualDpi(true);
1882 					//import std.stdio; writeln("updated");
1883 
1884 					if(!requestedInput) {
1885 						// this is what requests live updates should the configuration change
1886 						// each time you select input, it sends an initial event, so very important
1887 						// to not get into a loop of selecting input, getting event, updating data,
1888 						// and reselecting input...
1889 						requestedInput = true;
1890 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1891 						//import std.stdio; writeln("requested input");
1892 					}
1893 				} else {
1894 					fallback:
1895 					// make sure we disable events that aren't coming
1896 					xrrEventBase = -1;
1897 					// best guess... respect the custom scaling user command to some extent at least though
1898 					actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1899 				}
1900 			}
1901 			actualDpiLoadAttempted = true;
1902 		}
1903 		return actualDpi_;
1904 	}
1905 
1906 	private int actualDpi_;
1907 	private bool actualDpiLoadAttempted;
1908 
1909 	version(X11) private {
1910 		bool requestedInput;
1911 		static bool xRandrInfoLoadAttemped;
1912 		struct MonitorInfo {
1913 			Rectangle position;
1914 			Size size;
1915 			int dpi;
1916 
1917 			static MonitorInfo[] info;
1918 		}
1919 		bool screenPositionKnown;
1920 		int screenPositionX;
1921 		int screenPositionY;
1922 		void updateActualDpi(bool loadingNow = false) {
1923 			if(!loadingNow && !actualDpiLoadAttempted)
1924 				actualDpi(); // just to make it do the load 
1925 			foreach(idx, m; MonitorInfo.info) {
1926 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1927 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1928 					actualDpi_ = m.dpi;
1929 					//import std.stdio; writeln("monitor ", idx);
1930 					if(changed && onDpiChanged)
1931 						onDpiChanged();
1932 					break;
1933 				}
1934 			}
1935 		}
1936 	}
1937 
1938 	/++
1939 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
1940 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
1941 
1942 		History:
1943 			Added November 26, 2021 (dub v10.4)
1944 
1945 		See_Also:
1946 			[actualDpi]
1947 	+/
1948 	void delegate() onDpiChanged;
1949 
1950 	version(X11) {
1951 		void recreateAfterDisconnect() {
1952 			if(!stateDiscarded) return;
1953 
1954 			if(_parent !is null && _parent.stateDiscarded)
1955 				_parent.recreateAfterDisconnect();
1956 
1957 			bool wasHidden = hidden;
1958 
1959 			activeScreenPainter = null; // should already be done but just to confirm
1960 
1961 			actualDpi_ = 0;
1962 			actualDpiLoadAttempted = false;
1963 			xRandrInfoLoadAttemped = false;
1964 
1965 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1966 
1967 			if(auto dh = dropHandler) {
1968 				dropHandler = null;
1969 				enableDragAndDrop(this, dh);
1970 			}
1971 
1972 			if(recreateAdditionalConnectionState)
1973 				recreateAdditionalConnectionState();
1974 
1975 			hidden = wasHidden;
1976 			stateDiscarded = false;
1977 		}
1978 
1979 		bool stateDiscarded;
1980 		void discardConnectionState() {
1981 			if(XDisplayConnection.display)
1982 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
1983 			if(discardAdditionalConnectionState)
1984 				discardAdditionalConnectionState();
1985 			stateDiscarded = true;
1986 		}
1987 
1988 		void delegate() discardAdditionalConnectionState;
1989 		void delegate() recreateAdditionalConnectionState;
1990 
1991 	}
1992 
1993 	private DropHandler dropHandler;
1994 
1995 	SimpleWindow _parent;
1996 	bool beingOpenKeepsAppOpen = true;
1997 	/++
1998 		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.
1999 
2000 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2001 
2002 		Params:
2003 
2004 		width = the width of the window's client area, in pixels
2005 		height = the height of the window's client area, in pixels
2006 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2007 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2008 		resizable = [Resizability] has three options:
2009 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2010 			$(P `fixedSize` will not allow the user to resize the window.)
2011 			$(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.)
2012 		windowType = The type of window you want to make.
2013 		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.
2014 		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".
2015 	+/
2016 	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) {
2017 		claimGuiThread();
2018 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2019 		this._width = width;
2020 		this._height = height;
2021 		this.openglMode = opengl;
2022 		this.resizability = resizable;
2023 		this.windowType = windowType;
2024 		this.customizationFlags = customizationFlags;
2025 		this._title = (title is null ? "D Application" : title);
2026 		this._parent = parent;
2027 		impl.createWindow(width, height, this._title, opengl, parent);
2028 
2029 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2030 			beingOpenKeepsAppOpen = false;
2031 	}
2032 
2033 	/// ditto
2034 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2035 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2036 	}
2037 
2038 	/// Same as above, except using the `Size` struct instead of separate width and height.
2039 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2040 		this(size.width, size.height, title, opengl, resizable);
2041 	}
2042 
2043 	/// ditto
2044 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2045 		this(size, title, opengl, resizable);
2046 	}
2047 
2048 
2049 	/++
2050 		Creates a window based on the given [Image]. It's client area
2051 		width and height is equal to the image. (A window's client area
2052 		is the drawable space inside; it excludes the title bar, etc.)
2053 
2054 		Windows based on images will not be resizable and do not use OpenGL.
2055 
2056 		It will draw the image in upon creation, but this will be overwritten
2057 		upon any draws, including the initial window visible event.
2058 
2059 		You probably do not want to use this and it may be removed from
2060 		the library eventually, or I might change it to be a "permanent"
2061 		background image; one that is automatically drawn on it before any
2062 		other drawing event. idk.
2063 	+/
2064 	this(Image image, string title = null) {
2065 		this(image.width, image.height, title);
2066 		this.image = image;
2067 	}
2068 
2069 	/++
2070 		Wraps a native window handle with very little additional processing - notably no destruction
2071 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2072 		windows created through the low level API (so you can use platform-specific options and
2073 		other details SimpleWindow does not expose) available to the event loop wrappers.
2074 	+/
2075 	this(NativeWindowHandle nativeWindow) {
2076 		version(Windows)
2077 			impl.hwnd = nativeWindow;
2078 		else version(X11) {
2079 			impl.window = nativeWindow;
2080 			if(nativeWindow)
2081 				display = XDisplayConnection.get(); // get initial display to not segfault
2082 		} else version(OSXCocoa)
2083 			throw new NotYetImplementedException();
2084 		else featureNotImplemented();
2085 		// FIXME: set the size correctly
2086 		_width = 1;
2087 		_height = 1;
2088 		if(nativeWindow)
2089 			nativeMapping[nativeWindow] = this;
2090 
2091 		beingOpenKeepsAppOpen = false;
2092 
2093 		if(nativeWindow)
2094 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2095 		_suppressDestruction = true; // so it doesn't try to close
2096 	}
2097 
2098 	/++
2099 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2100 		The delegate will be called when the window manager asks you to take focus.
2101 
2102 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2103 
2104 		History:
2105 			Added April 1, 2022 (dub v10.8)
2106 	+/
2107 	SimpleWindow delegate() setRequestedInputFocus;
2108 
2109 	/// Experimental, do not use yet
2110 	/++
2111 		Grabs exclusive input from the user until you release it with
2112 		[releaseInputGrab].
2113 
2114 
2115 		Note: it is extremely rude to do this without good reason.
2116 		Reasons may include doing some kind of mouse drag operation
2117 		or popping up a temporary menu that should get events and will
2118 		be dismissed at ease by the user clicking away.
2119 
2120 		Params:
2121 			keyboard = do you want to grab keyboard input?
2122 			mouse = grab mouse input?
2123 			confine = confine the mouse cursor to inside this window?
2124 
2125 		History:
2126 			Prior to March 11, 2021, grabbing the keyboard would always also
2127 			set the X input focus. Now, it only focuses if it is a non-transient
2128 			window and otherwise manages the input direction internally.
2129 
2130 			This means spurious focus/blur events will no longer be sent and the
2131 			application will not steal focus from other applications (which the
2132 			window manager may have rejected anyway).
2133 	+/
2134 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2135 		static if(UsingSimpledisplayX11) {
2136 			XSync(XDisplayConnection.get, 0);
2137 			if(keyboard) {
2138 				if(isTransient && _parent) {
2139 					/*
2140 					FIXME:
2141 						setting the keyboard focus is not actually that helpful, what I more likely want
2142 						is the events from the parent window to be sent over here if we're transient.
2143 					*/
2144 
2145 					_parent.inputProxy = this;
2146 				} else {
2147 					XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
2148 				}
2149 			}
2150 			if(mouse) {
2151 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 
2152 				EventMask.PointerMotionMask // FIXME: not efficient
2153 				| EventMask.ButtonPressMask
2154 				| EventMask.ButtonReleaseMask
2155 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2156 				)
2157 			{
2158 				XSync(XDisplayConnection.get, 0);
2159 				import core.stdc.stdio;
2160 				printf("Grab input failed %d\n", res);
2161 				//throw new Exception("Grab input failed");
2162 			} else {
2163 				// cool
2164 			}
2165 			}
2166 
2167 		} else version(Windows) {
2168 			// FIXME: keyboard?
2169 			SetCapture(impl.hwnd);
2170 			if(confine) {
2171 				RECT rcClip;
2172 				//RECT rcOldClip;
2173 				//GetClipCursor(&rcOldClip); 
2174 				GetWindowRect(hwnd, &rcClip); 
2175 				ClipCursor(&rcClip); 
2176 			}
2177 		} else version(OSXCocoa) {
2178 			throw new NotYetImplementedException();
2179 		} else static assert(0);
2180 	}
2181 
2182 	/++
2183 		Returns the native window.
2184 
2185 		History:
2186 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2187 			to access it through the `impl` member (which is semi-supported
2188 			but platform specific and here it is simple enough to offer an accessor).
2189 
2190 		Bugs:
2191 			Not implemented outside Windows or X11.
2192 	+/
2193 	NativeWindowHandle nativeWindowHandle() {
2194 		version(X11)
2195 			return impl.window;
2196 		else version(Windows)
2197 			return impl.hwnd;
2198 		else
2199 			throw new NotYetImplementedException();
2200 	}
2201 
2202 	private bool isTransient() {
2203 		with(WindowTypes)
2204 		final switch(windowType) {
2205 			case normal, undecorated, eventOnly:
2206 			case nestedChild:
2207 				return (customizationFlags & WindowFlags.transient) ? true : false;
2208 			case dropdownMenu, popupMenu, notification:
2209 				return true;
2210 		}
2211 	}
2212 
2213 	private SimpleWindow inputProxy;
2214 
2215 	/++
2216 		Releases the grab acquired by [grabInput].
2217 	+/
2218 	void releaseInputGrab() {
2219 		static if(UsingSimpledisplayX11) {
2220 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2221 			if(_parent)
2222 				_parent.inputProxy = null;
2223 		} else version(Windows) {
2224 			ReleaseCapture();
2225 			ClipCursor(null); 
2226 		} else version(OSXCocoa) {
2227 			throw new NotYetImplementedException();
2228 		} else static assert(0);
2229 	}
2230 
2231 	/++
2232 		Sets the input focus to this window.
2233 
2234 		You shouldn't call this very often - please let the user control the input focus.
2235 	+/
2236 	void focus() {
2237 		static if(UsingSimpledisplayX11) {
2238 			XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
2239 		} else version(Windows) {
2240 			SetFocus(this.impl.hwnd);
2241 		} else version(OSXCocoa) {
2242 			throw new NotYetImplementedException();
2243 		} else static assert(0);
2244 	}
2245 
2246 	/++
2247 		Requests attention from the user for this window.
2248 
2249 
2250 		The typical result of this function is to change the color
2251 		of the taskbar icon, though it may be tweaked on specific
2252 		platforms.
2253 
2254 		It is meant to unobtrusively tell the user that something
2255 		relevant to them happened in the background and they should
2256 		check the window when they get a chance. Upon receiving the
2257 		keyboard focus, the window will automatically return to its
2258 		natural state.
2259 
2260 		If the window already has the keyboard focus, this function
2261 		may do nothing, because the user is presumed to already be
2262 		giving the window attention.
2263 
2264 		Implementation_note:
2265 
2266 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2267 		atom on X11 and the FlashWindow function on Windows.
2268 	+/
2269 	void requestAttention() {
2270 		if(_focused)
2271 			return;
2272 
2273 		version(Windows) {
2274 			FLASHWINFO info;
2275 			info.cbSize = info.sizeof;
2276 			info.hwnd = impl.hwnd;
2277 			info.dwFlags = FLASHW_TRAY;
2278 			info.uCount = 1;
2279 
2280 			FlashWindowEx(&info);
2281 
2282 		} else version(X11) {
2283 			demandingAttention = true;
2284 			demandAttention(this, true);
2285 		} else version(OSXCocoa) {
2286 			throw new NotYetImplementedException();
2287 		} else static assert(0);
2288 	}
2289 
2290 	private bool _focused;
2291 
2292 	version(X11) private bool demandingAttention;
2293 
2294 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2295 	/// You'll have to call `close()` manually if you set this delegate.
2296 	void delegate () closeQuery;
2297 
2298 	/// This will be called when window visibility was changed.
2299 	void delegate (bool becomesVisible) visibilityChanged;
2300 
2301 	/// This will be called when window becomes visible for the first time.
2302 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2303 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2304 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2305 	private bool _visibleForTheFirstTimeCalled;
2306 	void delegate () visibleForTheFirstTime;
2307 
2308 	/// Returns true if the window has been closed.
2309 	final @property bool closed() { return _closed; }
2310 
2311 	private final @property bool notClosed() { return !_closed; }
2312 
2313 	/// Returns true if the window is focused.
2314 	final @property bool focused() { return _focused; }
2315 
2316 	private bool _visible;
2317 	/// Returns true if the window is visible (mapped).
2318 	final @property bool visible() { return _visible; }
2319 
2320 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2321 	void close() {
2322 		if (!_closed) {
2323 			runInGuiThread( {
2324 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2325 				if (onClosing !is null) onClosing();
2326 				impl.closeWindow();
2327 				_closed = true;
2328 			} );
2329 		}
2330 	}
2331 
2332 	/++
2333 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2334 
2335 		History:
2336 			Overload added on March 7, 2021.
2337 	+/
2338 	void close() shared {
2339 		(cast() this).close();
2340 	}
2341 
2342 	/++
2343 
2344 	+/
2345 	void maximize() {
2346 		version(Windows)
2347 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2348 		else version(X11) {
2349 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2350 
2351 			// also note _NET_WM_STATE_FULLSCREEN
2352 		}
2353 
2354 	}
2355 
2356 	private bool _fullscreen;
2357 
2358 	/// not fully implemented but planned for a future release
2359 	void fullscreen(bool yes) {
2360 		version(X11)
2361 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2362 
2363 		_fullscreen = yes;
2364 
2365 	}
2366 
2367 	bool fullscreen() {
2368 		return _fullscreen;
2369 	}
2370 
2371 	/++
2372 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2373 
2374 	+/
2375 	void minimize() {
2376 		version(Windows)
2377 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2378 		//else version(X11)
2379 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2380 	}
2381 
2382 	/// Alias for `hidden = false`
2383 	void show() {
2384 		hidden = false;
2385 	}
2386 
2387 	/// Alias for `hidden = true`
2388 	void hide() {
2389 		hidden = true;
2390 	}
2391 
2392 	/// Hide cursor when it enters the window.
2393 	void hideCursor() {
2394 		version(OSXCocoa) throw new NotYetImplementedException(); else
2395 		if (!_closed) impl.hideCursor();
2396 	}
2397 
2398 	/// Don't hide cursor when it enters the window.
2399 	void showCursor() {
2400 		version(OSXCocoa) throw new NotYetImplementedException(); else
2401 		if (!_closed) impl.showCursor();
2402 	}
2403 
2404 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2405 	 *
2406 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2407 	 * control. Try to think for other approaches before using this function.
2408 	 *
2409 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2410 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2411 	 *       receive "mouse moved here" event.
2412 	 */
2413 	bool warpMouse (int x, int y) {
2414 		version(X11) {
2415 			if (!_closed) { impl.warpMouse(x, y); return true; }
2416 		} else version(Windows) {
2417 			if (!_closed) {
2418 				POINT point;
2419 				point.x = x;
2420 				point.y = y;
2421 				if(ClientToScreen(impl.hwnd, &point)) {
2422 					SetCursorPos(point.x, point.y);
2423 					return true;
2424 				}
2425 			}
2426 		}
2427 		return false;
2428 	}
2429 
2430 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2431 	void sendDummyEvent () {
2432 		version(X11) {
2433 			if (!_closed) { impl.sendDummyEvent(); }
2434 		}
2435 	}
2436 
2437 	/// Set window minimal size.
2438 	void setMinSize (int minwidth, int minheight) {
2439 		version(OSXCocoa) throw new NotYetImplementedException(); else
2440 		if (!_closed) impl.setMinSize(minwidth, minheight);
2441 	}
2442 
2443 	/// Set window maximal size.
2444 	void setMaxSize (int maxwidth, int maxheight) {
2445 		version(OSXCocoa) throw new NotYetImplementedException(); else
2446 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2447 	}
2448 
2449 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2450 	/// Currently only supported on X11.
2451 	void setResizeGranularity (int granx, int grany) {
2452 		version(OSXCocoa) throw new NotYetImplementedException(); else
2453 		if (!_closed) impl.setResizeGranularity(granx, grany);
2454 	}
2455 
2456 	/// Move window.
2457 	void move(int x, int y) {
2458 		version(OSXCocoa) throw new NotYetImplementedException(); else
2459 		if (!_closed) impl.move(x, y);
2460 	}
2461 
2462 	/// ditto
2463 	void move(Point p) {
2464 		version(OSXCocoa) throw new NotYetImplementedException(); else
2465 		if (!_closed) impl.move(p.x, p.y);
2466 	}
2467 
2468 	/++
2469 		Resize window.
2470 
2471 		Note that the width and height of the window are NOT instantly
2472 		updated - it waits for the window manager to approve the resize
2473 		request, which means you must return to the event loop before the
2474 		width and height are actually changed.
2475 	+/
2476 	void resize(int w, int h) {
2477 		if(!_closed && _fullscreen) fullscreen = false;
2478 		version(OSXCocoa) throw new NotYetImplementedException(); else
2479 		if (!_closed) impl.resize(w, h);
2480 	}
2481 
2482 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2483 	void moveResize (int x, int y, int w, int h) {
2484 		if(!_closed && _fullscreen) fullscreen = false;
2485 		version(OSXCocoa) throw new NotYetImplementedException(); else
2486 		if (!_closed) impl.moveResize(x, y, w, h);
2487 	}
2488 
2489 	private bool _hidden;
2490 
2491 	/// Returns true if the window is hidden.
2492 	final @property bool hidden() {
2493 		return _hidden;
2494 	}
2495 
2496 	/// Shows or hides the window based on the bool argument.
2497 	final @property void hidden(bool b) {
2498 		_hidden = b;
2499 		version(Windows) {
2500 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2501 		} else version(X11) {
2502 			if(b)
2503 				//XUnmapWindow(impl.display, impl.window);
2504 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2505 			else
2506 				XMapWindow(impl.display, impl.window);
2507 		} else version(OSXCocoa) {
2508 			throw new NotYetImplementedException();
2509 		} else static assert(0);
2510 	}
2511 
2512 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2513 	void opacity(double opacity) @property
2514 	in {
2515 		assert(opacity >= 0 && opacity <= 1);
2516 	} do {
2517 		version (Windows) {
2518 			impl.setOpacity(cast(ubyte)(255 * opacity));
2519 		} else version (X11) {
2520 			impl.setOpacity(cast(uint)(uint.max * opacity));
2521 		} else throw new NotYetImplementedException();
2522 	}
2523 
2524 	/++
2525 		Sets your event handlers, without entering the event loop. Useful if you
2526 		have multiple windows - set the handlers on each window, then only do
2527 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2528 
2529 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2530 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2531 		delegate signatures.
2532 	+/
2533 	void setEventHandlers(T...)(T eventHandlers) {
2534 		// FIXME: add more events
2535 		foreach(handler; eventHandlers) {
2536 			static if(__traits(compiles, handleKeyEvent = handler)) {
2537 				handleKeyEvent = handler;
2538 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2539 				handleCharEvent = handler;
2540 			} else static if(__traits(compiles, handlePulse = handler)) {
2541 				handlePulse = handler;
2542 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2543 				handleMouseEvent = handler;
2544 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2545 		}
2546 	}
2547 
2548 	/++
2549 		The event loop automatically returns when the window is closed
2550 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2551 		pulse timer is created. The event loop will block until an event
2552 		arrives or the pulse timer goes off.
2553 
2554 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2555 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2556 		[handleMouseEvent], based on the signature of delegates you provide.
2557 
2558 		Give one with no parameters to set a timer pulse handler. Give one that
2559 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2560 		and one that takes `dchar` for a char event handler. You can use as many
2561 		or as few handlers as you need for your application.
2562 
2563 		History:
2564 			The overload without `pulseTimeout` was added on December 8, 2021.
2565 
2566 			On December 9, 2021, the default blocking mode (which is now configurable
2567 			because [eventLoopWithBlockingMode] was added) switched from
2568 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2569 			should almost never be noticeable to you since the typical simpledisplay
2570 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2571 
2572 		See_Also:
2573 			[eventLoopWithBlockingMode]
2574 	+/
2575 	final int eventLoop(T...)(
2576 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2577 		T eventHandlers) /// delegate list like std.concurrency.receive
2578 	{
2579 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2580 	}
2581 
2582 	/// ditto
2583 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2584 	{
2585 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2586 	}
2587 
2588 	/++
2589 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2590 
2591 		History:
2592 			Added December 8, 2021 (dub v10.5)
2593 
2594 			Previously, this implementation was right inside [eventLoop], but when I wanted
2595 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2596 			just renamed it instead of adding as an overload. Besides, the new name makes it
2597 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2598 
2599 		See_Also:
2600 			[SimpleWindow.eventLoop], [EventLoop]
2601 
2602 		Bugs:
2603 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2604 	+/
2605 	final int eventLoopWithBlockingMode(T...)(
2606 		BlockingMode blockingMode, /// when you want this function to block until
2607 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2608 		T eventHandlers) /// delegate list like std.concurrency.receive
2609 	{
2610 		setEventHandlers(eventHandlers);
2611 
2612 		version(with_eventloop) {
2613 			// delegates event loop to my other module
2614 			version(X11)
2615 				XFlush(display);
2616 
2617 			import arsd.eventloop;
2618 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2619 			scope(exit) clearInterval(handle);
2620 
2621 			loop();
2622 			return 0;
2623 		} else version(OSXCocoa) {
2624 			// FIXME
2625 			if (handlePulse !is null && pulseTimeout != 0) {
2626 				timer = scheduledTimer(pulseTimeout*1e-3,
2627 					view, sel_registerName("simpledisplay_pulse"),
2628 					null, true);
2629 			}
2630 
2631             		setNeedsDisplay(view, true);
2632             		run(NSApp);
2633             		return 0;
2634         	} else {
2635 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2636 
2637 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2638 				return 0;
2639 
2640 			return el.run(
2641 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2642 					null :
2643 					&this.notClosed
2644 			);
2645 		}
2646 	}
2647 
2648 	/++
2649 		This lets you draw on the window (or its backing buffer) using basic
2650 		2D primitives.
2651 
2652 		Be sure to call this in a limited scope because your changes will not
2653 		actually appear on the window until ScreenPainter's destructor runs.
2654 
2655 		Returns: an instance of [ScreenPainter], which has the drawing methods
2656 		on it to draw on this window.
2657 
2658 		Params:
2659 			manualInvalidations = if you set this to true, you will need to
2660 			set the invalid rectangle on the painter yourself. If false, it
2661 			assumes the whole window has been redrawn each time you draw.
2662 
2663 			Only invalidated rectangles are blitted back to the window when
2664 			the destructor runs. Doing this yourself can reduce flickering
2665 			of child windows.
2666 
2667 		History:
2668 			The `manualInvalidations` parameter overload was added on
2669 			December 30, 2021 (dub v10.5)
2670 	+/
2671 	ScreenPainter draw() {
2672 		return draw(false);
2673 	}
2674 	/// ditto
2675 	ScreenPainter draw(bool manualInvalidations) {
2676 		return impl.getPainter(manualInvalidations);
2677 	}
2678 
2679 	// This is here to implement the interface we use for various native handlers.
2680 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2681 
2682 	// maps native window handles to SimpleWindow instances, if there are any
2683 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2684 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2685 
2686 	/// Width of the window's drawable client area, in pixels.
2687 	@scriptable
2688 	final @property int width() const pure nothrow @safe @nogc { return _width; }
2689 
2690 	/// Height of the window's drawable client area, in pixels.
2691 	@scriptable
2692 	final @property int height() const pure nothrow @safe @nogc { return _height; }
2693 
2694 	private int _width;
2695 	private int _height;
2696 
2697 	// HACK: making the best of some copy constructor woes with refcounting
2698 	private ScreenPainterImplementation* activeScreenPainter_;
2699 
2700 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2701 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2702 
2703 	private OpenGlOptions openglMode;
2704 	private Resizability resizability;
2705 	private WindowTypes windowType;
2706 	private int customizationFlags;
2707 
2708 	/// `true` if OpenGL was initialized for this window.
2709 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2710 		version(without_opengl)
2711 			return false;
2712 		else
2713 			return (openglMode == OpenGlOptions.yes);
2714 	}
2715 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2716 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2717 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2718 
2719 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2720 	/// to call this, as it's not recommended to share window between threads.
2721 	void mtLock () {
2722 		version(X11) {
2723 			XLockDisplay(this.display);
2724 		}
2725 	}
2726 
2727 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2728 	/// to call this, as it's not recommended to share window between threads.
2729 	void mtUnlock () {
2730 		version(X11) {
2731 			XUnlockDisplay(this.display);
2732 		}
2733 	}
2734 
2735 	/// Emit a beep to get user's attention.
2736 	void beep () {
2737 		version(X11) {
2738 			XBell(this.display, 100);
2739 		} else version(Windows) {
2740 			MessageBeep(0xFFFFFFFF);
2741 		}
2742 	}
2743 
2744 
2745 
2746 	version(without_opengl) {} else {
2747 
2748 		/// 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`.
2749 		void delegate() redrawOpenGlScene;
2750 
2751 		/// This will allow you to change OpenGL vsync state.
2752 		final @property void vsync (bool wait) {
2753 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2754 		  version(X11) {
2755 		    setAsCurrentOpenGlContext();
2756 		    glxSetVSync(display, impl.window, wait);
2757 		  } else version(Windows) {
2758 		    setAsCurrentOpenGlContext();
2759                     wglSetVSync(wait);
2760 		  }
2761 		}
2762 
2763 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
2764 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
2765 		/// enough without waiting 'em to finish their frame bussiness.
2766 		bool useGLFinish = true;
2767 
2768 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
2769 		/// 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.
2770 		void redrawOpenGlSceneNow() {
2771 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
2772 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2773 			if(redrawOpenGlScene is null)
2774 				return;
2775 
2776 			this.mtLock();
2777 			scope(exit) this.mtUnlock();
2778 
2779 			this.setAsCurrentOpenGlContext();
2780 
2781 			redrawOpenGlScene();
2782 
2783 			this.swapOpenGlBuffers();
2784 			// 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.
2785 			if (useGLFinish) glFinish();
2786 		}
2787 
2788 		private bool redrawOpenGlSceneSoonSet = false;
2789 		private static class RedrawOpenGlSceneEvent {
2790 			SimpleWindow w;
2791 			this(SimpleWindow w) { this.w = w; }
2792 		}
2793 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
2794 		/++
2795 			Queues an opengl redraw as soon as the other pending events are cleared.
2796 		+/
2797 		void redrawOpenGlSceneSoon() {
2798 			if(!redrawOpenGlSceneSoonSet) {
2799 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
2800 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
2801 				redrawOpenGlSceneSoonSet = true;
2802 			}
2803 			this.postEvent(redrawOpenGlSceneEvent, true);
2804 		}
2805 
2806 
2807 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2808 		void setAsCurrentOpenGlContext() {
2809 			assert(openglMode == OpenGlOptions.yes);
2810 			version(X11) {
2811 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
2812 					throw new Exception("glXMakeCurrent");
2813 			} else version(Windows) {
2814 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2815 				if (!wglMakeCurrent(ghDC, ghRC))
2816 					throw new Exception("wglMakeCurrent"); // let windows users suffer too
2817 			}
2818 		}
2819 
2820 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2821 		/// This doesn't throw, returning success flag instead.
2822 		bool setAsCurrentOpenGlContextNT() nothrow {
2823 			assert(openglMode == OpenGlOptions.yes);
2824 			version(X11) {
2825 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
2826 			} else version(Windows) {
2827 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2828 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
2829 			}
2830 		}
2831 
2832 		/// 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.
2833 		/// This doesn't throw, returning success flag instead.
2834 		bool releaseCurrentOpenGlContext() nothrow {
2835 			assert(openglMode == OpenGlOptions.yes);
2836 			version(X11) {
2837 				return (glXMakeCurrent(display, 0, null) != 0);
2838 			} else version(Windows) {
2839 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2840 				return wglMakeCurrent(ghDC, null) ? true : false;
2841 			}
2842 		}
2843 
2844 		/++
2845 			simpledisplay always uses double buffering, usually automatically. This
2846 			manually swaps the OpenGL buffers.
2847 
2848 
2849 			You should not need to call this yourself because simpledisplay will do it
2850 			for you after calling your `redrawOpenGlScene`.
2851 
2852 			Remember that this may throw an exception, which you can catch in a multithreaded
2853 			application to keep your thread from dying from an unhandled exception.
2854 		+/
2855 		void swapOpenGlBuffers() {
2856 			assert(openglMode == OpenGlOptions.yes);
2857 			version(X11) {
2858 				if (!this._visible) return; // no need to do this if window is invisible
2859 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2860 				glXSwapBuffers(display, impl.window);
2861 			} else version(Windows) {
2862 				SwapBuffers(ghDC);
2863 			}
2864 		}
2865 	}
2866 
2867 	/++
2868 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
2869 
2870 
2871 		---
2872 			auto window = new SimpleWindow(100, 100, "First title");
2873 			window.title = "A new title";
2874 		---
2875 
2876 		You may call this function at any time.
2877 	+/
2878 	@property void title(string title) {
2879 		_title = title;
2880 		version(OSXCocoa) throw new NotYetImplementedException(); else
2881 		impl.setTitle(title);
2882 	}
2883 
2884 	private string _title;
2885 
2886 	/// Gets the title
2887 	@property string title() {
2888 		if(_title is null)
2889 			_title = getRealTitle();
2890 		return _title;
2891 	}
2892 
2893 	/++
2894 		Get the title as set by the window manager.
2895 		May not match what you attempted to set.
2896 	+/
2897 	string getRealTitle() {
2898 		static if(is(typeof(impl.getTitle())))
2899 			return impl.getTitle();
2900 		else
2901 			return null;
2902 	}
2903 
2904 	// don't use this generally it is not yet really released
2905 	version(X11)
2906 	@property Image secret_icon() {
2907 		return secret_icon_inner;
2908 	}
2909 	private Image secret_icon_inner;
2910 
2911 
2912 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
2913 	@property void icon(MemoryImage icon) {
2914 		if(icon is null)
2915 			return;
2916 		auto tci = icon.getAsTrueColorImage();
2917 		version(Windows) {
2918 			winIcon = new WindowsIcon(icon);
2919 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
2920 		} else version(X11) {
2921 			secret_icon_inner = Image.fromMemoryImage(icon);
2922 			// FIXME: ensure this is correct
2923 			auto display = XDisplayConnection.get;
2924 			arch_ulong[] buffer;
2925 			buffer ~= icon.width;
2926 			buffer ~= icon.height;
2927 			foreach(c; tci.imageData.colors) {
2928 				arch_ulong b;
2929 				b |= c.a << 24;
2930 				b |= c.r << 16;
2931 				b |= c.g << 8;
2932 				b |= c.b;
2933 				buffer ~= b;
2934 			}
2935 
2936 			XChangeProperty(
2937 				display,
2938 				impl.window,
2939 				GetAtom!("_NET_WM_ICON", true)(display),
2940 				GetAtom!"CARDINAL"(display),
2941 				32 /* bits */,
2942 				0 /*PropModeReplace*/,
2943 				buffer.ptr,
2944 				cast(int) buffer.length);
2945 		} else version(OSXCocoa) {
2946 			throw new NotYetImplementedException();
2947 		} else static assert(0);
2948 	}
2949 
2950 	version(Windows)
2951 		private WindowsIcon winIcon;
2952 
2953 	bool _suppressDestruction;
2954 
2955 	~this() {
2956 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
2957 		if(_suppressDestruction)
2958 			return;
2959 		impl.dispose();
2960 	}
2961 
2962 	private bool _closed;
2963 
2964 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
2965 	/*
2966 	ScreenPainter drawTransiently() {
2967 		return impl.getPainter();
2968 	}
2969 	*/
2970 
2971 	/// Draws an image on the window. This is meant to provide quick look
2972 	/// of a static image generated elsewhere.
2973 	@property void image(Image i) {
2974 	/+
2975 		version(Windows) {
2976 			BITMAP bm;
2977 			HDC hdc = GetDC(hwnd);
2978 			HDC hdcMem = CreateCompatibleDC(hdc);
2979 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
2980 
2981 			GetObject(i.handle, bm.sizeof, &bm);
2982 
2983 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
2984 
2985 			SelectObject(hdcMem, hbmOld);
2986 			DeleteDC(hdcMem);
2987 			ReleaseDC(hwnd, hdc);
2988 
2989 			/*
2990 			RECT r;
2991 			r.right = i.width;
2992 			r.bottom = i.height;
2993 			InvalidateRect(hwnd, &r, false);
2994 			*/
2995 		} else
2996 		version(X11) {
2997 			if(!destroyed) {
2998 				if(i.usingXshm)
2999 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3000 				else
3001 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3002 			}
3003 		} else
3004 		version(OSXCocoa) {
3005 			draw().drawImage(Point(0, 0), i);
3006 			setNeedsDisplay(view, true);
3007 		} else static assert(0);
3008 	+/
3009 		auto painter = this.draw;
3010 		painter.drawImage(Point(0, 0), i);
3011 	}
3012 
3013 	/++
3014 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3015 
3016 		---
3017 		window.cursor = GenericCursor.Help;
3018 		// now the window mouse cursor is set to a generic help
3019 		---
3020 
3021 	+/
3022 	@property void cursor(MouseCursor cursor) {
3023 		version(OSXCocoa)
3024 			featureNotImplemented();
3025 		else
3026 		if(this.impl.curHidden <= 0) {
3027 			static if(UsingSimpledisplayX11) {
3028 				auto ch = cursor.cursorHandle;
3029 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3030 			} else version(Windows) {
3031 				auto ch = cursor.cursorHandle;
3032 				impl.currentCursor = ch;
3033 				SetCursor(ch); // redraw without waiting for mouse movement to update
3034 			} else featureNotImplemented();
3035 		}
3036 
3037 	}
3038 
3039 	/// What follows are the event handlers. These are set automatically
3040 	/// by the eventLoop function, but are still public so you can change
3041 	/// them later. wasPressed == true means key down. false == key up.
3042 
3043 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3044 	void delegate(KeyEvent ke) handleKeyEvent;
3045 
3046 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3047 	void delegate(dchar c) handleCharEvent;
3048 
3049 	/// Handles a timer pulse. Settable through setEventHandlers.
3050 	void delegate() handlePulse;
3051 
3052 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3053 	void delegate(bool) onFocusChange;
3054 
3055 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3056 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3057 	void delegate() onClosing;
3058 
3059 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3060 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3061 	 * last minute cleanup. */
3062 	void delegate() onDestroyed;
3063 
3064 	static if (UsingSimpledisplayX11)
3065 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3066 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3067 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3068 	 *
3069 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3070 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3071 
3072 	//version(Windows)
3073 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3074 
3075 	private {
3076 		int lastMouseX = int.min;
3077 		int lastMouseY = int.min;
3078 		void mdx(ref MouseEvent ev) {
3079 			if(lastMouseX == int.min || lastMouseY == int.min) {
3080 				ev.dx = 0;
3081 				ev.dy = 0;
3082 			} else {
3083 				ev.dx = ev.x - lastMouseX;
3084 				ev.dy = ev.y - lastMouseY;
3085 			}
3086 
3087 			lastMouseX = ev.x;
3088 			lastMouseY = ev.y;
3089 		}
3090 	}
3091 
3092 	/// Mouse event handler. Settable through setEventHandlers.
3093 	void delegate(MouseEvent) handleMouseEvent;
3094 
3095 	/// use to redraw child widgets if you use system apis to add stuff
3096 	void delegate() paintingFinished;
3097 
3098 	void delegate() paintingFinishedDg() {
3099 		return paintingFinished;
3100 	}
3101 
3102 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3103 	/// for this to ever happen.
3104 	void delegate(int width, int height) windowResized;
3105 
3106 	/++
3107 		Platform specific - handle any native message this window gets.
3108 
3109 		Note: this is called *in addition to* other event handlers, unless you either:
3110 
3111 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3112 
3113 		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.
3114 
3115 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3116 
3117 		On X, it takes the form of `int delegate(XEvent)`.
3118 
3119 		History:
3120 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3121 
3122 			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.
3123 	+/
3124 	NativeEventHandler handleNativeEvent_;
3125 
3126 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3127 		return handleNativeEvent_;
3128 	}
3129 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3130 		handleNativeEvent_ = neh;
3131 	}
3132 
3133 	version(Windows)
3134 	// compatibility shim with the old deprecated way
3135 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3136 	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) {
3137 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3138 			auto ret = dg(h, m, w, l);
3139 			if(ret == 0)
3140 				r = 1;
3141 			return ret;
3142 		};
3143 	}
3144 
3145 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3146 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3147 	/// this instead and it will work the same way.
3148 	__gshared NativeEventHandler handleNativeGlobalEvent;
3149 
3150 //  private:
3151 	/// The native implementation is available, but you shouldn't use it unless you are
3152 	/// familiar with the underlying operating system, don't mind depending on it, and
3153 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3154 	/// do what you need to do with handleNativeEvent instead.
3155 	///
3156 	/// This is likely to eventually change to be just a struct holding platform-specific
3157 	/// handles instead of a template mixin at some point because I'm not happy with the
3158 	/// code duplication here (ironically).
3159 	mixin NativeSimpleWindowImplementation!() impl;
3160 
3161 	/**
3162 		This is in-process one-way (from anything to window) event sending mechanics.
3163 		It is thread-safe, so it can be used in multi-threaded applications to send,
3164 		for example, "wake up and repaint" events when thread completed some operation.
3165 		This will allow to avoid using timer pulse to check events with synchronization,
3166 		'cause event handler will be called in UI thread. You can stop guessing which
3167 		pulse frequency will be enough for your app.
3168 		Note that events handlers may be called in arbitrary order, i.e. last registered
3169 		handler can be called first, and vice versa.
3170 	*/
3171 public:
3172 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3173 	 * "spamming" window with events it can't cope with.
3174 	 * It is safe to call this from non-UI threads.
3175 	 */
3176 	@property bool eventQueueEmpty() () {
3177 		synchronized(this) {
3178 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3179 		}
3180 		return true;
3181 	}
3182 
3183 	/** Does our custom event queue contains at least one with the given type?
3184 	 * Can be used in simple cases to prevent "spamming" window with events
3185 	 * it can't cope with.
3186 	 * It is safe to call this from non-UI threads.
3187 	 */
3188 	@property bool eventQueued(ET:Object) () {
3189 		synchronized(this) {
3190 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3191 				if (!o.doProcess) {
3192 					if (cast(ET)(o.evt)) return true;
3193 				}
3194 			}
3195 		}
3196 		return false;
3197 	}
3198 
3199 	/++
3200 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3201 
3202 		History:
3203 			Added May 12, 2021
3204 	+/
3205 	void delegate(Exception e) nothrow eventUncaughtException;
3206 
3207 	/** Add listener for custom event. Can be used like this:
3208 	 *
3209 	 * ---------------------
3210 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3211 	 *   ...
3212 	 *   win.removeEventListener(eid);
3213 	 * ---------------------
3214 	 *
3215 	 * Returns: 0 on failure (should never happen, so ignore it)
3216 	 *
3217 	 * $(WARNING Don't use this method in object destructors!)
3218 	 *
3219 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3220 	 *           'cause if event handler id counter will overflow, you won't be able
3221 	 *           to register any more events.)
3222 	 */
3223 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3224 		if (dg is null) return 0; // ignore empty handlers
3225 		synchronized(this) {
3226 			//FIXME: abort on overflow?
3227 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3228 			EventHandlerEntry e;
3229 			e.dg = delegate (Object o) {
3230 				if (auto co = cast(ET)o) {
3231 					try {
3232 						dg(co);
3233 					} catch (Exception e) {
3234 						// sorry!
3235 						if(eventUncaughtException)
3236 							eventUncaughtException(e);
3237 					}
3238 					return true;
3239 				}
3240 				return false;
3241 			};
3242 			e.id = lastUsedHandlerId;
3243 			auto optr = eventHandlers.ptr;
3244 			eventHandlers ~= e;
3245 			if (eventHandlers.ptr !is optr) {
3246 				import core.memory : GC;
3247 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3248 			}
3249 			return lastUsedHandlerId;
3250 		}
3251 	}
3252 
3253 	/// Remove event listener. It is safe to pass invalid event id here.
3254 	/// $(WARNING Don't use this method in object destructors!)
3255 	void removeEventListener() (uint id) {
3256 		if (id == 0 || id > lastUsedHandlerId) return;
3257 		synchronized(this) {
3258 			foreach (immutable idx; 0..eventHandlers.length) {
3259 				if (eventHandlers[idx].id == id) {
3260 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3261 					eventHandlers[$-1].dg = null;
3262 					eventHandlers.length -= 1;
3263 					eventHandlers.assumeSafeAppend;
3264 					return;
3265 				}
3266 			}
3267 		}
3268 	}
3269 
3270 	/// Post event to queue. It is safe to call this from non-UI threads.
3271 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3272 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3273 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3274 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3275 		if (this.closed) return false; // closed windows can't handle events
3276 
3277 		// remove all events of type `ET`
3278 		void removeAllET () {
3279 			uint eidx = 0, ec = eventQueueUsed;
3280 			auto eptr = eventQueue.ptr;
3281 			while (eidx < ec) {
3282 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3283 				if (cast(ET)eptr.evt !is null) {
3284 					// i found her!
3285 					if (inCustomEventProcessor) {
3286 						// if we're in custom event processing loop, processor will clear it for us
3287 						eptr.evt = null;
3288 						++eidx;
3289 						++eptr;
3290 					} else {
3291 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3292 						ec = --eventQueueUsed;
3293 						// clear last event (it is already copied)
3294 						eventQueue.ptr[ec].evt = null;
3295 					}
3296 				} else {
3297 					++eidx;
3298 					++eptr;
3299 				}
3300 			}
3301 		}
3302 
3303 		if (evt is null) {
3304 			if (replace) { synchronized(this) removeAllET(); }
3305 			// ignore empty events, they can't be handled anyway
3306 			return false;
3307 		}
3308 
3309 		// add events even if no event FD/event object created yet
3310 		synchronized(this) {
3311 			if (replace) removeAllET();
3312 			if (eventQueueUsed == uint.max) return false; // just in case
3313 			if (eventQueueUsed < eventQueue.length) {
3314 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3315 			} else {
3316 				if (eventQueue.capacity == eventQueue.length) {
3317 					// need to reallocate; do a trick to ensure that old array is cleared
3318 					auto oarr = eventQueue;
3319 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3320 					// just in case, do yet another check
3321 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3322 					import core.memory : GC;
3323 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3324 				} else {
3325 					auto optr = eventQueue.ptr;
3326 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3327 					assert(eventQueue.ptr is optr);
3328 				}
3329 				++eventQueueUsed;
3330 				assert(eventQueueUsed == eventQueue.length);
3331 			}
3332 			if (!eventWakeUp()) {
3333 				// can't wake up event processor, so there is no reason to keep the event
3334 				assert(eventQueueUsed > 0);
3335 				eventQueue[--eventQueueUsed].evt = null;
3336 				return false;
3337 			}
3338 			return true;
3339 		}
3340 	}
3341 
3342 	/// Post event to queue. It is safe to call this from non-UI threads.
3343 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3344 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3345 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3346 		return postTimeout!ET(evt, 0, replace);
3347 	}
3348 
3349 private:
3350 	private import core.time : MonoTime;
3351 
3352 	version(Posix) {
3353 		__gshared int customEventFDRead = -1;
3354 		__gshared int customEventFDWrite = -1;
3355 		__gshared int customSignalFD = -1;
3356 	} else version(Windows) {
3357 		__gshared HANDLE customEventH = null;
3358 	}
3359 
3360 	// wake up event processor
3361 	static bool eventWakeUp () {
3362 		version(X11) {
3363 			import core.sys.posix.unistd : write;
3364 			ulong n = 1;
3365 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3366 			return true;
3367 		} else version(Windows) {
3368 			if (customEventH !is null) SetEvent(customEventH);
3369 			return true;
3370 		} else {
3371 			// not implemented for other OSes
3372 			return false;
3373 		}
3374 	}
3375 
3376 	static struct QueuedEvent {
3377 		Object evt;
3378 		bool timed = false;
3379 		MonoTime hittime = MonoTime.zero;
3380 		bool doProcess = false; // process event at the current iteration (internal flag)
3381 
3382 		this (Object aevt, uint toutmsecs) {
3383 			evt = aevt;
3384 			if (toutmsecs > 0) {
3385 				import core.time : msecs;
3386 				timed = true;
3387 				hittime = MonoTime.currTime+toutmsecs.msecs;
3388 			}
3389 		}
3390 	}
3391 
3392 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3393 	static struct EventHandlerEntry {
3394 		CustomEventHandler dg;
3395 		uint id;
3396 	}
3397 
3398 	uint lastUsedHandlerId;
3399 	EventHandlerEntry[] eventHandlers;
3400 	QueuedEvent[] eventQueue = null;
3401 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3402 	bool inCustomEventProcessor = false; // required to properly remove events
3403 
3404 	// process queued events and call custom event handlers
3405 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3406 	void processCustomEvents () {
3407 		bool hasSomethingToDo = false;
3408 		uint ecount;
3409 		bool ocep;
3410 		synchronized(this) {
3411 			ocep = inCustomEventProcessor;
3412 			inCustomEventProcessor = true;
3413 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3414 			auto ctt = MonoTime.currTime;
3415 			bool hasEmpty = false;
3416 			// mark events to process (this is required for `eventQueued()`)
3417 			foreach (ref qe; eventQueue[0..ecount]) {
3418 				if (qe.evt is null) { hasEmpty = true; continue; }
3419 				if (qe.timed) {
3420 					qe.doProcess = (qe.hittime <= ctt);
3421 				} else {
3422 					qe.doProcess = true;
3423 				}
3424 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3425 			}
3426 			if (!hasSomethingToDo) {
3427 				// remove empty events
3428 				if (hasEmpty) {
3429 					uint eidx = 0, ec = eventQueueUsed;
3430 					auto eptr = eventQueue.ptr;
3431 					while (eidx < ec) {
3432 						if (eptr.evt is null) {
3433 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3434 							ec = --eventQueueUsed;
3435 							eventQueue.ptr[ec].evt = null; // make GC life easier
3436 						} else {
3437 							++eidx;
3438 							++eptr;
3439 						}
3440 					}
3441 				}
3442 				inCustomEventProcessor = ocep;
3443 				return;
3444 			}
3445 		}
3446 		// process marked events
3447 		uint efree = 0; // non-processed events will be put at this index
3448 		EventHandlerEntry[] eh;
3449 		Object evt;
3450 		foreach (immutable eidx; 0..ecount) {
3451 			synchronized(this) {
3452 				if (!eventQueue[eidx].doProcess) {
3453 					// skip this event
3454 					assert(efree <= eidx);
3455 					if (efree != eidx) {
3456 						// copy this event to queue start
3457 						eventQueue[efree] = eventQueue[eidx];
3458 						eventQueue[eidx].evt = null; // just in case
3459 					}
3460 					++efree;
3461 					continue;
3462 				}
3463 				evt = eventQueue[eidx].evt;
3464 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3465 				if (evt is null) continue; // just in case
3466 				// try all handlers; this can be slow, but meh...
3467 				eh = eventHandlers;
3468 			}
3469 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3470 			evt = null;
3471 			eh = null;
3472 		}
3473 		synchronized(this) {
3474 			// move all unprocessed events to queue top; efree holds first "free index"
3475 			foreach (immutable eidx; ecount..eventQueueUsed) {
3476 				assert(efree <= eidx);
3477 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3478 				++efree;
3479 			}
3480 			eventQueueUsed = efree;
3481 			// wake up event processor on next event loop iteration if we have more queued events
3482 			// also, remove empty events
3483 			bool awaken = false;
3484 			uint eidx = 0, ec = eventQueueUsed;
3485 			auto eptr = eventQueue.ptr;
3486 			while (eidx < ec) {
3487 				if (eptr.evt is null) {
3488 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3489 					ec = --eventQueueUsed;
3490 					eventQueue.ptr[ec].evt = null; // make GC life easier
3491 				} else {
3492 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3493 					++eidx;
3494 					++eptr;
3495 				}
3496 			}
3497 			inCustomEventProcessor = ocep;
3498 		}
3499 	}
3500 
3501 	// for all windows in nativeMapping
3502 	package static void processAllCustomEvents () {
3503 
3504 		cleanupQueue.process();
3505 
3506 		justCommunication.processCustomEvents();
3507 
3508 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3509 			if (sw is null || sw.closed) continue;
3510 			sw.processCustomEvents();
3511 		}
3512 
3513 		runPendingRunInGuiThreadDelegates();
3514 	}
3515 
3516 	// 0: infinite (i.e. no scheduled events in queue)
3517 	uint eventQueueTimeoutMSecs () {
3518 		synchronized(this) {
3519 			if (eventQueueUsed == 0) return 0;
3520 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3521 			uint res = int.max;
3522 			auto ctt = MonoTime.currTime;
3523 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3524 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3525 				if (qe.doProcess) continue; // just in case
3526 				if (!qe.timed) return 1; // minimal
3527 				if (qe.hittime <= ctt) return 1; // minimal
3528 				auto tms = (qe.hittime-ctt).total!"msecs";
3529 				if (tms < 1) tms = 1; // safety net
3530 				if (tms >= int.max) tms = int.max-1; // and another safety net
3531 				if (res > tms) res = cast(uint)tms;
3532 			}
3533 			return (res >= int.max ? 0 : res);
3534 		}
3535 	}
3536 
3537 	// for all windows in nativeMapping
3538 	static uint eventAllQueueTimeoutMSecs () {
3539 		uint res = uint.max;
3540 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3541 			if (sw is null || sw.closed) continue;
3542 			uint to = sw.eventQueueTimeoutMSecs();
3543 			if (to && to < res) {
3544 				res = to;
3545 				if (to == 1) break; // can't have less than this
3546 			}
3547 		}
3548 		return (res >= int.max ? 0 : res);
3549 	}
3550 
3551 	version(X11) {
3552 		ResizeEvent pendingResizeEvent;
3553 	}
3554 }
3555 
3556 /++
3557 	Magic pseudo-window for just posting events to a global queue.
3558 
3559 	Not entirely supported, I might delete it at any time.
3560 
3561 	Added Nov 5, 2021.
3562 +/
3563 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init);
3564 
3565 /* Drag and drop support { */
3566 version(X11) {
3567 
3568 } else version(Windows) {
3569 	import core.sys.windows.uuid;
3570 	import core.sys.windows.ole2;
3571 	import core.sys.windows.oleidl;
3572 	import core.sys.windows.objidl;
3573 	import core.sys.windows.wtypes;
3574 
3575 	pragma(lib, "ole32");
3576 	void initDnd() {
3577 		auto err = OleInitialize(null);
3578 		if(err != S_OK && err != S_FALSE)
3579 			throw new Exception("init");//err);
3580 	}
3581 }
3582 /* } End drag and drop support */
3583 
3584 
3585 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3586 /// See [GenericCursor].
3587 class MouseCursor {
3588 	int osId;
3589 	bool isStockCursor;
3590 	private this(int osId) {
3591 		this.osId = osId;
3592 		this.isStockCursor = true;
3593 	}
3594 
3595 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3596 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3597 
3598 	version(Windows) {
3599 		HCURSOR cursor_;
3600 		HCURSOR cursorHandle() {
3601 			if(cursor_ is null)
3602 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3603 			return cursor_;
3604 		}
3605 
3606 	} else static if(UsingSimpledisplayX11) {
3607 		Cursor cursor_ = None;
3608 		int xDisplaySequence;
3609 
3610 		Cursor cursorHandle() {
3611 			if(this.osId == None)
3612 				return None;
3613 
3614 			// we need to reload if we on a new X connection
3615 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3616 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3617 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3618 			}
3619 			return cursor_;
3620 		}
3621 	}
3622 }
3623 
3624 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3625 // https://tronche.com/gui/x/xlib/appendix/b/
3626 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3627 /// 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.
3628 enum GenericCursorType {
3629 	Default, /// The default arrow pointer.
3630 	Wait, /// A cursor indicating something is loading and the user must wait.
3631 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3632 	Help, /// A cursor indicating the user can get help about the pointer location.
3633 	Cross, /// A crosshair.
3634 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3635 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3636 	UpArrow, /// An arrow pointing straight up.
3637 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3638 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3639 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3640 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3641 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3642 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3643 
3644 }
3645 
3646 /*
3647 	X_plus == css cell == Windows ?
3648 */
3649 
3650 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3651 static struct GenericCursor {
3652 	static:
3653 	///
3654 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3655 		static MouseCursor mc;
3656 
3657 		auto type = __traits(getMember, GenericCursorType, str);
3658 
3659 		if(mc is null) {
3660 
3661 			version(Windows) {
3662 				int osId;
3663 				final switch(type) {
3664 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3665 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3666 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3667 					case GenericCursorType.Help: osId = IDC_HELP; break;
3668 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3669 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3670 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3671 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3672 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3673 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3674 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3675 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3676 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3677 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3678 				}
3679 			} else static if(UsingSimpledisplayX11) {
3680 				int osId;
3681 				final switch(type) {
3682 					case GenericCursorType.Default: osId = None; break;
3683 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3684 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3685 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3686 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3687 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3688 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3689 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3690 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3691 
3692 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3693 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3694 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3695 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3696 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3697 				}
3698 
3699 			} else featureNotImplemented();
3700 
3701 			mc = new MouseCursor(osId);
3702 		}
3703 		return mc;
3704 	}
3705 }
3706 
3707 
3708 /++
3709 	If you want to get more control over the event loop, you can use this.
3710 
3711 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
3712 	to `EventLoop.get.run`.
3713 +/
3714 struct EventLoop {
3715 	@disable this();
3716 
3717 	/// Gets a reference to an existing event loop
3718 	static EventLoop get() {
3719 		return EventLoop(0, null);
3720 	}
3721 
3722 	static void quitApplication() {
3723 		EventLoop.get().exit();
3724 	}
3725 
3726 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3727 
3728 	/// Construct an application-global event loop for yourself
3729 	/// See_Also: [SimpleWindow.setEventHandlers]
3730 	this(long pulseTimeout, void delegate() handlePulse) {
3731 		synchronized(monitor) {
3732 			if(impl is null) {
3733 				claimGuiThread();
3734 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3735 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
3736 			} else {
3737 				if(pulseTimeout) {
3738 					impl.pulseTimeout = pulseTimeout;
3739 					impl.handlePulse = handlePulse;
3740 				}
3741 			}
3742 			impl.refcount++;
3743 		}
3744 	}
3745 
3746 	~this() {
3747 		if(impl is null)
3748 			return;
3749 		impl.refcount--;
3750 		if(impl.refcount == 0) {
3751 			impl.dispose();
3752 			if(thisIsGuiThread)
3753 				guiThreadFinalize();
3754 		}
3755 
3756 	}
3757 
3758 	this(this) {
3759 		if(impl is null)
3760 			return;
3761 		impl.refcount++;
3762 	}
3763 
3764 	/// Runs the event loop until the whileCondition, if present, returns false
3765 	int run(bool delegate() whileCondition = null) {
3766 		assert(impl !is null);
3767 		impl.notExited = true;
3768 		return impl.run(whileCondition);
3769 	}
3770 
3771 	/// Exits the event loop
3772 	void exit() {
3773 		assert(impl !is null);
3774 		impl.notExited = false;
3775 	}
3776 
3777 	version(linux)
3778 	ref void delegate(int) signalHandler() {
3779 		assert(impl !is null);
3780 		return impl.signalHandler;
3781 	}
3782 
3783 	__gshared static EventLoopImpl* impl;
3784 }
3785 
3786 version(linux)
3787 	void delegate(int, int) globalHupHandler;
3788 
3789 version(Posix)
3790 	void makeNonBlocking(int fd) {
3791 		import fcntl = core.sys.posix.fcntl;
3792 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
3793 		if(flags == -1)
3794 			throw new Exception("fcntl get");
3795 		flags |= fcntl.O_NONBLOCK;
3796 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
3797 		if(s == -1)
3798 			throw new Exception("fcntl set");
3799 	}
3800 
3801 struct EventLoopImpl {
3802 	int refcount;
3803 
3804 	bool notExited = true;
3805 
3806 	version(linux) {
3807 		static import ep = core.sys.linux.epoll;
3808 		static import unix = core.sys.posix.unistd;
3809 		static import err = core.stdc.errno;
3810 		import core.sys.linux.timerfd;
3811 
3812 		void delegate(int) signalHandler;
3813 	}
3814 
3815 	version(X11) {
3816 		int pulseFd = -1;
3817 		version(linux) ep.epoll_event[16] events = void;
3818 	} else version(Windows) {
3819 		Timer pulser;
3820 		HANDLE[] handles;
3821 	}
3822 
3823 
3824 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3825 	/// to call this, as it's not recommended to share window between threads.
3826 	void mtLock () {
3827 		version(X11) {
3828 			XLockDisplay(this.display);
3829 		}
3830 	}
3831 
3832 	version(X11)
3833 	auto display() { return XDisplayConnection.get; }
3834 
3835 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3836 	/// to call this, as it's not recommended to share window between threads.
3837 	void mtUnlock () {
3838 		version(X11) {
3839 			XUnlockDisplay(this.display);
3840 		}
3841 	}
3842 
3843 	version(with_eventloop)
3844 	void initialize(long pulseTimeout) {}
3845 	else
3846 	void initialize(long pulseTimeout) {
3847 		version(Windows) {
3848 			if(pulseTimeout && handlePulse !is null)
3849 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
3850 
3851 			if (customEventH is null) {
3852 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
3853 				if (customEventH !is null) {
3854 					handles ~= customEventH;
3855 				} else {
3856 					// this is something that should not be; better be safe than sorry
3857 					throw new Exception("can't create eventfd for custom event processing");
3858 				}
3859 			}
3860 
3861 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
3862 		}
3863 
3864 		version(linux) {
3865 			prepareEventLoop();
3866 			{
3867 				auto display = XDisplayConnection.get;
3868 				// adding Xlib file
3869 				ep.epoll_event ev = void;
3870 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3871 				ev.events = ep.EPOLLIN;
3872 				ev.data.fd = display.fd;
3873 				//import std.conv;
3874 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
3875 					throw new Exception("add x fd");// ~ to!string(epollFd));
3876 				displayFd = display.fd;
3877 			}
3878 
3879 			if(pulseTimeout && handlePulse !is null) {
3880 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
3881 				if(pulseFd == -1)
3882 					throw new Exception("pulse timer create failed");
3883 
3884 				itimerspec value;
3885 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
3886 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3887 
3888 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
3889 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3890 
3891 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
3892 					throw new Exception("couldn't make pulse timer");
3893 
3894 				ep.epoll_event ev = void;
3895 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3896 				ev.events = ep.EPOLLIN;
3897 				ev.data.fd = pulseFd;
3898 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
3899 			}
3900 
3901 			// eventfd for custom events
3902 			if (customEventFDWrite == -1) {
3903 				customEventFDWrite = eventfd(0, 0);
3904 				customEventFDRead = customEventFDWrite;
3905 				if (customEventFDRead >= 0) {
3906 					ep.epoll_event ev = void;
3907 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3908 					ev.events = ep.EPOLLIN;
3909 					ev.data.fd = customEventFDRead;
3910 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
3911 				} else {
3912 					// this is something that should not be; better be safe than sorry
3913 					throw new Exception("can't create eventfd for custom event processing");
3914 				}
3915 			}
3916 
3917 			if (customSignalFD == -1) {
3918 				import core.sys.linux.sys.signalfd;
3919 
3920 				sigset_t sigset;
3921 				auto err = sigemptyset(&sigset);
3922 				assert(!err);
3923 				err = sigaddset(&sigset, SIGINT);
3924 				assert(!err);
3925 				err = sigaddset(&sigset, SIGHUP);
3926 				assert(!err);
3927 				err = sigprocmask(SIG_BLOCK, &sigset, null);
3928 				assert(!err);
3929 
3930 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
3931 				assert(customSignalFD != -1);
3932 
3933 				ep.epoll_event ev = void;
3934 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3935 				ev.events = ep.EPOLLIN;
3936 				ev.data.fd = customSignalFD;
3937 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
3938 			}
3939 		} else version(Posix) {
3940 			prepareEventLoop();
3941 			if (customEventFDRead == -1) {
3942 				int[2] bfr;
3943 				import core.sys.posix.unistd;
3944 				auto ret = pipe(bfr);
3945 				if(ret == -1) throw new Exception("pipe");
3946 				customEventFDRead = bfr[0];
3947 				customEventFDWrite = bfr[1];
3948 			}
3949 
3950 		}
3951 
3952 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
3953 
3954 		version(linux) {
3955 			this.mtLock();
3956 			scope(exit) this.mtUnlock();
3957 			XPending(display); // no, really
3958 		}
3959 
3960 		disposed = false;
3961 	}
3962 
3963 	bool disposed = true;
3964 	version(X11)
3965 		int displayFd = -1;
3966 
3967 	version(with_eventloop)
3968 	void dispose() {}
3969 	else
3970 	void dispose() {
3971 		disposed = true;
3972 		version(X11) {
3973 			if(pulseFd != -1) {
3974 				import unix = core.sys.posix.unistd;
3975 				unix.close(pulseFd);
3976 				pulseFd = -1;
3977 			}
3978 
3979 				version(linux)
3980 				if(displayFd != -1) {
3981 					// 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
3982 					ep.epoll_event ev = void;
3983 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3984 					ev.events = ep.EPOLLIN;
3985 					ev.data.fd = displayFd;
3986 					//import std.conv;
3987 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
3988 					displayFd = -1;
3989 				}
3990 
3991 		} else version(Windows) {
3992 			if(pulser !is null) {
3993 				pulser.destroy();
3994 				pulser = null;
3995 			}
3996 			if (customEventH !is null) {
3997 				CloseHandle(customEventH);
3998 				customEventH = null;
3999 			}
4000 		}
4001 	}
4002 
4003 	this(long pulseTimeout, void delegate() handlePulse) {
4004 		this.pulseTimeout = pulseTimeout;
4005 		this.handlePulse = handlePulse;
4006 		initialize(pulseTimeout);
4007 	}
4008 
4009 	private long pulseTimeout;
4010 	void delegate() handlePulse;
4011 
4012 	~this() {
4013 		dispose();
4014 	}
4015 
4016 	version(Posix)
4017 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4018 	version(Posix)
4019 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4020 	version(linux)
4021 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4022 	version(Windows)
4023 	ref auto customEventH() { return SimpleWindow.customEventH; }
4024 
4025 	version(with_eventloop) {
4026 		int loopHelper(bool delegate() whileCondition) {
4027 			// FIXME: whileCondition
4028 			import arsd.eventloop;
4029 			loop();
4030 			return 0;
4031 		}
4032 	} else
4033 	int loopHelper(bool delegate() whileCondition) {
4034 		version(X11) {
4035 			bool done = false;
4036 
4037 			XFlush(display);
4038 			insideXEventLoop = true;
4039 			scope(exit) insideXEventLoop = false;
4040 
4041 			version(linux) {
4042 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4043 					bool forceXPending = false;
4044 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4045 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4046 					{
4047 						this.mtLock();
4048 						scope(exit) this.mtUnlock();
4049 						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
4050 					}
4051 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4052 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4053 					if(nfds == -1) {
4054 						if(err.errno == err.EINTR) {
4055 							//if(forceXPending) goto xpending;
4056 							continue; // interrupted by signal, just try again
4057 						}
4058 						throw new Exception("epoll wait failure");
4059 					}
4060 
4061 					SimpleWindow.processAllCustomEvents(); // anyway
4062 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4063 					foreach(idx; 0 .. nfds) {
4064 						if(done) break;
4065 						auto fd = events[idx].data.fd;
4066 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4067 						auto flags = events[idx].events;
4068 						if(flags & ep.EPOLLIN) {
4069 							if (fd == customSignalFD) {
4070 								version(linux) {
4071 									import core.sys.linux.sys.signalfd;
4072 									import core.sys.posix.unistd : read;
4073 									signalfd_siginfo info;
4074 									read(customSignalFD, &info, info.sizeof);
4075 
4076 									auto sig = info.ssi_signo;
4077 
4078 									if(EventLoop.get.signalHandler !is null) {
4079 										EventLoop.get.signalHandler()(sig);
4080 									} else {
4081 										EventLoop.get.exit();
4082 									}
4083 								}
4084 							} else if(fd == display.fd) {
4085 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
4086 								this.mtLock();
4087 								scope(exit) this.mtUnlock();
4088 								while(!done && XPending(display)) {
4089 									done = doXNextEvent(this.display);
4090 								}
4091 								forceXPending = false;
4092 							} else if(fd == pulseFd) {
4093 								long expirationCount;
4094 								// 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...
4095 
4096 								handlePulse();
4097 
4098 								// read just to clear the buffer so poll doesn't trigger again
4099 								// BTW I read AFTER the pulse because if the pulse handler takes
4100 								// a lot of time to execute, we don't want the app to get stuck
4101 								// in a loop of timer hits without a chance to do anything else
4102 								//
4103 								// IOW handlePulse happens at most once per pulse interval.
4104 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4105 								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
4106 							} else if (fd == customEventFDRead) {
4107 								// we have some custom events; process 'em
4108 								import core.sys.posix.unistd : read;
4109 								ulong n;
4110 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4111 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4112 								//SimpleWindow.processAllCustomEvents();
4113 							} else {
4114 								// some other timer
4115 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
4116 
4117 								if(Timer* t = fd in Timer.mapping)
4118 									(*t).trigger();
4119 
4120 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4121 									(*pfr).ready(flags);
4122 
4123 								// or i might add support for other FDs too
4124 								// but for now it is just timer
4125 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4126 							}
4127 						}
4128 						if(flags & ep.EPOLLHUP) {
4129 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4130 								(*pfr).hup(flags);
4131 							if(globalHupHandler)
4132 								globalHupHandler(fd, flags);
4133 						}
4134 						/+
4135 						} else {
4136 							// not interested in OUT, we are just reading here.
4137 							//
4138 							// error or hup might also be reported
4139 							// but it shouldn't here since we are only
4140 							// using a few types of FD and Xlib will report
4141 							// if it dies.
4142 							// so instead of thoughtfully handling it, I'll
4143 							// just throw. for now at least
4144 
4145 							throw new Exception("epoll did something else");
4146 						}
4147 						+/
4148 					}
4149 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4150 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4151 					xpending:
4152 					if (!done && forceXPending) {
4153 						this.mtLock();
4154 						scope(exit) this.mtUnlock();
4155 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4156 						while(!done && XPending(display)) {
4157 							done = doXNextEvent(this.display);
4158 						}
4159 					}
4160 				}
4161 			} else {
4162 				// Generic fallback: yes to simple pulse support,
4163 				// but NO timer support!
4164 
4165 				// FIXME: we could probably support the POSIX timer_create
4166 				// signal-based option, but I'm in no rush to write it since
4167 				// I prefer the fd-based functions.
4168 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4169 
4170 					import core.sys.posix.poll;
4171 
4172 					pollfd[] pfds;
4173 					pollfd[32] pfdsBuffer;
4174 					auto len = PosixFdReader.mapping.length + 2;
4175 					// FIXME: i should just reuse the buffer
4176 					if(len < pfdsBuffer.length)
4177 						pfds = pfdsBuffer[0 .. len];
4178 					else
4179 						pfds = new pollfd[](len);
4180 
4181 					pfds[0].fd = display.fd;
4182 					pfds[0].events = POLLIN;
4183 					pfds[0].revents = 0;
4184 
4185 					int slot = 1;
4186 
4187 					if(customEventFDRead != -1) {
4188 						pfds[slot].fd = customEventFDRead;
4189 						pfds[slot].events = POLLIN;
4190 						pfds[slot].revents = 0;
4191 
4192 						slot++;
4193 					}
4194 
4195 					foreach(fd, obj; PosixFdReader.mapping) {
4196 						if(!obj.enabled) continue;
4197 						pfds[slot].fd = fd;
4198 						pfds[slot].events = POLLIN;
4199 						pfds[slot].revents = 0;
4200 
4201 						slot++;
4202 					}
4203 
4204 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4205 					if(ret == -1) throw new Exception("poll");
4206 
4207 					if(ret == 0) {
4208 						// FIXME it may not necessarily time out if events keep coming
4209 						if(handlePulse !is null)
4210 							handlePulse();
4211 					} else {
4212 						foreach(s; 0 .. slot) {
4213 							if(pfds[s].revents == 0) continue;
4214 
4215 							if(pfds[s].fd == display.fd) {
4216 								while(!done && XPending(display)) {
4217 									this.mtLock();
4218 									scope(exit) this.mtUnlock();
4219 									done = doXNextEvent(this.display);
4220 								}
4221 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4222 
4223 								import core.sys.posix.unistd : read;
4224 								ulong n;
4225 								read(customEventFDRead, &n, n.sizeof);
4226 								SimpleWindow.processAllCustomEvents();
4227 							} else {
4228 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4229 								if(pfds[s].revents & POLLNVAL) {
4230 									obj.dispose();
4231 								} else {
4232 									obj.ready(pfds[s].revents);
4233 								}
4234 							}
4235 
4236 							ret--;
4237 							if(ret == 0) break;
4238 						}
4239 					}
4240 				}
4241 			}
4242 		}
4243 		
4244 		version(Windows) {
4245 			int ret = -1;
4246 			MSG message;
4247 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4248 				eventLoopRound++;
4249 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4250 				auto waitResult = MsgWaitForMultipleObjectsEx(
4251 					cast(int) handles.length, handles.ptr,
4252 					(wto == 0 ? INFINITE : wto), /* timeout */
4253 					0x04FF, /* QS_ALLINPUT */
4254 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4255 
4256 				SimpleWindow.processAllCustomEvents(); // anyway
4257 				enum WAIT_OBJECT_0 = 0;
4258 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4259 					auto h = handles[waitResult - WAIT_OBJECT_0];
4260 					if(auto e = h in WindowsHandleReader.mapping) {
4261 						(*e).ready();
4262 					}
4263 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4264 					// message ready
4265 					int count;
4266 					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
4267 						ret = GetMessage(&message, null, 0, 0);
4268 						if(ret == -1)
4269 							throw new Exception("GetMessage failed");
4270 						TranslateMessage(&message);
4271 						DispatchMessage(&message);
4272 
4273 						count++;
4274 						if(count > 10)
4275 							break; // take the opportunity to catch up on other events
4276 
4277 						if(ret == 0) { // WM_QUIT
4278 							EventLoop.quitApplication();
4279 							break;
4280 						}
4281 					}
4282 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4283 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4284 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4285 					// timeout, should never happen since we aren't using it
4286 				} else if(waitResult == 0xFFFFFFFF) {
4287 						// failed
4288 						throw new Exception("MsgWaitForMultipleObjectsEx failed");
4289 				} else {
4290 					// idk....
4291 				}
4292 			}
4293 
4294 			// return message.wParam;
4295 			return 0;
4296 		} else {
4297 			return 0;
4298 		}
4299 	}
4300 
4301 	int run(bool delegate() whileCondition = null) {
4302 		if(disposed)
4303 			initialize(this.pulseTimeout);
4304 
4305 		version(X11) {
4306 			try {
4307 				return loopHelper(whileCondition);
4308 			} catch(XDisconnectException e) {
4309 				if(e.userRequested) {
4310 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4311 						item.discardConnectionState();
4312 					XCloseDisplay(XDisplayConnection.display);
4313 				}
4314 
4315 				XDisplayConnection.display = null;
4316 
4317 				this.dispose();
4318 
4319 				throw e;
4320 			}
4321 		} else {
4322 			return loopHelper(whileCondition);
4323 		}
4324 	}
4325 }
4326 
4327 
4328 /++
4329 	Provides an icon on the system notification area (also known as the system tray).
4330 
4331 
4332 	If a notification area is not available with the NotificationIcon object is created,
4333 	it will silently succeed and simply attempt to create one when an area becomes available.
4334 
4335 
4336 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
4337 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
4338 	use the older version.
4339 +/
4340 version(OSXCocoa) {} else // NotYetImplementedException
4341 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4342 
4343 	version(X11) {
4344 		void recreateAfterDisconnect() {
4345 			stateDiscarded = false;
4346 			clippixmap = None;
4347 			throw new Exception("NOT IMPLEMENTED");
4348 		}
4349 
4350 		bool stateDiscarded;
4351 		void discardConnectionState() {
4352 			stateDiscarded = true;
4353 		}
4354 	}
4355 
4356 
4357 	version(X11) {
4358 		Image img;
4359 
4360 		NativeEventHandler getNativeEventHandler() {
4361 			return delegate int(XEvent e) {
4362 				switch(e.type) {
4363 					case EventType.Expose:
4364 					//case EventType.VisibilityNotify:
4365 						redraw();
4366 					break;
4367 					case EventType.ClientMessage:
4368 						version(sddddd) {
4369 						import std.stdio;
4370 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4371 						writeln("\t", e.xclient.format);
4372 						writeln("\t", e.xclient.data.l);
4373 						}
4374 					break;
4375 					case EventType.ButtonPress:
4376 						auto event = e.xbutton;
4377 						if (onClick !is null || onClickEx !is null) {
4378 							MouseButton mb = cast(MouseButton)0;
4379 							switch (event.button) {
4380 								case 1: mb = MouseButton.left; break; // left
4381 								case 2: mb = MouseButton.middle; break; // middle
4382 								case 3: mb = MouseButton.right; break; // right
4383 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4384 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4385 								case 6: break; // idk
4386 								case 7: break; // idk
4387 								case 8: mb = MouseButton.backButton; break;
4388 								case 9: mb = MouseButton.forwardButton; break;
4389 								default:
4390 							}
4391 							if (mb) {
4392 								try { onClick()(mb); } catch (Exception) {}
4393 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4394 							}
4395 						}
4396 					break;
4397 					case EventType.EnterNotify:
4398 						if (onEnter !is null) {
4399 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4400 						}
4401 						break;
4402 					case EventType.LeaveNotify:
4403 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4404 						break;
4405 					case EventType.DestroyNotify:
4406 						active = false;
4407 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4408 					break;
4409 					case EventType.ConfigureNotify:
4410 						auto event = e.xconfigure;
4411 						this.width = event.width;
4412 						this.height = event.height;
4413 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4414 						redraw();
4415 					break;
4416 					default: return 1;
4417 				}
4418 				return 1;
4419 			};
4420 		}
4421 
4422 		/* private */ void hideBalloon() {
4423 			balloon.close();
4424 			version(with_timer)
4425 				timer.destroy();
4426 			balloon = null;
4427 			version(with_timer)
4428 				timer = null;
4429 		}
4430 
4431 		void redraw() {
4432 			if (!active) return;
4433 
4434 			auto display = XDisplayConnection.get;
4435 			auto gc = DefaultGC(display, DefaultScreen(display));
4436 			XClearWindow(display, nativeHandle);
4437 
4438 			XSetClipMask(display, gc, clippixmap);
4439 
4440 			XSetForeground(display, gc,
4441 				cast(uint) 0 << 16 |
4442 				cast(uint) 0 << 8 |
4443 				cast(uint) 0);
4444 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4445 
4446 			if (img is null) {
4447 				XSetForeground(display, gc,
4448 					cast(uint) 0 << 16 |
4449 					cast(uint) 127 << 8 |
4450 					cast(uint) 0);
4451 				XFillArc(display, nativeHandle,
4452 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4453 			} else {
4454 				int dx = 0;
4455 				int dy = 0;
4456 				if(width > img.width)
4457 					dx = (width - img.width) / 2;
4458 				if(height > img.height)
4459 					dy = (height - img.height) / 2;
4460 				XSetClipOrigin(display, gc, dx, dy);
4461 
4462 				if (img.usingXshm)
4463 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
4464 				else
4465 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
4466 			}
4467 			XSetClipMask(display, gc, None);
4468 			flushGui();
4469 		}
4470 
4471 		static Window getTrayOwner() {
4472 			auto display = XDisplayConnection.get;
4473 			auto i = cast(int) DefaultScreen(display);
4474 			if(i < 10 && i >= 0) {
4475 				static Atom atom;
4476 				if(atom == None)
4477 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4478 				return XGetSelectionOwner(display, atom);
4479 			}
4480 			return None;
4481 		}
4482 
4483 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4484 			auto to = getTrayOwner();
4485 			auto display = XDisplayConnection.get;
4486 			XEvent ev;
4487 			ev.xclient.type = EventType.ClientMessage;
4488 			ev.xclient.window = to;
4489 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4490 			ev.xclient.format = 32;
4491 			ev.xclient.data.l[0] = CurrentTime;
4492 			ev.xclient.data.l[1] = message;
4493 			ev.xclient.data.l[2] = d1;
4494 			ev.xclient.data.l[3] = d2;
4495 			ev.xclient.data.l[4] = d3;
4496 
4497 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4498 		}
4499 
4500 		private static NotificationAreaIcon[] activeIcons;
4501 
4502 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4503 		private void newManager() {
4504 			close();
4505 			createXWin();
4506 
4507 			if(this.clippixmap)
4508 				XFreePixmap(XDisplayConnection.get, clippixmap);
4509 			if(this.originalMemoryImage)
4510 				this.icon = this.originalMemoryImage;
4511 			else if(this.img)
4512 				this.icon = this.img;
4513 		}
4514 
4515 		private void createXWin () {
4516 			// create window
4517 			auto display = XDisplayConnection.get;
4518 
4519 			// to check for MANAGER on root window to catch new/changed tray owners
4520 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4521 			// so if a thing does appear, we can handle it
4522 			foreach(ai; activeIcons)
4523 				if(ai is this)
4524 					goto alreadythere;
4525 			activeIcons ~= this;
4526 			alreadythere:
4527 
4528 			// and check for an existing tray
4529 			auto trayOwner = getTrayOwner();
4530 			if(trayOwner == None)
4531 				return;
4532 				//throw new Exception("No notification area found");
4533 
4534 			Visual* v = cast(Visual*) CopyFromParent;
4535 			/+
4536 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4537 			if(visualProp !is null) {
4538 				c_ulong[] info = cast(c_ulong[]) visualProp;
4539 				if(info.length == 1) {
4540 					auto vid = info[0];
4541 					int returned;
4542 					XVisualInfo t;
4543 					t.visualid = vid;
4544 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4545 					if(got !is null) {
4546 						if(returned == 1) {
4547 							v = got.visual;
4548 							import std.stdio;
4549 							writeln("using special visual ", *got);
4550 						}
4551 						XFree(got);
4552 					}
4553 				}
4554 			}
4555 			+/
4556 
4557 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
4558 			assert(nativeWindow);
4559 
4560 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4561 
4562 			nativeHandle = nativeWindow;
4563 
4564 			///+
4565 			arch_ulong[2] info;
4566 			info[0] = 0;
4567 			info[1] = 1;
4568 
4569 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4570 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4571 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4572 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4573 
4574 			XChangeProperty(
4575 				display,
4576 				nativeWindow,
4577 				GetAtom!("_XEMBED_INFO", true)(display),
4578 				GetAtom!("_XEMBED_INFO", true)(display),
4579 				32 /* bits */,
4580 				0 /*PropModeReplace*/,
4581 				info.ptr,
4582 				2);
4583 
4584 			import core.sys.posix.unistd;
4585 			arch_ulong pid = getpid();
4586 
4587 			XChangeProperty(
4588 				display,
4589 				nativeWindow,
4590 				GetAtom!("_NET_WM_PID", true)(display),
4591 				XA_CARDINAL,
4592 				32 /* bits */,
4593 				0 /*PropModeReplace*/,
4594 				&pid,
4595 				1);
4596 
4597 			updateNetWmIcon();
4598 
4599 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4600 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4601 				XClassHint klass;
4602 				XWMHints wh;
4603 				XSizeHints size;
4604 				klass.res_name = sdpyWindowClassStr;
4605 				klass.res_class = sdpyWindowClassStr;
4606 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4607 			}
4608 
4609 				// believe it or not, THIS is what xfce needed for the 9999 issue
4610 				XSizeHints sh;
4611 					c_long spr;
4612 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4613 					sh.flags |= PMaxSize | PMinSize;
4614 				// FIXME maybe nicer resizing
4615 				sh.min_width = 16;
4616 				sh.min_height = 16;
4617 				sh.max_width = 16;
4618 				sh.max_height = 16;
4619 				XSetWMNormalHints(display, nativeWindow, &sh);
4620 
4621 
4622 			//+/
4623 
4624 
4625 			XSelectInput(display, nativeWindow,
4626 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4627 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4628 
4629 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4630 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4631 			active = true;
4632 		}
4633 
4634 		void updateNetWmIcon() {
4635 			if(img is null) return;
4636 			auto display = XDisplayConnection.get;
4637 			// FIXME: ensure this is correct
4638 			arch_ulong[] buffer;
4639 			auto imgMi = img.toTrueColorImage;
4640 			buffer ~= imgMi.width;
4641 			buffer ~= imgMi.height;
4642 			foreach(c; imgMi.imageData.colors) {
4643 				arch_ulong b;
4644 				b |= c.a << 24;
4645 				b |= c.r << 16;
4646 				b |= c.g << 8;
4647 				b |= c.b;
4648 				buffer ~= b;
4649 			}
4650 
4651 			XChangeProperty(
4652 				display,
4653 				nativeHandle,
4654 				GetAtom!"_NET_WM_ICON"(display),
4655 				GetAtom!"CARDINAL"(display),
4656 				32 /* bits */,
4657 				0 /*PropModeReplace*/,
4658 				buffer.ptr,
4659 				cast(int) buffer.length);
4660 		}
4661 
4662 
4663 
4664 		private SimpleWindow balloon;
4665 		version(with_timer)
4666 		private Timer timer;
4667 
4668 		private Window nativeHandle;
4669 		private Pixmap clippixmap = None;
4670 		private int width = 16;
4671 		private int height = 16;
4672 		private bool active = false;
4673 
4674 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4675 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4676 		void delegate () onLeave; /// X11 only.
4677 
4678 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4679 
4680 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4681 		void getWindowRect (out int x, out int y, out int width, out int height) {
4682 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4683 			Window dummyw;
4684 			auto dpy = XDisplayConnection.get;
4685 			//XWindowAttributes xwa;
4686 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4687 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4688 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4689 			width = this.width;
4690 			height = this.height;
4691 		}
4692 	}
4693 
4694 	/+
4695 		What I actually want from this:
4696 
4697 		* set / change: icon, tooltip
4698 		* handle: mouse click, right click
4699 		* show: notification bubble.
4700 	+/
4701 
4702 	version(Windows) {
4703 		WindowsIcon win32Icon;
4704 		HWND hwnd;
4705 
4706 		NOTIFYICONDATAW data;
4707 
4708 		NativeEventHandler getNativeEventHandler() {
4709 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
4710 				if(msg == WM_USER) {
4711 					auto event = LOWORD(lParam);
4712 					auto iconId = HIWORD(lParam);
4713 					//auto x = GET_X_LPARAM(wParam);
4714 					//auto y = GET_Y_LPARAM(wParam);
4715 					switch(event) {
4716 						case WM_LBUTTONDOWN:
4717 							onClick()(MouseButton.left);
4718 						break;
4719 						case WM_RBUTTONDOWN:
4720 							onClick()(MouseButton.right);
4721 						break;
4722 						case WM_MBUTTONDOWN:
4723 							onClick()(MouseButton.middle);
4724 						break;
4725 						case WM_MOUSEMOVE:
4726 							// sent, we could use it.
4727 						break;
4728 						case WM_MOUSEWHEEL:
4729 							// NOT SENT
4730 						break;
4731 						//case NIN_KEYSELECT:
4732 						//case NIN_SELECT:
4733 						//break;
4734 						default: {}
4735 					}
4736 				}
4737 				return 0;
4738 			};
4739 		}
4740 
4741 		enum NIF_SHOWTIP = 0x00000080;
4742 
4743 		private static struct NOTIFYICONDATAW {
4744 			DWORD cbSize;
4745 			HWND  hWnd;
4746 			UINT  uID;
4747 			UINT  uFlags;
4748 			UINT  uCallbackMessage;
4749 			HICON hIcon;
4750 			WCHAR[128] szTip;
4751 			DWORD dwState;
4752 			DWORD dwStateMask;
4753 			WCHAR[256] szInfo;
4754 			union {
4755 				UINT uTimeout;
4756 				UINT uVersion;
4757 			}
4758 			WCHAR[64] szInfoTitle;
4759 			DWORD dwInfoFlags;
4760 			GUID  guidItem;
4761 			HICON hBalloonIcon;
4762 		}
4763 
4764 	}
4765 
4766 	/++
4767 		Note that on Windows, only left, right, and middle buttons are sent.
4768 		Mouse wheel buttons are NOT set, so don't rely on those events if your
4769 		program is meant to be used on Windows too.
4770 	+/
4771 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
4772 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
4773 		// but on X, we need an Image, so its canonical ctor is there. They should
4774 		// forward to each other though.
4775 		version(X11) {
4776 			this.name = name;
4777 			this.onClick = onClick;
4778 			createXWin();
4779 			this.icon = icon;
4780 		} else version(Windows) {
4781 			this.onClick = onClick;
4782 			this.win32Icon = new WindowsIcon(icon);
4783 
4784 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
4785 
4786 			static bool registered = false;
4787 			if(!registered) {
4788 				WNDCLASSEX wc;
4789 				wc.cbSize = wc.sizeof;
4790 				wc.hInstance = hInstance;
4791 				wc.lpfnWndProc = &WndProc;
4792 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
4793 				if(!RegisterClassExW(&wc))
4794 					throw new WindowsApiException("RegisterClass");
4795 				registered = true;
4796 			}
4797 
4798 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
4799 			if(hwnd is null)
4800 				throw new Exception("CreateWindow");
4801 
4802 			data.cbSize = data.sizeof;
4803 			data.hWnd = hwnd;
4804 			data.uID = cast(uint) cast(void*) this;
4805 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
4806 				// NIF_INFO means show balloon
4807 			data.uCallbackMessage = WM_USER;
4808 			data.hIcon = this.win32Icon.hIcon;
4809 			data.szTip = ""; // FIXME
4810 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4811 			data.dwStateMask = NIS_HIDDEN; // windows vista
4812 
4813 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
4814 
4815 
4816 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
4817 
4818 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
4819 		} else version(OSXCocoa) {
4820 			throw new NotYetImplementedException();
4821 		} else static assert(0);
4822 	}
4823 
4824 	/// ditto
4825 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
4826 		version(X11) {
4827 			this.onClick = onClick;
4828 			this.name = name;
4829 			createXWin();
4830 			this.icon = icon;
4831 		} else version(Windows) {
4832 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
4833 		} else version(OSXCocoa) {
4834 			throw new NotYetImplementedException();
4835 		} else static assert(0);
4836 	}
4837 
4838 	version(X11) {
4839 		/++
4840 			X-specific extension (for now at least)
4841 		+/
4842 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4843 			this.onClickEx = onClickEx;
4844 			createXWin();
4845 			if (icon !is null) this.icon = icon;
4846 		}
4847 
4848 		/// ditto
4849 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4850 			this.onClickEx = onClickEx;
4851 			createXWin();
4852 			this.icon = icon;
4853 		}
4854 	}
4855 
4856 	private void delegate (MouseButton button) onClick_;
4857 
4858 	///
4859 	@property final void delegate(MouseButton) onClick() {
4860 		if(onClick_ is null)
4861 			onClick_ = delegate void(MouseButton) {};
4862 		return onClick_;
4863 	}
4864 
4865 	/// ditto
4866 	@property final void onClick(void delegate(MouseButton) handler) {
4867 		// I made this a property setter so we can wrap smaller arg
4868 		// delegates and just forward all to onClickEx or something.
4869 		onClick_ = handler;
4870 	}
4871 
4872 
4873 	string name_;
4874 	@property void name(string n) {
4875 		name_ = n;
4876 	}
4877 
4878 	@property string name() {
4879 		return name_;
4880 	}
4881 
4882 	private MemoryImage originalMemoryImage;
4883 
4884 	///
4885 	@property void icon(MemoryImage i) {
4886 		version(X11) {
4887 			this.originalMemoryImage = i;
4888 			if (!active) return;
4889 			if (i !is null) {
4890 				this.img = Image.fromMemoryImage(i);
4891 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
4892 				//import std.stdio; writeln("using pixmap ", clippixmap);
4893 				updateNetWmIcon();
4894 				redraw();
4895 			} else {
4896 				if (this.img !is null) {
4897 					this.img = null;
4898 					redraw();
4899 				}
4900 			}
4901 		} else version(Windows) {
4902 			this.win32Icon = new WindowsIcon(i);
4903 
4904 			data.uFlags = NIF_ICON;
4905 			data.hIcon = this.win32Icon.hIcon;
4906 
4907 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4908 		} else version(OSXCocoa) {
4909 			throw new NotYetImplementedException();
4910 		} else static assert(0);
4911 	}
4912 
4913 	/// ditto
4914 	@property void icon (Image i) {
4915 		version(X11) {
4916 			if (!active) return;
4917 			if (i !is img) {
4918 				originalMemoryImage = null;
4919 				img = i;
4920 				redraw();
4921 			}
4922 		} else version(Windows) {
4923 			this.icon(i is null ? null : i.toTrueColorImage());
4924 		} else version(OSXCocoa) {
4925 			throw new NotYetImplementedException();
4926 		} else static assert(0);
4927 	}
4928 
4929 	/++
4930 		Shows a balloon notification. You can only show one balloon at a time, if you call
4931 		it twice while one is already up, the first balloon will be replaced.
4932 		
4933 		
4934 		The user is free to block notifications and they will automatically disappear after
4935 		a timeout period.
4936 
4937 		Params:
4938 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
4939 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
4940 			icon = the icon to display with the notification. If null, it uses your existing icon.
4941 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
4942 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
4943 	+/
4944 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
4945 		bool useCustom = true;
4946 		version(libnotify) {
4947 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
4948 			try {
4949 				if(!active) return;
4950 
4951 				if(libnotify is null) {
4952 					libnotify = new C_DynamicLibrary("libnotify.so");
4953 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
4954 				}
4955 
4956 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
4957 
4958 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
4959 
4960 				if(onclick) {
4961 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
4962 					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);
4963 					libnotify_action_delegates_count++;
4964 				}
4965 
4966 				// FIXME icon
4967 
4968 				// set hint image-data
4969 				// set default action for onclick
4970 
4971 				void* error;
4972 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
4973 
4974 				useCustom = false;
4975 			} catch(Exception e) {
4976 
4977 			}
4978 		}
4979 		
4980 		version(X11) {
4981 		if(useCustom) {
4982 			if(!active) return;
4983 			if(balloon) {
4984 				hideBalloon();
4985 			}
4986 			// I know there are two specs for this, but one is never
4987 			// implemented by any window manager I have ever seen, and
4988 			// the other is a bloated mess and too complicated for simpledisplay...
4989 			// so doing my own little window instead.
4990 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
4991 
4992 			int x, y, width, height;
4993 			getWindowRect(x, y, width, height);
4994 
4995 			int bx = x - balloon.width;
4996 			int by = y - balloon.height;
4997 			if(bx < 0)
4998 				bx = x + width + balloon.width;
4999 			if(by < 0)
5000 				by = y + height;
5001 
5002 			// just in case, make sure it is actually on scren
5003 			if(bx < 0)
5004 				bx = 0;
5005 			if(by < 0)
5006 				by = 0;
5007 
5008 			balloon.move(bx, by);
5009 			auto painter = balloon.draw();
5010 			painter.fillColor = Color(220, 220, 220);
5011 			painter.outlineColor = Color.black;
5012 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5013 			auto iconWidth = icon is null ? 0 : icon.width;
5014 			if(icon)
5015 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5016 			iconWidth += 6; // margin around the icon
5017 
5018 			// draw a close button
5019 			painter.outlineColor = Color(44, 44, 44);
5020 			painter.fillColor = Color(255, 255, 255);
5021 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5022 			painter.pen = Pen(Color.black, 3);
5023 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5024 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5025 			painter.pen = Pen(Color.black, 1);
5026 			painter.fillColor = Color(220, 220, 220);
5027 
5028 			// Draw the title and message
5029 			painter.drawText(Point(4 + iconWidth, 4), title);
5030 			painter.drawLine(
5031 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5032 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5033 			);
5034 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5035 
5036 			balloon.setEventHandlers(
5037 				(MouseEvent ev) {
5038 					if(ev.type == MouseEventType.buttonPressed) {
5039 						if(ev.x > balloon.width - 16 && ev.y < 16)
5040 							hideBalloon();
5041 						else if(onclick)
5042 							onclick();
5043 					}
5044 				}
5045 			);
5046 			balloon.show();
5047 
5048 			version(with_timer)
5049 			timer = new Timer(timeout, &hideBalloon);
5050 			else {} // FIXME
5051 		}
5052 		} else version(Windows) {
5053 			enum NIF_INFO = 0x00000010;
5054 
5055 			data.uFlags = NIF_INFO;
5056 
5057 			// FIXME: go back to the last valid unicode code point
5058 			if(title.length > 40)
5059 				title = title[0 .. 40];
5060 			if(message.length > 220)
5061 				message = message[0 .. 220];
5062 
5063 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5064 			enum NIIF_LARGE_ICON  = 0x00000020;
5065 			enum NIIF_NOSOUND = 0x00000010;
5066 			enum NIIF_USER = 0x00000004;
5067 			enum NIIF_ERROR = 0x00000003;
5068 			enum NIIF_WARNING = 0x00000002;
5069 			enum NIIF_INFO = 0x00000001;
5070 			enum NIIF_NONE = 0;
5071 
5072 			WCharzBuffer t = WCharzBuffer(title);
5073 			WCharzBuffer m = WCharzBuffer(message);
5074 
5075 			t.copyInto(data.szInfoTitle);
5076 			m.copyInto(data.szInfo);
5077 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5078 
5079 			if(icon !is null) {
5080 				auto i = new WindowsIcon(icon);
5081 				data.hBalloonIcon = i.hIcon;
5082 				data.dwInfoFlags |= NIIF_USER;
5083 			}
5084 
5085 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5086 		} else version(OSXCocoa) {
5087 			throw new NotYetImplementedException();
5088 		} else static assert(0);
5089 	}
5090 
5091 	///
5092 	//version(Windows)
5093 	void show() {
5094 		version(X11) {
5095 			if(!hidden)
5096 				return;
5097 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5098 			hidden = false;
5099 		} else version(Windows) {
5100 			data.uFlags = NIF_STATE;
5101 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5102 			data.dwStateMask = NIS_HIDDEN; // windows vista
5103 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5104 		} else version(OSXCocoa) {
5105 			throw new NotYetImplementedException();
5106 		} else static assert(0);
5107 	}
5108 
5109 	version(X11)
5110 		bool hidden = false;
5111 
5112 	///
5113 	//version(Windows)
5114 	void hide() {
5115 		version(X11) {
5116 			if(hidden)
5117 				return;
5118 			hidden = true;
5119 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5120 		} else version(Windows) {
5121 			data.uFlags = NIF_STATE;
5122 			data.dwState = NIS_HIDDEN; // windows vista
5123 			data.dwStateMask = NIS_HIDDEN; // windows vista
5124 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5125 		} else version(OSXCocoa) {
5126 			throw new NotYetImplementedException();
5127 		} else static assert(0);
5128 	}
5129 
5130 	///
5131 	void close () {
5132 		version(X11) {
5133 			if (active) {
5134 				active = false; // event handler will set this too, but meh
5135 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5136 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5137 				flushGui();
5138 			}
5139 		} else version(Windows) {
5140 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5141 		} else version(OSXCocoa) {
5142 			throw new NotYetImplementedException();
5143 		} else static assert(0);
5144 	}
5145 
5146 	~this() {
5147 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5148 		version(X11)
5149 			if(clippixmap != None)
5150 				XFreePixmap(XDisplayConnection.get, clippixmap);
5151 		close();
5152 	}
5153 }
5154 
5155 version(X11)
5156 /// Call `XFreePixmap` on the return value.
5157 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5158 	char[] data = new char[](i.width * i.height / 8 + 2);
5159 	data[] = 0;
5160 
5161 	int bitOffset = 0;
5162 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5163 		ubyte v = c.a > 128 ? 1 : 0;
5164 		data[bitOffset / 8] |= v << (bitOffset%8);
5165 		bitOffset++;
5166 	}
5167 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5168 	return handle;
5169 }
5170 
5171 
5172 // basic functions to make timers
5173 /**
5174 	A timer that will trigger your function on a given interval.
5175 
5176 
5177 	You create a timer with an interval and a callback. It will continue
5178 	to fire on the interval until it is destroyed.
5179 
5180 	There are currently no one-off timers (instead, just create one and
5181 	destroy it when it is triggered) nor are there pause/resume functions -
5182 	the timer must again be destroyed and recreated if you want to pause it.
5183 
5184 	auto timer = new Timer(50, { it happened!; });
5185 	timer.destroy();
5186 
5187 	Timers can only be expected to fire when the event loop is running and only
5188 	once per iteration through the event loop.
5189 
5190 	History:
5191 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5192 		slow could lock up the event loop. It now guarantees other things will
5193 		get a chance to run between timer calls, even if that means not keeping up
5194 		with the requested interval.
5195 */
5196 version(with_timer) {
5197 class Timer {
5198 // FIXME: needs pause and unpause
5199 	// FIXME: I might add overloads for ones that take a count of
5200 	// how many elapsed since last time (on Windows, it will divide
5201 	// the ticks thing given, on Linux it is just available) and
5202 	// maybe one that takes an instance of the Timer itself too
5203 	/// Create a timer with a callback when it triggers.
5204 	this(int intervalInMilliseconds, void delegate() onPulse) {
5205 		assert(onPulse !is null);
5206 
5207 		this.intervalInMilliseconds = intervalInMilliseconds;
5208 		this.onPulse = onPulse;
5209 
5210 		version(Windows) {
5211 			/*
5212 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5213 			if(handle == 0)
5214 				throw new Exception("SetTimer fail");
5215 			*/
5216 
5217 			// thanks to Archival 998 for the WaitableTimer blocks
5218 			handle = CreateWaitableTimer(null, false, null);
5219 			long initialTime = -intervalInMilliseconds;
5220 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5221 				throw new Exception("SetWaitableTimer Failed");
5222 
5223 			mapping[handle] = this;
5224 
5225 		} else version(linux) {
5226 			static import ep = core.sys.linux.epoll;
5227 
5228 			import core.sys.linux.timerfd;
5229 
5230 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5231 			if(fd == -1)
5232 				throw new Exception("timer create failed");
5233 
5234 			mapping[fd] = this;
5235 
5236 			itimerspec value;
5237 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5238 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5239 
5240 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5241 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5242 
5243 			if(timerfd_settime(fd, 0, &value, null) == -1)
5244 				throw new Exception("couldn't make pulse timer");
5245 
5246 			version(with_eventloop) {
5247 				import arsd.eventloop;
5248 				addFileEventListeners(fd, &trigger, null, null);
5249 			} else {
5250 				prepareEventLoop();
5251 
5252 				ep.epoll_event ev = void;
5253 				ev.events = ep.EPOLLIN;
5254 				ev.data.fd = fd;
5255 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5256 			}
5257 		} else featureNotImplemented();
5258 	}
5259 
5260 	private int intervalInMilliseconds;
5261 
5262 	// just cuz I sometimes call it this.
5263 	alias dispose = destroy;
5264 
5265 	/// Stop and destroy the timer object.
5266 	void destroy() {
5267 		version(Windows) {
5268 			staticDestroy(handle);
5269 			handle = null;
5270 		} else version(linux) {
5271 			staticDestroy(fd);
5272 			fd = -1;
5273 		} else featureNotImplemented();
5274 	}
5275 
5276 	version(Windows)
5277 	static void staticDestroy(HANDLE handle) {
5278 		if(handle) {
5279 			// KillTimer(null, handle);
5280 			CancelWaitableTimer(cast(void*)handle);
5281 			mapping.remove(handle);
5282 			CloseHandle(handle);
5283 		}
5284 	}
5285 	else version(linux)
5286 	static void staticDestroy(int fd) {
5287 		if(fd != -1) {
5288 			import unix = core.sys.posix.unistd;
5289 			static import ep = core.sys.linux.epoll;
5290 
5291 			version(with_eventloop) {
5292 				import arsd.eventloop;
5293 				removeFileEventListeners(fd);
5294 			} else {
5295 				ep.epoll_event ev = void;
5296 				ev.events = ep.EPOLLIN;
5297 				ev.data.fd = fd;
5298 
5299 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5300 			}
5301 			unix.close(fd);
5302 			mapping.remove(fd);
5303 		}
5304 	}
5305 
5306 	~this() {
5307 		version(Windows) { if(handle)
5308 			cleanupQueue.queue!staticDestroy(handle);
5309 		} else version(linux) { if(fd != -1)
5310 			cleanupQueue.queue!staticDestroy(fd);
5311 		}
5312 	}
5313 
5314 
5315 	void changeTime(int intervalInMilliseconds)
5316 	{
5317 		this.intervalInMilliseconds = intervalInMilliseconds;
5318 		version(Windows)
5319 		{
5320 			if(handle)
5321 			{
5322 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5323 				long initialTime = -intervalInMilliseconds;
5324 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5325 					throw new Exception("couldn't change pulse timer");
5326 			}
5327 		}
5328 	}
5329 
5330 
5331 	private:
5332 
5333 	void delegate() onPulse;
5334 
5335 	int lastEventLoopRoundTriggered;
5336 
5337 	void trigger() {
5338 		version(linux) {
5339 			import unix = core.sys.posix.unistd;
5340 			long val;
5341 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5342 		} else version(Windows) {
5343 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5344 				return; // never try to actually run faster than the event loop
5345 			lastEventLoopRoundTriggered = eventLoopRound;
5346 		} else featureNotImplemented();
5347 
5348 		onPulse();
5349 	}
5350 
5351 	version(Windows)
5352 	void rearm() {
5353 
5354 	}
5355 
5356 	version(Windows)
5357 		extern(Windows)
5358 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5359 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5360 			if(Timer* t = timer in mapping) {
5361 				try
5362 				(*t).trigger();
5363 				catch(Exception e) { sdpy_abort(e); assert(0); }
5364 			}
5365 		}
5366 
5367 	version(Windows) {
5368 		//UINT_PTR handle;
5369 		//static Timer[UINT_PTR] mapping;
5370 		HANDLE handle;
5371 		__gshared Timer[HANDLE] mapping;
5372 	} else version(linux) {
5373 		int fd = -1;
5374 		__gshared Timer[int] mapping;
5375 	} else static assert(0, "timer not supported");
5376 }
5377 }
5378 
5379 version(Windows)
5380 private int eventLoopRound;
5381 
5382 version(Windows)
5383 /// 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
5384 class WindowsHandleReader {
5385 	///
5386 	this(void delegate() onReady, HANDLE handle) {
5387 		this.onReady = onReady;
5388 		this.handle = handle;
5389 
5390 		mapping[handle] = this;
5391 
5392 		enable();
5393 	}
5394 
5395 	///
5396 	void enable() {
5397 		auto el = EventLoop.get().impl;
5398 		el.handles ~= handle;
5399 	}
5400 
5401 	///
5402 	void disable() {
5403 		auto el = EventLoop.get().impl;
5404 		for(int i = 0; i < el.handles.length; i++) {
5405 			if(el.handles[i] is handle) {
5406 				el.handles[i] = el.handles[$-1];
5407 				el.handles = el.handles[0 .. $-1];
5408 				return;
5409 			}
5410 		}
5411 	}
5412 
5413 	void dispose() {
5414 		disable();
5415 		if(handle)
5416 			mapping.remove(handle);
5417 		handle = null;
5418 	}
5419 
5420 	void ready() {
5421 		if(onReady)
5422 			onReady();
5423 	}
5424 
5425 	HANDLE handle;
5426 	void delegate() onReady;
5427 
5428 	__gshared WindowsHandleReader[HANDLE] mapping;
5429 }
5430 
5431 version(Posix)
5432 /// Lets you add files to the event loop for reading. Use at your own risk.
5433 class PosixFdReader {
5434 	///
5435 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5436 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5437 	}
5438 
5439 	///
5440 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5441 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5442 	}
5443 
5444 	///
5445 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5446 		this.onReady = onReady;
5447 		this.fd = fd;
5448 		this.captureWrites = captureWrites;
5449 		this.captureReads = captureReads;
5450 
5451 		mapping[fd] = this;
5452 
5453 		version(with_eventloop) {
5454 			import arsd.eventloop;
5455 			addFileEventListeners(fd, &readyel);
5456 		} else {
5457 			enable();
5458 		}
5459 	}
5460 
5461 	bool captureReads;
5462 	bool captureWrites;
5463 
5464 	version(with_eventloop) {} else
5465 	///
5466 	void enable() {
5467 		prepareEventLoop();
5468 
5469 		enabled = true;
5470 
5471 		version(linux) {
5472 			static import ep = core.sys.linux.epoll;
5473 			ep.epoll_event ev = void;
5474 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5475 			//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5476 			ev.data.fd = fd;
5477 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5478 		} else {
5479 
5480 		}
5481 	}
5482 
5483 	version(with_eventloop) {} else
5484 	///
5485 	void disable() {
5486 		prepareEventLoop();
5487 
5488 		enabled = false;
5489 
5490 		version(linux) {
5491 			static import ep = core.sys.linux.epoll;
5492 			ep.epoll_event ev = void;
5493 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5494 			//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5495 			ev.data.fd = fd;
5496 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5497 		}
5498 	}
5499 
5500 	version(with_eventloop) {} else
5501 	///
5502 	void dispose() {
5503 		if(enabled)
5504 			disable();
5505 		if(fd != -1)
5506 			mapping.remove(fd);
5507 		fd = -1;
5508 	}
5509 
5510 	void delegate(int, bool, bool) onReady;
5511 
5512 	version(with_eventloop)
5513 	void readyel() {
5514 		onReady(fd, true, true);
5515 	}
5516 
5517 	void ready(uint flags) {
5518 		version(linux) {
5519 			static import ep = core.sys.linux.epoll;
5520 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5521 		} else {
5522 			import core.sys.posix.poll;
5523 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5524 		}
5525 	}
5526 
5527 	void hup(uint flags) {
5528 		if(onHup)
5529 			onHup();
5530 	}
5531 
5532 	void delegate() onHup;
5533 
5534 	int fd = -1;
5535 	private bool enabled;
5536 	__gshared PosixFdReader[int] mapping;
5537 }
5538 
5539 // basic functions to access the clipboard
5540 /+
5541 
5542 
5543 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5544 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5545 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5546 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5547 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5548 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5549 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5550 
5551 +/
5552 
5553 /++
5554 	this does a delegate because it is actually an async call on X...
5555 	the receiver may never be called if the clipboard is empty or unavailable
5556 	gets plain text from the clipboard.
5557 +/
5558 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5559 	version(Windows) {
5560 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5561 		if(OpenClipboard(hwndOwner) == 0)
5562 			throw new Exception("OpenClipboard");
5563 		scope(exit)
5564 			CloseClipboard();
5565 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5566 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5567 
5568 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5569 				scope(exit)
5570 					GlobalUnlock(dataHandle);
5571 
5572 				// FIXME: CR/LF conversions
5573 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5574 				int len = 0;
5575 				auto d = data;
5576 				while(*d) {
5577 					d++;
5578 					len++;
5579 				}
5580 				string s;
5581 				s.reserve(len);
5582 				foreach(dchar ch; data[0 .. len]) {
5583 					s ~= ch;
5584 				}
5585 				receiver(s);
5586 			}
5587 		}
5588 	} else version(X11) {
5589 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5590 	} else version(OSXCocoa) {
5591 		throw new NotYetImplementedException();
5592 	} else static assert(0);
5593 }
5594 
5595 // FIXME: a clipboard listener might be cool btw
5596 
5597 /++
5598 	this does a delegate because it is actually an async call on X...
5599 	the receiver may never be called if the clipboard is empty or unavailable
5600 	gets image from the clipboard.
5601 
5602 	templated because it introduces an optional dependency on arsd.bmp
5603 +/
5604 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5605 	version(Windows) {
5606 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5607 		if(OpenClipboard(hwndOwner) == 0)
5608 			throw new Exception("OpenClipboard");
5609 		scope(exit)
5610 			CloseClipboard();
5611 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5612 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5613 				scope(exit)
5614 					GlobalUnlock(dataHandle);
5615 
5616 				auto len = GlobalSize(dataHandle);
5617 
5618 				import arsd.bmp;
5619 				auto img = readBmp(data[0 .. len], false);
5620 				receiver(img);
5621 			}
5622 		}
5623 	} else version(X11) {
5624 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5625 	} else version(OSXCocoa) {
5626 		throw new NotYetImplementedException();
5627 	} else static assert(0);
5628 }
5629 
5630 version(Windows)
5631 struct WCharzBuffer {
5632 	wchar[] buffer;
5633 	wchar[256] staticBuffer = void;
5634 
5635 	size_t length() {
5636 		return buffer.length;
5637 	}
5638 
5639 	wchar* ptr() {
5640 		return buffer.ptr;
5641 	}
5642 
5643 	wchar[] slice() {
5644 		return buffer;
5645 	}
5646 
5647 	void copyInto(R)(ref R r) {
5648 		static if(is(R == wchar[N], size_t N)) {
5649 			r[0 .. this.length] = slice[];
5650 			r[this.length] = 0;
5651 		} else static assert(0, "can only copy into wchar[n], not " ~ R.stringof);
5652 	}
5653 
5654 	/++
5655 		conversionFlags = [WindowsStringConversionFlags]
5656 	+/
5657 	this(in char[] data, int conversionFlags = 0) {
5658 		conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name
5659 		auto sz = sizeOfConvertedWstring(data, conversionFlags);
5660 		if(sz > staticBuffer.length)
5661 			buffer = new wchar[](sz);
5662 		else
5663 			buffer = staticBuffer[];
5664 
5665 		buffer = makeWindowsString(data, buffer, conversionFlags);
5666 	}
5667 }
5668 
5669 version(Windows)
5670 int sizeOfConvertedWstring(in char[] s, int conversionFlags) {
5671 	int size = 0;
5672 
5673 	if(conversionFlags & WindowsStringConversionFlags.convertNewLines) {
5674 		// need to convert line endings, which means the length will get bigger.
5675 
5676 		// BTW I betcha this could be faster with some simd stuff.
5677 		char last;
5678 		foreach(char ch; s) {
5679 			if(ch == 10 && last != 13)
5680 				size++; // will add a 13 before it...
5681 			size++;
5682 			last = ch;
5683 		}
5684 	} else {
5685 		// no conversion necessary, just estimate based on length
5686 		/*
5687 			I don't think there's any string with a longer length
5688 			in code units when encoded in UTF-16 than it has in UTF-8.
5689 			This will probably over allocate, but that's OK.
5690 		*/
5691 		size = cast(int) s.length;
5692 	}
5693 
5694 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate)
5695 		size++;
5696 
5697 	return size;
5698 }
5699 
5700 version(Windows)
5701 enum WindowsStringConversionFlags : int {
5702 	zeroTerminate = 1,
5703 	convertNewLines = 2,
5704 }
5705 
5706 version(Windows)
5707 class WindowsApiException : Exception {
5708 	char[256] buffer;
5709 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5710 		assert(msg.length < 100);
5711 
5712 		auto error = GetLastError();
5713 		buffer[0 .. msg.length] = msg;
5714 		buffer[msg.length] = ' ';
5715 
5716 		int pos = cast(int) msg.length + 1;
5717 
5718 		if(error == 0)
5719 			buffer[pos++] = '0';
5720 		else {
5721 
5722 			auto ec = error;
5723 			auto init = pos;
5724 			while(ec) {
5725 				buffer[pos++] = (ec % 10) + '0';
5726 				ec /= 10;
5727 			}
5728 
5729 			buffer[pos++] = ' ';
5730 
5731 			size_t size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &(buffer[pos]), cast(DWORD) buffer.length - pos, null);
5732 
5733 			pos += size;
5734 		}
5735 
5736 
5737 		super(cast(string) buffer[0 .. pos], file, line, next);
5738 	}
5739 }
5740 
5741 class ErrnoApiException : Exception {
5742 	char[256] buffer;
5743 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5744 		assert(msg.length < 100);
5745 
5746 		import core.stdc.errno;
5747 		auto error = errno;
5748 		buffer[0 .. msg.length] = msg;
5749 		buffer[msg.length] = ' ';
5750 
5751 		int pos = cast(int) msg.length + 1;
5752 
5753 		if(error == 0)
5754 			buffer[pos++] = '0';
5755 		else {
5756 			auto init = pos;
5757 			while(error) {
5758 				buffer[pos++] = (error % 10) + '0';
5759 				error /= 10;
5760 			}
5761 			for(int i = 0; i < (pos - init) / 2; i++) {
5762 				char c = buffer[i + init];
5763 				buffer[i + init] = buffer[pos - (i + init) - 1];
5764 				buffer[pos - (i + init) - 1] = c;
5765 			}
5766 		}
5767 
5768 
5769 		super(cast(string) buffer[0 .. pos], file, line, next);
5770 	}
5771 
5772 }
5773 
5774 version(Windows)
5775 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) {
5776 	if(str.length == 0)
5777 		return null;
5778 
5779 	int pos = 0;
5780 	dchar last;
5781 	foreach(dchar c; str) {
5782 		if(c <= 0xFFFF) {
5783 			if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13)
5784 				buffer[pos++] = 13;
5785 			buffer[pos++] = cast(wchar) c;
5786 		} else if(c <= 0x10FFFF) {
5787 			buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
5788 			buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
5789 		}
5790 
5791 		last = c;
5792 	}
5793 
5794 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) {
5795 		buffer[pos] = 0;
5796 	}
5797 
5798 	return buffer[0 .. pos];
5799 }
5800 
5801 version(Windows)
5802 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
5803 	if(str.length == 0)
5804 		return null;
5805 
5806 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
5807 	if(got == 0) {
5808 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5809 			throw new Exception("not enough buffer");
5810 		else
5811 			throw new Exception("conversion"); // FIXME: GetLastError
5812 	}
5813 	return buffer[0 .. got];
5814 }
5815 
5816 version(Windows)
5817 string makeUtf8StringFromWindowsString(in wchar[] str) {
5818 	char[] buffer;
5819 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
5820 	buffer.length = got;
5821 
5822 	// it is unique because we just allocated it above!
5823 	return cast(string) makeUtf8StringFromWindowsString(str, buffer);
5824 }
5825 
5826 version(Windows)
5827 string makeUtf8StringFromWindowsString(wchar* str) {
5828 	char[] buffer;
5829 	auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
5830 	buffer.length = got;
5831 
5832 	got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
5833 	if(got == 0) {
5834 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5835 			throw new Exception("not enough buffer");
5836 		else
5837 			throw new Exception("conversion"); // FIXME: GetLastError
5838 	}
5839 	return cast(string) buffer[0 .. got];
5840 }
5841 
5842 int findIndexOfZero(in wchar[] str) {
5843 	foreach(idx, wchar ch; str)
5844 		if(ch == 0)
5845 			return cast(int) idx;
5846 	return cast(int) str.length;
5847 }
5848 int findIndexOfZero(in char[] str) {
5849 	foreach(idx, char ch; str)
5850 		if(ch == 0)
5851 			return cast(int) idx;
5852 	return cast(int) str.length;
5853 }
5854 
5855 /// Copies some text to the clipboard.
5856 void setClipboardText(SimpleWindow clipboardOwner, string text) {
5857 	assert(clipboardOwner !is null);
5858 	version(Windows) {
5859 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5860 			throw new Exception("OpenClipboard");
5861 		scope(exit)
5862 			CloseClipboard();
5863 		EmptyClipboard();
5864 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5865 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
5866 		if(handle is null) throw new Exception("GlobalAlloc");
5867 		if(auto data = cast(wchar*) GlobalLock(handle)) {
5868 			auto slice = data[0 .. sz];
5869 			scope(failure)
5870 				GlobalUnlock(handle);
5871 
5872 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5873 
5874 			GlobalUnlock(handle);
5875 			SetClipboardData(CF_UNICODETEXT, handle);
5876 		}
5877 	} else version(X11) {
5878 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
5879 	} else version(OSXCocoa) {
5880 		throw new NotYetImplementedException();
5881 	} else static assert(0);
5882 }
5883 
5884 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
5885 	assert(clipboardOwner !is null);
5886 	version(Windows) {
5887 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5888 			throw new Exception("OpenClipboard");
5889 		scope(exit)
5890 			CloseClipboard();
5891 		EmptyClipboard();
5892 
5893 
5894 		import arsd.bmp;
5895 		ubyte[] mdata;
5896 		mdata.reserve(img.width * img.height);
5897 		void sink(ubyte b) {
5898 			mdata ~= b;
5899 		}
5900 		writeBmpIndirect(img, &sink, false);
5901 
5902 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
5903 		if(handle is null) throw new Exception("GlobalAlloc");
5904 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
5905 			auto slice = data[0 .. mdata.length];
5906 			scope(failure)
5907 				GlobalUnlock(handle);
5908 
5909 			slice[] = mdata[];
5910 
5911 			GlobalUnlock(handle);
5912 			SetClipboardData(CF_DIB, handle);
5913 		}
5914 	} else version(X11) {
5915 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
5916 			mixin X11SetSelectionHandler_Basics;
5917 			private const(ubyte)[] mdata;
5918 			private const(ubyte)[] mdata_original;
5919 			this(MemoryImage img) {
5920 				import arsd.bmp;
5921 
5922 				mdata.reserve(img.width * img.height);
5923 				void sink(ubyte b) {
5924 					mdata ~= b;
5925 				}
5926 				writeBmpIndirect(img, &sink, true);
5927 
5928 				mdata_original = mdata;
5929 			}
5930 
5931 			Atom[] availableFormats() {
5932 				auto display = XDisplayConnection.get;
5933 				return [
5934 					GetAtom!"image/bmp"(display),
5935 					GetAtom!"TARGETS"(display)
5936 				];
5937 			}
5938 
5939 			ubyte[] getData(Atom format, return scope ubyte[] data) {
5940 				if(mdata.length < data.length) {
5941 					data[0 .. mdata.length] = mdata[];
5942 					auto ret = data[0 .. mdata.length];
5943 					mdata = mdata[$..$];
5944 					return ret;
5945 				} else {
5946 					data[] = mdata[0 .. data.length];
5947 					mdata = mdata[data.length .. $];
5948 					return data[];
5949 				}
5950 			}
5951 
5952 			void done() {
5953 				mdata = mdata_original;
5954 			}
5955 		}
5956 
5957 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
5958 	} else version(OSXCocoa) {
5959 		throw new NotYetImplementedException();
5960 	} else static assert(0);
5961 }
5962 
5963 
5964 version(X11) {
5965 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
5966 
5967 	private Atom*[] interredAtoms; // for discardAndRecreate
5968 
5969 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
5970 	/// Platform-specific for X11.
5971 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
5972 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
5973 		static Atom a;
5974 		if(!a) {
5975 			a = XInternAtom(display, name, !create);
5976 			interredAtoms ~= &a;
5977 		}
5978 		if(a == None)
5979 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
5980 		return a;
5981 	}
5982 
5983 	/// Platform-specific for X11 - gets atom names as a string.
5984 	string getAtomName(Atom atom, Display* display) {
5985 		auto got = XGetAtomName(display, atom);
5986 		scope(exit) XFree(got);
5987 		import core.stdc.string;
5988 		string s = got[0 .. strlen(got)].idup;
5989 		return s;
5990 	}
5991 
5992 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
5993 	void setPrimarySelection(SimpleWindow window, string text) {
5994 		setX11Selection!"PRIMARY"(window, text);
5995 	}
5996 
5997 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
5998 	void setSecondarySelection(SimpleWindow window, string text) {
5999 		setX11Selection!"SECONDARY"(window, text);
6000 	}
6001 
6002 	interface X11SetSelectionHandler {
6003 		// should include TARGETS right now
6004 		Atom[] availableFormats();
6005 		// Return the slice of data you filled, empty slice if done.
6006 		// this is to support the incremental thing
6007 		ubyte[] getData(Atom format, return scope ubyte[] data);
6008 
6009 		void done();
6010 
6011 		void handleRequest(XEvent);
6012 
6013 		bool matchesIncr(Window, Atom);
6014 		void sendMoreIncr(XPropertyEvent*);
6015 	}
6016 
6017 	mixin template X11SetSelectionHandler_Basics() {
6018 		Window incrWindow;
6019 		Atom incrAtom;
6020 		Atom selectionAtom;
6021 		Atom formatAtom;
6022 		ubyte[] toSend;
6023 		bool matchesIncr(Window w, Atom a) {
6024 			return incrAtom && incrAtom == a && w == incrWindow;
6025 		}
6026 		void sendMoreIncr(XPropertyEvent* event) {
6027 			auto display = XDisplayConnection.get;
6028 
6029 			XChangeProperty (display,
6030 				incrWindow,
6031 				incrAtom,
6032 				formatAtom,
6033 				8 /* bits */, PropModeReplace,
6034 				toSend.ptr, cast(int) toSend.length);
6035 
6036 			if(toSend.length != 0) {
6037 				toSend = this.getData(formatAtom, toSend[]);
6038 			} else {
6039 				this.done();
6040 				incrWindow = None;
6041 				incrAtom = None;
6042 				selectionAtom = None;
6043 				formatAtom = None;
6044 				toSend = null;
6045 			}
6046 		}
6047 		void handleRequest(XEvent ev) {
6048 
6049 			auto display = XDisplayConnection.get;
6050 
6051 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6052 			XSelectionEvent selectionEvent;
6053 			selectionEvent.type = EventType.SelectionNotify;
6054 			selectionEvent.display = event.display;
6055 			selectionEvent.requestor = event.requestor;
6056 			selectionEvent.selection = event.selection;
6057 			selectionEvent.time = event.time;
6058 			selectionEvent.target = event.target;
6059 
6060 			bool supportedType() {
6061 				foreach(t; this.availableFormats())
6062 					if(t == event.target)
6063 						return true;
6064 				return false;
6065 			}
6066 
6067 			if(event.property == None) {
6068 				selectionEvent.property = event.target;
6069 
6070 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6071 				XFlush(display);
6072 			} if(event.target == GetAtom!"TARGETS"(display)) {
6073 				/* respond with the supported types */
6074 				auto tlist = this.availableFormats();
6075 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6076 				selectionEvent.property = event.property;
6077 
6078 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6079 				XFlush(display);
6080 			} else if(supportedType()) {
6081 				auto buffer = new ubyte[](1024 * 64);
6082 				auto toSend = this.getData(event.target, buffer[]);
6083 
6084 				if(toSend.length < 32 * 1024) {
6085 					// small enough to send directly...
6086 					selectionEvent.property = event.property;
6087 					XChangeProperty (display,
6088 						selectionEvent.requestor,
6089 						selectionEvent.property,
6090 						event.target,
6091 						8 /* bits */, 0 /* PropModeReplace */,
6092 						toSend.ptr, cast(int) toSend.length);
6093 
6094 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6095 					XFlush(display);
6096 				} else {
6097 					// large, let's send incrementally
6098 					arch_ulong l = toSend.length;
6099 
6100 					// if I wanted other events from this window don't want to clear that out....
6101 					XWindowAttributes xwa;
6102 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6103 
6104 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6105 
6106 					incrWindow = event.requestor;
6107 					incrAtom = event.property;
6108 					formatAtom = event.target;
6109 					selectionAtom = event.selection;
6110 					this.toSend = toSend;
6111 
6112 					selectionEvent.property = event.property;
6113 					XChangeProperty (display,
6114 						selectionEvent.requestor,
6115 						selectionEvent.property,
6116 						GetAtom!"INCR"(display),
6117 						32 /* bits */, PropModeReplace,
6118 						&l, 1);
6119 
6120 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6121 					XFlush(display);
6122 				}
6123 				//if(after)
6124 					//after();
6125 			} else {
6126 				debug(sdpy_clip) {
6127 					import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display));
6128 				}
6129 				selectionEvent.property = None; // I don't know how to handle this type...
6130 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6131 				XFlush(display);
6132 			}
6133 		}
6134 	}
6135 
6136 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6137 		mixin X11SetSelectionHandler_Basics;
6138 		private const(ubyte)[] text;
6139 		private const(ubyte)[] text_original;
6140 		this(string text) {
6141 			this.text = cast(const ubyte[]) text;
6142 			this.text_original = this.text;
6143 		}
6144 		Atom[] availableFormats() {
6145 			auto display = XDisplayConnection.get;
6146 			return [
6147 				GetAtom!"UTF8_STRING"(display),
6148 				GetAtom!"text/plain"(display),
6149 				XA_STRING,
6150 				GetAtom!"TARGETS"(display)
6151 			];
6152 		}
6153 
6154 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6155 			if(text.length < data.length) {
6156 				data[0 .. text.length] = text[];
6157 				return data[0 .. text.length];
6158 			} else {
6159 				data[] = text[0 .. data.length];
6160 				text = text[data.length .. $];
6161 				return data[];
6162 			}
6163 		}
6164 
6165 		void done() {
6166 			text = text_original;
6167 		}
6168 	}
6169 
6170 	/// 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?!)
6171 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6172 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6173 	}
6174 
6175 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6176 		assert(window !is null);
6177 
6178 		auto display = XDisplayConnection.get();
6179 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6180 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6181 		else Atom a = GetAtom!atomName(display);
6182 
6183 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6184 
6185 		window.impl.setSelectionHandlers[a] = data;
6186 	}
6187 
6188 	///
6189 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6190 		getX11Selection!"PRIMARY"(window, handler);
6191 	}
6192 
6193 	// added July 28, 2020
6194 	// undocumented as experimental tho
6195 	interface X11GetSelectionHandler {
6196 		void handleData(Atom target, in ubyte[] data);
6197 		Atom findBestFormat(Atom[] answer);
6198 
6199 		void prepareIncremental(Window, Atom);
6200 		bool matchesIncr(Window, Atom);
6201 		void handleIncrData(Atom, in ubyte[] data);
6202 	}
6203 
6204 	mixin template X11GetSelectionHandler_Basics() {
6205 		Window incrWindow;
6206 		Atom incrAtom;
6207 
6208 		void prepareIncremental(Window w, Atom a) {
6209 			incrWindow = w;
6210 			incrAtom = a;
6211 		}
6212 		bool matchesIncr(Window w, Atom a) {
6213 			return incrWindow == w && incrAtom == a;
6214 		}
6215 
6216 		Atom incrFormatAtom;
6217 		ubyte[] incrData;
6218 		void handleIncrData(Atom format, in ubyte[] data) {
6219 			incrFormatAtom = format;
6220 
6221 			if(data.length)
6222 				incrData ~= data;
6223 			else
6224 				handleData(incrFormatAtom, incrData);
6225 
6226 		}
6227 	}
6228 
6229 	///
6230 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6231 		assert(window !is null);
6232 
6233 		auto display = XDisplayConnection.get();
6234 		auto atom = GetAtom!atomName(display);
6235 
6236 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6237 			this(void delegate(in char[]) handler) {
6238 				this.handler = handler;
6239 			}
6240 
6241 			mixin X11GetSelectionHandler_Basics;
6242 
6243 			void delegate(in char[]) handler;
6244 
6245 			void handleData(Atom target, in ubyte[] data) {
6246 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6247 					handler(cast(const char[]) data);
6248 			}
6249 
6250 			Atom findBestFormat(Atom[] answer) {
6251 				Atom best = None;
6252 				foreach(option; answer) {
6253 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6254 						best = option;
6255 						break;
6256 					} else if(option == XA_STRING) {
6257 						best = option;
6258 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6259 						best = option;
6260 					}
6261 				}
6262 				return best;
6263 			}
6264 		}
6265 
6266 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6267 
6268 		auto target = GetAtom!"TARGETS"(display);
6269 
6270 		// SDD_DATA is "simpledisplay.d data"
6271 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6272 	}
6273 
6274 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6275 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6276 		assert(window !is null);
6277 
6278 		auto display = XDisplayConnection.get();
6279 		auto atom = GetAtom!atomName(display);
6280 
6281 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6282 			this(void delegate(MemoryImage) handler) {
6283 				this.handler = handler;
6284 			}
6285 
6286 			mixin X11GetSelectionHandler_Basics;
6287 
6288 			void delegate(MemoryImage) handler;
6289 
6290 			void handleData(Atom target, in ubyte[] data) {
6291 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6292 					import arsd.bmp;
6293 					handler(readBmp(data));
6294 				}
6295 			}
6296 
6297 			Atom findBestFormat(Atom[] answer) {
6298 				Atom best = None;
6299 				foreach(option; answer) {
6300 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6301 						best = option;
6302 					}
6303 				}
6304 				return best;
6305 			}
6306 
6307 		}
6308 
6309 
6310 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6311 
6312 		auto target = GetAtom!"TARGETS"(display);
6313 
6314 		// SDD_DATA is "simpledisplay.d data"
6315 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6316 	}
6317 
6318 
6319 	///
6320 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6321 		Atom actualType;
6322 		int actualFormat;
6323 		arch_ulong actualItems;
6324 		arch_ulong bytesRemaining;
6325 		void* data;
6326 
6327 		auto display = XDisplayConnection.get();
6328 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6329 			if(actualFormat == 0)
6330 				return null;
6331 			else {
6332 				int byteLength;
6333 				if(actualFormat == 32) {
6334 					// 32 means it is a C long... which is variable length
6335 					actualFormat = cast(int) arch_long.sizeof * 8;
6336 				}
6337 
6338 				// then it is just a bit count
6339 				byteLength = cast(int) (actualItems * actualFormat / 8);
6340 
6341 				auto d = new ubyte[](byteLength);
6342 				d[] = cast(ubyte[]) data[0 .. byteLength];
6343 				XFree(data);
6344 				return d;
6345 			}
6346 		}
6347 		return null;
6348 	}
6349 
6350 	/* defined in the systray spec */
6351 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6352 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6353 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6354 
6355 
6356 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6357 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6358 	public class GlobalHotkey {
6359 		KeyEvent key;
6360 		void delegate () handler;
6361 
6362 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6363 
6364 		/// Create from initialzed KeyEvent object
6365 		this (KeyEvent akey, void delegate () ahandler=null) {
6366 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6367 			key = akey;
6368 			handler = ahandler;
6369 		}
6370 
6371 		/// Create from emacs-like key name ("C-M-Y", etc.)
6372 		this (const(char)[] akey, void delegate () ahandler=null) {
6373 			key = KeyEvent.parse(akey);
6374 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6375 			handler = ahandler;
6376 		}
6377 
6378 	}
6379 
6380 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6381 		//conwriteln("failed to grab key");
6382 		GlobalHotkeyManager.ghfailed = true;
6383 		return 0;
6384 	}
6385 
6386 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6387 		Image.impl.xshmfailed = true;
6388 		return 0;
6389 	}
6390 
6391 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6392 		import core.stdc.stdio;
6393 		char[265] buffer;
6394 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6395 		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);
6396 		return 0;
6397 	}
6398 
6399 	/++
6400 		Global hotkey manager. It contains static methods to manage global hotkeys.
6401 
6402 		---
6403 		 try {
6404 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6405 		} catch (Exception e) {
6406 			conwriteln("ERROR registering hotkey!");
6407 		}
6408 		EventLoop.get.run();
6409 		---
6410 
6411 		The key strings are based on Emacs. In practical terms,
6412 		`M` means `alt` and `H` means the Windows logo key. `C`
6413 		is `ctrl`.
6414 
6415 		$(WARNING
6416 			This is X-specific right now. If you are on
6417 			Windows, try [registerHotKey] instead.
6418 
6419 			We will probably merge these into a single
6420 			interface later.
6421 		)
6422 	+/
6423 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6424 		version(X11) {
6425 			void recreateAfterDisconnect() {
6426 				throw new Exception("NOT IMPLEMENTED");
6427 			}
6428 			void discardConnectionState() {
6429 				throw new Exception("NOT IMPLEMENTED");
6430 			}
6431 		}
6432 
6433 		private static immutable uint[8] masklist = [ 0,
6434 			KeyOrButtonMask.LockMask,
6435 			KeyOrButtonMask.Mod2Mask,
6436 			KeyOrButtonMask.Mod3Mask,
6437 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6438 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6439 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6440 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6441 		];
6442 		private __gshared GlobalHotkeyManager ghmanager;
6443 		private __gshared bool ghfailed = false;
6444 
6445 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6446 			if (modmask == 0) return false;
6447 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6448 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6449 			return true;
6450 		}
6451 
6452 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6453 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6454 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6455 			return modmask;
6456 		}
6457 
6458 		private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) {
6459 			uint keycode = cast(uint)ke.key;
6460 			auto dpy = XDisplayConnection.get;
6461 			return XKeysymToKeycode(dpy, keycode);
6462 		}
6463 
6464 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6465 
6466 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6467 
6468 		NativeEventHandler getNativeEventHandler () {
6469 			return delegate int (XEvent e) {
6470 				if (e.type != EventType.KeyPress) return 1;
6471 				auto kev = cast(const(XKeyEvent)*)&e;
6472 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6473 				if (auto ghkp = hash in globalHotkeyList) {
6474 					try {
6475 						ghkp.doHandle();
6476 					} catch (Exception e) {
6477 						import core.stdc.stdio : stderr, fprintf;
6478 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6479 					}
6480 				}
6481 				return 1;
6482 			};
6483 		}
6484 
6485 		private this () {
6486 			auto dpy = XDisplayConnection.get;
6487 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6488 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6489 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6490 		}
6491 
6492 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6493 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6494 		static void register (GlobalHotkey gh) {
6495 			if (gh is null) return;
6496 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6497 
6498 			auto dpy = XDisplayConnection.get;
6499 			immutable keycode = keyEvent2KeyCode(gh.key);
6500 
6501 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6502 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6503 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6504 			XSync(dpy, 0/*False*/);
6505 
6506 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6507 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6508 			ghfailed = false;
6509 			foreach (immutable uint ormask; masklist[]) {
6510 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6511 			}
6512 			XSync(dpy, 0/*False*/);
6513 			XSetErrorHandler(savedErrorHandler);
6514 
6515 			if (ghfailed) {
6516 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6517 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6518 				XSync(dpy, 0/*False*/);
6519 				XSetErrorHandler(savedErrorHandler);
6520 				throw new Exception("cannot register global hotkey");
6521 			}
6522 
6523 			globalHotkeyList[hash] = gh;
6524 		}
6525 
6526 		/// Ditto
6527 		static void register (const(char)[] akey, void delegate () ahandler) {
6528 			register(new GlobalHotkey(akey, ahandler));
6529 		}
6530 
6531 		private static void removeByHash (ulong hash) {
6532 			if (auto ghp = hash in globalHotkeyList) {
6533 				auto dpy = XDisplayConnection.get;
6534 				immutable keycode = keyEvent2KeyCode(ghp.key);
6535 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6536 				XSync(dpy, 0/*False*/);
6537 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6538 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6539 				XSync(dpy, 0/*False*/);
6540 				XSetErrorHandler(savedErrorHandler);
6541 				globalHotkeyList.remove(hash);
6542 			}
6543 		}
6544 
6545 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6546 		/// It is safe to unregister unknown or invalid hotkey.
6547 		static void unregister (GlobalHotkey gh) {
6548 			//TODO: add second AA for faster search? prolly doesn't worth it.
6549 			if (gh is null) return;
6550 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6551 				if (kv.value is gh) {
6552 					removeByHash(kv.key);
6553 					return;
6554 				}
6555 			}
6556 		}
6557 
6558 		/// Ditto.
6559 		static void unregister (const(char)[] key) {
6560 			auto kev = KeyEvent.parse(key);
6561 			immutable keycode = keyEvent2KeyCode(kev);
6562 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6563 		}
6564 	}
6565 }
6566 
6567 version(Windows) {
6568 	/++
6569 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6570 
6571 		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).
6572 	+/
6573 	void sendSyntheticInput(wstring s) {
6574 			INPUT[] inputs;
6575 			inputs.reserve(s.length * 2);
6576 
6577 			foreach(wchar c; s) {
6578 				INPUT input;
6579 				input.type = INPUT_KEYBOARD;
6580 				input.ki.wScan = c;
6581 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6582 				inputs ~= input;
6583 
6584 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6585 				inputs ~= input;
6586 			}
6587 
6588 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6589 				throw new Exception("SendInput failed");
6590 			}
6591 
6592 	}
6593 
6594 
6595 	// global hotkey helper function
6596 
6597 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6598 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6599 		__gshared int hotkeyId = 0;
6600 		int id = ++hotkeyId;
6601 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6602 			throw new Exception("RegisterHotKey failed");
6603 
6604 		__gshared void delegate()[WPARAM][HWND] handlers;
6605 
6606 		handlers[window.impl.hwnd][id] = handler;
6607 
6608 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6609 
6610 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6611 			switch(msg) {
6612 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6613 				case WM_HOTKEY:
6614 					if(auto list = hwnd in handlers) {
6615 						if(auto h = wParam in *list) {
6616 							(*h)();
6617 							return 0;
6618 						}
6619 					}
6620 				goto default;
6621 				default:
6622 			}
6623 			if(oldHandler)
6624 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6625 			return 1; // pass it on
6626 		};
6627 
6628 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6629 			oldHandler = window.handleNativeEvent;
6630 			window.handleNativeEvent = nativeEventHandler;
6631 		}
6632 
6633 		return id;
6634 	}
6635 
6636 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6637 	void unregisterHotKey(SimpleWindow window, int id) {
6638 		if(!UnregisterHotKey(window.impl.hwnd, id))
6639 			throw new Exception("UnregisterHotKey");
6640 	}
6641 }
6642 
6643 version (X11) {
6644 	pragma(lib, "dl");
6645 	import core.sys.posix.dlfcn;
6646 }
6647 
6648 /++
6649 	Allows for sending synthetic input to the X server via the Xtst
6650 	extension or on Windows using SendInput.
6651 
6652 	Please remember user input is meant to be user - don't use this
6653 	if you have some other alternative!
6654 
6655 	History:
6656 		Added May 17, 2020 with the X implementation.
6657 
6658 		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.)
6659 	Bugs:
6660 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6661 +/
6662 struct SyntheticInput {
6663 	@disable this();
6664 
6665 	private int* refcount;
6666 
6667 	version(X11) {
6668 		private void* lib;
6669 
6670 		private extern(C) {
6671 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6672 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6673 		}
6674 	}
6675 
6676 	/// The dummy param must be 0.
6677 	this(int dummy) {
6678 		version(X11) {
6679 			lib = dlopen("libXtst.so", RTLD_NOW);
6680 			if(lib is null)
6681 				throw new Exception("cannot load xtest lib extension");
6682 			scope(failure)
6683 				dlclose(lib);
6684 
6685 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6686 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6687 
6688 			if(XTestFakeKeyEvent is null)
6689 				throw new Exception("No XTestFakeKeyEvent");
6690 			if(XTestFakeButtonEvent is null)
6691 				throw new Exception("No XTestFakeButtonEvent");
6692 		}
6693 
6694 		refcount = new int;
6695 		*refcount = 1;
6696 	}
6697 
6698 	this(this) {
6699 		if(refcount)
6700 			*refcount += 1;
6701 	}
6702 
6703 	~this() {
6704 		if(refcount) {
6705 			*refcount -= 1;
6706 			if(*refcount == 0)
6707 				// I commented this because if I close the lib before
6708 				// XCloseDisplay, it is liable to segfault... so just
6709 				// gonna keep it loaded if it is loaded, no big deal
6710 				// anyway.
6711 				{} // dlclose(lib);
6712 		}
6713 	}
6714 
6715 	/++
6716 		Simulates typing a string into the keyboard.
6717 
6718 		Bugs:
6719 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6720 
6721 			Not implemented except on Windows and X11.
6722 	+/
6723 	void sendSyntheticInput(string s) {
6724 		version(Windows) {
6725 			INPUT[] inputs;
6726 			inputs.reserve(s.length * 2);
6727 
6728 			auto ei = GetMessageExtraInfo();
6729 
6730 			foreach(wchar c; s) {
6731 				INPUT input;
6732 				input.type = INPUT_KEYBOARD;
6733 				input.ki.wScan = c;
6734 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6735 				input.ki.dwExtraInfo = ei;
6736 				inputs ~= input;
6737 
6738 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6739 				inputs ~= input;
6740 			}
6741 
6742 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6743 				throw new Exception("SendInput failed");
6744 			}
6745 		} else version(X11) {
6746 			int delay = 0;
6747 			foreach(ch; s) {
6748 				pressKey(cast(Key) ch, true, delay);
6749 				pressKey(cast(Key) ch, false, delay);
6750 				delay += 5;
6751 			}
6752 		} else throw new NotYetImplementedException();
6753 	}
6754 
6755 	/++
6756 		Sends a fake press or release key event.
6757 
6758 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6759 
6760 		Bugs:
6761 			The `delay` parameter is not implemented yet on Windows.
6762 
6763 			Not implemented except on Windows and X11.
6764 	+/
6765 	void pressKey(Key key, bool pressed, int delay = 0) {
6766 		version(Windows) {
6767 			INPUT input;
6768 			input.type = INPUT_KEYBOARD;
6769 			input.ki.wVk = cast(ushort) key;
6770 
6771 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6772 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6773 
6774 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6775 				throw new Exception("SendInput failed");
6776 			}
6777 		} else version(X11) {
6778 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6779 		} else throw new NotYetImplementedException();
6780 	}
6781 
6782 	/++
6783 		Sends a fake mouse button press or release event.
6784 
6785 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6786 
6787 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6788 
6789 		Bugs:
6790 			The `delay` parameter is not implemented yet on Windows.
6791 
6792 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6793 
6794 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6795 	+/
6796 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6797 		version(Windows) {
6798 			INPUT input;
6799 			input.type = INPUT_MOUSE;
6800 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6801 
6802 			// input.mi.mouseData for a wheel event
6803 
6804 			switch(button) {
6805 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6806 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6807 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6808 				case MouseButton.wheelUp:
6809 				case MouseButton.wheelDown:
6810 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6811 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6812 				break;
6813 				case MouseButton.backButton: throw new NotYetImplementedException();
6814 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6815 				default:
6816 			}
6817 
6818 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6819 				throw new Exception("SendInput failed");
6820 			}
6821 		} else version(X11) {
6822 			int btn;
6823 
6824 			switch(button) {
6825 				case MouseButton.left: btn = 1; break;
6826 				case MouseButton.middle: btn = 2; break;
6827 				case MouseButton.right: btn = 3; break;
6828 				case MouseButton.wheelUp: btn = 4; break;
6829 				case MouseButton.wheelDown: btn = 5; break;
6830 				case MouseButton.backButton: btn = 8; break;
6831 				case MouseButton.forwardButton: btn = 9; break;
6832 				default:
6833 			}
6834 
6835 			assert(btn);
6836 
6837 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6838 		} else throw new NotYetImplementedException();
6839 	}
6840 
6841 	///
6842 	static void moveMouseArrowBy(int dx, int dy) {
6843 		version(Windows) {
6844 			INPUT input;
6845 			input.type = INPUT_MOUSE;
6846 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6847 			input.mi.dx = dx;
6848 			input.mi.dy = dy;
6849 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
6850 
6851 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6852 				throw new Exception("SendInput failed");
6853 			}
6854 		} else version(X11) {
6855 			auto disp = XDisplayConnection.get();
6856 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
6857 			XFlush(disp);
6858 		} else throw new NotYetImplementedException();
6859 	}
6860 
6861 	///
6862 	static void moveMouseArrowTo(int x, int y) {
6863 		version(Windows) {
6864 			INPUT input;
6865 			input.type = INPUT_MOUSE;
6866 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6867 			input.mi.dx = x;
6868 			input.mi.dy = y;
6869 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
6870 
6871 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6872 				throw new Exception("SendInput failed");
6873 			}
6874 		} else version(X11) {
6875 			auto disp = XDisplayConnection.get();
6876 			auto root = RootWindow(disp, DefaultScreen(disp));
6877 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
6878 			XFlush(disp);
6879 		} else throw new NotYetImplementedException();
6880 	}
6881 }
6882 
6883 
6884 
6885 /++
6886 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
6887 
6888 	See_Also:
6889 	$(LIST
6890 		*[ScreenPainter]
6891 		*[ScreenPainter.rasterOp]
6892 	)
6893 +/
6894 enum RasterOp {
6895 	normal, /// Replaces the pixel.
6896 	xor, /// Uses bitwise xor to draw.
6897 }
6898 
6899 // being phobos-free keeps the size WAY down
6900 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
6901 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
6902 package(arsd) const(wchar)* toWStringz(string s) {
6903 	wstring r;
6904 	foreach(dchar c; s)
6905 		r ~= c;
6906 	r ~= '\0';
6907 	return r.ptr;
6908 }
6909 private string[] split(in void[] a, char c) {
6910 		string[] ret;
6911 		size_t previous = 0;
6912 		foreach(i, char ch; cast(ubyte[]) a) {
6913 			if(ch == c) {
6914 				ret ~= cast(string) a[previous .. i];
6915 				previous = i + 1;
6916 			}
6917 		}
6918 		if(previous != a.length)
6919 			ret ~= cast(string) a[previous .. $];
6920 		return ret;
6921 	}
6922 
6923 version(without_opengl) {
6924 	enum OpenGlOptions {
6925 		no,
6926 	}
6927 } else {
6928 	/++
6929 		Determines if you want an OpenGL context created on the new window.
6930 
6931 
6932 		See more: [#topics-3d|in the 3d topic].
6933 
6934 		---
6935 		import arsd.simpledisplay;
6936 		void main() {
6937 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
6938 
6939 			// Set up the matrix
6940 			window.setAsCurrentOpenGlContext(); // make this window active
6941 
6942 			// This is called on each frame, we will draw our scene
6943 			window.redrawOpenGlScene = delegate() {
6944 
6945 			};
6946 
6947 			window.eventLoop(0);
6948 		}
6949 		---
6950 	+/
6951 	enum OpenGlOptions {
6952 		no, /// No OpenGL context is created
6953 		yes, /// Yes, create an OpenGL context
6954 	}
6955 
6956 	version(X11) {
6957 		static if (!SdpyIsUsingIVGLBinds) {
6958 
6959 
6960 			struct __GLXFBConfigRec {}
6961 			alias GLXFBConfig = __GLXFBConfigRec*;
6962 
6963 			//pragma(lib, "GL");
6964 			//pragma(lib, "GLU");
6965 			interface GLX {
6966 			extern(C) nothrow @nogc {
6967 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
6968 						const int *attrib_list);
6969 
6970 				 void glXCopyContext(Display *dpy, GLXContext src,
6971 						GLXContext dst, arch_ulong mask);
6972 
6973 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
6974 						GLXContext share_list, Bool direct);
6975 
6976 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
6977 						Pixmap pixmap);
6978 
6979 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
6980 
6981 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
6982 
6983 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
6984 						int attrib, int *value);
6985 
6986 				 GLXContext glXGetCurrentContext();
6987 
6988 				 GLXDrawable glXGetCurrentDrawable();
6989 
6990 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
6991 
6992 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
6993 						GLXContext ctx);
6994 
6995 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
6996 
6997 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
6998 
6999 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7000 
7001 				 void glXUseXFont(Font font, int first, int count, int list_base);
7002 
7003 				 void glXWaitGL();
7004 
7005 				 void glXWaitX();
7006 
7007 
7008 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7009 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7010 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7011 
7012 				char* glXQueryExtensionsString (Display*, int);
7013 				void* glXGetProcAddress (const(char)*);
7014 
7015 			}
7016 			}
7017 
7018 			version(OSX)
7019 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7020 			else
7021 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7022 			shared static this() {
7023 				glx.loadDynamicLibrary();
7024 			}
7025 
7026 			alias glbindGetProcAddress = glXGetProcAddress;
7027 		}
7028 	} else version(Windows) {
7029 		/* it is done below by interface GL */
7030 	} else
7031 		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.");
7032 }
7033 
7034 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7035 alias Resizablity = Resizability;
7036 
7037 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7038 enum Resizability {
7039 	fixedSize, /// the window cannot be resized
7040 	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.
7041 	automaticallyScaleIfPossible, /// if possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size. If this is impossible, it will not allow the user to resize the window at all. Note: window.width and window.height WILL be adjusted, which might throw you off if you draw based on them, so keep track of your expected width and height separately. That way, when it is scaled, things won't be thrown off.
7042 
7043 	// FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events
7044 }
7045 
7046 
7047 /++
7048 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7049 +/
7050 enum TextAlignment : uint {
7051 	Left = 0, ///
7052 	Center = 1, ///
7053 	Right = 2, ///
7054 
7055 	VerticalTop = 0, ///
7056 	VerticalCenter = 4, ///
7057 	VerticalBottom = 8, ///
7058 }
7059 
7060 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7061 alias Rectangle = arsd.color.Rectangle;
7062 
7063 
7064 /++
7065 	Keyboard press and release events.
7066 +/
7067 struct KeyEvent {
7068 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7069 	Key key;
7070 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7071 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7072 
7073 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7074 
7075 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7076 
7077 	SimpleWindow window; /// associated Window
7078 
7079 	/++
7080 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7081 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7082 		to predict if char events are actually coming..
7083 
7084 		Only available on X systems since this information is not given ahead of time elsewhere.
7085 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7086 
7087 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7088 		and potential quirks I'd recommend avoiding it.
7089 
7090 		History:
7091 			Added April 26, 2021 (dub v9.5)
7092 	+/
7093 	version(X11)
7094 		dchar[] charsPossible;
7095 
7096 	// convert key event to simplified string representation a-la emacs
7097 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7098 		uint dpos = 0;
7099 		void put (const(char)[] s...) nothrow @trusted {
7100 			static if (growdest) {
7101 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7102 			} else {
7103 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7104 			}
7105 		}
7106 
7107 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7108 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7109 		}
7110 
7111 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7112 
7113 		// put modifiers
7114 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7115 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7116 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7117 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7118 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7119 
7120 		if (this.key) {
7121 			foreach (string kn; __traits(allMembers, Key)) {
7122 				if (this.key == __traits(getMember, Key, kn)) {
7123 					// HACK!
7124 					static if (kn == "N0") put("0");
7125 					else static if (kn == "N1") put("1");
7126 					else static if (kn == "N2") put("2");
7127 					else static if (kn == "N3") put("3");
7128 					else static if (kn == "N4") put("4");
7129 					else static if (kn == "N5") put("5");
7130 					else static if (kn == "N6") put("6");
7131 					else static if (kn == "N7") put("7");
7132 					else static if (kn == "N8") put("8");
7133 					else static if (kn == "N9") put("9");
7134 					else put(kn);
7135 					return dest[0..dpos];
7136 				}
7137 			}
7138 			put("Unknown");
7139 		} else {
7140 			if (dpos && dest[dpos-1] == '+') --dpos;
7141 		}
7142 		return dest[0..dpos];
7143 	}
7144 
7145 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7146 
7147 	/** Parse string into key name with modifiers. It accepts things like:
7148 	 *
7149 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7150 	 *
7151 	 * Ctrl+Win+1 -- windows style
7152 	 *
7153 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7154 	 *
7155 	 * Ctrl Win 1 -- and space
7156 	 *
7157 	 * and even "Win + 1 + Ctrl".
7158 	 */
7159 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7160 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7161 
7162 		// remove trailing spaces
7163 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7164 
7165 		// tokens delimited by blank, '+', or '-'
7166 		// null on eol
7167 		const(char)[] getToken () nothrow @trusted @nogc {
7168 			// remove leading spaces and delimiters
7169 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7170 			if (name.length == 0) return null; // oops, no more tokens
7171 			// get token
7172 			size_t epos = 0;
7173 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7174 			assert(epos > 0 && epos <= name.length);
7175 			auto res = name[0..epos];
7176 			name = name[epos..$];
7177 			return res;
7178 		}
7179 
7180 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7181 			if (s0.length != s1.length) return false;
7182 			foreach (immutable ci, char c0; s0) {
7183 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7184 				char c1 = s1[ci];
7185 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7186 				if (c0 != c1) return false;
7187 			}
7188 			return true;
7189 		}
7190 
7191 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7192 		if (updown !is null) *updown = -1;
7193 		KeyEvent res;
7194 		res.key = cast(Key)0; // just in case
7195 		const(char)[] tk, tkn; // last token
7196 		bool allowEmascStyle = true;
7197 		bool ignoreModifiers = false;
7198 		tokenloop: for (;;) {
7199 			tk = tkn;
7200 			tkn = getToken();
7201 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7202 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7203 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7204 			if (allowEmascStyle && tkn.length != 0) {
7205 				if (tk.length == 1) {
7206 					char mdc = tk[0];
7207 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7208 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7209 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7210 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7211 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7212 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7213 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7214 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7215 				}
7216 			}
7217 			allowEmascStyle = false;
7218 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7219 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7220 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7221 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7222 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7223 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7224 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7225 			if (tk.length == 0) continue;
7226 			// try key name
7227 			if (res.key == 0) {
7228 				// little hack
7229 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7230 					final switch (tk[0]) {
7231 						case '0': tk = "N0"; break;
7232 						case '1': tk = "N1"; break;
7233 						case '2': tk = "N2"; break;
7234 						case '3': tk = "N3"; break;
7235 						case '4': tk = "N4"; break;
7236 						case '5': tk = "N5"; break;
7237 						case '6': tk = "N6"; break;
7238 						case '7': tk = "N7"; break;
7239 						case '8': tk = "N8"; break;
7240 						case '9': tk = "N9"; break;
7241 					}
7242 				}
7243 				foreach (string kn; __traits(allMembers, Key)) {
7244 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7245 				}
7246 			}
7247 			// unknown or duplicate key name, get out of here
7248 			break;
7249 		}
7250 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7251 		return res; // something
7252 	}
7253 
7254 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7255 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7256 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7257 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7258 		}
7259 		bool ignoreMods;
7260 		int updown;
7261 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7262 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7263 		if (this.key != ke.key) {
7264 			// things like "ctrl+alt" are complicated
7265 			uint tkm = this.modifierState&modmask;
7266 			uint kkm = ke.modifierState&modmask;
7267 			Key tk = this.key;
7268 			// ke
7269 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7270 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7271 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7272 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7273 			// this
7274 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7275 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7276 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7277 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7278 			return (tk == ke.key && tkm == kkm);
7279 		}
7280 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7281 	}
7282 }
7283 
7284 /// Sets the application name.
7285 @property string ApplicationName(string name) {
7286 	return _applicationName = name;
7287 }
7288 
7289 string _applicationName;
7290 
7291 /// ditto
7292 @property string ApplicationName() {
7293 	if(_applicationName is null) {
7294 		import core.runtime;
7295 		return Runtime.args[0];
7296 	}
7297 	return _applicationName;
7298 }
7299 
7300 
7301 /// Type of a [MouseEvent].
7302 enum MouseEventType : int {
7303 	motion = 0, /// The mouse moved inside the window
7304 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7305 	buttonReleased = 2, /// A mouse button was released
7306 }
7307 
7308 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7309 /++
7310 	Listen for this on your event listeners if you are interested in mouse action.
7311 
7312 	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.
7313 
7314 	Examples:
7315 
7316 	This will draw boxes on the window with the mouse as you hold the left button.
7317 	---
7318 	import arsd.simpledisplay;
7319 
7320 	void main() {
7321 		auto window = new SimpleWindow();
7322 
7323 		window.eventLoop(0,
7324 			(MouseEvent ev) {
7325 				if(ev.modifierState & ModifierState.leftButtonDown) {
7326 					auto painter = window.draw();
7327 					painter.fillColor = Color.red;
7328 					painter.outlineColor = Color.black;
7329 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7330 				}
7331 			}
7332 		);
7333 	}
7334 	---
7335 +/
7336 struct MouseEvent {
7337 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7338 
7339 	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.
7340 	int y; /// Current Y position of the cursor when the event fired.
7341 
7342 	int dx; /// Change in X position since last report
7343 	int dy; /// Change in Y position since last report
7344 
7345 	MouseButton button; /// See [MouseButton]
7346 	int modifierState; /// See [ModifierState]
7347 
7348 	version(X11)
7349 		private Time timestamp;
7350 
7351 	/// Returns a linear representation of mouse button,
7352 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7353 	///
7354 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7355 	@property ubyte buttonLinear() const {
7356 		import core.bitop;
7357 		if(button == 0)
7358 			return 0;
7359 		return (bsf(button) + 1) & 0b1111;
7360 	}
7361 
7362 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7363 
7364 	SimpleWindow window; /// The window in which the event happened.
7365 
7366 	Point globalCoordinates() {
7367 		Point p;
7368 		if(window is null)
7369 			throw new Exception("wtf");
7370 		static if(UsingSimpledisplayX11) {
7371 			Window child;
7372 			XTranslateCoordinates(
7373 				XDisplayConnection.get,
7374 				window.impl.window,
7375 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7376 				x, y, &p.x, &p.y, &child);
7377 			return p;
7378 		} else version(Windows) {
7379 			POINT[1] points;
7380 			points[0].x = x;
7381 			points[0].y = y;
7382 			MapWindowPoints(
7383 				window.impl.hwnd,
7384 				null,
7385 				points.ptr,
7386 				points.length
7387 			);
7388 			p.x = points[0].x;
7389 			p.y = points[0].y;
7390 
7391 			return p;
7392 		} else version(OSXCocoa) {
7393 			throw new NotYetImplementedException();
7394 		} else static assert(0);
7395 	}
7396 
7397 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7398 
7399 	/**
7400 	can contain emacs-like modifier prefix
7401 	case-insensitive names:
7402 		lmbX/leftX
7403 		rmbX/rightX
7404 		mmbX/middleX
7405 		wheelX
7406 		motion (no prefix allowed)
7407 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7408 	*/
7409 	static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7410 		if (str.length == 0) return false; // just in case
7411 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7412 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7413 		auto anchor = str;
7414 		uint mods = 0; // uint.max == any
7415 		// interesting bits in kmod
7416 		uint kmodmask =
7417 			ModifierState.shift|
7418 			ModifierState.ctrl|
7419 			ModifierState.alt|
7420 			ModifierState.windows|
7421 			ModifierState.leftButtonDown|
7422 			ModifierState.middleButtonDown|
7423 			ModifierState.rightButtonDown|
7424 			0;
7425 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7426 		bool wasButtons = false;
7427 		while (str.length) {
7428 			if (str.ptr[0] <= ' ') {
7429 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7430 				continue;
7431 			}
7432 			// one-letter modifier?
7433 			if (str.length >= 2 && str.ptr[1] == '-') {
7434 				switch (str.ptr[0]) {
7435 					case '*': // "any" modifier (cannot be undone)
7436 						mods = mods.max;
7437 						break;
7438 					case 'C': case 'c': // emacs "ctrl"
7439 						if (mods != mods.max) mods |= ModifierState.ctrl;
7440 						break;
7441 					case 'M': case 'm': // emacs "meta"
7442 						if (mods != mods.max) mods |= ModifierState.alt;
7443 						break;
7444 					case 'S': case 's': // emacs "shift"
7445 						if (mods != mods.max) mods |= ModifierState.shift;
7446 						break;
7447 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7448 						if (mods != mods.max) mods |= ModifierState.windows;
7449 						break;
7450 					default:
7451 						return false; // unknown modifier
7452 				}
7453 				str = str[2..$];
7454 				continue;
7455 			}
7456 			// word
7457 			char[16] buf = void; // locased
7458 			auto wep = 0;
7459 			while (str.length) {
7460 				immutable char ch = str.ptr[0];
7461 				if (ch <= ' ' || ch == '-') break;
7462 				str = str[1..$];
7463 				if (wep > buf.length) return false; // too long
7464 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7465 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7466 				else return false; // invalid char
7467 			}
7468 			if (wep == 0) return false; // just in case
7469 			uint bnum;
7470 			enum UpDown { None = -1, Up, Down, Any }
7471 			auto updown = UpDown.None; // 0: up; 1: down
7472 			switch (buf[0..wep]) {
7473 				// left button
7474 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7475 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7476 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7477 				case "lmb": case "left": bnum = 0; break;
7478 				// middle button
7479 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7480 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7481 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7482 				case "mmb": case "middle": bnum = 1; break;
7483 				// right button
7484 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7485 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7486 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7487 				case "rmb": case "right": bnum = 2; break;
7488 				// wheel
7489 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7490 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7491 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7492 				case "wheel": bnum = 3; break;
7493 				// motion
7494 				case "motion": bnum = 7; break;
7495 				// unknown
7496 				default: return false;
7497 			}
7498 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7499 			// parse possible "-up" or "-down"
7500 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7501 				wep = 0;
7502 				foreach (immutable idx, immutable char ch; str[1..$]) {
7503 					if (ch <= ' ' || ch == '-') break;
7504 					assert(idx == wep); // for now; trick
7505 					if (wep > buf.length) { wep = 0; break; } // too long
7506 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7507 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7508 					else { wep = 0; break; } // invalid char
7509 				}
7510 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7511 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7512 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7513 				// remove parsed part
7514 				if (updown != UpDown.None) str = str[wep+1..$];
7515 			}
7516 			if (updown == UpDown.None) {
7517 				updown = UpDown.Down;
7518 			}
7519 			wasButtons = wasButtons || (bnum <= 2);
7520 			//assert(updown != UpDown.None);
7521 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7522 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7523 			if (lastButt != lastButt.max) {
7524 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7525 				if (mods != mods.max) {
7526 					uint butbit = 0;
7527 					final switch (lastButt&0x03) {
7528 						case 0: butbit = ModifierState.leftButtonDown; break;
7529 						case 1: butbit = ModifierState.middleButtonDown; break;
7530 						case 2: butbit = ModifierState.rightButtonDown; break;
7531 					}
7532 					     if (lastButt&Flag.Down) mods |= butbit;
7533 					else if (lastButt&Flag.Up) mods &= ~butbit;
7534 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7535 				}
7536 			}
7537 			// remember last button
7538 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7539 		}
7540 		// no button -- nothing to do
7541 		if (lastButt == lastButt.max) return false;
7542 		// done parsing, check if something's left
7543 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7544 		// remove action button from mask
7545 		if ((lastButt&0xff) < 3) {
7546 			final switch (lastButt&0x03) {
7547 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7548 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7549 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7550 			}
7551 		}
7552 		// special case: "Motion" means "ignore buttons"
7553 		if ((lastButt&0xff) == 7 && !wasButtons) {
7554 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7555 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7556 		}
7557 		uint kmod = event.modifierState&kmodmask;
7558 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7559 		// check modifier state
7560 		if (mods != mods.max) {
7561 			if (kmod != mods) return false;
7562 		}
7563 		// now check type
7564 		if ((lastButt&0xff) == 7) {
7565 			// motion
7566 			if (event.type != MouseEventType.motion) return false;
7567 		} else if ((lastButt&0xff) == 3) {
7568 			// wheel
7569 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7570 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7571 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7572 			return false;
7573 		} else {
7574 			// buttons
7575 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7576 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7577 			{
7578 				return false;
7579 			}
7580 			// button number
7581 			switch (lastButt&0x03) {
7582 				case 0: if (event.button != MouseButton.left) return false; break;
7583 				case 1: if (event.button != MouseButton.middle) return false; break;
7584 				case 2: if (event.button != MouseButton.right) return false; break;
7585 				default: return false;
7586 			}
7587 		}
7588 		return true;
7589 	}
7590 }
7591 
7592 version(arsd_mevent_strcmp_test) unittest {
7593 	MouseEvent event;
7594 	event.type = MouseEventType.buttonPressed;
7595 	event.button = MouseButton.left;
7596 	event.modifierState = ModifierState.ctrl;
7597 	assert(event == "C-LMB");
7598 	assert(event != "C-LMBUP");
7599 	assert(event != "C-LMB-UP");
7600 	assert(event != "C-S-LMB");
7601 	assert(event == "*-LMB");
7602 	assert(event != "*-LMB-UP");
7603 
7604 	event.type = MouseEventType.buttonReleased;
7605 	assert(event != "C-LMB");
7606 	assert(event == "C-LMBUP");
7607 	assert(event == "C-LMB-UP");
7608 	assert(event != "C-S-LMB");
7609 	assert(event != "*-LMB");
7610 	assert(event == "*-LMB-UP");
7611 
7612 	event.button = MouseButton.right;
7613 	event.modifierState |= ModifierState.shift;
7614 	event.type = MouseEventType.buttonPressed;
7615 	assert(event != "C-LMB");
7616 	assert(event != "C-LMBUP");
7617 	assert(event != "C-LMB-UP");
7618 	assert(event != "C-S-LMB");
7619 	assert(event != "*-LMB");
7620 	assert(event != "*-LMB-UP");
7621 
7622 	assert(event != "C-RMB");
7623 	assert(event != "C-RMBUP");
7624 	assert(event != "C-RMB-UP");
7625 	assert(event == "C-S-RMB");
7626 	assert(event == "*-RMB");
7627 	assert(event != "*-RMB-UP");
7628 }
7629 
7630 /// This gives a few more options to drawing lines and such
7631 struct Pen {
7632 	Color color; /// the foreground color
7633 	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.
7634 	Style style; /// See [Style]
7635 /+
7636 // From X.h
7637 
7638 #define LineSolid		0
7639 #define LineOnOffDash		1
7640 #define LineDoubleDash		2
7641        LineDou-        The full path of the line is drawn, but the
7642        bleDash         even dashes are filled differently from the
7643                        odd dashes (see fill-style) with CapButt
7644                        style used where even and odd dashes meet.
7645 
7646 
7647 
7648 /* capStyle */
7649 
7650 #define CapNotLast		0
7651 #define CapButt			1
7652 #define CapRound		2
7653 #define CapProjecting		3
7654 
7655 /* joinStyle */
7656 
7657 #define JoinMiter		0
7658 #define JoinRound		1
7659 #define JoinBevel		2
7660 
7661 /* fillStyle */
7662 
7663 #define FillSolid		0
7664 #define FillTiled		1
7665 #define FillStippled		2
7666 #define FillOpaqueStippled	3
7667 
7668 
7669 +/
7670 	/// Style of lines drawn
7671 	enum Style {
7672 		Solid, /// a solid line
7673 		Dashed, /// a dashed line
7674 		Dotted, /// a dotted line
7675 	}
7676 }
7677 
7678 
7679 /++
7680 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7681 
7682 
7683 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7684 
7685 	$(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.)
7686 
7687 	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.
7688 
7689 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7690 
7691 	$(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.
7692 
7693 	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!
7694 
7695 	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!)
7696 
7697 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7698 
7699 	---
7700 		auto image = new Image(256, 256);
7701 		scope(exit) destroy(image);
7702 	---
7703 
7704 	As long as you don't hold on to it outside the scope.
7705 
7706 	I might change it to be an owned pointer at some point in the future.
7707 
7708 	)
7709 
7710 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7711 	you can also often get a fair amount of speedup by getting the raw data format and
7712 	writing some custom code.
7713 
7714 	FIXME INSERT EXAMPLES HERE
7715 
7716 
7717 +/
7718 final class Image {
7719 	///
7720 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7721 		this.width = width;
7722 		this.height = height;
7723 		this.enableAlpha = enableAlpha;
7724 
7725 		impl.createImage(width, height, forcexshm, enableAlpha);
7726 	}
7727 
7728 	///
7729 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7730 		this(size.width, size.height, forcexshm, enableAlpha);
7731 	}
7732 
7733 	private bool suppressDestruction;
7734 
7735 	version(X11)
7736 	this(XImage* handle) {
7737 		this.handle = handle;
7738 		this.rawData = cast(ubyte*) handle.data;
7739 		this.width = handle.width;
7740 		this.height = handle.height;
7741 		this.enableAlpha = handle.depth == 32;
7742 		suppressDestruction = true;
7743 	}
7744 
7745 	~this() {
7746 		if(suppressDestruction) return;
7747 		impl.dispose();
7748 	}
7749 
7750 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7751 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7752 	pure const @system nothrow {
7753 		/*
7754 			To use these to draw a blue rectangle with size WxH at position X,Y...
7755 
7756 			// make certain that it will fit before we proceed
7757 			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!
7758 
7759 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7760 			// (though calculating them isn't really that expensive).
7761 			auto nextLineAdjustment = img.adjustmentForNextLine();
7762 			auto offR = img.redByteOffset();
7763 			auto offB = img.blueByteOffset();
7764 			auto offG = img.greenByteOffset();
7765 			auto bpp = img.bytesPerPixel();
7766 
7767 			auto data = img.getDataPointer();
7768 
7769 			// figure out the starting byte offset
7770 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7771 
7772 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7773 
7774 			// and now our drawing loop for the rectangle
7775 			foreach(y; 0 .. H) {
7776 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7777 				foreach(x; 0 .. W) {
7778 					// write our color
7779 					data[offR] = 0;
7780 					data[offG] = 0;
7781 					data[offB] = 255;
7782 
7783 					data += bpp; // moving to the next pixel is just an addition...
7784 				}
7785 				startOfLine += nextLineAdjustment;
7786 			}
7787 
7788 
7789 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7790 
7791 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7792 			can be made into a bitmask or something so we can write them as *uint...
7793 		*/
7794 
7795 		///
7796 		int offsetForTopLeftPixel() {
7797 			version(X11) {
7798 				return 0;
7799 			} else version(Windows) {
7800 				if(enableAlpha) {
7801 					return (width * 4) * (height - 1);
7802 				} else {
7803 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7804 				}
7805 			} else version(OSXCocoa) {
7806 				return 0 ; //throw new NotYetImplementedException();
7807 			} else static assert(0, "fill in this info for other OSes");
7808 		}
7809 
7810 		///
7811 		int offsetForPixel(int x, int y) {
7812 			version(X11) {
7813 				auto offset = (y * width + x) * 4;
7814 				return offset;
7815 			} else version(Windows) {
7816 				if(enableAlpha) {
7817 					auto itemsPerLine = width * 4;
7818 					// remember, bmps are upside down
7819 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7820 					return offset;
7821 				} else {
7822 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7823 					// remember, bmps are upside down
7824 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7825 					return offset;
7826 				}
7827 			} else version(OSXCocoa) {
7828 				return 0 ; //throw new NotYetImplementedException();
7829 			} else static assert(0, "fill in this info for other OSes");
7830 		}
7831 
7832 		///
7833 		int adjustmentForNextLine() {
7834 			version(X11) {
7835 				return width * 4;
7836 			} else version(Windows) {
7837 				// windows bmps are upside down, so the adjustment is actually negative
7838 				if(enableAlpha)
7839 					return - (cast(int) width * 4);
7840 				else
7841 					return -((cast(int) width * 3 + 3) / 4) * 4;
7842 			} else version(OSXCocoa) {
7843 				return 0 ; //throw new NotYetImplementedException();
7844 			} else static assert(0, "fill in this info for other OSes");
7845 		}
7846 
7847 		/// once you have the position of a pixel, use these to get to the proper color
7848 		int redByteOffset() {
7849 			version(X11) {
7850 				return 2;
7851 			} else version(Windows) {
7852 				return 2;
7853 			} else version(OSXCocoa) {
7854 				return 0 ; //throw new NotYetImplementedException();
7855 			} else static assert(0, "fill in this info for other OSes");
7856 		}
7857 
7858 		///
7859 		int greenByteOffset() {
7860 			version(X11) {
7861 				return 1;
7862 			} else version(Windows) {
7863 				return 1;
7864 			} else version(OSXCocoa) {
7865 				return 0 ; //throw new NotYetImplementedException();
7866 			} else static assert(0, "fill in this info for other OSes");
7867 		}
7868 
7869 		///
7870 		int blueByteOffset() {
7871 			version(X11) {
7872 				return 0;
7873 			} else version(Windows) {
7874 				return 0;
7875 			} else version(OSXCocoa) {
7876 				return 0 ; //throw new NotYetImplementedException();
7877 			} else static assert(0, "fill in this info for other OSes");
7878 		}
7879 
7880 		/// Only valid if [enableAlpha] is true
7881 		int alphaByteOffset() {
7882 			version(X11) {
7883 				return 3;
7884 			} else version(Windows) {
7885 				return 3;
7886 			} else version(OSXCocoa) {
7887 				return 3; //throw new NotYetImplementedException();
7888 			} else static assert(0, "fill in this info for other OSes");
7889 		}
7890 	}
7891 
7892 	///
7893 	final void putPixel(int x, int y, Color c) {
7894 		if(x < 0 || x >= width)
7895 			return;
7896 		if(y < 0 || y >= height)
7897 			return;
7898 
7899 		impl.setPixel(x, y, c);
7900 	}
7901 
7902 	///
7903 	final Color getPixel(int x, int y) {
7904 		if(x < 0 || x >= width)
7905 			return Color.transparent;
7906 		if(y < 0 || y >= height)
7907 			return Color.transparent;
7908 
7909 		version(OSXCocoa) throw new NotYetImplementedException(); else
7910 		return impl.getPixel(x, y);
7911 	}
7912 
7913 	///
7914 	final void opIndexAssign(Color c, int x, int y) {
7915 		putPixel(x, y, c);
7916 	}
7917 
7918 	///
7919 	TrueColorImage toTrueColorImage() {
7920 		auto tci = new TrueColorImage(width, height);
7921 		convertToRgbaBytes(tci.imageData.bytes);
7922 		return tci;
7923 	}
7924 
7925 	///
7926 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) {
7927 		auto tci = i.getAsTrueColorImage();
7928 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
7929 		img.setRgbaBytes(tci.imageData.bytes);
7930 		return img;
7931 	}
7932 
7933 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
7934 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
7935 	/// if you pass null, it will allocate a new one.
7936 	ubyte[] getRgbaBytes(ubyte[] where = null) {
7937 		if(where is null)
7938 			where = new ubyte[this.width*this.height*4];
7939 		convertToRgbaBytes(where);
7940 		return where;
7941 	}
7942 
7943 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
7944 	void setRgbaBytes(in ubyte[] from ) {
7945 		assert(from.length == this.width * this.height * 4);
7946 		setFromRgbaBytes(from);
7947 	}
7948 
7949 	// FIXME: make properly cross platform by getting rgba right
7950 
7951 	/// warning: this is not portable across platforms because the data format can change
7952 	ubyte* getDataPointer() {
7953 		return impl.rawData;
7954 	}
7955 
7956 	/// for use with getDataPointer
7957 	final int bytesPerLine() const pure @safe nothrow {
7958 		version(Windows)
7959 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
7960 		else version(X11)
7961 			return 4 * width;
7962 		else version(OSXCocoa)
7963 			return 4 * width;
7964 		else static assert(0);
7965 	}
7966 
7967 	/// for use with getDataPointer
7968 	final int bytesPerPixel() const pure @safe nothrow {
7969 		version(Windows)
7970 			return enableAlpha ? 4 : 3;
7971 		else version(X11)
7972 			return 4;
7973 		else version(OSXCocoa)
7974 			return 4;
7975 		else static assert(0);
7976 	}
7977 
7978 	///
7979 	immutable int width;
7980 
7981 	///
7982 	immutable int height;
7983 
7984 	///
7985 	immutable bool enableAlpha;
7986     //private:
7987 	mixin NativeImageImplementation!() impl;
7988 }
7989 
7990 /++
7991 	A convenience function to pop up a window displaying the image.
7992 	If you pass a win, it will draw the image in it. Otherwise, it will
7993 	create a window with the size of the image and run its event loop, closing
7994 	when a key is pressed.
7995 
7996 	History:
7997 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
7998 		always block until the application quit which could cause bizarre behavior
7999 		inside a more complex application. Now, the default is to block until
8000 		this window closes if it is the only event loop running, and otherwise,
8001 		not to block at all and just pop up the display window asynchronously.
8002 +/
8003 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8004 	if(win is null) {
8005 		win = new SimpleWindow(image);
8006 		{
8007 			auto p = win.draw;
8008 			p.drawImage(Point(0, 0), image);
8009 		}
8010 		win.eventLoopWithBlockingMode(
8011 			bm, 0,
8012 			(KeyEvent ev) {
8013 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8014 			} );
8015 	} else {
8016 		win.image = image;
8017 	}
8018 }
8019 
8020 enum FontWeight : int {
8021 	dontcare = 0,
8022 	thin = 100,
8023 	extralight = 200,
8024 	light = 300,
8025 	regular = 400,
8026 	medium = 500,
8027 	semibold = 600,
8028 	bold = 700,
8029 	extrabold = 800,
8030 	heavy = 900
8031 }
8032 
8033 /++
8034 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8035 
8036 	History:
8037 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8038 +/
8039 interface MeasurableFont {
8040 	/++
8041 		Returns true if it is a monospace font, meaning each of the
8042 		glyphs (at least the ascii characters) have matching width
8043 		and no kerning, so you can determine the display width of some
8044 		strings by simply multiplying the string width by [averageWidth].
8045 
8046 		(Please note that multiply doesn't $(I actually) work in general,
8047 		consider characters like tab and newline, but it does sometimes.)
8048 	+/
8049 	bool isMonospace();
8050 
8051 	/++
8052 		The average width of glyphs in the font, traditionally equal to the
8053 		width of the lowercase x. Can be used to estimate bounding boxes,
8054 		especially if the font [isMonospace].
8055 
8056 		Given in pixels.
8057 	+/
8058 	int averageWidth();
8059 	/++
8060 		The height of the bounding box of a line.
8061 	+/
8062 	int height();
8063 	/++
8064 		The maximum ascent of a glyph above the baseline.
8065 
8066 		Given in pixels.
8067 	+/
8068 	int ascent();
8069 	/++
8070 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8071 
8072 		Given in pixels.
8073 	+/
8074 	int descent();
8075 	/++
8076 		The display width of the given string, and if you provide a window, it will use it to
8077 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8078 
8079 		Given in pixels.
8080 	+/
8081 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8082 
8083 }
8084 
8085 // FIXME: i need a font cache and it needs to handle disconnects.
8086 
8087 /++
8088 	Represents a font loaded off the operating system or the X server.
8089 
8090 
8091 	While the api here is unified cross platform, the fonts are not necessarily
8092 	available, even across machines of the same platform, so be sure to always check
8093 	for null (using [isNull]) and have a fallback plan.
8094 
8095 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8096 
8097 	Worst case, a null font will automatically fall back to the default font loaded
8098 	for your system.
8099 +/
8100 class OperatingSystemFont : MeasurableFont {
8101 	// FIXME: when the X Connection is lost, these need to be invalidated!
8102 	// that means I need to store the original stuff again to reconstruct it too.
8103 
8104 	version(X11) {
8105 		XFontStruct* font;
8106 		XFontSet fontset;
8107 
8108 		version(with_xft) {
8109 			XftFont* xftFont;
8110 			bool isXft;
8111 		}
8112 	} else version(Windows) {
8113 		HFONT font;
8114 		int width_;
8115 		int height_;
8116 	} else version(OSXCocoa) {
8117 		// FIXME
8118 	} else static assert(0);
8119 
8120 	/++
8121 		Constructs the class and immediately calls [load].
8122 	+/
8123 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8124 		load(name, size, weight, italic);
8125 	}
8126 
8127 	/++
8128 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8129 
8130 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8131 
8132 		History:
8133 			Added January 24, 2021.
8134 	+/
8135 	this() {
8136 		// this space intentionally left blank
8137 	}
8138 
8139 	/++
8140 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8141 
8142 		History:
8143 			Added November 13, 2020.
8144 	+/
8145 	version(with_xft)
8146 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8147 		unload();
8148 
8149 		if(!XftLibrary.attempted) {
8150 			XftLibrary.loadDynamicLibrary();
8151 		}
8152 
8153 		if(!XftLibrary.loadSuccessful)
8154 			return false;
8155 
8156 		auto display = XDisplayConnection.get;
8157 
8158 		char[256] nameBuffer = void;
8159 		int nbp = 0;
8160 
8161 		void add(in char[] a) {
8162 			nameBuffer[nbp .. nbp + a.length] = a[];
8163 			nbp += a.length;
8164 		}
8165 		add(name);
8166 
8167 		if(size) {
8168 			add(":size=");
8169 			add(toInternal!string(size));
8170 		}
8171 		if(weight != FontWeight.dontcare) {
8172 			add(":weight=");
8173 			add(weightToString(weight));
8174 		}
8175 		if(italic)
8176 			add(":slant=100");
8177 
8178 		nameBuffer[nbp] = 0;
8179 
8180 		this.xftFont = XftFontOpenName(
8181 			display,
8182 			DefaultScreen(display),
8183 			nameBuffer.ptr
8184 		);
8185 
8186 		this.isXft = true;
8187 
8188 		if(xftFont !is null) {
8189 			isMonospace_ = stringWidth("x") == stringWidth("M");
8190 			ascent_ = xftFont.ascent;
8191 			descent_ = xftFont.descent;
8192 		}
8193 
8194 		return !isNull();
8195 	}
8196 
8197 	/++
8198 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8199 
8200 
8201 		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.
8202 
8203 		If `pattern` is null, it returns all available font families.
8204 
8205 		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.
8206 
8207 		The format of the pattern is platform-specific.
8208 
8209 		History:
8210 			Added May 1, 2021 (dub v9.5)
8211 	+/
8212 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8213 		version(Windows) {
8214 			auto hdc = GetDC(null);
8215 			scope(exit) ReleaseDC(null, hdc);
8216 			LOGFONT logfont;
8217 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8218 				auto localHandler = *(cast(typeof(handler)*) p);
8219 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8220 			}
8221 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8222 		} else version(X11) {
8223 			//import core.stdc.stdio;
8224 			bool done = false;
8225 			version(with_xft) {
8226 				if(!XftLibrary.attempted) {
8227 					XftLibrary.loadDynamicLibrary();
8228 				}
8229 
8230 				if(!XftLibrary.loadSuccessful)
8231 					goto skipXft;
8232 
8233 				if(!FontConfigLibrary.attempted)
8234 					FontConfigLibrary.loadDynamicLibrary();
8235 				if(!FontConfigLibrary.loadSuccessful)
8236 					goto skipXft;
8237 
8238 				{
8239 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8240 					if(got is null)
8241 						goto skipXft;
8242 					scope(exit) FcFontSetDestroy(got);
8243 
8244 					auto fontPatterns = got.fonts[0 .. got.nfont];
8245 					foreach(candidate; fontPatterns) {
8246 						char* where, whereStyle;
8247 
8248 						char* pmg = FcNameUnparse(candidate);
8249 
8250 						//FcPatternGetString(candidate, "family", 0, &where);
8251 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8252 						//if(where && whereStyle) {
8253 						if(pmg) {
8254 							if(!handler(pmg.sliceCString))
8255 								return;
8256 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8257 						}
8258 					}
8259 				}
8260 			}
8261 
8262 			skipXft:
8263 
8264 			if(pattern is null)
8265 				pattern = "*";
8266 
8267 			int count;
8268 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8269 			scope(exit) XFreeFontNames(coreFontsRaw);
8270 
8271 			auto coreFonts = coreFontsRaw[0 .. count];
8272 
8273 			foreach(font; coreFonts) {
8274 				char[128] tmp;
8275 				tmp[0 ..5] = "core:";
8276 				auto cf = font.sliceCString;
8277 				if(5 + cf.length > tmp.length)
8278 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8279 				tmp[5 .. 5 + cf.length] = cf;
8280 				if(!handler(tmp[0 .. 5 + cf.length]))
8281 					return;
8282 			}
8283 		}
8284 	}
8285 
8286 	/++
8287 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8288 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8289 
8290 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8291 		underlying system doesn't support returning the raw bytes.
8292 
8293 		History:
8294 			Added September 10, 2021 (dub v10.3)
8295 	+/
8296 	ubyte[] getTtfBytes() {
8297 		if(isNull)
8298 			return null;
8299 
8300 		version(Windows) {
8301 			auto dc = GetDC(null);
8302 			auto orig = SelectObject(dc, font);
8303 
8304 			scope(exit) {
8305 				SelectObject(dc, orig);
8306 				ReleaseDC(null, dc);
8307 			}
8308 
8309 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8310 			if(res == GDI_ERROR)
8311 				return null;
8312 
8313 			ubyte[] buffer = new ubyte[](res);
8314 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8315 			if(res == GDI_ERROR)
8316 				return null; // wtf really tbh
8317 
8318 			return buffer;
8319 		} else version(with_xft) {
8320 			if(isXft && xftFont) {
8321 				if(!FontConfigLibrary.attempted)
8322 					FontConfigLibrary.loadDynamicLibrary();
8323 				if(!FontConfigLibrary.loadSuccessful)
8324 					return null;
8325 
8326 				char* file;
8327 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8328 					if (file !is null && file[0]) {
8329 						import core.stdc.stdio;
8330 						auto fp = fopen(file, "rb");
8331 						if(fp is null)
8332 							return null;
8333 						scope(exit)
8334 							fclose(fp);
8335 						fseek(fp, 0, SEEK_END);
8336 						ubyte[] buffer = new ubyte[](ftell(fp));
8337 						fseek(fp, 0, SEEK_SET);
8338 
8339 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8340 						if(got != buffer.length)
8341 							return null;
8342 
8343 						return buffer;
8344 					}
8345 				}
8346 			}
8347 			return null;
8348 		}
8349 	}
8350 
8351 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8352 
8353 	private string weightToString(FontWeight weight) {
8354 		with(FontWeight)
8355 		final switch(weight) {
8356 			case dontcare: return "*";
8357 			case thin: return "extralight";
8358 			case extralight: return "extralight";
8359 			case light: return "light";
8360 			case regular: return "regular";
8361 			case medium: return "medium";
8362 			case semibold: return "demibold";
8363 			case bold: return "bold";
8364 			case extrabold: return "demibold";
8365 			case heavy: return "black";
8366 		}
8367 	}
8368 
8369 	/++
8370 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8371 
8372 		History:
8373 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8374 	+/
8375 	version(X11)
8376 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8377 		unload();
8378 
8379 		string xfontstr;
8380 
8381 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8382 			// this is kinda a disgusting hack but if the user sends an exact
8383 			// string I'd like to honor it...
8384 			xfontstr = name;
8385 		} else {
8386 			string weightstr = weightToString(weight);
8387 			string sizestr;
8388 			if(size == 0)
8389 				sizestr = "*";
8390 			else
8391 				sizestr = toInternal!string(size);
8392 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8393 		}
8394 
8395 		//import std.stdio; writeln(xfontstr);
8396 
8397 		auto display = XDisplayConnection.get;
8398 
8399 		font = XLoadQueryFont(display, xfontstr.ptr);
8400 		if(font is null)
8401 			return false;
8402 
8403 		char** lol;
8404 		int lol2;
8405 		char* lol3;
8406 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8407 
8408 		prepareFontInfo();
8409 
8410 		return !isNull();
8411 	}
8412 
8413 	version(X11)
8414 	private void prepareFontInfo() {
8415 		if(font !is null) {
8416 			isMonospace_ = stringWidth("l") == stringWidth("M");
8417 			ascent_ = font.max_bounds.ascent;
8418 			descent_ = font.max_bounds.descent;
8419 		}
8420 	}
8421 
8422 	/++
8423 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8424 
8425 		History:
8426 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8427 	+/
8428 	version(Windows)
8429 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8430 		unload();
8431 
8432 		WCharzBuffer buffer = WCharzBuffer(name);
8433 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8434 
8435 		prepareFontInfo(hdc);
8436 
8437 		return !isNull();
8438 	}
8439 
8440 	version(Windows)
8441 	void prepareFontInfo(HDC hdc = null) {
8442 		if(font is null)
8443 			return;
8444 
8445 		TEXTMETRIC tm;
8446 		auto dc = hdc ? hdc : GetDC(null);
8447 		auto orig = SelectObject(dc, font);
8448 		GetTextMetrics(dc, &tm);
8449 		SelectObject(dc, orig);
8450 		if(hdc is null)
8451 			ReleaseDC(null, dc);
8452 
8453 		width_ = tm.tmAveCharWidth;
8454 		height_ = tm.tmHeight;
8455 		ascent_ = tm.tmAscent;
8456 		descent_ = tm.tmDescent;
8457 		// 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.
8458 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8459 	}
8460 
8461 
8462 	/++
8463 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8464 
8465 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8466 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8467 
8468 		On Windows, it forwards directly to [loadWin32].
8469 
8470 		Params:
8471 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8472 			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.
8473 			weight = approximate boldness, results may vary.
8474 			italic = try to get a slanted version of the given font.
8475 
8476 		History:
8477 			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.
8478 	+/
8479 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8480 		version(X11) {
8481 			version(with_xft) {
8482 				if(name.length > 5 && name[0 .. 5] == "core:") {
8483 					goto core;
8484 				}
8485 
8486 				if(loadXft(name, size, weight, italic))
8487 					return true;
8488 				// if xft fails, fallback to core to avoid breaking
8489 				// code that already depended on this.
8490 			}
8491 
8492 			core:
8493 
8494 			if(name.length > 5 && name[0 .. 5] == "core:") {
8495 				name = name[5 .. $];
8496 			}
8497 
8498 			return loadCoreX(name, size, weight, italic);
8499 		} else version(Windows) {
8500 			return loadWin32(name, size, weight, italic);
8501 		} else version(OSXCocoa) {
8502 			// FIXME
8503 			return false;
8504 		} else static assert(0);
8505 	}
8506 
8507 	///
8508 	void unload() {
8509 		if(isNull())
8510 			return;
8511 
8512 		version(X11) {
8513 			auto display = XDisplayConnection.display;
8514 
8515 			if(display is null)
8516 				return;
8517 
8518 			version(with_xft) {
8519 				if(isXft) {
8520 					if(xftFont)
8521 						XftFontClose(display, xftFont);
8522 					isXft = false;
8523 					xftFont = null;
8524 					return;
8525 				}
8526 			}
8527 
8528 			if(font && font !is ScreenPainterImplementation.defaultfont)
8529 				XFreeFont(display, font);
8530 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8531 				XFreeFontSet(display, fontset);
8532 
8533 			font = null;
8534 			fontset = null;
8535 		} else version(Windows) {
8536 			DeleteObject(font);
8537 			font = null;
8538 		} else version(OSXCocoa) {
8539 			// FIXME
8540 		} else static assert(0);
8541 	}
8542 
8543 	private bool isMonospace_;
8544 
8545 	/++
8546 		History:
8547 			Added January 16, 2021
8548 	+/
8549 	bool isMonospace() {
8550 		return isMonospace_;
8551 	}
8552 
8553 	/++
8554 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8555 
8556 		History:
8557 			Added March 26, 2020
8558 			Documented January 16, 2021
8559 	+/
8560 	int averageWidth() {
8561 		version(X11) {
8562 			return stringWidth("x");
8563 		} else version(Windows)
8564 			return width_;
8565 		else assert(0);
8566 	}
8567 
8568 	/++
8569 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8570 
8571 		History:
8572 			Added January 16, 2021
8573 	+/
8574 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8575 	// FIXME: what about tab?
8576 		if(isNull)
8577 			return 0;
8578 
8579 		version(X11) {
8580 			version(with_xft)
8581 				if(isXft && xftFont !is null) {
8582 					//return xftFont.max_advance_width;
8583 					XGlyphInfo extents;
8584 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8585 					//import std.stdio; writeln(extents);
8586 					return extents.xOff;
8587 				}
8588 			if(font is null)
8589 				return 0;
8590 			else if(fontset) {
8591 				XRectangle rect;
8592 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8593 
8594 				return rect.width;
8595 			} else {
8596 				return XTextWidth(font, s.ptr, cast(int) s.length);
8597 			}
8598 		} else version(Windows) {
8599 			WCharzBuffer buffer = WCharzBuffer(s);
8600 
8601 			return stringWidth(buffer.slice, window);
8602 		}
8603 		else assert(0);
8604 	}
8605 
8606 	version(Windows)
8607 	/// ditto
8608 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8609 		if(isNull)
8610 			return 0;
8611 		version(Windows) {
8612 			SIZE size;
8613 
8614 			prepareContext(window);
8615 			scope(exit) releaseContext();
8616 
8617 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8618 
8619 			return size.cx;
8620 		} else {
8621 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8622 			static assert(0, "not implemented yet");
8623 			//return stringWidth(s, window);
8624 		}
8625 	}
8626 
8627 	private {
8628 		int prepRefcount;
8629 
8630 		version(Windows) {
8631 			HDC dc;
8632 			HANDLE orig;
8633 			HWND hwnd;
8634 		}
8635 	}
8636 	/++
8637 		[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.
8638 
8639 		History:
8640 			Added January 23, 2021
8641 	+/
8642 	void prepareContext(SimpleWindow window = null) {
8643 		prepRefcount++;
8644 		if(prepRefcount == 1) {
8645 			version(Windows) {
8646 				hwnd = window is null ? null : window.impl.hwnd;
8647 				dc = GetDC(hwnd);
8648 				orig = SelectObject(dc, font);
8649 			}
8650 		}
8651 	}
8652 	/// ditto
8653 	void releaseContext() {
8654 		prepRefcount--;
8655 		if(prepRefcount == 0) {
8656 			version(Windows) {
8657 				SelectObject(dc, orig);
8658 				ReleaseDC(hwnd, dc);
8659 				hwnd = null;
8660 				dc = null;
8661 				orig = null;
8662 			}
8663 		}
8664 	}
8665 
8666 	/+
8667 		FIXME: I think I need advance and kerning pair
8668 
8669 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8670 	+/
8671 
8672 	/++
8673 		Returns the height of the font.
8674 
8675 		History:
8676 			Added March 26, 2020
8677 			Documented January 16, 2021
8678 	+/
8679 	int height() {
8680 		version(X11) {
8681 			version(with_xft)
8682 				if(isXft && xftFont !is null) {
8683 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8684 				}
8685 			if(font is null)
8686 				return 0;
8687 			return font.max_bounds.ascent + font.max_bounds.descent;
8688 		} else version(Windows)
8689 			return height_;
8690 		else assert(0);
8691 	}
8692 
8693 	private int ascent_;
8694 	private int descent_;
8695 
8696 	/++
8697 		Max ascent above the baseline.
8698 
8699 		History:
8700 			Added January 22, 2021
8701 	+/
8702 	int ascent() {
8703 		return ascent_;
8704 	}
8705 
8706 	/++
8707 		Max descent below the baseline.
8708 
8709 		History:
8710 			Added January 22, 2021
8711 	+/
8712 	int descent() {
8713 		return descent_;
8714 	}
8715 
8716 	/++
8717 		Loads the default font used by [ScreenPainter] if none others are loaded.
8718 
8719 		Returns:
8720 			This method mutates the `this` object, but then returns `this` for
8721 			easy chaining like:
8722 
8723 			---
8724 			auto font = foo.isNull ? foo : foo.loadDefault
8725 			---
8726 
8727 		History:
8728 			Added previously, but left unimplemented until January 24, 2021.
8729 	+/
8730 	OperatingSystemFont loadDefault() {
8731 		unload();
8732 
8733 		version(X11) {
8734 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8735 			// but meh since sdpy does its own thing, this should be ok too
8736 
8737 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8738 			this.font = ScreenPainterImplementation.defaultfont;
8739 			this.fontset = ScreenPainterImplementation.defaultfontset;
8740 
8741 			prepareFontInfo();
8742 		} else version(Windows) {
8743 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8744 			this.font = ScreenPainterImplementation.defaultGuiFont;
8745 
8746 			prepareFontInfo();
8747 		} else throw new NotYetImplementedException();
8748 
8749 		return this;
8750 	}
8751 
8752 	///
8753 	bool isNull() {
8754 		version(OSXCocoa) throw new NotYetImplementedException(); else {
8755 			version(with_xft)
8756 				if(isXft)
8757 					return xftFont is null;
8758 			return font is null;
8759 		}
8760 	}
8761 
8762 	/* Metrics */
8763 	/+
8764 		GetABCWidth
8765 		GetKerningPairs
8766 
8767 		if I do it right, I can size it all here, and match
8768 		what happens when I draw the full string with the OS functions.
8769 
8770 		subclasses might do the same thing while getting the glyphs on images
8771 	struct GlyphInfo {
8772 		int glyph;
8773 
8774 		size_t stringIdxStart;
8775 		size_t stringIdxEnd;
8776 
8777 		Rectangle boundingBox;
8778 	}
8779 	GlyphInfo[] getCharBoxes() {
8780 		// XftTextExtentsUtf8
8781 		return null;
8782 
8783 	}
8784 	+/
8785 
8786 	~this() {
8787 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
8788 		unload();
8789 	}
8790 }
8791 
8792 version(Windows)
8793 private string sliceCString(const(wchar)[] w) {
8794 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
8795 }
8796 
8797 private inout(char)[] sliceCString(inout(char)* s) {
8798 	import core.stdc.string;
8799 	auto len = strlen(s);
8800 	return s[0 .. len];
8801 }
8802 
8803 /**
8804 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
8805 	than constructing it directly. Then, it is reference counted so you can pass it
8806 	at around and when the last ref goes out of scope, the buffered drawing activities
8807 	are all carried out.
8808 
8809 
8810 	Most functions use the outlineColor instead of taking a color themselves.
8811 	ScreenPainter is reference counted and draws its buffer to the screen when its
8812 	final reference goes out of scope.
8813 */
8814 struct ScreenPainter {
8815 	CapableOfBeingDrawnUpon window;
8816 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) {
8817 		this.window = window;
8818 		if(window.closed)
8819 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
8820 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
8821 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
8822 		if(window.activeScreenPainter !is null) {
8823 			impl = window.activeScreenPainter;
8824 			if(impl.referenceCount == 0) {
8825 				impl.window = window;
8826 				impl.create(handle);
8827 			}
8828 			impl.manualInvalidations = manualInvalidations;
8829 			impl.referenceCount++;
8830 		//	writeln("refcount ++ ", impl.referenceCount);
8831 		} else {
8832 			impl = new ScreenPainterImplementation;
8833 			impl.window = window;
8834 			impl.create(handle);
8835 			impl.referenceCount = 1;
8836 			impl.manualInvalidations = manualInvalidations;
8837 			window.activeScreenPainter = impl;
8838 			//import std.stdio; writeln("constructed");
8839 		}
8840 
8841 		copyActiveOriginals();
8842 	}
8843 
8844 	/++
8845 		If you are using manual invalidations, this informs the
8846 		window system that a section needs to be redrawn.
8847 
8848 		If you didn't opt into manual invalidation, you don't
8849 		have to call this.
8850 
8851 		History:
8852 			Added December 30, 2021 (dub v10.5)
8853 	+/
8854 	void invalidateRect(Rectangle rect) {
8855 		if(impl is null) return;
8856 
8857 		// transform(rect)
8858 		rect.left += _originX;
8859 		rect.right += _originX;
8860 		rect.top += _originY;
8861 		rect.bottom += _originY;
8862 
8863 		impl.invalidateRect(rect);
8864 	}
8865 
8866 	private Pen originalPen;
8867 	private Color originalFillColor;
8868 	private arsd.color.Rectangle originalClipRectangle;
8869 	void copyActiveOriginals() {
8870 		if(impl is null) return;
8871 		originalPen = impl._activePen;
8872 		originalFillColor = impl._fillColor;
8873 		originalClipRectangle = impl._clipRectangle;
8874 	}
8875 
8876 	~this() {
8877 		if(impl is null) return;
8878 		impl.referenceCount--;
8879 		//writeln("refcount -- ", impl.referenceCount);
8880 		if(impl.referenceCount == 0) {
8881 			//import std.stdio; writeln("destructed");
8882 			impl.dispose();
8883 			*window.activeScreenPainter = ScreenPainterImplementation.init;
8884 			//import std.stdio; writeln("paint finished");
8885 		} else {
8886 			// there is still an active reference, reset stuff so the
8887 			// next user doesn't get weirdness via the reference
8888 			this.rasterOp = RasterOp.normal;
8889 			pen = originalPen;
8890 			fillColor = originalFillColor;
8891 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
8892 		}
8893 	}
8894 
8895 	this(this) {
8896 		if(impl is null) return;
8897 		impl.referenceCount++;
8898 		//writeln("refcount ++ ", impl.referenceCount);
8899 
8900 		copyActiveOriginals();
8901 	}
8902 
8903 	private int _originX;
8904 	private int _originY;
8905 	@property int originX() { return _originX; }
8906 	@property int originY() { return _originY; }
8907 	@property int originX(int a) {
8908 		_originX = a;
8909 		return _originX;
8910 	}
8911 	@property int originY(int a) {
8912 		_originY = a;
8913 		return _originY;
8914 	}
8915 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
8916 	private void transform(ref Point p) {
8917 		if(impl is null) return;
8918 		p.x += _originX;
8919 		p.y += _originY;
8920 	}
8921 
8922 	// this needs to be checked BEFORE the originX/Y transformation
8923 	private bool isClipped(Point p) {
8924 		return !currentClipRectangle.contains(p);
8925 	}
8926 	private bool isClipped(Point p, int width, int height) {
8927 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
8928 	}
8929 	private bool isClipped(Point p, Size s) {
8930 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
8931 	}
8932 	private bool isClipped(Point p, Point p2) {
8933 		// need to ensure the end points are actually included inside, so the +1 does that
8934 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
8935 	}
8936 
8937 
8938 	/++
8939 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
8940 
8941 		Returns:
8942 			The old clip rectangle.
8943 
8944 		History:
8945 			Return value was `void` prior to May 10, 2021.
8946 
8947 	+/
8948 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
8949 		if(impl is null) return currentClipRectangle;
8950 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
8951 			return currentClipRectangle; // no need to do anything
8952 		auto old = currentClipRectangle;
8953 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
8954 		transform(pt);
8955 
8956 		impl.setClipRectangle(pt.x, pt.y, width, height);
8957 
8958 		return old;
8959 	}
8960 
8961 	/// ditto
8962 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
8963 		if(impl is null) return currentClipRectangle;
8964 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
8965 	}
8966 
8967 	///
8968 	void setFont(OperatingSystemFont font) {
8969 		if(impl is null) return;
8970 		impl.setFont(font);
8971 	}
8972 
8973 	///
8974 	int fontHeight() {
8975 		if(impl is null) return 0;
8976 		return impl.fontHeight();
8977 	}
8978 
8979 	private Pen activePen;
8980 
8981 	///
8982 	@property void pen(Pen p) {
8983 		if(impl is null) return;
8984 		activePen = p;
8985 		impl.pen(p);
8986 	}
8987 
8988 	///
8989 	@scriptable
8990 	@property void outlineColor(Color c) {
8991 		if(impl is null) return;
8992 		if(activePen.color == c)
8993 			return;
8994 		activePen.color = c;
8995 		impl.pen(activePen);
8996 	}
8997 
8998 	///
8999 	@scriptable
9000 	@property void fillColor(Color c) {
9001 		if(impl is null) return;
9002 		impl.fillColor(c);
9003 	}
9004 
9005 	///
9006 	@property void rasterOp(RasterOp op) {
9007 		if(impl is null) return;
9008 		impl.rasterOp(op);
9009 	}
9010 
9011 
9012 	void updateDisplay() {
9013 		// FIXME this should do what the dtor does
9014 	}
9015 
9016 	/// 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)
9017 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9018 		if(impl is null) return;
9019 		if(isClipped(upperLeft, width, height)) return;
9020 		transform(upperLeft);
9021 		version(Windows) {
9022 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9023 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9024 			RECT clip = scroll;
9025 			RECT uncovered;
9026 			HRGN hrgn;
9027 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9028 				throw new Exception("ScrollDC");
9029 
9030 		} else version(X11) {
9031 			// FIXME: clip stuff outside this rectangle
9032 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9033 		} else version(OSXCocoa) {
9034 			throw new NotYetImplementedException();
9035 		} else static assert(0);
9036 	}
9037 
9038 	///
9039 	void clear(Color color = Color.white()) {
9040 		if(impl is null) return;
9041 		fillColor = color;
9042 		outlineColor = color;
9043 		drawRectangle(Point(0, 0), window.width, window.height);
9044 	}
9045 
9046 	/++
9047 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9048 
9049 		Params:
9050 			upperLeft = point on the window where the upper left corner of the image will be drawn
9051 			imageUpperLeft = point on the image to start the slice to draw
9052 			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.
9053 		History:
9054 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9055 	+/
9056 	version(OSXCocoa) {} else // NotYetImplementedException
9057 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9058 		if(impl is null) return;
9059 		if(isClipped(upperLeft, s.width, s.height)) return;
9060 		transform(upperLeft);
9061 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9062 	}
9063 
9064 	///
9065 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9066 		if(impl is null) return;
9067 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9068 		transform(upperLeft);
9069 		if(w == 0 || w > i.width)
9070 			w = i.width;
9071 		if(h == 0 || h > i.height)
9072 			h = i.height;
9073 		if(upperLeftOfImage.x < 0)
9074 			upperLeftOfImage.x = 0;
9075 		if(upperLeftOfImage.y < 0)
9076 			upperLeftOfImage.y = 0;
9077 
9078 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9079 	}
9080 
9081 	///
9082 	Size textSize(in char[] text) {
9083 		if(impl is null) return Size(0, 0);
9084 		return impl.textSize(text);
9085 	}
9086 
9087 	/++
9088 		Draws a string in the window with the set font (see [setFont] to change it).
9089 
9090 		Params:
9091 			upperLeft = the upper left point of the bounding box of the text
9092 			text = the string to draw
9093 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9094 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9095 	+/
9096 	@scriptable
9097 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9098 		if(impl is null) return;
9099 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9100 			if(isClipped(upperLeft, lowerRight)) return;
9101 			transform(lowerRight);
9102 		} else {
9103 			if(isClipped(upperLeft, textSize(text))) return;
9104 		}
9105 		transform(upperLeft);
9106 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9107 	}
9108 
9109 	/++
9110 		Draws text using a custom font.
9111 
9112 		This is still MAJOR work in progress.
9113 
9114 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9115 	+/
9116 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9117 		if(impl is null) return;
9118 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9119 		transform(upperLeft);
9120 		font.drawString(this, upperLeft, text);
9121 	}
9122 
9123 	version(Windows)
9124 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9125 		if(impl is null) return;
9126 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9127 		transform(upperLeft);
9128 
9129 		if(text.length && text[$-1] == '\n')
9130 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9131 
9132 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9133 	}
9134 
9135 	static struct TextDrawingContext {
9136 		Point boundingBoxUpperLeft;
9137 		Point boundingBoxLowerRight;
9138 
9139 		Point currentLocation;
9140 
9141 		Point lastDrewUpperLeft;
9142 		Point lastDrewLowerRight;
9143 
9144 		// how do i do right aligned rich text?
9145 		// i kinda want to do a pre-made drawing then right align
9146 		// draw the whole block.
9147 		//
9148 		// That's exactly the diff: inline vs block stuff.
9149 
9150 		// I need to get coordinates of an inline section out too,
9151 		// not just a bounding box, but a series of bounding boxes
9152 		// should be ok. Consider what's needed to detect a click
9153 		// on a link in the middle of a paragraph breaking a line.
9154 		//
9155 		// Generally, we should be able to get the rectangles of
9156 		// any portion we draw.
9157 		//
9158 		// It also needs to tell what text is left if it overflows
9159 		// out of the box, so we can do stuff like float images around
9160 		// it. It should not attempt to draw a letter that would be
9161 		// clipped.
9162 		//
9163 		// I might also turn off word wrap stuff.
9164 	}
9165 
9166 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9167 		if(impl is null) return;
9168 		// FIXME
9169 	}
9170 
9171 	/// Drawing an individual pixel is slow. Avoid it if possible.
9172 	void drawPixel(Point where) {
9173 		if(impl is null) return;
9174 		if(isClipped(where)) return;
9175 		transform(where);
9176 		impl.drawPixel(where.x, where.y);
9177 	}
9178 
9179 
9180 	/// Draws a pen using the current pen / outlineColor
9181 	@scriptable
9182 	void drawLine(Point starting, Point ending) {
9183 		if(impl is null) return;
9184 		if(isClipped(starting, ending)) return;
9185 		transform(starting);
9186 		transform(ending);
9187 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9188 	}
9189 
9190 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9191 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9192 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9193 	@scriptable
9194 	void drawRectangle(Point upperLeft, int width, int height) {
9195 		if(impl is null) return;
9196 		if(isClipped(upperLeft, width, height)) return;
9197 		transform(upperLeft);
9198 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9199 	}
9200 
9201 	/// ditto
9202 	void drawRectangle(Point upperLeft, Size size) {
9203 		if(impl is null) return;
9204 		if(isClipped(upperLeft, size.width, size.height)) return;
9205 		transform(upperLeft);
9206 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9207 	}
9208 
9209 	/// ditto
9210 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9211 		if(impl is null) return;
9212 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9213 		transform(upperLeft);
9214 		transform(lowerRightInclusive);
9215 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9216 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9217 	}
9218 
9219 	// overload added on May 12, 2021
9220 	/// ditto
9221 	void drawRectangle(Rectangle rect) {
9222 		drawRectangle(rect.upperLeft, rect.size);
9223 	}
9224 
9225 	/// Arguments are the points of the bounding rectangle
9226 	void drawEllipse(Point upperLeft, Point lowerRight) {
9227 		if(impl is null) return;
9228 		if(isClipped(upperLeft, lowerRight)) return;
9229 		transform(upperLeft);
9230 		transform(lowerRight);
9231 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9232 	}
9233 
9234 	/++
9235 		start and finish are units of degrees * 64
9236 
9237 		History:
9238 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9239 
9240 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9241 	+/
9242 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9243 		if(impl is null) return;
9244 		// FIXME: not actually implemented
9245 		if(isClipped(upperLeft, width, height)) return;
9246 		transform(upperLeft);
9247 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9248 	}
9249 
9250 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9251 	void drawCircle(Point upperLeft, int diameter) {
9252 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9253 	}
9254 
9255 	/// .
9256 	void drawPolygon(Point[] vertexes) {
9257 		if(impl is null) return;
9258 		assert(vertexes.length);
9259 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9260 		foreach(ref vertex; vertexes) {
9261 			if(vertex.x < minX)
9262 				minX = vertex.x;
9263 			if(vertex.y < minY)
9264 				minY = vertex.y;
9265 			if(vertex.x > maxX)
9266 				maxX = vertex.x;
9267 			if(vertex.y > maxY)
9268 				maxY = vertex.y;
9269 			transform(vertex);
9270 		}
9271 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9272 		impl.drawPolygon(vertexes);
9273 	}
9274 
9275 	/// ditto
9276 	void drawPolygon(Point[] vertexes...) {
9277 		if(impl is null) return;
9278 		drawPolygon(vertexes);
9279 	}
9280 
9281 
9282 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9283 
9284 	//mixin NativeScreenPainterImplementation!() impl;
9285 
9286 
9287 	// HACK: if I mixin the impl directly, it won't let me override the copy
9288 	// constructor! The linker complains about there being multiple definitions.
9289 	// I'll make the best of it and reference count it though.
9290 	ScreenPainterImplementation* impl;
9291 }
9292 
9293 	// HACK: I need a pointer to the implementation so it's separate
9294 	struct ScreenPainterImplementation {
9295 		CapableOfBeingDrawnUpon window;
9296 		int referenceCount;
9297 		mixin NativeScreenPainterImplementation!();
9298 	}
9299 
9300 // FIXME: i haven't actually tested the sprite class on MS Windows
9301 
9302 /**
9303 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9304 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9305 
9306 
9307 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9308 	though I'm not sure that's ideal and the implementation might change.
9309 
9310 	You create one by giving a window and an image. It optimizes for that window,
9311 	and copies the image into it to use as the initial picture. Creating a sprite
9312 	can be quite slow (especially over a network connection) so you should do it
9313 	as little as possible and just hold on to your sprite handles after making them.
9314 	simpledisplay does try to do its best though, using the XSHM extension if available,
9315 	but you should still write your code as if it will always be slow.
9316 
9317 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9318 	a fast operation - much faster than drawing the Image itself every time.
9319 
9320 	`Sprite` represents a scarce resource which should be freed when you
9321 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9322 	after it has been disposed. If you are unsure about this, don't take chances,
9323 	just let the garbage collector do it for you. But ideally, you can manage its
9324 	lifetime more efficiently.
9325 
9326 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9327 	support alpha blending in its drawing at this time. That might change in the
9328 	future, but if you need alpha blending right now, use OpenGL instead. See
9329 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9330 
9331 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9332 	in by setting the enableAlpha = true in the constructor.
9333 */
9334 version(OSXCocoa) {} else // NotYetImplementedException
9335 class Sprite : CapableOfBeingDrawnUpon {
9336 
9337 	///
9338 	ScreenPainter draw() {
9339 		return ScreenPainter(this, handle, false);
9340 	}
9341 
9342 	/++
9343 		Copies the sprite's current state into a [TrueColorImage].
9344 
9345 		Be warned: this can be a very slow operation
9346 
9347 		History:
9348 			Actually implemented on March 14, 2021
9349 	+/
9350 	TrueColorImage takeScreenshot() {
9351 		return trueColorImageFromNativeHandle(handle, width, height);
9352 	}
9353 
9354 	void delegate() paintingFinishedDg() { return null; }
9355 	bool closed() { return false; }
9356 	ScreenPainterImplementation* activeScreenPainter_;
9357 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9358 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9359 
9360 	version(Windows)
9361 		private ubyte* rawData;
9362 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9363 	// ditto on the XPicture stuff
9364 
9365 	version(X11) {
9366 		private static XRenderPictFormat* RGB24;
9367 		private static XRenderPictFormat* ARGB32;
9368 
9369 		private Picture xrenderPicture;
9370 	}
9371 
9372 	version(X11)
9373 	private static void requireXRender() {
9374 		if(!XRenderLibrary.loadAttempted) {
9375 			XRenderLibrary.loadDynamicLibrary();
9376 		}
9377 
9378 		if(!XRenderLibrary.loadSuccessful)
9379 			throw new Exception("XRender library load failure");
9380 
9381 		auto display = XDisplayConnection.get;
9382 
9383 		// FIXME: if we migrate X displays, these need to be changed
9384 		if(RGB24 is null)
9385 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9386 		if(ARGB32 is null)
9387 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9388 	}
9389 
9390 	protected this() {}
9391 
9392 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9393 		this._width = width;
9394 		this._height = height;
9395 		this.enableAlpha = enableAlpha;
9396 
9397 		version(X11) {
9398 			auto display = XDisplayConnection.get();
9399 
9400 			if(enableAlpha) {
9401 				requireXRender();
9402 			}
9403 
9404 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9405 
9406 			if(enableAlpha) {
9407 				XRenderPictureAttributes attrs;
9408 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9409 			}
9410 		} else version(Windows) {
9411 			version(CRuntime_DigitalMars) {
9412 				//if(enableAlpha)
9413 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9414 			}
9415 
9416 			BITMAPINFO infoheader;
9417 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9418 			infoheader.bmiHeader.biWidth = width;
9419 			infoheader.bmiHeader.biHeight = height;
9420 			infoheader.bmiHeader.biPlanes = 1;
9421 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9422 			infoheader.bmiHeader.biCompression = BI_RGB;
9423 
9424 			// FIXME: this should prolly be a device dependent bitmap...
9425 			handle = CreateDIBSection(
9426 				null,
9427 				&infoheader,
9428 				DIB_RGB_COLORS,
9429 				cast(void**) &rawData,
9430 				null,
9431 				0);
9432 
9433 			if(handle is null)
9434 				throw new Exception("couldn't create pixmap");
9435 		}
9436 	}
9437 
9438 	/// Makes a sprite based on the image with the initial contents from the Image
9439 	this(SimpleWindow win, Image i) {
9440 		this(win, i.width, i.height, i.enableAlpha);
9441 
9442 		version(X11) {
9443 			auto display = XDisplayConnection.get();
9444 			auto gc = XCreateGC(display, this.handle, 0, null);
9445 			scope(exit) XFreeGC(display, gc);
9446 			if(i.usingXshm)
9447 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9448 			else
9449 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9450 		} else version(Windows) {
9451 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9452 			auto arrLength = itemsPerLine * height;
9453 			rawData[0..arrLength] = i.rawData[0..arrLength];
9454 		} else version(OSXCocoa) {
9455 			// FIXME: I have no idea if this is even any good
9456 			ubyte* rawData;
9457 
9458 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9459 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
9460 				colorSpace,
9461 				kCGImageAlphaPremultipliedLast
9462 				|kCGBitmapByteOrder32Big);
9463 			CGColorSpaceRelease(colorSpace);
9464 			rawData = CGBitmapContextGetData(context);
9465 
9466 			auto rdl = (width * height * 4);
9467 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9468 		} else static assert(0);
9469 	}
9470 
9471 	/++
9472 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9473 
9474 		Params:
9475 			where = point on the window where the upper left corner of the image will be drawn
9476 			imageUpperLeft = point on the image to start the slice to draw
9477 			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.
9478 		History:
9479 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9480 	+/
9481 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9482 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9483 	}
9484 
9485 	/// Call this when you're ready to get rid of it
9486 	void dispose() {
9487 		version(X11) {
9488 			staticDispose(xrenderPicture, handle);
9489 			xrenderPicture = None;
9490 			handle = None;
9491 		} else version(Windows) {
9492 			staticDispose(handle);
9493 			handle = null;
9494 		} else version(OSXCocoa) {
9495 			staticDispose(context);
9496 			context = null;
9497 		} else static assert(0);
9498 
9499 	}
9500 
9501 	version(X11)
9502 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9503 		if(xrenderPicture)
9504 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9505 		if(handle)
9506 			XFreePixmap(XDisplayConnection.get(), handle);
9507 	}
9508 	else version(Windows)
9509 	static void staticDispose(HBITMAP handle) {
9510 		if(handle)
9511 			DeleteObject(handle);
9512 	}
9513 	else version(OSXCocoa)
9514 	static void staticDispose(CGContextRef context) {
9515 		if(context)
9516 			CGContextRelease(context);
9517 	}
9518 
9519 	~this() {
9520 		version(X11) { if(xrenderPicture || handle)
9521 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9522 		} else version(Windows) { if(handle)
9523 			cleanupQueue.queue!staticDispose(handle);
9524 		} else version(OSXCocoa) { if(context)
9525 			cleanupQueue.queue!staticDispose(context);
9526 		} else static assert(0);
9527 	}
9528 
9529 	///
9530 	final @property int width() { return _width; }
9531 
9532 	///
9533 	final @property int height() { return _height; }
9534 
9535 	///
9536 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9537 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9538 	}
9539 
9540 	auto nativeHandle() {
9541 		return handle;
9542 	}
9543 
9544 	private:
9545 
9546 	int _width;
9547 	int _height;
9548 	bool enableAlpha;
9549 	version(X11)
9550 		Pixmap handle;
9551 	else version(Windows)
9552 		HBITMAP handle;
9553 	else version(OSXCocoa)
9554 		CGContextRef context;
9555 	else static assert(0);
9556 }
9557 
9558 /++
9559 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9560 
9561 	History:
9562 		Added November 20, 2021 (dub v10.4)
9563 +/
9564 abstract class Gradient : Sprite {
9565 	protected this(int w, int h) {
9566 		version(X11) {
9567 			Sprite.requireXRender();
9568 
9569 			super();
9570 			enableAlpha = true;
9571 			_width = w;
9572 			_height = h;
9573 		} else version(Windows) {
9574 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9575 		}
9576 	}
9577 
9578 	version(Windows)
9579 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9580 		auto ptr = rawData;
9581 		foreach(j; 0 .. _height)
9582 		foreach(i; 0 .. _width) {
9583 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9584 			*rawData = (color.a * color.b) / 255; rawData++;
9585 			*rawData = (color.a * color.g) / 255; rawData++;
9586 			*rawData = (color.a * color.r) / 255; rawData++;
9587 			*rawData = color.a; rawData++;
9588 		}
9589 	}
9590 
9591 	version(X11)
9592 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9593 		assert(stops.length > 0);
9594 		assert(stops.length <= 16, "I got lazy with buffers");
9595 
9596 		XFixed[16] stopsPositions = void;
9597 		XRenderColor[16] colors = void;
9598 
9599 		foreach(idx, stop; stops) {
9600 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9601 			auto c = stop.c;
9602 			colors[idx] = XRenderColor(
9603 				cast(ushort)(c.r * ushort.max / 255),
9604 				cast(ushort)(c.g * ushort.max / 255),
9605 				cast(ushort)(c.b * ushort.max / 255),
9606 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9607 			);
9608 		}
9609 
9610 		xrenderPicture = dg(stopsPositions, colors);
9611 	}
9612 
9613 	///
9614 	static struct Stop {
9615 		float percentage; /// between 0 and 1.0
9616 		Color c;
9617 	}
9618 }
9619 
9620 /++
9621 	Creates a linear gradient between p1 and p2.
9622 
9623 	X ONLY RIGHT NOW
9624 
9625 	History:
9626 		Added November 20, 2021 (dub v10.4)
9627 
9628 	Bugs:
9629 		Not yet implemented on Windows.
9630 +/
9631 class LinearGradient : Gradient {
9632 	/++
9633 
9634 	+/
9635 	this(Point p1, Point p2, Stop[] stops...) {
9636 		super(p2.x, p2.y);
9637 
9638 		version(X11) {
9639 			XLinearGradient gradient;
9640 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9641 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9642 
9643 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9644 				return XRenderCreateLinearGradient(
9645 					XDisplayConnection.get,
9646 					&gradient,
9647 					stopsPositions.ptr,
9648 					colors.ptr,
9649 					cast(int) stops.length);
9650 			});
9651 		} else version(Windows) {
9652 			// FIXME
9653 			forEachPixel((int x, int y) {
9654 				import core.stdc.math;
9655 
9656 				//sqrtf(
9657 
9658 				return Color.transparent;
9659 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9660 			});
9661 		}
9662 	}
9663 }
9664 
9665 /++
9666 	A conical gradient goes from color to color around a circumference from a center point.
9667 
9668 	X ONLY RIGHT NOW
9669 
9670 	History:
9671 		Added November 20, 2021 (dub v10.4)
9672 
9673 	Bugs:
9674 		Not yet implemented on Windows.
9675 +/
9676 class ConicalGradient : Gradient {
9677 	/++
9678 
9679 	+/
9680 	this(Point center, float angleInDegrees, Stop[] stops...) {
9681 		super(center.x * 2, center.y * 2);
9682 
9683 		version(X11) {
9684 			XConicalGradient gradient;
9685 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9686 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9687 
9688 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9689 				return XRenderCreateConicalGradient(
9690 					XDisplayConnection.get,
9691 					&gradient,
9692 					stopsPositions.ptr,
9693 					colors.ptr,
9694 					cast(int) stops.length);
9695 			});
9696 		} else version(Windows) {
9697 			// FIXME
9698 			forEachPixel((int x, int y) {
9699 				import core.stdc.math;
9700 
9701 				//sqrtf(
9702 
9703 				return Color.transparent;
9704 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9705 			});
9706 
9707 		}
9708 	}
9709 }
9710 
9711 /++
9712 	A radial gradient goes from color to color based on distance from the center.
9713 	It is like rings of color.
9714 
9715 	X ONLY RIGHT NOW
9716 
9717 
9718 	More specifically, you create two circles: an inner circle and an outer circle.
9719 	The gradient is only drawn in the area outside the inner circle but inside the outer
9720 	circle. The closest line between those two circles forms the line for the gradient
9721 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9722 
9723 	History:
9724 		Added November 20, 2021 (dub v10.4)
9725 
9726 	Bugs:
9727 		Not yet implemented on Windows.
9728 +/
9729 class RadialGradient : Gradient {
9730 	/++
9731 
9732 	+/
9733 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
9734 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
9735 
9736 		version(X11) {
9737 			XRadialGradient gradient;
9738 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
9739 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
9740 
9741 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9742 				return XRenderCreateRadialGradient(
9743 					XDisplayConnection.get,
9744 					&gradient,
9745 					stopsPositions.ptr,
9746 					colors.ptr,
9747 					cast(int) stops.length);
9748 			});
9749 		} else version(Windows) {
9750 			// FIXME
9751 			forEachPixel((int x, int y) {
9752 				import core.stdc.math;
9753 
9754 				//sqrtf(
9755 
9756 				return Color.transparent;
9757 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9758 			});
9759 		}
9760 	}
9761 }
9762 
9763 
9764 
9765 /+
9766 	NOT IMPLEMENTED
9767 
9768 	A display-stored image optimized for relatively quick drawing, like
9769 	[Sprite], but this one supports alpha channel blending and does NOT
9770 	support direct drawing upon it with a [ScreenPainter].
9771 
9772 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
9773 	plain [ScreenPainter]... sort of.
9774 
9775 	On X11, it requires the Xrender extension and library. This is available
9776 	almost everywhere though.
9777 
9778 	History:
9779 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
9780 +/
9781 version(none)
9782 class AlphaSprite {
9783 	/++
9784 		Copies the given image into it.
9785 	+/
9786 	this(MemoryImage img) {
9787 
9788 		if(!XRenderLibrary.loadAttempted) {
9789 			XRenderLibrary.loadDynamicLibrary();
9790 
9791 			// FIXME: this needs to be reconstructed when the X server changes
9792 			repopulateX();
9793 		}
9794 		if(!XRenderLibrary.loadSuccessful)
9795 			throw new Exception("XRender library load failure");
9796 
9797 		// I probably need to put the alpha mask in a separate Picture
9798 		// ugh
9799 		// maybe the Sprite itself can have an alpha bitmask anyway
9800 
9801 
9802 		auto display = XDisplayConnection.get();
9803 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
9804 
9805 
9806 		XRenderPictureAttributes attrs;
9807 
9808 		handle = XRenderCreatePicture(
9809 			XDisplayConnection.get,
9810 			pixmap,
9811 			RGBA,
9812 			0,
9813 			&attrs
9814 		);
9815 
9816 	}
9817 
9818 	// maybe i'll use the create gradient functions too with static factories..
9819 
9820 	void drawAt(ScreenPainter painter, Point where) {
9821 		//painter.drawPixmap(this, where);
9822 
9823 		XRenderPictureAttributes attrs;
9824 
9825 		auto pic = XRenderCreatePicture(
9826 			XDisplayConnection.get,
9827 			painter.impl.d,
9828 			RGB,
9829 			0,
9830 			&attrs
9831 		);
9832 
9833 		XRenderComposite(
9834 			XDisplayConnection.get,
9835 			3, // PictOpOver
9836 			handle,
9837 			None,
9838 			pic,
9839 			0, // src
9840 			0,
9841 			0, // mask
9842 			0,
9843 			10, // dest
9844 			10,
9845 			100, // width
9846 			100
9847 		);
9848 
9849 		/+
9850 		XRenderFreePicture(
9851 			XDisplayConnection.get,
9852 			pic
9853 		);
9854 
9855 		XRenderFreePicture(
9856 			XDisplayConnection.get,
9857 			fill
9858 		);
9859 		+/
9860 		// on Windows you can stretch but Xrender still can't :(
9861 	}
9862 
9863 	static XRenderPictFormat* RGB;
9864 	static XRenderPictFormat* RGBA;
9865 	static void repopulateX() {
9866 		auto display = XDisplayConnection.get;
9867 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
9868 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
9869 	}
9870 
9871 	XPixmap pixmap;
9872 	Picture handle;
9873 }
9874 
9875 ///
9876 interface CapableOfBeingDrawnUpon {
9877 	///
9878 	ScreenPainter draw();
9879 	///
9880 	int width();
9881 	///
9882 	int height();
9883 	protected ScreenPainterImplementation* activeScreenPainter();
9884 	protected void activeScreenPainter(ScreenPainterImplementation*);
9885 	bool closed();
9886 
9887 	void delegate() paintingFinishedDg();
9888 
9889 	/// Be warned: this can be a very slow operation
9890 	TrueColorImage takeScreenshot();
9891 }
9892 
9893 /// 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].
9894 void flushGui() {
9895 	version(X11) {
9896 		auto dpy = XDisplayConnection.get();
9897 		XLockDisplay(dpy);
9898 		scope(exit) XUnlockDisplay(dpy);
9899 		XFlush(dpy);
9900 	}
9901 }
9902 
9903 /++
9904 	Runs the given code in the GUI thread when its event loop
9905 	is available, blocking until it completes. This allows you
9906 	to create and manipulate windows from another thread without
9907 	invoking undefined behavior.
9908 
9909 	If this is the gui thread, it runs the code immediately.
9910 
9911 	If no gui thread exists yet, the current thread is assumed
9912 	to be it. Attempting to create windows or run the event loop
9913 	in any other thread will cause an assertion failure.
9914 
9915 
9916 	$(TIP
9917 		Did you know you can use UFCS on delegate literals?
9918 
9919 		() {
9920 			// code here
9921 		}.runInGuiThread;
9922 	)
9923 
9924 	Returns:
9925 		`true` if the function was called, `false` if it was not.
9926 		The function may not be called because the gui thread had
9927 		already terminated by the time you called this.
9928 
9929 	History:
9930 		Added April 10, 2020 (v7.2.0)
9931 
9932 		Return value added and implementation tweaked to avoid locking
9933 		at program termination on February 24, 2021 (v9.2.1).
9934 +/
9935 bool runInGuiThread(scope void delegate() dg) @trusted {
9936 	claimGuiThread();
9937 
9938 	if(thisIsGuiThread) {
9939 		dg();
9940 		return true;
9941 	}
9942 
9943 	if(guiThreadTerminating)
9944 		return false;
9945 
9946 	import core.sync.semaphore;
9947 	static Semaphore sc;
9948 	if(sc is null)
9949 		sc = new Semaphore();
9950 
9951 	static RunQueueMember* rqm;
9952 	if(rqm is null)
9953 		rqm = new RunQueueMember;
9954 	rqm.dg = cast(typeof(rqm.dg)) dg;
9955 	rqm.signal = sc;
9956 	rqm.thrown = null;
9957 
9958 	synchronized(runInGuiThreadLock) {
9959 		runInGuiThreadQueue ~= rqm;
9960 	}
9961 
9962 	if(!SimpleWindow.eventWakeUp())
9963 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
9964 
9965 	rqm.signal.wait();
9966 	auto t = rqm.thrown;
9967 
9968 	if(t)
9969 		throw t;
9970 
9971 	return true;
9972 }
9973 
9974 // note it runs sync if this is the gui thread....
9975 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
9976 	claimGuiThread();
9977 
9978 	try {
9979 
9980 		if(thisIsGuiThread) {
9981 			dg();
9982 			return;
9983 		}
9984 
9985 		if(guiThreadTerminating)
9986 			return;
9987 
9988 		RunQueueMember* rqm = new RunQueueMember;
9989 		rqm.dg = cast(typeof(rqm.dg)) dg;
9990 		rqm.signal = null;
9991 		rqm.thrown = null;
9992 
9993 		synchronized(runInGuiThreadLock) {
9994 			runInGuiThreadQueue ~= rqm;
9995 		}
9996 
9997 		if(!SimpleWindow.eventWakeUp())
9998 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
9999 	} catch(Exception e) {
10000 		if(handleError)
10001 			handleError(e);
10002 	}
10003 }
10004 
10005 private void runPendingRunInGuiThreadDelegates() {
10006 	more:
10007 	RunQueueMember* next;
10008 	synchronized(runInGuiThreadLock) {
10009 		if(runInGuiThreadQueue.length) {
10010 			next = runInGuiThreadQueue[0];
10011 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10012 		} else {
10013 			next = null;
10014 		}
10015 	}
10016 
10017 	if(next) {
10018 		try {
10019 			next.dg();
10020 			next.thrown = null;
10021 		} catch(Throwable t) {
10022 			next.thrown = t;
10023 		}
10024 
10025 		if(next.signal)
10026 			next.signal.notify();
10027 
10028 		goto more;
10029 	}
10030 }
10031 
10032 private void claimGuiThread() nothrow {
10033 	import core.atomic;
10034 	if(cas(&guiThreadExists_, false, true))
10035 		thisIsGuiThread = true;
10036 }
10037 
10038 private struct RunQueueMember {
10039 	void delegate() dg;
10040 	import core.sync.semaphore;
10041 	Semaphore signal;
10042 	Throwable thrown;
10043 }
10044 
10045 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10046 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10047 private bool thisIsGuiThread = false;
10048 private shared bool guiThreadExists_ = false;
10049 private shared bool guiThreadTerminating = false;
10050 
10051 /++
10052 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10053 	event loop. All windows must be exclusively created and managed by a single thread.
10054 
10055 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10056 	when you call one of its constructors.
10057 
10058 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10059 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10060 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10061 
10062 	The reason this function is available is in case you want to message pass between a gui
10063 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10064 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10065 
10066 	History:
10067 		Added December 3, 2021 (dub v10.5)
10068 +/
10069 public bool guiThreadExists() {
10070 	return guiThreadExists_;
10071 }
10072 
10073 /++
10074 	Returns `true` if this thread is either running or set to be running the
10075 	simpledisplay.d gui core event loop because it owns windows.
10076 
10077 	It is important to keep gui-related functionality in the right thread, so you will
10078 	want to `runInGuiThread` when you call them (with some specific exceptions called
10079 	out in those specific functions' documentation). Notably, all windows must be
10080 	created and managed only from the gui thread.
10081 
10082 	Will return false if simpledisplay's other functions haven't been called
10083 	yet; check [guiThreadExists] in addition to this.
10084 
10085 	History:
10086 		Added December 3, 2021 (dub v10.5)
10087 +/
10088 public bool thisThreadRunningGui() {
10089 	return thisIsGuiThread;
10090 }
10091 
10092 /++
10093 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10094 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10095 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10096 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10097 	file instead if you are in one of those situations).
10098 
10099 	It does not support outputting very many types; just strings and ints are likely to actually work.
10100 
10101 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10102 	is unspecified meaning I can change it at any time. The only point of this function is to help
10103 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10104 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10105 	in those contexts.
10106 
10107 	$(WARNING
10108 		I reserve the right to change this function at any time. You can use it if it helps you
10109 		but do not rely on it for anything permanent.
10110 	)
10111 
10112 	History:
10113 		Added December 3, 2021. Not formally supported under any stable tag.
10114 +/
10115 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10116 	try {
10117 		version(Windows) {
10118 			import core.sys.windows.wincon;
10119 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10120 				AllocConsole();
10121 			const(char)* fn = "CONOUT$";
10122 		} else version(Posix) {
10123 			const(char)* fn = "/dev/tty";
10124 		} else static assert(0, "Function not implemented for your system");
10125 
10126 		if(fileOverride.length)
10127 			fn = fileOverride.ptr;
10128 
10129 		import core.stdc.stdio;
10130 		auto fp = fopen(fn, "wt");
10131 		if(fp is null) return;
10132 		scope(exit) fclose(fp);
10133 
10134 		string str;
10135 		foreach(item; t) {
10136 			static if(is(typeof(item) : const(char)[]))
10137 				str ~= item;
10138 			else
10139 				str ~= toInternal!string(item);
10140 			str ~= " ";
10141 		}
10142 		str ~= "\n";
10143 
10144 		fwrite(str.ptr, 1, str.length, fp);
10145 		fflush(fp);
10146 	} catch(Exception e) {
10147 		// sorry no hope
10148 	}
10149 }
10150 
10151 private void guiThreadFinalize() {
10152 	assert(thisIsGuiThread);
10153 
10154 	guiThreadTerminating = true; // don't add any more from this point on
10155 	runPendingRunInGuiThreadDelegates();
10156 }
10157 
10158 /+
10159 interface IPromise {
10160 	void reportProgress(int current, int max, string message);
10161 
10162 	/+ // not formally in cuz of templates but still
10163 	IPromise Then();
10164 	IPromise Catch();
10165 	IPromise Finally();
10166 	+/
10167 }
10168 
10169 /+
10170 	auto promise = async({ ... });
10171 	promise.Then(whatever).
10172 		Then(whateverelse).
10173 		Catch((exception) { });
10174 
10175 
10176 	A promise is run inside a fiber and it looks something like:
10177 
10178 	try {
10179 		auto res = whatever();
10180 		auto res2 = whateverelse(res);
10181 	} catch(Exception e) {
10182 		{ }(e);
10183 	}
10184 
10185 	When a thing succeeds, it is passed as an arg to the next
10186 +/
10187 class Promise(T) : IPromise {
10188 	auto Then() { return null; }
10189 	auto Catch() { return null; }
10190 	auto Finally() { return null; }
10191 
10192 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10193 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10194 	T await();
10195 }
10196 
10197 interface Task {
10198 }
10199 
10200 interface Resolvable(T) : Task {
10201 	void run();
10202 
10203 	void resolve(T);
10204 
10205 	Resolvable!T then(void delegate(T)); // returns a new promise
10206 	Resolvable!T error(Throwable); // js catch
10207 	Resolvable!T completed(); // js finally
10208 
10209 }
10210 
10211 /++
10212 	Runs `work` in a helper thread and sends its return value back to the main gui
10213 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10214 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10215 	kill the program.
10216 
10217 	You can call reportProgress(position, max, message) to update your parent window
10218 	on your progress.
10219 
10220 	I should also use `shared` methods. FIXME
10221 
10222 	History:
10223 		Added March 6, 2021 (dub version 9.3).
10224 +/
10225 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10226 	uponCompletion(work(null));
10227 }
10228 
10229 +/
10230 
10231 /// Used internal to dispatch events to various classes.
10232 interface CapableOfHandlingNativeEvent {
10233 	NativeEventHandler getNativeEventHandler();
10234 
10235 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10236 
10237 	version(X11) {
10238 		// if this is impossible, you are allowed to just throw from it
10239 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10240 		void recreateAfterDisconnect();
10241 		// discard any *connection specific* state, but keep enough that you
10242 		// can be recreated if possible. discardConnectionState() is always called immediately
10243 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10244 		// you need initialization order
10245 		void discardConnectionState();
10246 	}
10247 }
10248 
10249 version(X11)
10250 /++
10251 	State of keys on mouse events, especially motion.
10252 
10253 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10254 +/
10255 enum ModifierState : uint {
10256 	shift = 1, ///
10257 	capsLock = 2, ///
10258 	ctrl = 4, ///
10259 	alt = 8, /// Not always available on Windows
10260 	windows = 64, /// ditto
10261 	numLock = 16, ///
10262 
10263 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10264 	middleButtonDown = 512, /// ditto
10265 	rightButtonDown = 1024, /// ditto
10266 }
10267 else version(Windows)
10268 /// ditto
10269 enum ModifierState : uint {
10270 	shift = 4, ///
10271 	ctrl = 8, ///
10272 
10273 	// i'm not sure if the next two are available
10274 	alt = 256, /// not always available on Windows
10275 	windows = 512, /// ditto
10276 
10277 	capsLock = 1024, ///
10278 	numLock = 2048, ///
10279 
10280 	leftButtonDown = 1, /// not available on key events
10281 	middleButtonDown = 16, /// ditto
10282 	rightButtonDown = 2, /// ditto
10283 
10284 	backButtonDown = 0x20, /// not available on X
10285 	forwardButtonDown = 0x40, /// ditto
10286 }
10287 else version(OSXCocoa)
10288 // FIXME FIXME NotYetImplementedException
10289 enum ModifierState : uint {
10290 	shift = 1, ///
10291 	capsLock = 2, ///
10292 	ctrl = 4, ///
10293 	alt = 8, /// Not always available on Windows
10294 	windows = 64, /// ditto
10295 	numLock = 16, ///
10296 
10297 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10298 	middleButtonDown = 512, /// ditto
10299 	rightButtonDown = 1024, /// ditto
10300 }
10301 
10302 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10303 enum MouseButton : int {
10304 	none = 0,
10305 	left = 1, ///
10306 	right = 2, ///
10307 	middle = 4, ///
10308 	wheelUp = 8, ///
10309 	wheelDown = 16, ///
10310 	backButton = 32, /// often found on the thumb and used for back in browsers
10311 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10312 }
10313 
10314 version(X11) {
10315 	// FIXME: match ASCII whenever we can. Most of it is already there,
10316 	// but there's a few exceptions and mismatches with Windows
10317 
10318 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10319 	enum Key {
10320 		Escape = 0xff1b, ///
10321 		F1 = 0xffbe, ///
10322 		F2 = 0xffbf, ///
10323 		F3 = 0xffc0, ///
10324 		F4 = 0xffc1, ///
10325 		F5 = 0xffc2, ///
10326 		F6 = 0xffc3, ///
10327 		F7 = 0xffc4, ///
10328 		F8 = 0xffc5, ///
10329 		F9 = 0xffc6, ///
10330 		F10 = 0xffc7, ///
10331 		F11 = 0xffc8, ///
10332 		F12 = 0xffc9, ///
10333 		PrintScreen = 0xff61, ///
10334 		ScrollLock = 0xff14, ///
10335 		Pause = 0xff13, ///
10336 		Grave = 0x60, /// The $(BACKTICK) ~ key
10337 		// number keys across the top of the keyboard
10338 		N1 = 0x31, /// Number key atop the keyboard
10339 		N2 = 0x32, ///
10340 		N3 = 0x33, ///
10341 		N4 = 0x34, ///
10342 		N5 = 0x35, ///
10343 		N6 = 0x36, ///
10344 		N7 = 0x37, ///
10345 		N8 = 0x38, ///
10346 		N9 = 0x39, ///
10347 		N0 = 0x30, ///
10348 		Dash = 0x2d, ///
10349 		Equals = 0x3d, ///
10350 		Backslash = 0x5c, /// The \ | key
10351 		Backspace = 0xff08, ///
10352 		Insert = 0xff63, ///
10353 		Home = 0xff50, ///
10354 		PageUp = 0xff55, ///
10355 		Delete = 0xffff, ///
10356 		End = 0xff57, ///
10357 		PageDown = 0xff56, ///
10358 		Up = 0xff52, ///
10359 		Down = 0xff54, ///
10360 		Left = 0xff51, ///
10361 		Right = 0xff53, ///
10362 
10363 		Tab = 0xff09, ///
10364 		Q = 0x71, ///
10365 		W = 0x77, ///
10366 		E = 0x65, ///
10367 		R = 0x72, ///
10368 		T = 0x74, ///
10369 		Y = 0x79, ///
10370 		U = 0x75, ///
10371 		I = 0x69, ///
10372 		O = 0x6f, ///
10373 		P = 0x70, ///
10374 		LeftBracket = 0x5b, /// the [ { key
10375 		RightBracket = 0x5d, /// the ] } key
10376 		CapsLock = 0xffe5, ///
10377 		A = 0x61, ///
10378 		S = 0x73, ///
10379 		D = 0x64, ///
10380 		F = 0x66, ///
10381 		G = 0x67, ///
10382 		H = 0x68, ///
10383 		J = 0x6a, ///
10384 		K = 0x6b, ///
10385 		L = 0x6c, ///
10386 		Semicolon = 0x3b, ///
10387 		Apostrophe = 0x27, ///
10388 		Enter = 0xff0d, ///
10389 		Shift = 0xffe1, ///
10390 		Z = 0x7a, ///
10391 		X = 0x78, ///
10392 		C = 0x63, ///
10393 		V = 0x76, ///
10394 		B = 0x62, ///
10395 		N = 0x6e, ///
10396 		M = 0x6d, ///
10397 		Comma = 0x2c, ///
10398 		Period = 0x2e, ///
10399 		Slash = 0x2f, /// the / ? key
10400 		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
10401 		Ctrl = 0xffe3, ///
10402 		Windows = 0xffeb, ///
10403 		Alt = 0xffe9, ///
10404 		Space = 0x20, ///
10405 		Alt_r = 0xffea, /// ditto of shift_r
10406 		Windows_r = 0xffec, ///
10407 		Menu = 0xff67, ///
10408 		Ctrl_r = 0xffe4, ///
10409 
10410 		NumLock = 0xff7f, ///
10411 		Divide = 0xffaf, /// The / key on the number pad
10412 		Multiply = 0xffaa, /// The * key on the number pad
10413 		Minus = 0xffad, /// The - key on the number pad
10414 		Plus = 0xffab, /// The + key on the number pad
10415 		PadEnter = 0xff8d, /// Numberpad enter key
10416 		Pad1 = 0xff9c, /// Numberpad keys
10417 		Pad2 = 0xff99, ///
10418 		Pad3 = 0xff9b, ///
10419 		Pad4 = 0xff96, ///
10420 		Pad5 = 0xff9d, ///
10421 		Pad6 = 0xff98, ///
10422 		Pad7 = 0xff95, ///
10423 		Pad8 = 0xff97, ///
10424 		Pad9 = 0xff9a, ///
10425 		Pad0 = 0xff9e, ///
10426 		PadDot = 0xff9f, ///
10427 	}
10428 } else version(Windows) {
10429 	// the character here is for en-us layouts and for illustration only
10430 	// if you actually want to get characters, wait for character events
10431 	// (the argument to your event handler is simply a dchar)
10432 	// those will be converted by the OS for the right locale.
10433 
10434 	enum Key {
10435 		Escape = 0x1b,
10436 		F1 = 0x70,
10437 		F2 = 0x71,
10438 		F3 = 0x72,
10439 		F4 = 0x73,
10440 		F5 = 0x74,
10441 		F6 = 0x75,
10442 		F7 = 0x76,
10443 		F8 = 0x77,
10444 		F9 = 0x78,
10445 		F10 = 0x79,
10446 		F11 = 0x7a,
10447 		F12 = 0x7b,
10448 		PrintScreen = 0x2c,
10449 		ScrollLock = 0x91,
10450 		Pause = 0x13,
10451 		Grave = 0xc0,
10452 		// number keys across the top of the keyboard
10453 		N1 = 0x31,
10454 		N2 = 0x32,
10455 		N3 = 0x33,
10456 		N4 = 0x34,
10457 		N5 = 0x35,
10458 		N6 = 0x36,
10459 		N7 = 0x37,
10460 		N8 = 0x38,
10461 		N9 = 0x39,
10462 		N0 = 0x30,
10463 		Dash = 0xbd,
10464 		Equals = 0xbb,
10465 		Backslash = 0xdc,
10466 		Backspace = 0x08,
10467 		Insert = 0x2d,
10468 		Home = 0x24,
10469 		PageUp = 0x21,
10470 		Delete = 0x2e,
10471 		End = 0x23,
10472 		PageDown = 0x22,
10473 		Up = 0x26,
10474 		Down = 0x28,
10475 		Left = 0x25,
10476 		Right = 0x27,
10477 
10478 		Tab = 0x09,
10479 		Q = 0x51,
10480 		W = 0x57,
10481 		E = 0x45,
10482 		R = 0x52,
10483 		T = 0x54,
10484 		Y = 0x59,
10485 		U = 0x55,
10486 		I = 0x49,
10487 		O = 0x4f,
10488 		P = 0x50,
10489 		LeftBracket = 0xdb,
10490 		RightBracket = 0xdd,
10491 		CapsLock = 0x14,
10492 		A = 0x41,
10493 		S = 0x53,
10494 		D = 0x44,
10495 		F = 0x46,
10496 		G = 0x47,
10497 		H = 0x48,
10498 		J = 0x4a,
10499 		K = 0x4b,
10500 		L = 0x4c,
10501 		Semicolon = 0xba,
10502 		Apostrophe = 0xde,
10503 		Enter = 0x0d,
10504 		Shift = 0x10,
10505 		Z = 0x5a,
10506 		X = 0x58,
10507 		C = 0x43,
10508 		V = 0x56,
10509 		B = 0x42,
10510 		N = 0x4e,
10511 		M = 0x4d,
10512 		Comma = 0xbc,
10513 		Period = 0xbe,
10514 		Slash = 0xbf,
10515 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10516 		Ctrl = 0x11,
10517 		Windows = 0x5b,
10518 		Alt = -5, // FIXME
10519 		Space = 0x20,
10520 		Alt_r = 0xffea, // ditto of shift_r
10521 		Windows_r = 0x5c, // ditto of shift_r
10522 		Menu = 0x5d,
10523 		Ctrl_r = 0xa3, // ditto of shift_r
10524 
10525 		NumLock = 0x90,
10526 		Divide = 0x6f,
10527 		Multiply = 0x6a,
10528 		Minus = 0x6d,
10529 		Plus = 0x6b,
10530 		PadEnter = -8, // FIXME
10531 		Pad1 = 0x61,
10532 		Pad2 = 0x62,
10533 		Pad3 = 0x63,
10534 		Pad4 = 0x64,
10535 		Pad5 = 0x65,
10536 		Pad6 = 0x66,
10537 		Pad7 = 0x67,
10538 		Pad8 = 0x68,
10539 		Pad9 = 0x69,
10540 		Pad0 = 0x60,
10541 		PadDot = 0x6e,
10542 	}
10543 
10544 	// I'm keeping this around for reference purposes
10545 	// ideally all these buttons will be listed for all platforms,
10546 	// but now now I'm just focusing on my US keyboard
10547 	version(none)
10548 	enum Key {
10549 		LBUTTON = 0x01,
10550 		RBUTTON = 0x02,
10551 		CANCEL = 0x03,
10552 		MBUTTON = 0x04,
10553 		//static if (_WIN32_WINNT > =  0x500) {
10554 		XBUTTON1 = 0x05,
10555 		XBUTTON2 = 0x06,
10556 		//}
10557 		BACK = 0x08,
10558 		TAB = 0x09,
10559 		CLEAR = 0x0C,
10560 		RETURN = 0x0D,
10561 		SHIFT = 0x10,
10562 		CONTROL = 0x11,
10563 		MENU = 0x12,
10564 		PAUSE = 0x13,
10565 		CAPITAL = 0x14,
10566 		KANA = 0x15,
10567 		HANGEUL = 0x15,
10568 		HANGUL = 0x15,
10569 		JUNJA = 0x17,
10570 		FINAL = 0x18,
10571 		HANJA = 0x19,
10572 		KANJI = 0x19,
10573 		ESCAPE = 0x1B,
10574 		CONVERT = 0x1C,
10575 		NONCONVERT = 0x1D,
10576 		ACCEPT = 0x1E,
10577 		MODECHANGE = 0x1F,
10578 		SPACE = 0x20,
10579 		PRIOR = 0x21,
10580 		NEXT = 0x22,
10581 		END = 0x23,
10582 		HOME = 0x24,
10583 		LEFT = 0x25,
10584 		UP = 0x26,
10585 		RIGHT = 0x27,
10586 		DOWN = 0x28,
10587 		SELECT = 0x29,
10588 		PRINT = 0x2A,
10589 		EXECUTE = 0x2B,
10590 		SNAPSHOT = 0x2C,
10591 		INSERT = 0x2D,
10592 		DELETE = 0x2E,
10593 		HELP = 0x2F,
10594 		LWIN = 0x5B,
10595 		RWIN = 0x5C,
10596 		APPS = 0x5D,
10597 		SLEEP = 0x5F,
10598 		NUMPAD0 = 0x60,
10599 		NUMPAD1 = 0x61,
10600 		NUMPAD2 = 0x62,
10601 		NUMPAD3 = 0x63,
10602 		NUMPAD4 = 0x64,
10603 		NUMPAD5 = 0x65,
10604 		NUMPAD6 = 0x66,
10605 		NUMPAD7 = 0x67,
10606 		NUMPAD8 = 0x68,
10607 		NUMPAD9 = 0x69,
10608 		MULTIPLY = 0x6A,
10609 		ADD = 0x6B,
10610 		SEPARATOR = 0x6C,
10611 		SUBTRACT = 0x6D,
10612 		DECIMAL = 0x6E,
10613 		DIVIDE = 0x6F,
10614 		F1 = 0x70,
10615 		F2 = 0x71,
10616 		F3 = 0x72,
10617 		F4 = 0x73,
10618 		F5 = 0x74,
10619 		F6 = 0x75,
10620 		F7 = 0x76,
10621 		F8 = 0x77,
10622 		F9 = 0x78,
10623 		F10 = 0x79,
10624 		F11 = 0x7A,
10625 		F12 = 0x7B,
10626 		F13 = 0x7C,
10627 		F14 = 0x7D,
10628 		F15 = 0x7E,
10629 		F16 = 0x7F,
10630 		F17 = 0x80,
10631 		F18 = 0x81,
10632 		F19 = 0x82,
10633 		F20 = 0x83,
10634 		F21 = 0x84,
10635 		F22 = 0x85,
10636 		F23 = 0x86,
10637 		F24 = 0x87,
10638 		NUMLOCK = 0x90,
10639 		SCROLL = 0x91,
10640 		LSHIFT = 0xA0,
10641 		RSHIFT = 0xA1,
10642 		LCONTROL = 0xA2,
10643 		RCONTROL = 0xA3,
10644 		LMENU = 0xA4,
10645 		RMENU = 0xA5,
10646 		//static if (_WIN32_WINNT > =  0x500) {
10647 		BROWSER_BACK = 0xA6,
10648 		BROWSER_FORWARD = 0xA7,
10649 		BROWSER_REFRESH = 0xA8,
10650 		BROWSER_STOP = 0xA9,
10651 		BROWSER_SEARCH = 0xAA,
10652 		BROWSER_FAVORITES = 0xAB,
10653 		BROWSER_HOME = 0xAC,
10654 		VOLUME_MUTE = 0xAD,
10655 		VOLUME_DOWN = 0xAE,
10656 		VOLUME_UP = 0xAF,
10657 		MEDIA_NEXT_TRACK = 0xB0,
10658 		MEDIA_PREV_TRACK = 0xB1,
10659 		MEDIA_STOP = 0xB2,
10660 		MEDIA_PLAY_PAUSE = 0xB3,
10661 		LAUNCH_MAIL = 0xB4,
10662 		LAUNCH_MEDIA_SELECT = 0xB5,
10663 		LAUNCH_APP1 = 0xB6,
10664 		LAUNCH_APP2 = 0xB7,
10665 		//}
10666 		OEM_1 = 0xBA,
10667 		//static if (_WIN32_WINNT > =  0x500) {
10668 		OEM_PLUS = 0xBB,
10669 		OEM_COMMA = 0xBC,
10670 		OEM_MINUS = 0xBD,
10671 		OEM_PERIOD = 0xBE,
10672 		//}
10673 		OEM_2 = 0xBF,
10674 		OEM_3 = 0xC0,
10675 		OEM_4 = 0xDB,
10676 		OEM_5 = 0xDC,
10677 		OEM_6 = 0xDD,
10678 		OEM_7 = 0xDE,
10679 		OEM_8 = 0xDF,
10680 		//static if (_WIN32_WINNT > =  0x500) {
10681 		OEM_102 = 0xE2,
10682 		//}
10683 		PROCESSKEY = 0xE5,
10684 		//static if (_WIN32_WINNT > =  0x500) {
10685 		PACKET = 0xE7,
10686 		//}
10687 		ATTN = 0xF6,
10688 		CRSEL = 0xF7,
10689 		EXSEL = 0xF8,
10690 		EREOF = 0xF9,
10691 		PLAY = 0xFA,
10692 		ZOOM = 0xFB,
10693 		NONAME = 0xFC,
10694 		PA1 = 0xFD,
10695 		OEM_CLEAR = 0xFE,
10696 	}
10697 
10698 } else version(OSXCocoa) {
10699 	// FIXME
10700 	enum Key {
10701 		Escape = 0x1b,
10702 		F1 = 0x70,
10703 		F2 = 0x71,
10704 		F3 = 0x72,
10705 		F4 = 0x73,
10706 		F5 = 0x74,
10707 		F6 = 0x75,
10708 		F7 = 0x76,
10709 		F8 = 0x77,
10710 		F9 = 0x78,
10711 		F10 = 0x79,
10712 		F11 = 0x7a,
10713 		F12 = 0x7b,
10714 		PrintScreen = 0x2c,
10715 		ScrollLock = -2, // FIXME
10716 		Pause = -3, // FIXME
10717 		Grave = 0xc0,
10718 		// number keys across the top of the keyboard
10719 		N1 = 0x31,
10720 		N2 = 0x32,
10721 		N3 = 0x33,
10722 		N4 = 0x34,
10723 		N5 = 0x35,
10724 		N6 = 0x36,
10725 		N7 = 0x37,
10726 		N8 = 0x38,
10727 		N9 = 0x39,
10728 		N0 = 0x30,
10729 		Dash = 0xbd,
10730 		Equals = 0xbb,
10731 		Backslash = 0xdc,
10732 		Backspace = 0x08,
10733 		Insert = 0x2d,
10734 		Home = 0x24,
10735 		PageUp = 0x21,
10736 		Delete = 0x2e,
10737 		End = 0x23,
10738 		PageDown = 0x22,
10739 		Up = 0x26,
10740 		Down = 0x28,
10741 		Left = 0x25,
10742 		Right = 0x27,
10743 
10744 		Tab = 0x09,
10745 		Q = 0x51,
10746 		W = 0x57,
10747 		E = 0x45,
10748 		R = 0x52,
10749 		T = 0x54,
10750 		Y = 0x59,
10751 		U = 0x55,
10752 		I = 0x49,
10753 		O = 0x4f,
10754 		P = 0x50,
10755 		LeftBracket = 0xdb,
10756 		RightBracket = 0xdd,
10757 		CapsLock = 0x14,
10758 		A = 0x41,
10759 		S = 0x53,
10760 		D = 0x44,
10761 		F = 0x46,
10762 		G = 0x47,
10763 		H = 0x48,
10764 		J = 0x4a,
10765 		K = 0x4b,
10766 		L = 0x4c,
10767 		Semicolon = 0xba,
10768 		Apostrophe = 0xde,
10769 		Enter = 0x0d,
10770 		Shift = 0x10,
10771 		Z = 0x5a,
10772 		X = 0x58,
10773 		C = 0x43,
10774 		V = 0x56,
10775 		B = 0x42,
10776 		N = 0x4e,
10777 		M = 0x4d,
10778 		Comma = 0xbc,
10779 		Period = 0xbe,
10780 		Slash = 0xbf,
10781 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10782 		Ctrl = 0x11,
10783 		Windows = 0x5b,
10784 		Alt = -5, // FIXME
10785 		Space = 0x20,
10786 		Alt_r = 0xffea, // ditto of shift_r
10787 		Windows_r = -6, // FIXME
10788 		Menu = 0x5d,
10789 		Ctrl_r = -7, // FIXME
10790 
10791 		NumLock = 0x90,
10792 		Divide = 0x6f,
10793 		Multiply = 0x6a,
10794 		Minus = 0x6d,
10795 		Plus = 0x6b,
10796 		PadEnter = -8, // FIXME
10797 		// FIXME for the rest of these:
10798 		Pad1 = 0xff9c,
10799 		Pad2 = 0xff99,
10800 		Pad3 = 0xff9b,
10801 		Pad4 = 0xff96,
10802 		Pad5 = 0xff9d,
10803 		Pad6 = 0xff98,
10804 		Pad7 = 0xff95,
10805 		Pad8 = 0xff97,
10806 		Pad9 = 0xff9a,
10807 		Pad0 = 0xff9e,
10808 		PadDot = 0xff9f,
10809 	}
10810 
10811 }
10812 
10813 /* Additional utilities */
10814 
10815 
10816 Color fromHsl(real h, real s, real l) {
10817 	return arsd.color.fromHsl([h,s,l]);
10818 }
10819 
10820 
10821 
10822 /* ********** What follows is the system-specific implementations *********/
10823 version(Windows) {
10824 
10825 
10826 	// helpers for making HICONs from MemoryImages
10827 	class WindowsIcon {
10828 		struct Win32Icon(int colorCount) {
10829 		align(1):
10830 			uint biSize;
10831 			int biWidth;
10832 			int biHeight;
10833 			ushort biPlanes;
10834 			ushort biBitCount;
10835 			uint biCompression;
10836 			uint biSizeImage;
10837 			int biXPelsPerMeter;
10838 			int biYPelsPerMeter;
10839 			uint biClrUsed;
10840 			uint biClrImportant;
10841 			RGBQUAD[colorCount] biColors;
10842 			/* Pixels:
10843 			Uint8 pixels[]
10844 			*/
10845 			/* Mask:
10846 			Uint8 mask[]
10847 			*/
10848 
10849 			ubyte[4096] data;
10850 
10851 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
10852 				width = mi.width;
10853 				height = mi.height;
10854 
10855 				auto indexedImage = cast(IndexedImage) mi;
10856 				if(indexedImage is null)
10857 					indexedImage = quantize(mi.getAsTrueColorImage());
10858 
10859 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
10860 				assert(height %4 == 0);
10861 
10862 				int icon_plen = height*((width+3)&~3);
10863 				int icon_mlen = height*((((width+7)/8)+3)&~3);
10864 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
10865 
10866 				biSize = 40;
10867 				biWidth = width;
10868 				biHeight = height*2;
10869 				biPlanes = 1;
10870 				biBitCount = 8;
10871 				biSizeImage = icon_plen+icon_mlen;
10872 
10873 				int offset = 0;
10874 				int andOff = icon_plen * 8; // the and offset is in bits
10875 				for(int y = height - 1; y >= 0; y--) {
10876 					int off2 = y * width;
10877 					foreach(x; 0 .. width) {
10878 						const b = indexedImage.data[off2 + x];
10879 						data[offset] = b;
10880 						offset++;
10881 
10882 						const andBit = andOff % 8;
10883 						const andIdx = andOff / 8;
10884 						assert(b < indexedImage.palette.length);
10885 						// this is anded to the destination, since and 0 means erase,
10886 						// we want that to  be opaque, and 1 for transparent
10887 						auto transparent = (indexedImage.palette[b].a <= 127);
10888 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
10889 
10890 						andOff++;
10891 					}
10892 
10893 					andOff += andOff % 32;
10894 				}
10895 
10896 				foreach(idx, entry; indexedImage.palette) {
10897 					if(entry.a > 127) {
10898 						biColors[idx].rgbBlue = entry.b;
10899 						biColors[idx].rgbGreen = entry.g;
10900 						biColors[idx].rgbRed = entry.r;
10901 					} else {
10902 						biColors[idx].rgbBlue = 255;
10903 						biColors[idx].rgbGreen = 255;
10904 						biColors[idx].rgbRed = 255;
10905 					}
10906 				}
10907 
10908 				/*
10909 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
10910 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
10911 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
10912 				auto pngMap = fetchPaletteWin32(png);
10913 				biColors[0..pngMap.length] = pngMap[];
10914 				*/
10915 			}
10916 		}
10917 
10918 
10919 		Win32Icon!(256) icon_win32;
10920 
10921 
10922 		this(MemoryImage mi) {
10923 			int icon_len, width, height;
10924 
10925 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
10926 
10927 			/*
10928 			PNG* png = readPnpngData);
10929 			PNGHeader pngh = getHeader(png);
10930 			void* icon_win32;
10931 			if(pngh.depth == 4) {
10932 				auto i = new Win32Icon!(16);
10933 				i.fromPNG(png, pngh, icon_len, width, height);
10934 				icon_win32 = i;
10935 			}
10936 			else if(pngh.depth == 8) {
10937 				auto i = new Win32Icon!(256);
10938 				i.fromPNG(png, pngh, icon_len, width, height);
10939 				icon_win32 = i;
10940 			} else assert(0);
10941 			*/
10942 
10943 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
10944 
10945 			if(hIcon is null) throw new Exception("CreateIconFromResourceEx");
10946 		}
10947 
10948 		~this() {
10949 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
10950 			DestroyIcon(hIcon);
10951 		}
10952 
10953 		HICON hIcon;
10954 	}
10955 
10956 
10957 
10958 
10959 
10960 
10961 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
10962 	alias HWND NativeWindowHandle;
10963 
10964 	extern(Windows)
10965 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
10966 		try {
10967 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
10968 				// it returns zero if the message is handled, so we won't do anything more there
10969 				// do I like that though?
10970 				int mustReturn;
10971 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
10972 				if(mustReturn)
10973 					return ret;
10974 			}
10975 
10976 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
10977 				if(window.getNativeEventHandler !is null) {
10978 					int mustReturn;
10979 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
10980 					if(mustReturn)
10981 						return ret;
10982 				}
10983 				if(auto w = cast(SimpleWindow) (*window))
10984 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
10985 				else
10986 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
10987 			} else {
10988 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
10989 			}
10990 		} catch (Exception e) {
10991 			try {
10992 				sdpy_abort(e);
10993 				return 0;
10994 			} catch(Exception e) { assert(0); }
10995 		}
10996 	}
10997 
10998 	void sdpy_abort(Throwable e) nothrow {
10999 		try
11000 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11001 		catch(Exception e)
11002 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11003 		ExitProcess(1);
11004 	}
11005 
11006 	mixin template NativeScreenPainterImplementation() {
11007 		HDC hdc;
11008 		HWND hwnd;
11009 		//HDC windowHdc;
11010 		HBITMAP oldBmp;
11011 
11012 		void create(NativeWindowHandle window) {
11013 			hwnd = window;
11014 
11015 			if(auto sw = cast(SimpleWindow) this.window) {
11016 				// drawing on a window, double buffer
11017 				auto windowHdc = GetDC(hwnd);
11018 
11019 				auto buffer = sw.impl.buffer;
11020 				if(buffer is null) {
11021 					hdc = windowHdc;
11022 					windowDc = true;
11023 				} else {
11024 					hdc = CreateCompatibleDC(windowHdc);
11025 
11026 					ReleaseDC(hwnd, windowHdc);
11027 
11028 					oldBmp = SelectObject(hdc, buffer);
11029 				}
11030 			} else {
11031 				// drawing on something else, draw directly
11032 				hdc = CreateCompatibleDC(null);
11033 				SelectObject(hdc, window);
11034 			}
11035 
11036 			// X doesn't draw a text background, so neither should we
11037 			SetBkMode(hdc, TRANSPARENT);
11038 
11039 			ensureDefaultFontLoaded();
11040 
11041 			if(defaultGuiFont) {
11042 				SelectObject(hdc, defaultGuiFont);
11043 				// DeleteObject(defaultGuiFont);
11044 			}
11045 		}
11046 
11047 		static HFONT defaultGuiFont;
11048 		static void ensureDefaultFontLoaded() {
11049 			static bool triedDefaultGuiFont = false;
11050 			if(!triedDefaultGuiFont) {
11051 				NONCLIENTMETRICS params;
11052 				params.cbSize = params.sizeof;
11053 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11054 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11055 				}
11056 				triedDefaultGuiFont = true;
11057 			}
11058 		}
11059 
11060 		void setFont(OperatingSystemFont font) {
11061 			if(font && font.font) {
11062 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11063 					// error... how to handle tho?
11064 				}
11065 			}
11066 			else if(defaultGuiFont)
11067 				SelectObject(hdc, defaultGuiFont);
11068 		}
11069 
11070 		arsd.color.Rectangle _clipRectangle;
11071 
11072 		void setClipRectangle(int x, int y, int width, int height) {
11073 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11074 
11075 			if(width == 0 || height == 0) {
11076 				SelectClipRgn(hdc, null);
11077 			} else {
11078 				auto region = CreateRectRgn(x, y, x + width, y + height);
11079 				SelectClipRgn(hdc, region);
11080 				DeleteObject(region);
11081 			}
11082 		}
11083 
11084 
11085 		// just because we can on Windows...
11086 		//void create(Image image);
11087 
11088 		void invalidateRect(Rectangle invalidRect) {
11089 			RECT rect;
11090 			rect.left = invalidRect.left;
11091 			rect.right = invalidRect.right;
11092 			rect.top = invalidRect.top;
11093 			rect.bottom = invalidRect.bottom;
11094 			InvalidateRect(hwnd, &rect, false);
11095 		}
11096 		bool manualInvalidations;
11097 
11098 		void dispose() {
11099 			// FIXME: this.window.width/height is probably wrong
11100 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11101 			// ReleaseDC(hwnd, windowHdc);
11102 
11103 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11104 			if(cast(SimpleWindow) this.window) {
11105 				if(!manualInvalidations)
11106 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11107 			}
11108 
11109 			if(originalPen !is null)
11110 				SelectObject(hdc, originalPen);
11111 			if(currentPen !is null)
11112 				DeleteObject(currentPen);
11113 			if(originalBrush !is null)
11114 				SelectObject(hdc, originalBrush);
11115 			if(currentBrush !is null)
11116 				DeleteObject(currentBrush);
11117 
11118 			SelectObject(hdc, oldBmp);
11119 
11120 			if(windowDc)
11121 				ReleaseDC(hwnd, hdc);
11122 			else
11123 				DeleteDC(hdc);
11124 
11125 			if(window.paintingFinishedDg !is null)
11126 				window.paintingFinishedDg()();
11127 		}
11128 
11129 		bool windowDc;
11130 		HPEN originalPen;
11131 		HPEN currentPen;
11132 
11133 		Pen _activePen;
11134 
11135 		Color _outlineColor;
11136 
11137 		@property void pen(Pen p) {
11138 			_activePen = p;
11139 			_outlineColor = p.color;
11140 
11141 			HPEN pen;
11142 			if(p.color.a == 0) {
11143 				pen = GetStockObject(NULL_PEN);
11144 			} else {
11145 				int style = PS_SOLID;
11146 				final switch(p.style) {
11147 					case Pen.Style.Solid:
11148 						style = PS_SOLID;
11149 					break;
11150 					case Pen.Style.Dashed:
11151 						style = PS_DASH;
11152 					break;
11153 					case Pen.Style.Dotted:
11154 						style = PS_DOT;
11155 					break;
11156 				}
11157 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11158 			}
11159 			auto orig = SelectObject(hdc, pen);
11160 			if(originalPen is null)
11161 				originalPen = orig;
11162 
11163 			if(currentPen !is null)
11164 				DeleteObject(currentPen);
11165 
11166 			currentPen = pen;
11167 
11168 			// the outline is like a foreground since it's done that way on X
11169 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11170 
11171 		}
11172 
11173 		@property void rasterOp(RasterOp op) {
11174 			int mode;
11175 			final switch(op) {
11176 				case RasterOp.normal:
11177 					mode = R2_COPYPEN;
11178 				break;
11179 				case RasterOp.xor:
11180 					mode = R2_XORPEN;
11181 				break;
11182 			}
11183 			SetROP2(hdc, mode);
11184 		}
11185 
11186 		HBRUSH originalBrush;
11187 		HBRUSH currentBrush;
11188 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11189 		@property void fillColor(Color c) {
11190 			if(c == _fillColor)
11191 				return;
11192 			_fillColor = c;
11193 			HBRUSH brush;
11194 			if(c.a == 0) {
11195 				brush = GetStockObject(HOLLOW_BRUSH);
11196 			} else {
11197 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11198 			}
11199 			auto orig = SelectObject(hdc, brush);
11200 			if(originalBrush is null)
11201 				originalBrush = orig;
11202 
11203 			if(currentBrush !is null)
11204 				DeleteObject(currentBrush);
11205 
11206 			currentBrush = brush;
11207 
11208 			// background color is NOT set because X doesn't draw text backgrounds
11209 			//   SetBkColor(hdc, RGB(255, 255, 255));
11210 		}
11211 
11212 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11213 			BITMAP bm;
11214 
11215 			HDC hdcMem = CreateCompatibleDC(hdc);
11216 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11217 
11218 			GetObject(i.handle, bm.sizeof, &bm);
11219 
11220 			// or should I AlphaBlend!??!?!
11221 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11222 
11223 			SelectObject(hdcMem, hbmOld);
11224 			DeleteDC(hdcMem);
11225 		}
11226 
11227 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11228 			BITMAP bm;
11229 
11230 			HDC hdcMem = CreateCompatibleDC(hdc);
11231 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11232 
11233 			GetObject(s.handle, bm.sizeof, &bm);
11234 
11235 			version(CRuntime_DigitalMars) goto noalpha;
11236 
11237 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11238 			if(s.enableAlpha) {
11239 				auto dw = w ? w : bm.bmWidth;
11240 				auto dh = h ? h : bm.bmHeight;
11241 				BLENDFUNCTION bf;
11242 				bf.BlendOp = AC_SRC_OVER;
11243 				bf.SourceConstantAlpha = 255;
11244 				bf.AlphaFormat = AC_SRC_ALPHA;
11245 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11246 			} else {
11247 				noalpha:
11248 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11249 			}
11250 
11251 			SelectObject(hdcMem, hbmOld);
11252 			DeleteDC(hdcMem);
11253 		}
11254 
11255 		Size textSize(scope const(char)[] text) {
11256 			bool dummyX;
11257 			if(text.length == 0) {
11258 				text = " ";
11259 				dummyX = true;
11260 			}
11261 			RECT rect;
11262 			WCharzBuffer buffer = WCharzBuffer(text);
11263 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11264 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11265 		}
11266 
11267 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11268 			if(text.length && text[$-1] == '\n')
11269 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11270 			if(text.length && text[$-1] == '\r')
11271 				text = text[0 .. $-1];
11272 
11273 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11274 			if(x2 == 0 && y2 == 0) {
11275 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11276 			} else {
11277 				RECT rect;
11278 				rect.left = x;
11279 				rect.top = y;
11280 				rect.right = x2;
11281 				rect.bottom = y2;
11282 
11283 				uint mode = DT_LEFT;
11284 				if(alignment & TextAlignment.Right)
11285 					mode = DT_RIGHT;
11286 				else if(alignment & TextAlignment.Center)
11287 					mode = DT_CENTER;
11288 
11289 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11290 				if(alignment & TextAlignment.VerticalCenter)
11291 					mode |= DT_VCENTER | DT_SINGLELINE;
11292 
11293 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11294 			}
11295 
11296 			/*
11297 			uint mode;
11298 
11299 			if(alignment & TextAlignment.Center)
11300 				mode = TA_CENTER;
11301 
11302 			SetTextAlign(hdc, mode);
11303 			*/
11304 		}
11305 
11306 		int fontHeight() {
11307 			TEXTMETRIC metric;
11308 			if(GetTextMetricsW(hdc, &metric)) {
11309 				return metric.tmHeight;
11310 			}
11311 
11312 			return 16; // idk just guessing here, maybe we should throw
11313 		}
11314 
11315 		void drawPixel(int x, int y) {
11316 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11317 		}
11318 
11319 		// The basic shapes, outlined
11320 
11321 		void drawLine(int x1, int y1, int x2, int y2) {
11322 			MoveToEx(hdc, x1, y1, null);
11323 			LineTo(hdc, x2, y2);
11324 		}
11325 
11326 		void drawRectangle(int x, int y, int width, int height) {
11327 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11328 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11329 		}
11330 
11331 		/// Arguments are the points of the bounding rectangle
11332 		void drawEllipse(int x1, int y1, int x2, int y2) {
11333 			Ellipse(hdc, x1, y1, x2, y2);
11334 		}
11335 
11336 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11337 			if((start % (360*64)) == (finish % (360*64)))
11338 				drawEllipse(x1, y1, x1 + width, y1 + height);
11339 			else {
11340 				import core.stdc.math;
11341 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11342 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11343 
11344 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11345 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11346 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11347 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11348 
11349 				if(_activePen.color.a)
11350 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11351 				if(_fillColor.a)
11352 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11353 			}
11354 		}
11355 
11356 		void drawPolygon(Point[] vertexes) {
11357 			POINT[] points;
11358 			points.length = vertexes.length;
11359 
11360 			foreach(i, p; vertexes) {
11361 				points[i].x = p.x;
11362 				points[i].y = p.y;
11363 			}
11364 
11365 			Polygon(hdc, points.ptr, cast(int) points.length);
11366 		}
11367 	}
11368 
11369 
11370 	// Mix this into the SimpleWindow class
11371 	mixin template NativeSimpleWindowImplementation() {
11372 		int curHidden = 0; // counter
11373 		__gshared static bool[string] knownWinClasses;
11374 		static bool altPressed = false;
11375 
11376 		HANDLE oldCursor;
11377 
11378 		void hideCursor () {
11379 			if(curHidden == 0)
11380 				oldCursor = SetCursor(null);
11381 			++curHidden;
11382 		}
11383 
11384 		void showCursor () {
11385 			--curHidden;
11386 			if(curHidden == 0) {
11387 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11388 			}
11389 		}
11390 
11391 
11392 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11393 
11394 		void setMinSize (int minwidth, int minheight) {
11395 			minWidth = minwidth;
11396 			minHeight = minheight;
11397 		}
11398 		void setMaxSize (int maxwidth, int maxheight) {
11399 			maxWidth = maxwidth;
11400 			maxHeight = maxheight;
11401 		}
11402 
11403 		// FIXME i'm not sure that Windows has this functionality
11404 		// though it is nonessential anyway.
11405 		void setResizeGranularity (int granx, int grany) {}
11406 
11407 		ScreenPainter getPainter(bool manualInvalidations) {
11408 			return ScreenPainter(this, hwnd, manualInvalidations);
11409 		}
11410 
11411 		HBITMAP buffer;
11412 
11413 		void setTitle(string title) {
11414 			WCharzBuffer bfr = WCharzBuffer(title);
11415 			SetWindowTextW(hwnd, bfr.ptr);
11416 		}
11417 
11418 		string getTitle() {
11419 			auto len = GetWindowTextLengthW(hwnd);
11420 			if (!len)
11421 				return null;
11422 			wchar[256] tmpBuffer;
11423 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11424 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11425 			auto str = buffer[0 .. len2];
11426 			return makeUtf8StringFromWindowsString(str);
11427 		}
11428 
11429 		void move(int x, int y) {
11430 			RECT rect;
11431 			GetWindowRect(hwnd, &rect);
11432 			// move it while maintaining the same size...
11433 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11434 		}
11435 
11436 		void resize(int w, int h) {
11437 			RECT rect;
11438 			GetWindowRect(hwnd, &rect);
11439 
11440 			RECT client;
11441 			GetClientRect(hwnd, &client);
11442 
11443 			rect.right = rect.right - client.right + w;
11444 			rect.bottom = rect.bottom - client.bottom + h;
11445 
11446 			// same position, new size for the client rectangle
11447 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11448 
11449 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
11450 		}
11451 
11452 		void moveResize (int x, int y, int w, int h) {
11453 			// what's given is the client rectangle, we need to adjust
11454 
11455 			RECT rect;
11456 			rect.left = x;
11457 			rect.top = y;
11458 			rect.right = w + x;
11459 			rect.bottom = h + y;
11460 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11461 				throw new Exception("AdjustWindowRect");
11462 
11463 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11464 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
11465 			if (windowResized !is null) windowResized(w, h);
11466 		}
11467 
11468 		version(without_opengl) {} else {
11469 			HGLRC ghRC;
11470 			HDC ghDC;
11471 		}
11472 
11473 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11474 			string cnamec;
11475 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11476 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11477 				cnamec = "DSimpleWindow";
11478 			} else {
11479 				cnamec = sdpyWindowClass;
11480 			}
11481 
11482 			WCharzBuffer cn = WCharzBuffer(cnamec);
11483 
11484 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11485 
11486 			if(cnamec !in knownWinClasses) {
11487 				WNDCLASSEX wc;
11488 
11489 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11490 				// to the object. Maybe.
11491 				wc.cbSize = wc.sizeof;
11492 				wc.cbClsExtra = 0;
11493 				wc.cbWndExtra = 0;
11494 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11495 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11496 				wc.hIcon = LoadIcon(hInstance, null);
11497 				wc.hInstance = hInstance;
11498 				wc.lpfnWndProc = &WndProc;
11499 				wc.lpszClassName = cn.ptr;
11500 				wc.hIconSm = null;
11501 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11502 				if(!RegisterClassExW(&wc))
11503 					throw new WindowsApiException("RegisterClassExW");
11504 				knownWinClasses[cnamec] = true;
11505 			}
11506 
11507 			int style;
11508 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11509 
11510 			// FIXME: windowType and customizationFlags
11511 			final switch(windowType) {
11512 				case WindowTypes.normal:
11513 					style = WS_OVERLAPPEDWINDOW;
11514 				break;
11515 				case WindowTypes.undecorated:
11516 					style = WS_POPUP | WS_SYSMENU;
11517 				break;
11518 				case WindowTypes.eventOnly:
11519 					_hidden = true;
11520 				break;
11521 				case WindowTypes.dropdownMenu:
11522 				case WindowTypes.popupMenu:
11523 				case WindowTypes.notification:
11524 					style = WS_POPUP;
11525 					flags |= WS_EX_NOACTIVATE;
11526 				break;
11527 				case WindowTypes.nestedChild:
11528 					style = WS_CHILD;
11529 				break;
11530 			}
11531 
11532 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11533 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11534 
11535 			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
11536 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11537 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11538 
11539 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11540 				setOpacity(255);
11541 
11542 			SimpleWindow.nativeMapping[hwnd] = this;
11543 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11544 
11545 			if(windowType == WindowTypes.eventOnly)
11546 				return;
11547 
11548 			HDC hdc = GetDC(hwnd);
11549 
11550 
11551 			version(without_opengl) {}
11552 			else {
11553 				if(opengl == OpenGlOptions.yes) {
11554 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11555 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11556 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11557 					ghDC = hdc;
11558 					PIXELFORMATDESCRIPTOR pfd;
11559 
11560 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11561 					pfd.nVersion = 1;
11562 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11563 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11564 					pfd.iPixelType = PFD_TYPE_RGBA;
11565 					pfd.cColorBits = 24;
11566 					pfd.cDepthBits = 24;
11567 					pfd.cAccumBits = 0;
11568 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11569 
11570 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11571 
11572 					if (pixelformat == 0)
11573 						throw new WindowsApiException("ChoosePixelFormat");
11574 
11575 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11576 						throw new WindowsApiException("SetPixelFormat");
11577 
11578 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11579 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11580 						// so we will create fake context to get that stupid address
11581 						auto tmpcc = wglCreateContext(ghDC);
11582 						if (tmpcc !is null) {
11583 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11584 							wglMakeCurrent(ghDC, tmpcc);
11585 							wglInitOtherFunctions();
11586 						}
11587 					}
11588 
11589 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11590 						int[9] contextAttribs = [
11591 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11592 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11593 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11594 							// for modern context, set "forward compatibility" flag too
11595 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11596 							0/*None*/,
11597 						];
11598 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11599 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11600 							// activate fallback mode
11601 							// 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;
11602 							ghRC = wglCreateContext(ghDC);
11603 						}
11604 						if (ghRC is null)
11605 							throw new WindowsApiException("wglCreateContextAttribsARB");
11606 					} else {
11607 						// try to do at least something
11608 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11609 							sdpyOpenGLContextVersion = 0;
11610 							ghRC = wglCreateContext(ghDC);
11611 						}
11612 						if (ghRC is null)
11613 							throw new WindowsApiException("wglCreateContext");
11614 					}
11615 				}
11616 			}
11617 
11618 			if(opengl == OpenGlOptions.no) {
11619 				buffer = CreateCompatibleBitmap(hdc, width, height);
11620 
11621 				auto hdcBmp = CreateCompatibleDC(hdc);
11622 				// make sure it's filled with a blank slate
11623 				auto oldBmp = SelectObject(hdcBmp, buffer);
11624 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11625 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11626 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11627 				SelectObject(hdcBmp, oldBmp);
11628 				SelectObject(hdcBmp, oldBrush);
11629 				SelectObject(hdcBmp, oldPen);
11630 				DeleteDC(hdcBmp);
11631 
11632 				bmpWidth = width;
11633 				bmpHeight = height;
11634 
11635 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11636 			}
11637 
11638 			// We want the window's client area to match the image size
11639 			RECT rcClient, rcWindow;
11640 			POINT ptDiff;
11641 			GetClientRect(hwnd, &rcClient);
11642 			GetWindowRect(hwnd, &rcWindow);
11643 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11644 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11645 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11646 
11647 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11648 				ShowWindow(hwnd, SW_SHOWNORMAL);
11649 			} else {
11650 				_hidden = true;
11651 			}
11652 			this._visibleForTheFirstTimeCalled = false; // hack!
11653 		}
11654 
11655 
11656 		void dispose() {
11657 			if(buffer)
11658 				DeleteObject(buffer);
11659 		}
11660 
11661 		void closeWindow() {
11662 			DestroyWindow(hwnd);
11663 		}
11664 
11665 		bool setOpacity(ubyte alpha) {
11666 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11667 		}
11668 
11669 		HANDLE currentCursor;
11670 
11671 		// returns zero if it recognized the event
11672 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11673 			MouseEvent mouse;
11674 
11675 			void mouseEvent(bool isScreen, ulong mods) {
11676 				auto x = LOWORD(lParam);
11677 				auto y = HIWORD(lParam);
11678 				if(isScreen) {
11679 					POINT p;
11680 					p.x = x;
11681 					p.y = y;
11682 					ScreenToClient(hwnd, &p);
11683 					x = cast(ushort) p.x;
11684 					y = cast(ushort) p.y;
11685 				}
11686 				mouse.x = x + offsetX;
11687 				mouse.y = y + offsetY;
11688 
11689 				wind.mdx(mouse);
11690 				mouse.modifierState = cast(int) mods;
11691 				mouse.window = wind;
11692 
11693 				if(wind.handleMouseEvent)
11694 					wind.handleMouseEvent(mouse);
11695 			}
11696 
11697 			switch(msg) {
11698 				case WM_GETMINMAXINFO:
11699 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11700 
11701 					if(wind.minWidth > 0) {
11702 						RECT rect;
11703 						rect.left = 100;
11704 						rect.top = 100;
11705 						rect.right = wind.minWidth + 100;
11706 						rect.bottom = wind.minHeight + 100;
11707 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11708 							throw new WindowsApiException("AdjustWindowRect");
11709 
11710 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11711 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11712 					}
11713 
11714 					if(wind.maxWidth < int.max) {
11715 						RECT rect;
11716 						rect.left = 100;
11717 						rect.top = 100;
11718 						rect.right = wind.maxWidth + 100;
11719 						rect.bottom = wind.maxHeight + 100;
11720 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11721 							throw new WindowsApiException("AdjustWindowRect");
11722 
11723 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11724 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11725 					}
11726 				break;
11727 				case WM_CHAR:
11728 					wchar c = cast(wchar) wParam;
11729 					if(wind.handleCharEvent)
11730 						wind.handleCharEvent(cast(dchar) c);
11731 				break;
11732 				  case WM_SETFOCUS:
11733 				  case WM_KILLFOCUS:
11734 					wind._focused = (msg == WM_SETFOCUS);
11735 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
11736 					if(wind.onFocusChange)
11737 						wind.onFocusChange(msg == WM_SETFOCUS);
11738 				  break;
11739 
11740 				case WM_SYSKEYDOWN:
11741 					goto case;
11742 				case WM_SYSKEYUP:
11743 					if(lParam & (1 << 29)) {
11744 						goto case;
11745 					} else {
11746 						// no window has keyboard focus
11747 						goto default;
11748 					}
11749 				case WM_KEYDOWN:
11750 				case WM_KEYUP:
11751 					KeyEvent ev;
11752 					ev.key = cast(Key) wParam;
11753 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
11754 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
11755 
11756 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
11757 
11758 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
11759 						ev.modifierState |= ModifierState.shift;
11760 					//k8: this doesn't work; thanks for nothing, windows
11761 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
11762 						ev.modifierState |= ModifierState.alt;*/
11763 					// this never seems to actually be set
11764 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11765 
11766 					if (wParam == 0x12) {
11767 						altPressed = (msg == WM_SYSKEYDOWN);
11768 					}
11769 
11770 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
11771 						altPressed = false;
11772 					}
11773 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
11774 
11775 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11776 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
11777 						ev.modifierState |= ModifierState.ctrl;
11778 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
11779 						ev.modifierState |= ModifierState.windows;
11780 					if(GetKeyState(Key.NumLock))
11781 						ev.modifierState |= ModifierState.numLock;
11782 					if(GetKeyState(Key.CapsLock))
11783 						ev.modifierState |= ModifierState.capsLock;
11784 
11785 					/+
11786 					// we always want to send the character too, so let's convert it
11787 					ubyte[256] state;
11788 					wchar[16] buffer;
11789 					GetKeyboardState(state.ptr);
11790 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
11791 
11792 					foreach(dchar d; buffer) {
11793 						ev.character = d;
11794 						break;
11795 					}
11796 					+/
11797 
11798 					ev.window = wind;
11799 					if(wind.handleKeyEvent)
11800 						wind.handleKeyEvent(ev);
11801 				break;
11802 				case 0x020a /*WM_MOUSEWHEEL*/:
11803 					// send click
11804 					mouse.type = cast(MouseEventType) 1;
11805 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11806 					mouseEvent(true, LOWORD(wParam));
11807 
11808 					// also send release
11809 					mouse.type = cast(MouseEventType) 2;
11810 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11811 					mouseEvent(true, LOWORD(wParam));
11812 				break;
11813 				case WM_MOUSEMOVE:
11814 					mouse.type = cast(MouseEventType) 0;
11815 					mouseEvent(false, wParam);
11816 				break;
11817 				case WM_LBUTTONDOWN:
11818 				case WM_LBUTTONDBLCLK:
11819 					mouse.type = cast(MouseEventType) 1;
11820 					mouse.button = MouseButton.left;
11821 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
11822 					mouseEvent(false, wParam);
11823 				break;
11824 				case WM_LBUTTONUP:
11825 					mouse.type = cast(MouseEventType) 2;
11826 					mouse.button = MouseButton.left;
11827 					mouseEvent(false, wParam);
11828 				break;
11829 				case WM_RBUTTONDOWN:
11830 				case WM_RBUTTONDBLCLK:
11831 					mouse.type = cast(MouseEventType) 1;
11832 					mouse.button = MouseButton.right;
11833 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
11834 					mouseEvent(false, wParam);
11835 				break;
11836 				case WM_RBUTTONUP:
11837 					mouse.type = cast(MouseEventType) 2;
11838 					mouse.button = MouseButton.right;
11839 					mouseEvent(false, wParam);
11840 				break;
11841 				case WM_MBUTTONDOWN:
11842 				case WM_MBUTTONDBLCLK:
11843 					mouse.type = cast(MouseEventType) 1;
11844 					mouse.button = MouseButton.middle;
11845 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
11846 					mouseEvent(false, wParam);
11847 				break;
11848 				case WM_MBUTTONUP:
11849 					mouse.type = cast(MouseEventType) 2;
11850 					mouse.button = MouseButton.middle;
11851 					mouseEvent(false, wParam);
11852 				break;
11853 				case WM_XBUTTONDOWN:
11854 				case WM_XBUTTONDBLCLK:
11855 					mouse.type = cast(MouseEventType) 1;
11856 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11857 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
11858 					mouseEvent(false, wParam);
11859 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
11860 				case WM_XBUTTONUP:
11861 					mouse.type = cast(MouseEventType) 2;
11862 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11863 					mouseEvent(false, wParam);
11864 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
11865 
11866 				default: return 1;
11867 			}
11868 			return 0;
11869 		}
11870 
11871 		HWND hwnd;
11872 		private int oldWidth;
11873 		private int oldHeight;
11874 		private bool inSizeMove;
11875 
11876 		/++
11877 			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.
11878 
11879 			History:
11880 				Added November 23, 2021
11881 
11882 				Not fully stable, may be moved out of the impl struct.
11883 
11884 				Default value changed to `true` on February 15, 2021
11885 		+/
11886 		bool doLiveResizing = true;
11887 
11888 		package int bmpWidth;
11889 		package int bmpHeight;
11890 
11891 		// the extern(Windows) wndproc should just forward to this
11892 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
11893 		try {
11894 			assert(hwnd is this.hwnd);
11895 
11896 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
11897 			switch(msg) {
11898 				case WM_MENUCHAR: // menu active but key not associated with a thing.
11899 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
11900 					// The main things we can do are select, execute, close, or ignore
11901 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
11902 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
11903 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
11904 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
11905 
11906 					// returns the value in the *high order word* of the return value
11907 					// hence the << 16
11908 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
11909 				case WM_SETCURSOR:
11910 					if(cast(HWND) wParam !is hwnd)
11911 						return 0; // further processing elsewhere
11912 
11913 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
11914 						SetCursor(this.curHidden > 0 ? null : currentCursor);
11915 						return 1;
11916 					} else {
11917 						return DefWindowProc(hwnd, msg, wParam, lParam);
11918 					}
11919 				//break;
11920 
11921 				case WM_CLOSE:
11922 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
11923 				break;
11924 				case WM_DESTROY:
11925 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
11926 					SimpleWindow.nativeMapping.remove(hwnd);
11927 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
11928 
11929 					bool anyImportant = false;
11930 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
11931 						if(w.beingOpenKeepsAppOpen) {
11932 							anyImportant = true;
11933 							break;
11934 						}
11935 					if(!anyImportant) {
11936 						PostQuitMessage(0);
11937 					}
11938 				break;
11939 				case 0x02E0 /*WM_DPICHANGED*/:
11940 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
11941 
11942 					RECT* prcNewWindow = cast(RECT*)lParam;
11943 					// docs say this is the recommended position and we should honor it
11944 					SetWindowPos(hwnd,
11945 							null,
11946 							prcNewWindow.left,
11947 							prcNewWindow.top,
11948 							prcNewWindow.right - prcNewWindow.left,
11949 							prcNewWindow.bottom - prcNewWindow.top,
11950 							SWP_NOZORDER | SWP_NOACTIVATE);
11951 
11952 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
11953 					// im not sure it is completely correct
11954 					// but without it the tabs and such do look weird as things change.
11955 					if(SystemParametersInfoForDpi) {
11956 						LOGFONT lfText;
11957 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
11958 						HFONT hFontNew = CreateFontIndirect(&lfText);
11959 						if (hFontNew)
11960 						{
11961 							//DeleteObject(hFontOld);
11962 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
11963 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
11964 								return TRUE;
11965 							}
11966 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
11967 						}
11968 					}
11969 
11970 					if(this.onDpiChanged)
11971 						this.onDpiChanged();
11972 				break;
11973 				case WM_ENTERIDLE:
11974 					// when a menu is up, it stops normal event processing (modal message loop)
11975 					// but this at least gives us a chance to SOMETIMES catch up
11976 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
11977 					SimpleWindow.processAllCustomEvents;
11978 					SimpleWindow.processAllCustomEvents;
11979 					SleepEx(0, true);
11980 					break;
11981 				case WM_SIZE:
11982 					if(wParam == 1 /* SIZE_MINIMIZED */)
11983 						break;
11984 					_width = LOWORD(lParam);
11985 					_height = HIWORD(lParam);
11986 
11987 					// I want to avoid tearing in the windows (my code is inefficient
11988 					// so this is a hack around that) so while sizing, we don't trigger,
11989 					// but we do want to trigger on events like mazimize.
11990 					if(!inSizeMove || doLiveResizing)
11991 						goto size_changed;
11992 				break;
11993 				/+
11994 				case WM_SIZING:
11995 					import std.stdio; writeln("size");
11996 				break;
11997 				+/
11998 				// I don't like the tearing I get when redrawing on WM_SIZE
11999 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12000 				// so instead it is going to redraw only at the end of a size.
12001 				case 0x0231: /* WM_ENTERSIZEMOVE */
12002 					inSizeMove = true;
12003 				break;
12004 				case 0x0232: /* WM_EXITSIZEMOVE */
12005 					inSizeMove = false;
12006 
12007 					size_changed:
12008 
12009 					// nothing relevant changed, don't bother redrawing
12010 					if(oldWidth == width && oldHeight == height) {
12011 						break;
12012 					}
12013 
12014 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12015 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12016 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12017 						// gotta get the double buffer bmp to match the window
12018 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12019 						if(width > bmpWidth || height > bmpHeight) {
12020 							auto hdc = GetDC(hwnd);
12021 							auto oldBuffer = buffer;
12022 							buffer = CreateCompatibleBitmap(hdc, width, height);
12023 
12024 							auto hdcBmp = CreateCompatibleDC(hdc);
12025 							auto oldBmp = SelectObject(hdcBmp, buffer);
12026 
12027 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12028 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12029 
12030 							/+
12031 							RECT r;
12032 							r.left = 0;
12033 							r.top = 0;
12034 							r.right = width;
12035 							r.bottom = height;
12036 							auto c = Color.green;
12037 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12038 							FillRect(hdcBmp, &r, brush);
12039 							DeleteObject(brush);
12040 							+/
12041 
12042 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12043 
12044 							bmpWidth = width;
12045 							bmpHeight = height;
12046 
12047 							SelectObject(hdcOldBmp, oldOldBmp);
12048 							DeleteDC(hdcOldBmp);
12049 
12050 							SelectObject(hdcBmp, oldBmp);
12051 							DeleteDC(hdcBmp);
12052 
12053 							ReleaseDC(hwnd, hdc);
12054 
12055 							DeleteObject(oldBuffer);
12056 						}
12057 					}
12058 
12059 					version(without_opengl) {} else
12060 					if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
12061 						glViewport(0, 0, width, height);
12062 					}
12063 
12064 					if(windowResized !is null)
12065 						windowResized(width, height);
12066 
12067 					if(inSizeMove) {
12068 						SimpleWindow.processAllCustomEvents();
12069 						SimpleWindow.processAllCustomEvents();
12070 					} else {
12071 						// when it is all done, make sure everything is freshly drawn or there might be
12072 						// weird bugs left.
12073 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12074 					}
12075 
12076 					oldWidth = this.width;
12077 					oldHeight = this.height;
12078 				break;
12079 				case WM_ERASEBKGND:
12080 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12081 					if (!this._visibleForTheFirstTimeCalled) {
12082 						this._visibleForTheFirstTimeCalled = true;
12083 						if (this.visibleForTheFirstTime !is null) {
12084 							version(without_opengl) {} else {
12085 								if(openglMode == OpenGlOptions.yes) {
12086 									this.setAsCurrentOpenGlContextNT();
12087 									glViewport(0, 0, width, height);
12088 								}
12089 							}
12090 							this.visibleForTheFirstTime();
12091 						}
12092 					}
12093 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12094 					version(without_opengl) {} else {
12095 						if (openglMode == OpenGlOptions.yes) return 1;
12096 					}
12097 					// call windows default handler, so it can paint standard controls
12098 					goto default;
12099 				case WM_CTLCOLORBTN:
12100 				case WM_CTLCOLORSTATIC:
12101 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12102 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12103 					GetSysColorBrush(COLOR_3DFACE);
12104 				//break;
12105 				case WM_SHOWWINDOW:
12106 					this._visible = (wParam != 0);
12107 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12108 						this._visibleForTheFirstTimeCalled = true;
12109 						if (this.visibleForTheFirstTime !is null) {
12110 							version(without_opengl) {} else {
12111 								if(openglMode == OpenGlOptions.yes) {
12112 									this.setAsCurrentOpenGlContextNT();
12113 									glViewport(0, 0, width, height);
12114 								}
12115 							}
12116 							this.visibleForTheFirstTime();
12117 						}
12118 					}
12119 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12120 					break;
12121 				case WM_PAINT: {
12122 					if (!this._visibleForTheFirstTimeCalled) {
12123 						this._visibleForTheFirstTimeCalled = true;
12124 						if (this.visibleForTheFirstTime !is null) {
12125 							version(without_opengl) {} else {
12126 								if(openglMode == OpenGlOptions.yes) {
12127 									this.setAsCurrentOpenGlContextNT();
12128 									glViewport(0, 0, width, height);
12129 								}
12130 							}
12131 							this.visibleForTheFirstTime();
12132 						}
12133 					}
12134 
12135 					BITMAP bm;
12136 					PAINTSTRUCT ps;
12137 
12138 					HDC hdc = BeginPaint(hwnd, &ps);
12139 
12140 					if(openglMode == OpenGlOptions.no) {
12141 
12142 						HDC hdcMem = CreateCompatibleDC(hdc);
12143 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12144 
12145 						GetObject(buffer, bm.sizeof, &bm);
12146 
12147 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12148 						if(resizability == Resizability.automaticallyScaleIfPossible)
12149 						StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12150 						else
12151 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12152 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12153 
12154 						SelectObject(hdcMem, hbmOld);
12155 						DeleteDC(hdcMem);
12156 						EndPaint(hwnd, &ps);
12157 					} else {
12158 						EndPaint(hwnd, &ps);
12159 						version(without_opengl) {} else
12160 							redrawOpenGlSceneNow();
12161 					}
12162 				} break;
12163 				  default:
12164 					return DefWindowProc(hwnd, msg, wParam, lParam);
12165 			}
12166 			 return 0;
12167 
12168 		}
12169 		catch(Throwable t) {
12170 			sdpyPrintDebugString(t.toString);
12171 			return 0;
12172 		}
12173 		}
12174 	}
12175 
12176 	mixin template NativeImageImplementation() {
12177 		HBITMAP handle;
12178 		ubyte* rawData;
12179 
12180 	final:
12181 
12182 		Color getPixel(int x, int y) {
12183 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12184 			// remember, bmps are upside down
12185 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12186 
12187 			Color c;
12188 			if(enableAlpha)
12189 				c.a = rawData[offset + 3];
12190 			else
12191 				c.a = 255;
12192 			c.b = rawData[offset + 0];
12193 			c.g = rawData[offset + 1];
12194 			c.r = rawData[offset + 2];
12195 			c.unPremultiply();
12196 			return c;
12197 		}
12198 
12199 		void setPixel(int x, int y, Color c) {
12200 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12201 			// remember, bmps are upside down
12202 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12203 
12204 			if(enableAlpha)
12205 				c.premultiply();
12206 
12207 			rawData[offset + 0] = c.b;
12208 			rawData[offset + 1] = c.g;
12209 			rawData[offset + 2] = c.r;
12210 			if(enableAlpha)
12211 				rawData[offset + 3] = c.a;
12212 		}
12213 
12214 		void convertToRgbaBytes(ubyte[] where) {
12215 			assert(where.length == this.width * this.height * 4);
12216 
12217 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12218 			int idx = 0;
12219 			int offset = itemsPerLine * (height - 1);
12220 			// remember, bmps are upside down
12221 			for(int y = height - 1; y >= 0; y--) {
12222 				auto offsetStart = offset;
12223 				for(int x = 0; x < width; x++) {
12224 					where[idx + 0] = rawData[offset + 2]; // r
12225 					where[idx + 1] = rawData[offset + 1]; // g
12226 					where[idx + 2] = rawData[offset + 0]; // b
12227 					if(enableAlpha) {
12228 						where[idx + 3] = rawData[offset + 3]; // a
12229 						unPremultiplyRgba(where[idx .. idx + 4]);
12230 						offset++;
12231 					} else
12232 						where[idx + 3] = 255; // a
12233 					idx += 4;
12234 					offset += 3;
12235 				}
12236 
12237 				offset = offsetStart - itemsPerLine;
12238 			}
12239 		}
12240 
12241 		void setFromRgbaBytes(in ubyte[] what) {
12242 			assert(what.length == this.width * this.height * 4);
12243 
12244 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12245 			int idx = 0;
12246 			int offset = itemsPerLine * (height - 1);
12247 			// remember, bmps are upside down
12248 			for(int y = height - 1; y >= 0; y--) {
12249 				auto offsetStart = offset;
12250 				for(int x = 0; x < width; x++) {
12251 					if(enableAlpha) {
12252 						auto a = what[idx + 3];
12253 
12254 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12255 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12256 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12257 						rawData[offset + 3] = a; // a
12258 						//premultiplyBgra(rawData[offset .. offset + 4]);
12259 						offset++;
12260 					} else {
12261 						rawData[offset + 2] = what[idx + 0]; // r
12262 						rawData[offset + 1] = what[idx + 1]; // g
12263 						rawData[offset + 0] = what[idx + 2]; // b
12264 					}
12265 					idx += 4;
12266 					offset += 3;
12267 				}
12268 
12269 				offset = offsetStart - itemsPerLine;
12270 			}
12271 		}
12272 
12273 
12274 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12275 			BITMAPINFO infoheader;
12276 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12277 			infoheader.bmiHeader.biWidth = width;
12278 			infoheader.bmiHeader.biHeight = height;
12279 			infoheader.bmiHeader.biPlanes = 1;
12280 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12281 			infoheader.bmiHeader.biCompression = BI_RGB;
12282 
12283 			handle = CreateDIBSection(
12284 				null,
12285 				&infoheader,
12286 				DIB_RGB_COLORS,
12287 				cast(void**) &rawData,
12288 				null,
12289 				0);
12290 			if(handle is null)
12291 				throw new WindowsApiException("create image failed");
12292 
12293 		}
12294 
12295 		void dispose() {
12296 			DeleteObject(handle);
12297 		}
12298 	}
12299 
12300 	enum KEY_ESCAPE = 27;
12301 }
12302 version(X11) {
12303 	/// This is the default font used. You might change this before doing anything else with
12304 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12305 	/// for cross-platform compatibility.
12306 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12307 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12308 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12309 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12310 
12311 	alias int delegate(XEvent) NativeEventHandler;
12312 	alias Window NativeWindowHandle;
12313 
12314 	enum KEY_ESCAPE = 9;
12315 
12316 	mixin template NativeScreenPainterImplementation() {
12317 		Display* display;
12318 		Drawable d;
12319 		Drawable destiny;
12320 
12321 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12322 		GC gc;
12323 
12324 		__gshared bool fontAttempted;
12325 
12326 		__gshared XFontStruct* defaultfont;
12327 		__gshared XFontSet defaultfontset;
12328 
12329 		XFontStruct* font;
12330 		XFontSet fontset;
12331 
12332 		void create(NativeWindowHandle window) {
12333 			this.display = XDisplayConnection.get();
12334 
12335 			Drawable buffer = None;
12336 			if(auto sw = cast(SimpleWindow) this.window) {
12337 				buffer = sw.impl.buffer;
12338 				this.destiny = cast(Drawable) window;
12339 			} else {
12340 				buffer = cast(Drawable) window;
12341 				this.destiny = None;
12342 			}
12343 
12344 			this.d = cast(Drawable) buffer;
12345 
12346 			auto dgc = DefaultGC(display, DefaultScreen(display));
12347 
12348 			this.gc = XCreateGC(display, d, 0, null);
12349 
12350 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12351 
12352 			ensureDefaultFontLoaded();
12353 
12354 			font = defaultfont;
12355 			fontset = defaultfontset;
12356 
12357 			if(font) {
12358 				XSetFont(display, gc, font.fid);
12359 			}
12360 		}
12361 
12362 		static void ensureDefaultFontLoaded() {
12363 			if(!fontAttempted) {
12364 				auto display = XDisplayConnection.get;
12365 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12366 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12367 				if(font is null) {
12368 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12369 					font = XLoadQueryFont(display, xfontstr.ptr);
12370 				}
12371 
12372 				char** lol;
12373 				int lol2;
12374 				char* lol3;
12375 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12376 
12377 				fontAttempted = true;
12378 
12379 				defaultfont = font;
12380 				defaultfontset = fontset;
12381 			}
12382 		}
12383 
12384 		arsd.color.Rectangle _clipRectangle;
12385 		void setClipRectangle(int x, int y, int width, int height) {
12386 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12387 			if(width == 0 || height == 0) {
12388 				XSetClipMask(display, gc, None);
12389 
12390 				if(xrenderPicturePainter) {
12391 
12392 					XRectangle[1] rects;
12393 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12394 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12395 				}
12396 
12397 				version(with_xft) {
12398 					if(xftFont is null || xftDraw is null)
12399 						return;
12400 					XftDrawSetClip(xftDraw, null);
12401 				}
12402 			} else {
12403 				XRectangle[1] rects;
12404 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12405 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12406 
12407 				if(xrenderPicturePainter)
12408 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12409 
12410 				version(with_xft) {
12411 					if(xftFont is null || xftDraw is null)
12412 						return;
12413 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12414 				}
12415 			}
12416 		}
12417 
12418 		version(with_xft) {
12419 			XftFont* xftFont;
12420 			XftDraw* xftDraw;
12421 
12422 			XftColor xftColor;
12423 
12424 			void updateXftColor() {
12425 				if(xftFont is null)
12426 					return;
12427 
12428 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12429 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12430 
12431 				XftColorAllocValue(
12432 					display,
12433 					DefaultVisual(display, DefaultScreen(display)),
12434 					DefaultColormap(display, 0),
12435 					&colorIn,
12436 					&xftColor
12437 				);
12438 			}
12439 		}
12440 
12441 		void setFont(OperatingSystemFont font) {
12442 			version(with_xft) {
12443 				if(font && font.isXft && font.xftFont)
12444 					this.xftFont = font.xftFont;
12445 				else
12446 					this.xftFont = null;
12447 
12448 				if(this.xftFont) {
12449 					if(xftDraw is null) {
12450 						xftDraw = XftDrawCreate(
12451 							display,
12452 							d,
12453 							DefaultVisual(display, DefaultScreen(display)),
12454 							DefaultColormap(display, 0)
12455 						);
12456 
12457 						updateXftColor();
12458 					}
12459 
12460 					return;
12461 				}
12462 			}
12463 
12464 			if(font && font.font) {
12465 				this.font = font.font;
12466 				this.fontset = font.fontset;
12467 				XSetFont(display, gc, font.font.fid);
12468 			} else {
12469 				this.font = defaultfont;
12470 				this.fontset = defaultfontset;
12471 			}
12472 
12473 		}
12474 
12475 		private Picture xrenderPicturePainter;
12476 
12477 		bool manualInvalidations;
12478 		void invalidateRect(Rectangle invalidRect) {
12479 			// FIXME if manualInvalidations
12480 		}
12481 
12482 		void dispose() {
12483 			this.rasterOp = RasterOp.normal;
12484 
12485 			if(xrenderPicturePainter) {
12486 				XRenderFreePicture(display, xrenderPicturePainter);
12487 				xrenderPicturePainter = None;
12488 			}
12489 
12490 			// FIXME: this.window.width/height is probably wrong
12491 
12492 			// src x,y     then dest x, y
12493 			if(destiny != None) {
12494 				// FIXME: if manual invalidations we can actually only copy some of the area.
12495 				// if(manualInvalidations)
12496 				XSetClipMask(display, gc, None);
12497 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12498 			}
12499 
12500 			XFreeGC(display, gc);
12501 
12502 			version(with_xft)
12503 			if(xftDraw) {
12504 				XftDrawDestroy(xftDraw);
12505 				xftDraw = null;
12506 			}
12507 
12508 			/+
12509 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12510 			if(font && font !is defaultfont) {
12511 				XFreeFont(display, font);
12512 				font = null;
12513 			}
12514 			if(fontset && fontset !is defaultfontset) {
12515 				XFreeFontSet(display, fontset);
12516 				fontset = null;
12517 			}
12518 			+/
12519 			XFlush(display);
12520 
12521 			if(window.paintingFinishedDg !is null)
12522 				window.paintingFinishedDg()();
12523 		}
12524 
12525 		bool backgroundIsNotTransparent = true;
12526 		bool foregroundIsNotTransparent = true;
12527 
12528 		bool _penInitialized = false;
12529 		Pen _activePen;
12530 
12531 		Color _outlineColor;
12532 		Color _fillColor;
12533 
12534 		@property void pen(Pen p) {
12535 			if(_penInitialized && p == _activePen) {
12536 				return;
12537 			}
12538 			_penInitialized = true;
12539 			_activePen = p;
12540 			_outlineColor = p.color;
12541 
12542 			int style;
12543 
12544 			byte dashLength;
12545 
12546 			final switch(p.style) {
12547 				case Pen.Style.Solid:
12548 					style = 0 /*LineSolid*/;
12549 				break;
12550 				case Pen.Style.Dashed:
12551 					style = 1 /*LineOnOffDash*/;
12552 					dashLength = 4;
12553 				break;
12554 				case Pen.Style.Dotted:
12555 					style = 1 /*LineOnOffDash*/;
12556 					dashLength = 1;
12557 				break;
12558 			}
12559 
12560 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
12561 			if(dashLength)
12562 				XSetDashes(display, gc, 0, &dashLength, 1);
12563 
12564 			if(p.color.a == 0) {
12565 				foregroundIsNotTransparent = false;
12566 				return;
12567 			}
12568 
12569 			foregroundIsNotTransparent = true;
12570 
12571 			XSetForeground(display, gc, colorToX(p.color, display));
12572 
12573 			version(with_xft)
12574 				updateXftColor();
12575 		}
12576 
12577 		RasterOp _currentRasterOp;
12578 		bool _currentRasterOpInitialized = false;
12579 		@property void rasterOp(RasterOp op) {
12580 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12581 				return;
12582 			_currentRasterOp = op;
12583 			_currentRasterOpInitialized = true;
12584 			int mode;
12585 			final switch(op) {
12586 				case RasterOp.normal:
12587 					mode = GXcopy;
12588 				break;
12589 				case RasterOp.xor:
12590 					mode = GXxor;
12591 				break;
12592 			}
12593 			XSetFunction(display, gc, mode);
12594 		}
12595 
12596 
12597 		bool _fillColorInitialized = false;
12598 
12599 		@property void fillColor(Color c) {
12600 			if(_fillColorInitialized && _fillColor == c)
12601 				return; // already good, no need to waste time calling it
12602 			_fillColor = c;
12603 			_fillColorInitialized = true;
12604 			if(c.a == 0) {
12605 				backgroundIsNotTransparent = false;
12606 				return;
12607 			}
12608 
12609 			backgroundIsNotTransparent = true;
12610 
12611 			XSetBackground(display, gc, colorToX(c, display));
12612 
12613 		}
12614 
12615 		void swapColors() {
12616 			auto tmp = _fillColor;
12617 			fillColor = _outlineColor;
12618 			auto newPen = _activePen;
12619 			newPen.color = tmp;
12620 			pen(newPen);
12621 		}
12622 
12623 		uint colorToX(Color c, Display* display) {
12624 			auto visual = DefaultVisual(display, DefaultScreen(display));
12625 			import core.bitop;
12626 			uint color = 0;
12627 			{
12628 			auto startBit = bsf(visual.red_mask);
12629 			auto lastBit = bsr(visual.red_mask);
12630 			auto r = cast(uint) c.r;
12631 			r >>= 7 - (lastBit - startBit);
12632 			r <<= startBit;
12633 			color |= r;
12634 			}
12635 			{
12636 			auto startBit = bsf(visual.green_mask);
12637 			auto lastBit = bsr(visual.green_mask);
12638 			auto g = cast(uint) c.g;
12639 			g >>= 7 - (lastBit - startBit);
12640 			g <<= startBit;
12641 			color |= g;
12642 			}
12643 			{
12644 			auto startBit = bsf(visual.blue_mask);
12645 			auto lastBit = bsr(visual.blue_mask);
12646 			auto b = cast(uint) c.b;
12647 			b >>= 7 - (lastBit - startBit);
12648 			b <<= startBit;
12649 			color |= b;
12650 			}
12651 
12652 
12653 
12654 			return color;
12655 		}
12656 
12657 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12658 			// source x, source y
12659 			if(ix >= i.width) return;
12660 			if(iy >= i.height) return;
12661 			if(ix + w > i.width) w = i.width - ix;
12662 			if(iy + h > i.height) h = i.height - iy;
12663 			if(i.usingXshm)
12664 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12665 			else
12666 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12667 		}
12668 
12669 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12670 			if(s.enableAlpha) {
12671 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12672 				if(this.xrenderPicturePainter == None) {
12673 					XRenderPictureAttributes attrs;
12674 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12675 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12676 
12677 					// need to initialize the clip
12678 					XRectangle[1] rects;
12679 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12680 
12681 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12682 				}
12683 
12684 				XRenderComposite(
12685 					display,
12686 					3, // PicOpOver
12687 					s.xrenderPicture,
12688 					None,
12689 					this.xrenderPicturePainter,
12690 					ix,
12691 					iy,
12692 					0,
12693 					0,
12694 					x,
12695 					y,
12696 					w ? w : s.width,
12697 					h ? h : s.height
12698 				);
12699 			} else {
12700 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12701 			}
12702 		}
12703 
12704 		int fontHeight() {
12705 			version(with_xft)
12706 				if(xftFont !is null)
12707 					return xftFont.height;
12708 			if(font)
12709 				return font.max_bounds.ascent + font.max_bounds.descent;
12710 			return 12; // pretty common default...
12711 		}
12712 
12713 		int textWidth(in char[] line) {
12714 			version(with_xft)
12715 			if(xftFont) {
12716 				if(line.length == 0)
12717 					return 0;
12718 				XGlyphInfo extents;
12719 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12720 				return extents.width;
12721 			}
12722 
12723 			if(fontset) {
12724 				if(line.length == 0)
12725 					return 0;
12726 				XRectangle rect;
12727 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12728 
12729 				return rect.width;
12730 			}
12731 
12732 			if(font)
12733 				// FIXME: unicode
12734 				return XTextWidth( font, line.ptr, cast(int) line.length);
12735 			else
12736 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
12737 		}
12738 
12739 		Size textSize(in char[] text) {
12740 			auto maxWidth = 0;
12741 			auto lineHeight = fontHeight;
12742 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
12743 			foreach(line; text.split('\n')) {
12744 				int textWidth = this.textWidth(line);
12745 				if(textWidth > maxWidth)
12746 					maxWidth = textWidth;
12747 				h += lineHeight + 4;
12748 			}
12749 			return Size(maxWidth, h);
12750 		}
12751 
12752 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
12753 			const(char)[] text;
12754 			version(with_xft)
12755 			if(xftFont) {
12756 				text = originalText;
12757 				goto loaded;
12758 			}
12759 
12760 			if(fontset)
12761 				text = originalText;
12762 			else {
12763 				text.reserve(originalText.length);
12764 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
12765 				// then strip the rest so there isn't garbage
12766 				foreach(dchar ch; originalText)
12767 					if(ch < 256)
12768 						text ~= cast(ubyte) ch;
12769 					else
12770 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
12771 			}
12772 			loaded:
12773 			if(text.length == 0)
12774 				return;
12775 
12776 			// FIXME: should we clip it to the bounding box?
12777 			int textHeight = fontHeight;
12778 
12779 			auto lines = text.split('\n');
12780 
12781 			const lineHeight = textHeight;
12782 			textHeight *= lines.length;
12783 
12784 			int cy = y;
12785 
12786 			if(alignment & TextAlignment.VerticalBottom) {
12787 				if(y2 <= 0)
12788 					return;
12789 				auto h = y2 - y;
12790 				if(h > textHeight) {
12791 					cy += h - textHeight;
12792 					cy -= lineHeight / 2;
12793 				}
12794 			} else if(alignment & TextAlignment.VerticalCenter) {
12795 				if(y2 <= 0)
12796 					return;
12797 				auto h = y2 - y;
12798 				if(textHeight < h) {
12799 					cy += (h - textHeight) / 2;
12800 					//cy -= lineHeight / 4;
12801 				}
12802 			}
12803 
12804 			foreach(line; text.split('\n')) {
12805 				int textWidth = this.textWidth(line);
12806 
12807 				int px = x, py = cy;
12808 
12809 				if(alignment & TextAlignment.Center) {
12810 					if(x2 <= 0)
12811 						return;
12812 					auto w = x2 - x;
12813 					if(w > textWidth)
12814 						px += (w - textWidth) / 2;
12815 				} else if(alignment & TextAlignment.Right) {
12816 					if(x2 <= 0)
12817 						return;
12818 					auto pos = x2 - textWidth;
12819 					if(pos > x)
12820 						px = pos;
12821 				}
12822 
12823 				version(with_xft)
12824 				if(xftFont) {
12825 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
12826 
12827 					goto carry_on;
12828 				}
12829 
12830 				if(fontset)
12831 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12832 				else
12833 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12834 				carry_on:
12835 				cy += lineHeight + 4;
12836 			}
12837 		}
12838 
12839 		void drawPixel(int x, int y) {
12840 			XDrawPoint(display, d, gc, x, y);
12841 		}
12842 
12843 		// The basic shapes, outlined
12844 
12845 		void drawLine(int x1, int y1, int x2, int y2) {
12846 			if(foregroundIsNotTransparent)
12847 				XDrawLine(display, d, gc, x1, y1, x2, y2);
12848 		}
12849 
12850 		void drawRectangle(int x, int y, int width, int height) {
12851 			if(backgroundIsNotTransparent) {
12852 				swapColors();
12853 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
12854 				swapColors();
12855 			}
12856 			// 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
12857 			if(foregroundIsNotTransparent)
12858 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
12859 		}
12860 
12861 		/// Arguments are the points of the bounding rectangle
12862 		void drawEllipse(int x1, int y1, int x2, int y2) {
12863 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
12864 		}
12865 
12866 		// NOTE: start and finish are in units of degrees * 64
12867 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
12868 			if(backgroundIsNotTransparent) {
12869 				swapColors();
12870 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
12871 				swapColors();
12872 			}
12873 			if(foregroundIsNotTransparent) {
12874 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
12875 				// Windows draws the straight lines on the edges too so FIXME sort of
12876 			}
12877 		}
12878 
12879 		void drawPolygon(Point[] vertexes) {
12880 			XPoint[16] pointsBuffer;
12881 			XPoint[] points;
12882 			if(vertexes.length <= pointsBuffer.length)
12883 				points = pointsBuffer[0 .. vertexes.length];
12884 			else
12885 				points.length = vertexes.length;
12886 
12887 			foreach(i, p; vertexes) {
12888 				points[i].x = cast(short) p.x;
12889 				points[i].y = cast(short) p.y;
12890 			}
12891 
12892 			if(backgroundIsNotTransparent) {
12893 				swapColors();
12894 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
12895 				swapColors();
12896 			}
12897 			if(foregroundIsNotTransparent) {
12898 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
12899 			}
12900 		}
12901 	}
12902 
12903 	/* XRender { */
12904 
12905 	struct XRenderColor {
12906 		ushort red;
12907 		ushort green;
12908 		ushort blue;
12909 		ushort alpha;
12910 	}
12911 
12912 	alias Picture = XID;
12913 	alias PictFormat = XID;
12914 
12915 	struct XGlyphInfo {
12916 		ushort width;
12917 		ushort height;
12918 		short x;
12919 		short y;
12920 		short xOff;
12921 		short yOff;
12922 	}
12923 
12924 struct XRenderDirectFormat {
12925     short   red;
12926     short   redMask;
12927     short   green;
12928     short   greenMask;
12929     short   blue;
12930     short   blueMask;
12931     short   alpha;
12932     short   alphaMask;
12933 }
12934 
12935 struct XRenderPictFormat {
12936     PictFormat		id;
12937     int			type;
12938     int			depth;
12939     XRenderDirectFormat	direct;
12940     Colormap		colormap;
12941 }
12942 
12943 enum PictFormatID	=   (1 << 0);
12944 enum PictFormatType	=   (1 << 1);
12945 enum PictFormatDepth	=   (1 << 2);
12946 enum PictFormatRed	=   (1 << 3);
12947 enum PictFormatRedMask  =(1 << 4);
12948 enum PictFormatGreen	=   (1 << 5);
12949 enum PictFormatGreenMask=(1 << 6);
12950 enum PictFormatBlue	=   (1 << 7);
12951 enum PictFormatBlueMask =(1 << 8);
12952 enum PictFormatAlpha	=   (1 << 9);
12953 enum PictFormatAlphaMask=(1 << 10);
12954 enum PictFormatColormap =(1 << 11);
12955 
12956 struct XRenderPictureAttributes {
12957 	int 		repeat;
12958 	Picture		alpha_map;
12959 	int			alpha_x_origin;
12960 	int			alpha_y_origin;
12961 	int			clip_x_origin;
12962 	int			clip_y_origin;
12963 	Pixmap		clip_mask;
12964 	Bool		graphics_exposures;
12965 	int			subwindow_mode;
12966 	int			poly_edge;
12967 	int			poly_mode;
12968 	Atom		dither;
12969 	Bool		component_alpha;
12970 }
12971 
12972 alias int XFixed;
12973 
12974 struct XPointFixed {
12975     XFixed  x, y;
12976 }
12977 
12978 struct XCircle {
12979     XFixed x;
12980     XFixed y;
12981     XFixed radius;
12982 }
12983 
12984 struct XTransform {
12985     XFixed[3][3]  matrix;
12986 }
12987 
12988 struct XFilters {
12989     int	    nfilter;
12990     char    **filter;
12991     int	    nalias;
12992     short   *alias_;
12993 }
12994 
12995 struct XIndexValue {
12996     c_ulong    pixel;
12997     ushort   red, green, blue, alpha;
12998 }
12999 
13000 struct XAnimCursor {
13001     Cursor	    cursor;
13002     c_ulong   delay;
13003 }
13004 
13005 struct XLinearGradient {
13006     XPointFixed p1;
13007     XPointFixed p2;
13008 }
13009 
13010 struct XRadialGradient {
13011     XCircle inner;
13012     XCircle outer;
13013 }
13014 
13015 struct XConicalGradient {
13016     XPointFixed center;
13017     XFixed angle; /* in degrees */
13018 }
13019 
13020 enum PictStandardARGB32  = 0;
13021 enum PictStandardRGB24   = 1;
13022 enum PictStandardA8	 =  2;
13023 enum PictStandardA4	 =  3;
13024 enum PictStandardA1	 =  4;
13025 enum PictStandardNUM	 =  5;
13026 
13027 interface XRender {
13028 extern(C) @nogc:
13029 
13030 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13031 
13032 	Status XRenderQueryVersion (Display *dpy,
13033 			int     *major_versionp,
13034 			int     *minor_versionp);
13035 
13036 	Status XRenderQueryFormats (Display *dpy);
13037 
13038 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13039 
13040 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13041 
13042 	XRenderPictFormat *
13043 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13044 
13045 	XRenderPictFormat *
13046 		XRenderFindFormat (Display			*dpy,
13047 				c_ulong		mask,
13048 				const XRenderPictFormat	*templ,
13049 				int				count);
13050 	XRenderPictFormat *
13051 		XRenderFindStandardFormat (Display		*dpy,
13052 				int			format);
13053 
13054 	XIndexValue *
13055 		XRenderQueryPictIndexValues(Display			*dpy,
13056 				const XRenderPictFormat	*format,
13057 				int				*num);
13058 
13059 	Picture XRenderCreatePicture(
13060 		Display *dpy,
13061 		Drawable drawable,
13062 		const XRenderPictFormat *format,
13063 		c_ulong valuemask,
13064 		const XRenderPictureAttributes *attributes);
13065 
13066 	void XRenderChangePicture (Display				*dpy,
13067 				Picture				picture,
13068 				c_ulong			valuemask,
13069 				const XRenderPictureAttributes  *attributes);
13070 
13071 	void
13072 		XRenderSetPictureClipRectangles (Display	    *dpy,
13073 				Picture	    picture,
13074 				int		    xOrigin,
13075 				int		    yOrigin,
13076 				const XRectangle *rects,
13077 				int		    n);
13078 
13079 	void
13080 		XRenderSetPictureClipRegion (Display	    *dpy,
13081 				Picture	    picture,
13082 				Region	    r);
13083 
13084 	void
13085 		XRenderSetPictureTransform (Display	    *dpy,
13086 				Picture	    picture,
13087 				XTransform	    *transform);
13088 
13089 	void
13090 		XRenderFreePicture (Display                   *dpy,
13091 				Picture                   picture);
13092 
13093 	void
13094 		XRenderComposite (Display   *dpy,
13095 				int	    op,
13096 				Picture   src,
13097 				Picture   mask,
13098 				Picture   dst,
13099 				int	    src_x,
13100 				int	    src_y,
13101 				int	    mask_x,
13102 				int	    mask_y,
13103 				int	    dst_x,
13104 				int	    dst_y,
13105 				uint	width,
13106 				uint	height);
13107 
13108 
13109 	Picture XRenderCreateSolidFill (Display *dpy,
13110 			const XRenderColor *color);
13111 
13112 	Picture XRenderCreateLinearGradient (Display *dpy,
13113 			const XLinearGradient *gradient,
13114 			const XFixed *stops,
13115 			const XRenderColor *colors,
13116 			int nstops);
13117 
13118 	Picture XRenderCreateRadialGradient (Display *dpy,
13119 			const XRadialGradient *gradient,
13120 			const XFixed *stops,
13121 			const XRenderColor *colors,
13122 			int nstops);
13123 
13124 	Picture XRenderCreateConicalGradient (Display *dpy,
13125 			const XConicalGradient *gradient,
13126 			const XFixed *stops,
13127 			const XRenderColor *colors,
13128 			int nstops);
13129 
13130 
13131 
13132 	Cursor
13133 		XRenderCreateCursor (Display	    *dpy,
13134 				Picture	    source,
13135 				uint   x,
13136 				uint   y);
13137 
13138 	XFilters *
13139 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13140 
13141 	void
13142 		XRenderSetPictureFilter (Display    *dpy,
13143 				Picture    picture,
13144 				const char *filter,
13145 				XFixed	    *params,
13146 				int	    nparams);
13147 
13148 	Cursor
13149 		XRenderCreateAnimCursor (Display	*dpy,
13150 				int		ncursor,
13151 				XAnimCursor	*cursors);
13152 }
13153 
13154 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13155 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13156 
13157 	/* XRender } */
13158 
13159 	/* Xrandr { */
13160 
13161 struct XRRMonitorInfo {
13162     Atom name;
13163     Bool primary;
13164     Bool automatic;
13165     int noutput;
13166     int x;
13167     int y;
13168     int width;
13169     int height;
13170     int mwidth;
13171     int mheight;
13172     /*RROutput*/ void *outputs;
13173 }
13174 
13175 struct XRRScreenChangeNotifyEvent {
13176     int type;                   /* event base */
13177     c_ulong serial;       /* # of last request processed by server */
13178     Bool send_event;            /* true if this came from a SendEvent request */
13179     Display *display;           /* Display the event was read from */
13180     Window window;              /* window which selected for this event */
13181     Window root;                /* Root window for changed screen */
13182     Time timestamp;             /* when the screen change occurred */
13183     Time config_timestamp;      /* when the last configuration change */
13184     ushort/*SizeID*/ size_index;
13185     ushort/*SubpixelOrder*/ subpixel_order;
13186     ushort/*Rotation*/ rotation;
13187     int width;
13188     int height;
13189     int mwidth;
13190     int mheight;
13191 }
13192 
13193 enum RRScreenChangeNotify = 0;
13194 
13195 enum RRScreenChangeNotifyMask = 1;
13196 
13197 __gshared int xrrEventBase = -1;
13198 
13199 
13200 interface XRandr {
13201 extern(C) @nogc:
13202 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13203 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13204 
13205 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13206 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13207 
13208 	void XRRSelectInput(Display *dpy, Window window, int mask);
13209 }
13210 
13211 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13212 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13213 	/* Xrandr } */
13214 
13215 	/* Xft { */
13216 
13217 	// actually freetype
13218 	alias void FT_Face;
13219 
13220 	// actually fontconfig
13221 	private alias FcBool = int;
13222 	alias void FcCharSet;
13223 	alias void FcPattern;
13224 	alias void FcResult;
13225 	enum FcEndian { FcEndianBig, FcEndianLittle }
13226 	struct FcFontSet {
13227 		int nfont;
13228 		int sfont;
13229 		FcPattern** fonts;
13230 	}
13231 
13232 	// actually XRegion
13233 	struct BOX {
13234 		short x1, x2, y1, y2;
13235 	}
13236 	struct _XRegion {
13237 		c_long size;
13238 		c_long numRects;
13239 		BOX* rects;
13240 		BOX extents;
13241 	}
13242 
13243 	alias Region = _XRegion*;
13244 
13245 	// ok actually Xft
13246 
13247 	struct XftFontInfo;
13248 
13249 	struct XftFont {
13250 		int         ascent;
13251 		int         descent;
13252 		int         height;
13253 		int         max_advance_width;
13254 		FcCharSet*  charset;
13255 		FcPattern*  pattern;
13256 	}
13257 
13258 	struct XftDraw;
13259 
13260 	struct XftColor {
13261 		c_ulong pixel;
13262 		XRenderColor color;
13263 	}
13264 
13265 	struct XftCharSpec {
13266 		dchar           ucs4;
13267 		short           x;
13268 		short           y;
13269 	}
13270 
13271 	struct XftCharFontSpec {
13272 		XftFont         *font;
13273 		dchar           ucs4;
13274 		short           x;
13275 		short           y;
13276 	}
13277 
13278 	struct XftGlyphSpec {
13279 		uint            glyph;
13280 		short           x;
13281 		short           y;
13282 	}
13283 
13284 	struct XftGlyphFontSpec {
13285 		XftFont         *font;
13286 		uint            glyph;
13287 		short           x;
13288 		short           y;
13289 	}
13290 
13291 	interface Xft {
13292 	extern(C) @nogc pure:
13293 
13294 	Bool XftColorAllocName (Display  *dpy,
13295 				const Visual   *visual,
13296 				Colormap cmap,
13297 				const char     *name,
13298 				XftColor *result);
13299 
13300 	Bool XftColorAllocValue (Display         *dpy,
13301 				Visual          *visual,
13302 				Colormap        cmap,
13303 				const XRenderColor    *color,
13304 				XftColor        *result);
13305 
13306 	void XftColorFree (Display   *dpy,
13307 				Visual    *visual,
13308 				Colormap  cmap,
13309 				XftColor  *color);
13310 
13311 	Bool XftDefaultHasRender (Display *dpy);
13312 
13313 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13314 
13315 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13316 
13317 	XftDraw * XftDrawCreate (Display   *dpy,
13318 		       Drawable  drawable,
13319 		       Visual    *visual,
13320 		       Colormap  colormap);
13321 
13322 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13323 			     Pixmap   bitmap);
13324 
13325 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13326 			    Pixmap  pixmap,
13327 			    int     depth);
13328 
13329 	void XftDrawChange (XftDraw  *draw,
13330 		       Drawable drawable);
13331 
13332 	Display * XftDrawDisplay (XftDraw *draw);
13333 
13334 	Drawable XftDrawDrawable (XftDraw *draw);
13335 
13336 	Colormap XftDrawColormap (XftDraw *draw);
13337 
13338 	Visual * XftDrawVisual (XftDraw *draw);
13339 
13340 	void XftDrawDestroy (XftDraw *draw);
13341 
13342 	Picture XftDrawPicture (XftDraw *draw);
13343 
13344 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13345 
13346 	void XftDrawGlyphs (XftDraw          *draw,
13347 				const XftColor *color,
13348 				XftFont          *pub,
13349 				int              x,
13350 				int              y,
13351 				const uint  *glyphs,
13352 				int              nglyphs);
13353 
13354 	void XftDrawString8 (XftDraw             *draw,
13355 				const XftColor    *color,
13356 				XftFont             *pub,
13357 				int                 x,
13358 				int                 y,
13359 				const char     *string,
13360 				int                 len);
13361 
13362 	void XftDrawString16 (XftDraw            *draw,
13363 				const XftColor   *color,
13364 				XftFont            *pub,
13365 				int                x,
13366 				int                y,
13367 				const wchar   *string,
13368 				int                len);
13369 
13370 	void XftDrawString32 (XftDraw            *draw,
13371 				const XftColor   *color,
13372 				XftFont            *pub,
13373 				int                x,
13374 				int                y,
13375 				const dchar   *string,
13376 				int                len);
13377 
13378 	void XftDrawStringUtf8 (XftDraw          *draw,
13379 				const XftColor *color,
13380 				XftFont          *pub,
13381 				int              x,
13382 				int              y,
13383 				const char  *string,
13384 				int              len);
13385 	void XftDrawStringUtf16 (XftDraw             *draw,
13386 				const XftColor    *color,
13387 				XftFont             *pub,
13388 				int                 x,
13389 				int                 y,
13390 				const char     *string,
13391 				FcEndian            endian,
13392 				int                 len);
13393 
13394 	void XftDrawCharSpec (XftDraw                *draw,
13395 				const XftColor       *color,
13396 				XftFont                *pub,
13397 				const XftCharSpec    *chars,
13398 				int                    len);
13399 
13400 	void XftDrawCharFontSpec (XftDraw                    *draw,
13401 				const XftColor           *color,
13402 				const XftCharFontSpec    *chars,
13403 				int                        len);
13404 
13405 	void XftDrawGlyphSpec (XftDraw               *draw,
13406 				const XftColor      *color,
13407 				XftFont               *pub,
13408 				const XftGlyphSpec  *glyphs,
13409 				int                   len);
13410 
13411 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13412 				const XftColor          *color,
13413 				const XftGlyphFontSpec  *glyphs,
13414 				int                       len);
13415 
13416 	void XftDrawRect (XftDraw            *draw,
13417 				const XftColor   *color,
13418 				int                x,
13419 				int                y,
13420 				uint       width,
13421 				uint       height);
13422 
13423 	Bool XftDrawSetClip (XftDraw     *draw,
13424 				Region      r);
13425 
13426 
13427 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13428 				int                   xOrigin,
13429 				int                   yOrigin,
13430 				const XRectangle    *rects,
13431 				int                   n);
13432 
13433 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13434 				int        mode);
13435 
13436 	void XftGlyphExtents (Display            *dpy,
13437 				XftFont            *pub,
13438 				const uint    *glyphs,
13439 				int                nglyphs,
13440 				XGlyphInfo         *extents);
13441 
13442 	void XftTextExtents8 (Display            *dpy,
13443 				XftFont            *pub,
13444 				const char    *string,
13445 				int                len,
13446 				XGlyphInfo         *extents);
13447 
13448 	void XftTextExtents16 (Display           *dpy,
13449 				XftFont           *pub,
13450 				const wchar  *string,
13451 				int               len,
13452 				XGlyphInfo        *extents);
13453 
13454 	void XftTextExtents32 (Display           *dpy,
13455 				XftFont           *pub,
13456 				const dchar  *string,
13457 				int               len,
13458 				XGlyphInfo        *extents);
13459 
13460 	void XftTextExtentsUtf8 (Display         *dpy,
13461 				XftFont         *pub,
13462 				const char *string,
13463 				int             len,
13464 				XGlyphInfo      *extents);
13465 
13466 	void XftTextExtentsUtf16 (Display            *dpy,
13467 				XftFont            *pub,
13468 				const char    *string,
13469 				FcEndian           endian,
13470 				int                len,
13471 				XGlyphInfo         *extents);
13472 
13473 	FcPattern * XftFontMatch (Display           *dpy,
13474 				int               screen,
13475 				const FcPattern *pattern,
13476 				FcResult          *result);
13477 
13478 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13479 
13480 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13481 
13482 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13483 
13484 	FT_Face XftLockFace (XftFont *pub);
13485 
13486 	void XftUnlockFace (XftFont *pub);
13487 
13488 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13489 
13490 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13491 
13492 	dchar XftFontInfoHash (const XftFontInfo *fi);
13493 
13494 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13495 
13496 	XftFont * XftFontOpenInfo (Display        *dpy,
13497 				FcPattern      *pattern,
13498 				XftFontInfo    *fi);
13499 
13500 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13501 
13502 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13503 
13504 	void XftFontClose (Display *dpy, XftFont *pub);
13505 
13506 	FcBool XftInitFtLibrary();
13507 	void XftFontLoadGlyphs (Display          *dpy,
13508 				XftFont          *pub,
13509 				FcBool           need_bitmaps,
13510 				const uint  *glyphs,
13511 				int              nglyph);
13512 
13513 	void XftFontUnloadGlyphs (Display            *dpy,
13514 				XftFont            *pub,
13515 				const uint    *glyphs,
13516 				int                nglyph);
13517 
13518 	FcBool XftFontCheckGlyph (Display  *dpy,
13519 				XftFont  *pub,
13520 				FcBool   need_bitmaps,
13521 				uint  glyph,
13522 				uint  *missing,
13523 				int      *nmissing);
13524 
13525 	FcBool XftCharExists (Display      *dpy,
13526 				XftFont      *pub,
13527 				dchar    ucs4);
13528 
13529 	uint XftCharIndex (Display       *dpy,
13530 				XftFont       *pub,
13531 				dchar      ucs4);
13532 	FcBool XftInit (const char *config);
13533 
13534 	int XftGetVersion ();
13535 
13536 	FcFontSet * XftListFonts (Display   *dpy,
13537 				int       screen,
13538 				...);
13539 
13540 	FcPattern *XftNameParse (const char *name);
13541 
13542 	void XftGlyphRender (Display         *dpy,
13543 				int             op,
13544 				Picture         src,
13545 				XftFont         *pub,
13546 				Picture         dst,
13547 				int             srcx,
13548 				int             srcy,
13549 				int             x,
13550 				int             y,
13551 				const uint *glyphs,
13552 				int             nglyphs);
13553 
13554 	void XftGlyphSpecRender (Display                 *dpy,
13555 				int                     op,
13556 				Picture                 src,
13557 				XftFont                 *pub,
13558 				Picture                 dst,
13559 				int                     srcx,
13560 				int                     srcy,
13561 				const XftGlyphSpec    *glyphs,
13562 				int                     nglyphs);
13563 
13564 	void XftCharSpecRender (Display              *dpy,
13565 				int                  op,
13566 				Picture              src,
13567 				XftFont              *pub,
13568 				Picture              dst,
13569 				int                  srcx,
13570 				int                  srcy,
13571 				const XftCharSpec  *chars,
13572 				int                  len);
13573 	void XftGlyphFontSpecRender (Display                     *dpy,
13574 				int                         op,
13575 				Picture                     src,
13576 				Picture                     dst,
13577 				int                         srcx,
13578 				int                         srcy,
13579 				const XftGlyphFontSpec    *glyphs,
13580 				int                         nglyphs);
13581 
13582 	void XftCharFontSpecRender (Display                  *dpy,
13583 				int                      op,
13584 				Picture                  src,
13585 				Picture                  dst,
13586 				int                      srcx,
13587 				int                      srcy,
13588 				const XftCharFontSpec  *chars,
13589 				int                      len);
13590 
13591 	void XftTextRender8 (Display         *dpy,
13592 				int             op,
13593 				Picture         src,
13594 				XftFont         *pub,
13595 				Picture         dst,
13596 				int             srcx,
13597 				int             srcy,
13598 				int             x,
13599 				int             y,
13600 				const char *string,
13601 				int             len);
13602 	void XftTextRender16 (Display            *dpy,
13603 				int                op,
13604 				Picture            src,
13605 				XftFont            *pub,
13606 				Picture            dst,
13607 				int                srcx,
13608 				int                srcy,
13609 				int                x,
13610 				int                y,
13611 				const wchar   *string,
13612 				int                len);
13613 
13614 	void XftTextRender16BE (Display          *dpy,
13615 				int              op,
13616 				Picture          src,
13617 				XftFont          *pub,
13618 				Picture          dst,
13619 				int              srcx,
13620 				int              srcy,
13621 				int              x,
13622 				int              y,
13623 				const char  *string,
13624 				int              len);
13625 
13626 	void XftTextRender16LE (Display          *dpy,
13627 				int              op,
13628 				Picture          src,
13629 				XftFont          *pub,
13630 				Picture          dst,
13631 				int              srcx,
13632 				int              srcy,
13633 				int              x,
13634 				int              y,
13635 				const char  *string,
13636 				int              len);
13637 
13638 	void XftTextRender32 (Display            *dpy,
13639 				int                op,
13640 				Picture            src,
13641 				XftFont            *pub,
13642 				Picture            dst,
13643 				int                srcx,
13644 				int                srcy,
13645 				int                x,
13646 				int                y,
13647 				const dchar   *string,
13648 				int                len);
13649 
13650 	void XftTextRender32BE (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 char  *string,
13660 				int              len);
13661 
13662 	void XftTextRender32LE (Display          *dpy,
13663 				int              op,
13664 				Picture          src,
13665 				XftFont          *pub,
13666 				Picture          dst,
13667 				int              srcx,
13668 				int              srcy,
13669 				int              x,
13670 				int              y,
13671 				const char  *string,
13672 				int              len);
13673 
13674 	void XftTextRenderUtf8 (Display          *dpy,
13675 				int              op,
13676 				Picture          src,
13677 				XftFont          *pub,
13678 				Picture          dst,
13679 				int              srcx,
13680 				int              srcy,
13681 				int              x,
13682 				int              y,
13683 				const char  *string,
13684 				int              len);
13685 
13686 	void XftTextRenderUtf16 (Display         *dpy,
13687 				int             op,
13688 				Picture         src,
13689 				XftFont         *pub,
13690 				Picture         dst,
13691 				int             srcx,
13692 				int             srcy,
13693 				int             x,
13694 				int             y,
13695 				const char *string,
13696 				FcEndian        endian,
13697 				int             len);
13698 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13699 
13700 	}
13701 
13702 	interface FontConfig {
13703 	extern(C) @nogc pure:
13704 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13705 		void FcFontSetDestroy(FcFontSet*);
13706 		char* FcNameUnparse(const FcPattern *);
13707 	}
13708 
13709 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13710 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13711 
13712 
13713 	/* Xft } */
13714 
13715 	class XDisconnectException : Exception {
13716 		bool userRequested;
13717 		this(bool userRequested = true) {
13718 			this.userRequested = userRequested;
13719 			super("X disconnected");
13720 		}
13721 	}
13722 
13723 	/++
13724 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
13725 
13726 		Please note that it returns
13727 	+/
13728 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
13729 
13730 		static XErrorEvent[] errorBuffer;
13731 		
13732 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
13733 			errorBuffer ~= *evt;
13734 			return 0;
13735 		}
13736 
13737 		auto savedErrorHandler = XSetErrorHandler(&handler);
13738 
13739 		try {
13740 			dg();
13741 		} finally {
13742 			XSync(XDisplayConnection.get, 0/*False*/);
13743 			XSetErrorHandler(savedErrorHandler);
13744 		}
13745 
13746 		auto bfr = errorBuffer;
13747 		errorBuffer = null;
13748 
13749 		return bfr;
13750 	}
13751 
13752 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
13753 	class XDisplayConnection {
13754 		private __gshared Display* display;
13755 		private __gshared XIM xim;
13756 		private __gshared char* displayName;
13757 
13758 		private __gshared int connectionSequence_;
13759 		private __gshared bool isLocal_;
13760 
13761 		/// use this for lazy caching when reconnection
13762 		static int connectionSequenceNumber() { return connectionSequence_; }
13763 
13764 		/++
13765 			Guesses if the connection appears to be local.
13766 
13767 			History:
13768 				Added June 3, 2021
13769 		+/
13770 		static @property bool isLocal() nothrow @trusted @nogc {
13771 			return isLocal_;
13772 		}
13773 
13774 		/// Attempts recreation of state, may require application assistance
13775 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
13776 		/// then call this, and if successful, reenter the loop.
13777 		static void discardAndRecreate(string newDisplayString = null) {
13778 			if(insideXEventLoop)
13779 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
13780 
13781 			// 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
13782 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
13783 
13784 			foreach(handle; chnenhm) {
13785 				handle.discardConnectionState();
13786 			}
13787 
13788 			discardState();
13789 
13790 			if(newDisplayString !is null)
13791 				setDisplayName(newDisplayString);
13792 
13793 			auto display = get();
13794 
13795 			foreach(handle; chnenhm) {
13796 				handle.recreateAfterDisconnect();
13797 			}
13798 		}
13799 
13800 		private __gshared EventMask rootEventMask;
13801 
13802 		/++
13803 			Requests the specified input from the root window on the connection, in addition to any other request.
13804 
13805 			
13806 			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.
13807 
13808 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
13809 		+/
13810 		static void addRootInput(EventMask mask) {
13811 			auto old = rootEventMask;
13812 			rootEventMask |= mask;
13813 			get(); // to ensure display connected
13814 			if(display !is null && rootEventMask != old)
13815 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
13816 		}
13817 
13818 		static void discardState() {
13819 			freeImages();
13820 
13821 			foreach(atomPtr; interredAtoms)
13822 				*atomPtr = 0;
13823 			interredAtoms = null;
13824 			interredAtoms.assumeSafeAppend();
13825 
13826 			ScreenPainterImplementation.fontAttempted = false;
13827 			ScreenPainterImplementation.defaultfont = null;
13828 			ScreenPainterImplementation.defaultfontset = null;
13829 
13830 			Image.impl.xshmQueryCompleted = false;
13831 			Image.impl._xshmAvailable = false;
13832 
13833 			SimpleWindow.nativeMapping = null;
13834 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
13835 			// GlobalHotkeyManager
13836 
13837 			display = null;
13838 			xim = null;
13839 		}
13840 
13841 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
13842 		private static void createXIM () {
13843 			import core.stdc.locale : setlocale, LC_ALL;
13844 			import core.stdc.stdio : stderr, fprintf;
13845 			import core.stdc.stdlib : free;
13846 			import core.stdc.string : strdup;
13847 
13848 			static immutable string[3] mtry = [ null, "@im=local", "@im=" ];
13849 
13850 			auto olocale = strdup(setlocale(LC_ALL, null));
13851 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
13852 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
13853 
13854 			//fprintf(stderr, "opening IM...\n");
13855 			foreach (string s; mtry) {
13856 				if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
13857 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
13858 			}
13859 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
13860 		}
13861 
13862 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
13863 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
13864 		static struct ImgList {
13865 			size_t img; // class; hide it from GC
13866 			ImgList* next;
13867 		}
13868 
13869 		static __gshared ImgList* imglist = null;
13870 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
13871 
13872 		static void registerImage (Image img) {
13873 			if (!imglistLocked && img !is null) {
13874 				import core.stdc.stdlib : malloc;
13875 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
13876 				assert(it !is null); // do proper checks
13877 				it.img = cast(size_t)cast(void*)img;
13878 				it.next = imglist;
13879 				imglist = it;
13880 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
13881 			}
13882 		}
13883 
13884 		static void unregisterImage (Image img) {
13885 			if (!imglistLocked && img !is null) {
13886 				import core.stdc.stdlib : free;
13887 				ImgList* prev = null;
13888 				ImgList* cur = imglist;
13889 				while (cur !is null) {
13890 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
13891 					prev = cur;
13892 					cur = cur.next;
13893 				}
13894 				if (cur !is null) {
13895 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
13896 					free(cur);
13897 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
13898 				} else {
13899 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
13900 				}
13901 			}
13902 		}
13903 
13904 		static void freeImages () { // needed for discardAndRecreate
13905 			imglistLocked = true;
13906 			scope(exit) imglistLocked = false;
13907 			ImgList* cur = imglist;
13908 			ImgList* next = null;
13909 			while (cur !is null) {
13910 				import core.stdc.stdlib : free;
13911 				next = cur.next;
13912 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
13913 				(cast(Image)cast(void*)cur.img).dispose();
13914 				free(cur);
13915 				cur = next;
13916 			}
13917 			imglist = null;
13918 		}
13919 
13920 		/// can be used to override normal handling of display name
13921 		/// from environment and/or command line
13922 		static setDisplayName(string newDisplayName) {
13923 			displayName = cast(char*) (newDisplayName ~ '\0');
13924 		}
13925 
13926 		/// resets to the default display string
13927 		static resetDisplayName() {
13928 			displayName = null;
13929 		}
13930 
13931 		///
13932 		static Display* get() {
13933 			if(display is null) {
13934 				if(!librariesSuccessfullyLoaded)
13935 					throw new Exception("Unable to load X11 client libraries");
13936 				display = XOpenDisplay(displayName);
13937 
13938 				isLocal_ = false;
13939 
13940 				connectionSequence_++;
13941 				if(display is null)
13942 					throw new Exception("Unable to open X display");
13943 
13944 				auto str = display.display_name;
13945 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
13946 				// and otherwise it probably isn't
13947 				if(str is null || (str[0] != ':' && str[0] != '/'))
13948 					isLocal_ = false;
13949 				else
13950 					isLocal_ = true;
13951 
13952 				//XSetErrorHandler(&adrlogger);
13953 				//XSynchronize(display, true);
13954 
13955 
13956 				XSetIOErrorHandler(&x11ioerrCB);
13957 				Bool sup;
13958 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
13959 				createXIM();
13960 				version(with_eventloop) {
13961 					import arsd.eventloop;
13962 					addFileEventListeners(display.fd, &eventListener, null, null);
13963 				}
13964 			}
13965 
13966 			return display;
13967 		}
13968 
13969 		extern(C)
13970 		static int x11ioerrCB(Display* dpy) {
13971 			throw new XDisconnectException(false);
13972 		}
13973 
13974 		version(with_eventloop) {
13975 			import arsd.eventloop;
13976 			static void eventListener(OsFileHandle fd) {
13977 				//this.mtLock();
13978 				//scope(exit) this.mtUnlock();
13979 				while(XPending(display))
13980 					doXNextEvent(display);
13981 			}
13982 		}
13983 
13984 		// close connection on program exit -- we need this to properly free all images
13985 		static ~this () {
13986 			// the gui thread must clean up after itself or else Xlib might deadlock
13987 			// using this flag on any thread destruction is the easiest way i know of
13988 			// (shared static this is run by the LAST thread to exit, which may not be
13989 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
13990 			if(thisIsGuiThread)
13991 				close();
13992 		}
13993 
13994 		///
13995 		static void close() {
13996 			if(display is null)
13997 				return;
13998 
13999 			version(with_eventloop) {
14000 				import arsd.eventloop;
14001 				removeFileEventListeners(display.fd);
14002 			}
14003 
14004 			// now remove all registered images to prevent shared memory leaks
14005 			freeImages();
14006 
14007 			// tbh I don't know why it is doing this but like if this happens to run
14008 			// from the other thread there's frequent hanging inside here.
14009 			if(thisIsGuiThread)
14010 				XCloseDisplay(display);
14011 			display = null;
14012 		}
14013 	}
14014 
14015 	mixin template NativeImageImplementation() {
14016 		XImage* handle;
14017 		ubyte* rawData;
14018 
14019 		XShmSegmentInfo shminfo;
14020 
14021 		__gshared bool xshmQueryCompleted;
14022 		__gshared bool _xshmAvailable;
14023 		public static @property bool xshmAvailable() {
14024 			if(!xshmQueryCompleted) {
14025 				int i1, i2, i3;
14026 				xshmQueryCompleted = true;
14027 
14028 				if(!XDisplayConnection.isLocal)
14029 					_xshmAvailable = false;
14030 				else
14031 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14032 			}
14033 			return _xshmAvailable;
14034 		}
14035 
14036 		bool usingXshm;
14037 	final:
14038 
14039 		private __gshared bool xshmfailed;
14040 
14041 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14042 			auto display = XDisplayConnection.get();
14043 			assert(display !is null);
14044 			auto screen = DefaultScreen(display);
14045 
14046 			// it will only use shared memory for somewhat largish images,
14047 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14048 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14049 
14050 
14051 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14052 				// the actual use still fails. For example, if the program is in a container and permission denied
14053 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14054 				//
14055 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14056 
14057 
14058 				// synchronize so preexisting buffers are clear
14059 				XSync(display, false);
14060 				xshmfailed = false;
14061 
14062 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14063 
14064 
14065 				usingXshm = true;
14066 				handle = XShmCreateImage(
14067 					display,
14068 					DefaultVisual(display, screen),
14069 					enableAlpha ? 32: 24,
14070 					ImageFormat.ZPixmap,
14071 					null,
14072 					&shminfo,
14073 					width, height);
14074 				if(handle is null)
14075 					goto abortXshm1;
14076 
14077 				if(handle.bytes_per_line != 4 * width)
14078 					goto abortXshm2;
14079 
14080 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14081 				if(shminfo.shmid < 0)
14082 					goto abortXshm3;
14083 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14084 				if(rawData == cast(ubyte*) -1)
14085 					goto abortXshm4;
14086 				shminfo.readOnly = 0;
14087 				XShmAttach(display, &shminfo);
14088 
14089 				// and now to the final error check to ensure it actually worked.
14090 				XSync(display, false);
14091 				if(xshmfailed)
14092 					goto abortXshm5;
14093 
14094 				XSetErrorHandler(oldErrorHandler);
14095 
14096 				XDisplayConnection.registerImage(this);
14097 				// if I don't flush here there's a chance the dtor will run before the
14098 				// ctor and lead to a bad value X error. While this hurts the efficiency
14099 				// it is local anyway so prolly better to keep it simple
14100 				XFlush(display);
14101 
14102 				return;
14103 
14104 				abortXshm5:
14105 					shmdt(shminfo.shmaddr);
14106 					rawData = null;
14107 
14108 				abortXshm4:
14109 					shmctl(shminfo.shmid, IPC_RMID, null);
14110 
14111 				abortXshm3:
14112 					// nothing needed, the shmget failed so there's nothing to free
14113 
14114 				abortXshm2:
14115 					XDestroyImage(handle);
14116 					handle = null;
14117 
14118 				abortXshm1:
14119 					XSetErrorHandler(oldErrorHandler);
14120 					usingXshm = false;
14121 					handle = null;
14122 
14123 					shminfo = typeof(shminfo).init;
14124 
14125 					_xshmAvailable = false; // don't try again in the future
14126 
14127 					//import std.stdio; writeln("fallingback");
14128 
14129 					goto fallback;
14130 
14131 			} else {
14132 				fallback:
14133 
14134 				if (forcexshm) throw new Exception("can't create XShm Image");
14135 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14136 				import core.stdc.stdlib : malloc;
14137 				rawData = cast(ubyte*) malloc(width * height * 4);
14138 
14139 				handle = XCreateImage(
14140 					display,
14141 					DefaultVisual(display, screen),
14142 					enableAlpha ? 32 : 24, // bpp
14143 					ImageFormat.ZPixmap,
14144 					0, // offset
14145 					rawData,
14146 					width, height,
14147 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14148 			}
14149 		}
14150 
14151 		void dispose() {
14152 			// note: this calls free(rawData) for us
14153 			if(handle) {
14154 				if (usingXshm) {
14155 					XDisplayConnection.unregisterImage(this);
14156 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14157 				}
14158 				XDestroyImage(handle);
14159 				if(usingXshm) {
14160 					shmdt(shminfo.shmaddr);
14161 					shmctl(shminfo.shmid, IPC_RMID, null);
14162 				}
14163 				handle = null;
14164 			}
14165 		}
14166 
14167 		Color getPixel(int x, int y) {
14168 			auto offset = (y * width + x) * 4;
14169 			Color c;
14170 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14171 			c.b = rawData[offset + 0];
14172 			c.g = rawData[offset + 1];
14173 			c.r = rawData[offset + 2];
14174 			if(enableAlpha)
14175 				c.unPremultiply;
14176 			return c;
14177 		}
14178 
14179 		void setPixel(int x, int y, Color c) {
14180 			if(enableAlpha)
14181 				c.premultiply();
14182 			auto offset = (y * width + x) * 4;
14183 			rawData[offset + 0] = c.b;
14184 			rawData[offset + 1] = c.g;
14185 			rawData[offset + 2] = c.r;
14186 			if(enableAlpha)
14187 				rawData[offset + 3] = c.a;
14188 		}
14189 
14190 		void convertToRgbaBytes(ubyte[] where) {
14191 			assert(where.length == this.width * this.height * 4);
14192 
14193 			// if rawData had a length....
14194 			//assert(rawData.length == where.length);
14195 			for(int idx = 0; idx < where.length; idx += 4) {
14196 				where[idx + 0] = rawData[idx + 2]; // r
14197 				where[idx + 1] = rawData[idx + 1]; // g
14198 				where[idx + 2] = rawData[idx + 0]; // b
14199 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14200 
14201 				if(enableAlpha)
14202 					unPremultiplyRgba(where[idx .. idx + 4]);
14203 			}
14204 		}
14205 
14206 		void setFromRgbaBytes(in ubyte[] where) {
14207 			assert(where.length == this.width * this.height * 4);
14208 
14209 			// if rawData had a length....
14210 			//assert(rawData.length == where.length);
14211 			for(int idx = 0; idx < where.length; idx += 4) {
14212 				rawData[idx + 2] = where[idx + 0]; // r
14213 				rawData[idx + 1] = where[idx + 1]; // g
14214 				rawData[idx + 0] = where[idx + 2]; // b
14215 				if(enableAlpha) {
14216 					rawData[idx + 3] = where[idx + 3]; // a
14217 					premultiplyBgra(rawData[idx .. idx + 4]);
14218 				}
14219 			}
14220 		}
14221 
14222 	}
14223 
14224 	mixin template NativeSimpleWindowImplementation() {
14225 		GC gc;
14226 		Window window;
14227 		Display* display;
14228 
14229 		Pixmap buffer;
14230 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14231 		XIC xic; // input context
14232 		int curHidden = 0; // counter
14233 		Cursor blankCurPtr = 0;
14234 		int cursorSequenceNumber = 0;
14235 		int warpEventCount = 0; // number of mouse movement events to eat
14236 
14237 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14238 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14239 
14240 		version(without_opengl) {} else
14241 		GLXContext glc;
14242 
14243 		private void fixFixedSize(bool forced=false) (int width, int height) {
14244 			if (forced || this.resizability == Resizability.fixedSize) {
14245 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14246 				XSizeHints sh;
14247 				static if (!forced) {
14248 					c_long spr;
14249 					XGetWMNormalHints(display, window, &sh, &spr);
14250 					sh.flags |= PMaxSize | PMinSize;
14251 				} else {
14252 					sh.flags = PMaxSize | PMinSize;
14253 				}
14254 				sh.min_width = width;
14255 				sh.min_height = height;
14256 				sh.max_width = width;
14257 				sh.max_height = height;
14258 				XSetWMNormalHints(display, window, &sh);
14259 				//XFlush(display);
14260 			}
14261 		}
14262 
14263 		ScreenPainter getPainter(bool manualInvalidations) {
14264 			return ScreenPainter(this, window, manualInvalidations);
14265 		}
14266 
14267 		void move(int x, int y) {
14268 			XMoveWindow(display, window, x, y);
14269 		}
14270 
14271 		void resize(int w, int h) {
14272 			if (w < 1) w = 1;
14273 			if (h < 1) h = 1;
14274 			XResizeWindow(display, window, w, h);
14275 
14276 			// calling this now to avoid waiting for the server to
14277 			// acknowledge the resize; draws without returning to the
14278 			// event loop will thus actually work. the server's event
14279 			// btw might overrule this and resize it again
14280 			recordX11Resize(display, this, w, h);
14281 
14282 			// FIXME: do we need to set this as the opengl context to do the glViewport change?
14283 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
14284 		}
14285 
14286 		void moveResize (int x, int y, int w, int h) {
14287 			if (w < 1) w = 1;
14288 			if (h < 1) h = 1;
14289 			XMoveResizeWindow(display, window, x, y, w, h);
14290 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
14291 		}
14292 
14293 		void hideCursor () {
14294 			if (curHidden++ == 0) {
14295 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14296 					static const(char)[1] cmbmp = 0;
14297 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14298 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14299 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14300 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14301 					XFreePixmap(display, pm);
14302 				}
14303 				XDefineCursor(display, window, blankCurPtr);
14304 			}
14305 		}
14306 
14307 		void showCursor () {
14308 			if (--curHidden == 0) XUndefineCursor(display, window);
14309 		}
14310 
14311 		void warpMouse (int x, int y) {
14312 			// here i will send dummy "ignore next mouse motion" event,
14313 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14314 			// and we don't need to report it to the user (as warping is
14315 			// used when the user needs movement deltas).
14316 			//XClientMessageEvent xclient;
14317 			XEvent e;
14318 			e.xclient.type = EventType.ClientMessage;
14319 			e.xclient.window = window;
14320 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14321 			e.xclient.format = 32;
14322 			e.xclient.data.l[0] = 0;
14323 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14324 			//{ 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]); }
14325 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14326 			// now warp pointer...
14327 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14328 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14329 			// ...and flush
14330 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14331 			XFlush(display);
14332 		}
14333 
14334 		void sendDummyEvent () {
14335 			// here i will send dummy event to ping event queue
14336 			XEvent e;
14337 			e.xclient.type = EventType.ClientMessage;
14338 			e.xclient.window = window;
14339 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14340 			e.xclient.format = 32;
14341 			e.xclient.data.l[0] = 0;
14342 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14343 			XFlush(display);
14344 		}
14345 
14346 		void setTitle(string title) {
14347 			if (title.ptr is null) title = "";
14348 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14349 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14350 			XTextProperty windowName;
14351 			windowName.value = title.ptr;
14352 			windowName.encoding = XA_UTF8; //XA_STRING;
14353 			windowName.format = 8;
14354 			windowName.nitems = cast(uint)title.length;
14355 			XSetWMName(display, window, &windowName);
14356 			char[1024] namebuf = 0;
14357 			auto maxlen = namebuf.length-1;
14358 			if (maxlen > title.length) maxlen = title.length;
14359 			namebuf[0..maxlen] = title[0..maxlen];
14360 			XStoreName(display, window, namebuf.ptr);
14361 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14362 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14363 		}
14364 
14365 		string[] getTitles() {
14366 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14367 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14368 			XTextProperty textProp;
14369 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14370 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14371 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14372 				} else
14373 					return [];
14374 			} else
14375 				return null;
14376 		}
14377 
14378 		string getTitle() {
14379 			auto titles = getTitles();
14380 			return titles.length ? titles[0] : null;
14381 		}
14382 
14383 		void setMinSize (int minwidth, int minheight) {
14384 			import core.stdc.config : c_long;
14385 			if (minwidth < 1) minwidth = 1;
14386 			if (minheight < 1) minheight = 1;
14387 			XSizeHints sh;
14388 			c_long spr;
14389 			XGetWMNormalHints(display, window, &sh, &spr);
14390 			sh.min_width = minwidth;
14391 			sh.min_height = minheight;
14392 			sh.flags |= PMinSize;
14393 			XSetWMNormalHints(display, window, &sh);
14394 			flushGui();
14395 		}
14396 
14397 		void setMaxSize (int maxwidth, int maxheight) {
14398 			import core.stdc.config : c_long;
14399 			if (maxwidth < 1) maxwidth = 1;
14400 			if (maxheight < 1) maxheight = 1;
14401 			XSizeHints sh;
14402 			c_long spr;
14403 			XGetWMNormalHints(display, window, &sh, &spr);
14404 			sh.max_width = maxwidth;
14405 			sh.max_height = maxheight;
14406 			sh.flags |= PMaxSize;
14407 			XSetWMNormalHints(display, window, &sh);
14408 			flushGui();
14409 		}
14410 
14411 		void setResizeGranularity (int granx, int grany) {
14412 			import core.stdc.config : c_long;
14413 			if (granx < 1) granx = 1;
14414 			if (grany < 1) grany = 1;
14415 			XSizeHints sh;
14416 			c_long spr;
14417 			XGetWMNormalHints(display, window, &sh, &spr);
14418 			sh.width_inc = granx;
14419 			sh.height_inc = grany;
14420 			sh.flags |= PResizeInc;
14421 			XSetWMNormalHints(display, window, &sh);
14422 			flushGui();
14423 		}
14424 
14425 		void setOpacity (uint opacity) {
14426 			arch_ulong o = opacity;
14427 			if (opacity == uint.max)
14428 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14429 			else
14430 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14431 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14432 		}
14433 
14434 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14435 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14436 			display = XDisplayConnection.get();
14437 			auto screen = DefaultScreen(display);
14438 
14439 			bool overrideRedirect = false;
14440 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14441 				overrideRedirect = true;
14442 
14443 			version(without_opengl) {}
14444 			else {
14445 				if(opengl == OpenGlOptions.yes) {
14446 					GLXFBConfig fbconf = null;
14447 					XVisualInfo* vi = null;
14448 					bool useLegacy = false;
14449 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14450 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14451 						int[23] visualAttribs = [
14452 							GLX_X_RENDERABLE , 1/*True*/,
14453 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14454 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14455 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14456 							GLX_RED_SIZE     , 8,
14457 							GLX_GREEN_SIZE   , 8,
14458 							GLX_BLUE_SIZE    , 8,
14459 							GLX_ALPHA_SIZE   , 8,
14460 							GLX_DEPTH_SIZE   , 24,
14461 							GLX_STENCIL_SIZE , 8,
14462 							GLX_DOUBLEBUFFER , 1/*True*/,
14463 							0/*None*/,
14464 						];
14465 						int fbcount;
14466 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14467 						if (fbcount == 0) {
14468 							useLegacy = true; // try to do at least something
14469 						} else {
14470 							// pick the FB config/visual with the most samples per pixel
14471 							int bestidx = -1, bestns = -1;
14472 							foreach (int fbi; 0..fbcount) {
14473 								int sb, samples;
14474 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14475 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14476 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14477 							}
14478 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14479 							fbconf = fbc[bestidx];
14480 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14481 							XFree(fbc);
14482 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14483 						}
14484 					}
14485 					if (vi is null || useLegacy) {
14486 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14487 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14488 						useLegacy = true;
14489 					}
14490 					if (vi is null) throw new Exception("no open gl visual found");
14491 
14492 					XSetWindowAttributes swa;
14493 					auto root = RootWindow(display, screen);
14494 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14495 
14496 					swa.override_redirect = overrideRedirect;
14497 
14498 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14499 						0, 0, width, height,
14500 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14501 
14502 					// now try to use `glXCreateContextAttribsARB()` if it's here
14503 					if (!useLegacy) {
14504 						// request fairly advanced context, even with stencil buffer!
14505 						int[9] contextAttribs = [
14506 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14507 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14508 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14509 							// for modern context, set "forward compatibility" flag too
14510 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14511 							0/*None*/,
14512 						];
14513 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14514 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14515 							sdpyOpenGLContextVersion = 0;
14516 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14517 						}
14518 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14519 					} else {
14520 						// fallback to old GLX call
14521 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14522 							sdpyOpenGLContextVersion = 0;
14523 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14524 						}
14525 					}
14526 					// sync to ensure any errors generated are processed
14527 					XSync(display, 0/*False*/);
14528 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14529 					if(glc is null)
14530 						throw new Exception("glc");
14531 				}
14532 			}
14533 
14534 			if(opengl == OpenGlOptions.no) {
14535 
14536 				XSetWindowAttributes swa;
14537 				swa.background_pixel = WhitePixel(display, screen);
14538 				swa.border_pixel = BlackPixel(display, screen);
14539 				swa.override_redirect = overrideRedirect;
14540 				auto root = RootWindow(display, screen);
14541 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14542 
14543 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14544 					0, 0, width, height,
14545 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14546 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14547 
14548 
14549 
14550 				/*
14551 				window = XCreateSimpleWindow(
14552 					display,
14553 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14554 					0, 0, // x, y
14555 					width, height,
14556 					1, // border width
14557 					BlackPixel(display, screen), // border
14558 					WhitePixel(display, screen)); // background
14559 				*/
14560 
14561 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14562 				bufferw = width;
14563 				bufferh = height;
14564 
14565 				gc = DefaultGC(display, screen);
14566 
14567 				// clear out the buffer to get us started...
14568 				XSetForeground(display, gc, WhitePixel(display, screen));
14569 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14570 				XSetForeground(display, gc, BlackPixel(display, screen));
14571 			}
14572 
14573 			// input context
14574 			//TODO: create this only for top-level windows, and reuse that?
14575 			if (XDisplayConnection.xim !is null) {
14576 				xic = XCreateIC(XDisplayConnection.xim,
14577 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
14578 						/*XNClientWindow*/"clientWindow".ptr, window,
14579 						/*XNFocusWindow*/"focusWindow".ptr, window,
14580 						null);
14581 				if (xic is null) {
14582 					import core.stdc.stdio : stderr, fprintf;
14583 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
14584 				}
14585 			}
14586 
14587 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14588 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14589 			// window class
14590 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14591 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14592 				XClassHint klass;
14593 				XWMHints wh;
14594 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14595 					wh.input = true;
14596 					wh.flags |= InputHint;
14597 				}
14598 				XSizeHints size;
14599 				klass.res_name = sdpyWindowClassStr;
14600 				klass.res_class = sdpyWindowClassStr;
14601 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14602 			}
14603 
14604 			setTitle(title);
14605 			SimpleWindow.nativeMapping[window] = this;
14606 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14607 
14608 			// This gives our window a close button
14609 			if (windowType != WindowTypes.eventOnly) {
14610 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14611 				int useAtoms;
14612 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14613 					useAtoms = 2;
14614 				} else {
14615 					useAtoms = 1;
14616 				}
14617 				assert(useAtoms <= atoms.length);
14618 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14619 			}
14620 
14621 			// FIXME: windowType and customizationFlags
14622 			Atom[8] wsatoms; // here, due to goto
14623 			int wmsacount = 0; // here, due to goto
14624 
14625 			try
14626 			final switch(windowType) {
14627 				case WindowTypes.normal:
14628 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14629 				break;
14630 				case WindowTypes.undecorated:
14631 					motifHideDecorations();
14632 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14633 				break;
14634 				case WindowTypes.eventOnly:
14635 					_hidden = true;
14636 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14637 					goto hiddenWindow;
14638 				//break;
14639 				case WindowTypes.nestedChild:
14640 					// handled in XCreateWindow calls
14641 				break;
14642 
14643 				case WindowTypes.dropdownMenu:
14644 					motifHideDecorations();
14645 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14646 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14647 				break;
14648 				case WindowTypes.popupMenu:
14649 					motifHideDecorations();
14650 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14651 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14652 				break;
14653 				case WindowTypes.notification:
14654 					motifHideDecorations();
14655 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14656 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14657 				break;
14658 				/+
14659 				case WindowTypes.menu:
14660 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14661 					motifHideDecorations();
14662 				break;
14663 				case WindowTypes.desktop:
14664 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14665 				break;
14666 				case WindowTypes.dock:
14667 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14668 				break;
14669 				case WindowTypes.toolbar:
14670 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14671 				break;
14672 				case WindowTypes.menu:
14673 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14674 				break;
14675 				case WindowTypes.utility:
14676 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14677 				break;
14678 				case WindowTypes.splash:
14679 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14680 				break;
14681 				case WindowTypes.dialog:
14682 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14683 				break;
14684 				case WindowTypes.tooltip:
14685 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14686 				break;
14687 				case WindowTypes.notification:
14688 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14689 				break;
14690 				case WindowTypes.combo:
14691 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14692 				break;
14693 				case WindowTypes.dnd:
14694 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14695 				break;
14696 				+/
14697 			}
14698 			catch(Exception e) {
14699 				// XInternAtom failed, prolly a WM
14700 				// that doesn't support these things
14701 			}
14702 
14703 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14704 			// the two following flags may be ignored by WM
14705 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14706 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14707 
14708 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14709 
14710 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14711 
14712 			// What would be ideal here is if they only were
14713 			// selected if there was actually an event handler
14714 			// for them...
14715 
14716 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14717 
14718 			hiddenWindow:
14719 
14720 			// set the pid property for lookup later by window managers
14721 			// a standard convenience
14722 			import core.sys.posix.unistd;
14723 			arch_ulong pid = getpid();
14724 
14725 			XChangeProperty(
14726 				display,
14727 				impl.window,
14728 				GetAtom!("_NET_WM_PID", true)(display),
14729 				XA_CARDINAL,
14730 				32 /* bits */,
14731 				0 /*PropModeReplace*/,
14732 				&pid,
14733 				1);
14734 
14735 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
14736 				if(parent is null) assert(0);
14737 				XChangeProperty(
14738 					display,
14739 					impl.window,
14740 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
14741 					XA_WINDOW,
14742 					32 /* bits */,
14743 					0 /*PropModeReplace*/,
14744 					&parent.impl.window,
14745 					1);
14746 
14747 			}
14748 
14749 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
14750 				XMapWindow(display, window);
14751 			} else {
14752 				_hidden = true;
14753 			}
14754 		}
14755 
14756 		void selectDefaultInput(bool forceIncludeMouseMotion) {
14757 			auto mask = EventMask.ExposureMask |
14758 				EventMask.KeyPressMask |
14759 				EventMask.KeyReleaseMask |
14760 				EventMask.PropertyChangeMask |
14761 				EventMask.FocusChangeMask |
14762 				EventMask.StructureNotifyMask |
14763 				EventMask.VisibilityChangeMask
14764 				| EventMask.ButtonPressMask
14765 				| EventMask.ButtonReleaseMask
14766 			;
14767 
14768 			// xshm is our shortcut for local connections
14769 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
14770 				mask |= EventMask.PointerMotionMask;
14771 			else
14772 				mask |= EventMask.ButtonMotionMask;
14773 
14774 			XSelectInput(display, window, mask);
14775 		}
14776 
14777 
14778 		void setNetWMWindowType(Atom type) {
14779 			Atom[2] atoms;
14780 
14781 			atoms[0] = type;
14782 			// generic fallback
14783 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
14784 
14785 			XChangeProperty(
14786 				display,
14787 				impl.window,
14788 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
14789 				XA_ATOM,
14790 				32 /* bits */,
14791 				0 /*PropModeReplace*/,
14792 				atoms.ptr,
14793 				cast(int) atoms.length);
14794 		}
14795 
14796 		void motifHideDecorations(bool hide = true) {
14797 			MwmHints hints;
14798 			hints.flags = MWM_HINTS_DECORATIONS;
14799 			hints.decorations = hide ? 0 : 1;
14800 
14801 			XChangeProperty(
14802 				display,
14803 				impl.window,
14804 				GetAtom!"_MOTIF_WM_HINTS"(display),
14805 				GetAtom!"_MOTIF_WM_HINTS"(display),
14806 				32 /* bits */,
14807 				0 /*PropModeReplace*/,
14808 				&hints,
14809 				hints.sizeof / 4);
14810 		}
14811 
14812 		/*k8: unused
14813 		void createOpenGlContext() {
14814 
14815 		}
14816 		*/
14817 
14818 		void closeWindow() {
14819 			// I can't close this or a child window closing will
14820 			// break events for everyone. So I'm just leaking it right
14821 			// now and that is probably perfectly fine...
14822 			version(none)
14823 			if (customEventFDRead != -1) {
14824 				import core.sys.posix.unistd : close;
14825 				auto same = customEventFDRead == customEventFDWrite;
14826 
14827 				close(customEventFDRead);
14828 				if(!same)
14829 					close(customEventFDWrite);
14830 				customEventFDRead = -1;
14831 				customEventFDWrite = -1;
14832 			}
14833 			if(buffer)
14834 				XFreePixmap(display, buffer);
14835 			bufferw = bufferh = 0;
14836 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
14837 			XDestroyWindow(display, window);
14838 			XFlush(display);
14839 		}
14840 
14841 		void dispose() {
14842 		}
14843 
14844 		bool destroyed = false;
14845 	}
14846 
14847 	bool insideXEventLoop;
14848 }
14849 
14850 version(X11) {
14851 
14852 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
14853 
14854 	private class ResizeEvent {
14855 		int width, height;
14856 	}
14857 
14858 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
14859 		if(win.pendingResizeEvent is null) {
14860 			win.pendingResizeEvent = new ResizeEvent();
14861 			win.addEventListener((ResizeEvent re) {
14862 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
14863 			});
14864 		}
14865 		win.pendingResizeEvent.width = width;
14866 		win.pendingResizeEvent.height = height;
14867 		if(!win.eventQueued!ResizeEvent) {
14868 			win.postEvent(win.pendingResizeEvent);
14869 		}
14870 	}
14871 
14872 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
14873 		if(width != win.width || height != win.height) {
14874 			win._width = width;
14875 			win._height = height;
14876 
14877 			if(win.openglMode == OpenGlOptions.no) {
14878 				// FIXME: could this be more efficient?
14879 
14880 				if (win.bufferw < width || win.bufferh < height) {
14881 					//{ 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); }
14882 					// grow the internal buffer to match the window...
14883 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
14884 					{
14885 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
14886 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
14887 						scope(exit) XFreeGC(win.display, xgc);
14888 						XSetClipMask(win.display, xgc, None);
14889 						XSetForeground(win.display, xgc, 0);
14890 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
14891 					}
14892 					XCopyArea(display,
14893 						cast(Drawable) win.buffer,
14894 						cast(Drawable) newPixmap,
14895 						win.gc, 0, 0,
14896 						win.bufferw < width ? win.bufferw : win.width,
14897 						win.bufferh < height ? win.bufferh : win.height,
14898 						0, 0);
14899 
14900 					XFreePixmap(display, win.buffer);
14901 					win.buffer = newPixmap;
14902 					win.bufferw = width;
14903 					win.bufferh = height;
14904 				}
14905 
14906 				// clear unused parts of the buffer
14907 				if (win.bufferw > width || win.bufferh > height) {
14908 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
14909 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
14910 					scope(exit) XFreeGC(win.display, xgc);
14911 					XSetClipMask(win.display, xgc, None);
14912 					XSetForeground(win.display, xgc, 0);
14913 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
14914 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
14915 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
14916 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
14917 				}
14918 
14919 			}
14920 
14921 			version(without_opengl) {} else
14922 			if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) {
14923 				glViewport(0, 0, width, height);
14924 			}
14925 
14926 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
14927 
14928 			if(win.windowResized !is null) {
14929 				XUnlockDisplay(display);
14930 				scope(exit) XLockDisplay(display);
14931 				win.windowResized(width, height);
14932 			}
14933 		}
14934 	}
14935 
14936 
14937 	/// Platform-specific, you might use it when doing a custom event loop.
14938 	bool doXNextEvent(Display* display) {
14939 		bool done;
14940 		XEvent e;
14941 		XNextEvent(display, &e);
14942 		version(sddddd) {
14943 			import std.stdio, std.conv : to;
14944 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
14945 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
14946 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
14947 			}
14948 		}
14949 
14950 		// filter out compose events
14951 		if (XFilterEvent(&e, None)) {
14952 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
14953 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
14954 			return false;
14955 		}
14956 		// process keyboard mapping changes
14957 		if (e.type == EventType.KeymapNotify) {
14958 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
14959 			XRefreshKeyboardMapping(&e.xmapping);
14960 			return false;
14961 		}
14962 
14963 		version(with_eventloop)
14964 			import arsd.eventloop;
14965 
14966 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
14967 			// see windows impl's comments
14968 			XUnlockDisplay(display);
14969 			scope(exit) XLockDisplay(display);
14970 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
14971 			if(ret == 0)
14972 				return done;
14973 		}
14974 
14975 
14976 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
14977 			if(win.getNativeEventHandler !is null) {
14978 				XUnlockDisplay(display);
14979 				scope(exit) XLockDisplay(display);
14980 				auto ret = win.getNativeEventHandler()(e);
14981 				if(ret == 0)
14982 					return done;
14983 			}
14984 		}
14985 
14986 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
14987 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
14988 				// we get this because of the RRScreenChangeNotifyMask
14989 
14990 				// this isn't actually an ideal way to do it since it wastes time
14991 				// but meh it is simple and it works.
14992 				win.actualDpiLoadAttempted = false;
14993 				SimpleWindow.xRandrInfoLoadAttemped = false;
14994 				win.updateActualDpi(); // trigger a reload
14995 			}
14996 		}
14997 
14998 		switch(e.type) {
14999 		  case EventType.SelectionClear:
15000 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15001 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15002 				//import std.stdio; writeln("SelectionClear");
15003 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15004 			}
15005 		  break;
15006 		  case EventType.SelectionRequest:
15007 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15008 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15009 				// import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15010 				XUnlockDisplay(display);
15011 				scope(exit) XLockDisplay(display);
15012 				(*ssh).handleRequest(e);
15013 			}
15014 		  break;
15015 		  case EventType.PropertyNotify:
15016 			// import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15017 
15018 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15019 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15020 					ssh.sendMoreIncr(&e.xproperty);
15021 			}
15022 
15023 
15024 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15025 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15026 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15027 					Atom target;
15028 					int format;
15029 					arch_ulong bytesafter, length;
15030 					void* value;
15031 
15032 					ubyte[] s;
15033 					Atom targetToKeep;
15034 
15035 					XGetWindowProperty(
15036 						e.xproperty.display,
15037 						e.xproperty.window,
15038 						e.xproperty.atom,
15039 						0,
15040 						100000 /* length */,
15041 						true, /* erase it to signal we got it and want more */
15042 						0 /*AnyPropertyType*/,
15043 						&target, &format, &length, &bytesafter, &value);
15044 
15045 					if(!targetToKeep)
15046 						targetToKeep = target;
15047 
15048 					auto id = (cast(ubyte*) value)[0 .. length];
15049 
15050 					handler.handleIncrData(targetToKeep, id);
15051 
15052 					XFree(value);
15053 				}
15054 			}
15055 		  break;
15056 		  case EventType.SelectionNotify:
15057 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15058 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15059 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15060 					XUnlockDisplay(display);
15061 					scope(exit) XLockDisplay(display);
15062 					handler.handleData(None, null);
15063 				} else {
15064 					Atom target;
15065 					int format;
15066 					arch_ulong bytesafter, length;
15067 					void* value;
15068 					XGetWindowProperty(
15069 						e.xselection.display,
15070 						e.xselection.requestor,
15071 						e.xselection.property,
15072 						0,
15073 						100000 /* length */,
15074 						//false, /* don't erase it */
15075 						true, /* do erase it lol */
15076 						0 /*AnyPropertyType*/,
15077 						&target, &format, &length, &bytesafter, &value);
15078 
15079 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15080 
15081 					{
15082 						XUnlockDisplay(display);
15083 						scope(exit) XLockDisplay(display);
15084 
15085 						if(target == XA_ATOM) {
15086 							// initial request, see what they are able to work with and request the best one
15087 							// we can handle, if available
15088 
15089 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15090 							Atom best = handler.findBestFormat(answer);
15091 
15092 							/+
15093 							writeln("got ", answer);
15094 							foreach(a; answer)
15095 								printf("%s\n", XGetAtomName(display, a));
15096 							writeln("best ", best);
15097 							+/
15098 
15099 							if(best != None) {
15100 								// actually request the best format
15101 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15102 							}
15103 						} else if(target == GetAtom!"INCR"(display)) {
15104 							// incremental
15105 
15106 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15107 
15108 							// signal the sending program that we see
15109 							// the incr and are ready to receive more.
15110 							XDeleteProperty(
15111 								e.xselection.display,
15112 								e.xselection.requestor,
15113 								e.xselection.property);
15114 						} else {
15115 							// unsupported type... maybe, forward
15116 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15117 						}
15118 					}
15119 					XFree(value);
15120 					/*
15121 					XDeleteProperty(
15122 						e.xselection.display,
15123 						e.xselection.requestor,
15124 						e.xselection.property);
15125 					*/
15126 				}
15127 			}
15128 		  break;
15129 		  case EventType.ConfigureNotify:
15130 			auto event = e.xconfigure;
15131 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15132 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
15133 
15134 				/+
15135 					The ICCCM says window managers must send a synthetic event when the window
15136 					is moved but NOT when it is resized. In the resize case, an event is sent
15137 					with position (0, 0) which can be wrong and break the dpi calculations.
15138 
15139 					So we only consider the synthetic events from the WM and otherwise
15140 					need to wait for some other event to get the position which... sucks.
15141 
15142 					I'd rather not have windows changing their layout on mouse motion after
15143 					switching monitors... might be forced to but for now just ignoring it.
15144 
15145 					Easiest way to switch monitors without sending a size position is by
15146 					maximize or fullscreen in a setup like mine, but on most setups those
15147 					work on the monitor it is already living on, so it should be ok most the
15148 					time.
15149 				+/
15150 				if(event.send_event) {
15151 					win.screenPositionKnown = true;
15152 					win.screenPositionX = event.x;
15153 					win.screenPositionY = event.y;
15154 					win.updateActualDpi();
15155 				}
15156 
15157 				recordX11ResizeAsync(display, *win, event.width, event.height);
15158 			}
15159 		  break;
15160 		  case EventType.Expose:
15161 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15162 				// if it is closing from a popup menu, it can get
15163 				// an Expose event right by the end and trigger a
15164 				// BadDrawable error ... we'll just check
15165 				// closed to handle that.
15166 				if((*win).closed) break;
15167 				if((*win).openglMode == OpenGlOptions.no) {
15168 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15169 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15170 					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);
15171 				} else {
15172 					// need to redraw the scene somehow
15173 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15174 						XUnlockDisplay(display);
15175 						scope(exit) XLockDisplay(display);
15176 						version(without_opengl) {} else
15177 						win.redrawOpenGlSceneSoon();
15178 					}
15179 				}
15180 			}
15181 		  break;
15182 		  case EventType.FocusIn:
15183 		  case EventType.FocusOut:
15184 
15185 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15186 				/+
15187 
15188 				void info(string detail) {
15189 					string s;
15190 					import std.conv;
15191 					import std.datetime;
15192 					s ~= to!string(Clock.currTime);
15193 					s ~= " ";
15194 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15195 					s ~= " ";
15196 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15197 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15198 					s ~= detail;
15199 					s ~= " ";
15200 
15201 					sdpyPrintDebugString(s);
15202 
15203 				}
15204 
15205 				switch(e.xfocus.detail) {
15206 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15207 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15208 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15209 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15210 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15211 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15212 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15213 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15214 					default:
15215 
15216 				}
15217 				+/
15218 
15219 
15220 				if (win.xic !is null) {
15221 					//{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); }
15222 					if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic);
15223 				}
15224 
15225 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15226 					break; // just ignore these they seem irrelevant
15227 
15228 				auto old = win._focused;
15229 				win._focused = e.type == EventType.FocusIn;
15230 
15231 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15232 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15233 					win._focused = true;
15234 
15235 				if(win.demandingAttention)
15236 					demandAttention(*win, false);
15237 
15238 				if(old != win._focused && win.onFocusChange) {
15239 					XUnlockDisplay(display);
15240 					scope(exit) XLockDisplay(display);
15241 					win.onFocusChange(win._focused);
15242 				}
15243 			}
15244 		  break;
15245 		  case EventType.VisibilityNotify:
15246 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15247 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15248 						if (win.visibilityChanged !is null) {
15249 								XUnlockDisplay(display);
15250 								scope(exit) XLockDisplay(display);
15251 								win.visibilityChanged(false);
15252 							}
15253 					} else {
15254 						if (win.visibilityChanged !is null) {
15255 							XUnlockDisplay(display);
15256 							scope(exit) XLockDisplay(display);
15257 							win.visibilityChanged(true);
15258 						}
15259 					}
15260 				}
15261 				break;
15262 		  case EventType.ClientMessage:
15263 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15264 					// "ignore next mouse motion" event, increment ignore counter for teh window
15265 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15266 						++(*win).warpEventCount;
15267 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15268 					} else {
15269 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15270 					}
15271 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15272 					// user clicked the close button on the window manager
15273 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15274 						XUnlockDisplay(display);
15275 						scope(exit) XLockDisplay(display);
15276 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15277 					}
15278 
15279 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15280 					//import std.stdio; writeln("HAPPENED");
15281 					// user clicked the close button on the window manager
15282 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15283 						XUnlockDisplay(display);
15284 						scope(exit) XLockDisplay(display);
15285 
15286 						auto setTo = *win;
15287 
15288 						if(win.setRequestedInputFocus !is null) {
15289 							auto s = win.setRequestedInputFocus();
15290 							if(s !is null)
15291 								setTo = s;
15292 						}
15293 
15294 						assert(setTo !is null);
15295 
15296 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15297 
15298 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15299 					}
15300 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15301 					foreach(nai; NotificationAreaIcon.activeIcons)
15302 						nai.newManager();
15303 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15304 
15305 					bool xDragWindow = true;
15306 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15307 						//XDefineCursor(display, xDragWindow.impl.window,
15308 							//import std.stdio; writeln("XdndStatus ", e.xclient.data.l);
15309 					}
15310 					if(auto dh = win.dropHandler) {
15311 
15312 						static Atom[3] xFormatsBuffer;
15313 						static Atom[] xFormats;
15314 
15315 						void resetXFormats() {
15316 							xFormatsBuffer[] = 0;
15317 							xFormats = xFormatsBuffer[];
15318 						}
15319 
15320 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15321 							// on Windows it is supposed to return the effect you actually do FIXME
15322 
15323 							auto sourceWindow =  e.xclient.data.l[0];
15324 
15325 							xFormatsBuffer[0] = e.xclient.data.l[2];
15326 							xFormatsBuffer[1] = e.xclient.data.l[3];
15327 							xFormatsBuffer[2] = e.xclient.data.l[4];
15328 
15329 							if(e.xclient.data.l[1] & 1) {
15330 								// can just grab it all but like we don't necessarily need them...
15331 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15332 							} else {
15333 								int len;
15334 								foreach(fmt; xFormatsBuffer)
15335 									if(fmt) len++;
15336 								xFormats = xFormatsBuffer[0 .. len];
15337 							}
15338 
15339 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15340 
15341 							dh.dragEnter(&pkg);
15342 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15343 
15344 							auto pack = e.xclient.data.l[2];
15345 
15346 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15347 
15348 
15349 							XClientMessageEvent xclient;
15350 
15351 							xclient.type = EventType.ClientMessage;
15352 							xclient.window = e.xclient.data.l[0];
15353 							xclient.message_type = GetAtom!"XdndStatus"(display);
15354 							xclient.format = 32;
15355 							xclient.data.l[0] = win.impl.window;
15356 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15357 							auto r = result.consistentWithin;
15358 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15359 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15360 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15361 
15362 							XSendEvent(
15363 								display,
15364 								e.xclient.data.l[0],
15365 								false,
15366 								EventMask.NoEventMask,
15367 								cast(XEvent*) &xclient
15368 							);
15369 
15370 
15371 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15372 							//import std.stdio; writeln("XdndLeave");
15373 							// drop cancelled.
15374 							// data.l[0] is the source window
15375 							dh.dragLeave();
15376 
15377 							resetXFormats();
15378 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15379 							// drop happening, should fetch data, then send finished
15380 							//import std.stdio; writeln("XdndDrop");
15381 
15382 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15383 
15384 							dh.drop(&pkg);
15385 
15386 							resetXFormats();
15387 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15388 							// import std.stdio; writeln("XdndFinished");
15389 
15390 							dh.finish();
15391 						}
15392 
15393 					}
15394 				}
15395 		  break;
15396 		  case EventType.MapNotify:
15397 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15398 					(*win)._visible = true;
15399 					if (!(*win)._visibleForTheFirstTimeCalled) {
15400 						(*win)._visibleForTheFirstTimeCalled = true;
15401 						if ((*win).visibleForTheFirstTime !is null) {
15402 							XUnlockDisplay(display);
15403 							scope(exit) XLockDisplay(display);
15404 							version(without_opengl) {} else {
15405 								if((*win).openglMode == OpenGlOptions.yes) {
15406 									(*win).setAsCurrentOpenGlContextNT();
15407 									glViewport(0, 0, (*win).width, (*win).height);
15408 								}
15409 							}
15410 							(*win).visibleForTheFirstTime();
15411 						}
15412 					}
15413 					if ((*win).visibilityChanged !is null) {
15414 						XUnlockDisplay(display);
15415 						scope(exit) XLockDisplay(display);
15416 						(*win).visibilityChanged(true);
15417 					}
15418 				}
15419 		  break;
15420 		  case EventType.UnmapNotify:
15421 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15422 					win._visible = false;
15423 					if (win.visibilityChanged !is null) {
15424 						XUnlockDisplay(display);
15425 						scope(exit) XLockDisplay(display);
15426 						win.visibilityChanged(false);
15427 					}
15428 			}
15429 		  break;
15430 		  case EventType.DestroyNotify:
15431 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15432 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15433 				win._closed = true; // just in case
15434 				win.destroyed = true;
15435 				if (win.xic !is null) {
15436 					XDestroyIC(win.xic);
15437 					win.xic = null; // just in calse
15438 				}
15439 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15440 				bool anyImportant = false;
15441 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15442 					if(w.beingOpenKeepsAppOpen) {
15443 						anyImportant = true;
15444 						break;
15445 					}
15446 				if(!anyImportant) {
15447 					EventLoop.quitApplication();
15448 					done = true;
15449 				}
15450 			}
15451 			auto window = e.xdestroywindow.window;
15452 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15453 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15454 
15455 			version(with_eventloop) {
15456 				if(done) exit();
15457 			}
15458 		  break;
15459 
15460 		  case EventType.MotionNotify:
15461 			MouseEvent mouse;
15462 			auto event = e.xmotion;
15463 
15464 			mouse.type = MouseEventType.motion;
15465 			mouse.x = event.x;
15466 			mouse.y = event.y;
15467 			mouse.modifierState = event.state;
15468 
15469 			mouse.timestamp = event.time;
15470 
15471 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15472 				mouse.window = *win;
15473 				if (win.warpEventCount > 0) {
15474 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15475 					--(*win).warpEventCount;
15476 					(*win).mdx(mouse); // so deltas will be correctly updated
15477 				} else {
15478 					win.warpEventCount = 0; // just in case
15479 					(*win).mdx(mouse);
15480 					if((*win).handleMouseEvent) {
15481 						XUnlockDisplay(display);
15482 						scope(exit) XLockDisplay(display);
15483 						(*win).handleMouseEvent(mouse);
15484 					}
15485 				}
15486 			}
15487 
15488 		  	version(with_eventloop)
15489 				send(mouse);
15490 		  break;
15491 		  case EventType.ButtonPress:
15492 		  case EventType.ButtonRelease:
15493 			MouseEvent mouse;
15494 			auto event = e.xbutton;
15495 
15496 			mouse.timestamp = event.time;
15497 
15498 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15499 			mouse.x = event.x;
15500 			mouse.y = event.y;
15501 
15502 			static Time lastMouseDownTime = 0;
15503 
15504 			mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15505 			if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time;
15506 
15507 			switch(event.button) {
15508 				case 1: mouse.button = MouseButton.left; break; // left
15509 				case 2: mouse.button = MouseButton.middle; break; // middle
15510 				case 3: mouse.button = MouseButton.right; break; // right
15511 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15512 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15513 				case 6: break; // idk
15514 				case 7: break; // idk
15515 				case 8: mouse.button = MouseButton.backButton; break;
15516 				case 9: mouse.button = MouseButton.forwardButton; break;
15517 				default:
15518 			}
15519 
15520 			// FIXME: double check this
15521 			mouse.modifierState = event.state;
15522 
15523 			//mouse.modifierState = event.detail;
15524 
15525 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15526 				mouse.window = *win;
15527 				(*win).mdx(mouse);
15528 				if((*win).handleMouseEvent) {
15529 					XUnlockDisplay(display);
15530 					scope(exit) XLockDisplay(display);
15531 					(*win).handleMouseEvent(mouse);
15532 				}
15533 			}
15534 			version(with_eventloop)
15535 				send(mouse);
15536 		  break;
15537 
15538 		  case EventType.KeyPress:
15539 		  case EventType.KeyRelease:
15540 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15541 			KeyEvent ke;
15542 			ke.pressed = e.type == EventType.KeyPress;
15543 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15544 
15545 			auto sym = XKeycodeToKeysym(
15546 				XDisplayConnection.get(),
15547 				e.xkey.keycode,
15548 				0);
15549 
15550 			ke.key = cast(Key) sym;//e.xkey.keycode;
15551 
15552 			ke.modifierState = e.xkey.state;
15553 
15554 			// import std.stdio; writefln("%x", sym);
15555 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15556 			int charbuflen = 0; // return value of XwcLookupString
15557 			if (ke.pressed) {
15558 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15559 				if (win !is null && win.xic !is null) {
15560 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15561 					Status status;
15562 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15563 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15564 				} else {
15565 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15566 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15567 					char[16] buffer;
15568 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15569 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15570 				}
15571 			}
15572 
15573 			// if there's no char, subst one
15574 			if (charbuflen == 0) {
15575 				switch (sym) {
15576 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15577 					case 0xff8d: // keypad enter
15578 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15579 					default : // ignore
15580 				}
15581 			}
15582 
15583 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15584 				ke.window = *win;
15585 
15586 
15587 				if(win.inputProxy)
15588 					win = &win.inputProxy;
15589 
15590 				// char events are separate since they are on Windows too
15591 				// also, xcompose can generate long char sequences
15592 				// don't send char events if Meta and/or Hyper is pressed
15593 				// TODO: ctrl+char should only send control chars; not yet
15594 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15595 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15596 				}
15597 
15598 				dchar[32] charsComingBuffer;
15599 				int charsComingPosition;
15600 				dchar[] charsComing = charsComingBuffer[];
15601 
15602 				if (ke.pressed && charbuflen > 0) {
15603 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15604 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15605 						if(charsComingPosition >= charsComing.length)
15606 							charsComing.length = charsComingPosition + 8;
15607 
15608 						charsComing[charsComingPosition++] = ch;
15609 					}
15610 
15611 					charsComing = charsComing[0 .. charsComingPosition];
15612 				} else {
15613 					charsComing = null;
15614 				}
15615 
15616 				ke.charsPossible = charsComing;
15617 
15618 				if (win.handleKeyEvent) {
15619 					XUnlockDisplay(display);
15620 					scope(exit) XLockDisplay(display);
15621 					win.handleKeyEvent(ke);
15622 				}
15623 
15624 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15625 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15626 					XUnlockDisplay(display);
15627 					scope(exit) XLockDisplay(display);
15628 					foreach(ch; charsComing)
15629 						win.handleCharEvent(ch);
15630 				}
15631 			}
15632 
15633 			version(with_eventloop)
15634 				send(ke);
15635 		  break;
15636 		  default:
15637 		}
15638 
15639 		return done;
15640 	}
15641 }
15642 
15643 /* *************************************** */
15644 /*      Done with simpledisplay stuff      */
15645 /* *************************************** */
15646 
15647 // Necessary C library bindings follow
15648 version(Windows) {} else
15649 version(X11) {
15650 
15651 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15652 
15653 // X11 bindings needed here
15654 /*
15655 	A little of this is from the bindings project on
15656 	D Source and some of it is copy/paste from the C
15657 	header.
15658 
15659 	The DSource listing consistently used D's long
15660 	where C used long. That's wrong - C long is 32 bit, so
15661 	it should be int in D. I changed that here.
15662 
15663 	Note:
15664 	This isn't complete, just took what I needed for myself.
15665 */
15666 
15667 import core.stdc.stddef : wchar_t;
15668 
15669 interface XLib {
15670 extern(C) nothrow @nogc {
15671 	char* XResourceManagerString(Display*);
15672 	void XrmInitialize();
15673 	XrmDatabase XrmGetStringDatabase(char* data);
15674 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15675 
15676 	Cursor XCreateFontCursor(Display*, uint shape);
15677 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15678 	int XUndefineCursor(Display* display, Window w);
15679 
15680 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15681 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15682 	int XFreeCursor(Display* display, Cursor cursor);
15683 
15684 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
15685 
15686 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
15687 
15688 	char *XKeysymToString(KeySym keysym);
15689 	KeySym XKeycodeToKeysym(
15690 		Display*		/* display */,
15691 		KeyCode		/* keycode */,
15692 		int			/* index */
15693 	);
15694 
15695 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
15696 
15697 	int XFree(void*);
15698 	int XDeleteProperty(Display *display, Window w, Atom property);
15699 
15700 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
15701 
15702 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
15703 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
15704 		*actual_type_return, int *actual_format_return, arch_ulong
15705 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
15706 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
15707 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
15708 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
15709 
15710 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
15711 
15712 	Window XGetSelectionOwner(Display *display, Atom selection);
15713 
15714 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
15715 
15716 	char** XListFonts(Display*, const char*, int, int*);
15717 	void XFreeFontNames(char**);
15718 
15719 	Display* XOpenDisplay(const char*);
15720 	int XCloseDisplay(Display*);
15721 
15722 	int XSynchronize(Display*, bool);
15723 
15724 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
15725 
15726 	Bool XSupportsLocale();
15727 	char* XSetLocaleModifiers(const(char)* modifier_list);
15728 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15729 	Status XCloseOM(XOM om);
15730 
15731 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15732 	Status XCloseIM(XIM im);
15733 
15734 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15735 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15736 	Display* XDisplayOfIM(XIM im);
15737 	char* XLocaleOfIM(XIM im);
15738 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
15739 	void XDestroyIC(XIC ic);
15740 	void XSetICFocus(XIC ic);
15741 	void XUnsetICFocus(XIC ic);
15742 	//wchar_t* XwcResetIC(XIC ic);
15743 	char* XmbResetIC(XIC ic);
15744 	char* Xutf8ResetIC(XIC ic);
15745 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15746 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15747 	XIM XIMOfIC(XIC ic);
15748 
15749 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
15750 
15751 
15752 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
15753 	int XFreeFont(Display *display, XFontStruct *font_struct);
15754 	int XSetFont(Display* display, GC gc, Font font);
15755 	int XTextWidth(XFontStruct*, in char*, int);
15756 
15757 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
15758 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
15759 
15760 	Window XCreateSimpleWindow(
15761 		Display*	/* display */,
15762 		Window		/* parent */,
15763 		int			/* x */,
15764 		int			/* y */,
15765 		uint		/* width */,
15766 		uint		/* height */,
15767 		uint		/* border_width */,
15768 		uint		/* border */,
15769 		uint		/* background */
15770 	);
15771 	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);
15772 
15773 	int XReparentWindow(Display*, Window, Window, int, int);
15774 	int XClearWindow(Display*, Window);
15775 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
15776 	int XMoveWindow(Display*, Window, int, int);
15777 	int XResizeWindow(Display *display, Window w, uint width, uint height);
15778 
15779 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
15780 
15781 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
15782 
15783 	XImage *XCreateImage(
15784 		Display*		/* display */,
15785 		Visual*		/* visual */,
15786 		uint	/* depth */,
15787 		int			/* format */,
15788 		int			/* offset */,
15789 		ubyte*		/* data */,
15790 		uint	/* width */,
15791 		uint	/* height */,
15792 		int			/* bitmap_pad */,
15793 		int			/* bytes_per_line */
15794 	);
15795 
15796 	Status XInitImage (XImage* image);
15797 
15798 	Atom XInternAtom(
15799 		Display*		/* display */,
15800 		const char*	/* atom_name */,
15801 		Bool		/* only_if_exists */
15802 	);
15803 
15804 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
15805 	char* XGetAtomName(Display*, Atom);
15806 	Status XGetAtomNames(Display*, Atom*, int count, char**);
15807 
15808 	int XPutImage(
15809 		Display*	/* display */,
15810 		Drawable	/* d */,
15811 		GC			/* gc */,
15812 		XImage*	/* image */,
15813 		int			/* src_x */,
15814 		int			/* src_y */,
15815 		int			/* dest_x */,
15816 		int			/* dest_y */,
15817 		uint		/* width */,
15818 		uint		/* height */
15819 	);
15820 
15821 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
15822 
15823 
15824 	int XDestroyWindow(
15825 		Display*	/* display */,
15826 		Window		/* w */
15827 	);
15828 
15829 	int XDestroyImage(XImage*);
15830 
15831 	int XSelectInput(
15832 		Display*	/* display */,
15833 		Window		/* w */,
15834 		EventMask	/* event_mask */
15835 	);
15836 
15837 	int XMapWindow(
15838 		Display*	/* display */,
15839 		Window		/* w */
15840 	);
15841 
15842 	Status XIconifyWindow(Display*, Window, int);
15843 	int XMapRaised(Display*, Window);
15844 	int XMapSubwindows(Display*, Window);
15845 
15846 	int XNextEvent(
15847 		Display*	/* display */,
15848 		XEvent*		/* event_return */
15849 	);
15850 
15851 	int XMaskEvent(Display*, arch_long, XEvent*);
15852 
15853 	Bool XFilterEvent(XEvent *event, Window window);
15854 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
15855 
15856 	Status XSetWMProtocols(
15857 		Display*	/* display */,
15858 		Window		/* w */,
15859 		Atom*		/* protocols */,
15860 		int			/* count */
15861 	);
15862 
15863 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
15864 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
15865 
15866 
15867 	Status XInitThreads();
15868 	void XLockDisplay (Display* display);
15869 	void XUnlockDisplay (Display* display);
15870 
15871 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
15872 
15873 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
15874 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
15875 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
15876 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
15877 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
15878 
15879 
15880 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
15881 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
15882 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
15883 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
15884 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
15885 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
15886 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
15887 	int XDrawPoint(Display*, Drawable, GC, int, int);
15888 	int XSetForeground(Display*, GC, uint);
15889 	int XSetBackground(Display*, GC, uint);
15890 
15891 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
15892 	void XFreeFontSet(Display*, XFontSet);
15893 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
15894 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
15895 
15896 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
15897 	 	
15898 
15899 //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);
15900 
15901 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
15902 	int XSetFunction(Display*, GC, int);
15903 
15904 	GC XCreateGC(Display*, Drawable, uint, void*);
15905 	int XCopyGC(Display*, GC, uint, GC);
15906 	int XFreeGC(Display*, GC);
15907 
15908 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
15909 	bool XCheckMaskEvent(Display*, int, XEvent*);
15910 
15911 	int XPending(Display*);
15912 	int XEventsQueued(Display* display, int mode);
15913 
15914 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
15915 	int XFreePixmap(Display*, Pixmap);
15916 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
15917 	int XFlush(Display*);
15918 	int XBell(Display*, int);
15919 	int XSync(Display*, bool);
15920 
15921 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
15922 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
15923 
15924 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
15925 	int XUngrabKeyboard(Display*, Time);
15926 
15927 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
15928 
15929 	KeySym XStringToKeysym(const char *string);
15930 
15931 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
15932 
15933 	Window XDefaultRootWindow(Display*);
15934 
15935 	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);
15936 
15937 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 
15938 
15939 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
15940 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
15941 
15942 	Status XAllocColor(Display*, Colormap, XColor*);
15943 
15944 	int XWithdrawWindow(Display*, Window, int);
15945 	int XUnmapWindow(Display*, Window);
15946 	int XLowerWindow(Display*, Window);
15947 	int XRaiseWindow(Display*, Window);
15948 
15949 	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);
15950 	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);
15951 
15952 	int XGetInputFocus(Display*, Window*, int*);
15953 	int XSetInputFocus(Display*, Window, int, Time);
15954 
15955 	XErrorHandler XSetErrorHandler(XErrorHandler);
15956 
15957 	int XGetErrorText(Display*, int, char*, int);
15958 
15959 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
15960 
15961 
15962 	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);
15963 	int XUngrabPointer(Display *display, Time time);
15964 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
15965 
15966 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
15967 
15968 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
15969 	int XSetClipMask(Display*, GC, Pixmap);
15970 	int XSetClipOrigin(Display*, GC, int, int);
15971 
15972 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
15973 
15974 	void XSetWMName(Display*, Window, XTextProperty*);
15975 	Status XGetWMName(Display*, Window, XTextProperty*);
15976 	int XStoreName(Display* display, Window w, const(char)* window_name);
15977 
15978 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
15979 
15980 }
15981 }
15982 
15983 interface Xext {
15984 extern(C) nothrow @nogc {
15985 	Status XShmAttach(Display*, XShmSegmentInfo*);
15986 	Status XShmDetach(Display*, XShmSegmentInfo*);
15987 	Status XShmPutImage(
15988 		Display*            /* dpy */,
15989 		Drawable            /* d */,
15990 		GC                  /* gc */,
15991 		XImage*             /* image */,
15992 		int                 /* src_x */,
15993 		int                 /* src_y */,
15994 		int                 /* dst_x */,
15995 		int                 /* dst_y */,
15996 		uint        /* src_width */,
15997 		uint        /* src_height */,
15998 		Bool                /* send_event */
15999 	);
16000 
16001 	Status XShmQueryExtension(Display*);
16002 
16003 	XImage *XShmCreateImage(
16004 		Display*            /* dpy */,
16005 		Visual*             /* visual */,
16006 		uint        /* depth */,
16007 		int                 /* format */,
16008 		char*               /* data */,
16009 		XShmSegmentInfo*    /* shminfo */,
16010 		uint        /* width */,
16011 		uint        /* height */
16012 	);
16013 
16014 	Pixmap XShmCreatePixmap(
16015 		Display*            /* dpy */,
16016 		Drawable            /* d */,
16017 		char*               /* data */,
16018 		XShmSegmentInfo*    /* shminfo */,
16019 		uint        /* width */,
16020 		uint        /* height */,
16021 		uint        /* depth */
16022 	);
16023 
16024 }
16025 }
16026 
16027 	// this requires -lXpm
16028 	//int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16029 
16030 
16031 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16032 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16033 shared static this() {
16034 	xlib.loadDynamicLibrary();
16035 	xext.loadDynamicLibrary();
16036 }
16037 
16038 
16039 extern(C) nothrow @nogc {
16040 
16041 alias XrmDatabase = void*;
16042 struct XrmValue {
16043 	uint size;
16044 	void* addr;
16045 }
16046 
16047 struct XVisualInfo {
16048 	Visual* visual;
16049 	VisualID visualid;
16050 	int screen;
16051 	uint depth;
16052 	int c_class;
16053 	c_ulong red_mask;
16054 	c_ulong green_mask;
16055 	c_ulong blue_mask;
16056 	int colormap_size;
16057 	int bits_per_rgb;
16058 }
16059 
16060 enum VisualNoMask=	0x0;
16061 enum VisualIDMask=	0x1;
16062 enum VisualScreenMask=0x2;
16063 enum VisualDepthMask=	0x4;
16064 enum VisualClassMask=	0x8;
16065 enum VisualRedMaskMask=0x10;
16066 enum VisualGreenMaskMask=0x20;
16067 enum VisualBlueMaskMask=0x40;
16068 enum VisualColormapSizeMask=0x80;
16069 enum VisualBitsPerRGBMask=0x100;
16070 enum VisualAllMask=	0x1FF;
16071 
16072 enum AnyKey = 0;
16073 enum AnyModifier = 1 << 15;
16074 
16075 // XIM and other crap
16076 struct _XOM {}
16077 struct _XIM {}
16078 struct _XIC {}
16079 alias XOM = _XOM*;
16080 alias XIM = _XIM*;
16081 alias XIC = _XIC*;
16082 
16083 alias XIMStyle = arch_ulong;
16084 enum : arch_ulong {
16085 	XIMPreeditArea      = 0x0001,
16086 	XIMPreeditCallbacks = 0x0002,
16087 	XIMPreeditPosition  = 0x0004,
16088 	XIMPreeditNothing   = 0x0008,
16089 	XIMPreeditNone      = 0x0010,
16090 	XIMStatusArea       = 0x0100,
16091 	XIMStatusCallbacks  = 0x0200,
16092 	XIMStatusNothing    = 0x0400,
16093 	XIMStatusNone       = 0x0800,
16094 }
16095 
16096 
16097 /* X Shared Memory Extension functions */
16098 	//pragma(lib, "Xshm");
16099 	alias arch_ulong ShmSeg;
16100 	struct XShmSegmentInfo {
16101 		ShmSeg shmseg;
16102 		int shmid;
16103 		ubyte* shmaddr;
16104 		Bool readOnly;
16105 	}
16106 
16107 	// and the necessary OS functions
16108 	int shmget(int, size_t, int);
16109 	void* shmat(int, in void*, int);
16110 	int shmdt(in void*);
16111 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16112 
16113 	enum IPC_PRIVATE = 0;
16114 	enum IPC_CREAT = 512;
16115 	enum IPC_RMID = 0;
16116 
16117 /* MIT-SHM end */
16118 
16119 
16120 enum MappingType:int {
16121 	MappingModifier		=0,
16122 	MappingKeyboard		=1,
16123 	MappingPointer		=2
16124 }
16125 
16126 /* ImageFormat -- PutImage, GetImage */
16127 enum ImageFormat:int {
16128 	XYBitmap	=0,	/* depth 1, XYFormat */
16129 	XYPixmap	=1,	/* depth == drawable depth */
16130 	ZPixmap	=2	/* depth == drawable depth */
16131 }
16132 
16133 enum ModifierName:int {
16134 	ShiftMapIndex	=0,
16135 	LockMapIndex	=1,
16136 	ControlMapIndex	=2,
16137 	Mod1MapIndex	=3,
16138 	Mod2MapIndex	=4,
16139 	Mod3MapIndex	=5,
16140 	Mod4MapIndex	=6,
16141 	Mod5MapIndex	=7
16142 }
16143 
16144 enum ButtonMask:int {
16145 	Button1Mask	=1<<8,
16146 	Button2Mask	=1<<9,
16147 	Button3Mask	=1<<10,
16148 	Button4Mask	=1<<11,
16149 	Button5Mask	=1<<12,
16150 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16151 }
16152 
16153 enum KeyOrButtonMask:uint {
16154 	ShiftMask	=1<<0,
16155 	LockMask	=1<<1,
16156 	ControlMask	=1<<2,
16157 	Mod1Mask	=1<<3,
16158 	Mod2Mask	=1<<4,
16159 	Mod3Mask	=1<<5,
16160 	Mod4Mask	=1<<6,
16161 	Mod5Mask	=1<<7,
16162 	Button1Mask	=1<<8,
16163 	Button2Mask	=1<<9,
16164 	Button3Mask	=1<<10,
16165 	Button4Mask	=1<<11,
16166 	Button5Mask	=1<<12,
16167 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16168 }
16169 
16170 enum ButtonName:int {
16171 	Button1	=1,
16172 	Button2	=2,
16173 	Button3	=3,
16174 	Button4	=4,
16175 	Button5	=5
16176 }
16177 
16178 /* Notify modes */
16179 enum NotifyModes:int
16180 {
16181 	NotifyNormal		=0,
16182 	NotifyGrab			=1,
16183 	NotifyUngrab		=2,
16184 	NotifyWhileGrabbed	=3
16185 }
16186 enum NotifyHint = 1;	/* for MotionNotify events */
16187 
16188 /* Notify detail */
16189 enum NotifyDetail:int
16190 {
16191 	NotifyAncestor			=0,
16192 	NotifyVirtual			=1,
16193 	NotifyInferior			=2,
16194 	NotifyNonlinear			=3,
16195 	NotifyNonlinearVirtual	=4,
16196 	NotifyPointer			=5,
16197 	NotifyPointerRoot		=6,
16198 	NotifyDetailNone		=7
16199 }
16200 
16201 /* Visibility notify */
16202 
16203 enum VisibilityNotify:int
16204 {
16205 VisibilityUnobscured		=0,
16206 VisibilityPartiallyObscured	=1,
16207 VisibilityFullyObscured		=2
16208 }
16209 
16210 
16211 enum WindowStackingMethod:int
16212 {
16213 	Above		=0,
16214 	Below		=1,
16215 	TopIf		=2,
16216 	BottomIf	=3,
16217 	Opposite	=4
16218 }
16219 
16220 /* Circulation request */
16221 enum CirculationRequest:int
16222 {
16223 	PlaceOnTop		=0,
16224 	PlaceOnBottom	=1
16225 }
16226 
16227 enum PropertyNotification:int
16228 {
16229 	PropertyNewValue	=0,
16230 	PropertyDelete		=1
16231 }
16232 
16233 enum ColorMapNotification:int
16234 {
16235 	ColormapUninstalled	=0,
16236 	ColormapInstalled		=1
16237 }
16238 
16239 
16240 	struct _XPrivate {}
16241 	struct _XrmHashBucketRec {}
16242 
16243 	alias void* XPointer;
16244 	alias void* XExtData;
16245 
16246 	version( X86_64 ) {
16247 		alias ulong XID;
16248 		alias ulong arch_ulong;
16249 		alias long arch_long;
16250 	} else version (AArch64) {
16251 		alias ulong XID;
16252 		alias ulong arch_ulong;
16253 		alias long arch_long;
16254 	} else {
16255 		alias uint XID;
16256 		alias uint arch_ulong;
16257 		alias int arch_long;
16258 	}
16259 
16260 	alias XID Window;
16261 	alias XID Drawable;
16262 	alias XID Pixmap;
16263 
16264 	alias arch_ulong Atom;
16265 	alias int Bool;
16266 	alias Display XDisplay;
16267 
16268 	alias int ByteOrder;
16269 	alias arch_ulong Time;
16270 	alias void ScreenFormat;
16271 
16272 	struct XImage {
16273 		int width, height;			/* size of image */
16274 		int xoffset;				/* number of pixels offset in X direction */
16275 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16276 		void *data;					/* pointer to image data */
16277 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16278 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16279 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16280 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16281 		int depth;					/* depth of image */
16282 		int bytes_per_line;			/* accelarator to next line */
16283 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16284 		arch_ulong red_mask;	/* bits in z arrangment */
16285 		arch_ulong green_mask;
16286 		arch_ulong blue_mask;
16287 		XPointer obdata;			/* hook for the object routines to hang on */
16288 		static struct F {				/* image manipulation routines */
16289 			XImage* function(
16290 				XDisplay* 			/* display */,
16291 				Visual*				/* visual */,
16292 				uint				/* depth */,
16293 				int					/* format */,
16294 				int					/* offset */,
16295 				ubyte*				/* data */,
16296 				uint				/* width */,
16297 				uint				/* height */,
16298 				int					/* bitmap_pad */,
16299 				int					/* bytes_per_line */) create_image;
16300 			int function(XImage *) destroy_image;
16301 			arch_ulong function(XImage *, int, int) get_pixel;
16302 			int function(XImage *, int, int, arch_ulong) put_pixel;
16303 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16304 			int function(XImage *, arch_long) add_pixel;
16305 		}
16306 		F f;
16307 	}
16308 	version(X86_64) static assert(XImage.sizeof == 136);
16309 	else version(X86) static assert(XImage.sizeof == 88);
16310 
16311 struct XCharStruct {
16312 	short       lbearing;       /* origin to left edge of raster */
16313 	short       rbearing;       /* origin to right edge of raster */
16314 	short       width;          /* advance to next char's origin */
16315 	short       ascent;         /* baseline to top edge of raster */
16316 	short       descent;        /* baseline to bottom edge of raster */
16317 	ushort attributes;  /* per char flags (not predefined) */
16318 }
16319 
16320 /*
16321  * To allow arbitrary information with fonts, there are additional properties
16322  * returned.
16323  */
16324 struct XFontProp {
16325 	Atom name;
16326 	arch_ulong card32;
16327 }
16328 
16329 alias Atom Font;
16330 
16331 struct XFontStruct {
16332 	XExtData *ext_data;           /* Hook for extension to hang data */
16333 	Font fid;                     /* Font ID for this font */
16334 	uint direction;           /* Direction the font is painted */
16335 	uint min_char_or_byte2;   /* First character */
16336 	uint max_char_or_byte2;   /* Last character */
16337 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16338 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16339 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16340 	uint default_char;        /* Char to print for undefined character */
16341 	int n_properties;             /* How many properties there are */
16342 	XFontProp *properties;        /* Pointer to array of additional properties*/
16343 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16344 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16345 	XCharStruct *per_char;        /* first_char to last_char information */
16346 	int ascent;                   /* Max extent above baseline for spacing */
16347 	int descent;                  /* Max descent below baseline for spacing */
16348 }
16349 
16350 
16351 /*
16352  * Definitions of specific events.
16353  */
16354 struct XKeyEvent
16355 {
16356 	int type;			/* of event */
16357 	arch_ulong serial;		/* # of last request processed by server */
16358 	Bool send_event;	/* true if this came from a SendEvent request */
16359 	Display *display;	/* Display the event was read from */
16360 	Window window;	        /* "event" window it is reported relative to */
16361 	Window root;	        /* root window that the event occurred on */
16362 	Window subwindow;	/* child window */
16363 	Time time;		/* milliseconds */
16364 	int x, y;		/* pointer x, y coordinates in event window */
16365 	int x_root, y_root;	/* coordinates relative to root */
16366 	KeyOrButtonMask state;	/* key or button mask */
16367 	uint keycode;	/* detail */
16368 	Bool same_screen;	/* same screen flag */
16369 }
16370 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16371 alias XKeyEvent XKeyPressedEvent;
16372 alias XKeyEvent XKeyReleasedEvent;
16373 
16374 struct XButtonEvent
16375 {
16376 	int type;		/* of event */
16377 	arch_ulong serial;	/* # of last request processed by server */
16378 	Bool send_event;	/* true if this came from a SendEvent request */
16379 	Display *display;	/* Display the event was read from */
16380 	Window window;	        /* "event" window it is reported relative to */
16381 	Window root;	        /* root window that the event occurred on */
16382 	Window subwindow;	/* child window */
16383 	Time time;		/* milliseconds */
16384 	int x, y;		/* pointer x, y coordinates in event window */
16385 	int x_root, y_root;	/* coordinates relative to root */
16386 	KeyOrButtonMask state;	/* key or button mask */
16387 	uint button;	/* detail */
16388 	Bool same_screen;	/* same screen flag */
16389 }
16390 alias XButtonEvent XButtonPressedEvent;
16391 alias XButtonEvent XButtonReleasedEvent;
16392 
16393 struct XMotionEvent{
16394 	int type;		/* of event */
16395 	arch_ulong serial;	/* # of last request processed by server */
16396 	Bool send_event;	/* true if this came from a SendEvent request */
16397 	Display *display;	/* Display the event was read from */
16398 	Window window;	        /* "event" window reported relative to */
16399 	Window root;	        /* root window that the event occurred on */
16400 	Window subwindow;	/* child window */
16401 	Time time;		/* milliseconds */
16402 	int x, y;		/* pointer x, y coordinates in event window */
16403 	int x_root, y_root;	/* coordinates relative to root */
16404 	KeyOrButtonMask state;	/* key or button mask */
16405 	byte is_hint;		/* detail */
16406 	Bool same_screen;	/* same screen flag */
16407 }
16408 alias XMotionEvent XPointerMovedEvent;
16409 
16410 struct XCrossingEvent{
16411 	int type;		/* of event */
16412 	arch_ulong serial;	/* # of last request processed by server */
16413 	Bool send_event;	/* true if this came from a SendEvent request */
16414 	Display *display;	/* Display the event was read from */
16415 	Window window;	        /* "event" window reported relative to */
16416 	Window root;	        /* root window that the event occurred on */
16417 	Window subwindow;	/* child window */
16418 	Time time;		/* milliseconds */
16419 	int x, y;		/* pointer x, y coordinates in event window */
16420 	int x_root, y_root;	/* coordinates relative to root */
16421 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16422 	NotifyDetail detail;
16423 	/*
16424 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16425 	 * NotifyNonlinear,NotifyNonlinearVirtual
16426 	 */
16427 	Bool same_screen;	/* same screen flag */
16428 	Bool focus;		/* Boolean focus */
16429 	KeyOrButtonMask state;	/* key or button mask */
16430 }
16431 alias XCrossingEvent XEnterWindowEvent;
16432 alias XCrossingEvent XLeaveWindowEvent;
16433 
16434 struct XFocusChangeEvent{
16435 	int type;		/* FocusIn or FocusOut */
16436 	arch_ulong serial;	/* # of last request processed by server */
16437 	Bool send_event;	/* true if this came from a SendEvent request */
16438 	Display *display;	/* Display the event was read from */
16439 	Window window;		/* window of event */
16440 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16441 				   NotifyGrab, NotifyUngrab */
16442 	NotifyDetail detail;
16443 	/*
16444 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16445 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16446 	 * NotifyPointerRoot, NotifyDetailNone
16447 	 */
16448 }
16449 alias XFocusChangeEvent XFocusInEvent;
16450 alias XFocusChangeEvent XFocusOutEvent;
16451 
16452 enum CWBackPixmap              = (1L<<0);
16453 enum CWBackPixel               = (1L<<1);
16454 enum CWBorderPixmap            = (1L<<2);
16455 enum CWBorderPixel             = (1L<<3);
16456 enum CWBitGravity              = (1L<<4);
16457 enum CWWinGravity              = (1L<<5);
16458 enum CWBackingStore            = (1L<<6);
16459 enum CWBackingPlanes           = (1L<<7);
16460 enum CWBackingPixel            = (1L<<8);
16461 enum CWOverrideRedirect        = (1L<<9);
16462 enum CWSaveUnder               = (1L<<10);
16463 enum CWEventMask               = (1L<<11);
16464 enum CWDontPropagate           = (1L<<12);
16465 enum CWColormap                = (1L<<13);
16466 enum CWCursor                  = (1L<<14);
16467 
16468 struct XWindowAttributes {
16469 	int x, y;			/* location of window */
16470 	int width, height;		/* width and height of window */
16471 	int border_width;		/* border width of window */
16472 	int depth;			/* depth of window */
16473 	Visual *visual;			/* the associated visual structure */
16474 	Window root;			/* root of screen containing window */
16475 	int class_;			/* InputOutput, InputOnly*/
16476 	int bit_gravity;		/* one of the bit gravity values */
16477 	int win_gravity;		/* one of the window gravity values */
16478 	int backing_store;		/* NotUseful, WhenMapped, Always */
16479 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16480 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16481 	Bool save_under;		/* boolean, should bits under be saved? */
16482 	Colormap colormap;		/* color map to be associated with window */
16483 	Bool map_installed;		/* boolean, is color map currently installed*/
16484 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16485 	arch_long all_event_masks;		/* set of events all people have interest in*/
16486 	arch_long your_event_mask;		/* my event mask */
16487 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16488 	Bool override_redirect;		/* boolean value for override-redirect */
16489 	Screen *screen;			/* back pointer to correct screen */
16490 }
16491 
16492 enum IsUnmapped = 0;
16493 enum IsUnviewable = 1;
16494 enum IsViewable = 2;
16495 
16496 struct XSetWindowAttributes {
16497 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16498 	arch_ulong background_pixel;/* background pixel */
16499 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16500 	arch_ulong border_pixel;/* border pixel value */
16501 	int bit_gravity;         /* one of bit gravity values */
16502 	int win_gravity;         /* one of the window gravity values */
16503 	int backing_store;       /* NotUseful, WhenMapped, Always */
16504 	arch_ulong backing_planes;/* planes to be preserved if possible */
16505 	arch_ulong backing_pixel;/* value to use in restoring planes */
16506 	Bool save_under;         /* should bits under be saved? (popups) */
16507 	arch_long event_mask;         /* set of events that should be saved */
16508 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16509 	Bool override_redirect;  /* boolean value for override_redirect */
16510 	Colormap colormap;       /* color map to be associated with window */
16511 	Cursor cursor;           /* cursor to be displayed (or None) */
16512 }
16513 
16514 
16515 alias int Status;
16516 
16517 
16518 enum EventMask:int
16519 {
16520 	NoEventMask				=0,
16521 	KeyPressMask			=1<<0,
16522 	KeyReleaseMask			=1<<1,
16523 	ButtonPressMask			=1<<2,
16524 	ButtonReleaseMask		=1<<3,
16525 	EnterWindowMask			=1<<4,
16526 	LeaveWindowMask			=1<<5,
16527 	PointerMotionMask		=1<<6,
16528 	PointerMotionHintMask	=1<<7,
16529 	Button1MotionMask		=1<<8,
16530 	Button2MotionMask		=1<<9,
16531 	Button3MotionMask		=1<<10,
16532 	Button4MotionMask		=1<<11,
16533 	Button5MotionMask		=1<<12,
16534 	ButtonMotionMask		=1<<13,
16535 	KeymapStateMask		=1<<14,
16536 	ExposureMask			=1<<15,
16537 	VisibilityChangeMask	=1<<16,
16538 	StructureNotifyMask		=1<<17,
16539 	ResizeRedirectMask		=1<<18,
16540 	SubstructureNotifyMask	=1<<19,
16541 	SubstructureRedirectMask=1<<20,
16542 	FocusChangeMask			=1<<21,
16543 	PropertyChangeMask		=1<<22,
16544 	ColormapChangeMask		=1<<23,
16545 	OwnerGrabButtonMask		=1<<24
16546 }
16547 
16548 struct MwmHints {
16549 	c_ulong flags;
16550 	c_ulong functions;
16551 	c_ulong decorations;
16552 	c_long input_mode;
16553 	c_ulong status;
16554 }
16555 
16556 enum {
16557 	MWM_HINTS_FUNCTIONS = (1L << 0),
16558 	MWM_HINTS_DECORATIONS =  (1L << 1),
16559 
16560 	MWM_FUNC_ALL = (1L << 0),
16561 	MWM_FUNC_RESIZE = (1L << 1),
16562 	MWM_FUNC_MOVE = (1L << 2),
16563 	MWM_FUNC_MINIMIZE = (1L << 3),
16564 	MWM_FUNC_MAXIMIZE = (1L << 4),
16565 	MWM_FUNC_CLOSE = (1L << 5),
16566 
16567 	MWM_DECOR_ALL = (1L << 0),
16568 	MWM_DECOR_BORDER = (1L << 1),
16569 	MWM_DECOR_RESIZEH = (1L << 2),
16570 	MWM_DECOR_TITLE = (1L << 3),
16571 	MWM_DECOR_MENU = (1L << 4),
16572 	MWM_DECOR_MINIMIZE = (1L << 5),
16573 	MWM_DECOR_MAXIMIZE = (1L << 6),
16574 }
16575 
16576 import core.stdc.config : c_long, c_ulong;
16577 
16578 	/* Size hints mask bits */
16579 
16580 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16581 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16582 	enum   PPosition   = (1L << 2)          /* program specified position */;
16583 	enum   PSize       = (1L << 3)          /* program specified size */;
16584 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16585 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16586 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16587 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16588 	enum   PBaseSize   = (1L << 8);
16589 	enum   PWinGravity = (1L << 9);
16590 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16591 	struct XSizeHints {
16592 		arch_long flags;         /* marks which fields in this structure are defined */
16593 		int x, y;           /* Obsolete */
16594 		int width, height;  /* Obsolete */
16595 		int min_width, min_height;
16596 		int max_width, max_height;
16597 		int width_inc, height_inc;
16598 		struct Aspect {
16599 			int x;       /* numerator */
16600 			int y;       /* denominator */
16601 		}
16602 
16603 		Aspect min_aspect;
16604 		Aspect max_aspect;
16605 		int base_width, base_height;
16606 		int win_gravity;
16607 		/* this structure may be extended in the future */
16608 	}
16609 
16610 
16611 
16612 enum EventType:int
16613 {
16614 	KeyPress			=2,
16615 	KeyRelease			=3,
16616 	ButtonPress			=4,
16617 	ButtonRelease		=5,
16618 	MotionNotify		=6,
16619 	EnterNotify			=7,
16620 	LeaveNotify			=8,
16621 	FocusIn				=9,
16622 	FocusOut			=10,
16623 	KeymapNotify		=11,
16624 	Expose				=12,
16625 	GraphicsExpose		=13,
16626 	NoExpose			=14,
16627 	VisibilityNotify	=15,
16628 	CreateNotify		=16,
16629 	DestroyNotify		=17,
16630 	UnmapNotify		=18,
16631 	MapNotify			=19,
16632 	MapRequest			=20,
16633 	ReparentNotify		=21,
16634 	ConfigureNotify		=22,
16635 	ConfigureRequest	=23,
16636 	GravityNotify		=24,
16637 	ResizeRequest		=25,
16638 	CirculateNotify		=26,
16639 	CirculateRequest	=27,
16640 	PropertyNotify		=28,
16641 	SelectionClear		=29,
16642 	SelectionRequest	=30,
16643 	SelectionNotify		=31,
16644 	ColormapNotify		=32,
16645 	ClientMessage		=33,
16646 	MappingNotify		=34,
16647 	LASTEvent			=35	/* must be bigger than any event # */
16648 }
16649 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16650 struct XKeymapEvent
16651 {
16652 	int type;
16653 	arch_ulong serial;	/* # of last request processed by server */
16654 	Bool send_event;	/* true if this came from a SendEvent request */
16655 	Display *display;	/* Display the event was read from */
16656 	Window window;
16657 	byte[32] key_vector;
16658 }
16659 
16660 struct XExposeEvent
16661 {
16662 	int type;
16663 	arch_ulong serial;	/* # of last request processed by server */
16664 	Bool send_event;	/* true if this came from a SendEvent request */
16665 	Display *display;	/* Display the event was read from */
16666 	Window window;
16667 	int x, y;
16668 	int width, height;
16669 	int count;		/* if non-zero, at least this many more */
16670 }
16671 
16672 struct XGraphicsExposeEvent{
16673 	int type;
16674 	arch_ulong serial;	/* # of last request processed by server */
16675 	Bool send_event;	/* true if this came from a SendEvent request */
16676 	Display *display;	/* Display the event was read from */
16677 	Drawable drawable;
16678 	int x, y;
16679 	int width, height;
16680 	int count;		/* if non-zero, at least this many more */
16681 	int major_code;		/* core is CopyArea or CopyPlane */
16682 	int minor_code;		/* not defined in the core */
16683 }
16684 
16685 struct XNoExposeEvent{
16686 	int type;
16687 	arch_ulong serial;	/* # of last request processed by server */
16688 	Bool send_event;	/* true if this came from a SendEvent request */
16689 	Display *display;	/* Display the event was read from */
16690 	Drawable drawable;
16691 	int major_code;		/* core is CopyArea or CopyPlane */
16692 	int minor_code;		/* not defined in the core */
16693 }
16694 
16695 struct XVisibilityEvent{
16696 	int type;
16697 	arch_ulong serial;	/* # of last request processed by server */
16698 	Bool send_event;	/* true if this came from a SendEvent request */
16699 	Display *display;	/* Display the event was read from */
16700 	Window window;
16701 	VisibilityNotify state;		/* Visibility state */
16702 }
16703 
16704 struct XCreateWindowEvent{
16705 	int type;
16706 	arch_ulong serial;	/* # of last request processed by server */
16707 	Bool send_event;	/* true if this came from a SendEvent request */
16708 	Display *display;	/* Display the event was read from */
16709 	Window parent;		/* parent of the window */
16710 	Window window;		/* window id of window created */
16711 	int x, y;		/* window location */
16712 	int width, height;	/* size of window */
16713 	int border_width;	/* border width */
16714 	Bool override_redirect;	/* creation should be overridden */
16715 }
16716 
16717 struct XDestroyWindowEvent
16718 {
16719 	int type;
16720 	arch_ulong serial;		/* # of last request processed by server */
16721 	Bool send_event;	/* true if this came from a SendEvent request */
16722 	Display *display;	/* Display the event was read from */
16723 	Window event;
16724 	Window window;
16725 }
16726 
16727 struct XUnmapEvent
16728 {
16729 	int type;
16730 	arch_ulong serial;		/* # of last request processed by server */
16731 	Bool send_event;	/* true if this came from a SendEvent request */
16732 	Display *display;	/* Display the event was read from */
16733 	Window event;
16734 	Window window;
16735 	Bool from_configure;
16736 }
16737 
16738 struct XMapEvent
16739 {
16740 	int type;
16741 	arch_ulong serial;		/* # of last request processed by server */
16742 	Bool send_event;	/* true if this came from a SendEvent request */
16743 	Display *display;	/* Display the event was read from */
16744 	Window event;
16745 	Window window;
16746 	Bool override_redirect;	/* Boolean, is override set... */
16747 }
16748 
16749 struct XMapRequestEvent
16750 {
16751 	int type;
16752 	arch_ulong serial;	/* # of last request processed by server */
16753 	Bool send_event;	/* true if this came from a SendEvent request */
16754 	Display *display;	/* Display the event was read from */
16755 	Window parent;
16756 	Window window;
16757 }
16758 
16759 struct XReparentEvent
16760 {
16761 	int type;
16762 	arch_ulong serial;	/* # of last request processed by server */
16763 	Bool send_event;	/* true if this came from a SendEvent request */
16764 	Display *display;	/* Display the event was read from */
16765 	Window event;
16766 	Window window;
16767 	Window parent;
16768 	int x, y;
16769 	Bool override_redirect;
16770 }
16771 
16772 struct XConfigureEvent
16773 {
16774 	int type;
16775 	arch_ulong serial;	/* # of last request processed by server */
16776 	Bool send_event;	/* true if this came from a SendEvent request */
16777 	Display *display;	/* Display the event was read from */
16778 	Window event;
16779 	Window window;
16780 	int x, y;
16781 	int width, height;
16782 	int border_width;
16783 	Window above;
16784 	Bool override_redirect;
16785 }
16786 
16787 struct XGravityEvent
16788 {
16789 	int type;
16790 	arch_ulong serial;	/* # of last request processed by server */
16791 	Bool send_event;	/* true if this came from a SendEvent request */
16792 	Display *display;	/* Display the event was read from */
16793 	Window event;
16794 	Window window;
16795 	int x, y;
16796 }
16797 
16798 struct XResizeRequestEvent
16799 {
16800 	int type;
16801 	arch_ulong serial;	/* # of last request processed by server */
16802 	Bool send_event;	/* true if this came from a SendEvent request */
16803 	Display *display;	/* Display the event was read from */
16804 	Window window;
16805 	int width, height;
16806 }
16807 
16808 struct  XConfigureRequestEvent
16809 {
16810 	int type;
16811 	arch_ulong serial;	/* # of last request processed by server */
16812 	Bool send_event;	/* true if this came from a SendEvent request */
16813 	Display *display;	/* Display the event was read from */
16814 	Window parent;
16815 	Window window;
16816 	int x, y;
16817 	int width, height;
16818 	int border_width;
16819 	Window above;
16820 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
16821 	arch_ulong value_mask;
16822 }
16823 
16824 struct XCirculateEvent
16825 {
16826 	int type;
16827 	arch_ulong serial;	/* # of last request processed by server */
16828 	Bool send_event;	/* true if this came from a SendEvent request */
16829 	Display *display;	/* Display the event was read from */
16830 	Window event;
16831 	Window window;
16832 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16833 }
16834 
16835 struct XCirculateRequestEvent
16836 {
16837 	int type;
16838 	arch_ulong serial;	/* # of last request processed by server */
16839 	Bool send_event;	/* true if this came from a SendEvent request */
16840 	Display *display;	/* Display the event was read from */
16841 	Window parent;
16842 	Window window;
16843 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16844 }
16845 
16846 struct XPropertyEvent
16847 {
16848 	int type;
16849 	arch_ulong serial;	/* # of last request processed by server */
16850 	Bool send_event;	/* true if this came from a SendEvent request */
16851 	Display *display;	/* Display the event was read from */
16852 	Window window;
16853 	Atom atom;
16854 	Time time;
16855 	PropertyNotification state;		/* NewValue, Deleted */
16856 }
16857 
16858 struct XSelectionClearEvent
16859 {
16860 	int type;
16861 	arch_ulong serial;	/* # of last request processed by server */
16862 	Bool send_event;	/* true if this came from a SendEvent request */
16863 	Display *display;	/* Display the event was read from */
16864 	Window window;
16865 	Atom selection;
16866 	Time time;
16867 }
16868 
16869 struct XSelectionRequestEvent
16870 {
16871 	int type;
16872 	arch_ulong serial;	/* # of last request processed by server */
16873 	Bool send_event;	/* true if this came from a SendEvent request */
16874 	Display *display;	/* Display the event was read from */
16875 	Window owner;
16876 	Window requestor;
16877 	Atom selection;
16878 	Atom target;
16879 	Atom property;
16880 	Time time;
16881 }
16882 
16883 struct XSelectionEvent
16884 {
16885 	int type;
16886 	arch_ulong serial;	/* # of last request processed by server */
16887 	Bool send_event;	/* true if this came from a SendEvent request */
16888 	Display *display;	/* Display the event was read from */
16889 	Window requestor;
16890 	Atom selection;
16891 	Atom target;
16892 	Atom property;		/* ATOM or None */
16893 	Time time;
16894 }
16895 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
16896 
16897 struct XColormapEvent
16898 {
16899 	int type;
16900 	arch_ulong serial;	/* # of last request processed by server */
16901 	Bool send_event;	/* true if this came from a SendEvent request */
16902 	Display *display;	/* Display the event was read from */
16903 	Window window;
16904 	Colormap colormap;	/* COLORMAP or None */
16905 	Bool new_;		/* C++ */
16906 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
16907 }
16908 version(X86_64) static assert(XColormapEvent.sizeof == 56);
16909 
16910 struct XClientMessageEvent
16911 {
16912 	int type;
16913 	arch_ulong serial;	/* # of last request processed by server */
16914 	Bool send_event;	/* true if this came from a SendEvent request */
16915 	Display *display;	/* Display the event was read from */
16916 	Window window;
16917 	Atom message_type;
16918 	int format;
16919 	union Data{
16920 		byte[20] b;
16921 		short[10] s;
16922 		arch_ulong[5] l;
16923 	}
16924 	Data data;
16925 
16926 }
16927 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
16928 
16929 struct XMappingEvent
16930 {
16931 	int type;
16932 	arch_ulong serial;	/* # of last request processed by server */
16933 	Bool send_event;	/* true if this came from a SendEvent request */
16934 	Display *display;	/* Display the event was read from */
16935 	Window window;		/* unused */
16936 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
16937 				   MappingPointer */
16938 	int first_keycode;	/* first keycode */
16939 	int count;		/* defines range of change w. first_keycode*/
16940 }
16941 
16942 struct XErrorEvent
16943 {
16944 	int type;
16945 	Display *display;	/* Display the event was read from */
16946 	XID resourceid;		/* resource id */
16947 	arch_ulong serial;	/* serial number of failed request */
16948 	ubyte error_code;	/* error code of failed request */
16949 	ubyte request_code;	/* Major op-code of failed request */
16950 	ubyte minor_code;	/* Minor op-code of failed request */
16951 }
16952 
16953 struct XAnyEvent
16954 {
16955 	int type;
16956 	arch_ulong serial;	/* # of last request processed by server */
16957 	Bool send_event;	/* true if this came from a SendEvent request */
16958 	Display *display;/* Display the event was read from */
16959 	Window window;	/* window on which event was requested in event mask */
16960 }
16961 
16962 union XEvent{
16963 	int type;		/* must not be changed; first element */
16964 	XAnyEvent xany;
16965 	XKeyEvent xkey;
16966 	XButtonEvent xbutton;
16967 	XMotionEvent xmotion;
16968 	XCrossingEvent xcrossing;
16969 	XFocusChangeEvent xfocus;
16970 	XExposeEvent xexpose;
16971 	XGraphicsExposeEvent xgraphicsexpose;
16972 	XNoExposeEvent xnoexpose;
16973 	XVisibilityEvent xvisibility;
16974 	XCreateWindowEvent xcreatewindow;
16975 	XDestroyWindowEvent xdestroywindow;
16976 	XUnmapEvent xunmap;
16977 	XMapEvent xmap;
16978 	XMapRequestEvent xmaprequest;
16979 	XReparentEvent xreparent;
16980 	XConfigureEvent xconfigure;
16981 	XGravityEvent xgravity;
16982 	XResizeRequestEvent xresizerequest;
16983 	XConfigureRequestEvent xconfigurerequest;
16984 	XCirculateEvent xcirculate;
16985 	XCirculateRequestEvent xcirculaterequest;
16986 	XPropertyEvent xproperty;
16987 	XSelectionClearEvent xselectionclear;
16988 	XSelectionRequestEvent xselectionrequest;
16989 	XSelectionEvent xselection;
16990 	XColormapEvent xcolormap;
16991 	XClientMessageEvent xclient;
16992 	XMappingEvent xmapping;
16993 	XErrorEvent xerror;
16994 	XKeymapEvent xkeymap;
16995 	arch_ulong[24] pad;
16996 }
16997 
16998 
16999 	struct Display {
17000 		XExtData *ext_data;	/* hook for extension to hang data */
17001 		_XPrivate *private1;
17002 		int fd;			/* Network socket. */
17003 		int private2;
17004 		int proto_major_version;/* major version of server's X protocol */
17005 		int proto_minor_version;/* minor version of servers X protocol */
17006 		char *vendor;		/* vendor of the server hardware */
17007 	    	XID private3;
17008 		XID private4;
17009 		XID private5;
17010 		int private6;
17011 		XID function(Display*)resource_alloc;/* allocator function */
17012 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17013 		int bitmap_unit;	/* padding and data requirements */
17014 		int bitmap_pad;		/* padding requirements on bitmaps */
17015 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17016 		int nformats;		/* number of pixmap formats in list */
17017 		ScreenFormat *pixmap_format;	/* pixmap format list */
17018 		int private8;
17019 		int release;		/* release of the server */
17020 		_XPrivate *private9;
17021 		_XPrivate *private10;
17022 		int qlen;		/* Length of input event queue */
17023 		arch_ulong last_request_read; /* seq number of last event read */
17024 		arch_ulong request;	/* sequence number of last request. */
17025 		XPointer private11;
17026 		XPointer private12;
17027 		XPointer private13;
17028 		XPointer private14;
17029 		uint max_request_size; /* maximum number 32 bit words in request*/
17030 		_XrmHashBucketRec *db;
17031 		int function  (Display*)private15;
17032 		char *display_name;	/* "host:display" string used on this connect*/
17033 		int default_screen;	/* default screen for operations */
17034 		int nscreens;		/* number of screens on this server*/
17035 		Screen *screens;	/* pointer to list of screens */
17036 		arch_ulong motion_buffer;	/* size of motion buffer */
17037 		arch_ulong private16;
17038 		int min_keycode;	/* minimum defined keycode */
17039 		int max_keycode;	/* maximum defined keycode */
17040 		XPointer private17;
17041 		XPointer private18;
17042 		int private19;
17043 		byte *xdefaults;	/* contents of defaults from server */
17044 		/* there is more to this structure, but it is private to Xlib */
17045 	}
17046 
17047 	// I got these numbers from a C program as a sanity test
17048 	version(X86_64) {
17049 		static assert(Display.sizeof == 296);
17050 		static assert(XPointer.sizeof == 8);
17051 		static assert(XErrorEvent.sizeof == 40);
17052 		static assert(XAnyEvent.sizeof == 40);
17053 		static assert(XMappingEvent.sizeof == 56);
17054 		static assert(XEvent.sizeof == 192);
17055     	} else version (AArch64) {
17056         	// omit check for aarch64
17057 	} else {
17058 		static assert(Display.sizeof == 176);
17059 		static assert(XPointer.sizeof == 4);
17060 		static assert(XEvent.sizeof == 96);
17061 	}
17062 
17063 struct Depth
17064 {
17065 	int depth;		/* this depth (Z) of the depth */
17066 	int nvisuals;		/* number of Visual types at this depth */
17067 	Visual *visuals;	/* list of visuals possible at this depth */
17068 }
17069 
17070 alias void* GC;
17071 alias c_ulong VisualID;
17072 alias XID Colormap;
17073 alias XID Cursor;
17074 alias XID KeySym;
17075 alias uint KeyCode;
17076 enum None = 0;
17077 }
17078 
17079 version(without_opengl) {}
17080 else {
17081 extern(C) nothrow @nogc {
17082 
17083 
17084 static if(!SdpyIsUsingIVGLBinds) {
17085 enum GLX_USE_GL=            1;       /* support GLX rendering */
17086 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17087 enum GLX_LEVEL=             3;       /* level in plane stacking */
17088 enum GLX_RGBA=              4;       /* true if RGBA mode */
17089 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17090 enum GLX_STEREO=            6;       /* stereo buffering supported */
17091 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17092 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17093 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17094 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17095 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17096 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17097 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17098 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17099 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17100 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17101 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17102 
17103 
17104 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17105 
17106 
17107 
17108 enum GL_TRUE = 1;
17109 enum GL_FALSE = 0;
17110 alias int GLint;
17111 }
17112 
17113 alias XID GLXContextID;
17114 alias XID GLXPixmap;
17115 alias XID GLXDrawable;
17116 alias XID GLXPbuffer;
17117 alias XID GLXWindow;
17118 alias XID GLXFBConfigID;
17119 alias void* GLXContext;
17120 
17121 }
17122 }
17123 
17124 enum AllocNone = 0;
17125 
17126 extern(C) {
17127 	/* WARNING, this type not in Xlib spec */
17128 	extern(C) alias XIOErrorHandler = int function (Display* display);
17129 }
17130 
17131 extern(C) nothrow
17132 alias XErrorHandler = int function(Display*, XErrorEvent*);
17133 
17134 extern(C) nothrow @nogc {
17135 struct Screen{
17136 	XExtData *ext_data;		/* hook for extension to hang data */
17137 	Display *display;		/* back pointer to display structure */
17138 	Window root;			/* Root window id. */
17139 	int width, height;		/* width and height of screen */
17140 	int mwidth, mheight;	/* width and height of  in millimeters */
17141 	int ndepths;			/* number of depths possible */
17142 	Depth *depths;			/* list of allowable depths on the screen */
17143 	int root_depth;			/* bits per pixel */
17144 	Visual *root_visual;	/* root visual */
17145 	GC default_gc;			/* GC for the root root visual */
17146 	Colormap cmap;			/* default color map */
17147 	uint white_pixel;
17148 	uint black_pixel;		/* White and Black pixel values */
17149 	int max_maps, min_maps;	/* max and min color maps */
17150 	int backing_store;		/* Never, WhenMapped, Always */
17151 	bool save_unders;
17152 	int root_input_mask;	/* initial root input mask */
17153 }
17154 
17155 struct Visual
17156 {
17157 	XExtData *ext_data;	/* hook for extension to hang data */
17158 	VisualID visualid;	/* visual id of this visual */
17159 	int class_;			/* class of screen (monochrome, etc.) */
17160 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17161 	int bits_per_rgb;	/* log base 2 of distinct color values */
17162 	int map_entries;	/* color map entries */
17163 }
17164 
17165 	alias Display* _XPrivDisplay;
17166 
17167 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17168 		assert(dpy !is null);
17169 		return &dpy.screens[scr];
17170 	}
17171 
17172 	extern(D) Window RootWindow(Display *dpy,int scr) {
17173 		return ScreenOfDisplay(dpy,scr).root;
17174 	}
17175 
17176 	struct XWMHints {
17177 		arch_long flags;
17178 		Bool input;
17179 		int initial_state;
17180 		Pixmap icon_pixmap;
17181 		Window icon_window;
17182 		int icon_x, icon_y;
17183 		Pixmap icon_mask;
17184 		XID window_group;
17185 	}
17186 
17187 	struct XClassHint {
17188 		char* res_name;
17189 		char* res_class;
17190 	}
17191 
17192 	extern(D) int DefaultScreen(Display *dpy) {
17193 		return dpy.default_screen;
17194 	}
17195 
17196 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17197 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17198 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17199 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17200 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17201 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17202 
17203 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17204 
17205 	enum int AnyPropertyType = 0;
17206 	enum int Success = 0;
17207 
17208 	enum int RevertToNone = None;
17209 	enum int PointerRoot = 1;
17210 	enum Time CurrentTime = 0;
17211 	enum int RevertToPointerRoot = PointerRoot;
17212 	enum int RevertToParent = 2;
17213 
17214 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17215 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17216 	}
17217 
17218 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17219 		return ScreenOfDisplay(dpy,scr).root_visual;
17220 	}
17221 
17222 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17223 		return ScreenOfDisplay(dpy,scr).default_gc;
17224 	}
17225 
17226 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17227 		return ScreenOfDisplay(dpy,scr).black_pixel;
17228 	}
17229 
17230 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17231 		return ScreenOfDisplay(dpy,scr).white_pixel;
17232 	}
17233 
17234 	alias void* XFontSet; // i think
17235 	struct XmbTextItem {
17236 		char* chars;
17237 		int nchars;
17238 		int delta;
17239 		XFontSet font_set;
17240 	}
17241 
17242 	struct XTextItem {
17243 		char* chars;
17244 		int nchars;
17245 		int delta;
17246 		Font font;
17247 	}
17248 
17249 	enum {
17250 		GXclear        = 0x0, /* 0 */
17251 		GXand          = 0x1, /* src AND dst */
17252 		GXandReverse   = 0x2, /* src AND NOT dst */
17253 		GXcopy         = 0x3, /* src */
17254 		GXandInverted  = 0x4, /* NOT src AND dst */
17255 		GXnoop         = 0x5, /* dst */
17256 		GXxor          = 0x6, /* src XOR dst */
17257 		GXor           = 0x7, /* src OR dst */
17258 		GXnor          = 0x8, /* NOT src AND NOT dst */
17259 		GXequiv        = 0x9, /* NOT src XOR dst */
17260 		GXinvert       = 0xa, /* NOT dst */
17261 		GXorReverse    = 0xb, /* src OR NOT dst */
17262 		GXcopyInverted = 0xc, /* NOT src */
17263 		GXorInverted   = 0xd, /* NOT src OR dst */
17264 		GXnand         = 0xe, /* NOT src OR NOT dst */
17265 		GXset          = 0xf, /* 1 */
17266 	}
17267 	enum QueueMode : int {
17268 		QueuedAlready,
17269 		QueuedAfterReading,
17270 		QueuedAfterFlush
17271 	}
17272 
17273 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17274 
17275 	struct XPoint {
17276 		short x;
17277 		short y;
17278 	}
17279 
17280 	enum CoordMode:int {
17281 		CoordModeOrigin = 0,
17282 		CoordModePrevious = 1
17283 	}
17284 
17285 	enum PolygonShape:int {
17286 		Complex = 0,
17287 		Nonconvex = 1,
17288 		Convex = 2
17289 	}
17290 
17291 	struct XTextProperty {
17292 		const(char)* value;		/* same as Property routines */
17293 		Atom encoding;			/* prop type */
17294 		int format;				/* prop data format: 8, 16, or 32 */
17295 		arch_ulong nitems;		/* number of data items in value */
17296 	}
17297 
17298 	version( X86_64 ) {
17299 		static assert(XTextProperty.sizeof == 32);
17300 	}
17301 
17302 
17303 	struct XGCValues {
17304 		int function_;           /* logical operation */
17305 		arch_ulong plane_mask;/* plane mask */
17306 		arch_ulong foreground;/* foreground pixel */
17307 		arch_ulong background;/* background pixel */
17308 		int line_width;         /* line width */
17309 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17310 		int cap_style;          /* CapNotLast, CapButt,
17311 					   CapRound, CapProjecting */
17312 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17313 		int fill_style;         /* FillSolid, FillTiled,
17314 					   FillStippled, FillOpaeueStippled */
17315 		int fill_rule;          /* EvenOddRule, WindingRule */
17316 		int arc_mode;           /* ArcChord, ArcPieSlice */
17317 		Pixmap tile;            /* tile pixmap for tiling operations */
17318 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17319 		int ts_x_origin;        /* offset for tile or stipple operations */
17320 		int ts_y_origin;
17321 		Font font;              /* default text font for text operations */
17322 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17323 		Bool graphics_exposures;/* boolean, should exposures be generated */
17324 		int clip_x_origin;      /* origin for clipping */
17325 		int clip_y_origin;
17326 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17327 		int dash_offset;        /* patterned/dashed line information */
17328 		char dashes;
17329 	}
17330 
17331 	struct XColor {
17332 		arch_ulong pixel;
17333 		ushort red, green, blue;
17334 		byte flags;
17335 		byte pad;
17336 	}
17337 
17338 	struct XRectangle {
17339 		short x;
17340 		short y;
17341 		ushort width;
17342 		ushort height;
17343 	}
17344 
17345 	enum ClipByChildren = 0;
17346 	enum IncludeInferiors = 1;
17347 
17348 	enum Atom XA_PRIMARY = 1;
17349 	enum Atom XA_SECONDARY = 2;
17350 	enum Atom XA_STRING = 31;
17351 	enum Atom XA_CARDINAL = 6;
17352 	enum Atom XA_WM_NAME = 39;
17353 	enum Atom XA_ATOM = 4;
17354 	enum Atom XA_WINDOW = 33;
17355 	enum Atom XA_WM_HINTS = 35;
17356 	enum int PropModeAppend = 2;
17357 	enum int PropModeReplace = 0;
17358 	enum int PropModePrepend = 1;
17359 
17360 	enum int CopyFromParent = 0;
17361 	enum int InputOutput = 1;
17362 
17363 	// XWMHints
17364 	enum InputHint = 1 << 0;
17365 	enum StateHint = 1 << 1;
17366 	enum IconPixmapHint = (1L << 2);
17367 	enum IconWindowHint = (1L << 3);
17368 	enum IconPositionHint = (1L << 4);
17369 	enum IconMaskHint = (1L << 5);
17370 	enum WindowGroupHint = (1L << 6);
17371 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17372 	enum XUrgencyHint = (1L << 8);
17373 
17374 	// GC Components
17375 	enum GCFunction           =   (1L<<0);
17376 	enum GCPlaneMask         =    (1L<<1);
17377 	enum GCForeground       =     (1L<<2);
17378 	enum GCBackground      =      (1L<<3);
17379 	enum GCLineWidth      =       (1L<<4);
17380 	enum GCLineStyle     =        (1L<<5);
17381 	enum GCCapStyle     =         (1L<<6);
17382 	enum GCJoinStyle   =          (1L<<7);
17383 	enum GCFillStyle  =           (1L<<8);
17384 	enum GCFillRule  =            (1L<<9);
17385 	enum GCTile     =             (1L<<10);
17386 	enum GCStipple           =    (1L<<11);
17387 	enum GCTileStipXOrigin  =     (1L<<12);
17388 	enum GCTileStipYOrigin =      (1L<<13);
17389 	enum GCFont               =   (1L<<14);
17390 	enum GCSubwindowMode     =    (1L<<15);
17391 	enum GCGraphicsExposures=     (1L<<16);
17392 	enum GCClipXOrigin     =      (1L<<17);
17393 	enum GCClipYOrigin    =       (1L<<18);
17394 	enum GCClipMask      =        (1L<<19);
17395 	enum GCDashOffset   =         (1L<<20);
17396 	enum GCDashList    =          (1L<<21);
17397 	enum GCArcMode    =           (1L<<22);
17398 	enum GCLastBit   =            22;
17399 
17400 
17401 	enum int WithdrawnState = 0;
17402 	enum int NormalState = 1;
17403 	enum int IconicState = 3;
17404 
17405 }
17406 } else version (OSXCocoa) {
17407 private:
17408 	alias void* id;
17409 	alias void* Class;
17410 	alias void* SEL;
17411 	alias void* IMP;
17412 	alias void* Ivar;
17413 	alias byte BOOL;
17414 	alias const(void)* CFStringRef;
17415 	alias const(void)* CFAllocatorRef;
17416 	alias const(void)* CFTypeRef;
17417 	alias const(void)* CGContextRef;
17418 	alias const(void)* CGColorSpaceRef;
17419 	alias const(void)* CGImageRef;
17420 	alias ulong CGBitmapInfo;
17421 
17422 	struct objc_super {
17423 		id self;
17424 		Class superclass;
17425 	}
17426 
17427 	struct CFRange {
17428 		long location, length;
17429 	}
17430 
17431 	struct NSPoint {
17432 		double x, y;
17433 
17434 		static fromTuple(T)(T tupl) {
17435 			return NSPoint(tupl.tupleof);
17436 		}
17437 	}
17438 	struct NSSize {
17439 		double width, height;
17440 	}
17441 	struct NSRect {
17442 		NSPoint origin;
17443 		NSSize size;
17444 	}
17445 	alias NSPoint CGPoint;
17446 	alias NSSize CGSize;
17447 	alias NSRect CGRect;
17448 
17449 	struct CGAffineTransform {
17450 		double a, b, c, d, tx, ty;
17451 	}
17452 
17453 	enum NSApplicationActivationPolicyRegular = 0;
17454 	enum NSBackingStoreBuffered = 2;
17455 	enum kCFStringEncodingUTF8 = 0x08000100;
17456 
17457 	enum : size_t {
17458 		NSBorderlessWindowMask = 0,
17459 		NSTitledWindowMask = 1 << 0,
17460 		NSClosableWindowMask = 1 << 1,
17461 		NSMiniaturizableWindowMask = 1 << 2,
17462 		NSResizableWindowMask = 1 << 3,
17463 		NSTexturedBackgroundWindowMask = 1 << 8
17464 	}
17465 
17466 	enum : ulong {
17467 		kCGImageAlphaNone,
17468 		kCGImageAlphaPremultipliedLast,
17469 		kCGImageAlphaPremultipliedFirst,
17470 		kCGImageAlphaLast,
17471 		kCGImageAlphaFirst,
17472 		kCGImageAlphaNoneSkipLast,
17473 		kCGImageAlphaNoneSkipFirst
17474 	}
17475 	enum : ulong {
17476 		kCGBitmapAlphaInfoMask = 0x1F,
17477 		kCGBitmapFloatComponents = (1 << 8),
17478 		kCGBitmapByteOrderMask = 0x7000,
17479 		kCGBitmapByteOrderDefault = (0 << 12),
17480 		kCGBitmapByteOrder16Little = (1 << 12),
17481 		kCGBitmapByteOrder32Little = (2 << 12),
17482 		kCGBitmapByteOrder16Big = (3 << 12),
17483 		kCGBitmapByteOrder32Big = (4 << 12)
17484 	}
17485 	enum CGPathDrawingMode {
17486 		kCGPathFill,
17487 		kCGPathEOFill,
17488 		kCGPathStroke,
17489 		kCGPathFillStroke,
17490 		kCGPathEOFillStroke
17491 	}
17492 	enum objc_AssociationPolicy : size_t {
17493 		OBJC_ASSOCIATION_ASSIGN = 0,
17494 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
17495 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
17496 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
17497 		OBJC_ASSOCIATION_COPY = 0x303 //01403
17498 	}
17499 
17500 	extern(C) {
17501 		id objc_msgSend(id receiver, SEL selector, ...);
17502 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
17503 		id objc_getClass(const(char)* name);
17504 		SEL sel_registerName(const(char)* str);
17505 		Class objc_allocateClassPair(Class superclass, const(char)* name,
17506 									 size_t extra_bytes);
17507 		void objc_registerClassPair(Class cls);
17508 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
17509 		id objc_getAssociatedObject(id object, void* key);
17510 		void objc_setAssociatedObject(id object, void* key, id value,
17511 									  objc_AssociationPolicy policy);
17512 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
17513 		id object_getIvar(id object, Ivar ivar);
17514 		void object_setIvar(id object, Ivar ivar, id value);
17515 		BOOL class_addIvar(Class cls, const(char)* name,
17516 						   size_t size, ubyte alignment, const(char)* types);
17517 
17518 		extern __gshared id NSApp;
17519 
17520 		void CFRelease(CFTypeRef obj);
17521 
17522 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
17523 											const(char)* bytes, long numBytes,
17524 											long encoding,
17525 											BOOL isExternalRepresentation);
17526 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
17527 							 char lossByte, bool isExternalRepresentation,
17528 							 char* buffer, long maxBufLen, long* usedBufLen);
17529 		long CFStringGetLength(CFStringRef theString);
17530 
17531 		CGContextRef CGBitmapContextCreate(void* data,
17532 										   size_t width, size_t height,
17533 										   size_t bitsPerComponent,
17534 										   size_t bytesPerRow,
17535 										   CGColorSpaceRef colorspace,
17536 										   CGBitmapInfo bitmapInfo);
17537 		void CGContextRelease(CGContextRef c);
17538 		ubyte* CGBitmapContextGetData(CGContextRef c);
17539 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
17540 		size_t CGBitmapContextGetWidth(CGContextRef c);
17541 		size_t CGBitmapContextGetHeight(CGContextRef c);
17542 
17543 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
17544 		void CGColorSpaceRelease(CGColorSpaceRef cs);
17545 
17546 		void CGContextSetRGBStrokeColor(CGContextRef c,
17547 										double red, double green, double blue,
17548 										double alpha);
17549 		void CGContextSetRGBFillColor(CGContextRef c,
17550 									  double red, double green, double blue,
17551 									  double alpha);
17552 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
17553 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
17554 									  const(char)* str, size_t length);
17555 		void CGContextStrokeLineSegments(CGContextRef c,
17556 										 const(CGPoint)* points, size_t count);
17557 
17558 		void CGContextBeginPath(CGContextRef c);
17559 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
17560 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
17561 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
17562 							 double startAngle, double endAngle, long clockwise);
17563 		void CGContextAddRect(CGContextRef c, CGRect rect);
17564 		void CGContextAddLines(CGContextRef c,
17565 							   const(CGPoint)* points, size_t count);
17566 		void CGContextSaveGState(CGContextRef c);
17567 		void CGContextRestoreGState(CGContextRef c);
17568 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
17569 								 ulong textEncoding);
17570 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
17571 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
17572 
17573 		void CGImageRelease(CGImageRef image);
17574 	}
17575 
17576 private:
17577     // A convenient method to create a CFString (=NSString) from a D string.
17578     CFStringRef createCFString(string str) {
17579         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
17580                                              kCFStringEncodingUTF8, false);
17581     }
17582 
17583     // Objective-C calls.
17584     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
17585         auto _cmd = sel_registerName(selector.ptr);
17586         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17587         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
17588     }
17589     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
17590         auto _cmd = sel_registerName(selector.ptr);
17591         auto cls = objc_getClass(className);
17592         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17593         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
17594     }
17595     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
17596         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
17597     }
17598 
17599     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
17600     alias objc_msgSend_classMethod!("alloc", id) alloc;
17601     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
17602                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
17603     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
17604     alias objc_msgSend_specialized!("center", void) center;
17605     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
17606     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
17607     alias objc_msgSend_specialized!("release", void) release;
17608     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
17609     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
17610     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
17611     alias objc_msgSend_specialized!("invalidate", void) invalidate;
17612     alias objc_msgSend_specialized!("close", void) close;
17613     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
17614                                     id, double, id, SEL, id, BOOL) scheduledTimer;
17615     alias objc_msgSend_specialized!("run", void) run;
17616     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
17617                                     id) currentNSGraphicsContext;
17618     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
17619     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
17620     alias objc_msgSend_specialized!("superclass", Class) superclass;
17621     alias objc_msgSend_specialized!("init", id) init;
17622     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
17623     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
17624     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
17625                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
17626     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
17627     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
17628     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
17629                                     void, BOOL) activateIgnoringOtherApps;
17630     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
17631                                     id) sharedNSApplication;
17632     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
17633 } else static assert(0, "Unsupported operating system");
17634 
17635 
17636 version(OSXCocoa) {
17637 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
17638 	//
17639 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
17640 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
17641 	//
17642 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
17643 	// Probably won't even fully compile right now
17644 
17645     import std.math : PI;
17646     import std.algorithm : map;
17647     import std.array : array;
17648 
17649     alias SimpleWindow NativeWindowHandle;
17650     alias void delegate(id) NativeEventHandler;
17651 
17652     __gshared Ivar simpleWindowIvar;
17653 
17654     enum KEY_ESCAPE = 27;
17655 
17656     mixin template NativeImageImplementation() {
17657         CGContextRef context;
17658         ubyte* rawData;
17659     final:
17660 
17661 	void convertToRgbaBytes(ubyte[] where) {
17662 		assert(where.length == this.width * this.height * 4);
17663 
17664 		// if rawData had a length....
17665 		//assert(rawData.length == where.length);
17666 		for(long idx = 0; idx < where.length; idx += 4) {
17667 			auto alpha = rawData[idx + 3];
17668 			if(alpha == 255) {
17669 				where[idx + 0] = rawData[idx + 0]; // r
17670 				where[idx + 1] = rawData[idx + 1]; // g
17671 				where[idx + 2] = rawData[idx + 2]; // b
17672 				where[idx + 3] = rawData[idx + 3]; // a
17673 			} else {
17674 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
17675 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
17676 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
17677 				where[idx + 3] = rawData[idx + 3]; // a
17678 
17679 			}
17680 		}
17681 	}
17682 
17683 	void setFromRgbaBytes(in ubyte[] where) {
17684 		// FIXME: this is probably wrong
17685 		assert(where.length == this.width * this.height * 4);
17686 
17687 		// if rawData had a length....
17688 		//assert(rawData.length == where.length);
17689 		for(long idx = 0; idx < where.length; idx += 4) {
17690 			auto alpha = rawData[idx + 3];
17691 			if(alpha == 255) {
17692 				rawData[idx + 0] = where[idx + 0]; // r
17693 				rawData[idx + 1] = where[idx + 1]; // g
17694 				rawData[idx + 2] = where[idx + 2]; // b
17695 				rawData[idx + 3] = where[idx + 3]; // a
17696 			} else {
17697 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
17698 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
17699 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
17700 				rawData[idx + 3] = where[idx + 3]; // a
17701 
17702 			}
17703 		}
17704 	}
17705 
17706 
17707         void createImage(int width, int height, bool forcexshm=false) {
17708             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17709             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
17710                                             colorSpace,
17711                                             kCGImageAlphaPremultipliedLast
17712                                                    |kCGBitmapByteOrder32Big);
17713             CGColorSpaceRelease(colorSpace);
17714             rawData = CGBitmapContextGetData(context);
17715         }
17716         void dispose() {
17717             CGContextRelease(context);
17718         }
17719 
17720         void setPixel(int x, int y, Color c) {
17721             auto offset = (y * width + x) * 4;
17722             if (c.a == 255) {
17723                 rawData[offset + 0] = c.r;
17724                 rawData[offset + 1] = c.g;
17725                 rawData[offset + 2] = c.b;
17726                 rawData[offset + 3] = c.a;
17727             } else {
17728                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
17729                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
17730                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
17731                 rawData[offset + 3] = c.a;
17732             }
17733         }
17734     }
17735 
17736     mixin template NativeScreenPainterImplementation() {
17737         CGContextRef context;
17738         ubyte[4] _outlineComponents;
17739 	id view;
17740 
17741         void create(NativeWindowHandle window) {
17742             context = window.drawingContext;
17743 	    view = window.view;
17744         }
17745 
17746         void dispose() {
17747             	setNeedsDisplay(view, true);
17748         }
17749 
17750 	bool manualInvalidations;
17751 	void invalidateRect(Rectangle invalidRect) { }
17752 
17753 	// NotYetImplementedException
17754 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
17755 	void rasterOp(RasterOp op) {}
17756 	Pen _activePen;
17757 	Color _fillColor;
17758 	Rectangle _clipRectangle;
17759 	void setClipRectangle(int, int, int, int) {}
17760 	void setFont(OperatingSystemFont) {}
17761 	int fontHeight() { return 14; }
17762 
17763 	// end
17764 
17765         void pen(Pen pen) {
17766 	    _activePen = pen;
17767 	    auto color = pen.color; // FIXME
17768             double alphaComponent = color.a/255.0f;
17769             CGContextSetRGBStrokeColor(context,
17770                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
17771 
17772             if (color.a != 255) {
17773                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
17774                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
17775                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
17776                 _outlineComponents[3] = color.a;
17777             } else {
17778                 _outlineComponents[0] = color.r;
17779                 _outlineComponents[1] = color.g;
17780                 _outlineComponents[2] = color.b;
17781                 _outlineComponents[3] = color.a;
17782             }
17783         }
17784 
17785         @property void fillColor(Color color) {
17786             CGContextSetRGBFillColor(context,
17787                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
17788         }
17789 
17790         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
17791 		// NotYetImplementedException for upper left/width/height
17792             auto cgImage = CGBitmapContextCreateImage(image.context);
17793             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17794                                CGBitmapContextGetHeight(image.context));
17795             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17796             CGImageRelease(cgImage);
17797         }
17798 
17799 	version(OSXCocoa) {} else // NotYetImplementedException
17800         void drawPixmap(Sprite image, int x, int y) {
17801 		// FIXME: is this efficient?
17802             auto cgImage = CGBitmapContextCreateImage(image.context);
17803             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17804                                CGBitmapContextGetHeight(image.context));
17805             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17806             CGImageRelease(cgImage);
17807         }
17808 
17809 
17810         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
17811 		// FIXME: alignment
17812             if (_outlineComponents[3] != 0) {
17813                 CGContextSaveGState(context);
17814                 auto invAlpha = 1.0f/_outlineComponents[3];
17815                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
17816                                                   _outlineComponents[1]*invAlpha,
17817                                                   _outlineComponents[2]*invAlpha,
17818                                                   _outlineComponents[3]/255.0f);
17819                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
17820 // auto cfstr = cast(id)createCFString(text);
17821 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
17822 // NSPoint(x, y), null);
17823 // CFRelease(cfstr);
17824                 CGContextRestoreGState(context);
17825             }
17826         }
17827 
17828         void drawPixel(int x, int y) {
17829             auto rawData = CGBitmapContextGetData(context);
17830             auto width = CGBitmapContextGetWidth(context);
17831             auto height = CGBitmapContextGetHeight(context);
17832             auto offset = ((height - y - 1) * width + x) * 4;
17833             rawData[offset .. offset+4] = _outlineComponents;
17834         }
17835 
17836         void drawLine(int x1, int y1, int x2, int y2) {
17837             CGPoint[2] linePoints;
17838             linePoints[0] = CGPoint(x1, y1);
17839             linePoints[1] = CGPoint(x2, y2);
17840             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
17841         }
17842 
17843         void drawRectangle(int x, int y, int width, int height) {
17844             CGContextBeginPath(context);
17845             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
17846             CGContextAddRect(context, rect);
17847             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17848         }
17849 
17850         void drawEllipse(int x1, int y1, int x2, int y2) {
17851             CGContextBeginPath(context);
17852             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
17853             CGContextAddEllipseInRect(context, rect);
17854             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17855         }
17856 
17857         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
17858             // @@@BUG@@@ Does not support elliptic arc (width != height).
17859             CGContextBeginPath(context);
17860             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
17861                             start*PI/(180*64), finish*PI/(180*64), 0);
17862             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17863         }
17864 
17865         void drawPolygon(Point[] intPoints) {
17866             CGContextBeginPath(context);
17867             auto points = array(map!(CGPoint.fromTuple)(intPoints));
17868             CGContextAddLines(context, points.ptr, points.length);
17869             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17870         }
17871     }
17872 
17873     mixin template NativeSimpleWindowImplementation() {
17874         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
17875             synchronized {
17876                 if (NSApp == null) initializeApp();
17877             }
17878 
17879             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
17880 
17881             // create the window.
17882             window = initWithContentRect(alloc("NSWindow"),
17883                                          contentRect,
17884                                          NSTitledWindowMask
17885                                             |NSClosableWindowMask
17886                                             |NSMiniaturizableWindowMask
17887                                             |NSResizableWindowMask,
17888                                          NSBackingStoreBuffered,
17889                                          true);
17890 
17891             // set the title & move the window to center.
17892             auto windowTitle = createCFString(title);
17893             setTitle(window, windowTitle);
17894             CFRelease(windowTitle);
17895             center(window);
17896 
17897             // create area to draw on.
17898             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17899             drawingContext = CGBitmapContextCreate(null, width, height,
17900                                                    8, 4*width, colorSpace,
17901                                                    kCGImageAlphaPremultipliedLast
17902                                                       |kCGBitmapByteOrder32Big);
17903             CGColorSpaceRelease(colorSpace);
17904             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
17905             auto matrix = CGContextGetTextMatrix(drawingContext);
17906             matrix.c = -matrix.c;
17907             matrix.d = -matrix.d;
17908             CGContextSetTextMatrix(drawingContext, matrix);
17909 
17910             // create the subview that things will be drawn on.
17911             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
17912             setContentView(window, view);
17913             object_setIvar(view, simpleWindowIvar, cast(id)this);
17914             release(view);
17915 
17916             setBackgroundColor(window, whiteNSColor);
17917             makeKeyAndOrderFront(window, null);
17918         }
17919         void dispose() {
17920             closeWindow();
17921             release(window);
17922         }
17923         void closeWindow() {
17924             invalidate(timer);
17925             .close(window);
17926         }
17927 
17928         ScreenPainter getPainter(bool manualInvalidations) {
17929 		return ScreenPainter(this, this, manualInvalidations);
17930 	}
17931 
17932         id window;
17933         id timer;
17934         id view;
17935         CGContextRef drawingContext;
17936     }
17937 
17938     extern(C) {
17939     private:
17940         BOOL returnTrue3(id self, SEL _cmd, id app) {
17941             return true;
17942         }
17943         BOOL returnTrue2(id self, SEL _cmd) {
17944             return true;
17945         }
17946 
17947         void pulse(id self, SEL _cmd) {
17948             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17949             simpleWindow.handlePulse();
17950             setNeedsDisplay(self, true);
17951         }
17952         void drawRect(id self, SEL _cmd, NSRect rect) {
17953             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17954             auto curCtx = graphicsPort(currentNSGraphicsContext);
17955             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17956             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
17957                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
17958             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17959             CGImageRelease(cgImage);
17960         }
17961         void keyDown(id self, SEL _cmd, id event) {
17962             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17963 
17964             // the event may have multiple characters, and we send them all at
17965             // once.
17966             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
17967                 auto chars = characters(event);
17968                 auto range = CFRange(0, CFStringGetLength(chars));
17969                 auto buffer = new char[range.length*3];
17970                 long actualLength;
17971                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
17972                                  buffer.ptr, cast(int) buffer.length, &actualLength);
17973                 foreach (dchar dc; buffer[0..actualLength]) {
17974                     if (simpleWindow.handleCharEvent)
17975                         simpleWindow.handleCharEvent(dc);
17976 		    // NotYetImplementedException
17977                     //if (simpleWindow.handleKeyEvent)
17978                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
17979                 }
17980             }
17981 
17982             // the event's 'keyCode' is hardware-dependent. I don't think people
17983             // will like it. Let's leave it to the native handler.
17984 
17985             // perform the default action.
17986 
17987 	    // so the default action is to make a bomp sound and i dont want that
17988 	    // sooooooooo yeah not gonna do that.
17989 
17990             //auto superData = objc_super(self, superclass(self));
17991             //alias extern(C) void function(objc_super*, SEL, id) T;
17992             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
17993         }
17994     }
17995 
17996     // initialize the app so that it can be interacted with the user.
17997     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
17998     private void initializeApp() {
17999         // push an autorelease pool to avoid leaking.
18000         init(alloc("NSAutoreleasePool"));
18001 
18002         // create a new NSApp instance
18003         sharedNSApplication;
18004         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
18005 
18006         // create the "Quit" menu.
18007         auto menuBar = init(alloc("NSMenu"));
18008         auto appMenuItem = init(alloc("NSMenuItem"));
18009         addItem(menuBar, appMenuItem);
18010         setMainMenu(NSApp, menuBar);
18011         release(appMenuItem);
18012         release(menuBar);
18013 
18014         auto appMenu = init(alloc("NSMenu"));
18015         auto quitTitle = createCFString("Quit");
18016         auto q = createCFString("q");
18017         auto quitItem = initWithTitle(alloc("NSMenuItem"),
18018                                       quitTitle, sel_registerName("terminate:"), q);
18019         addItem(appMenu, quitItem);
18020         setSubmenu(appMenuItem, appMenu);
18021         release(quitItem);
18022         release(appMenu);
18023         CFRelease(q);
18024         CFRelease(quitTitle);
18025 
18026         // assign a delegate for the application, allow it to quit when the last
18027         // window is closed.
18028         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
18029                                                     "SDWindowCloseDelegate", 0);
18030         class_addMethod(delegateClass,
18031                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
18032                         &returnTrue3, "c@:@");
18033         objc_registerClassPair(delegateClass);
18034 
18035         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
18036         setDelegate(NSApp, appDelegate);
18037         activateIgnoringOtherApps(NSApp, true);
18038 
18039         // create a new view that draws the graphics and respond to keyDown
18040         // events.
18041         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
18042                                                 "SDGraphicsView", (void*).sizeof);
18043         class_addIvar(viewClass, "simpledisplay_simpleWindow",
18044                       (void*).sizeof, (void*).alignof, "^v");
18045         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
18046                         &pulse, "v@:");
18047         class_addMethod(viewClass, sel_registerName("drawRect:"),
18048                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
18049         class_addMethod(viewClass, sel_registerName("isFlipped"),
18050                         &returnTrue2, "c@:");
18051         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
18052                         &returnTrue2, "c@:");
18053         class_addMethod(viewClass, sel_registerName("keyDown:"),
18054                         &keyDown, "v@:@");
18055         objc_registerClassPair(viewClass);
18056         simpleWindowIvar = class_getInstanceVariable(viewClass,
18057                                                      "simpledisplay_simpleWindow");
18058     }
18059 }
18060 
18061 version(without_opengl) {} else
18062 extern(System) nothrow @nogc {
18063 	//enum uint GL_VERSION = 0x1F02;
18064 	//const(char)* glGetString (/*GLenum*/uint);
18065 	version(X11) {
18066 	static if (!SdpyIsUsingIVGLBinds) {
18067 
18068 		enum GLX_X_RENDERABLE = 0x8012;
18069 		enum GLX_DRAWABLE_TYPE = 0x8010;
18070 		enum GLX_RENDER_TYPE = 0x8011;
18071 		enum GLX_X_VISUAL_TYPE = 0x22;
18072 		enum GLX_TRUE_COLOR = 0x8002;
18073 		enum GLX_WINDOW_BIT = 0x00000001;
18074 		enum GLX_RGBA_BIT = 0x00000001;
18075 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18076 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18077 		enum GLX_SAMPLES = 0x186a1;
18078 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18079 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18080 	}
18081 
18082 		// GLX_EXT_swap_control
18083 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18084 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18085 
18086 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18087 		extern(System) {
18088 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18089 		}
18090 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18091 
18092 		// this made public so we don't have to get it again and again
18093 		public bool glXCreateContextAttribsARB_present () {
18094 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18095 				// get it
18096 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18097 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18098 			}
18099 			return (glXCreateContextAttribsARBFn !is null);
18100 		}
18101 
18102 		// this made public so we don't have to get it again and again
18103 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18104 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18105 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18106 		}
18107 
18108 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18109 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18110 
18111 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18112 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18113 			if (_glx_swapInterval_fn is null) {
18114 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18115 				if (_glx_swapInterval_fn is null) {
18116 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18117 					return;
18118 				}
18119 				version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); }
18120 			}
18121 
18122 			if(glXSwapIntervalMESA is null) {
18123 				// it seems to require both to actually take effect on many computers
18124 				// idk why
18125 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18126 				if(glXSwapIntervalMESA is null)
18127 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18128 			}
18129 
18130 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18131 				glXSwapIntervalMESA(wait ? 1 : 0);
18132 
18133 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18134 		}
18135 	} else version(Windows) {
18136 	static if (!SdpyIsUsingIVGLBinds) {
18137 	enum GL_TRUE = 1;
18138 	enum GL_FALSE = 0;
18139 	alias int GLint;
18140 
18141 	public void* glbindGetProcAddress (const(char)* name) {
18142 		void* res = wglGetProcAddress(name);
18143 		if (res is null) {
18144 			/+
18145 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18146 			import core.sys.windows.windef, core.sys.windows.winbase;
18147 			__gshared HINSTANCE dll = null;
18148 			if (dll is null) {
18149 				dll = LoadLibraryA("opengl32.dll");
18150 				if (dll is null) return null; // <32, but idc
18151 			}
18152 			res = GetProcAddress(dll, name);
18153 			+/
18154 			res = GetProcAddress(gl.libHandle, name);
18155 		}
18156 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18157 		return res;
18158 	}
18159 	}
18160 
18161  
18162  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18163         void wglSetVSync(bool wait) {
18164 		if(wglSwapIntervalEXT is null) {
18165 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18166 			if(wglSwapIntervalEXT is null)
18167 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18168 		}
18169 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18170 			return;
18171 
18172 		wglSwapIntervalEXT(wait ? 1 : 0);
18173 	}
18174 
18175 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18176 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18177 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18178 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18179 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18180 
18181 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18182 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18183 
18184 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18185 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18186 
18187 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18188 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18189 
18190 		void wglInitOtherFunctions () {
18191 			if (wglCreateContextAttribsARB is null) {
18192 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18193 			}
18194 		}
18195 	}
18196 
18197 	static if (!SdpyIsUsingIVGLBinds) {
18198 
18199 	interface GL {
18200 		extern(System) @nogc nothrow:
18201 
18202 		void glGetIntegerv(int, void*);
18203 		void glMatrixMode(int);
18204 		void glPushMatrix();
18205 		void glLoadIdentity();
18206 		void glOrtho(double, double, double, double, double, double);
18207 		void glFrustum(double, double, double, double, double, double);
18208 
18209 		void glPopMatrix();
18210 		void glEnable(int);
18211 		void glDisable(int);
18212 		void glClear(int);
18213 		void glBegin(int);
18214 		void glVertex2f(float, float);
18215 		void glVertex3f(float, float, float);
18216 		void glEnd();
18217 		void glColor3b(byte, byte, byte);
18218 		void glColor3ub(ubyte, ubyte, ubyte);
18219 		void glColor4b(byte, byte, byte, byte);
18220 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18221 		void glColor3i(int, int, int);
18222 		void glColor3ui(uint, uint, uint);
18223 		void glColor4i(int, int, int, int);
18224 		void glColor4ui(uint, uint, uint, uint);
18225 		void glColor3f(float, float, float);
18226 		void glColor4f(float, float, float, float);
18227 		void glTranslatef(float, float, float);
18228 		void glScalef(float, float, float);
18229 		version(X11) {
18230 			void glSecondaryColor3b(byte, byte, byte);
18231 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18232 			void glSecondaryColor3i(int, int, int);
18233 			void glSecondaryColor3ui(uint, uint, uint);
18234 			void glSecondaryColor3f(float, float, float);
18235 		}
18236 
18237 		void glDrawElements(int, int, int, void*);
18238 
18239 		void glRotatef(float, float, float, float);
18240 
18241 		uint glGetError();
18242 
18243 		void glDeleteTextures(int, uint*);
18244 
18245 
18246 		void glRasterPos2i(int, int);
18247 		void glDrawPixels(int, int, uint, uint, void*);
18248 		void glClearColor(float, float, float, float);
18249 
18250 
18251 		void glPixelStorei(uint, int);
18252 
18253 		void glGenTextures(uint, uint*);
18254 		void glBindTexture(int, int);
18255 		void glTexParameteri(uint, uint, int);
18256 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18257 		void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
18258 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18259 			/*GLsizei*/int width, /*GLsizei*/int height,
18260 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
18261 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18262 
18263 		void glLineWidth(int);
18264 
18265 
18266 		void glTexCoord2f(float, float);
18267 		void glVertex2i(int, int);
18268 		void glBlendFunc (int, int);
18269 		void glDepthFunc (int);
18270 		void glViewport(int, int, int, int);
18271 
18272 		void glClearDepth(double);
18273 
18274 		void glReadBuffer(uint);
18275 		void glReadPixels(int, int, int, int, int, int, void*);
18276 
18277 		void glFlush();
18278 		void glFinish();
18279 
18280 		version(Windows) {
18281 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18282 			HGLRC wglCreateContext(HDC);
18283 			HGLRC wglCreateLayerContext(HDC, int);
18284 			BOOL wglDeleteContext(HGLRC);
18285 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18286 			HGLRC wglGetCurrentContext();
18287 			HDC wglGetCurrentDC();
18288 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18289 			PROC wglGetProcAddress(LPCSTR);
18290 			BOOL wglMakeCurrent(HDC, HGLRC);
18291 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18292 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18293 			BOOL wglShareLists(HGLRC, HGLRC);
18294 			BOOL wglSwapLayerBuffers(HDC, UINT);
18295 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18296 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18297 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18298 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18299 		}
18300 
18301 	}
18302 
18303 	interface GL3 {
18304 		extern(System) @nogc nothrow:
18305 
18306 		void glGenVertexArrays(GLsizei, GLuint*);
18307 		void glBindVertexArray(GLuint);
18308 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18309 		void glGenerateMipmap(GLenum);
18310 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18311 		void glStencilMask(GLuint);
18312 		void glStencilFunc(GLenum, GLint, GLuint);
18313 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18314 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18315 		GLuint glCreateProgram();
18316 		GLuint glCreateShader(GLenum);
18317 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18318 		void glCompileShader(GLuint);
18319 		void glGetShaderiv(GLuint, GLenum, GLint*);
18320 		void glAttachShader(GLuint, GLuint);
18321 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18322 		void glLinkProgram(GLuint);
18323 		void glGetProgramiv(GLuint, GLenum, GLint*);
18324 		void glDeleteProgram(GLuint);
18325 		void glDeleteShader(GLuint);
18326 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18327 		void glGenBuffers(GLsizei, GLuint*);
18328 
18329 		void glUniform1f(GLint location, GLfloat v0); 
18330 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 
18331 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 
18332 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 
18333 		void glUniform1i(GLint location, GLint v0); 
18334 		void glUniform2i(GLint location, GLint v0, GLint v1); 
18335 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 
18336 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 
18337 		void glUniform1ui(GLint location, GLuint v0); 
18338 		void glUniform2ui(GLint location, GLuint v0, GLuint v1); 
18339 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 
18340 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 
18341 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 
18342 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 
18343 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 
18344 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 
18345 		void glUniform1iv(GLint location, GLsizei count, const GLint *value); 
18346 		void glUniform2iv(GLint location, GLsizei count, const GLint *value); 
18347 		void glUniform3iv(GLint location, GLsizei count, const GLint *value); 
18348 		void glUniform4iv(GLint location, GLsizei count, const GLint *value); 
18349 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 
18350 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 
18351 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 
18352 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18353 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18354 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18355 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18356 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18357 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18358 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18359 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18360 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18361 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18362 
18363 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18364 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18365 		void glDrawArrays(GLenum, GLint, GLsizei);
18366 		void glStencilOp(GLenum, GLenum, GLenum);
18367 		void glUseProgram(GLuint);
18368 		void glCullFace(GLenum);
18369 		void glFrontFace(GLenum);
18370 		void glActiveTexture(GLenum);
18371 		void glBindBuffer(GLenum, GLuint);
18372 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18373 		void glEnableVertexAttribArray(GLuint);
18374 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18375 		void glUniform1i(GLint, GLint);
18376 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18377 		void glDisableVertexAttribArray(GLuint);
18378 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18379 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18380 		void glLogicOp (GLenum opcode);
18381 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18382 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18383 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18384 		GLenum glCheckFramebufferStatus (GLenum target);
18385 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18386 	}
18387 
18388 	interface GL4 {
18389 		extern(System) @nogc nothrow:
18390 
18391 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18392 			/*GLsizei*/int width, /*GLsizei*/int height,
18393 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
18394 	}
18395 
18396 	interface GLU {
18397 		extern(System) @nogc nothrow:
18398 
18399 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18400 		void gluPerspective(double, double, double, double);
18401 
18402 		char* gluErrorString(uint);
18403 	}
18404 
18405 
18406 	enum GL_RED = 0x1903;
18407 	enum GL_ALPHA = 0x1906;
18408 
18409 	enum uint GL_FRONT = 0x0404;
18410 
18411 	enum uint GL_BLEND = 0x0be2;
18412 	enum uint GL_LEQUAL = 0x0203;
18413 
18414 
18415 	enum uint GL_RGB = 0x1907;
18416 	enum uint GL_BGRA = 0x80e1;
18417 	enum uint GL_RGBA = 0x1908;
18418 	enum uint GL_TEXTURE_2D =   0x0DE1;
18419 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18420 	enum uint GL_NEAREST = 0x2600;
18421 	enum uint GL_LINEAR = 0x2601;
18422 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18423 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18424 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18425 	enum uint GL_REPEAT = 0x2901;
18426 	enum uint GL_CLAMP = 0x2900;
18427 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18428 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18429 	enum uint GL_DECAL = 0x2101;
18430 	enum uint GL_MODULATE = 0x2100;
18431 	enum uint GL_TEXTURE_ENV = 0x2300;
18432 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18433 	enum uint GL_REPLACE = 0x1E01;
18434 	enum uint GL_LIGHTING = 0x0B50;
18435 	enum uint GL_DITHER = 0x0BD0;
18436 
18437 	enum uint GL_NO_ERROR = 0;
18438 
18439 
18440 
18441 	enum int GL_VIEWPORT = 0x0BA2;
18442 	enum int GL_MODELVIEW = 0x1700;
18443 	enum int GL_TEXTURE = 0x1702;
18444 	enum int GL_PROJECTION = 0x1701;
18445 	enum int GL_DEPTH_TEST = 0x0B71;
18446 
18447 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18448 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18449 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18450 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18451 
18452 	enum int GL_POINTS = 0x0000;
18453 	enum int GL_LINES =  0x0001;
18454 	enum int GL_LINE_LOOP = 0x0002;
18455 	enum int GL_LINE_STRIP = 0x0003;
18456 	enum int GL_TRIANGLES = 0x0004;
18457 	enum int GL_TRIANGLE_STRIP = 5;
18458 	enum int GL_TRIANGLE_FAN = 6;
18459 	enum int GL_QUADS = 7;
18460 	enum int GL_QUAD_STRIP = 8;
18461 	enum int GL_POLYGON = 9;
18462 
18463 	alias GLvoid = void;
18464 	alias GLboolean = ubyte;
18465 	alias GLuint = uint;
18466 	alias GLenum = uint;
18467 	alias GLchar = char;
18468 	alias GLsizei = int;
18469 	alias GLfloat = float;
18470 	alias GLintptr = size_t;
18471 	alias GLsizeiptr = ptrdiff_t;
18472 
18473 
18474 	enum uint GL_INVALID_ENUM = 0x0500;
18475 
18476 	enum uint GL_ZERO = 0;
18477 	enum uint GL_ONE = 1;
18478 
18479 	enum uint GL_BYTE = 0x1400;
18480 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18481 	enum uint GL_SHORT = 0x1402;
18482 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18483 	enum uint GL_INT = 0x1404;
18484 	enum uint GL_UNSIGNED_INT = 0x1405;
18485 	enum uint GL_FLOAT = 0x1406;
18486 	enum uint GL_2_BYTES = 0x1407;
18487 	enum uint GL_3_BYTES = 0x1408;
18488 	enum uint GL_4_BYTES = 0x1409;
18489 	enum uint GL_DOUBLE = 0x140A;
18490 
18491 	enum uint GL_STREAM_DRAW = 0x88E0;
18492 
18493 	enum uint GL_CCW = 0x0901;
18494 
18495 	enum uint GL_STENCIL_TEST = 0x0B90;
18496 	enum uint GL_SCISSOR_TEST = 0x0C11;
18497 
18498 	enum uint GL_EQUAL = 0x0202;
18499 	enum uint GL_NOTEQUAL = 0x0205;
18500 
18501 	enum uint GL_ALWAYS = 0x0207;
18502 	enum uint GL_KEEP = 0x1E00;
18503 
18504 	enum uint GL_INCR = 0x1E02;
18505 
18506 	enum uint GL_INCR_WRAP = 0x8507;
18507 	enum uint GL_DECR_WRAP = 0x8508;
18508 
18509 	enum uint GL_CULL_FACE = 0x0B44;
18510 	enum uint GL_BACK = 0x0405;
18511 
18512 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18513 	enum uint GL_VERTEX_SHADER = 0x8B31;
18514 
18515 	enum uint GL_COMPILE_STATUS = 0x8B81;
18516 	enum uint GL_LINK_STATUS = 0x8B82;
18517 
18518 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18519 
18520 	enum uint GL_STATIC_DRAW = 0x88E4;
18521 
18522 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18523 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18524 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18525 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18526 
18527 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18528 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18529 
18530 	enum uint GL_TEXTURE0 = 0x84C0U;
18531 	enum uint GL_TEXTURE1 = 0x84C1U;
18532 
18533 	enum uint GL_ARRAY_BUFFER = 0x8892;
18534 
18535 	enum uint GL_SRC_COLOR = 0x0300;
18536 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18537 	enum uint GL_SRC_ALPHA = 0x0302;
18538 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18539 	enum uint GL_DST_ALPHA = 0x0304;
18540 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18541 	enum uint GL_DST_COLOR = 0x0306;
18542 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18543 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18544 
18545 	enum uint GL_INVERT = 0x150AU;
18546 
18547 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18548 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18549 
18550 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18551 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18552 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18553 
18554 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18555 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18556 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18557 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18558 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18559 
18560 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18561 	enum uint GL_CLEAR = 0x1500U;
18562 	enum uint GL_COPY = 0x1503U;
18563 	enum uint GL_XOR = 0x1506U;
18564 
18565 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18566 
18567 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18568 
18569 	}
18570 }
18571 
18572 /++
18573 	History:
18574 		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.
18575 +/
18576 __gshared bool gluSuccessfullyLoaded = true;
18577 
18578 version(without_opengl) {} else {
18579 static if(!SdpyIsUsingIVGLBinds) {
18580 	version(Windows) {
18581 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18582 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18583 	} else {
18584 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18585 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18586 	}
18587 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18588 
18589 
18590 	shared static this() {
18591 		gl.loadDynamicLibrary();
18592 
18593 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18594 		// unless those functions are actually used
18595 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18596 		glu.loadDynamicLibrary();
18597 	}
18598 }
18599 }
18600 
18601 /++
18602 	Convenience method for converting D arrays to opengl buffer data
18603 
18604 	I would LOVE to overload it with the original glBufferData, but D won't
18605 	let me since glBufferData is a function pointer :(
18606 
18607 	Added: August 25, 2020 (version 8.5)
18608 +/
18609 version(without_opengl) {} else
18610 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18611 	glBufferData(target, data.length, data.ptr, usage);
18612 }
18613 
18614 /+
18615 /++
18616 	A matrix for simple uses that easily integrates with [OpenGlShader].
18617 
18618 	Might not be useful to you since it only as some simple functions and
18619 	probably isn't that fast.
18620 
18621 	Note it uses an inline static array for its storage, so copying it
18622 	may be expensive.
18623 +/
18624 struct BasicMatrix(int columns, int rows, T = float) {
18625 	import core.stdc.math;
18626 
18627 	T[columns * rows] data = 0.0;
18628 
18629 	/++
18630 		Basic operations that operate *in place*.
18631 	+/
18632 	void translate() {
18633 
18634 	}
18635 
18636 	/// ditto
18637 	void scale() {
18638 
18639 	}
18640 
18641 	/// ditto
18642 	void rotate() {
18643 
18644 	}
18645 
18646 	/++
18647 
18648 	+/
18649 	static if(columns == rows)
18650 	static BasicMatrix identity() {
18651 		BasicMatrix m;
18652 		foreach(i; 0 .. columns)
18653 			data[0 + i + i * columns] = 1.0;
18654 		return m;
18655 	}
18656 
18657 	static BasicMatrix ortho() {
18658 		return BasicMatrix.init;
18659 	}
18660 }
18661 +/
18662 
18663 /++
18664 	Convenience class for using opengl shaders.
18665 
18666 	Ensure that you've loaded opengl 3+ and set your active
18667 	context before trying to use this.
18668 
18669 	Added: August 25, 2020 (version 8.5)
18670 +/
18671 version(without_opengl) {} else
18672 final class OpenGlShader {
18673 	private int shaderProgram_;
18674 	private @property void shaderProgram(int a) {
18675 		shaderProgram_ = a;
18676 	}
18677 	/// Get the program ID for use in OpenGL functions.
18678 	public @property int shaderProgram() {
18679 		return shaderProgram_;
18680 	}
18681 
18682 	/++
18683 
18684 	+/
18685 	static struct Source {
18686 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
18687 		string code; ///
18688 	}
18689 
18690 	/++
18691 		Helper method to just compile some shader code and check for errors
18692 		while you do glCreateShader, etc. on the outside yourself.
18693 
18694 		This just does `glShaderSource` and `glCompileShader` for the given code.
18695 
18696 		If you the OpenGlShader class constructor, you never need to call this yourself.
18697 	+/
18698 	static void compile(int sid, Source code) {
18699 		const(char)*[1] buffer;
18700 		int[1] lengthBuffer;
18701 
18702 		buffer[0] = code.code.ptr;
18703 		lengthBuffer[0] = cast(int) code.code.length;
18704 
18705 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
18706 		glCompileShader(sid);
18707 
18708 		int success;
18709 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
18710 		if(!success) {
18711 			char[512] info;
18712 			int len;
18713 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
18714 
18715 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
18716 		}
18717 	}
18718 
18719 	/++
18720 		Calls `glLinkProgram` and throws if error a occurs.
18721 
18722 		If you the OpenGlShader class constructor, you never need to call this yourself.
18723 	+/
18724 	static void link(int shaderProgram) {
18725 		glLinkProgram(shaderProgram);
18726 		int success;
18727 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
18728 		if(!success) {
18729 			char[512] info;
18730 			int len;
18731 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
18732 
18733 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
18734 		}
18735 	}
18736 
18737 	/++
18738 		Constructs the shader object by calling `glCreateProgram`, then
18739 		compiling each given [Source], and finally, linking them together.
18740 
18741 		Throws: on compile or link failure.
18742 	+/
18743 	this(Source[] codes...) {
18744 		shaderProgram = glCreateProgram();
18745 
18746 		int[16] shadersBufferStack;
18747 
18748 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 
18749 			shadersBufferStack[0 .. codes.length] :
18750 			new int[](codes.length);
18751 
18752 		foreach(idx, code; codes) {
18753 			shadersBuffer[idx] = glCreateShader(code.type);
18754 
18755 			compile(shadersBuffer[idx], code);
18756 
18757 			glAttachShader(shaderProgram, shadersBuffer[idx]);
18758 		}
18759 
18760 		link(shaderProgram);
18761 
18762 		foreach(s; shadersBuffer)
18763 			glDeleteShader(s);
18764 	}
18765 
18766 	/// Calls `glUseProgram(this.shaderProgram)`
18767 	void use() {
18768 		glUseProgram(this.shaderProgram);
18769 	}
18770 
18771 	/// Deletes the program.
18772 	void delete_() {
18773 		glDeleteProgram(shaderProgram);
18774 		shaderProgram = 0;
18775 	}
18776 
18777 	/++
18778 		[OpenGlShader.uniforms].name gives you one of these.
18779 
18780 		You can get the id out of it or just assign
18781 	+/
18782 	static struct Uniform {
18783 		/// the id passed to glUniform*
18784 		int id;
18785 
18786 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
18787 		void opAssign(float x, float y, float z, float w) {
18788 			if(id != -1)
18789 			glUniform4f(id, x, y, z, w);
18790 		}
18791 
18792 		void opAssign(float x) {
18793 			if(id != -1)
18794 			glUniform1f(id, x);
18795 		}
18796 
18797 		void opAssign(float x, float y) {
18798 			if(id != -1)
18799 			glUniform2f(id, x, y);
18800 		}
18801 
18802 		void opAssign(T)(T t) {
18803 			t.glUniform(id);
18804 		}
18805 	}
18806 
18807 	static struct UniformsHelper {
18808 		OpenGlShader _shader;
18809 
18810 		@property Uniform opDispatch(string name)() {
18811 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
18812 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
18813 			//if(i == -1)
18814 				//throw new Exception("Could not find uniform " ~ name);
18815 			return Uniform(i);
18816 		}
18817 
18818 		@property void opDispatch(string name, T)(T t) {
18819 			Uniform f = this.opDispatch!name;
18820 			t.glUniform(f);
18821 		}
18822 	}
18823 
18824 	/++
18825 		Gives access to the uniforms through dot access.
18826 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
18827 	+/
18828 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
18829 }
18830 
18831 version(without_opengl) {} else {
18832 /++
18833 	A static container of experimental types and value constructors for opengl 3+ shaders.
18834 
18835 
18836 	You can declare variables like:
18837 
18838 	```
18839 	OGL.vec3f something;
18840 	```
18841 
18842 	But generally it would be used with [OpenGlShader]'s uniform helpers like
18843 
18844 	```
18845 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
18846 	```
18847 
18848 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
18849 
18850 
18851 	History:
18852 		Added December 7, 2021. Not yet stable.
18853 +/
18854 final class OGL {
18855 	static:
18856 
18857 	private template typeFromSpecifier(string specifier) {
18858 		static if(specifier == "f")
18859 			alias typeFromSpecifier = GLfloat;
18860 		else static if(specifier == "i")
18861 			alias typeFromSpecifier = GLint;
18862 		else static if(specifier == "ui")
18863 			alias typeFromSpecifier = GLuint;
18864 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
18865 	}
18866 
18867 	private template CommonType(T...) {
18868 		static if(T.length == 1)
18869 			alias CommonType = T[0];
18870 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
18871 			alias CommonType = CommonType!(C, T[2 .. $]);
18872 	}
18873 
18874 	private template typesToSpecifier(T...) {
18875 		static if(is(CommonType!T == float))
18876 			enum typesToSpecifier = "f";
18877 		else static if(is(CommonType!T == int))
18878 			enum typesToSpecifier = "i";
18879 		else static if(is(CommonType!T == uint))
18880 			enum typesToSpecifier = "ui";
18881 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
18882 	}
18883 
18884 	private template genNames(size_t dim, size_t dim2 = 0) {
18885 		string helper() {
18886 			string s;
18887 			if(dim2) {
18888 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
18889 			} else {
18890 				if(dim > 0) s ~= "type x = 0;";
18891 				if(dim > 1) s ~= "type y = 0;";
18892 				if(dim > 2) s ~= "type z = 0;";
18893 				if(dim > 3) s ~= "type w = 0;";
18894 			}
18895 			return s;
18896 		}
18897 
18898 		enum genNames = helper();
18899 	}
18900 
18901 	// there's vec, arrays of vec, mat, and arrays of mat
18902 	template opDispatch(string name)
18903 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
18904 	{
18905 		static if(name[4] == 'x') {
18906 			enum dimX = cast(int) (name[3] - '0');
18907 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
18908 
18909 			enum dimY = cast(int) (name[5] - '0');
18910 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
18911 
18912 			enum isArray = name[$ - 1] == 'v';
18913 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
18914 			alias type = typeFromSpecifier!typeSpecifier;
18915 		} else {
18916 			enum dim = cast(int) (name[3] - '0');
18917 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
18918 			enum isArray = name[$ - 1] == 'v';
18919 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
18920 			alias type = typeFromSpecifier!typeSpecifier;
18921 		}
18922 
18923 		align(1)
18924 		struct opDispatch {
18925 			align(1):
18926 			static if(name[4] == 'x')
18927 				mixin(genNames!(dimX, dimY));
18928 			else
18929 				mixin(genNames!dim);
18930 
18931 			private void glUniform(OpenGlShader.Uniform assignTo) {
18932 				glUniform(assignTo.id);
18933 			}
18934 			private void glUniform(int assignTo) {
18935 				static if(name[4] == 'x') {
18936 					// FIXME
18937 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
18938 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
18939 				} else
18940 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
18941 			}
18942 		}
18943 	}
18944 
18945 	auto vec(T...)(T members) {
18946 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
18947 	}
18948 }
18949 }
18950 
18951 version(linux) {
18952 	version(with_eventloop) {} else {
18953 		private int epollFd = -1;
18954 		void prepareEventLoop() {
18955 			if(epollFd != -1)
18956 				return; // already initialized, no need to do it again
18957 			import ep = core.sys.linux.epoll;
18958 
18959 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
18960 			if(epollFd == -1)
18961 				throw new Exception("epoll create failure");
18962 		}
18963 	}
18964 } else version(Posix) {
18965 	void prepareEventLoop() {}
18966 }
18967 
18968 version(X11) {
18969 	import core.stdc.locale : LC_ALL; // rdmd fix
18970 	__gshared bool sdx_isUTF8Locale;
18971 
18972 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
18973 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
18974 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
18975 	// anal magic is here. I (Ketmar) hope you like it.
18976 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
18977 	// always return correct unicode symbols. The detection is here 'cause user can change locale
18978 	// later.
18979 
18980 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
18981 	shared static this () {
18982 		if(!librariesSuccessfullyLoaded)
18983 			return;
18984 
18985 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
18986 
18987 		// this doesn't hurt; it may add some locking, but the speed is still
18988 		// allows doing 60 FPS videogames; also, ignore the result, as most
18989 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
18990 		// never seen this failing).
18991 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
18992 
18993 		setlocale(LC_ALL, "");
18994 		// check if out locale is UTF-8
18995 		auto lct = setlocale(LC_CTYPE, null);
18996 		if (lct is null) {
18997 			sdx_isUTF8Locale = false;
18998 		} else {
18999 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19000 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19001 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19002 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19003 				{
19004 					sdx_isUTF8Locale = true;
19005 					break;
19006 				}
19007 			}
19008 		}
19009 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19010 	}
19011 }
19012 
19013 class ExperimentalTextComponent2 {
19014 	/+
19015 		Stage 1: get it working monospace
19016 		Stage 2: use proportional font
19017 		Stage 3: allow changes in inline style
19018 		Stage 4: allow new fonts and sizes in the middle
19019 		Stage 5: optimize gap buffer
19020 		Stage 6: optimize layout
19021 		Stage 7: word wrap
19022 		Stage 8: justification
19023 		Stage 9: editing, selection, etc.
19024 
19025 			Operations:
19026 				insert text
19027 				overstrike text
19028 				select
19029 				cut
19030 				modify
19031 	+/
19032 
19033 	/++
19034 		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.
19035 	+/
19036 	this(SimpleWindow window) {
19037 		this.window = window;
19038 	}
19039 
19040 	private SimpleWindow window;
19041 
19042 
19043 	/++
19044 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19045 		representing the internal parts. The first pass is focused on the x parameter, then the
19046 		renderer is responsible for going back to the parts in the current line and calling
19047 		adjustDownForAscent to change the y params.
19048 	+/
19049 	static interface ComponentRenderHelper {
19050 
19051 		/+
19052 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19053 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19054 			to move (adjust y to make room for new line) until you get back to the same position,
19055 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19056 
19057 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19058 			once you reach something that is unchanged, you can stop.
19059 		+/
19060 
19061 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19062 
19063 		int ascent() const;
19064 		int descent() const;
19065 
19066 		int advance() const;
19067 
19068 		bool endsWithExplititLineBreak() const;
19069 	}
19070 
19071 	static interface RenderResult {
19072 		/++
19073 			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.
19074 		+/
19075 		void popFront();
19076 		@property bool empty() const;
19077 		@property ComponentRenderHelper front() const;
19078 
19079 		void repositionForNextLine(Point baseline, int availableWidth);
19080 	}
19081 
19082 	static interface ComponentInFlow {
19083 		void draw(ScreenPainter painter);
19084 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19085 
19086 		bool startsWithExplicitLineBreak() const;
19087 	}
19088 
19089 	static class TextFlowComponent : ComponentInFlow {
19090 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19091 
19092 		Color foreground;
19093 		Color background;
19094 
19095 		OperatingSystemFont font; // should NEVER be null
19096 
19097 		ubyte attributes; // underline, strike through, display on new block
19098 
19099 		version(Windows)
19100 			const(wchar)[] content;
19101 		else
19102 			const(char)[] content; // this should NEVER have a newline, except at the end
19103 
19104 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19105 
19106 		// could prolly put some spacing around it too like margin / padding
19107 
19108 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19109 			in { assert(font !is null);
19110 			     assert(!font.isNull); }
19111 			do
19112 		{
19113 			this.foreground = f;
19114 			this.background = b;
19115 			this.font = font;
19116 
19117 			this.attributes = attr;
19118 			version(Windows) {
19119 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19120 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19121 				auto buffer = new wchar[](sz);
19122 				this.content = makeWindowsString(c, buffer, conversionFlags);
19123 			} else {
19124 				this.content = c.dup;
19125 			}
19126 		}
19127 
19128 		void draw(ScreenPainter painter) {
19129 			painter.setFont(this.font);
19130 			painter.outlineColor = this.foreground;
19131 			painter.fillColor = Color.transparent;
19132 			foreach(rendered; this.rendered) {
19133 				// the component works in term of baseline,
19134 				// but the painter works in term of upper left bounding box
19135 				// so need to translate that
19136 
19137 				if(this.background.a) {
19138 					painter.fillColor = this.background;
19139 					painter.outlineColor = this.background;
19140 
19141 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19142 
19143 					painter.outlineColor = this.foreground;
19144 					painter.fillColor = Color.transparent;
19145 				}
19146 
19147 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19148 
19149 				// FIXME: strike through, underline, highlight selection, etc.
19150 			}
19151 		}
19152 	}
19153 
19154 	// I could split the parts into words on render
19155 	// for easier word-wrap, each one being an unbreakable "inline-block"
19156 	private TextFlowComponent[] parts;
19157 	private int needsRerenderFrom;
19158 
19159 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19160 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19161 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19162 	}
19163 
19164 	static struct RenderedComponent {
19165 		int startX;
19166 		int startY;
19167 		short width;
19168 		// 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!
19169 		// for individual chars in here you've gotta process on demand
19170 		version(Windows)
19171 			const(wchar)[] slice;
19172 		else
19173 			const(char)[] slice;
19174 	}
19175 
19176 
19177 	void rerender(Rectangle boundingBox) {
19178 		Point baseline = boundingBox.upperLeft;
19179 
19180 		this.boundingBox.left = boundingBox.left;
19181 		this.boundingBox.top = boundingBox.top;
19182 
19183 		auto remainingParts = parts;
19184 
19185 		int largestX;
19186 
19187 
19188 		foreach(part; parts)
19189 			part.font.prepareContext(window);
19190 		scope(exit)
19191 		foreach(part; parts)
19192 			part.font.releaseContext();
19193 
19194 		calculateNextLine:
19195 
19196 		int nextLineHeight = 0;
19197 		int nextBiggestDescent = 0;
19198 
19199 		foreach(part; remainingParts) {
19200 			auto height = part.font.ascent;
19201 			if(height > nextLineHeight)
19202 				nextLineHeight = height;
19203 			if(part.font.descent > nextBiggestDescent)
19204 				nextBiggestDescent = part.font.descent;
19205 			if(part.content.length && part.content[$-1] == '\n')
19206 				break;
19207 		}
19208 
19209 		baseline.y += nextLineHeight;
19210 		auto lineStart = baseline;
19211 
19212 		while(remainingParts.length) {
19213 			remainingParts[0].rendered = null;
19214 
19215 			bool eol;
19216 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19217 				eol = true;
19218 
19219 			// FIXME: word wrap
19220 			auto font = remainingParts[0].font;
19221 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19222 			auto width = font.stringWidth(slice, window);
19223 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19224 
19225 			remainingParts = remainingParts[1 .. $];
19226 			baseline.x += width;
19227 
19228 			if(eol) {
19229 				baseline.y += nextBiggestDescent;
19230 				if(baseline.x > largestX)
19231 					largestX = baseline.x;
19232 				baseline.x = lineStart.x;
19233 				goto calculateNextLine;
19234 			}
19235 		}
19236 
19237 		if(baseline.x > largestX)
19238 			largestX = baseline.x;
19239 
19240 		this.boundingBox.right = largestX;
19241 		this.boundingBox.bottom = baseline.y;
19242 	}
19243 
19244 	// you must call rerender first!
19245 	void draw(ScreenPainter painter) {
19246 		foreach(part; parts) {
19247 			part.draw(painter);
19248 		}
19249 	}
19250 
19251 	struct IdentifyResult {
19252 		TextFlowComponent part;
19253 		int charIndexInPart;
19254 		int totalCharIndex = -1; // if this is -1, it just means the end
19255 
19256 		Rectangle boundingBox;
19257 	}
19258 
19259 	IdentifyResult identify(Point pt, bool exact = false) {
19260 		if(parts.length == 0)
19261 			return IdentifyResult(null, 0);
19262 
19263 		if(pt.y < boundingBox.top) {
19264 			if(exact)
19265 				return IdentifyResult(null, 1);
19266 			return IdentifyResult(parts[0], 0);
19267 		}
19268 		if(pt.y > boundingBox.bottom) {
19269 			if(exact)
19270 				return IdentifyResult(null, 2);
19271 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19272 		}
19273 
19274 		int tci = 0;
19275 
19276 		// I should probably like binary search this or something...
19277 		foreach(ref part; parts) {
19278 			foreach(rendered; part.rendered) {
19279 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19280 				if(rect.contains(pt)) {
19281 					auto x = pt.x - rendered.startX;
19282 					auto estimatedIdx = x / part.font.averageWidth;
19283 
19284 					if(estimatedIdx < 0)
19285 						estimatedIdx = 0;
19286 
19287 					if(estimatedIdx > rendered.slice.length)
19288 						estimatedIdx = cast(int) rendered.slice.length;
19289 
19290 					int idx;
19291 					int x1, x2;
19292 					if(part.font.isMonospace) {
19293 						auto w = part.font.averageWidth;
19294 						if(!exact && x > (estimatedIdx + 1) * w)
19295 							return IdentifyResult(null, 4);
19296 						idx = estimatedIdx;
19297 						x1 = idx * w;
19298 						x2 = (idx + 1) * w;
19299 					} else {
19300 						idx = estimatedIdx;
19301 
19302 						part.font.prepareContext(window);
19303 						scope(exit) part.font.releaseContext();
19304 
19305 						// int iterations;
19306 
19307 						while(true) {
19308 							// iterations++;
19309 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19310 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19311 
19312 							x1 += rendered.startX;
19313 							x2 += rendered.startX;
19314 
19315 							if(pt.x < x1) {
19316 								if(idx == 0) {
19317 									if(exact)
19318 										return IdentifyResult(null, 6);
19319 									else
19320 										break;
19321 								}
19322 								idx--;
19323 							} else if(pt.x > x2) {
19324 								idx++;
19325 								if(idx > rendered.slice.length) {
19326 									if(exact)
19327 										return IdentifyResult(null, 5);
19328 									else
19329 										break;
19330 								}
19331 							} else if(pt.x >= x1 && pt.x <= x2) {
19332 								if(idx)
19333 									idx--; // point it at the original index
19334 								break; // we fit
19335 							}
19336 						}
19337 
19338 						// import std.stdio; writeln(iterations)
19339 					}
19340 
19341 
19342 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19343 				}
19344 			}
19345 			tci += cast(int) part.content.length; // FIXME: utf-8?
19346 		}
19347 		return IdentifyResult(null, 3);
19348 	}
19349 
19350 	Rectangle boundingBox; // only set after [rerender]
19351 
19352 	// text will be positioned around the exclusion zone
19353 	static struct ExclusionZone {
19354 
19355 	}
19356 
19357 	ExclusionZone[] exclusionZones;
19358 }
19359 
19360 
19361 // Don't use this yet. When I'm happy with it, I will move it to the
19362 // regular module namespace.
19363 mixin template ExperimentalTextComponent() {
19364 
19365 static:
19366 
19367 	alias Rectangle = arsd.color.Rectangle;
19368 
19369 	struct ForegroundColor {
19370 		Color color;
19371 		alias color this;
19372 
19373 		this(Color c) {
19374 			color = c;
19375 		}
19376 
19377 		this(int r, int g, int b, int a = 255) {
19378 			color = Color(r, g, b, a);
19379 		}
19380 
19381 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19382 			return ForegroundColor(mixin("Color." ~ s));
19383 		}
19384 	}
19385 
19386 	struct BackgroundColor {
19387 		Color color;
19388 		alias color this;
19389 
19390 		this(Color c) {
19391 			color = c;
19392 		}
19393 
19394 		this(int r, int g, int b, int a = 255) {
19395 			color = Color(r, g, b, a);
19396 		}
19397 
19398 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19399 			return BackgroundColor(mixin("Color." ~ s));
19400 		}
19401 	}
19402 
19403 	static class InlineElement {
19404 		string text;
19405 
19406 		BlockElement containingBlock;
19407 
19408 		Color color = Color.black;
19409 		Color backgroundColor = Color.transparent;
19410 		ushort styles;
19411 
19412 		string font;
19413 		int fontSize;
19414 
19415 		int lineHeight;
19416 
19417 		void* identifier;
19418 
19419 		Rectangle boundingBox;
19420 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19421 
19422 		bool isMergeCompatible(InlineElement other) {
19423 			return
19424 				containingBlock is other.containingBlock &&
19425 				color == other.color &&
19426 				backgroundColor == other.backgroundColor &&
19427 				styles == other.styles &&
19428 				font == other.font &&
19429 				fontSize == other.fontSize &&
19430 				lineHeight == other.lineHeight &&
19431 				true;
19432 		}
19433 
19434 		int xOfIndex(size_t index) {
19435 			if(index < letterXs.length)
19436 				return letterXs[index];
19437 			else
19438 				return boundingBox.right;
19439 		}
19440 
19441 		InlineElement clone() {
19442 			auto ie = new InlineElement();
19443 			ie.tupleof = this.tupleof;
19444 			return ie;
19445 		}
19446 
19447 		InlineElement getPreviousInlineElement() {
19448 			InlineElement prev = null;
19449 			foreach(ie; this.containingBlock.parts) {
19450 				if(ie is this)
19451 					break;
19452 				prev = ie;
19453 			}
19454 			if(prev is null) {
19455 				BlockElement pb;
19456 				BlockElement cb = this.containingBlock;
19457 				moar:
19458 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19459 					if(ie is cb)
19460 						break;
19461 					pb = ie;
19462 				}
19463 				if(pb is null)
19464 					return null;
19465 				if(pb.parts.length == 0) {
19466 					cb = pb;
19467 					goto moar;
19468 				}
19469 
19470 				prev = pb.parts[$-1];
19471 
19472 			}
19473 			return prev;
19474 		}
19475 
19476 		InlineElement getNextInlineElement() {
19477 			InlineElement next = null;
19478 			foreach(idx, ie; this.containingBlock.parts) {
19479 				if(ie is this) {
19480 					if(idx + 1 < this.containingBlock.parts.length)
19481 						next = this.containingBlock.parts[idx + 1];
19482 					break;
19483 				}
19484 			}
19485 			if(next is null) {
19486 				BlockElement n;
19487 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19488 					if(ie is this.containingBlock) {
19489 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19490 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19491 						break;
19492 					}
19493 				}
19494 				if(n is null)
19495 					return null;
19496 
19497 				if(n.parts.length)
19498 					next = n.parts[0];
19499 				else {} // FIXME
19500 
19501 			}
19502 			return next;
19503 		}
19504 
19505 	}
19506 
19507 	// Block elements are used entirely for positioning inline elements,
19508 	// which are the things that are actually drawn.
19509 	class BlockElement {
19510 		InlineElement[] parts;
19511 		uint alignment;
19512 
19513 		int whiteSpace; // pre, pre-wrap, wrap
19514 
19515 		TextLayout containingLayout;
19516 
19517 		// inputs
19518 		Point where;
19519 		Size minimumSize;
19520 		Size maximumSize;
19521 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19522 		void* identifier;
19523 
19524 		Rectangle margin;
19525 		Rectangle padding;
19526 
19527 		// outputs
19528 		Rectangle[] boundingBoxes;
19529 	}
19530 
19531 	struct TextIdentifyResult {
19532 		InlineElement element;
19533 		int offset;
19534 
19535 		private TextIdentifyResult fixupNewline() {
19536 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19537 				offset--;
19538 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19539 				offset--;
19540 			}
19541 			return this;
19542 		}
19543 	}
19544 
19545 	class TextLayout {
19546 		BlockElement[] blocks;
19547 		Rectangle boundingBox_;
19548 		Rectangle boundingBox() { return boundingBox_; }
19549 		void boundingBox(Rectangle r) {
19550 			if(r != boundingBox_) {
19551 				boundingBox_ = r;
19552 				layoutInvalidated = true;
19553 			}
19554 		}
19555 
19556 		Rectangle contentBoundingBox() {
19557 			Rectangle r;
19558 			foreach(block; blocks)
19559 			foreach(ie; block.parts) {
19560 				if(ie.boundingBox.right > r.right)
19561 					r.right = ie.boundingBox.right;
19562 				if(ie.boundingBox.bottom > r.bottom)
19563 					r.bottom = ie.boundingBox.bottom;
19564 			}
19565 			return r;
19566 		}
19567 
19568 		BlockElement[] getBlocks() {
19569 			return blocks;
19570 		}
19571 
19572 		InlineElement[] getTexts() {
19573 			InlineElement[] elements;
19574 			foreach(block; blocks)
19575 				elements ~= block.parts;
19576 			return elements;
19577 		}
19578 
19579 		string getPlainText() {
19580 			string text;
19581 			foreach(block; blocks)
19582 				foreach(part; block.parts)
19583 					text ~= part.text;
19584 			return text;
19585 		}
19586 
19587 		string getHtml() {
19588 			return null; // FIXME
19589 		}
19590 
19591 		this(Rectangle boundingBox) {
19592 			this.boundingBox = boundingBox;
19593 		}
19594 
19595 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19596 			auto be = new BlockElement();
19597 			be.containingLayout = this;
19598 			if(after is null)
19599 				blocks ~= be;
19600 			else {
19601 				foreach(idx, b; blocks) {
19602 					if(b is after.containingBlock) {
19603 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19604 						break;
19605 					}
19606 				}
19607 			}
19608 			return be;
19609 		}
19610 
19611 		void clear() {
19612 			blocks = null;
19613 			selectionStart = selectionEnd = caret = Caret.init;
19614 		}
19615 
19616 		void addText(Args...)(Args args) {
19617 			if(blocks.length == 0)
19618 				addBlock();
19619 
19620 			InlineElement ie = new InlineElement();
19621 			foreach(idx, arg; args) {
19622 				static if(is(typeof(arg) == ForegroundColor))
19623 					ie.color = arg;
19624 				else static if(is(typeof(arg) == TextFormat)) {
19625 					if(arg & 0x8000) // ~TextFormat.something turns it off
19626 						ie.styles &= arg;
19627 					else
19628 						ie.styles |= arg;
19629 				} else static if(is(typeof(arg) == string)) {
19630 					static if(idx == 0 && args.length > 1)
19631 						static assert(0, "Put styles before the string.");
19632 					size_t lastLineIndex;
19633 					foreach(cidx, char a; arg) {
19634 						if(a == '\n') {
19635 							ie.text = arg[lastLineIndex .. cidx + 1];
19636 							lastLineIndex = cidx + 1;
19637 							ie.containingBlock = blocks[$-1];
19638 							blocks[$-1].parts ~= ie.clone;
19639 							ie.text = null;
19640 						} else {
19641 
19642 						}
19643 					}
19644 
19645 					ie.text = arg[lastLineIndex .. $];
19646 					ie.containingBlock = blocks[$-1];
19647 					blocks[$-1].parts ~= ie.clone;
19648 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
19649 				}
19650 			}
19651 
19652 			invalidateLayout();
19653 		}
19654 
19655 		void tryMerge(InlineElement into, InlineElement what) {
19656 			if(!into.isMergeCompatible(what)) {
19657 				return; // cannot merge, different configs
19658 			}
19659 
19660 			// cool, can merge, bring text together...
19661 			into.text ~= what.text;
19662 
19663 			// and remove what
19664 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
19665 				if(what.containingBlock.parts[a] is what) {
19666 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
19667 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
19668 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
19669 
19670 				}
19671 			}
19672 
19673 			// FIXME: ensure no other carets have a reference to it
19674 		}
19675 
19676 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
19677 		TextIdentifyResult identify(int x, int y, bool exact = false) {
19678 			TextIdentifyResult inexactMatch;
19679 			foreach(block; blocks) {
19680 				foreach(part; block.parts) {
19681 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
19682 
19683 						// FIXME binary search
19684 						int tidx;
19685 						int lastX;
19686 						foreach_reverse(idxo, lx; part.letterXs) {
19687 							int idx = cast(int) idxo;
19688 							if(lx <= x) {
19689 								if(lastX && lastX - x < x - lx)
19690 									tidx = idx + 1;
19691 								else
19692 									tidx = idx;
19693 								break;
19694 							}
19695 							lastX = lx;
19696 						}
19697 
19698 						return TextIdentifyResult(part, tidx).fixupNewline;
19699 					} else if(!exact) {
19700 						// we're not in the box, but are we on the same line?
19701 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
19702 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
19703 					}
19704 				}
19705 			}
19706 
19707 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
19708 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
19709 
19710 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
19711 		}
19712 
19713 		void moveCaretToPixelCoordinates(int x, int y) {
19714 			auto result = identify(x, y);
19715 			caret.inlineElement = result.element;
19716 			caret.offset = result.offset;
19717 		}
19718 
19719 		void selectToPixelCoordinates(int x, int y) {
19720 			auto result = identify(x, y);
19721 
19722 			if(y < caretLastDrawnY1) {
19723 				// on a previous line, carat is selectionEnd
19724 				selectionEnd = caret;
19725 
19726 				selectionStart = Caret(this, result.element, result.offset);
19727 			} else if(y > caretLastDrawnY2) {
19728 				// on a later line
19729 				selectionStart = caret;
19730 
19731 				selectionEnd = Caret(this, result.element, result.offset);
19732 			} else {
19733 				// on the same line...
19734 				if(x <= caretLastDrawnX) {
19735 					selectionEnd = caret;
19736 					selectionStart = Caret(this, result.element, result.offset);
19737 				} else {
19738 					selectionStart = caret;
19739 					selectionEnd = Caret(this, result.element, result.offset);
19740 				}
19741 
19742 			}
19743 		}
19744 
19745 
19746 		/// Call this if the inputs change. It will reflow everything
19747 		void redoLayout(ScreenPainter painter) {
19748 			//painter.setClipRectangle(boundingBox);
19749 			auto pos = Point(boundingBox.left, boundingBox.top);
19750 
19751 			int lastHeight;
19752 			void nl() {
19753 				pos.x = boundingBox.left;
19754 				pos.y += lastHeight;
19755 			}
19756 			foreach(block; blocks) {
19757 				nl();
19758 				foreach(part; block.parts) {
19759 					part.letterXs = null;
19760 
19761 					auto size = painter.textSize(part.text);
19762 					version(Windows)
19763 						if(part.text.length && part.text[$-1] == '\n')
19764 							size.height /= 2; // windows counts the new line at the end, but we don't want that
19765 
19766 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
19767 
19768 					foreach(idx, char c; part.text) {
19769 							// FIXME: unicode
19770 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
19771 					}
19772 
19773 					pos.x += size.width;
19774 					if(pos.x >= boundingBox.right) {
19775 						pos.y += size.height;
19776 						pos.x = boundingBox.left;
19777 						lastHeight = 0;
19778 					} else {
19779 						lastHeight = size.height;
19780 					}
19781 
19782 					if(part.text.length && part.text[$-1] == '\n')
19783 						nl();
19784 				}
19785 			}
19786 
19787 			layoutInvalidated = false;
19788 		}
19789 
19790 		bool layoutInvalidated = true;
19791 		void invalidateLayout() {
19792 			layoutInvalidated = true;
19793 		}
19794 
19795 // FIXME: caret can remain sometimes when inserting
19796 // FIXME: inserting at the beginning once you already have something can eff it up.
19797 		void drawInto(ScreenPainter painter, bool focused = false) {
19798 			if(layoutInvalidated)
19799 				redoLayout(painter);
19800 			foreach(block; blocks) {
19801 				foreach(part; block.parts) {
19802 					painter.outlineColor = part.color;
19803 					painter.fillColor = part.backgroundColor;
19804 
19805 					auto pos = part.boundingBox.upperLeft;
19806 					auto size = part.boundingBox.size;
19807 
19808 					painter.drawText(pos, part.text);
19809 					if(part.styles & TextFormat.underline)
19810 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
19811 					if(part.styles & TextFormat.strikethrough)
19812 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
19813 				}
19814 			}
19815 
19816 			// on every redraw, I will force the caret to be
19817 			// redrawn too, in order to eliminate perceived lag
19818 			// when moving around with the mouse.
19819 			eraseCaret(painter);
19820 
19821 			if(focused) {
19822 				highlightSelection(painter);
19823 				drawCaret(painter);
19824 			}
19825 		}
19826 
19827 		Color selectionXorColor = Color(255, 255, 127);
19828 
19829 		void highlightSelection(ScreenPainter painter) {
19830 			if(selectionStart is selectionEnd)
19831 				return; // no selection
19832 
19833 			if(selectionStart.inlineElement is null) return;
19834 			if(selectionEnd.inlineElement is null) return;
19835 
19836 			assert(selectionStart.inlineElement !is null);
19837 			assert(selectionEnd.inlineElement !is null);
19838 
19839 			painter.rasterOp = RasterOp.xor;
19840 			painter.outlineColor = Color.transparent;
19841 			painter.fillColor = selectionXorColor;
19842 
19843 			auto at = selectionStart.inlineElement;
19844 			auto atOffset = selectionStart.offset;
19845 			bool done;
19846 			while(at) {
19847 				auto box = at.boundingBox;
19848 				if(atOffset < at.letterXs.length)
19849 					box.left = at.letterXs[atOffset];
19850 
19851 				if(at is selectionEnd.inlineElement) {
19852 					if(selectionEnd.offset < at.letterXs.length)
19853 						box.right = at.letterXs[selectionEnd.offset];
19854 					done = true;
19855 				}
19856 
19857 				painter.drawRectangle(box.upperLeft, box.width, box.height);
19858 
19859 				if(done)
19860 					break;
19861 
19862 				at = at.getNextInlineElement();
19863 				atOffset = 0;
19864 			}
19865 		}
19866 
19867 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
19868 		bool caretShowingOnScreen = false;
19869 		void drawCaret(ScreenPainter painter) {
19870 			//painter.setClipRectangle(boundingBox);
19871 			int x, y1, y2;
19872 			if(caret.inlineElement is null) {
19873 				x = boundingBox.left;
19874 				y1 = boundingBox.top + 2;
19875 				y2 = boundingBox.top + painter.fontHeight;
19876 			} else {
19877 				x = caret.inlineElement.xOfIndex(caret.offset);
19878 				y1 = caret.inlineElement.boundingBox.top + 2;
19879 				y2 = caret.inlineElement.boundingBox.bottom - 2;
19880 			}
19881 
19882 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
19883 				eraseCaret(painter);
19884 
19885 			painter.pen = Pen(Color.white, 1);
19886 			painter.rasterOp = RasterOp.xor;
19887 			painter.drawLine(
19888 				Point(x, y1),
19889 				Point(x, y2)
19890 			);
19891 			painter.rasterOp = RasterOp.normal;
19892 			caretShowingOnScreen = !caretShowingOnScreen;
19893 
19894 			if(caretShowingOnScreen) {
19895 				caretLastDrawnX = x;
19896 				caretLastDrawnY1 = y1;
19897 				caretLastDrawnY2 = y2;
19898 			}
19899 		}
19900 
19901 		Rectangle caretBoundingBox() {
19902 			int x, y1, y2;
19903 			if(caret.inlineElement is null) {
19904 				x = boundingBox.left;
19905 				y1 = boundingBox.top + 2;
19906 				y2 = boundingBox.top + 16;
19907 			} else {
19908 				x = caret.inlineElement.xOfIndex(caret.offset);
19909 				y1 = caret.inlineElement.boundingBox.top + 2;
19910 				y2 = caret.inlineElement.boundingBox.bottom - 2;
19911 			}
19912 
19913 			return Rectangle(x, y1, x + 1, y2);
19914 		}
19915 
19916 		void eraseCaret(ScreenPainter painter) {
19917 			//painter.setClipRectangle(boundingBox);
19918 			if(!caretShowingOnScreen) return;
19919 			painter.pen = Pen(Color.white, 1);
19920 			painter.rasterOp = RasterOp.xor;
19921 			painter.drawLine(
19922 				Point(caretLastDrawnX, caretLastDrawnY1),
19923 				Point(caretLastDrawnX, caretLastDrawnY2)
19924 			);
19925 
19926 			caretShowingOnScreen = false;
19927 			painter.rasterOp = RasterOp.normal;
19928 		}
19929 
19930 		/// Caret movement api
19931 		/// These should give the user a logical result based on what they see on screen...
19932 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
19933 		void moveUp() {
19934 			if(caret.inlineElement is null) return;
19935 			auto x = caret.inlineElement.xOfIndex(caret.offset);
19936 			auto y = caret.inlineElement.boundingBox.top + 2;
19937 
19938 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
19939 			if(y < 0)
19940 				return;
19941 
19942 			auto i = identify(x, y);
19943 
19944 			if(i.element) {
19945 				caret.inlineElement = i.element;
19946 				caret.offset = i.offset;
19947 			}
19948 		}
19949 		void moveDown() {
19950 			if(caret.inlineElement is null) return;
19951 			auto x = caret.inlineElement.xOfIndex(caret.offset);
19952 			auto y = caret.inlineElement.boundingBox.bottom - 2;
19953 
19954 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
19955 
19956 			auto i = identify(x, y);
19957 			if(i.element) {
19958 				caret.inlineElement = i.element;
19959 				caret.offset = i.offset;
19960 			}
19961 		}
19962 		void moveLeft() {
19963 			if(caret.inlineElement is null) return;
19964 			if(caret.offset)
19965 				caret.offset--;
19966 			else {
19967 				auto p = caret.inlineElement.getPreviousInlineElement();
19968 				if(p) {
19969 					caret.inlineElement = p;
19970 					if(p.text.length && p.text[$-1] == '\n')
19971 						caret.offset = cast(int) p.text.length - 1;
19972 					else
19973 						caret.offset = cast(int) p.text.length;
19974 				}
19975 			}
19976 		}
19977 		void moveRight() {
19978 			if(caret.inlineElement is null) return;
19979 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
19980 				caret.offset++;
19981 			} else {
19982 				auto p = caret.inlineElement.getNextInlineElement();
19983 				if(p) {
19984 					caret.inlineElement = p;
19985 					caret.offset = 0;
19986 				}
19987 			}
19988 		}
19989 		void moveHome() {
19990 			if(caret.inlineElement is null) return;
19991 			auto x = 0;
19992 			auto y = caret.inlineElement.boundingBox.top + 2;
19993 
19994 			auto i = identify(x, y);
19995 
19996 			if(i.element) {
19997 				caret.inlineElement = i.element;
19998 				caret.offset = i.offset;
19999 			}
20000 		}
20001 		void moveEnd() {
20002 			if(caret.inlineElement is null) return;
20003 			auto x = int.max;
20004 			auto y = caret.inlineElement.boundingBox.top + 2;
20005 
20006 			auto i = identify(x, y);
20007 
20008 			if(i.element) {
20009 				caret.inlineElement = i.element;
20010 				caret.offset = i.offset;
20011 			}
20012 
20013 		}
20014 		void movePageUp(ref Caret caret) {}
20015 		void movePageDown(ref Caret caret) {}
20016 
20017 		void moveDocumentStart(ref Caret caret) {
20018 			if(blocks.length && blocks[0].parts.length)
20019 				caret = Caret(this, blocks[0].parts[0], 0);
20020 			else
20021 				caret = Caret.init;
20022 		}
20023 
20024 		void moveDocumentEnd(ref Caret caret) {
20025 			if(blocks.length) {
20026 				auto parts = blocks[$-1].parts;
20027 				if(parts.length) {
20028 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20029 				} else {
20030 					caret = Caret.init;
20031 				}
20032 			} else
20033 				caret = Caret.init;
20034 		}
20035 
20036 		void deleteSelection() {
20037 			if(selectionStart is selectionEnd)
20038 				return;
20039 
20040 			if(selectionStart.inlineElement is null) return;
20041 			if(selectionEnd.inlineElement is null) return;
20042 
20043 			assert(selectionStart.inlineElement !is null);
20044 			assert(selectionEnd.inlineElement !is null);
20045 
20046 			auto at = selectionStart.inlineElement;
20047 
20048 			if(selectionEnd.inlineElement is at) {
20049 				// same element, need to chop out
20050 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20051 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20052 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20053 			} else {
20054 				// different elements, we can do it with slicing
20055 				at.text = at.text[0 .. selectionStart.offset];
20056 				if(selectionStart.offset < at.letterXs.length)
20057 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20058 
20059 				at = at.getNextInlineElement();
20060 
20061 				while(at) {
20062 					if(at is selectionEnd.inlineElement) {
20063 						at.text = at.text[selectionEnd.offset .. $];
20064 						if(selectionEnd.offset < at.letterXs.length)
20065 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20066 						selectionEnd.offset = 0;
20067 						break;
20068 					} else {
20069 						auto cfd = at;
20070 						cfd.text = null; // delete the whole thing
20071 
20072 						at = at.getNextInlineElement();
20073 
20074 						if(cfd.text.length == 0) {
20075 							// and remove cfd
20076 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20077 								if(cfd.containingBlock.parts[a] is cfd) {
20078 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20079 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20080 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20081 
20082 								}
20083 							}
20084 						}
20085 					}
20086 				}
20087 			}
20088 
20089 			caret = selectionEnd;
20090 			selectNone();
20091 
20092 			invalidateLayout();
20093 
20094 		}
20095 
20096 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20097 		void insert(in char[] text) {
20098 			foreach(dchar ch; text)
20099 				insert(ch);
20100 		}
20101 		/// ditto
20102 		void insert(dchar ch) {
20103 
20104 			bool selectionDeleted = false;
20105 			if(selectionStart !is selectionEnd) {
20106 				deleteSelection();
20107 				selectionDeleted = true;
20108 			}
20109 
20110 			if(ch == 127) {
20111 				delete_();
20112 				return;
20113 			}
20114 			if(ch == 8) {
20115 				if(!selectionDeleted)
20116 					backspace();
20117 				return;
20118 			}
20119 
20120 			invalidateLayout();
20121 
20122 			if(ch == 13) ch = 10;
20123 			auto e = caret.inlineElement;
20124 			if(e is null) {
20125 				addText("" ~ cast(char) ch) ; // FIXME
20126 				return;
20127 			}
20128 
20129 			if(caret.offset == e.text.length) {
20130 				e.text ~= cast(char) ch; // FIXME
20131 				caret.offset++;
20132 				if(ch == 10) {
20133 					auto c = caret.inlineElement.clone;
20134 					c.text = null;
20135 					c.letterXs = null;
20136 					insertPartAfter(c,e);
20137 					caret = Caret(this, c, 0);
20138 				}
20139 			} else {
20140 				// FIXME cast char sucks
20141 				if(ch == 10) {
20142 					auto c = caret.inlineElement.clone;
20143 					c.text = e.text[caret.offset .. $];
20144 					if(caret.offset < c.letterXs.length)
20145 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20146 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20147 					if(caret.offset <= e.letterXs.length) {
20148 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20149 					}
20150 					insertPartAfter(c,e);
20151 					caret = Caret(this, c, 0);
20152 				} else {
20153 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20154 					caret.offset++;
20155 				}
20156 			}
20157 		}
20158 
20159 		void insertPartAfter(InlineElement what, InlineElement where) {
20160 			foreach(idx, p; where.containingBlock.parts) {
20161 				if(p is where) {
20162 					if(idx + 1 == where.containingBlock.parts.length)
20163 						where.containingBlock.parts ~= what;
20164 					else
20165 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20166 					return;
20167 				}
20168 			}
20169 		}
20170 
20171 		void cleanupStructures() {
20172 			for(size_t i = 0; i < blocks.length; i++) {
20173 				auto block = blocks[i];
20174 				for(size_t a = 0; a < block.parts.length; a++) {
20175 					auto part = block.parts[a];
20176 					if(part.text.length == 0) {
20177 						for(size_t b = a; b < block.parts.length - 1; b++)
20178 							block.parts[b] = block.parts[b+1];
20179 						block.parts = block.parts[0 .. $-1];
20180 					}
20181 				}
20182 				if(block.parts.length == 0) {
20183 					for(size_t a = i; a < blocks.length - 1; a++)
20184 						blocks[a] = blocks[a+1];
20185 					blocks = blocks[0 .. $-1];
20186 				}
20187 			}
20188 		}
20189 
20190 		void backspace() {
20191 			try_again:
20192 			auto e = caret.inlineElement;
20193 			if(e is null)
20194 				return;
20195 			if(caret.offset == 0) {
20196 				auto prev = e.getPreviousInlineElement();
20197 				if(prev is null)
20198 					return;
20199 				auto newOffset = cast(int) prev.text.length;
20200 				tryMerge(prev, e);
20201 				caret.inlineElement = prev;
20202 				caret.offset = prev is null ? 0 : newOffset;
20203 
20204 				goto try_again;
20205 			} else if(caret.offset == e.text.length) {
20206 				e.text = e.text[0 .. $-1];
20207 				caret.offset--;
20208 			} else {
20209 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20210 				caret.offset--;
20211 			}
20212 			//cleanupStructures();
20213 
20214 			invalidateLayout();
20215 		}
20216 		void delete_() {
20217 			if(selectionStart !is selectionEnd)
20218 				deleteSelection();
20219 			else {
20220 				auto before = caret;
20221 				moveRight();
20222 				if(caret != before) {
20223 					backspace();
20224 				}
20225 			}
20226 
20227 			invalidateLayout();
20228 		}
20229 		void overstrike() {}
20230 
20231 		/// Selection API. See also: caret movement.
20232 		void selectAll() {
20233 			moveDocumentStart(selectionStart);
20234 			moveDocumentEnd(selectionEnd);
20235 		}
20236 		bool selectNone() {
20237 			if(selectionStart != selectionEnd) {
20238 				selectionStart = selectionEnd = Caret.init;
20239 				return true;
20240 			}
20241 			return false;
20242 		}
20243 
20244 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20245 		/// They will modify the current selection if there is one and will splice one in if needed.
20246 		void changeAttributes() {}
20247 
20248 
20249 		/// Text search api. They manipulate the selection and/or caret.
20250 		void findText(string text) {}
20251 		void findIndex(size_t textIndex) {}
20252 
20253 		// sample event handlers
20254 
20255 		void handleEvent(KeyEvent event) {
20256 			//if(event.type == KeyEvent.Type.KeyPressed) {
20257 
20258 			//}
20259 		}
20260 
20261 		void handleEvent(dchar ch) {
20262 
20263 		}
20264 
20265 		void handleEvent(MouseEvent event) {
20266 
20267 		}
20268 
20269 		bool contentEditable; // can it be edited?
20270 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20271 		bool contentSelectable; // selectable?
20272 
20273 		Caret caret;
20274 		Caret selectionStart;
20275 		Caret selectionEnd;
20276 
20277 		bool insertMode;
20278 	}
20279 
20280 	struct Caret {
20281 		TextLayout layout;
20282 		InlineElement inlineElement;
20283 		int offset;
20284 	}
20285 
20286 	enum TextFormat : ushort {
20287 		// decorations
20288 		underline = 1,
20289 		strikethrough = 2,
20290 
20291 		// font selectors
20292 
20293 		bold = 0x4000 | 1, // weight 700
20294 		light = 0x4000 | 2, // weight 300
20295 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20296 		// bold | light is really invalid but should give weight 500
20297 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20298 
20299 		italic = 0x4000 | 8,
20300 		smallcaps = 0x4000 | 16,
20301 	}
20302 
20303 	void* findFont(string family, int weight, TextFormat formats) {
20304 		return null;
20305 	}
20306 
20307 }
20308 
20309 /++
20310 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20311 
20312 	History:
20313 		Added February 19, 2021
20314 +/
20315 /// Group: drag_and_drop
20316 interface DropHandler {
20317 	/++
20318 		Called when the drag enters the handler's area.
20319 	+/
20320 	DragAndDropAction dragEnter(DropPackage*);
20321 	/++
20322 		Called when the drag leaves the handler's area or is
20323 		cancelled. You should free your resources when this is called.
20324 	+/
20325 	void dragLeave();
20326 	/++
20327 		Called continually as the drag moves over the handler's area.
20328 
20329 		Returns: feedback to the dragger
20330 	+/
20331 	DropParameters dragOver(Point pt);
20332 	/++
20333 		The user dropped the data and you should process it now. You can
20334 		access the data through the given [DropPackage].
20335 	+/
20336 	void drop(scope DropPackage*);
20337 	/++
20338 		Called when the drop is complete. You should free whatever temporary
20339 		resources you were using. It is often reasonable to simply forward
20340 		this call to [dragLeave].
20341 	+/
20342 	void finish();
20343 
20344 	/++
20345 		Parameters returned by [DropHandler.drop].
20346 	+/
20347 	static struct DropParameters {
20348 		/++
20349 			Acceptable action over this area.
20350 		+/
20351 		DragAndDropAction action;
20352 		/++
20353 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20354 
20355 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20356 		+/
20357 		Rectangle consistentWithin;
20358 	}
20359 }
20360 
20361 /++
20362 	History:
20363 		Added February 19, 2021
20364 +/
20365 /// Group: drag_and_drop
20366 enum DragAndDropAction {
20367 	none = 0,
20368 	copy,
20369 	move,
20370 	link,
20371 	ask,
20372 	custom
20373 }
20374 
20375 /++
20376 	An opaque structure representing dropped data. It contains
20377 	private, platform-specific data that your `drop` function
20378 	should simply forward.
20379 
20380 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20381 
20382 	History:
20383 		Added February 19, 2021
20384 +/
20385 /// Group: drag_and_drop
20386 struct DropPackage {
20387 	/++
20388 		Lists the available formats as magic numbers. You should compare these
20389 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20390 		understand the passed data.
20391 	+/
20392 	DraggableData.FormatId[] availableFormats() {
20393 		version(X11) {
20394 			return xFormats;
20395 		} else version(Windows) {
20396 			if(pDataObj is null)
20397 				return null;
20398 
20399 			typeof(return) ret;
20400 
20401 			IEnumFORMATETC ef;
20402 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20403 				FORMATETC fmt;
20404 				ULONG fetched;
20405 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20406 					if(fetched == 0)
20407 						break;
20408 
20409 					if(fmt.lindex != -1)
20410 						continue;
20411 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20412 						continue;
20413 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20414 						continue;
20415 
20416 					ret ~= fmt.cfFormat;
20417 				}
20418 			}
20419 
20420 			return ret;
20421 		}
20422 	}
20423 
20424 	/++
20425 		Gets data from the drop and optionally accepts it.
20426 
20427 		Returns:
20428 			void because the data is fed asynchronously through the `dg` parameter.
20429 
20430 		Params:
20431 			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.
20432 
20433 			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.
20434 
20435 			Calling `getData` again after accepting a drop is not permitted.
20436 
20437 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20438 
20439 			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.
20440 
20441 		Throws:
20442 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20443 
20444 		History:
20445 			Included in first release of [DropPackage].
20446 	+/
20447 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20448 		version(X11) {
20449 
20450 			auto display = XDisplayConnection.get();
20451 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20452 			auto best = format;
20453 
20454 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20455 
20456 				XDisplay* display;
20457 				Atom selectionAtom;
20458 				DraggableData.FormatId best;
20459 				DraggableData.FormatId format;
20460 				void delegate(scope ubyte[] data) dg;
20461 				DragAndDropAction acceptedAction;
20462 				Window sourceWindow;
20463 				SimpleWindow win;
20464 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20465 					this.display = display;
20466 					this.win = win;
20467 					this.sourceWindow = sourceWindow;
20468 					this.format = format;
20469 					this.selectionAtom = selectionAtom;
20470 					this.best = best;
20471 					this.dg = dg;
20472 					this.acceptedAction = acceptedAction;
20473 				}
20474 
20475 
20476 				mixin X11GetSelectionHandler_Basics;
20477 
20478 				void handleData(Atom target, in ubyte[] data) {
20479 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20480 
20481 					dg(cast(ubyte[]) data);
20482 
20483 					if(acceptedAction != DragAndDropAction.none) {
20484 						auto display = XDisplayConnection.get;
20485 
20486 						XClientMessageEvent xclient;
20487 
20488 						xclient.type = EventType.ClientMessage;
20489 						xclient.window = sourceWindow;
20490 						xclient.message_type = GetAtom!"XdndFinished"(display);
20491 						xclient.format = 32;
20492 						xclient.data.l[0] = win.impl.window;
20493 						xclient.data.l[1] = 1; // drop successful
20494 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20495 
20496 						XSendEvent(
20497 							display,
20498 							sourceWindow,
20499 							false,
20500 							EventMask.NoEventMask,
20501 							cast(XEvent*) &xclient
20502 						);
20503 
20504 						XFlush(display);
20505 					}
20506 				}
20507 
20508 				Atom findBestFormat(Atom[] answer) {
20509 					Atom best = None;
20510 					foreach(option; answer) {
20511 						if(option == format) {
20512 							best = option;
20513 							break;
20514 						}
20515 						/*
20516 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20517 							best = option;
20518 							break;
20519 						} else if(option == XA_STRING) {
20520 							best = option;
20521 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20522 							best = option;
20523 						}
20524 						*/
20525 					}
20526 					return best;
20527 				}
20528 			}
20529 
20530 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20531 
20532 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20533 
20534 		} else version(Windows) {
20535 
20536 			// clean up like DragLeave
20537 			// pass effect back up
20538 
20539 			FORMATETC t;
20540 			assert(format >= 0 && format <= ushort.max);
20541 			t.cfFormat = cast(ushort) format;
20542 			t.lindex = -1;
20543 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20544 			t.tymed = TYMED.TYMED_HGLOBAL;
20545 
20546 			STGMEDIUM m;
20547 
20548 			if(pDataObj.GetData(&t, &m) != S_OK) {
20549 				// fail
20550 			} else {
20551 				// succeed, take the data and clean up
20552 
20553 				// FIXME: ensure it is legit HGLOBAL
20554 				auto handle = m.hGlobal;
20555 
20556 				if(handle) {
20557 					auto sz = GlobalSize(handle);
20558 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20559 						scope(exit) GlobalUnlock(handle);
20560 						scope(exit) GlobalFree(handle);
20561 
20562 						auto data = ptr[0 .. sz];
20563 
20564 						dg(data);
20565 					}
20566 				}
20567 			}
20568 		}
20569 	}
20570 
20571 	private:
20572 
20573 	version(X11) {
20574 		SimpleWindow win;
20575 		Window sourceWindow;
20576 		Time dataTimestamp;
20577 
20578 		Atom[] xFormats;
20579 	}
20580 	version(Windows) {
20581 		IDataObject pDataObj;
20582 	}
20583 }
20584 
20585 /++
20586 	A generic helper base class for making a drop handler with a preference list of custom types.
20587 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20588 	droppers too.
20589 
20590 	It assumes the whole window it used, but you can subclass to change that.
20591 
20592 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20593 
20594 	History:
20595 		Added February 19, 2021
20596 +/
20597 /// Group: drag_and_drop
20598 class GenericDropHandlerBase : DropHandler {
20599 	// no fancy state here so no need to do anything here
20600 	void finish() { }
20601 	void dragLeave() { }
20602 
20603 	private DragAndDropAction acceptedAction;
20604 	private DraggableData.FormatId acceptedFormat;
20605 	private void delegate(scope ubyte[]) acceptedHandler;
20606 
20607 	struct FormatHandler {
20608 		DraggableData.FormatId format;
20609 		void delegate(scope ubyte[]) handler;
20610 	}
20611 
20612 	protected abstract FormatHandler[] formatHandlers();
20613 
20614 	DragAndDropAction dragEnter(DropPackage* pkg) {
20615 		debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20616 		foreach(fmt; formatHandlers())
20617 		foreach(f; pkg.availableFormats())
20618 			if(f == fmt.format) {
20619 				acceptedFormat = f;
20620 				acceptedHandler = fmt.handler;
20621 				return acceptedAction = DragAndDropAction.copy;
20622 			}
20623 		return acceptedAction = DragAndDropAction.none;
20624 	}
20625 	DropParameters dragOver(Point pt) {
20626 		return DropParameters(acceptedAction);
20627 	}
20628 
20629 	void drop(scope DropPackage* dropPackage) {
20630 		if(!acceptedFormat || acceptedHandler is null) {
20631 			debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20632 			return; // prolly shouldn't happen anyway...
20633 		}
20634 
20635 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20636 	}
20637 }
20638 
20639 /++
20640 	A simple handler for making your window accept drops of plain text.
20641 
20642 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20643 
20644 	History:
20645 		Added February 22, 2021
20646 +/
20647 /// Group: drag_and_drop
20648 class TextDropHandler : GenericDropHandlerBase {
20649 	private void delegate(in char[] text) dg;
20650 
20651 	/++
20652 
20653 	+/
20654 	this(void delegate(in char[] text) dg) {
20655 		this.dg = dg;
20656 	}
20657 
20658 	protected override FormatHandler[] formatHandlers() {
20659 		version(X11)
20660 			return [
20661 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
20662 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
20663 			];
20664 		else version(Windows)
20665 			return [
20666 				FormatHandler(CF_UNICODETEXT, &translator),
20667 			];
20668 	}
20669 
20670 	private void translator(scope ubyte[] data) {
20671 		version(X11)
20672 			dg(cast(char[]) data);
20673 		else version(Windows)
20674 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
20675 	}
20676 }
20677 
20678 /++
20679 	A simple handler for making your window accept drops of files, issued to you as file names.
20680 
20681 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20682 
20683 	History:
20684 		Added February 22, 2021
20685 +/
20686 /// Group: drag_and_drop
20687 
20688 class FilesDropHandler : GenericDropHandlerBase {
20689 	private void delegate(in char[][]) dg;
20690 
20691 	/++
20692 
20693 	+/
20694 	this(void delegate(in char[][] fileNames) dg) {
20695 		this.dg = dg;
20696 	}
20697 
20698 	protected override FormatHandler[] formatHandlers() {
20699 		version(X11)
20700 			return [
20701 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
20702 			];
20703 		else version(Windows)
20704 			return [
20705 				FormatHandler(CF_HDROP, &translator),
20706 			];
20707 	}
20708 
20709 	private void translator(scope ubyte[] data) {
20710 		version(X11) {
20711 			char[] listString = cast(char[]) data;
20712 			char[][16] buffer;
20713 			int count;
20714 			char[][] result = buffer[];
20715 
20716 			void commit(char[] s) {
20717 				if(count == result.length)
20718 					result.length += 16;
20719 				if(s.length > 7 && s[0 ..7] == "file://")
20720 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
20721 				result[count++] = s;
20722 			}
20723 
20724 			size_t last;
20725 			foreach(idx, char c; listString) {
20726 				if(c == '\n') {
20727 					commit(listString[last .. idx - 1]); // a \r
20728 					last = idx + 1; // a \n
20729 				}
20730 			}
20731 
20732 			if(last < listString.length) {
20733 				commit(listString[last .. $]);
20734 			}
20735 
20736 			// FIXME: they are uris now, should I translate it to local file names?
20737 			// of course the host name is supposed to be there cuz of X rokking...
20738 
20739 			dg(result[0 .. count]);
20740 		} else version(Windows) {
20741 
20742 			static struct DROPFILES {
20743 				DWORD pFiles;
20744 				POINT pt;
20745 				BOOL  fNC;
20746 				BOOL  fWide;
20747 			}
20748 
20749 
20750 			const(char)[][16] buffer;
20751 			int count;
20752 			const(char)[][] result = buffer[];
20753 			size_t last;
20754 
20755 			void commitA(in char[] stuff) {
20756 				if(count == result.length)
20757 					result.length += 16;
20758 				result[count++] = stuff;
20759 			}
20760 
20761 			void commitW(in wchar[] stuff) {
20762 				commitA(makeUtf8StringFromWindowsString(stuff));
20763 			}
20764 
20765 			void magic(T)(T chars) {
20766 				size_t idx;
20767 				while(chars[idx]) {
20768 					last = idx;
20769 					while(chars[idx]) {
20770 						idx++;
20771 					}
20772 					static if(is(T == char*))
20773 						commitA(chars[last .. idx]);
20774 					else
20775 						commitW(chars[last .. idx]);
20776 					idx++;
20777 				}
20778 			}
20779 
20780 			auto df = cast(DROPFILES*) data.ptr;
20781 			if(df.fWide) {
20782 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
20783 				magic(chars);
20784 			} else {
20785 				char* chars = cast(char*) (data.ptr + df.pFiles);
20786 				magic(chars);
20787 			}
20788 			dg(result[0 .. count]);
20789 		}
20790 	}
20791 }
20792 
20793 /++
20794 	Interface to describe data being dragged. See also [draggable] helper function.
20795 
20796 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20797 
20798 	History:
20799 		Added February 19, 2021
20800 +/
20801 interface DraggableData {
20802 	version(X11)
20803 		alias FormatId = Atom;
20804 	else
20805 		alias FormatId = uint;
20806 	/++
20807 		Gets the platform-specific FormatId associated with the given named format.
20808 
20809 		This may be a MIME type, but may also be other various strings defined by the
20810 		programs you want to interoperate with.
20811 
20812 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
20813 		and convert it to some particular type for you.
20814 	+/
20815 	static FormatId getFormatId(string name)() {
20816 		version(X11)
20817 			return GetAtom!name(XDisplayConnection.get);
20818 		else version(Windows) {
20819 			static UINT cache;
20820 			if(!cache)
20821 				cache = RegisterClipboardFormatA(name);
20822 			return cache;
20823 		} else
20824 			throw new NotYetImplementedException();
20825 	}
20826 
20827 	/++
20828 		Looks up a string to represent the name for the given format, if there is one.
20829 
20830 		You should avoid using this function because it is slow. It is provided more for
20831 		debugging than for primary use.
20832 	+/
20833 	static string getFormatName(FormatId format) {
20834 		version(X11) {
20835 			if(format == 0)
20836 				return "None";
20837 			else
20838 				return getAtomName(format, XDisplayConnection.get);
20839 		} else version(Windows) {
20840 			switch(format) {
20841 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
20842 				case CF_DIBV5: return "CF_DIBV5";
20843 				case CF_RIFF: return "CF_RIFF";
20844 				case CF_WAVE: return "CF_WAVE";
20845 				case CF_HDROP: return "CF_HDROP";
20846 				default:
20847 					char[1024] name;
20848 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
20849 					return name[0 .. count].idup;
20850 			}
20851 		}
20852 	}
20853 
20854 	FormatId[] availableFormats();
20855 	// Return the slice of data you filled, empty slice if done.
20856 	// this is to support the incremental thing
20857 	ubyte[] getData(FormatId format, return scope ubyte[] data);
20858 
20859 	size_t dataLength(FormatId format);
20860 }
20861 
20862 /++
20863 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20864 
20865 	History:
20866 		Added February 19, 2021
20867 +/
20868 DraggableData draggable(string s) {
20869 	version(X11)
20870 	return new class X11SetSelectionHandler_Text, DraggableData {
20871 		this() {
20872 			super(s);
20873 		}
20874 
20875 		override FormatId[] availableFormats() {
20876 			return X11SetSelectionHandler_Text.availableFormats();
20877 		}
20878 
20879 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
20880 			return X11SetSelectionHandler_Text.getData(format, data);
20881 		}
20882 
20883 		size_t dataLength(FormatId format) {
20884 			return s.length;
20885 		}
20886 	};
20887 	version(Windows)
20888 	return new class DraggableData {
20889 		FormatId[] availableFormats() {
20890 			return [CF_UNICODETEXT];
20891 		}
20892 
20893 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
20894 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
20895 		}
20896 
20897 		size_t dataLength(FormatId format) {
20898 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
20899 		}
20900 	};
20901 }
20902 
20903 /++
20904 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20905 
20906 	History:
20907 		Added February 19, 2021
20908 +/
20909 /// Group: drag_and_drop
20910 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
20911 in {
20912 	assert(window !is null);
20913 	assert(handler !is null);
20914 }
20915 do
20916 {
20917 	version(X11) {
20918 		auto sh = cast(X11SetSelectionHandler) handler;
20919 		if(sh is null) {
20920 			// gotta make my own adapter.
20921 			sh = new class X11SetSelectionHandler {
20922 				mixin X11SetSelectionHandler_Basics;
20923 
20924 				Atom[] availableFormats() { return handler.availableFormats(); }
20925 				ubyte[] getData(Atom format, return scope ubyte[] data) {
20926 					return handler.getData(format, data);
20927 				}
20928 
20929 				// since the drop selection is only ever used once it isn't important
20930 				// to reset it.
20931 				void done() {}
20932 			};
20933 		}
20934 		return doDragDropX11(window, sh, action);
20935 	} else version(Windows) {
20936 		return doDragDropWindows(window, handler, action);
20937 	} else throw new NotYetImplementedException();
20938 }
20939 
20940 version(Windows)
20941 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
20942 	IDataObject obj = new class IDataObject {
20943 		ULONG refCount;
20944 		ULONG AddRef() {
20945 			return ++refCount;
20946 		}
20947 		ULONG Release() {
20948 			return --refCount;
20949 		}
20950 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
20951 			if (IID_IUnknown == *riid) {
20952 				*ppv = cast(void*) cast(IUnknown) this;
20953 			}
20954 			else if (IID_IDataObject == *riid) {
20955 				*ppv = cast(void*) cast(IDataObject) this;
20956 			}
20957 			else {
20958 				*ppv = null;
20959 				return E_NOINTERFACE;
20960 			}
20961 
20962 			AddRef();
20963 			return NOERROR;
20964 		}
20965 
20966 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
20967 			// import std.stdio; writeln("Advise");
20968 			return E_NOTIMPL;
20969 		}
20970 		HRESULT DUnadvise(DWORD dwConnection) {
20971 			return E_NOTIMPL;
20972 		}
20973 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
20974 			// import std.stdio; writeln("EnumDAdvise");
20975 			return OLE_E_ADVISENOTSUPPORTED;
20976 		}
20977 		// tell what formats it supports
20978 
20979 		FORMATETC[] types;
20980 		this() {
20981 			FORMATETC t;
20982 			foreach(ty; handler.availableFormats()) {
20983 				assert(ty <= ushort.max && ty >= 0);
20984 				t.cfFormat = cast(ushort) ty;
20985 				t.lindex = -1;
20986 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20987 				t.tymed = TYMED.TYMED_HGLOBAL;
20988 			}
20989 			types ~= t;
20990 		}
20991 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
20992 			if(dwDirection == DATADIR.DATADIR_GET) {
20993 				*ppenumFormatEtc = new class IEnumFORMATETC {
20994 					ULONG refCount;
20995 					ULONG AddRef() {
20996 						return ++refCount;
20997 					}
20998 					ULONG Release() {
20999 						return --refCount;
21000 					}
21001 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21002 						if (IID_IUnknown == *riid) {
21003 							*ppv = cast(void*) cast(IUnknown) this;
21004 						}
21005 						else if (IID_IEnumFORMATETC == *riid) {
21006 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21007 						}
21008 						else {
21009 							*ppv = null;
21010 							return E_NOINTERFACE;
21011 						}
21012 
21013 						AddRef();
21014 						return NOERROR;
21015 					}
21016 
21017 
21018 					int pos;
21019 					this() {
21020 						pos = 0;
21021 					}
21022 
21023 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21024 						// import std.stdio; writeln("clone");
21025 						return E_NOTIMPL; // FIXME
21026 					}
21027 
21028 					// Caller is responsible for freeing memory
21029 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21030 						// fetched may be null if celt is one
21031 						if(celt != 1)
21032 							return E_NOTIMPL; // FIXME
21033 
21034 						if(celt + pos > types.length)
21035 							return S_FALSE;
21036 
21037 						*rgelt = types[pos++];
21038 
21039 						if(pceltFetched !is null)
21040 							*pceltFetched = 1;
21041 
21042 						// import std.stdio; writeln("ok celt ", celt);
21043 						return S_OK;
21044 					}
21045 
21046 					HRESULT Reset() {
21047 						pos = 0;
21048 						return S_OK;
21049 					}
21050 
21051 					HRESULT Skip(ULONG celt) {
21052 						if(celt + pos <= types.length) {
21053 							pos += celt;
21054 							return S_OK;
21055 						}
21056 						return S_FALSE;
21057 					}
21058 				};
21059 
21060 				return S_OK;
21061 			} else
21062 				return E_NOTIMPL;
21063 		}
21064 		// given a format, return the format you'd prefer to use cuz it is identical
21065 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21066 			// FIXME: prolly could be better but meh
21067 			// import std.stdio; writeln("gcf: ", *pformatectIn);
21068 			*pformatetcOut = *pformatectIn;
21069 			return S_OK;
21070 		}
21071 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21072 			foreach(ty; types) {
21073 				if(ty == *pformatetcIn) {
21074 					auto format = ty.cfFormat;
21075 					// import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty);
21076 					STGMEDIUM medium;
21077 					medium.tymed = TYMED.TYMED_HGLOBAL;
21078 
21079 					auto sz = handler.dataLength(format);
21080 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21081 					if(handle is null) throw new Exception("GlobalAlloc");
21082 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21083 						auto slice = data[0 .. sz];
21084 						scope(exit)
21085 							GlobalUnlock(handle);
21086 
21087 						handler.getData(format, cast(ubyte[]) slice[]);
21088 					}
21089 
21090 
21091 					medium.hGlobal = handle; // FIXME
21092 					*pmedium = medium;
21093 					return S_OK;
21094 				}
21095 			}
21096 			return DV_E_FORMATETC;
21097 		}
21098 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21099 			// import std.stdio; writeln("GDH: ", *pformatetcIn);
21100 			return E_NOTIMPL; // FIXME
21101 		}
21102 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21103 			auto search = *pformatetc;
21104 			search.tymed &= TYMED.TYMED_HGLOBAL;
21105 			foreach(ty; types)
21106 				if(ty == search) {
21107 					// import std.stdio; writeln("QueryGetData ", search, " ", types[0]);
21108 					return S_OK;
21109 				}
21110 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21111 				//import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]);
21112 			}
21113 			return S_FALSE;
21114 		}
21115 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21116 			// import std.stdio; writeln("SetData: ");
21117 			return E_NOTIMPL;
21118 		}
21119 	};
21120 
21121 
21122 	IDropSource src = new class IDropSource {
21123 		ULONG refCount;
21124 		ULONG AddRef() {
21125 			return ++refCount;
21126 		}
21127 		ULONG Release() {
21128 			return --refCount;
21129 		}
21130 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21131 			if (IID_IUnknown == *riid) {
21132 				*ppv = cast(void*) cast(IUnknown) this;
21133 			}
21134 			else if (IID_IDropSource == *riid) {
21135 				*ppv = cast(void*) cast(IDropSource) this;
21136 			}
21137 			else {
21138 				*ppv = null;
21139 				return E_NOINTERFACE;
21140 			}
21141 
21142 			AddRef();
21143 			return NOERROR;
21144 		}
21145 
21146 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21147 			if(fEscapePressed)
21148 				return DRAGDROP_S_CANCEL;
21149 			if(!(grfKeyState & MK_LBUTTON))
21150 				return DRAGDROP_S_DROP;
21151 			return S_OK;
21152 		}
21153 
21154 		int GiveFeedback(uint dwEffect) {
21155 			return DRAGDROP_S_USEDEFAULTCURSORS;
21156 		}
21157 	};
21158 
21159 	DWORD effect;
21160 
21161 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21162 
21163 	DROPEFFECT de = win32DragAndDropAction(action);
21164 
21165 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21166 	// but still prolly a FIXME
21167 
21168 	auto ret = DoDragDrop(obj, src, de, &effect);
21169 	/+
21170 	import std.stdio;
21171 	if(ret == DRAGDROP_S_DROP)
21172 		writeln("drop ", effect);
21173 	else if(ret == DRAGDROP_S_CANCEL)
21174 		writeln("cancel");
21175 	else if(ret == S_OK)
21176 		writeln("ok");
21177 	else writeln(ret);
21178 	+/
21179 
21180 	return ret;
21181 }
21182 
21183 version(Windows)
21184 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21185 	DROPEFFECT de;
21186 
21187 	with(DragAndDropAction)
21188 	with(DROPEFFECT)
21189 	final switch(action) {
21190 		case none: de = DROPEFFECT_NONE; break;
21191 		case copy: de = DROPEFFECT_COPY; break;
21192 		case move: de = DROPEFFECT_MOVE; break;
21193 		case link: de = DROPEFFECT_LINK; break;
21194 		case ask: throw new Exception("ask not implemented yet");
21195 		case custom: throw new Exception("custom not implemented yet");
21196 	}
21197 
21198 	return de;
21199 }
21200 
21201 
21202 /++
21203 	History:
21204 		Added February 19, 2021
21205 +/
21206 /// Group: drag_and_drop
21207 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21208 	version(X11) {
21209 		auto display = XDisplayConnection.get;
21210 
21211 		Atom atom = 5; // right???
21212 
21213 		XChangeProperty(
21214 			display,
21215 			window.impl.window,
21216 			GetAtom!"XdndAware"(display),
21217 			XA_ATOM,
21218 			32 /* bits */,
21219 			PropModeReplace,
21220 			&atom,
21221 			1);
21222 
21223 		window.dropHandler = handler;
21224 	} else version(Windows) {
21225 
21226 		initDnd();
21227 
21228 		auto dropTarget = new class (handler) IDropTarget {
21229 			DropHandler handler;
21230 			this(DropHandler handler) {
21231 				this.handler = handler;
21232 			}
21233 			ULONG refCount;
21234 			ULONG AddRef() {
21235 				return ++refCount;
21236 			}
21237 			ULONG Release() {
21238 				return --refCount;
21239 			}
21240 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21241 				if (IID_IUnknown == *riid) {
21242 					*ppv = cast(void*) cast(IUnknown) this;
21243 				}
21244 				else if (IID_IDropTarget == *riid) {
21245 					*ppv = cast(void*) cast(IDropTarget) this;
21246 				}
21247 				else {
21248 					*ppv = null;
21249 					return E_NOINTERFACE;
21250 				}
21251 
21252 				AddRef();
21253 				return NOERROR;
21254 			}
21255 
21256 
21257 			// ///////////////////
21258 
21259 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21260 				DropPackage dropPackage = DropPackage(pDataObj);
21261 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21262 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21263 			}
21264 
21265 			HRESULT DragLeave() {
21266 				handler.dragLeave();
21267 				// release the IDataObject if needed
21268 				return S_OK;
21269 			}
21270 
21271 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21272 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21273 
21274 				*pdwEffect = win32DragAndDropAction(res.action);
21275 				// same as DragEnter basically
21276 				return S_OK;
21277 			}
21278 
21279 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21280 				DropPackage pkg = DropPackage(pDataObj);
21281 				handler.drop(&pkg);
21282 
21283 				return S_OK;
21284 			}
21285 		};
21286 		// Windows can hold on to the handler and try to call it
21287 		// during which time the GC can't see it. so important to
21288 		// manually manage this. At some point i'll FIXME and make
21289 		// all my com instances manually managed since they supposed
21290 		// to respect the refcount.
21291 		import core.memory;
21292 		GC.addRoot(cast(void*) dropTarget);
21293 
21294 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21295 			throw new Exception("register");
21296 
21297 		window.dropHandler = handler;
21298 	} else throw new NotYetImplementedException();
21299 }
21300 
21301 
21302 
21303 static if(UsingSimpledisplayX11) {
21304 
21305 enum _NET_WM_STATE_ADD = 1;
21306 enum _NET_WM_STATE_REMOVE = 0;
21307 enum _NET_WM_STATE_TOGGLE = 2;
21308 
21309 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21310 void demandAttention(SimpleWindow window, bool needs = true) {
21311 	demandAttention(window.impl.window, needs);
21312 }
21313 
21314 /// ditto
21315 void demandAttention(Window window, bool needs = true) {
21316 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21317 }
21318 
21319 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21320 	auto display = XDisplayConnection.get();
21321 	if(atom == None)
21322 		return; // non-failure error
21323 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21324 
21325 	XClientMessageEvent xclient;
21326 
21327 	xclient.type = EventType.ClientMessage;
21328 	xclient.window = window;
21329 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21330 	xclient.format = 32;
21331 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21332 	xclient.data.l[1] = atom;
21333 	xclient.data.l[2] = atom2;
21334 	xclient.data.l[3] = 1;
21335 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21336 
21337 	XSendEvent(
21338 		display,
21339 		RootWindow(display, DefaultScreen(display)),
21340 		false,
21341 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21342 		cast(XEvent*) &xclient
21343 	);
21344 
21345 	/+
21346 	XChangeProperty(
21347 		display,
21348 		window.impl.window,
21349 		GetAtom!"_NET_WM_STATE"(display),
21350 		XA_ATOM,
21351 		32 /* bits */,
21352 		PropModeAppend,
21353 		&atom,
21354 		1);
21355 	+/
21356 }
21357 
21358 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21359 	Atom actionAtom;
21360 	with(DragAndDropAction)
21361 	final switch(action) {
21362 		case none: actionAtom = None; break;
21363 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21364 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21365 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21366 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21367 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21368 	}
21369 
21370 	return actionAtom;
21371 }
21372 
21373 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21374 	// FIXME: I need to show user feedback somehow.
21375 	auto display = XDisplayConnection.get;
21376 
21377 	auto actionAtom = dndActionAtom(display, action);
21378 	assert(actionAtom, "Don't use action none to accept a drop");
21379 
21380 	setX11Selection!"XdndSelection"(window, handler, null);
21381 
21382 	auto oldKeyHandler = window.handleKeyEvent;
21383 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21384 
21385 	auto oldCharHandler = window.handleCharEvent;
21386 	scope(exit) window.handleCharEvent = oldCharHandler;
21387 
21388 	auto oldMouseHandler = window.handleMouseEvent;
21389 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21390 
21391 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21392 
21393 	import core.sys.posix.sys.time;
21394 	timeval tv;
21395 	gettimeofday(&tv, null);
21396 
21397 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21398 
21399 	Time lastMouseTimestamp;
21400 
21401 	bool dnding = true;
21402 	Window lastIn = None;
21403 
21404 	void leave() {
21405 		if(lastIn == None)
21406 			return;
21407 
21408 		XEvent ev;
21409 		ev.xclient.type = EventType.ClientMessage;
21410 		ev.xclient.window = lastIn;
21411 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21412 		ev.xclient.format = 32;
21413 		ev.xclient.data.l[0] = window.impl.window;
21414 
21415 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21416 		XFlush(display);
21417 
21418 		lastIn = None;
21419 	}
21420 
21421 	void enter(Window w) {
21422 		assert(lastIn == None);
21423 
21424 		lastIn = w;
21425 
21426 		XEvent ev;
21427 		ev.xclient.type = EventType.ClientMessage;
21428 		ev.xclient.window = lastIn;
21429 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21430 		ev.xclient.format = 32;
21431 		ev.xclient.data.l[0] = window.impl.window;
21432 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21433 
21434 		auto types = handler.availableFormats();
21435 		assert(types.length > 0);
21436 
21437 		ev.xclient.data.l[2] = types[0];
21438 		if(types.length > 1)
21439 			ev.xclient.data.l[3] = types[1];
21440 		if(types.length > 2)
21441 			ev.xclient.data.l[4] = types[2];
21442 
21443 		// FIXME: other types?!?!? and make sure we skip TARGETS
21444 
21445 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21446 		XFlush(display);
21447 	}
21448 
21449 	void position(int rootX, int rootY) {
21450 		assert(lastIn != None);
21451 
21452 		XEvent ev;
21453 		ev.xclient.type = EventType.ClientMessage;
21454 		ev.xclient.window = lastIn;
21455 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21456 		ev.xclient.format = 32;
21457 		ev.xclient.data.l[0] = window.impl.window;
21458 		ev.xclient.data.l[1] = 0; // reserved
21459 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21460 		ev.xclient.data.l[3] = dataTimestamp;
21461 		ev.xclient.data.l[4] = actionAtom;
21462 
21463 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21464 		XFlush(display);
21465 
21466 	}
21467 
21468 	void drop() {
21469 		XEvent ev;
21470 		ev.xclient.type = EventType.ClientMessage;
21471 		ev.xclient.window = lastIn;
21472 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21473 		ev.xclient.format = 32;
21474 		ev.xclient.data.l[0] = window.impl.window;
21475 		ev.xclient.data.l[1] = 0; // reserved
21476 		ev.xclient.data.l[2] = dataTimestamp;
21477 
21478 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21479 		XFlush(display);
21480 
21481 		lastIn = None;
21482 		dnding = false;
21483 	}
21484 
21485 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21486 	// but idk if i should...
21487 
21488 	window.setEventHandlers(
21489 		delegate(KeyEvent ev) {
21490 			if(ev.pressed == true && ev.key == Key.Escape) {
21491 				// cancel
21492 				dnding = false;
21493 			}
21494 		},
21495 		delegate(MouseEvent ev) {
21496 			if(ev.timestamp < lastMouseTimestamp)
21497 				return;
21498 
21499 			lastMouseTimestamp = ev.timestamp;
21500 
21501 			if(ev.type == MouseEventType.motion) {
21502 				auto display = XDisplayConnection.get;
21503 				auto root = RootWindow(display, DefaultScreen(display));
21504 
21505 				Window topWindow;
21506 				int rootX, rootY;
21507 
21508 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21509 
21510 				if(topWindow == None)
21511 					return;
21512 
21513 				top:
21514 				if(auto result = topWindow in eligibility) {
21515 					auto dropWindow = *result;
21516 					if(dropWindow == None) {
21517 						leave();
21518 						return;
21519 					}
21520 
21521 					if(dropWindow != lastIn) {
21522 						leave();
21523 						enter(dropWindow);
21524 						position(rootX, rootY);
21525 					} else {
21526 						position(rootX, rootY);
21527 					}
21528 				} else {
21529 					// determine eligibility
21530 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21531 					if(data.length == 1) {
21532 						// in case there is no WM or it isn't reparenting
21533 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21534 					} else {
21535 
21536 						Window tryScanChildren(Window search, int maxRecurse) {
21537 							// could be reparenting window manager, so gotta check the next few children too
21538 							Window child;
21539 							int x;
21540 							int y;
21541 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21542 
21543 							if(child == None)
21544 								return None;
21545 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21546 							if(data.length == 1) {
21547 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21548 							} else {
21549 								if(maxRecurse)
21550 									return tryScanChildren(child, maxRecurse - 1);
21551 								else
21552 									return None;
21553 							}
21554 
21555 						}
21556 
21557 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21558 						auto topResult = tryScanChildren(topWindow, 3);
21559 						// it is easy to have a false negative due to the mouse going over a WM
21560 						// child window like the close button if separate from the frame... so I
21561 						// can't really cache negatives, :(
21562 						if(topResult != None) {
21563 							eligibility[topWindow] = topResult;
21564 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21565 						}
21566 					}
21567 
21568 				}
21569 
21570 			} else if(ev.type == MouseEventType.buttonReleased) {
21571 				drop();
21572 				dnding = false;
21573 			}
21574 		}
21575 	);
21576 
21577 	window.grabInput();
21578 	scope(exit)
21579 		window.releaseInputGrab();
21580 
21581 
21582 	EventLoop.get.run(() => dnding);
21583 
21584 	return 0;
21585 }
21586 
21587 /// X-specific
21588 TrueColorImage getWindowNetWmIcon(Window window) {
21589 	try {
21590 		auto display = XDisplayConnection.get;
21591 
21592 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21593 
21594 		if (data.length > arch_ulong.sizeof * 2) {
21595 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21596 			// these are an array of rgba images that we have to convert into pixmaps ourself
21597 
21598 			int width = cast(int) meta[0];
21599 			int height = cast(int) meta[1];
21600 
21601 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21602 
21603 			static if(arch_ulong.sizeof == 4) {
21604 				bytes = bytes[0 .. width * height * 4];
21605 				alias imageData = bytes;
21606 			} else static if(arch_ulong.sizeof == 8) {
21607 				bytes = bytes[0 .. width * height * 8];
21608 				auto imageData = new ubyte[](4 * width * height);
21609 			} else static assert(0);
21610 
21611 
21612 
21613 			// this returns ARGB. Remember it is little-endian so
21614 			//                                         we have BGRA
21615 			// our thing uses RGBA, which in little endian, is ABGR
21616 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21617 				auto r = bytes[idx + 2];
21618 				auto g = bytes[idx + 1];
21619 				auto b = bytes[idx + 0];
21620 				auto a = bytes[idx + 3];
21621 
21622 				imageData[idx2 + 0] = r;
21623 				imageData[idx2 + 1] = g;
21624 				imageData[idx2 + 2] = b;
21625 				imageData[idx2 + 3] = a;
21626 			}
21627 
21628 			return new TrueColorImage(width, height, imageData);
21629 		}
21630 
21631 		return null;
21632 	} catch(Exception e) {
21633 		return null;
21634 	}
21635 }
21636 
21637 } /* UsingSimpledisplayX11 */
21638 
21639 
21640 void loadBinNameToWindowClassName () {
21641 	import core.stdc.stdlib : realloc;
21642 	version(linux) {
21643 		// args[0] MAY be empty, so we'll just use this
21644 		import core.sys.posix.unistd : readlink;
21645 		char[1024] ebuf = void; // 1KB should be enough for everyone!
21646 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
21647 		if (len < 1) return;
21648 	} else /*version(Windows)*/ {
21649 		import core.runtime : Runtime;
21650 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
21651 		auto ebuf = Runtime.args[0];
21652 		auto len = ebuf.length;
21653 	}
21654 	auto pos = len;
21655 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
21656 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
21657 	if (sdpyWindowClassStr is null) return; // oops
21658 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
21659 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
21660 }
21661 
21662 /++
21663 	An interface representing a font that is drawn with custom facilities.
21664 
21665 	You might want [OperatingSystemFont] instead, which represents
21666 	a font loaded and drawn by functions native to the operating system.
21667 
21668 	WARNING: I might still change this.
21669 +/
21670 interface DrawableFont : MeasurableFont {
21671 	/++
21672 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
21673 
21674 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
21675 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
21676 		fill color, but that's up to the implementation.
21677 	+/
21678 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
21679 
21680 	/++
21681 		Requests that the given string is added to the image cache. You should only do this rarely, but
21682 		if you have a string that you know will be used over and over again, adding it to a cache can
21683 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
21684 		to implement this as a do-nothing method).
21685 	+/
21686 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
21687 }
21688 
21689 /++
21690 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
21691 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
21692 
21693 	You should also consider [OperatingSystemFont], which loads and draws a font with
21694 	facilities native to the user's operating system. You might also consider
21695 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
21696 	of game, as they have their own ways to draw text too.
21697 
21698 	Be warned: this can be slow, especially on remote connections to the X server, since
21699 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
21700 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
21701 	experiment in your specific case.
21702 
21703 	Please note that the return type of [DrawableFont] also includes an implementation of
21704 	[MeasurableFont].
21705 +/
21706 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
21707 	import arsd.ttf;
21708 	static class ArsdTtfFont : DrawableFont {
21709 		TtfFont font;
21710 		int size;
21711 		this(in ubyte[] data, int size) {
21712 			font = TtfFont(data);
21713 			this.size = size;
21714 
21715 
21716 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
21717 			int ascent_, descent_, line_gap;
21718 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
21719 
21720 			int advance, lsb;
21721 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
21722 			xWidth = cast(int) (advance * scale);
21723 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
21724 			MWidth = cast(int) (advance * scale);
21725 		}
21726 
21727 		private int ascent_;
21728 		private int descent_;
21729 		private int xWidth;
21730 		private int MWidth;
21731 
21732 		bool isMonospace() {
21733 			return xWidth == MWidth;
21734 		}
21735 		int averageWidth() {
21736 			return xWidth;
21737 		}
21738 		int height() {
21739 			return size;
21740 		}
21741 		int ascent() {
21742 			return ascent_;
21743 		}
21744 		int descent() {
21745 			return descent_;
21746 		}
21747 
21748 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
21749 			int width, height;
21750 			font.getStringSize(s, size, width, height);
21751 			return width;
21752 		}
21753 
21754 
21755 
21756 		Sprite[string] cache;
21757 
21758 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
21759 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
21760 			cache[text] = sprite;
21761 		}
21762 
21763 		Image stringToImage(Color fg, Color bg, in char[] text) {
21764 			int width, height;
21765 			auto data = font.renderString(text, size, width, height);
21766 			auto image = new TrueColorImage(width, height);
21767 			int pos = 0;
21768 			foreach(y; 0 .. height)
21769 			foreach(x; 0 .. width) {
21770 				fg.a = data[0];
21771 				bg.a = 255;
21772 				auto color = alphaBlend(fg, bg);
21773 				image.imageData.bytes[pos++] = color.r;
21774 				image.imageData.bytes[pos++] = color.g;
21775 				image.imageData.bytes[pos++] = color.b;
21776 				image.imageData.bytes[pos++] = data[0];
21777 				data = data[1 .. $];
21778 			}
21779 			assert(data.length == 0);
21780 
21781 			return Image.fromMemoryImage(image);
21782 		}
21783 
21784 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
21785 			Sprite sprite = (text in cache) ? *(text in cache) : null;
21786 
21787 			auto fg = painter.impl._outlineColor;
21788 			auto bg = painter.impl._fillColor;
21789 
21790 			if(sprite !is null) {
21791 				auto w = cast(SimpleWindow) painter.window;
21792 				assert(w !is null);
21793 
21794 				sprite.drawAt(painter, upperLeft);
21795 			} else {
21796 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
21797 			}
21798 		}
21799 	}
21800 
21801 	return new ArsdTtfFont(data, size);
21802 }
21803 
21804 class NotYetImplementedException : Exception {
21805 	this(string file = __FILE__, size_t line = __LINE__) {
21806 		super("Not yet implemented", file, line);
21807 	}
21808 }
21809 
21810 ///
21811 __gshared bool librariesSuccessfullyLoaded = true;
21812 ///
21813 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
21814 
21815 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
21816 	mixin(staticForeachReplacement!Iface);
21817 
21818 	void loadDynamicLibrary() @nogc {
21819 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
21820 	}
21821 
21822         void loadDynamicLibraryForReal() {
21823                 foreach(name; __traits(derivedMembers, Iface)) {
21824                         mixin("alias tmp = " ~ name ~ ";");
21825                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
21826                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
21827                 }
21828         }
21829 }
21830 
21831 private const(char)[] staticForeachReplacement(Iface)() pure {
21832 /*
21833 	// just this for gdc 9....
21834 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
21835 
21836         static foreach(name; __traits(derivedMembers, Iface))
21837                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
21838 */
21839 
21840 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
21841 	size_t pos;
21842 
21843 	void append(in char[] what) {
21844 		if(pos + what.length > code.length)
21845 			code.length = (code.length * 3) / 2;
21846 		code[pos .. pos + what.length] = what[];
21847 		pos += what.length;
21848 	}
21849 
21850         foreach(name; __traits(derivedMembers, Iface)) {
21851                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
21852 		append(name);
21853 		append(`")) `);
21854 		append(name);
21855 		append(";");
21856 	}
21857 
21858 	return code[0 .. pos];
21859 }
21860 
21861 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
21862 	mixin(staticForeachReplacement!Iface);
21863 
21864 	private __gshared void* libHandle;
21865 	private __gshared bool attempted;
21866 
21867         void loadDynamicLibrary() @nogc {
21868 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
21869 	}
21870 
21871 	bool loadAttempted() {
21872 		return attempted;
21873 	}
21874 	bool loadSuccessful() {
21875 		return libHandle !is null;
21876 	}
21877 
21878         void loadDynamicLibraryForReal() {
21879 		attempted = true;
21880                 version(Posix) {
21881                         import core.sys.posix.dlfcn;
21882 			version(OSX) {
21883 				version(X11)
21884                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
21885 				else
21886                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
21887 			} else {
21888                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
21889 				if(libHandle is null)
21890                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
21891 			}
21892 
21893 			static void* loadsym(void* l, const char* name) {
21894 				import core.stdc.stdlib;
21895 				if(l is null)
21896 					return &abort;
21897 				return dlsym(l, name);
21898 			}
21899                 } else version(Windows) {
21900                         import core.sys.windows.winbase;
21901                         libHandle = LoadLibrary(library ~ ".dll");
21902 			static void* loadsym(void* l, const char* name) {
21903 				import core.stdc.stdlib;
21904 				if(l is null)
21905 					return &abort;
21906 				return GetProcAddress(l, name);
21907 			}
21908                 }
21909                 if(libHandle is null) {
21910 			success = false;
21911                         //throw new Exception("load failure of library " ~ library);
21912 		}
21913                 foreach(name; __traits(derivedMembers, Iface)) {
21914                         mixin("alias tmp = " ~ name ~ ";");
21915                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
21916                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
21917                 }
21918         }
21919 
21920         void unloadDynamicLibrary() {
21921                 version(Posix) {
21922                         import core.sys.posix.dlfcn;
21923                         dlclose(libHandle);
21924                 } else version(Windows) {
21925                         import core.sys.windows.winbase;
21926                         FreeLibrary(libHandle);
21927                 }
21928                 foreach(name; __traits(derivedMembers, Iface))
21929                         mixin(name ~ " = null;");
21930         }
21931 }
21932 
21933 /+
21934 	The GC can be called from any thread, and a lot of cleanup must be done
21935 	on the gui thread. Since the GC can interrupt any locks - including being
21936 	triggered inside a critical section - it is vital to avoid deadlocks to get
21937 	these functions called from the right place.
21938 
21939 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
21940 	right now.
21941 
21942 	The cleanup function is run when the event loop gets around to it, which is just
21943 	whenever there's something there after it has been woken up for other work. It does
21944 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
21945 	(Well actually it might be ok but i don't wanna mess with it right now.)
21946 +/
21947 private struct CleanupQueue {
21948 	import core.stdc.stdlib;
21949 
21950 	void queue(alias func, T...)(T args) {
21951 		static struct Args {
21952 			T args;
21953 		}
21954 		static struct RealJob {
21955 			Job j;
21956 			Args a;
21957 		}
21958 		static void call(Job* data) {
21959 			auto rj = cast(RealJob*) data;
21960 			func(rj.a.args);
21961 		}
21962 
21963 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
21964 		thing.j.call = &call;
21965 		thing.a.args = args;
21966 
21967 		buffer[tail++] = cast(Job*) thing;
21968 
21969 		// FIXME: set overflowed
21970 	}
21971 
21972 	void process() {
21973 		const tail = this.tail;
21974 
21975 		while(tail != head) {
21976 			Job* job = cast(Job*) buffer[head++];
21977 			job.call(job);
21978 			free(job);
21979 		}
21980 
21981 		if(overflowed)
21982 			throw new Exception("cleanup overflowed");
21983 	}
21984 
21985 	private:
21986 
21987 	ubyte tail; // must ONLY be written by queue
21988 	ubyte head; // must ONLY be written by process
21989 	bool overflowed;
21990 
21991 	static struct Job {
21992 		void function(Job*) call;
21993 	}
21994 
21995 	void*[256] buffer;
21996 }
21997 private __gshared CleanupQueue cleanupQueue;
21998 
21999 version(X11)
22000 /++
22001 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22002 
22003 	$(WARNING
22004 		This function is exempted from stability guarantees.
22005 	)
22006 +/
22007 float customScalingFactorForMonitor(int monitorNumber) {
22008 	import core.stdc.stdlib;
22009 	auto val = getenv("ARSD_SCALING_FACTOR");
22010 
22011 	if(val is null)
22012 		return 1.0;
22013 
22014 	char[16] buffer = 0;
22015 	int pos;
22016 
22017 	const(char)* at = val;
22018 
22019 	foreach(item; 0 .. monitorNumber + 1) {
22020 		if(*at == 0)
22021 			break; // reuse the last number when we at the end of the string
22022 		pos = 0;
22023 		while(pos + 1 < buffer.length && *at && *at != ';') {
22024 			buffer[pos++] = *at;
22025 			at++;
22026 		}
22027 		if(*at)
22028 			at++; // skip the semicolon
22029 		buffer[pos] = 0;
22030 	}
22031 
22032 	//sdpyPrintDebugString(buffer[0 .. pos]);
22033 
22034 	import core.stdc.math;
22035 	auto f = atof(buffer.ptr);
22036 
22037 	if(f <= 0.0 || isnan(f) || isinf(f))
22038 		return 1.0;
22039 
22040 	return f;
22041 }
22042 
22043 void guiAbortProcess(string msg) {
22044 	import core.stdc.stdlib;
22045 	version(Windows) {
22046 		WCharzBuffer t = WCharzBuffer(msg);
22047 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22048 	} else {
22049 		import core.stdc.stdio;
22050 		fwrite(msg.ptr, 1, msg.length, stderr);
22051 		msg = "\n";
22052 		fwrite(msg.ptr, 1, msg.length, stderr);
22053 		fflush(stderr);
22054 	}
22055 
22056 	abort();
22057 }
22058 
22059 private int minInternal(int a, int b) {
22060 	return (a < b) ? a : b;
22061 }
22062 
22063 private alias scriptable = arsd_jsvar_compatible;