1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 /+
4 	To share some stuff between two opengl threads:
5 	windows
6 	https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
7 	linux
8 	https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
9 +/
10 
11 
12 // Search for: FIXME: leaks if multithreaded gc
13 
14 // https://freedesktop.org/wiki/Specifications/XDND/
15 
16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
17 
18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
20 
21 
22 // on Mac with X11: -L-L/usr/X11/lib
23 
24 /+
25 
26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
27 
28 	Progress bar in taskbar
29 		- i can probably just set a property on the window...
30 		  it sets that prop to an integer 0 .. 100. Taskbar
31 		  deletes it or window deletes it when it is handled.
32 		- prolly display it as a nice little line at the bottom.
33 
34 
35 from gtk:
36 
37 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
38 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
39 
40 >+  if (cardinal > 0)
41 >+  {
42 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
45 >+                     XA_CARDINAL, 32,
46 >+                     PropModeReplace,
47 >+                     (guchar *) &cardinal, 1);
48 >+  }
49 >+  else
50 >+  {
51 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
52 >+                     xid,
53 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
54 >+  }
55 
56 from Windows:
57 
58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
59 
60 interface
61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
63 listen for msg, return TRUE
64 interface->SetProgressState(hwnd, TBPF_NORMAL);
65 interface->SetProgressValue(hwnd, 40, 100);
66 
67 
68 	My new notification system.
69 		- use a unix socket? or a x property? or a udp port?
70 		- could of course also get on the dbus train but ugh.
71 		- it could also reply with the info as a string for easy remote examination.
72 
73 +/
74 
75 /*
76 	Event Loop would be nices:
77 
78 	* add on idle - runs when nothing else happens
79 		* which can specify how long to yield for
80 	* send messages without a recipient window
81 	* setTimeout
82 	* setInterval
83 */
84 
85 /*
86 	Classic games I want to add:
87 		* my tetris clone
88 		* pac man
89 */
90 
91 /*
92 	Text layout needs a lot of work. Plain drawText is useful but too
93 	limited. It will need some kind of text context thing which it will
94 	update and you can pass it on and get more details out of it.
95 
96 	It will need a bounding box, a current cursor location that is updated
97 	as drawing continues, and various changable facts (which can also be
98 	changed on the painter i guess) like font, color, size, background,
99 	etc.
100 
101 	We can also fetch the caret location from it somehow.
102 
103 	Should prolly be an overload of drawText
104 
105 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
106 
107 		WS_EX_NOACTIVATE
108 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
109 		full screen windows. Can just set the atom on X. Windows will be harder.
110 
111 		moving windows. resizing windows.
112 
113 		hide cursor, capture cursor, change cursor.
114 
115 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
116 	sure the pieces are there to do its job easily and make other jobs possible.
117 */
118 
119 /++
120 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
121 	including creating windows, drawing on them, working with the clipboard,
122 	timers, OpenGL, and more. However, it does NOT provide high level GUI
123 	widgets. See my minigui.d, an extension to this module, for that
124 	functionality.
125 
126 	simpledisplay provides cross-platform wrapping for Windows and Linux
127 	(and perhaps other OSes that use X11), but also does not prevent you
128 	from using the underlying facilities if you need them. It has a goal
129 	of working efficiently over a remote X link (at least as far as Xlib
130 	reasonably allows.)
131 
132 	simpledisplay depends on [arsd.color|color.d], which should be available from the
133 	same place where you got this file. Other than that, however, it has
134 	very few dependencies and ones that don't come with the OS and/or the
135 	compiler are all opt-in.
136 
137 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
138 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
139 
140 	simpledisplay is basically stable. I plan to refactor the internals,
141 	and may add new features and fix bugs, but It do not expect to
142 	significantly change the API. It has been stable a few years already now.
143 
144 	Installation_instructions:
145 
146 	`simpledisplay.d` does not have any dependencies outside the
147 	operating system and `color.d`, so it should just work most the
148 	time, but there are a few caveats on some systems:
149 
150 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
151 	console to be automatically allocated.
152 
153 	Please note when compiling on Win64, you need to explicitly list
154 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
155 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
156 
157 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
158 	note the "w".
159 
160 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
161 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
162 	See [EnableWindowsSubsystem] for more information.
163 
164 	$(PITFALL
165 		With the Windows subsystem, there is no console, so standard writeln will throw!
166 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
167 		create a console as needed.
168 	)
169 
170 	On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
171 
172 	On Ubuntu, you might need to install X11 development libraries to
173 	successfully link.
174 
175 	$(CONSOLE
176 		$ sudo apt-get install libglc-dev
177 		$ sudo apt-get install libx11-dev
178 	)
179 
180 
181 	Jump_list:
182 
183 	Don't worry, you don't have to read this whole documentation file!
184 
185 	Check out the [#event-example] and [#Pong-example] to get started quickly.
186 
187 	The main classes you may want to create are [SimpleWindow], [Timer],
188 	[Image], and [Sprite].
189 
190 	The main functions you'll want are [setClipboardText] and [getClipboardText].
191 
192 	There are also platform-specific functions available such as [XDisplayConnection]
193 	and [GetAtom] for X11, among others.
194 
195 	See the examples and topics list below to learn more.
196 
197 	$(WARNING
198 		There should only be one GUI thread per application,
199 		and all windows should be created in it and your
200 		event loop should run there.
201 
202 		To do otherwise is undefined behavior and has no
203 		cross platform guarantees.
204 	)
205 
206 	$(H2 About this documentation)
207 
208 	The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
209 
210 	Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
211 
212 	All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
213 
214 	To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
215 
216 	If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
217 
218 	At points, I will talk about implementation details in the documentation. These are sometimes
219 	subject to change, but nevertheless useful to understand what is really going on. You can learn
220 	more about some of the referenced things by searching the web for info about using them from C.
221 	You can always look at the source of simpledisplay.d too for the most authoritative source on
222 	its specific implementation. If you disagree with how I did something, please contact me so we
223 	can discuss it!
224 
225 	$(H2 Using with fibers)
226 
227 	simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
228 
229 	$(H2 Topics)
230 
231 	$(H3 $(ID topic-windows) Windows)
232 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
233 		window on the user's screen.
234 
235 		You may create multiple windows, if the underlying platform supports it. You may check
236 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
237 		SimpleWindow's constructor at runtime to handle those cases.
238 
239 		A single running event loop will handle as many windows as needed.
240 
241 	$(H3 $(ID topic-event-loops) Event loops)
242 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
243 
244 		The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there:
245 
246 		---
247 		// dmd example.d simpledisplay.d color.d
248 		import arsd.simpledisplay;
249 		void main() {
250 			auto window = new SimpleWindow(200, 200);
251 			window.eventLoop(0,
252 			  delegate (dchar) { /* got a character key press */ }
253 			);
254 		}
255 		---
256 
257 		$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
258 
259 		On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
260 
261 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
262 
263 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
264 
265 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
266 
267 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
268 		Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
269 
270 		See the [NotificationAreaIcon] class.
271 
272 	$(H3 $(ID topic-input-handling) Input handling)
273 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
274 
275 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
276 
277 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
278 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
279 
280 		Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
281 
282 		---
283 		// dmd example.d simpledisplay.d color.d
284 		import arsd.simpledisplay;
285 		void main() {
286 			auto window = new SimpleWindow(200, 200);
287 			{ // introduce sub-scope
288 				auto painter = window.draw(); // begin drawing
289 				/* draw here */
290 				painter.outlineColor = Color.red;
291 				painter.fillColor = Color.black;
292 				painter.drawRectangle(Point(0, 0), 200, 200);
293 			} // end scope, calling `painter`'s destructor, drawing to the screen.
294 			window.eventLoop(0); // handle events
295 		}
296 		---
297 
298 		Painting is done based on two color properties, a pen and a brush.
299 
300 		At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
301 
302 		FIXME Add example of 2d opengl drawing here.
303 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
304 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
305 
306 		Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
307 
308 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
309 
310 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
311 
312 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
313 
314 		simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
315 
316 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
317 
318 		---
319 		import arsd.simpledisplay;
320 
321 		void main() {
322 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
323 
324 			float otherColor = 0.0;
325 			float colorDelta = 0.05;
326 
327 			window.redrawOpenGlScene = delegate() {
328 				glLoadIdentity();
329 				glBegin(GL_QUADS);
330 
331 				glColor3f(1.0, otherColor, 0);
332 				glVertex3f(-0.8, -0.8, 0);
333 
334 				glColor3f(1.0, otherColor, 1.0);
335 				glVertex3f(0.8, -0.8, 0);
336 
337 				glColor3f(0, 1.0, otherColor);
338 				glVertex3f(0.8, 0.8, 0);
339 
340 				glColor3f(otherColor, 0, 1.0);
341 				glVertex3f(-0.8, 0.8, 0);
342 
343 				glEnd();
344 			};
345 
346 			window.eventLoop(50, () {
347 				otherColor += colorDelta;
348 				if(otherColor > 1.0) {
349 					otherColor = 1.0;
350 					colorDelta = -0.05;
351 				}
352 				if(otherColor < 0) {
353 					otherColor = 0;
354 					colorDelta = 0.05;
355 				}
356 				// at the end of the timer, we have to request a redraw
357 				// or we won't see the changes.
358 				window.redrawOpenGlSceneSoon();
359 			});
360 		}
361 		---
362 
363 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
364 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
365 		simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too.
366 
367 		This example program shows how you can set up a shader to draw a rectangle:
368 
369 		---
370 		import arsd.simpledisplay;
371 
372 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
373 
374 		void main() {
375 			// First thing we do, before creating the window, is declare what version we want.
376 			setOpenGLContextVersion(3, 3);
377 			// turning off legacy compat is required to use version 3.3 and newer
378 			openGLContextCompatible = false;
379 
380 			uint VAO;
381 			OpenGlShader shader;
382 
383 			// then we can create the window.
384 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
385 
386 			// additional setup needs to be done when it is visible, simpledisplay offers a property
387 			// for exactly that:
388 			window.visibleForTheFirstTime = delegate() {
389 				// now with the window loaded, we can start loading the modern opengl functions.
390 
391 				// you MUST set the context first.
392 				window.setAsCurrentOpenGlContext;
393 				// then load the remainder of the library
394 				gl3.loadDynamicLibrary();
395 
396 				// now you can create the shaders, etc.
397 				shader = new OpenGlShader(
398 					OpenGlShader.Source(GL_VERTEX_SHADER, `
399 						#version 330 core
400 						layout (location = 0) in vec3 aPos;
401 						void main() {
402 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
403 						}
404 					`),
405 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
406 						#version 330 core
407 						out vec4 FragColor;
408 						uniform vec4 mycolor;
409 						void main() {
410 							FragColor = mycolor;
411 						}
412 					`),
413 				);
414 
415 				// and do whatever other setup you want.
416 
417 				float[] vertices = [
418 					0.5f,  0.5f, 0.0f,  // top right
419 					0.5f, -0.5f, 0.0f,  // bottom right
420 					-0.5f, -0.5f, 0.0f,  // bottom left
421 					-0.5f,  0.5f, 0.0f   // top left
422 				];
423 				uint[] indices = [  // note that we start from 0!
424 					0, 1, 3,  // first Triangle
425 					1, 2, 3   // second Triangle
426 				];
427 				uint VBO, EBO;
428 				glGenVertexArrays(1, &VAO);
429 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
430 				glBindVertexArray(VAO);
431 
432 				glGenBuffers(1, &VBO);
433 				glGenBuffers(1, &EBO);
434 
435 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
436 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
437 
438 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
439 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
440 
441 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
442 				glEnableVertexAttribArray(0);
443 
444 				// the library will set the initial viewport and trigger our first draw,
445 				// so these next two lines are NOT needed. they are just here as comments
446 				// to show what would happen next.
447 
448 				// glViewport(0, 0, window.width, window.height);
449 				// window.redrawOpenGlSceneNow();
450 			};
451 
452 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
453 			// it is our render method.
454 			window.redrawOpenGlScene = delegate() {
455 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
456 				glClear(GL_COLOR_BUFFER_BIT);
457 
458 				glUseProgram(shader.shaderProgram);
459 
460 				// the shader helper class has methods to set uniforms too
461 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
462 
463 				glBindVertexArray(VAO);
464 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
465 			};
466 
467 			window.eventLoop(0);
468 		}
469 		---
470 
471 	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.
472 
473 	$(H3 $(ID vulkan) Vulkan)
474 
475 	See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
476 
477 	https://github.com/adamdruppe/VulkanizeDSdpy
478 
479 	https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
480 
481 	$(H3 $(ID topic-images) Displaying images)
482 		You can also load PNG images using [arsd.png].
483 
484 		---
485 		// dmd example.d simpledisplay.d color.d png.d
486 		import arsd.simpledisplay;
487 		import arsd.png;
488 
489 		void main() {
490 			auto image = Image.fromMemoryImage(readPng("image.png"));
491 			displayImage(image);
492 		}
493 		---
494 
495 		Compile with `dmd example.d simpledisplay.d png.d`.
496 
497 		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.
498 
499 	$(H3 $(ID topic-sprites) Sprites)
500 		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.
501 
502 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
503 
504 	$(H3 $(ID topic-clipboard) Clipboard)
505 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
506 
507 		It also has helpers for handling X-specific events.
508 
509 	$(H3 $(ID topic-dnd) Drag and Drop)
510 		See [enableDragAndDrop] and [draggable].
511 
512 	$(H3 $(ID topic-timers) Timers)
513 		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].
514 
515 		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.
516 
517 		---
518 			import arsd.simpledisplay;
519 
520 			void main() {
521 				auto window = new SimpleWindow(400, 400);
522 				// every 100 ms, it will draw a random line
523 				// on the window.
524 				window.eventLoop(100, {
525 					auto painter = window.draw();
526 
527 					import std.random;
528 					// random color
529 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
530 					// random line
531 					painter.drawLine(
532 						Point(uniform(0, window.width), uniform(0, window.height)),
533 						Point(uniform(0, window.width), uniform(0, window.height)));
534 
535 				});
536 			}
537 		---
538 
539 		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.
540 
541 		The pulse timer and instances of the [Timer] class may be combined at will.
542 
543 		---
544 			import arsd.simpledisplay;
545 
546 			void main() {
547 				auto window = new SimpleWindow(400, 400);
548 				auto timer = new Timer(1000, delegate {
549 					auto painter = window.draw();
550 					painter.clear();
551 				});
552 
553 				window.eventLoop(0);
554 			}
555 		---
556 
557 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
558 
559 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
560 		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.
561 
562 		See also: `xwindows.d` from my github.
563 
564 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
565 		`handleNativeEvent` and `handleNativeGlobalEvent`.
566 
567 	$(H3 $(ID topic-integration) Integration with other libraries)
568 		Integration with a third-party event loop is possible.
569 
570 		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.
571 
572 	$(H3 $(ID topic-guis) GUI widgets)
573 		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!
574 
575 		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.
576 
577 		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.)
578 
579 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
580 
581 	$(H2 Platform-specific tips and tricks)
582 
583 	X_tips:
584 
585 	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.
586 
587 	$(H4 apitrace)
588 
589 	Out of the box, simpledisplay might not work as expected in combination with
590 	[apitrace](https://apitrace.github.io).
591 	However it can be instructed to specifically load the GL/GLX wrapper libraries provided by apitrace instead of
592 	the system libraries. This should restore the lost functionality.
593 
594 	$(NUMBERED_LIST
595 		* Compile with `-version=apitrace`.
596 		* When launching such a simpledisplay app, it must be able to locate the apitrace wrapper libraries.
597 		* Running the app will generate an apitrace trace file.
598 		  It should print a log message similar to "apitrace: loaded into /directory" during startup.
599 	)
600 
601 	There are multiple ways to enable a simpledisplay app to locate the wrapper libraries.
602 
603 	One way to achieved this is by pointing the `LD_LIBRARY_PATH` environment variable to the directory containing
604 	those wrappers.
605 
606 	```sh
607 	LD_LIBRARY_PATH=/path/to/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
608 
609 	# e.g.
610 	LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
611 	```
612 
613 	Alternatively, the simpledisplay app can also be launched via $(I apitrace).
614 
615 	```sh
616 	apitrace trace -a gl ./myapp
617 	```
618 
619 	Another way that seems to work is to preload `glxtrace.so` through `LD_PRELOAD`.
620 
621 	```sh
622 	LD_PRELOAD=/path/to/apitrace/wrappers/glxtrace.so ./myapp
623 	```
624 
625 	Windows_tips:
626 
627 	You can add icons or manifest files to your exe using a resource file.
628 
629 	To create a Windows .ico file, use the gimp or something. I'll write a helper
630 	program later.
631 
632 	Create `yourapp.rc`:
633 
634 	```rc
635 		1 ICON filename.ico
636 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
637 	```
638 
639 	And `yourapp.exe.manifest`:
640 
641 	```xml
642 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
643 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
644 		<assemblyIdentity
645 		    version="1.0.0.0"
646 		    processorArchitecture="*"
647 		    name="CompanyName.ProductName.YourApplication"
648 		    type="win32"
649 		/>
650 		<description>Your application description here.</description>
651 		<dependency>
652 		    <dependentAssembly>
653 			<assemblyIdentity
654 			    type="win32"
655 			    name="Microsoft.Windows.Common-Controls"
656 			    version="6.0.0.0"
657 			    processorArchitecture="*"
658 			    publicKeyToken="6595b64144ccf1df"
659 			    language="*"
660 			/>
661 		    </dependentAssembly>
662 		</dependency>
663 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
664 			<windowsSettings>
665 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
666 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
667 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
668 				<!-- to render crisply in DPI-unaware contexts -->
669 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
670 			</windowsSettings>
671 		</application>
672 		</assembly>
673 	```
674 
675 	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`.
676 
677 	Doing this lets you opt into various new things since Windows XP.
678 
679 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
680 
681 	$(H2 Tips)
682 
683 	$(H3 Name conflicts)
684 
685 	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:
686 
687 	---
688 	static import sdpy = arsd.simpledisplay;
689 	import arsd.simpledisplay : SimpleWindow;
690 
691 	void main() {
692 		auto window = new SimpleWindow();
693 		sdpy.EventLoop.get.run();
694 	}
695 	---
696 
697 	$(H2 $(ID developer-notes) Developer notes)
698 
699 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
700 	implementation though.
701 
702 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
703 	suck. If I was rewriting it, I wouldn't do it that way again.
704 
705 	This file must not have any more required dependencies. If you need bindings, add
706 	them right to this file. Once it gets into druntime and is there for a while, remove
707 	bindings from here to avoid conflicts (or put them in an appropriate version block
708 	so it continues to just work on old dmd), but wait a couple releases before making the
709 	transition so this module remains usable with older versions of dmd.
710 
711 	You may have optional dependencies if needed by putting them in version blocks or
712 	template functions. You may also extend the module with other modules with UFCS without
713 	actually editing this - that is nice to do if you can.
714 
715 	Try to make functions work the same way across operating systems. I typically make
716 	it thinly wrap Windows, then emulate that on Linux.
717 
718 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
719 	Phobos! So try to avoid it.
720 
721 	See more comments throughout the source.
722 
723 	I realize this file is fairly large, but over half that is just bindings at the bottom
724 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
725 	to understand. I suggest you jump around the source by looking for a particular
726 	declaration you're interested in, like `class SimpleWindow` using your editor's search
727 	function, then look at one piece at a time.
728 
729 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
730 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
731 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
732 
733 	I live in the eastern United States, so I will most likely not be around at night in
734 	that US east timezone.
735 
736 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
737 
738 	Building documentation: use my adrdox generator, `dub run adrdox`.
739 
740 	Examples:
741 
742 	$(DIV $(ID Event-example))
743 	$(H3 $(ID event-example) Event example)
744 	This program creates a window and draws events inside them as they
745 	happen, scrolling the text in the window as needed. Run this program
746 	and experiment to get a feel for where basic input events take place
747 	in the library.
748 
749 	---
750 	// dmd example.d simpledisplay.d color.d
751 	import arsd.simpledisplay;
752 	import std.conv;
753 
754 	void main() {
755 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
756 
757 		int y = 0;
758 
759 		void addLine(string text) {
760 			auto painter = window.draw();
761 
762 			if(y + painter.fontHeight >= window.height) {
763 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
764 				y -= painter.fontHeight;
765 			}
766 
767 			painter.outlineColor = Color.red;
768 			painter.fillColor = Color.black;
769 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
770 
771 			painter.outlineColor = Color.white;
772 
773 			painter.drawText(Point(10, y), text);
774 
775 			y += painter.fontHeight;
776 		}
777 
778 		window.eventLoop(1000,
779 		  () {
780 			addLine("Timer went off!");
781 		  },
782 		  (KeyEvent event) {
783 			addLine(to!string(event));
784 		  },
785 		  (MouseEvent event) {
786 			addLine(to!string(event));
787 		  },
788 		  (dchar ch) {
789 			addLine(to!string(ch));
790 		  }
791 		);
792 	}
793 	---
794 
795 	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.
796 
797 	$(COMMENT
798 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
799 
800 	---
801 
802 	---
803 	)
804 
805 	History:
806 		Initial release in April 2011.
807 
808 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
809 
810 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
811 
812 		On October 5, 2024, apitrace support was added for Linux targets.
813 +/
814 module arsd.simpledisplay;
815 
816 import arsd.core;
817 
818 // FIXME: tetris demo
819 // FIXME: space invaders demo
820 // FIXME: asteroids demo
821 
822 /++ $(ID Pong-example)
823 	$(H3 Pong)
824 
825 	This program creates a little Pong-like game. Player one is controlled
826 	with the keyboard.  Player two is controlled with the mouse. It demos
827 	the pulse timer, event handling, and some basic drawing.
828 +/
829 version(demos)
830 unittest {
831 	// dmd example.d simpledisplay.d color.d
832 	import arsd.simpledisplay;
833 
834 	enum paddleMovementSpeed = 8;
835 	enum paddleHeight = 48;
836 
837 	void main() {
838 		auto window = new SimpleWindow(600, 400, "Pong game!");
839 
840 		int playerOnePosition, playerTwoPosition;
841 		int playerOneMovement, playerTwoMovement;
842 		int playerOneScore, playerTwoScore;
843 
844 		int ballX, ballY;
845 		int ballDx, ballDy;
846 
847 		void serve() {
848 			import std.random;
849 
850 			ballX = window.width / 2;
851 			ballY = window.height / 2;
852 			ballDx = uniform(-4, 4) * 3;
853 			ballDy = uniform(-4, 4) * 3;
854 			if(ballDx == 0)
855 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
856 		}
857 
858 		serve();
859 
860 		window.eventLoop(50, // set a 50 ms timer pulls
861 			// This runs once per timer pulse
862 			delegate () {
863 				auto painter = window.draw();
864 
865 				painter.clear();
866 
867 				// Update everyone's motion
868 				playerOnePosition += playerOneMovement;
869 				playerTwoPosition += playerTwoMovement;
870 
871 				ballX += ballDx;
872 				ballY += ballDy;
873 
874 				// Bounce off the top and bottom edges of the window
875 				if(ballY + 7 >= window.height)
876 					ballDy = -ballDy;
877 				if(ballY - 8 <= 0)
878 					ballDy = -ballDy;
879 
880 				// Bounce off the paddle, if it is in position
881 				if(ballX - 8 <= 16) {
882 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
883 						ballDx = -ballDx + 1; // add some speed to keep it interesting
884 						ballDy += playerOneMovement; // and y movement based on your controls too
885 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
886 					} else {
887 						// Missed it
888 						playerTwoScore ++;
889 						serve();
890 					}
891 				}
892 
893 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
894 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
895 						ballDx = -ballDx - 1;
896 						ballDy += playerTwoMovement;
897 						ballX = window.width - 24;
898 					} else {
899 						// Missed it
900 						playerOneScore ++;
901 						serve();
902 					}
903 				}
904 
905 				// Draw the paddles
906 				painter.outlineColor = Color.black;
907 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
908 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
909 
910 				// Draw the ball
911 				painter.fillColor = Color.red;
912 				painter.outlineColor = Color.yellow;
913 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
914 
915 				// Draw the score
916 				painter.outlineColor = Color.blue;
917 				import std.conv;
918 				painter.drawText(Point(64, 4), to!string(playerOneScore));
919 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
920 
921 			},
922 			delegate (KeyEvent event) {
923 				// Player 1's controls are the arrow keys on the keyboard
924 				if(event.key == Key.Down)
925 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
926 				if(event.key == Key.Up)
927 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
928 
929 			},
930 			delegate (MouseEvent event) {
931 				// Player 2's controls are mouse movement while the left button is held down
932 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
933 					if(event.dy > 0)
934 						playerTwoMovement = paddleMovementSpeed;
935 					else if(event.dy < 0)
936 						playerTwoMovement = -paddleMovementSpeed;
937 				} else {
938 					playerTwoMovement = 0;
939 				}
940 			}
941 		);
942 	}
943 }
944 
945 /++ $(H3 $(ID example-minesweeper) Minesweeper)
946 
947 	This minesweeper demo shows how we can implement another classic
948 	game with simpledisplay and shows some mouse input and basic output
949 	code.
950 +/
951 version(demos)
952 unittest {
953 	import arsd.simpledisplay;
954 
955 	enum GameSquare {
956 		mine = 0,
957 		clear,
958 		m1, m2, m3, m4, m5, m6, m7, m8
959 	}
960 
961 	enum UserSquare {
962 		unknown,
963 		revealed,
964 		flagged,
965 		questioned
966 	}
967 
968 	enum GameState {
969 		inProgress,
970 		lose,
971 		win
972 	}
973 
974 	GameSquare[] board;
975 	UserSquare[] userState;
976 	GameState gameState;
977 	int boardWidth;
978 	int boardHeight;
979 
980 	bool isMine(int x, int y) {
981 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
982 			return false;
983 		return board[y * boardWidth + x] == GameSquare.mine;
984 	}
985 
986 	GameState reveal(int x, int y) {
987 		if(board[y * boardWidth + x] == GameSquare.clear) {
988 			floodFill(userState, boardWidth, boardHeight,
989 				UserSquare.unknown, UserSquare.revealed,
990 				x, y,
991 				(x, y) {
992 					if(board[y * boardWidth + x] == GameSquare.clear)
993 						return true;
994 					else {
995 						userState[y * boardWidth + x] = UserSquare.revealed;
996 						return false;
997 					}
998 				});
999 		} else {
1000 			userState[y * boardWidth + x] = UserSquare.revealed;
1001 			if(isMine(x, y))
1002 				return GameState.lose;
1003 		}
1004 
1005 		foreach(state; userState) {
1006 			if(state == UserSquare.unknown || state == UserSquare.questioned)
1007 				return GameState.inProgress;
1008 		}
1009 
1010 		return GameState.win;
1011 	}
1012 
1013 	void initializeBoard(int width, int height, int numberOfMines) {
1014 		boardWidth = width;
1015 		boardHeight = height;
1016 		board.length = width * height;
1017 
1018 		userState.length = width * height;
1019 		userState[] = UserSquare.unknown;
1020 
1021 		import std.algorithm, std.random, std.range;
1022 
1023 		board[] = GameSquare.clear;
1024 
1025 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
1026 			board[minePosition] = GameSquare.mine;
1027 
1028 		int x;
1029 		int y;
1030 		foreach(idx, ref square; board) {
1031 			if(square == GameSquare.clear) {
1032 				int danger = 0;
1033 				danger += isMine(x-1, y-1)?1:0;
1034 				danger += isMine(x-1, y)?1:0;
1035 				danger += isMine(x-1, y+1)?1:0;
1036 				danger += isMine(x, y-1)?1:0;
1037 				danger += isMine(x, y+1)?1:0;
1038 				danger += isMine(x+1, y-1)?1:0;
1039 				danger += isMine(x+1, y)?1:0;
1040 				danger += isMine(x+1, y+1)?1:0;
1041 
1042 				square = cast(GameSquare) (danger + 1);
1043 			}
1044 
1045 			x++;
1046 			if(x == width) {
1047 				x = 0;
1048 				y++;
1049 			}
1050 		}
1051 	}
1052 
1053 	void redraw(SimpleWindow window) {
1054 		import std.conv;
1055 
1056 		auto painter = window.draw();
1057 
1058 		painter.clear();
1059 
1060 		final switch(gameState) with(GameState) {
1061 			case inProgress:
1062 				break;
1063 			case win:
1064 				painter.fillColor = Color.green;
1065 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1066 				return;
1067 			case lose:
1068 				painter.fillColor = Color.red;
1069 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1070 				return;
1071 		}
1072 
1073 		int x = 0;
1074 		int y = 0;
1075 
1076 		foreach(idx, square; board) {
1077 			auto state = userState[idx];
1078 
1079 			final switch(state) with(UserSquare) {
1080 				case unknown:
1081 					painter.outlineColor = Color.black;
1082 					painter.fillColor = Color(128,128,128);
1083 
1084 					painter.drawRectangle(
1085 						Point(x * 20, y * 20),
1086 						20, 20
1087 					);
1088 				break;
1089 				case revealed:
1090 					if(square == GameSquare.clear) {
1091 						painter.outlineColor = Color.white;
1092 						painter.fillColor = Color.white;
1093 
1094 						painter.drawRectangle(
1095 							Point(x * 20, y * 20),
1096 							20, 20
1097 						);
1098 					} else {
1099 						painter.outlineColor = Color.black;
1100 						painter.fillColor = Color.white;
1101 
1102 						painter.drawText(
1103 							Point(x * 20, y * 20),
1104 							to!string(square)[1..2],
1105 							Point(x * 20 + 20, y * 20 + 20),
1106 							TextAlignment.Center | TextAlignment.VerticalCenter);
1107 					}
1108 				break;
1109 				case flagged:
1110 					painter.outlineColor = Color.black;
1111 					painter.fillColor = Color.red;
1112 					painter.drawRectangle(
1113 						Point(x * 20, y * 20),
1114 						20, 20
1115 					);
1116 				break;
1117 				case questioned:
1118 					painter.outlineColor = Color.black;
1119 					painter.fillColor = Color.yellow;
1120 					painter.drawRectangle(
1121 						Point(x * 20, y * 20),
1122 						20, 20
1123 					);
1124 				break;
1125 			}
1126 
1127 			x++;
1128 			if(x == boardWidth) {
1129 				x = 0;
1130 				y++;
1131 			}
1132 		}
1133 
1134 	}
1135 
1136 	void main() {
1137 		auto window = new SimpleWindow(200, 200);
1138 
1139 		initializeBoard(10, 10, 10);
1140 
1141 		redraw(window);
1142 		window.eventLoop(0,
1143 			delegate (MouseEvent me) {
1144 				if(me.type != MouseEventType.buttonPressed)
1145 					return;
1146 				auto x = me.x / 20;
1147 				auto y = me.y / 20;
1148 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1149 					if(me.button == MouseButton.left) {
1150 						gameState = reveal(x, y);
1151 					} else {
1152 						userState[y*boardWidth+x] = UserSquare.flagged;
1153 					}
1154 					redraw(window);
1155 				}
1156 			}
1157 		);
1158 	}
1159 }
1160 
1161 // FIXME: tetris demo
1162 // FIXME: space invaders demo
1163 // FIXME: asteroids demo
1164 
1165 version(OSX) version(DigitalMars) version=OSXCocoa;
1166 
1167 version(Emscripten) {
1168 	version=allow_unimplemented_features;
1169 	version=without_opengl;
1170 }
1171 
1172 
1173 version(OSXCocoa) {
1174 	version=without_opengl;
1175 	version=allow_unimplemented_features;
1176 	// version=OSXCocoa;
1177 	// pragma(linkerDirective, "-framework Cocoa");
1178 }
1179 
1180 version(without_opengl) {
1181 	enum SdpyIsUsingIVGLBinds = false;
1182 } else /*version(Posix)*/ {
1183 	static if (__traits(compiles, (){import iv.glbinds;})) {
1184 		enum SdpyIsUsingIVGLBinds = true;
1185 		public import iv.glbinds;
1186 		//pragma(msg, "SDPY: using iv.glbinds");
1187 	} else {
1188 		enum SdpyIsUsingIVGLBinds = false;
1189 	}
1190 //} else {
1191 //	enum SdpyIsUsingIVGLBinds = false;
1192 }
1193 
1194 version(Windows) {
1195 	//import core.sys.windows.windows;
1196 	import core.sys.windows.winnls;
1197 	import core.sys.windows.windef;
1198 	import core.sys.windows.basetyps;
1199 	import core.sys.windows.winbase;
1200 	import core.sys.windows.winuser;
1201 	import core.sys.windows.shellapi;
1202 	import core.sys.windows.wingdi;
1203 	static import gdi = core.sys.windows.wingdi; // so i
1204 
1205 	pragma(lib, "gdi32");
1206 	pragma(lib, "user32");
1207 
1208 	// for AlphaBlend... a breaking change....
1209 	version(CRuntime_DigitalMars) { } else
1210 		pragma(lib, "msimg32");
1211 
1212 	// core.sys.windows.dwmapi
1213 	private {
1214 		/++
1215 			See_also:
1216 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute
1217 		 +/
1218 		extern extern(Windows) HRESULT DwmGetWindowAttribute(
1219 			HWND hwnd,
1220 			DWORD dwAttribute,
1221 			PVOID pvAttribute,
1222 			DWORD cbAttribute
1223 		) nothrow @nogc;
1224 
1225 		/++
1226 			See_also:
1227 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
1228 		 +/
1229 		extern extern(Windows) HRESULT DwmSetWindowAttribute(
1230 			HWND hwnd,
1231 			DWORD dwAttribute,
1232 			LPCVOID pvAttribute,
1233 			DWORD cbAttribute
1234 		) nothrow @nogc;
1235 
1236 		/++
1237 			See_also:
1238 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
1239 		 +/
1240 		enum DWMWINDOWATTRIBUTE {
1241 			// incomplete, only declare what we need
1242 
1243 			/++
1244 				Usage:
1245 					pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*`
1246 
1247 				$(NOTE
1248 					Requires Windows 11 or later.
1249 				)
1250 			 +/
1251 			DWMWA_WINDOW_CORNER_PREFERENCE = 33,
1252 		}
1253 
1254 		/++
1255 			See_also:
1256 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
1257 		 +/
1258 		enum DWM_WINDOW_CORNER_PREFERENCE {
1259 			/// System decision
1260 			DWMWCP_DEFAULT = 0,
1261 
1262 			/// Never
1263 			DWMWCP_DONOTROUND = 1,
1264 
1265 			// If "appropriate"
1266 			DWMWCP_ROUND = 2,
1267 
1268 			// If "appropriate", but smaller radius
1269 			DWMWCP_ROUNDSMALL = 3
1270 		}
1271 
1272 		bool fromDWM(
1273 			DWM_WINDOW_CORNER_PREFERENCE value,
1274 			out CornerStyle result,
1275 		) @safe pure nothrow @nogc {
1276 			switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1277 				case DWMWCP_DEFAULT:
1278 					result = CornerStyle.automatic;
1279 					return true;
1280 				case DWMWCP_DONOTROUND:
1281 					result = CornerStyle.rectangular;
1282 					return true;
1283 				case DWMWCP_ROUND:
1284 					result = CornerStyle.rounded;
1285 					return true;
1286 				case DWMWCP_ROUNDSMALL:
1287 					result = CornerStyle.roundedSlightly;
1288 					return true;
1289 				default:
1290 					return false;
1291 			}
1292 		}
1293 
1294 		bool toDWM(
1295 			CornerStyle value,
1296 			out DWM_WINDOW_CORNER_PREFERENCE result,
1297 		) @safe pure nothrow @nogc {
1298 			final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1299 				case CornerStyle.automatic:
1300 					result = DWMWCP_DEFAULT;
1301 					return true;
1302 				case CornerStyle.rectangular:
1303 					result = DWMWCP_DONOTROUND;
1304 					return true;
1305 				case CornerStyle.rounded:
1306 					result = DWMWCP_ROUND;
1307 					return true;
1308 				case CornerStyle.roundedSlightly:
1309 					result = DWMWCP_ROUNDSMALL;
1310 					return true;
1311 			}
1312 		}
1313 
1314 		pragma(lib, "dwmapi");
1315 	}
1316 } else version(Emscripten) {
1317 } else version (linux) {
1318 	//k8: this is hack for rdmd. sorry.
1319 	static import core.sys.linux.epoll;
1320 	static import core.sys.linux.timerfd;
1321 }
1322 
1323 
1324 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1325 
1326 // http://wiki.dlang.org/Simpledisplay.d
1327 
1328 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1329 
1330 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1331 // but can i control the scroll lock led
1332 
1333 
1334 // Note: if you are using Image on X, you might want to do:
1335 /*
1336 	static if(UsingSimpledisplayX11) {
1337 		if(!Image.impl.xshmAvailable) {
1338 			// the images will use the slower XPutImage, you might
1339 			// want to consider an alternative method to get better speed
1340 		}
1341 	}
1342 
1343 	If the shared memory extension is available though, simpledisplay uses it
1344 	for a significant speed boost whenever you draw large Images.
1345 */
1346 
1347 // 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.
1348 
1349 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1350 
1351 /*
1352 	Biggest FIXME:
1353 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1354 
1355 		clean up opengl contexts when their windows close
1356 
1357 		fix resizing the bitmaps/pixmaps
1358 */
1359 
1360 // BTW on Windows:
1361 // -L/SUBSYSTEM:WINDOWS:5.0
1362 // to dmd will make a nice windows binary w/o a console if you want that.
1363 
1364 /*
1365 	Stuff to add:
1366 
1367 	use multibyte functions everywhere we can
1368 
1369 	OpenGL windows
1370 	more event stuff
1371 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1372 
1373 
1374 	resizeEvent
1375 		and make the windows non-resizable by default,
1376 		or perhaps stretched (if I can find something in X like StretchBlt)
1377 
1378 	take a screenshot function!
1379 
1380 	Pens and brushes?
1381 	Maybe a global event loop?
1382 
1383 	Mouse deltas
1384 	Key items
1385 */
1386 
1387 /*
1388 From MSDN:
1389 
1390 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1391 
1392 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.
1393 
1394 */
1395 
1396 version(Emscripten) {
1397 
1398 } else version(linux) {
1399 	version = X11;
1400 	version(without_libnotify) {
1401 		// we cool
1402 	}
1403 	else
1404 		version = libnotify;
1405 }
1406 
1407 version(libnotify) {
1408 	pragma(lib, "dl");
1409 	import core.sys.posix.dlfcn;
1410 
1411 	void delegate()[int] libnotify_action_delegates;
1412 	int libnotify_action_delegates_count;
1413 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1414 		auto idx = cast(int) user_data;
1415 		if(auto dgptr = idx in libnotify_action_delegates) {
1416 			(*dgptr)();
1417 			libnotify_action_delegates.remove(idx);
1418 		}
1419 	}
1420 
1421 	struct C_DynamicLibrary {
1422 		void* handle;
1423 		this(string name) {
1424 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1425 			if(handle is null)
1426 				throw new Exception("dlopen");
1427 		}
1428 
1429 		void close() {
1430 			dlclose(handle);
1431 		}
1432 
1433 		~this() {
1434 			// close
1435 		}
1436 
1437 		// FIXME: this looks up by name every time....
1438 		template call(string func, Ret, Args...) {
1439 			extern(C) Ret function(Args) fptr;
1440 			typeof(fptr) call() {
1441 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1442 				return fptr;
1443 			}
1444 		}
1445 	}
1446 
1447 	C_DynamicLibrary* libnotify;
1448 }
1449 
1450 version(OSX) {
1451 	version(OSXCocoa) {}
1452 	else { version = X11; }
1453 }
1454 	//version = OSXCocoa; // this was written by KennyTM
1455 version(FreeBSD)
1456 	version = X11;
1457 version(Solaris)
1458 	version = X11;
1459 
1460 version(X11) {
1461 	version(without_xft) {}
1462 	else version=with_xft;
1463 }
1464 
1465 void featureNotImplemented()() {
1466 	version(allow_unimplemented_features)
1467 		throw new NotYetImplementedException();
1468 	else
1469 		static assert(0);
1470 }
1471 
1472 // these are so the static asserts don't trigger unless you want to
1473 // add support to it for an OS
1474 version(Windows)
1475 	version = with_timer;
1476 version(linux)
1477 	version = with_timer;
1478 version(OSXCocoa)
1479 	version = with_timer;
1480 
1481 version(with_timer)
1482 	enum bool SimpledisplayTimerAvailable = true;
1483 else
1484 	enum bool SimpledisplayTimerAvailable = false;
1485 
1486 /// 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.
1487 version(Windows)
1488 	enum bool UsingSimpledisplayWindows = true;
1489 else
1490 	enum bool UsingSimpledisplayWindows = false;
1491 
1492 /// 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.
1493 version(X11)
1494 	enum bool UsingSimpledisplayX11 = true;
1495 else
1496 	enum bool UsingSimpledisplayX11 = false;
1497 
1498 /// 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.
1499 version(OSXCocoa)
1500 	enum bool UsingSimpledisplayCocoa = true;
1501 else
1502 	enum bool UsingSimpledisplayCocoa = false;
1503 
1504 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1505 version(Windows)
1506 	enum multipleWindowsSupported = true;
1507 else version(Emscripten)
1508 	enum multipleWindowsSupported = false;
1509 else version(X11)
1510 	enum multipleWindowsSupported = true;
1511 else version(OSXCocoa)
1512 	enum multipleWindowsSupported = true;
1513 else
1514 	static assert(0);
1515 
1516 version(without_opengl)
1517 	enum bool OpenGlEnabled = false;
1518 else
1519 	enum bool OpenGlEnabled = true;
1520 
1521 /++
1522 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1523 	If you mix this in above your `main` function, you no longer need to use the linker
1524 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1525 
1526 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1527 
1528 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1529 	stderr writeln. It will fail and throw an exception.
1530 
1531 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1532 
1533 	History:
1534 		Added November 24, 2021 (dub v10.4)
1535 +/
1536 mixin template EnableWindowsSubsystem() {
1537 	version(Windows)
1538 	version(CRuntime_Microsoft) {
1539 		pragma(linkerDirective, "/subsystem:windows");
1540 		version(LDC)
1541 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1542 		else
1543 			pragma(linkerDirective, "/entry:mainCRTStartup");
1544 	}
1545 }
1546 
1547 
1548 /++
1549 	After selecting a type from [WindowTypes], you may further customize
1550 	its behavior by setting one or more of these flags.
1551 
1552 
1553 	The different window types have different meanings of `normal`. If the
1554 	window type already is a good match for what you want to do, you should
1555 	just use [WindowFlags.normal], the default, which will do the right thing
1556 	for your users.
1557 
1558 	The window flags will not always be honored by the operating system
1559 	and window managers; they are hints, not commands.
1560 +/
1561 enum WindowFlags : int {
1562 	normal = 0, ///
1563 	skipTaskbar = 1, ///
1564 	alwaysOnTop = 2, ///
1565 	alwaysOnBottom = 4, ///
1566 	cannotBeActivated = 8, ///
1567 	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.
1568 	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.
1569 	/++
1570 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1571 		it is still a top-level window. This should NOT be set separately for most window types.
1572 
1573 		A transient window will not keep the application open if its main window closes.
1574 
1575 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1576 
1577 
1578 		From the ICCM:
1579 
1580 		$(BLOCKQUOTE
1581 			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.
1582 
1583 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1584 		)
1585 
1586 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1587 
1588 		History:
1589 			Added February 23, 2021 but not yet stabilized.
1590 	+/
1591 	transient = 64,
1592 	/++
1593 		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.
1594 
1595 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1596 
1597 		History:
1598 			Added April 1, 2022
1599 	+/
1600 	managesChildWindowFocus = 128,
1601 
1602 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1603 }
1604 
1605 /++
1606 	When creating a window, you can pass a type to SimpleWindow's constructor,
1607 	then further customize the window by changing `WindowFlags`.
1608 
1609 
1610 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1611 	use. The others are there to build a foundation for a higher level GUI toolkit,
1612 	but are themselves not as high level as you might think from their names.
1613 
1614 	This list is based on the EMWH spec for X11.
1615 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1616 +/
1617 enum WindowTypes : int {
1618 	/// An ordinary application window.
1619 	normal,
1620 	/// 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.
1621 	undecorated,
1622 	/// 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.
1623 	eventOnly,
1624 	/// A drop down menu, such as from a menu bar
1625 	dropdownMenu,
1626 	/// A popup menu, such as from a right click
1627 	popupMenu,
1628 	/// A popup bubble notification
1629 	notification,
1630 	/*
1631 	menu, /// a tearable menu bar
1632 	splashScreen, /// a loading splash screen for your application
1633 	tooltip, /// A tiny window showing temporary help text or something.
1634 	comboBoxDropdown,
1635 	toolbar
1636 	*/
1637 	/// a dialog box of some sort
1638 	dialog,
1639 	/// a child nested inside the parent. You must pass a parent window to the ctor
1640 	nestedChild,
1641 
1642 	/++
1643 		The type you get when you pass in an existing browser handle, which means most
1644 		of simpledisplay's fancy things will not be done since they were never set up.
1645 
1646 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1647 		failure; you should use the existing handle constructor.
1648 
1649 		History:
1650 			Added November 17, 2022 (previously it would have type `normal`)
1651 	+/
1652 	minimallyWrapped
1653 }
1654 
1655 
1656 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1657 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1658 private __gshared char* sdpyWindowClassStr = null;
1659 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1660 
1661 /**
1662 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1663 	You may want to change context version if you want to use advanced shaders or
1664 	other modern OpenGL techinques. This setting doesn't affect already created
1665 	windows. You may use version 2.1 as your default, which should be supported
1666 	by any box since 2006, so seems to be a reasonable choice.
1667 
1668 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1669 	old context creation code without any version specified. This is the safest
1670 	way to init OpenGL, but it may not give you access to advanced features.
1671 
1672 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1673 */
1674 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1675 
1676 /**
1677 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1678 	pipeline functions, and without "compatible" mode you won't be able to use
1679 	your old non-shader-based code with such contexts. By default SimpleDisplay
1680 	creates compatible context, so you can gradually upgrade your OpenGL code if
1681 	you want to (or leave it as is, as it should "just work").
1682 */
1683 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1684 
1685 /**
1686 	Set to `true` to allow creating OpenGL context with lower version than requested
1687 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1688 	`openGLContextFallbackActivated()` will return `true`.
1689 	*/
1690 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1691 
1692 /**
1693 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1694 	*/
1695 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1696 
1697 /++
1698 	History:
1699 		Added April 24, 2023  (dub v11.0)
1700 +/
1701 version(without_opengl) {} else
1702 auto openGLCurrentContext() {
1703 	version(Windows)
1704 		return wglGetCurrentContext();
1705 	else
1706 		return glXGetCurrentContext();
1707 }
1708 
1709 
1710 /**
1711 	Set window class name for all following `new SimpleWindow()` calls.
1712 
1713 	WARNING! For Windows, you should set your class name before creating any
1714 	window, and NEVER change it after that!
1715 */
1716 void sdpyWindowClass (const(char)[] v) {
1717 	import core.stdc.stdlib : realloc;
1718 	if (v.length == 0) v = "SimpleDisplayWindow";
1719 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1720 	if (sdpyWindowClassStr is null) return; // oops
1721 	sdpyWindowClassStr[0..v.length+1] = 0;
1722 	sdpyWindowClassStr[0..v.length] = v[];
1723 }
1724 
1725 /**
1726 	Get current window class name.
1727 */
1728 string sdpyWindowClass () @trusted {
1729 	if (sdpyWindowClassStr is null) return null;
1730 	foreach (immutable idx; 0..size_t.max-1) {
1731 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1732 	}
1733 	return null;
1734 }
1735 
1736 /++
1737 	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.
1738 
1739 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1740 +/
1741 float[2] getDpi() {
1742 	float[2] dpi;
1743 	version(Windows) {
1744 		HDC screen = GetDC(null);
1745 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1746 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1747 	} else version(X11) {
1748 		auto display = XDisplayConnection.get;
1749 		auto screen = DefaultScreen(display);
1750 
1751 		void fallback() {
1752 			/+
1753 			// 25.4 millimeters in an inch...
1754 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1755 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1756 			+/
1757 
1758 			// the physical size isn't actually as important as the logical size since this is
1759 			// all about scaling really
1760 			dpi[0] = 96;
1761 			dpi[1] = 96;
1762 		}
1763 
1764 		auto xft = getXftDpi();
1765 		if(xft is float.init)
1766 			fallback();
1767 		else {
1768 			dpi[0] = xft;
1769 			dpi[1] = xft;
1770 		}
1771 	}
1772 
1773 	return dpi;
1774 }
1775 
1776 version(X11)
1777 float getXftDpi() {
1778 	auto display = XDisplayConnection.get;
1779 
1780 	char* resourceString = XResourceManagerString(display);
1781 	XrmInitialize();
1782 
1783 	if (resourceString) {
1784 		auto db = XrmGetStringDatabase(resourceString);
1785 		XrmValue value;
1786 		char* type;
1787 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1788 			if (value.addr) {
1789 				import core.stdc.stdlib;
1790 				return atof(cast(char*) value.addr);
1791 			}
1792 		}
1793 	}
1794 
1795 	return float.init;
1796 }
1797 
1798 /++
1799 	Implementation used by [SimpleWindow.takeScreenshot].
1800 
1801 	Params:
1802 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1803 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1804 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1805 		x = the x-offset of the image to capture, from the left.
1806 		y = the y-offset of the image to capture, from the top.
1807 
1808 	History:
1809 		Added on March 14, 2021
1810 
1811 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1812 
1813 +/
1814 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1815 	TrueColorImage got;
1816 	version(X11) {
1817 		auto display = XDisplayConnection.get;
1818 		if(handle == 0)
1819 			handle = RootWindow(display, DefaultScreen(display));
1820 
1821 		if(width == 0 || height == 0) {
1822 			Window root;
1823 			int xpos, ypos;
1824 			uint widthret, heightret, borderret, depthret;
1825 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1826 
1827 			if(width == 0)
1828 				width = widthret;
1829 			if(height == 0)
1830 				height = heightret;
1831 		}
1832 
1833 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1834 
1835 		// https://github.com/adamdruppe/arsd/issues/98
1836 
1837 		auto i = new Image(image);
1838 		got = i.toTrueColorImage();
1839 
1840 		XDestroyImage(image);
1841 	} else version(Windows) {
1842 		auto hdc = GetDC(handle);
1843 		scope(exit) ReleaseDC(handle, hdc);
1844 
1845 		if(width == 0 || height == 0) {
1846 			BITMAP bmHeader;
1847 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1848 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1849 			if(width == 0)
1850 				width = bmHeader.bmWidth;
1851 			if(height == 0)
1852 				height = bmHeader.bmHeight;
1853 		}
1854 
1855 		auto i = new Image(width, height);
1856 		HDC hdcMem = CreateCompatibleDC(hdc);
1857 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1858 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1859 		SelectObject(hdcMem, hbmOld);
1860 		DeleteDC(hdcMem);
1861 
1862 		got = i.toTrueColorImage();
1863 	} else featureNotImplemented();
1864 
1865 	return got;
1866 }
1867 
1868 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1869 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1870 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1871 
1872 version(Windows)
1873 shared static this() {
1874 	auto lib = LoadLibrary("User32.dll");
1875 	if(lib is null)
1876 		return;
1877 	//scope(exit)
1878 		//FreeLibrary(lib);
1879 
1880 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1881 
1882 	if(SetProcessDpiAwarenessContext is null)
1883 		return;
1884 
1885 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1886 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1887 		//writeln(GetLastError());
1888 	}
1889 
1890 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1891 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1892 }
1893 
1894 /++
1895 	Blocking mode for event loop calls associated with a window instance.
1896 
1897 	History:
1898 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1899 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1900 		is, all would block until the application quit.
1901 
1902 		That behavior can still be achieved here with `untilApplicationQuits`,
1903 		or explicitly calling the top-level `EventLoop.get.run` function.
1904 +/
1905 enum BlockingMode {
1906 	/++
1907 		The event loop call will block until the whole application is ready
1908 		to quit if it is the only one running, but if it is nested inside
1909 		another one, it will only block until the window you're calling it on
1910 		closes.
1911 	+/
1912 	automatic             = 0x00,
1913 	/++
1914 		The event loop call will only return when the whole application
1915 		is ready to quit. This usually means all windows have been closed.
1916 
1917 		This is appropriate for your main application event loop.
1918 	+/
1919 	untilApplicationQuits = 0x01,
1920 	/++
1921 		The event loop will return when the window you're calling it on
1922 		closes. If there are other windows still open, they may be destroyed
1923 		unless you have another event loop running later.
1924 
1925 		This might be appropriate for a modal dialog box loop. Remember that
1926 		other windows are still processing input though, so you can end up
1927 		with a lengthy call stack if this happens in a loop, similar to a
1928 		recursive function (well, it literally is a recursive function, just
1929 		not an obvious looking one).
1930 	+/
1931 	untilWindowCloses     = 0x02,
1932 	/++
1933 		If an event loop is already running, this call will immediately
1934 		return, allowing the existing loop to handle it. If not, this call
1935 		will block until the condition you bitwise-or into the flag.
1936 
1937 		The default is to block until the application quits, same as with
1938 		the `automatic` setting (since if it were nested, which triggers until
1939 		window closes in automatic, this flag would instead not block at all),
1940 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1941 		it will only nest until the window closes. You might want that if you are
1942 		going to open two windows simultaneously and want closing just one of them
1943 		to trigger the event loop return.
1944 	+/
1945 	onlyIfNotNested       = 0x10,
1946 }
1947 
1948 /++
1949 	Window corner visuals preference
1950  +/
1951 enum CornerStyle {
1952 	/++
1953 		Use the default style automatically applied by the system or its window manager/compositor.
1954 	 +/
1955 	automatic,
1956 
1957 	/++
1958 		Prefer rectangular window corners
1959 	 +/
1960 	rectangular,
1961 
1962 	/++
1963 		Prefer rounded window corners
1964 	 +/
1965 	rounded,
1966 
1967 	/++
1968 		Prefer slightly-rounded window corners
1969 	 +/
1970 	roundedSlightly,
1971 }
1972 
1973 /++
1974 	The flagship window class.
1975 
1976 
1977 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1978 	out of more advanced or complex features of the underlying windowing system.
1979 
1980 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1981 	and get a suitable window to work with.
1982 
1983 	From there, you can opt into additional features, like custom resizability and OpenGL support
1984 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1985 	and customization flags with the final two constructor arguments.
1986 
1987 	If none of that works for you, you can also create a window using native function calls, then
1988 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1989 	though, if you do this, managing the window is still your own responsibility! Notably, you
1990 	will need to destroy it yourself.
1991 +/
1992 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1993 
1994 	/++
1995 		Copies the window's current state into a [TrueColorImage].
1996 
1997 		Be warned: this can be a very slow operation
1998 
1999 		History:
2000 			Actually implemented on March 14, 2021
2001 	+/
2002 	TrueColorImage takeScreenshot() {
2003 		version(Windows)
2004 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
2005 		else version(OSXCocoa)
2006 			throw new NotYetImplementedException();
2007 		else
2008 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
2009 	}
2010 
2011 	/++
2012 		Returns the actual logical DPI for the window on its current display monitor. If the window
2013 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
2014 
2015 		Please note this function may return zero if it doesn't know the answer!
2016 
2017 
2018 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
2019 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
2020 
2021 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
2022 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
2023 		window primarily resides on by checking the center point of the window against the monitor map.
2024 
2025 		Returns:
2026 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
2027 			assumes the X and Y dpi are the same.
2028 
2029 		History:
2030 			Added November 26, 2021 (dub v10.4)
2031 
2032 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
2033 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
2034 			that.
2035 
2036 		Bugs:
2037 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
2038 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
2039 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
2040 			and 1.5 on the secondary monitor.
2041 
2042 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
2043 			is a historical misnomer - the real thing of interest is the scale factor and due to
2044 			compatibility concerns the scale would modify dpi values to trick applications. But since
2045 			that's the terminology common out there, I used it too.
2046 
2047 		See_Also:
2048 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
2049 			as this since the window many be on a different monitor, but it is a reasonable fallback
2050 			to use if `actualDpi` returns 0.
2051 
2052 			[onDpiChanged] is changed when `actualDpi` has changed.
2053 	+/
2054 	int actualDpi() {
2055 		version(X11) bool useFallbackDpi = false;
2056 		if(!actualDpiLoadAttempted) {
2057 			// FIXME: do the actual monitor we are on
2058 			// and on X this is a good chance to load the monitor map.
2059 			version(Windows) {
2060 				if(GetDpiForWindow)
2061 					actualDpi_ = GetDpiForWindow(impl.hwnd);
2062 			} else version(X11) {
2063 				if(!xRandrInfoLoadAttemped) {
2064 					xRandrInfoLoadAttemped = true;
2065 					if(!XRandrLibrary.attempted) {
2066 						XRandrLibrary.loadDynamicLibrary();
2067 					}
2068 
2069 					if(XRandrLibrary.loadSuccessful) {
2070 						auto display = XDisplayConnection.get;
2071 						int scratch;
2072 						int major, minor;
2073 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
2074 							goto fallback;
2075 
2076 						XRRQueryVersion(display, &major, &minor);
2077 						if(major <= 1 && minor < 5)
2078 							goto fallback;
2079 
2080 						int count;
2081 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
2082 						if(monitors is null)
2083 							goto fallback;
2084 						scope(exit) XRRFreeMonitors(monitors);
2085 
2086 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
2087 						MonitorInfo.info.assumeSafeAppend();
2088 						foreach(idx, monitor; monitors[0 .. count]) {
2089 							MonitorInfo.info ~= MonitorInfo(
2090 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2091 								Size(monitor.mwidth, monitor.mheight),
2092 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
2093 							);
2094 
2095 							/+
2096 							if(monitor.mwidth == 0 || monitor.mheight == 0)
2097 							// unknown physical size, just guess 96 to avoid divide by zero
2098 							MonitorInfo.info ~= MonitorInfo(
2099 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2100 								Size(monitor.mwidth, monitor.mheight),
2101 								96
2102 							);
2103 							else
2104 							// and actual thing
2105 							MonitorInfo.info ~= MonitorInfo(
2106 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2107 								Size(monitor.mwidth, monitor.mheight),
2108 								minInternal(
2109 									// millimeter to int then rounding up.
2110 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
2111 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
2112 								)
2113 							);
2114 							+/
2115 						}
2116 					// writeln("Here", MonitorInfo.info);
2117 					}
2118 				}
2119 
2120 				if(XRandrLibrary.loadSuccessful) {
2121 					updateActualDpi(true);
2122 					// writeln("updated");
2123 
2124 					if(!requestedInput) {
2125 						// this is what requests live updates should the configuration change
2126 						// each time you select input, it sends an initial event, so very important
2127 						// to not get into a loop of selecting input, getting event, updating data,
2128 						// and reselecting input...
2129 						requestedInput = true;
2130 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
2131 						// writeln("requested input");
2132 					}
2133 				} else {
2134 					fallback:
2135 					// make sure we disable events that aren't coming
2136 					xrrEventBase = -1;
2137 					// best guess... respect the custom scaling user command to some extent at least though
2138 					useFallbackDpi = true;
2139 				}
2140 			} else version(OSXCocoa) {
2141 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
2142 			}
2143 			actualDpiLoadAttempted = true;
2144 		} else version(X11) if(MonitorInfo.info.length == 0) {
2145 			useFallbackDpi = true;
2146 		}
2147 
2148 		version(X11)
2149 		if(useFallbackDpi || actualDpi_ == 0) // FIXME: the actualDpi_ will be populated eventually when we get the first synthetic configure event from the window manager, but that might be a little while so actualDpi_ can be 0 until then...
2150 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
2151 		return actualDpi_;
2152 	}
2153 
2154 	private int actualDpi_;
2155 	private bool actualDpiLoadAttempted;
2156 
2157 	version(X11) private {
2158 		bool requestedInput;
2159 		static bool xRandrInfoLoadAttemped;
2160 		struct MonitorInfo {
2161 			Rectangle position;
2162 			Size size;
2163 			int dpi;
2164 
2165 			static MonitorInfo[] info;
2166 		}
2167 		bool screenPositionKnown;
2168 		int screenPositionX;
2169 		int screenPositionY;
2170 		void updateActualDpi(bool loadingNow = false) {
2171 			if(!loadingNow && !actualDpiLoadAttempted)
2172 				actualDpi(); // just to make it do the load
2173 			foreach(idx, m; MonitorInfo.info) {
2174 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
2175 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
2176 					actualDpi_ = m.dpi;
2177 					// writeln("monitor ", idx);
2178 					if(changed && onDpiChanged)
2179 						onDpiChanged();
2180 					break;
2181 				}
2182 			}
2183 		}
2184 	}
2185 
2186 	/++
2187 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2188 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2189 
2190 		History:
2191 			Added November 26, 2021 (dub v10.4)
2192 
2193 		See_Also:
2194 			[actualDpi]
2195 	+/
2196 	void delegate() onDpiChanged;
2197 
2198 	version(X11) {
2199 		void recreateAfterDisconnect() {
2200 			if(!stateDiscarded) return;
2201 
2202 			if(_parent !is null && _parent.stateDiscarded)
2203 				_parent.recreateAfterDisconnect();
2204 
2205 			bool wasHidden = hidden;
2206 
2207 			activeScreenPainter = null; // should already be done but just to confirm
2208 
2209 			actualDpi_ = 0;
2210 			actualDpiLoadAttempted = false;
2211 			xRandrInfoLoadAttemped = false;
2212 
2213 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2214 
2215 			if(auto dh = dropHandler) {
2216 				dropHandler = null;
2217 				enableDragAndDrop(this, dh);
2218 			}
2219 
2220 			if(recreateAdditionalConnectionState)
2221 				recreateAdditionalConnectionState();
2222 
2223 			hidden = wasHidden;
2224 			stateDiscarded = false;
2225 		}
2226 
2227 		bool stateDiscarded;
2228 		void discardConnectionState() {
2229 			if(XDisplayConnection.display)
2230 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2231 			if(discardAdditionalConnectionState)
2232 				discardAdditionalConnectionState();
2233 			stateDiscarded = true;
2234 		}
2235 
2236 		void delegate() discardAdditionalConnectionState;
2237 		void delegate() recreateAdditionalConnectionState;
2238 
2239 	}
2240 
2241 	private DropHandler dropHandler;
2242 
2243 	SimpleWindow _parent;
2244 	bool beingOpenKeepsAppOpen = true;
2245 	/++
2246 		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.
2247 
2248 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2249 
2250 		Params:
2251 
2252 		width = the width of the window's client area, in pixels
2253 		height = the height of the window's client area, in pixels
2254 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2255 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2256 		resizable = [Resizability] has three options:
2257 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2258 			$(P `fixedSize` will not allow the user to resize the window.)
2259 			$(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.)
2260 		windowType = The type of window you want to make.
2261 		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.
2262 		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".
2263 	+/
2264 	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) {
2265 		claimGuiThread();
2266 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2267 		this._width = this._virtualWidth = width;
2268 		this._height = this._virtualHeight = height;
2269 		this.openglMode = opengl;
2270 		version(X11) {
2271 			// auto scale not implemented except with opengl and even there it is kinda weird
2272 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2273 				resizable = Resizability.fixedSize;
2274 		}
2275 		this.resizability = resizable;
2276 		this.windowType = windowType;
2277 		this.customizationFlags = customizationFlags;
2278 		this._title = (title is null ? "D Application" : title);
2279 		this._parent = parent;
2280 		impl.createWindow(width, height, this._title, opengl, parent);
2281 
2282 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2283 			beingOpenKeepsAppOpen = false;
2284 	}
2285 
2286 	/// ditto
2287 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2288 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2289 	}
2290 
2291 	/// Same as above, except using the `Size` struct instead of separate width and height.
2292 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2293 		this(size.width, size.height, title, opengl, resizable);
2294 	}
2295 
2296 	/// ditto
2297 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2298 		this(size, title, opengl, resizable);
2299 	}
2300 
2301 
2302 	/++
2303 		Creates a window based on the given [Image]. It's client area
2304 		width and height is equal to the image. (A window's client area
2305 		is the drawable space inside; it excludes the title bar, etc.)
2306 
2307 		Windows based on images will not be resizable and do not use OpenGL.
2308 
2309 		It will draw the image in upon creation, but this will be overwritten
2310 		upon any draws, including the initial window visible event.
2311 
2312 		You probably do not want to use this and it may be removed from
2313 		the library eventually, or I might change it to be a "permanent"
2314 		background image; one that is automatically drawn on it before any
2315 		other drawing event. idk.
2316 	+/
2317 	this(Image image, string title = null) {
2318 		this(image.width, image.height, title);
2319 		this.image = image;
2320 	}
2321 
2322 	/++
2323 		Wraps a native window handle with very little additional processing - notably no destruction
2324 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2325 		windows created through the low level API (so you can use platform-specific options and
2326 		other details SimpleWindow does not expose) available to the event loop wrappers.
2327 	+/
2328 	this(NativeWindowHandle nativeWindow) {
2329 		windowType = WindowTypes.minimallyWrapped;
2330 		version(Windows)
2331 			impl.hwnd = nativeWindow;
2332 		else version(X11) {
2333 			impl.window = nativeWindow;
2334 			if(nativeWindow)
2335 				display = XDisplayConnection.get(); // get initial display to not segfault
2336 		} else version(Emscripten) {
2337 			// FIXME
2338 		} else version(OSXCocoa) {
2339 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2340 		} else featureNotImplemented();
2341 		// FIXME: set the size correctly
2342 		_width = 1;
2343 		_height = 1;
2344 		if(nativeWindow)
2345 			nativeMapping[cast(void*) nativeWindow] = this;
2346 
2347 		beingOpenKeepsAppOpen = false;
2348 
2349 		if(nativeWindow)
2350 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2351 		_suppressDestruction = true; // so it doesn't try to close
2352 	}
2353 
2354 	/++
2355 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2356 		The delegate will be called when the window manager asks you to take focus.
2357 
2358 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2359 
2360 		History:
2361 			Added April 1, 2022 (dub v10.8)
2362 	+/
2363 	SimpleWindow delegate() setRequestedInputFocus;
2364 
2365 	/// Experimental, do not use yet
2366 	/++
2367 		Grabs exclusive input from the user until you release it with
2368 		[releaseInputGrab].
2369 
2370 
2371 		Note: it is extremely rude to do this without good reason.
2372 		Reasons may include doing some kind of mouse drag operation
2373 		or popping up a temporary menu that should get events and will
2374 		be dismissed at ease by the user clicking away.
2375 
2376 		Params:
2377 			keyboard = do you want to grab keyboard input?
2378 			mouse = grab mouse input?
2379 			confine = confine the mouse cursor to inside this window?
2380 
2381 		History:
2382 			Prior to March 11, 2021, grabbing the keyboard would always also
2383 			set the X input focus. Now, it only focuses if it is a non-transient
2384 			window and otherwise manages the input direction internally.
2385 
2386 			This means spurious focus/blur events will no longer be sent and the
2387 			application will not steal focus from other applications (which the
2388 			window manager may have rejected anyway).
2389 	+/
2390 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2391 		static if(UsingSimpledisplayX11) {
2392 			XSync(XDisplayConnection.get, 0);
2393 			if(keyboard) {
2394 				if(isTransient && _parent) {
2395 					/*
2396 					FIXME:
2397 						setting the keyboard focus is not actually that helpful, what I more likely want
2398 						is the events from the parent window to be sent over here if we're transient.
2399 					*/
2400 
2401 					_parent.inputProxy = this;
2402 				} else {
2403 
2404 					SimpleWindow setTo;
2405 					if(setRequestedInputFocus !is null)
2406 						setTo = setRequestedInputFocus();
2407 					if(setTo is null)
2408 						setTo = this;
2409 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2410 				}
2411 			}
2412 			if(mouse) {
2413 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2414 				EventMask.PointerMotionMask // FIXME: not efficient
2415 				| EventMask.ButtonPressMask
2416 				| EventMask.ButtonReleaseMask
2417 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2418 				)
2419 			{
2420 				XSync(XDisplayConnection.get, 0);
2421 				import core.stdc.stdio;
2422 				printf("Grab input failed %d\n", res);
2423 				//throw new Exception("Grab input failed");
2424 			} else {
2425 				// cool
2426 			}
2427 			}
2428 
2429 		} else version(Windows) {
2430 			// FIXME: keyboard?
2431 			SetCapture(impl.hwnd);
2432 			if(confine) {
2433 				RECT rcClip;
2434 				//RECT rcOldClip;
2435 				//GetClipCursor(&rcOldClip);
2436 				GetWindowRect(hwnd, &rcClip);
2437 				ClipCursor(&rcClip);
2438 			}
2439 		} else version(Emscripten) {
2440 			// nothing necessary
2441 		} else version(OSXCocoa) {
2442 			// throw new NotYetImplementedException();
2443 		} else static assert(0);
2444 	}
2445 
2446 	private Point imePopupLocation = Point(0, 0);
2447 
2448 	/++
2449 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2450 
2451 		Bugs:
2452 			Not implemented outside X11.
2453 	+/
2454 	void setIMEPopupLocation(Point location) {
2455 		static if(UsingSimpledisplayX11) {
2456 			imePopupLocation = location;
2457 			updateIMEPopupLocation();
2458 		} else {
2459 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2460 			// throw new NotYetImplementedException();
2461 		}
2462 	}
2463 
2464 	/// ditto
2465 	void setIMEPopupLocation(int x, int y) {
2466 		return setIMEPopupLocation(Point(x, y));
2467 	}
2468 
2469 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2470 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2471 	// receives a ConfigureNotify event
2472 	private void updateIMEPopupLocation() {
2473 		static if(UsingSimpledisplayX11) {
2474 			if (xic is null) {
2475 				return;
2476 			}
2477 
2478 			XPoint nspot;
2479 			nspot.x = cast(short) imePopupLocation.x;
2480 			nspot.y = cast(short) imePopupLocation.y;
2481 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2482 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2483 			XFree(preeditAttr);
2484 		}
2485 	}
2486 
2487 	private bool imeFocused = true;
2488 
2489 	/++
2490 		Tells the IME whether or not an input field is currently focused in the window.
2491 
2492 		Bugs:
2493 			Not implemented outside X11.
2494 	+/
2495 	void setIMEFocused(bool value) {
2496 		imeFocused = value;
2497 		updateIMEFocused();
2498 	}
2499 
2500 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2501 	private void updateIMEFocused() {
2502 		static if(UsingSimpledisplayX11) {
2503 			if (xic is null) {
2504 				return;
2505 			}
2506 
2507 			if (focused && imeFocused) {
2508 				XSetICFocus(xic);
2509 			} else {
2510 				XUnsetICFocus(xic);
2511 			}
2512 		}
2513 	}
2514 
2515 	/++
2516 		Returns the native window.
2517 
2518 		History:
2519 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2520 			to access it through the `impl` member (which is semi-supported
2521 			but platform specific and here it is simple enough to offer an accessor).
2522 
2523 		Bugs:
2524 			Not implemented outside Windows or X11.
2525 	+/
2526 	NativeWindowHandle nativeWindowHandle() {
2527 		version(X11)
2528 			return impl.window;
2529 		else version(Windows)
2530 			return impl.hwnd;
2531 		else
2532 			throw new NotYetImplementedException();
2533 	}
2534 
2535 	private bool isTransient() {
2536 		with(WindowTypes)
2537 		final switch(windowType) {
2538 			case normal, undecorated, eventOnly:
2539 			case nestedChild, minimallyWrapped:
2540 				return (customizationFlags & WindowFlags.transient) ? true : false;
2541 			case dropdownMenu, popupMenu, notification, dialog:
2542 				return true;
2543 		}
2544 	}
2545 
2546 	private SimpleWindow inputProxy;
2547 
2548 	/++
2549 		Releases the grab acquired by [grabInput].
2550 	+/
2551 	void releaseInputGrab() {
2552 		static if(UsingSimpledisplayX11) {
2553 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2554 			if(_parent)
2555 				_parent.inputProxy = null;
2556 		} else version(Windows) {
2557 			ReleaseCapture();
2558 			ClipCursor(null);
2559 		} else version(OSXCocoa) {
2560 			// throw new NotYetImplementedException();
2561 		} else version(Emscripten) {
2562 			// nothing needed
2563 		} else static assert(0);
2564 	}
2565 
2566 	/++
2567 		Sets the input focus to this window.
2568 
2569 		You shouldn't call this very often - please let the user control the input focus.
2570 	+/
2571 	void focus() {
2572 		static if(UsingSimpledisplayX11) {
2573 			SimpleWindow setTo;
2574 			if(setRequestedInputFocus !is null)
2575 				setTo = setRequestedInputFocus();
2576 			if(setTo is null)
2577 				setTo = this;
2578 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2579 		} else version(Windows) {
2580 			SetFocus(this.impl.hwnd);
2581 		} else version(Emscripten) {
2582 			throw new NotYetImplementedException();
2583 		} else version(OSXCocoa) {
2584 			throw new NotYetImplementedException();
2585 		} else static assert(0);
2586 	}
2587 
2588 	/++
2589 		Requests attention from the user for this window.
2590 
2591 
2592 		The typical result of this function is to change the color
2593 		of the taskbar icon, though it may be tweaked on specific
2594 		platforms.
2595 
2596 		It is meant to unobtrusively tell the user that something
2597 		relevant to them happened in the background and they should
2598 		check the window when they get a chance. Upon receiving the
2599 		keyboard focus, the window will automatically return to its
2600 		natural state.
2601 
2602 		If the window already has the keyboard focus, this function
2603 		may do nothing, because the user is presumed to already be
2604 		giving the window attention.
2605 
2606 		Implementation_note:
2607 
2608 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2609 		atom on X11 and the FlashWindow function on Windows.
2610 	+/
2611 	void requestAttention() {
2612 		if(_focused)
2613 			return;
2614 
2615 		version(Windows) {
2616 			FLASHWINFO info;
2617 			info.cbSize = info.sizeof;
2618 			info.hwnd = impl.hwnd;
2619 			info.dwFlags = FLASHW_TRAY;
2620 			info.uCount = 1;
2621 
2622 			FlashWindowEx(&info);
2623 
2624 		} else version(X11) {
2625 			demandingAttention = true;
2626 			demandAttention(this, true);
2627 		} else version(Emscripten) {
2628 			throw new NotYetImplementedException();
2629 		} else version(OSXCocoa) {
2630 			throw new NotYetImplementedException();
2631 		} else static assert(0);
2632 	}
2633 
2634 	private bool _focused;
2635 
2636 	version(X11) private bool demandingAttention;
2637 
2638 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2639 	/// You'll have to call `close()` manually if you set this delegate.
2640 	void delegate () closeQuery;
2641 
2642 	/// This will be called when window visibility was changed.
2643 	void delegate (bool becomesVisible) visibilityChanged;
2644 
2645 	/// This will be called when window becomes visible for the first time.
2646 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2647 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2648 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2649 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2650 	private bool _visibleForTheFirstTimeCalled;
2651 	void delegate () visibleForTheFirstTime;
2652 
2653 	/// Returns true if the window has been closed.
2654 	final @property bool closed() { return _closed; }
2655 
2656 	private final @property bool notClosed() { return !_closed; }
2657 
2658 	/// Returns true if the window is focused.
2659 	final @property bool focused() { return _focused; }
2660 
2661 	private bool _visible;
2662 	/// Returns true if the window is visible (mapped).
2663 	final @property bool visible() { return _visible; }
2664 
2665 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2666 	void close() {
2667 		if (!_closed) {
2668 			runInGuiThread( {
2669 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2670 				if (onClosing !is null) onClosing();
2671 				impl.closeWindow();
2672 				_closed = true;
2673 			} );
2674 		}
2675 	}
2676 
2677 	/++
2678 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2679 
2680 		History:
2681 			Overload added on March 7, 2021.
2682 	+/
2683 	void close() shared {
2684 		(cast() this).close();
2685 	}
2686 
2687 	/++
2688 
2689 	+/
2690 	void maximize() {
2691 		version(Windows)
2692 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2693 		else version(X11) {
2694 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2695 
2696 			// also note _NET_WM_STATE_FULLSCREEN
2697 		}
2698 
2699 	}
2700 
2701 	private bool _fullscreen;
2702 	version(Windows)
2703 	private WINDOWPLACEMENT g_wpPrev;
2704 
2705 	/// not fully implemented but planned for a future release
2706 	void fullscreen(bool yes) {
2707 		version(Windows) {
2708 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2709 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2710 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2711 				MONITORINFO mi;
2712 				mi.cbSize = MONITORINFO.sizeof;
2713 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2714 					GetMonitorInfo(MonitorFromWindow(hwnd,
2715 					               MONITOR_DEFAULTTOPRIMARY), &mi)) {
2716 					SetWindowLong(hwnd, GWL_STYLE,
2717 					              dwStyle & ~WS_OVERLAPPEDWINDOW);
2718 					SetWindowPos(hwnd, HWND_TOP,
2719 					             mi.rcMonitor.left, mi.rcMonitor.top,
2720 					             mi.rcMonitor.right - mi.rcMonitor.left,
2721 					             mi.rcMonitor.bottom - mi.rcMonitor.top,
2722 					             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2723 				}
2724 			} else {
2725 				SetWindowLong(hwnd, GWL_STYLE,
2726 				              dwStyle | WS_OVERLAPPEDWINDOW);
2727 				SetWindowPlacement(hwnd, &g_wpPrev);
2728 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2729 				             SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2730 				             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2731 			}
2732 
2733 		} else version(X11) {
2734 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2735 		}
2736 
2737 		_fullscreen = yes;
2738 
2739 	}
2740 
2741 	bool fullscreen() {
2742 		return _fullscreen;
2743 	}
2744 
2745 	/++
2746 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2747 
2748 	+/
2749 	void minimize() {
2750 		version(Windows)
2751 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2752 		//else version(X11)
2753 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2754 	}
2755 
2756 	/// Alias for `hidden = false`
2757 	void show() {
2758 		hidden = false;
2759 	}
2760 
2761 	/// Alias for `hidden = true`
2762 	void hide() {
2763 		hidden = true;
2764 	}
2765 
2766 	/// Hide cursor when it enters the window.
2767 	void hideCursor() {
2768 		version(OSXCocoa) throw new NotYetImplementedException(); else
2769 		if (!_closed) impl.hideCursor();
2770 	}
2771 
2772 	/// Don't hide cursor when it enters the window.
2773 	void showCursor() {
2774 		version(OSXCocoa) throw new NotYetImplementedException(); else
2775 		if (!_closed) impl.showCursor();
2776 	}
2777 
2778 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2779 	 *
2780 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2781 	 * control. Try to think for other approaches before using this function.
2782 	 *
2783 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2784 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2785 	 *       receive "mouse moved here" event.
2786 	 */
2787 	bool warpMouse (int x, int y) {
2788 		version(X11) {
2789 			if (!_closed) { impl.warpMouse(x, y); return true; }
2790 		} else version(Windows) {
2791 			if (!_closed) {
2792 				POINT point;
2793 				point.x = x;
2794 				point.y = y;
2795 				if(ClientToScreen(impl.hwnd, &point)) {
2796 					SetCursorPos(point.x, point.y);
2797 					return true;
2798 				}
2799 			}
2800 		}
2801 		return false;
2802 	}
2803 
2804 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2805 	void sendDummyEvent () {
2806 		version(X11) {
2807 			if (!_closed) { impl.sendDummyEvent(); }
2808 		}
2809 	}
2810 
2811 	/// Set window minimal size.
2812 	void setMinSize (int minwidth, int minheight) {
2813 		version(OSXCocoa) throw new NotYetImplementedException(); else
2814 		if (!_closed) impl.setMinSize(minwidth, minheight);
2815 	}
2816 
2817 	/// Set window maximal size.
2818 	void setMaxSize (int maxwidth, int maxheight) {
2819 		version(OSXCocoa) throw new NotYetImplementedException(); else
2820 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2821 	}
2822 
2823 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2824 	/// Currently only supported on X11.
2825 	void setResizeGranularity (int granx, int grany) {
2826 		version(OSXCocoa) throw new NotYetImplementedException(); else
2827 		if (!_closed) impl.setResizeGranularity(granx, grany);
2828 	}
2829 
2830 	/// Move window.
2831 	void move(int x, int y) {
2832 		version(OSXCocoa) throw new NotYetImplementedException(); else
2833 		if (!_closed) impl.move(x, y);
2834 	}
2835 
2836 	/// ditto
2837 	void move(Point p) {
2838 		version(OSXCocoa) throw new NotYetImplementedException(); else
2839 		if (!_closed) impl.move(p.x, p.y);
2840 	}
2841 
2842 	/++
2843 		Resize window.
2844 
2845 		Note that the width and height of the window are NOT instantly
2846 		updated - it waits for the window manager to approve the resize
2847 		request, which means you must return to the event loop before the
2848 		width and height are actually changed.
2849 	+/
2850 	void resize(int w, int h) {
2851 		if(!_closed && _fullscreen) fullscreen = false;
2852 		version(OSXCocoa) throw new NotYetImplementedException(); else
2853 		if (!_closed) impl.resize(w, h);
2854 	}
2855 
2856 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2857 	void moveResize (int x, int y, int w, int h) {
2858 		if(!_closed && _fullscreen) fullscreen = false;
2859 		version(OSXCocoa) throw new NotYetImplementedException(); else
2860 		if (!_closed) impl.moveResize(x, y, w, h);
2861 	}
2862 
2863 	private bool _hidden;
2864 
2865 	/// Returns true if the window is hidden.
2866 	final @property bool hidden() {
2867 		return _hidden;
2868 	}
2869 
2870 	/// Shows or hides the window based on the bool argument.
2871 	final @property void hidden(bool b) {
2872 		_hidden = b;
2873 		version(Windows) {
2874 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2875 		} else version(X11) {
2876 			if(b)
2877 				//XUnmapWindow(impl.display, impl.window);
2878 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2879 			else
2880 				XMapWindow(impl.display, impl.window);
2881 		} else version(OSXCocoa) {
2882 			// throw new NotYetImplementedException();
2883 		} else version(Emscripten) {
2884 		} else static assert(0);
2885 	}
2886 
2887 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2888 	void opacity(double opacity) @property
2889 	in {
2890 		assert(opacity >= 0 && opacity <= 1);
2891 	} do {
2892 		version (Windows) {
2893 			impl.setOpacity(cast(ubyte)(255 * opacity));
2894 		} else version (X11) {
2895 			impl.setOpacity(cast(uint)(uint.max * opacity));
2896 		} else throw new NotYetImplementedException();
2897 	}
2898 
2899 	/++
2900 		Sets your event handlers, without entering the event loop. Useful if you
2901 		have multiple windows - set the handlers on each window, then only do
2902 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2903 
2904 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2905 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2906 		delegate signatures.
2907 	+/
2908 	void setEventHandlers(T...)(T eventHandlers) {
2909 		// FIXME: add more events
2910 		foreach(handler; eventHandlers) {
2911 			static if(__traits(compiles, handleKeyEvent = handler)) {
2912 				handleKeyEvent = handler;
2913 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2914 				handleCharEvent = handler;
2915 			} else static if(__traits(compiles, handlePulse = handler)) {
2916 				handlePulse = handler;
2917 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2918 				handleMouseEvent = handler;
2919 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2920 		}
2921 	}
2922 
2923 	/++
2924 		The event loop automatically returns when the window is closed
2925 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2926 		pulse timer is created. The event loop will block until an event
2927 		arrives or the pulse timer goes off.
2928 
2929 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2930 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2931 		[handleMouseEvent], based on the signature of delegates you provide.
2932 
2933 		Give one with no parameters to set a timer pulse handler. Give one that
2934 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2935 		and one that takes `dchar` for a char event handler. You can use as many
2936 		or as few handlers as you need for your application.
2937 
2938 		Bugs:
2939 
2940 		$(PITFALL
2941 			You should always have one event loop live for your application.
2942 			If you make two windows in sequence, the second call to eventLoop
2943 			might fail:
2944 
2945 			---
2946 			// don't do this!
2947 			auto window = new SimpleWindow();
2948 			window.eventLoop(0);
2949 
2950 			auto window2 = new SimpleWindow();
2951 			window2.eventLoop(0); // problematic! might crash
2952 			---
2953 
2954 			simpledisplay's current implementation assumes that final cleanup is
2955 			done when the event loop refcount reaches zero. So after the first
2956 			eventLoop returns, when there isn't already another one active, it assumes
2957 			the program will exit soon and cleans up.
2958 
2959 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2960 			it eventually, but in the mean time, there's an easy solution:
2961 
2962 			---
2963 			// do this
2964 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2965 
2966 			auto window = new SimpleWindow();
2967 			window.eventLoop(0);
2968 
2969 			auto window2 = new SimpleWindow();
2970 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2971 			---
2972 
2973 			By adding a top-level reference to the event loop, it ensures the final cleanup
2974 			is not performed until it goes out of scope too, letting the individual window loops
2975 			work without trouble despite the bug.
2976 		)
2977 
2978 		History:
2979 			The overload without `pulseTimeout` was added on December 8, 2021.
2980 
2981 			On December 9, 2021, the default blocking mode (which is now configurable
2982 			because [eventLoopWithBlockingMode] was added) switched from
2983 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2984 			should almost never be noticeable to you since the typical simpledisplay
2985 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2986 
2987 		See_Also:
2988 			[eventLoopWithBlockingMode]
2989 	+/
2990 	final int eventLoop(T...)(
2991 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2992 		T eventHandlers) /// delegate list like std.concurrency.receive
2993 	{
2994 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2995 	}
2996 
2997 	/// ditto
2998 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2999 	{
3000 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
3001 	}
3002 
3003 	/++
3004 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
3005 
3006 		History:
3007 			Added December 8, 2021 (dub v10.5)
3008 
3009 			Previously, this implementation was right inside [eventLoop], but when I wanted
3010 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
3011 			just renamed it instead of adding as an overload. Besides, the new name makes it
3012 			easier to remember the order and avoids ambiguity between two int-like params anyway.
3013 
3014 		See_Also:
3015 			[SimpleWindow.eventLoop], [EventLoop]
3016 
3017 		Bugs:
3018 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
3019 	+/
3020 	final int eventLoopWithBlockingMode(T...)(
3021 		BlockingMode blockingMode, /// when you want this function to block until
3022 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3023 		T eventHandlers) /// delegate list like std.concurrency.receive
3024 	{
3025 		setEventHandlers(eventHandlers);
3026 
3027 		version(with_eventloop) {
3028 			// delegates event loop to my other module
3029 			version(X11)
3030 				XFlush(display);
3031 
3032 			import arsd.eventloop;
3033 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
3034 			scope(exit) clearInterval(handle);
3035 
3036 			loop();
3037 			return 0;
3038 		} else version(OSXCocoa) {
3039 			// FIXME
3040 			if (handlePulse !is null && pulseTimeout != 0) {
3041 				timer = NSTimer.schedule(pulseTimeout*1e-3,
3042 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
3043 					null, true);
3044 			}
3045 
3046 			view.setNeedsDisplay(true);
3047 
3048 			NSApp.run();
3049 			return 0;
3050 		} else {
3051 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
3052 
3053 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
3054 				return 0;
3055 
3056 			return el.run(
3057 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
3058 					null :
3059 					&this.notClosed
3060 			);
3061 		}
3062 	}
3063 
3064 	/++
3065 		This lets you draw on the window (or its backing buffer) using basic
3066 		2D primitives.
3067 
3068 		Be sure to call this in a limited scope because your changes will not
3069 		actually appear on the window until ScreenPainter's destructor runs.
3070 
3071 		Returns: an instance of [ScreenPainter], which has the drawing methods
3072 		on it to draw on this window.
3073 
3074 		Params:
3075 			manualInvalidations = if you set this to true, you will need to
3076 			set the invalid rectangle on the painter yourself. If false, it
3077 			assumes the whole window has been redrawn each time you draw.
3078 
3079 			Only invalidated rectangles are blitted back to the window when
3080 			the destructor runs. Doing this yourself can reduce flickering
3081 			of child windows.
3082 
3083 		History:
3084 			The `manualInvalidations` parameter overload was added on
3085 			December 30, 2021 (dub v10.5)
3086 	+/
3087 	ScreenPainter draw() {
3088 		return draw(false);
3089 	}
3090 	/// ditto
3091 	ScreenPainter draw(bool manualInvalidations) {
3092 		return impl.getPainter(manualInvalidations);
3093 	}
3094 
3095 	// This is here to implement the interface we use for various native handlers.
3096 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
3097 
3098 	// maps native window handles to SimpleWindow instances, if there are any
3099 	// you shouldn't need this, but it is public in case you do in a native event handler or something
3100 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
3101 	version(OSXCocoa)
3102 	public __gshared SimpleWindow[void*] nativeMapping;
3103 	else
3104 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
3105 
3106 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
3107 	private int _virtualWidth;
3108 	private int _virtualHeight;
3109 
3110 	/// Width of the window's drawable client area, in pixels.
3111 	@scriptable
3112 	final @property int width() const pure nothrow @safe @nogc {
3113 		if(resizability == Resizability.automaticallyScaleIfPossible)
3114 			return _virtualWidth;
3115 		else
3116 			return _width;
3117 	}
3118 
3119 	/// Height of the window's drawable client area, in pixels.
3120 	@scriptable
3121 	final @property int height() const pure nothrow @safe @nogc {
3122 		if(resizability == Resizability.automaticallyScaleIfPossible)
3123 			return _virtualHeight;
3124 		else
3125 			return _height;
3126 	}
3127 
3128 	/++
3129 		Returns the actual size of the window, bypassing the logical
3130 		illusions of [Resizability.automaticallyScaleIfPossible].
3131 
3132 		History:
3133 			Added November 11, 2022 (dub v10.10)
3134 	+/
3135 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
3136 		return Size(_width, _height);
3137 	}
3138 
3139 
3140 	private int _width;
3141 	private int _height;
3142 
3143 	// HACK: making the best of some copy constructor woes with refcounting
3144 	private ScreenPainterImplementation* activeScreenPainter_;
3145 
3146 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
3147 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
3148 
3149 	private OpenGlOptions openglMode;
3150 	private Resizability resizability;
3151 	private WindowTypes windowType;
3152 	private int customizationFlags;
3153 
3154 	/// `true` if OpenGL was initialized for this window.
3155 	@property bool isOpenGL () const pure nothrow @safe @nogc {
3156 		version(without_opengl)
3157 			return false;
3158 		else
3159 			return (openglMode == OpenGlOptions.yes);
3160 	}
3161 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
3162 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
3163 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
3164 
3165 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3166 	/// to call this, as it's not recommended to share window between threads.
3167 	void mtLock () {
3168 		version(X11) {
3169 			XLockDisplay(this.display);
3170 		}
3171 	}
3172 
3173 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3174 	/// to call this, as it's not recommended to share window between threads.
3175 	void mtUnlock () {
3176 		version(X11) {
3177 			XUnlockDisplay(this.display);
3178 		}
3179 	}
3180 
3181 	/// Emit a beep to get user's attention.
3182 	void beep () {
3183 		version(X11) {
3184 			XBell(this.display, 100);
3185 		} else version(Windows) {
3186 			MessageBeep(0xFFFFFFFF);
3187 		}
3188 	}
3189 
3190 
3191 
3192 	version(without_opengl) {} else {
3193 
3194 		/// 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`.
3195 		void delegate() redrawOpenGlScene;
3196 
3197 		/// This will allow you to change OpenGL vsync state.
3198 		final @property void vsync (bool wait) {
3199 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3200 			version(X11) {
3201 				setAsCurrentOpenGlContext();
3202 				glxSetVSync(display, impl.window, wait);
3203 			} else version(Windows) {
3204 				setAsCurrentOpenGlContext();
3205 				wglSetVSync(wait);
3206 			}
3207 		}
3208 
3209 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3210 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3211 		/// enough without waiting 'em to finish their frame business.
3212 		bool useGLFinish = true;
3213 
3214 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3215 		/// 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.
3216 		void redrawOpenGlSceneNow() {
3217 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3218 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3219 			if(redrawOpenGlScene is null)
3220 				return;
3221 
3222 			this.mtLock();
3223 			scope(exit) this.mtUnlock();
3224 
3225 			this.setAsCurrentOpenGlContext();
3226 
3227 			redrawOpenGlScene();
3228 
3229 			this.swapOpenGlBuffers();
3230 			// 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.
3231 			if (useGLFinish) glFinish();
3232 		}
3233 
3234 		private bool redrawOpenGlSceneSoonSet = false;
3235 		private static class RedrawOpenGlSceneEvent {
3236 			SimpleWindow w;
3237 			this(SimpleWindow w) { this.w = w; }
3238 		}
3239 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3240 		/++
3241 			Queues an opengl redraw as soon as the other pending events are cleared.
3242 		+/
3243 		void redrawOpenGlSceneSoon() {
3244 			if(redrawOpenGlScene is null)
3245 				return;
3246 
3247 			if(!redrawOpenGlSceneSoonSet) {
3248 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3249 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3250 				redrawOpenGlSceneSoonSet = true;
3251 			}
3252 			this.postEvent(redrawOpenGlSceneEvent, true);
3253 		}
3254 
3255 
3256 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3257 		void setAsCurrentOpenGlContext() {
3258 			assert(openglMode == OpenGlOptions.yes);
3259 			version(X11) {
3260 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3261 					throw new Exception("glXMakeCurrent");
3262 			} else version(Windows) {
3263 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3264 				if (!wglMakeCurrent(ghDC, ghRC))
3265 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3266 			}
3267 		}
3268 
3269 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3270 		/// This doesn't throw, returning success flag instead.
3271 		bool setAsCurrentOpenGlContextNT() nothrow {
3272 			assert(openglMode == OpenGlOptions.yes);
3273 			version(X11) {
3274 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3275 			} else version(Windows) {
3276 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3277 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3278 			}
3279 		}
3280 
3281 		/// 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.
3282 		/// This doesn't throw, returning success flag instead.
3283 		bool releaseCurrentOpenGlContext() nothrow {
3284 			assert(openglMode == OpenGlOptions.yes);
3285 			version(X11) {
3286 				return (glXMakeCurrent(display, 0, null) != 0);
3287 			} else version(Windows) {
3288 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3289 				return wglMakeCurrent(ghDC, null) ? true : false;
3290 			}
3291 		}
3292 
3293 		/++
3294 			simpledisplay always uses double buffering, usually automatically. This
3295 			manually swaps the OpenGL buffers. You should only use this if you are NOT
3296 			using the [redrawOpenGlScene] delegate.
3297 
3298 
3299 			You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it
3300 			for you after calling your `redrawOpenGlScene`. Please note that once you swap
3301 			buffers, the contents become undefined - the implementation, in the OpenGL driver
3302 			or the desktop compositor, may not actually just swap two buffers. The back buffer's
3303 			contents are $(B undefined) after calling this function.
3304 
3305 			See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers
3306 			and https://linux.die.net/man/3/glxswapbuffers
3307 
3308 			Remember that this may throw an exception, which you can catch in a multithreaded
3309 			application to keep your thread from dying from an unhandled exception.
3310 		+/
3311 		void swapOpenGlBuffers() {
3312 			assert(openglMode == OpenGlOptions.yes);
3313 			version(X11) {
3314 				if (!this._visible) return; // no need to do this if window is invisible
3315 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3316 				glXSwapBuffers(display, impl.window);
3317 			} else version(Windows) {
3318 				SwapBuffers(ghDC);
3319 			}
3320 		}
3321 	}
3322 
3323 	/++
3324 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3325 
3326 
3327 		---
3328 			auto window = new SimpleWindow(100, 100, "First title");
3329 			window.title = "A new title";
3330 		---
3331 
3332 		You may call this function at any time.
3333 	+/
3334 	@property void title(string title) {
3335 		_title = title;
3336 		version(OSXCocoa) throw new NotYetImplementedException(); else
3337 		impl.setTitle(title);
3338 	}
3339 
3340 	private string _title;
3341 
3342 	/// Gets the title
3343 	@property string title() {
3344 		if(_title is null)
3345 			_title = getRealTitle();
3346 		return _title;
3347 	}
3348 
3349 	/++
3350 		Get the title as set by the window manager.
3351 		May not match what you attempted to set.
3352 	+/
3353 	string getRealTitle() {
3354 		static if(is(typeof(impl.getTitle())))
3355 			return impl.getTitle();
3356 		else
3357 			return null;
3358 	}
3359 
3360 	// don't use this generally it is not yet really released
3361 	version(X11)
3362 	@property Image secret_icon() {
3363 		return secret_icon_inner;
3364 	}
3365 	private Image secret_icon_inner;
3366 
3367 
3368 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3369 	@property void icon(MemoryImage icon) {
3370 		if(icon is null)
3371 			return;
3372 		auto tci = icon.getAsTrueColorImage();
3373 		version(Windows) {
3374 			winIcon = new WindowsIcon(icon);
3375 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3376 		} else version(X11) {
3377 			secret_icon_inner = Image.fromMemoryImage(icon);
3378 			// FIXME: ensure this is correct
3379 			auto display = XDisplayConnection.get;
3380 			arch_ulong[] buffer;
3381 			buffer ~= icon.width;
3382 			buffer ~= icon.height;
3383 			foreach(c; tci.imageData.colors) {
3384 				arch_ulong b;
3385 				b |= c.a << 24;
3386 				b |= c.r << 16;
3387 				b |= c.g << 8;
3388 				b |= c.b;
3389 				buffer ~= b;
3390 			}
3391 
3392 			XChangeProperty(
3393 				display,
3394 				impl.window,
3395 				GetAtom!("_NET_WM_ICON", true)(display),
3396 				GetAtom!"CARDINAL"(display),
3397 				32 /* bits */,
3398 				0 /*PropModeReplace*/,
3399 				buffer.ptr,
3400 				cast(int) buffer.length);
3401 		} else version(OSXCocoa) {
3402 			throw new NotYetImplementedException();
3403 		} else version(Emscripten) {
3404 			throw new NotYetImplementedException();
3405 		} else static assert(0);
3406 	}
3407 
3408 	version(Windows)
3409 		private WindowsIcon winIcon;
3410 
3411 	bool _suppressDestruction;
3412 
3413 	~this() {
3414 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3415 		if(_suppressDestruction)
3416 			return;
3417 		impl.dispose();
3418 	}
3419 
3420 	private bool _closed;
3421 
3422 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3423 	/*
3424 	ScreenPainter drawTransiently() {
3425 		return impl.getPainter();
3426 	}
3427 	*/
3428 
3429 	/// Draws an image on the window. This is meant to provide quick look
3430 	/// of a static image generated elsewhere.
3431 	@property void image(Image i) {
3432 	/+
3433 		version(Windows) {
3434 			BITMAP bm;
3435 			HDC hdc = GetDC(hwnd);
3436 			HDC hdcMem = CreateCompatibleDC(hdc);
3437 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3438 
3439 			GetObject(i.handle, bm.sizeof, &bm);
3440 
3441 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3442 
3443 			SelectObject(hdcMem, hbmOld);
3444 			DeleteDC(hdcMem);
3445 			ReleaseDC(hwnd, hdc);
3446 
3447 			/*
3448 			RECT r;
3449 			r.right = i.width;
3450 			r.bottom = i.height;
3451 			InvalidateRect(hwnd, &r, false);
3452 			*/
3453 		} else
3454 		version(X11) {
3455 			if(!destroyed) {
3456 				if(i.usingXshm)
3457 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3458 				else
3459 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3460 			}
3461 		} else
3462 		version(OSXCocoa) {
3463 			draw().drawImage(Point(0, 0), i);
3464 			setNeedsDisplay(view, true);
3465 		} else static assert(0);
3466 	+/
3467 		auto painter = this.draw;
3468 		painter.drawImage(Point(0, 0), i);
3469 	}
3470 
3471 	/++
3472 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3473 
3474 		---
3475 		window.cursor = GenericCursor.Help;
3476 		// now the window mouse cursor is set to a generic help
3477 		---
3478 
3479 	+/
3480 	@property void cursor(MouseCursor cursor) {
3481 		version(OSXCocoa)
3482 			{} // featureNotImplemented();
3483 		else
3484 		if(this.impl.curHidden <= 0) {
3485 			static if(UsingSimpledisplayX11) {
3486 				auto ch = cursor.cursorHandle;
3487 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3488 			} else version(Windows) {
3489 				auto ch = cursor.cursorHandle;
3490 				impl.currentCursor = ch;
3491 				SetCursor(ch); // redraw without waiting for mouse movement to update
3492 			} else featureNotImplemented();
3493 		}
3494 
3495 	}
3496 
3497 	/// What follows are the event handlers. These are set automatically
3498 	/// by the eventLoop function, but are still public so you can change
3499 	/// them later. wasPressed == true means key down. false == key up.
3500 
3501 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3502 	void delegate(KeyEvent ke) handleKeyEvent;
3503 
3504 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3505 	void delegate(dchar c) handleCharEvent;
3506 
3507 	/// Handles a timer pulse. Settable through setEventHandlers.
3508 	void delegate() handlePulse;
3509 
3510 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3511 	void delegate(bool) onFocusChange;
3512 
3513 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3514 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3515 	void delegate() onClosing;
3516 
3517 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3518 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3519 	 * last minute cleanup. */
3520 	void delegate() onDestroyed;
3521 
3522 	static if (UsingSimpledisplayX11)
3523 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3524 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3525 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3526 	 *
3527 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3528 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3529 
3530 	//version(Windows)
3531 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3532 
3533 	private {
3534 		int lastMouseX = int.min;
3535 		int lastMouseY = int.min;
3536 		void mdx(ref MouseEvent ev) {
3537 			if(lastMouseX == int.min || lastMouseY == int.min) {
3538 				ev.dx = 0;
3539 				ev.dy = 0;
3540 			} else {
3541 				ev.dx = ev.x - lastMouseX;
3542 				ev.dy = ev.y - lastMouseY;
3543 			}
3544 
3545 			lastMouseX = ev.x;
3546 			lastMouseY = ev.y;
3547 		}
3548 	}
3549 
3550 	/// Mouse event handler. Settable through setEventHandlers.
3551 	void delegate(MouseEvent) handleMouseEvent;
3552 
3553 	/// use to redraw child widgets if you use system apis to add stuff
3554 	void delegate() paintingFinished;
3555 
3556 	void delegate() paintingFinishedDg() {
3557 		return paintingFinished;
3558 	}
3559 
3560 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3561 	/// for this to ever happen.
3562 	void delegate(int width, int height) windowResized;
3563 
3564 	/++
3565 		Platform specific - handle any native message this window gets.
3566 
3567 		Note: this is called *in addition to* other event handlers, unless you either:
3568 
3569 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3570 
3571 		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.
3572 
3573 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3574 
3575 		On X, it takes the form of `int delegate(XEvent)`.
3576 
3577 		History:
3578 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3579 
3580 			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.
3581 	+/
3582 	NativeEventHandler handleNativeEvent_;
3583 
3584 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3585 		return handleNativeEvent_;
3586 	}
3587 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3588 		handleNativeEvent_ = neh;
3589 	}
3590 
3591 	version(Windows)
3592 	// compatibility shim with the old deprecated way
3593 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3594 	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) {
3595 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3596 			auto ret = dg(h, m, w, l);
3597 			if(ret == 0)
3598 				r = 1;
3599 			return ret;
3600 		};
3601 	}
3602 
3603 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3604 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3605 	/// this instead and it will work the same way.
3606 	__gshared NativeEventHandler handleNativeGlobalEvent;
3607 
3608 //  private:
3609 	/// The native implementation is available, but you shouldn't use it unless you are
3610 	/// familiar with the underlying operating system, don't mind depending on it, and
3611 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3612 	/// do what you need to do with handleNativeEvent instead.
3613 	///
3614 	/// This is likely to eventually change to be just a struct holding platform-specific
3615 	/// handles instead of a template mixin at some point because I'm not happy with the
3616 	/// code duplication here (ironically).
3617 	mixin NativeSimpleWindowImplementation!() impl;
3618 
3619 	/**
3620 		This is in-process one-way (from anything to window) event sending mechanics.
3621 		It is thread-safe, so it can be used in multi-threaded applications to send,
3622 		for example, "wake up and repaint" events when thread completed some operation.
3623 		This will allow to avoid using timer pulse to check events with synchronization,
3624 		'cause event handler will be called in UI thread. You can stop guessing which
3625 		pulse frequency will be enough for your app.
3626 		Note that events handlers may be called in arbitrary order, i.e. last registered
3627 		handler can be called first, and vice versa.
3628 	*/
3629 public:
3630 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3631 	 * "spamming" window with events it can't cope with.
3632 	 * It is safe to call this from non-UI threads.
3633 	 */
3634 	@property bool eventQueueEmpty() () {
3635 		synchronized(this) {
3636 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3637 		}
3638 		return true;
3639 	}
3640 
3641 	/** Does our custom event queue contains at least one with the given type?
3642 	 * Can be used in simple cases to prevent "spamming" window with events
3643 	 * it can't cope with.
3644 	 * It is safe to call this from non-UI threads.
3645 	 */
3646 	@property bool eventQueued(ET:Object) () {
3647 		synchronized(this) {
3648 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3649 				if (!o.doProcess) {
3650 					if (cast(ET)(o.evt)) return true;
3651 				}
3652 			}
3653 		}
3654 		return false;
3655 	}
3656 
3657 	/++
3658 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3659 
3660 		History:
3661 			Added May 12, 2021
3662 	+/
3663 	void delegate(Exception e) nothrow eventUncaughtException;
3664 
3665 	/** Add listener for custom event. Can be used like this:
3666 	 *
3667 	 * ---------------------
3668 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3669 	 *   ...
3670 	 *   win.removeEventListener(eid);
3671 	 * ---------------------
3672 	 *
3673 	 * Returns: 0 on failure (should never happen, so ignore it)
3674 	 *
3675 	 * $(WARNING Don't use this method in object destructors!)
3676 	 *
3677 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3678 	 *           'cause if event handler id counter will overflow, you won't be able
3679 	 *           to register any more events.)
3680 	 */
3681 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3682 		if (dg is null) return 0; // ignore empty handlers
3683 		synchronized(this) {
3684 			//FIXME: abort on overflow?
3685 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3686 			EventHandlerEntry e;
3687 			e.dg = delegate (Object o) {
3688 				if (auto co = cast(ET)o) {
3689 					try {
3690 						dg(co);
3691 					} catch (Exception e) {
3692 						// sorry!
3693 						if(eventUncaughtException)
3694 							eventUncaughtException(e);
3695 					}
3696 					return true;
3697 				}
3698 				return false;
3699 			};
3700 			e.id = lastUsedHandlerId;
3701 			auto optr = eventHandlers.ptr;
3702 			eventHandlers ~= e;
3703 			if (eventHandlers.ptr !is optr) {
3704 				import core.memory : GC;
3705 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3706 			}
3707 			return lastUsedHandlerId;
3708 		}
3709 	}
3710 
3711 	/// Remove event listener. It is safe to pass invalid event id here.
3712 	/// $(WARNING Don't use this method in object destructors!)
3713 	void removeEventListener() (uint id) {
3714 		if (id == 0 || id > lastUsedHandlerId) return;
3715 		synchronized(this) {
3716 			foreach (immutable idx; 0..eventHandlers.length) {
3717 				if (eventHandlers[idx].id == id) {
3718 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3719 					eventHandlers[$-1].dg = null;
3720 					eventHandlers.length -= 1;
3721 					eventHandlers.assumeSafeAppend;
3722 					return;
3723 				}
3724 			}
3725 		}
3726 	}
3727 
3728 	/// Post event to queue. It is safe to call this from non-UI threads.
3729 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3730 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3731 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3732 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3733 		if (this.closed) return false; // closed windows can't handle events
3734 
3735 		// remove all events of type `ET`
3736 		void removeAllET () {
3737 			uint eidx = 0, ec = eventQueueUsed;
3738 			auto eptr = eventQueue.ptr;
3739 			while (eidx < ec) {
3740 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3741 				if (cast(ET)eptr.evt !is null) {
3742 					// i found her!
3743 					if (inCustomEventProcessor) {
3744 						// if we're in custom event processing loop, processor will clear it for us
3745 						eptr.evt = null;
3746 						++eidx;
3747 						++eptr;
3748 					} else {
3749 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3750 						ec = --eventQueueUsed;
3751 						// clear last event (it is already copied)
3752 						eventQueue.ptr[ec].evt = null;
3753 					}
3754 				} else {
3755 					++eidx;
3756 					++eptr;
3757 				}
3758 			}
3759 		}
3760 
3761 		if (evt is null) {
3762 			if (replace) { synchronized(this) removeAllET(); }
3763 			// ignore empty events, they can't be handled anyway
3764 			return false;
3765 		}
3766 
3767 		// add events even if no event FD/event object created yet
3768 		synchronized(this) {
3769 			if (replace) removeAllET();
3770 			if (eventQueueUsed == uint.max) return false; // just in case
3771 			if (eventQueueUsed < eventQueue.length) {
3772 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3773 			} else {
3774 				if (eventQueue.capacity == eventQueue.length) {
3775 					// need to reallocate; do a trick to ensure that old array is cleared
3776 					auto oarr = eventQueue;
3777 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3778 					// just in case, do yet another check
3779 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3780 					import core.memory : GC;
3781 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3782 				} else {
3783 					auto optr = eventQueue.ptr;
3784 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3785 					assert(eventQueue.ptr is optr);
3786 				}
3787 				++eventQueueUsed;
3788 				assert(eventQueueUsed == eventQueue.length);
3789 			}
3790 			if (!eventWakeUp()) {
3791 				// can't wake up event processor, so there is no reason to keep the event
3792 				assert(eventQueueUsed > 0);
3793 				eventQueue[--eventQueueUsed].evt = null;
3794 				return false;
3795 			}
3796 			return true;
3797 		}
3798 	}
3799 
3800 	/// Post event to queue. It is safe to call this from non-UI threads.
3801 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3802 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3803 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3804 		return postTimeout!ET(evt, 0, replace);
3805 	}
3806 
3807 private:
3808 	private import core.time : MonoTime;
3809 
3810 	version(Posix) {
3811 		__gshared int customEventFDRead = -1;
3812 		__gshared int customEventFDWrite = -1;
3813 		__gshared int customSignalFD = -1;
3814 	} else version(Windows) {
3815 		__gshared HANDLE customEventH = null;
3816 	}
3817 
3818 	// wake up event processor
3819 	static bool eventWakeUp () {
3820 		version(X11) {
3821 			import core.sys.posix.unistd : write;
3822 			ulong n = 1;
3823 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3824 			return true;
3825 		} else version(Windows) {
3826 			if (customEventH !is null) SetEvent(customEventH);
3827 			return true;
3828 		} else version(OSXCocoa) {
3829 			if(globalAppDelegate)
3830 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3831 			return true;
3832 		} else {
3833 			// not implemented for other OSes
3834 			return false;
3835 		}
3836 	}
3837 
3838 	static struct QueuedEvent {
3839 		Object evt;
3840 		bool timed = false;
3841 		MonoTime hittime = MonoTime.zero;
3842 		bool doProcess = false; // process event at the current iteration (internal flag)
3843 
3844 		this (Object aevt, uint toutmsecs) {
3845 			evt = aevt;
3846 			if (toutmsecs > 0) {
3847 				import core.time : msecs;
3848 				timed = true;
3849 				hittime = MonoTime.currTime+toutmsecs.msecs;
3850 			}
3851 		}
3852 	}
3853 
3854 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3855 	static struct EventHandlerEntry {
3856 		CustomEventHandler dg;
3857 		uint id;
3858 	}
3859 
3860 	uint lastUsedHandlerId;
3861 	EventHandlerEntry[] eventHandlers;
3862 	QueuedEvent[] eventQueue = null;
3863 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3864 	bool inCustomEventProcessor = false; // required to properly remove events
3865 
3866 	// process queued events and call custom event handlers
3867 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3868 	void processCustomEvents () @system {
3869 		bool hasSomethingToDo = false;
3870 		uint ecount;
3871 		bool ocep;
3872 		synchronized(this) {
3873 			ocep = inCustomEventProcessor;
3874 			inCustomEventProcessor = true;
3875 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3876 			auto ctt = MonoTime.currTime;
3877 			bool hasEmpty = false;
3878 			// mark events to process (this is required for `eventQueued()`)
3879 			foreach (ref qe; eventQueue[0..ecount]) {
3880 				if (qe.evt is null) { hasEmpty = true; continue; }
3881 				if (qe.timed) {
3882 					qe.doProcess = (qe.hittime <= ctt);
3883 				} else {
3884 					qe.doProcess = true;
3885 				}
3886 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3887 			}
3888 			if (!hasSomethingToDo) {
3889 				// remove empty events
3890 				if (hasEmpty) {
3891 					uint eidx = 0, ec = eventQueueUsed;
3892 					auto eptr = eventQueue.ptr;
3893 					while (eidx < ec) {
3894 						if (eptr.evt is null) {
3895 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3896 							ec = --eventQueueUsed;
3897 							eventQueue.ptr[ec].evt = null; // make GC life easier
3898 						} else {
3899 							++eidx;
3900 							++eptr;
3901 						}
3902 					}
3903 				}
3904 				inCustomEventProcessor = ocep;
3905 				return;
3906 			}
3907 		}
3908 		// process marked events
3909 		uint efree = 0; // non-processed events will be put at this index
3910 		EventHandlerEntry[] eh;
3911 		Object evt;
3912 		foreach (immutable eidx; 0..ecount) {
3913 			synchronized(this) {
3914 				if (!eventQueue[eidx].doProcess) {
3915 					// skip this event
3916 					assert(efree <= eidx);
3917 					if (efree != eidx) {
3918 						// copy this event to queue start
3919 						eventQueue[efree] = eventQueue[eidx];
3920 						eventQueue[eidx].evt = null; // just in case
3921 					}
3922 					++efree;
3923 					continue;
3924 				}
3925 				evt = eventQueue[eidx].evt;
3926 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3927 				if (evt is null) continue; // just in case
3928 				// try all handlers; this can be slow, but meh...
3929 				eh = eventHandlers;
3930 			}
3931 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3932 			evt = null;
3933 			eh = null;
3934 		}
3935 		synchronized(this) {
3936 			// move all unprocessed events to queue top; efree holds first "free index"
3937 			foreach (immutable eidx; ecount..eventQueueUsed) {
3938 				assert(efree <= eidx);
3939 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3940 				++efree;
3941 			}
3942 			eventQueueUsed = efree;
3943 			// wake up event processor on next event loop iteration if we have more queued events
3944 			// also, remove empty events
3945 			bool awaken = false;
3946 			uint eidx = 0, ec = eventQueueUsed;
3947 			auto eptr = eventQueue.ptr;
3948 			while (eidx < ec) {
3949 				if (eptr.evt is null) {
3950 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3951 					ec = --eventQueueUsed;
3952 					eventQueue.ptr[ec].evt = null; // make GC life easier
3953 				} else {
3954 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3955 					++eidx;
3956 					++eptr;
3957 				}
3958 			}
3959 			inCustomEventProcessor = ocep;
3960 		}
3961 	}
3962 
3963 	// for all windows in nativeMapping
3964 	package static void processAllCustomEvents () @system {
3965 
3966 		cleanupQueue.process();
3967 
3968 		justCommunication.processCustomEvents();
3969 
3970 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3971 			if (sw is null || sw.closed) continue;
3972 			sw.processCustomEvents();
3973 		}
3974 
3975 		runPendingRunInGuiThreadDelegates();
3976 	}
3977 
3978 	// 0: infinite (i.e. no scheduled events in queue)
3979 	uint eventQueueTimeoutMSecs () {
3980 		synchronized(this) {
3981 			if (eventQueueUsed == 0) return 0;
3982 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3983 			uint res = int.max;
3984 			auto ctt = MonoTime.currTime;
3985 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3986 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3987 				if (qe.doProcess) continue; // just in case
3988 				if (!qe.timed) return 1; // minimal
3989 				if (qe.hittime <= ctt) return 1; // minimal
3990 				auto tms = (qe.hittime-ctt).total!"msecs";
3991 				if (tms < 1) tms = 1; // safety net
3992 				if (tms >= int.max) tms = int.max-1; // and another safety net
3993 				if (res > tms) res = cast(uint)tms;
3994 			}
3995 			return (res >= int.max ? 0 : res);
3996 		}
3997 	}
3998 
3999 	// for all windows in nativeMapping
4000 	static uint eventAllQueueTimeoutMSecs () {
4001 		uint res = uint.max;
4002 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
4003 			if (sw is null || sw.closed) continue;
4004 			uint to = sw.eventQueueTimeoutMSecs();
4005 			if (to && to < res) {
4006 				res = to;
4007 				if (to == 1) break; // can't have less than this
4008 			}
4009 		}
4010 		return (res >= int.max ? 0 : res);
4011 	}
4012 
4013 	version(X11) {
4014 		ResizeEvent pendingResizeEvent;
4015 	}
4016 
4017 	/++
4018 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
4019 
4020 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
4021 		worth so you can disable it by setting this to `true`.
4022 
4023 		History:
4024 			Added November 13, 2022.
4025 	+/
4026 	public bool suppressAutoOpenglViewport = false;
4027 	private void updateOpenglViewportIfNeeded(int width, int height) {
4028 		if(suppressAutoOpenglViewport) return;
4029 
4030 		version(without_opengl) {} else
4031 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
4032 		// writeln(width, " ", height);
4033 			setAsCurrentOpenGlContextNT();
4034 			glViewport(0, 0, width, height);
4035 		}
4036 	}
4037 
4038 	// TODO: Implement on non-Windows platforms (where available).
4039 	private CornerStyle _fauxCornerStyle = CornerStyle.automatic;
4040 
4041 	/++
4042 		Style of the window's corners
4043 
4044 		$(WARNING
4045 			Currently only implemented on Windows targets.
4046 			Has no visual effect elsewhere.
4047 
4048 			Windows: Requires Windows 11 or later.
4049 		)
4050 
4051 		History:
4052 			Added September 09, 2024.
4053 	 +/
4054 	public CornerStyle cornerStyle() @trusted {
4055 		version(Windows) {
4056 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4057 			const apiResult = DwmGetWindowAttribute(
4058 				this.hwnd,
4059 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4060 				&dwmCorner,
4061 				typeof(dwmCorner).sizeof
4062 			);
4063 
4064 			if (apiResult != S_OK) {
4065 				// Unsupported?
4066 				if (apiResult == E_INVALIDARG) {
4067 					// Feature unsupported; Windows version probably too old.
4068 					// Requires Windows 11 (build 22000) or later.
4069 					return _fauxCornerStyle;
4070 				}
4071 
4072 				throw new WindowsApiException("DwmGetWindowAttribute", apiResult);
4073 			}
4074 
4075 			CornerStyle corner;
4076 			if (!dwmCorner.fromDWM(corner)) {
4077 				throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner);
4078 			}
4079 			return corner;
4080 		} else {
4081 			return _fauxCornerStyle;
4082 		}
4083 	}
4084 
4085 	/// ditto
4086 	public void cornerStyle(const CornerStyle corner) @trusted {
4087 		version(Windows) {
4088 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4089 			if (!corner.toDWM(dwmCorner)) {
4090 				assert(false, "This should have been impossible because of a final switch.");
4091 			}
4092 
4093 			const apiResult = DwmSetWindowAttribute(
4094 				this.hwnd,
4095 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4096 				&dwmCorner,
4097 				typeof(dwmCorner).sizeof
4098 			);
4099 
4100 			if (apiResult != S_OK) {
4101 				// Unsupported?
4102 				if (apiResult == E_INVALIDARG) {
4103 					// Feature unsupported; Windows version probably too old.
4104 					// Requires Windows 11 (build 22000) or later.
4105 					_fauxCornerStyle = corner;
4106 					return;
4107 				}
4108 
4109 				throw new WindowsApiException("DwmSetWindowAttribute", apiResult);
4110 			}
4111 		} else {
4112 			_fauxCornerStyle = corner;
4113 		}
4114 	}
4115 }
4116 
4117 version(OSXCocoa)
4118 	enum NSWindow NullWindow = null;
4119 else
4120 	enum NullWindow = NativeWindowHandle.init;
4121 
4122 /++
4123 	Magic pseudo-window for just posting events to a global queue.
4124 
4125 	Not entirely supported, I might delete it at any time.
4126 
4127 	Added Nov 5, 2021.
4128 +/
4129 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
4130 
4131 /* Drag and drop support { */
4132 version(X11) {
4133 
4134 } else version(Windows) {
4135 	import core.sys.windows.uuid;
4136 	import core.sys.windows.ole2;
4137 	import core.sys.windows.oleidl;
4138 	import core.sys.windows.objidl;
4139 	import core.sys.windows.wtypes;
4140 
4141 	pragma(lib, "ole32");
4142 	void initDnd() {
4143 		auto err = OleInitialize(null);
4144 		if(err != S_OK && err != S_FALSE)
4145 			throw new Exception("init");//err);
4146 	}
4147 }
4148 /* } End drag and drop support */
4149 
4150 
4151 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
4152 /// See [GenericCursor].
4153 class MouseCursor {
4154 	int osId;
4155 	bool isStockCursor;
4156 	private this(int osId) {
4157 		this.osId = osId;
4158 		this.isStockCursor = true;
4159 	}
4160 
4161 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
4162 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
4163 
4164 	version(Windows) {
4165 		HCURSOR cursor_;
4166 		HCURSOR cursorHandle() {
4167 			if(cursor_ is null)
4168 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
4169 			return cursor_;
4170 		}
4171 
4172 	} else static if(UsingSimpledisplayX11) {
4173 		Cursor cursor_ = None;
4174 		int xDisplaySequence;
4175 
4176 		Cursor cursorHandle() {
4177 			if(this.osId == None)
4178 				return None;
4179 
4180 			// we need to reload if we on a new X connection
4181 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
4182 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
4183 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
4184 			}
4185 			return cursor_;
4186 		}
4187 	}
4188 }
4189 
4190 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
4191 // https://tronche.com/gui/x/xlib/appendix/b/
4192 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
4193 /// 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.
4194 enum GenericCursorType {
4195 	Default, /// The default arrow pointer.
4196 	Wait, /// A cursor indicating something is loading and the user must wait.
4197 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
4198 	Help, /// A cursor indicating the user can get help about the pointer location.
4199 	Cross, /// A crosshair.
4200 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
4201 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
4202 	UpArrow, /// An arrow pointing straight up.
4203 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
4204 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
4205 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
4206 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
4207 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
4208 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
4209 
4210 }
4211 
4212 /*
4213 	X_plus == css cell == Windows ?
4214 */
4215 
4216 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
4217 static struct GenericCursor {
4218 	static:
4219 	///
4220 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
4221 		static MouseCursor mc;
4222 
4223 		auto type = __traits(getMember, GenericCursorType, str);
4224 
4225 		if(mc is null) {
4226 
4227 			version(Windows) {
4228 				int osId;
4229 				final switch(type) {
4230 					case GenericCursorType.Default: osId = IDC_ARROW; break;
4231 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
4232 					case GenericCursorType.Hand: osId = IDC_HAND; break;
4233 					case GenericCursorType.Help: osId = IDC_HELP; break;
4234 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
4235 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
4236 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
4237 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
4238 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
4239 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
4240 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
4241 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
4242 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
4243 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
4244 				}
4245 			} else static if(UsingSimpledisplayX11) {
4246 				int osId;
4247 				final switch(type) {
4248 					case GenericCursorType.Default: osId = None; break;
4249 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
4250 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
4251 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
4252 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
4253 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
4254 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
4255 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
4256 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
4257 
4258 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
4259 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
4260 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
4261 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
4262 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
4263 				}
4264 
4265 			} else {
4266 				int osId;
4267 				// featureNotImplemented();
4268 			}
4269 
4270 			mc = new MouseCursor(osId);
4271 		}
4272 		return mc;
4273 	}
4274 }
4275 
4276 
4277 /++
4278 	If you want to get more control over the event loop, you can use this.
4279 
4280 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4281 	to `EventLoop.get.run`.
4282 +/
4283 struct EventLoop {
4284 	@disable this();
4285 
4286 	/// Gets a reference to an existing event loop
4287 	static EventLoop get() {
4288 		return EventLoop(0, null);
4289 	}
4290 
4291 	static void quitApplication() {
4292 		version(use_arsd_core) {
4293 			import arsd.core;
4294 			ICoreEventLoop.exitApplication();
4295 		}
4296 		EventLoop.get().exit();
4297 	}
4298 
4299 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4300 
4301 	/// Construct an application-global event loop for yourself
4302 	/// See_Also: [SimpleWindow.setEventHandlers]
4303 	this(long pulseTimeout, void delegate() handlePulse) {
4304 		synchronized(monitor) {
4305 			if(impl is null) {
4306 				claimGuiThread();
4307 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4308 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4309 			} else {
4310 				if(pulseTimeout) {
4311 					impl.pulseTimeout = pulseTimeout;
4312 					impl.handlePulse = handlePulse;
4313 				}
4314 			}
4315 			impl.refcount++;
4316 		}
4317 	}
4318 
4319 	~this() {
4320 		if(impl is null)
4321 			return;
4322 		impl.refcount--;
4323 		if(impl.refcount == 0) {
4324 			impl.dispose();
4325 			if(thisIsGuiThread)
4326 				guiThreadFinalize();
4327 		}
4328 
4329 	}
4330 
4331 	this(this) {
4332 		if(impl is null)
4333 			return;
4334 		impl.refcount++;
4335 	}
4336 
4337 	/// Runs the event loop until the whileCondition, if present, returns false
4338 	int run(bool delegate() whileCondition = null) {
4339 		assert(impl !is null);
4340 		impl.notExited = true;
4341 		return impl.run(whileCondition);
4342 	}
4343 
4344 	/// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program)
4345 	void exit() {
4346 		assert(impl !is null);
4347 		impl.notExited = false;
4348 
4349 		version(use_arsd_core) {
4350 			import arsd.core;
4351 			ICoreEventLoop.exitApplication();
4352 		}
4353 	}
4354 
4355 	version(linux)
4356 	ref void delegate(int) signalHandler() {
4357 		assert(impl !is null);
4358 		return impl.signalHandler;
4359 	}
4360 
4361 	__gshared static EventLoopImpl* impl;
4362 }
4363 
4364 version(linux)
4365 	void delegate(int, int) globalHupHandler;
4366 
4367 version(Posix)
4368 	void makeNonBlocking(int fd) {
4369 		import fcntl = core.sys.posix.fcntl;
4370 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4371 		if(flags == -1)
4372 			throw new Exception("fcntl get");
4373 		flags |= fcntl.O_NONBLOCK;
4374 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4375 		if(s == -1)
4376 			throw new Exception("fcntl set");
4377 	}
4378 
4379 struct EventLoopImpl {
4380 	int refcount;
4381 
4382 	bool notExited = true;
4383 
4384 	version(Emscripten) {
4385 		void delegate(int) signalHandler;
4386 		static import unix = core.sys.posix.unistd;
4387 		static import err = core.stdc.errno;
4388 	} else
4389 	version(linux) {
4390 		static import ep = core.sys.linux.epoll;
4391 		static import unix = core.sys.posix.unistd;
4392 		static import err = core.stdc.errno;
4393 		import core.sys.linux.timerfd;
4394 
4395 		void delegate(int) signalHandler;
4396 	}
4397 
4398 	version(X11) {
4399 		int pulseFd = -1;
4400 		version(Emscripten) {} else
4401 		version(linux) ep.epoll_event[16] events = void;
4402 	} else version(Windows) {
4403 		Timer pulser;
4404 		HANDLE[] handles;
4405 	}
4406 
4407 
4408 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4409 	/// to call this, as it's not recommended to share window between threads.
4410 	void mtLock () {
4411 		version(X11) {
4412 			XLockDisplay(this.display);
4413 		}
4414 	}
4415 
4416 	version(X11)
4417 	auto display() { return XDisplayConnection.get; }
4418 
4419 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4420 	/// to call this, as it's not recommended to share window between threads.
4421 	void mtUnlock () {
4422 		version(X11) {
4423 			XUnlockDisplay(this.display);
4424 		}
4425 	}
4426 
4427 	version(with_eventloop)
4428 	void initialize(long pulseTimeout) {}
4429 	else
4430 	void initialize(long pulseTimeout) @system {
4431 		version(Windows) {
4432 			if(pulseTimeout && handlePulse !is null)
4433 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4434 
4435 			if (customEventH is null) {
4436 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4437 				if (customEventH !is null) {
4438 					handles ~= customEventH;
4439 				} else {
4440 					// this is something that should not be; better be safe than sorry
4441 					throw new Exception("can't create eventfd for custom event processing");
4442 				}
4443 			}
4444 
4445 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4446 		}
4447 
4448 		version(Emscripten) {
4449 
4450 		} else version(linux) {
4451 			prepareEventLoop();
4452 			{
4453 				auto display = XDisplayConnection.get;
4454 				// adding Xlib file
4455 				ep.epoll_event ev = void;
4456 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4457 				ev.events = ep.EPOLLIN;
4458 				ev.data.fd = display.fd;
4459 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4460 					throw new Exception("add x fd");// ~ to!string(epollFd));
4461 				displayFd = display.fd;
4462 			}
4463 
4464 			if(pulseTimeout && handlePulse !is null) {
4465 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4466 				if(pulseFd == -1)
4467 					throw new Exception("pulse timer create failed");
4468 
4469 				itimerspec value;
4470 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4471 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4472 
4473 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4474 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4475 
4476 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4477 					throw new Exception("couldn't make pulse timer");
4478 
4479 				ep.epoll_event ev = void;
4480 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4481 				ev.events = ep.EPOLLIN;
4482 				ev.data.fd = pulseFd;
4483 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4484 			}
4485 
4486 			// eventfd for custom events
4487 			if (customEventFDWrite == -1) {
4488 				customEventFDWrite = eventfd(0, 0);
4489 				customEventFDRead = customEventFDWrite;
4490 				if (customEventFDRead >= 0) {
4491 					ep.epoll_event ev = void;
4492 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4493 					ev.events = ep.EPOLLIN;
4494 					ev.data.fd = customEventFDRead;
4495 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4496 				} else {
4497 					// this is something that should not be; better be safe than sorry
4498 					throw new Exception("can't create eventfd for custom event processing");
4499 				}
4500 			}
4501 
4502 			if (customSignalFD == -1) {
4503 				import core.sys.linux.sys.signalfd;
4504 
4505 				sigset_t sigset;
4506 				auto err = sigemptyset(&sigset);
4507 				assert(!err);
4508 				err = sigaddset(&sigset, SIGINT);
4509 				assert(!err);
4510 				err = sigaddset(&sigset, SIGHUP);
4511 				assert(!err);
4512 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4513 				assert(!err);
4514 
4515 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4516 				assert(customSignalFD != -1);
4517 
4518 				ep.epoll_event ev = void;
4519 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4520 				ev.events = ep.EPOLLIN;
4521 				ev.data.fd = customSignalFD;
4522 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4523 			}
4524 		} else version(Posix) {
4525 			prepareEventLoop();
4526 			if (customEventFDRead == -1) {
4527 				int[2] bfr;
4528 				import core.sys.posix.unistd;
4529 				auto ret = pipe(bfr);
4530 				if(ret == -1) throw new Exception("pipe");
4531 				customEventFDRead = bfr[0];
4532 				customEventFDWrite = bfr[1];
4533 			}
4534 
4535 		}
4536 
4537 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4538 
4539 		version(linux) {
4540 			this.mtLock();
4541 			scope(exit) this.mtUnlock();
4542 			version(X11)
4543 				XPending(display); // no, really
4544 		}
4545 
4546 		disposed = false;
4547 	}
4548 
4549 	bool disposed = true;
4550 	version(X11)
4551 		int displayFd = -1;
4552 
4553 	version(with_eventloop)
4554 	void dispose() {}
4555 	else
4556 	void dispose() @system {
4557 		disposed = true;
4558 		version(X11) {
4559 			if(pulseFd != -1) {
4560 				import unix = core.sys.posix.unistd;
4561 				unix.close(pulseFd);
4562 				pulseFd = -1;
4563 			}
4564 
4565 				version(Emscripten) {} else
4566 				version(linux)
4567 				if(displayFd != -1) {
4568 					// 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
4569 					ep.epoll_event ev = void;
4570 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4571 					ev.events = ep.EPOLLIN;
4572 					ev.data.fd = displayFd;
4573 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4574 					displayFd = -1;
4575 				}
4576 
4577 		} else version(Windows) {
4578 			if(pulser !is null) {
4579 				pulser.destroy();
4580 				pulser = null;
4581 			}
4582 			if (customEventH !is null) {
4583 				CloseHandle(customEventH);
4584 				customEventH = null;
4585 			}
4586 		}
4587 	}
4588 
4589 	this(long pulseTimeout, void delegate() handlePulse) {
4590 		this.pulseTimeout = pulseTimeout;
4591 		this.handlePulse = handlePulse;
4592 		initialize(pulseTimeout);
4593 	}
4594 
4595 	private long pulseTimeout;
4596 	void delegate() handlePulse;
4597 
4598 	~this() {
4599 		dispose();
4600 	}
4601 
4602 	version(Posix)
4603 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4604 	version(Posix)
4605 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4606 	version(linux)
4607 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4608 	version(Windows)
4609 	ref auto customEventH() { return SimpleWindow.customEventH; }
4610 
4611 	version(X11) {
4612 		bool doXPending() {
4613 			bool done = false;
4614 
4615 			this.mtLock();
4616 			scope(exit) this.mtUnlock();
4617 			//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4618 			while(!done && XPending(display)) {
4619 				done = doXNextEvent(this.display);
4620 			}
4621 
4622 			return done;
4623 		}
4624 		void doXNextEventVoid() {
4625 			doXPending();
4626 		}
4627 	}
4628 
4629 	version(with_eventloop) {
4630 		int loopHelper(bool delegate() whileCondition) {
4631 			// FIXME: whileCondition
4632 			import arsd.eventloop;
4633 			loop();
4634 			return 0;
4635 		}
4636 	} else
4637 	int loopHelper(bool delegate() whileCondition) {
4638 		version(X11) {
4639 			bool done = false;
4640 
4641 			XFlush(display);
4642 			insideXEventLoop = true;
4643 			scope(exit) insideXEventLoop = false;
4644 
4645 			version(use_arsd_core) {
4646 				import arsd.core;
4647 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4648 
4649 				static bool loopInitialized = false;
4650 				if(!loopInitialized) {
4651 					el.addDelegateOnLoopIteration(&doXNextEventVoid, 0);
4652 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4653 
4654 					if(customSignalFD != -1)
4655 					cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() {
4656 						version(linux) {
4657 							import core.sys.linux.sys.signalfd;
4658 							import core.sys.posix.unistd : read;
4659 							signalfd_siginfo info;
4660 							read(customSignalFD, &info, info.sizeof);
4661 
4662 							auto sig = info.ssi_signo;
4663 
4664 							if(EventLoop.get.signalHandler !is null) {
4665 								EventLoop.get.signalHandler()(sig);
4666 							} else {
4667 								EventLoop.get.exit();
4668 							}
4669 						}
4670 					}));
4671 
4672 					if(display.fd != -1)
4673 					cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() {
4674 						this.mtLock();
4675 						scope(exit) this.mtUnlock();
4676 						while(!done && XPending(display)) {
4677 							done = doXNextEvent(this.display);
4678 						}
4679 					}));
4680 
4681 					if(pulseFd != -1)
4682 					cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() {
4683 						long expirationCount;
4684 						// 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...
4685 
4686 						handlePulse();
4687 
4688 						// read just to clear the buffer so poll doesn't trigger again
4689 						// BTW I read AFTER the pulse because if the pulse handler takes
4690 						// a lot of time to execute, we don't want the app to get stuck
4691 						// in a loop of timer hits without a chance to do anything else
4692 						//
4693 						// IOW handlePulse happens at most once per pulse interval.
4694 						unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4695 					}));
4696 
4697 					if(customEventFDRead != -1)
4698 					cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() {
4699 						// we have some custom events; process 'em
4700 						import core.sys.posix.unistd : read;
4701 						ulong n;
4702 						read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4703 						//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4704 						//SimpleWindow.processAllCustomEvents();
4705 					}));
4706 
4707 					// FIXME: posix fds
4708 					// FIXME up?
4709 
4710 
4711 					loopInitialized = true;
4712 				}
4713 
4714 				el.run(() => !whileCondition());
4715 			} else version(linux) {
4716 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4717 					bool forceXPending = false;
4718 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4719 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4720 					{
4721 						this.mtLock();
4722 						scope(exit) this.mtUnlock();
4723 						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
4724 					}
4725 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4726 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4727 					if(nfds == -1) {
4728 						if(err.errno == err.EINTR) {
4729 							//if(forceXPending) goto xpending;
4730 							continue; // interrupted by signal, just try again
4731 						}
4732 						throw new Exception("epoll wait failure");
4733 					}
4734 					// writeln(nfds, " ", events[0].data.fd);
4735 
4736 					SimpleWindow.processAllCustomEvents(); // anyway
4737 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4738 					foreach(idx; 0 .. nfds) {
4739 						if(done) break;
4740 						auto fd = events[idx].data.fd;
4741 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4742 						auto flags = events[idx].events;
4743 						if(flags & ep.EPOLLIN) {
4744 							if (fd == customSignalFD) {
4745 								version(linux) {
4746 									import core.sys.linux.sys.signalfd;
4747 									import core.sys.posix.unistd : read;
4748 									signalfd_siginfo info;
4749 									read(customSignalFD, &info, info.sizeof);
4750 
4751 									auto sig = info.ssi_signo;
4752 
4753 									if(EventLoop.get.signalHandler !is null) {
4754 										EventLoop.get.signalHandler()(sig);
4755 									} else {
4756 										EventLoop.get.exit();
4757 									}
4758 								}
4759 							} else if(fd == display.fd) {
4760 								version(sdddd) { writeln("X EVENT PENDING!"); }
4761 								this.mtLock();
4762 								scope(exit) this.mtUnlock();
4763 								while(!done && XPending(display)) {
4764 									done = doXNextEvent(this.display);
4765 								}
4766 								forceXPending = false;
4767 							} else if(fd == pulseFd) {
4768 								long expirationCount;
4769 								// 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...
4770 
4771 								handlePulse();
4772 
4773 								// read just to clear the buffer so poll doesn't trigger again
4774 								// BTW I read AFTER the pulse because if the pulse handler takes
4775 								// a lot of time to execute, we don't want the app to get stuck
4776 								// in a loop of timer hits without a chance to do anything else
4777 								//
4778 								// IOW handlePulse happens at most once per pulse interval.
4779 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4780 								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
4781 							} else if (fd == customEventFDRead) {
4782 								// we have some custom events; process 'em
4783 								import core.sys.posix.unistd : read;
4784 								ulong n;
4785 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4786 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4787 								//SimpleWindow.processAllCustomEvents();
4788 
4789 								forceXPending = true;
4790 							} else {
4791 								// some other timer
4792 								version(sdddd) { writeln("unknown fd: ", fd); }
4793 
4794 								if(Timer* t = fd in Timer.mapping)
4795 									(*t).trigger();
4796 
4797 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4798 									(*pfr).ready(flags);
4799 
4800 								// we don't know what the user did in this timer, so we need to assume that
4801 								// there's X data to be flushed and potentially processed
4802 								forceXPending = true;
4803 
4804 								// or i might add support for other FDs too
4805 								// but for now it is just timer
4806 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4807 							}
4808 						}
4809 						if(flags & ep.EPOLLHUP) {
4810 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4811 								(*pfr).hup(flags);
4812 							if(globalHupHandler)
4813 								globalHupHandler(fd, flags);
4814 						}
4815 						/+
4816 						} else {
4817 							// not interested in OUT, we are just reading here.
4818 							//
4819 							// error or hup might also be reported
4820 							// but it shouldn't here since we are only
4821 							// using a few types of FD and Xlib will report
4822 							// if it dies.
4823 							// so instead of thoughtfully handling it, I'll
4824 							// just throw. for now at least
4825 
4826 							throw new Exception("epoll did something else");
4827 						}
4828 						+/
4829 					}
4830 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4831 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4832 					xpending:
4833 					if (!done && forceXPending) {
4834 						done = doXPending();
4835 					}
4836 				}
4837 			} else {
4838 				// Generic fallback: yes to simple pulse support,
4839 				// but NO timer support!
4840 
4841 				// FIXME: we could probably support the POSIX timer_create
4842 				// signal-based option, but I'm in no rush to write it since
4843 				// I prefer the fd-based functions.
4844 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4845 
4846 					import core.sys.posix.poll;
4847 
4848 					pollfd[] pfds;
4849 					pollfd[32] pfdsBuffer;
4850 					auto len = PosixFdReader.mapping.length + 2;
4851 					// FIXME: i should just reuse the buffer
4852 					if(len < pfdsBuffer.length)
4853 						pfds = pfdsBuffer[0 .. len];
4854 					else
4855 						pfds = new pollfd[](len);
4856 
4857 					pfds[0].fd = display.fd;
4858 					pfds[0].events = POLLIN;
4859 					pfds[0].revents = 0;
4860 
4861 					int slot = 1;
4862 
4863 					if(customEventFDRead != -1) {
4864 						pfds[slot].fd = customEventFDRead;
4865 						pfds[slot].events = POLLIN;
4866 						pfds[slot].revents = 0;
4867 
4868 						slot++;
4869 					}
4870 
4871 					foreach(fd, obj; PosixFdReader.mapping) {
4872 						if(!obj.enabled) continue;
4873 						pfds[slot].fd = fd;
4874 						pfds[slot].events = POLLIN;
4875 						pfds[slot].revents = 0;
4876 
4877 						slot++;
4878 					}
4879 
4880 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4881 					if(ret == -1) throw new Exception("poll");
4882 
4883 					if(ret == 0) {
4884 						// FIXME it may not necessarily time out if events keep coming
4885 						if(handlePulse !is null)
4886 							handlePulse();
4887 					} else {
4888 						foreach(s; 0 .. slot) {
4889 							if(pfds[s].revents == 0) continue;
4890 
4891 							if(pfds[s].fd == display.fd) {
4892 								while(!done && XPending(display)) {
4893 									this.mtLock();
4894 									scope(exit) this.mtUnlock();
4895 									done = doXNextEvent(this.display);
4896 								}
4897 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4898 
4899 								import core.sys.posix.unistd : read;
4900 								ulong n;
4901 								read(customEventFDRead, &n, n.sizeof);
4902 								SimpleWindow.processAllCustomEvents();
4903 							} else {
4904 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4905 								if(pfds[s].revents & POLLNVAL) {
4906 									obj.dispose();
4907 								} else {
4908 									obj.ready(pfds[s].revents);
4909 								}
4910 							}
4911 
4912 							ret--;
4913 							if(ret == 0) break;
4914 						}
4915 					}
4916 				}
4917 			}
4918 		}
4919 
4920 		version(Windows) {
4921 
4922 			version(use_arsd_core) {
4923 				import arsd.core;
4924 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4925 				static bool loopInitialized = false;
4926 				if(!loopInitialized) {
4927 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4928 					el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0);
4929 					loopInitialized = true;
4930 				}
4931 				el.run(() => !whileCondition());
4932 			} else {
4933 				int ret = -1;
4934 				MSG message;
4935 				while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4936 					eventLoopRound++;
4937 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4938 					auto waitResult = MsgWaitForMultipleObjectsEx(
4939 						cast(int) handles.length, handles.ptr,
4940 						(wto == 0 ? INFINITE : wto), /* timeout */
4941 						0x04FF, /* QS_ALLINPUT */
4942 						0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4943 
4944 					SimpleWindow.processAllCustomEvents(); // anyway
4945 					enum WAIT_OBJECT_0 = 0;
4946 					if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4947 						auto h = handles[waitResult - WAIT_OBJECT_0];
4948 						if(auto e = h in WindowsHandleReader.mapping) {
4949 							(*e).ready();
4950 						}
4951 					} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4952 						// message ready
4953 						int count;
4954 						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
4955 							ret = GetMessage(&message, null, 0, 0);
4956 							if(ret == -1)
4957 								throw new WindowsApiException("GetMessage", GetLastError());
4958 							TranslateMessage(&message);
4959 							DispatchMessage(&message);
4960 
4961 							count++;
4962 							if(count > 10)
4963 								break; // take the opportunity to catch up on other events
4964 
4965 							if(ret == 0) { // WM_QUIT
4966 								EventLoop.quitApplication();
4967 								break;
4968 							}
4969 						}
4970 					} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4971 						SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4972 					} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4973 						// timeout, should never happen since we aren't using it
4974 					} else if(waitResult == 0xFFFFFFFF) {
4975 							// failed
4976 							throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4977 					} else {
4978 						// idk....
4979 					}
4980 				}
4981 			}
4982 
4983 			// return message.wParam;
4984 			return 0;
4985 		} else {
4986 			return 0;
4987 		}
4988 	}
4989 
4990 	int run(bool delegate() whileCondition = null) {
4991 		if(disposed)
4992 			initialize(this.pulseTimeout);
4993 
4994 		version(X11) {
4995 			try {
4996 				return loopHelper(whileCondition);
4997 			} catch(XDisconnectException e) {
4998 				if(e.userRequested) {
4999 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
5000 						item.discardConnectionState();
5001 					XCloseDisplay(XDisplayConnection.display);
5002 				}
5003 
5004 				XDisplayConnection.display = null;
5005 
5006 				this.dispose();
5007 
5008 				throw e;
5009 			}
5010 		} else {
5011 			return loopHelper(whileCondition);
5012 		}
5013 	}
5014 }
5015 
5016 
5017 /++
5018 	Provides an icon on the system notification area (also known as the system tray).
5019 
5020 
5021 	If a notification area is not available with the NotificationIcon object is created,
5022 	it will silently succeed and simply attempt to create one when an area becomes available.
5023 
5024 
5025 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
5026 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
5027 	with true color was added at that time. I was just too lazy to write the fallback.
5028 
5029 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
5030 	you use arsd 10.x when targeting Windows XP.
5031 +/
5032 version(Emscripten) {} else
5033 version(OSXCocoa) {} else // NotYetImplementedException
5034 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
5035 
5036 	version(X11) {
5037 		void recreateAfterDisconnect() {
5038 			stateDiscarded = false;
5039 			clippixmap = None;
5040 			throw new Exception("NOT IMPLEMENTED");
5041 		}
5042 
5043 		bool stateDiscarded;
5044 		void discardConnectionState() {
5045 			stateDiscarded = true;
5046 		}
5047 	}
5048 
5049 
5050 	version(X11) {
5051 		Image img;
5052 
5053 		NativeEventHandler getNativeEventHandler() {
5054 			return delegate int(XEvent e) {
5055 				switch(e.type) {
5056 					case EventType.Expose:
5057 					//case EventType.VisibilityNotify:
5058 						redraw();
5059 					break;
5060 					case EventType.ClientMessage:
5061 						version(sddddd) {
5062 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
5063 						writeln("\t", e.xclient.format);
5064 						writeln("\t", e.xclient.data.l);
5065 						}
5066 					break;
5067 					case EventType.ButtonPress:
5068 						auto event = e.xbutton;
5069 						if (onClick !is null || onClickEx !is null) {
5070 							MouseButton mb = cast(MouseButton)0;
5071 							switch (event.button) {
5072 								case 1: mb = MouseButton.left; break; // left
5073 								case 2: mb = MouseButton.middle; break; // middle
5074 								case 3: mb = MouseButton.right; break; // right
5075 								case 4: mb = MouseButton.wheelUp; break; // scroll up
5076 								case 5: mb = MouseButton.wheelDown; break; // scroll down
5077 								case 6: break; // scroll left...
5078 								case 7: break; // scroll right...
5079 								case 8: mb = MouseButton.backButton; break;
5080 								case 9: mb = MouseButton.forwardButton; break;
5081 								default:
5082 							}
5083 							if (mb) {
5084 								try { onClick()(mb); } catch (Exception) {}
5085 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
5086 							}
5087 						}
5088 					break;
5089 					case EventType.EnterNotify:
5090 						if (onEnter !is null) {
5091 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
5092 						}
5093 						break;
5094 					case EventType.LeaveNotify:
5095 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
5096 						break;
5097 					case EventType.DestroyNotify:
5098 						active = false;
5099 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
5100 					break;
5101 					case EventType.ConfigureNotify:
5102 						auto event = e.xconfigure;
5103 						this.width = event.width;
5104 						this.height = event.height;
5105 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
5106 						redraw();
5107 					break;
5108 					default: return 1;
5109 				}
5110 				return 1;
5111 			};
5112 		}
5113 
5114 		/* private */ void hideBalloon() {
5115 			balloon.close();
5116 			version(with_timer)
5117 				timer.destroy();
5118 			balloon = null;
5119 			version(with_timer)
5120 				timer = null;
5121 		}
5122 
5123 		void redraw() {
5124 			if (!active) return;
5125 
5126 			auto display = XDisplayConnection.get;
5127 			GC gc;
5128 
5129 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
5130 
5131 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
5132 				Visual *visual;
5133 				XVisualInfo vis_info;
5134 				XSetWindowAttributes win_attr;
5135 				c_ulong win_mask;
5136 
5137 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
5138 					assert(0);
5139 					// return 1;
5140 				}
5141 
5142 				visual = vis_info.visual;
5143 
5144 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
5145 				win_attr.background_pixel = 0;
5146 				win_attr.border_pixel = 0;
5147 
5148 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
5149 
5150 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
5151 
5152 				return 0;
5153 			}
5154 
5155 			if(useAlpha)
5156 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
5157 			else
5158 				gc = DefaultGC(display, DefaultScreen(display));
5159 
5160 			XClearWindow(display, nativeHandle);
5161 
5162 			if(!useAlpha && img !is null)
5163 				XSetClipMask(display, gc, clippixmap);
5164 
5165 			/+
5166 			XSetForeground(display, gc,
5167 				cast(uint) 0 << 16 |
5168 				cast(uint) 0 << 8 |
5169 				cast(uint) 0);
5170 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
5171 			+/
5172 
5173 			if (img is null) {
5174 				XSetForeground(display, gc,
5175 					cast(uint) 0 << 16 |
5176 					cast(uint) 127 << 8 |
5177 					cast(uint) 0);
5178 				XFillArc(display, nativeHandle,
5179 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
5180 			} else {
5181 				int dx = 0;
5182 				int dy = 0;
5183 				if(width > img.width)
5184 					dx = (width - img.width) / 2;
5185 				if(height > img.height)
5186 					dy = (height - img.height) / 2;
5187 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
5188 				XSetClipOrigin(display, gc, dx, dy);
5189 
5190 				int max(int a, int b) {
5191 					if(a > b) return a; else return b;
5192 				}
5193 
5194 				if (img.usingXshm)
5195 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
5196 				else
5197 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
5198 			}
5199 			XSetClipMask(display, gc, None);
5200 			flushGui();
5201 		}
5202 
5203 		static Window getTrayOwner() {
5204 			auto display = XDisplayConnection.get;
5205 			auto i = cast(int) DefaultScreen(display);
5206 			if(i < 10 && i >= 0) {
5207 				static Atom atom;
5208 				if(atom == None)
5209 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
5210 				return XGetSelectionOwner(display, atom);
5211 			}
5212 			return None;
5213 		}
5214 
5215 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
5216 			auto to = getTrayOwner();
5217 			auto display = XDisplayConnection.get;
5218 			XEvent ev;
5219 			ev.xclient.type = EventType.ClientMessage;
5220 			ev.xclient.window = to;
5221 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
5222 			ev.xclient.format = 32;
5223 			ev.xclient.data.l[0] = CurrentTime;
5224 			ev.xclient.data.l[1] = message;
5225 			ev.xclient.data.l[2] = d1;
5226 			ev.xclient.data.l[3] = d2;
5227 			ev.xclient.data.l[4] = d3;
5228 
5229 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
5230 		}
5231 
5232 		private static NotificationAreaIcon[] activeIcons;
5233 
5234 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
5235 		private void newManager() {
5236 			close();
5237 			createXWin();
5238 
5239 			if(this.clippixmap)
5240 				XFreePixmap(XDisplayConnection.get, clippixmap);
5241 			if(this.originalMemoryImage)
5242 				this.icon = this.originalMemoryImage;
5243 			else if(this.img)
5244 				this.icon = this.img;
5245 		}
5246 
5247 		private bool useAlpha = false;
5248 
5249 		private void createXWin () {
5250 			// create window
5251 			auto display = XDisplayConnection.get;
5252 
5253 			// to check for MANAGER on root window to catch new/changed tray owners
5254 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
5255 			// so if a thing does appear, we can handle it
5256 			foreach(ai; activeIcons)
5257 				if(ai is this)
5258 					goto alreadythere;
5259 			activeIcons ~= this;
5260 			alreadythere:
5261 
5262 			// and check for an existing tray
5263 			auto trayOwner = getTrayOwner();
5264 			if(trayOwner == None)
5265 				return;
5266 				//throw new Exception("No notification area found");
5267 
5268 			Visual* v = cast(Visual*) CopyFromParent;
5269 
5270 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
5271 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
5272 			// a resize event later.
5273 			width = 22;
5274 			height = 22;
5275 
5276 			// if they system gave us a 32 bit visual we need to switch to it too
5277 			int depth = 24;
5278 
5279 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
5280 			if(visualProp !is null) {
5281 				c_ulong[] info = cast(c_ulong[]) visualProp;
5282 				if(info.length == 1) {
5283 					auto vid = info[0];
5284 					int returned;
5285 					XVisualInfo t;
5286 					t.visualid = vid;
5287 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
5288 					if(got !is null) {
5289 						if(returned == 1) {
5290 							v = got.visual;
5291 							depth = got.depth;
5292 							// writeln("using special visual ", got.depth);
5293 							// writeln(depth);
5294 						}
5295 						XFree(got);
5296 					}
5297 				}
5298 			}
5299 
5300 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
5301 			XSetWindowAttributes attr;
5302 			attr.background_pixel = 0;
5303 			attr.border_pixel = 0;
5304 			attr.override_redirect = 0;
5305 			if(v !is cast(Visual*) CopyFromParent) {
5306 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
5307 				CWFlags |= CWColormap;
5308 				if(depth == 32)
5309 					useAlpha = true;
5310 				else
5311 					goto plain;
5312 			} else {
5313 				plain:
5314 				attr.background_pixmap = 1 /* ParentRelative */;
5315 				CWFlags |= CWBackPixmap;
5316 			}
5317 
5318 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
5319 
5320 			assert(nativeWindow);
5321 
5322 			if(!useAlpha)
5323 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
5324 
5325 			nativeHandle = nativeWindow;
5326 
5327 			///+
5328 			arch_ulong[2] info;
5329 			info[0] = 0;
5330 			info[1] = 1;
5331 
5332 			string title = this.name is null ? "simpledisplay.d program" : this.name;
5333 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
5334 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
5335 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
5336 
5337 			XChangeProperty(
5338 				display,
5339 				nativeWindow,
5340 				GetAtom!("_XEMBED_INFO", true)(display),
5341 				GetAtom!("_XEMBED_INFO", true)(display),
5342 				32 /* bits */,
5343 				0 /*PropModeReplace*/,
5344 				info.ptr,
5345 				2);
5346 
5347 			import core.sys.posix.unistd;
5348 			arch_ulong pid = getpid();
5349 
5350 			XChangeProperty(
5351 				display,
5352 				nativeWindow,
5353 				GetAtom!("_NET_WM_PID", true)(display),
5354 				XA_CARDINAL,
5355 				32 /* bits */,
5356 				0 /*PropModeReplace*/,
5357 				&pid,
5358 				1);
5359 
5360 			updateNetWmIcon();
5361 
5362 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
5363 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
5364 				XClassHint klass;
5365 				XWMHints wh;
5366 				XSizeHints size;
5367 				klass.res_name = sdpyWindowClassStr;
5368 				klass.res_class = sdpyWindowClassStr;
5369 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
5370 			}
5371 
5372 				// believe it or not, THIS is what xfce needed for the 9999 issue
5373 				XSizeHints sh;
5374 				c_long spr;
5375 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
5376 				sh.flags |= PMaxSize | PMinSize;
5377 				// FIXME maybe nicer resizing
5378 				sh.min_width = 16;
5379 				sh.min_height = 16;
5380 				sh.max_width = 22;
5381 				sh.max_height = 22;
5382 				XSetWMNormalHints(display, nativeWindow, &sh);
5383 
5384 
5385 			//+/
5386 
5387 
5388 			XSelectInput(display, nativeWindow,
5389 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
5390 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5391 
5392 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5393 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5394 
5395 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5396 			active = true;
5397 		}
5398 
5399 		void updateNetWmIcon() {
5400 			if(img is null) return;
5401 			auto display = XDisplayConnection.get;
5402 			// FIXME: ensure this is correct
5403 			arch_ulong[] buffer;
5404 			auto imgMi = img.toTrueColorImage;
5405 			buffer ~= imgMi.width;
5406 			buffer ~= imgMi.height;
5407 			foreach(c; imgMi.imageData.colors) {
5408 				arch_ulong b;
5409 				b |= c.a << 24;
5410 				b |= c.r << 16;
5411 				b |= c.g << 8;
5412 				b |= c.b;
5413 				buffer ~= b;
5414 			}
5415 
5416 			XChangeProperty(
5417 				display,
5418 				nativeHandle,
5419 				GetAtom!"_NET_WM_ICON"(display),
5420 				GetAtom!"CARDINAL"(display),
5421 				32 /* bits */,
5422 				0 /*PropModeReplace*/,
5423 				buffer.ptr,
5424 				cast(int) buffer.length);
5425 		}
5426 
5427 
5428 
5429 		private SimpleWindow balloon;
5430 		version(with_timer)
5431 		private Timer timer;
5432 
5433 		private Window nativeHandle;
5434 		private Pixmap clippixmap = None;
5435 		private int width = 16;
5436 		private int height = 16;
5437 		private bool active = false;
5438 
5439 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5440 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5441 		void delegate () onLeave; /// X11 only.
5442 
5443 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5444 
5445 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5446 		void getWindowRect (out int x, out int y, out int width, out int height) {
5447 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5448 			Window dummyw;
5449 			auto dpy = XDisplayConnection.get;
5450 			//XWindowAttributes xwa;
5451 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5452 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5453 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5454 			width = this.width;
5455 			height = this.height;
5456 		}
5457 	}
5458 
5459 	/+
5460 		What I actually want from this:
5461 
5462 		* set / change: icon, tooltip
5463 		* handle: mouse click, right click
5464 		* show: notification bubble.
5465 	+/
5466 
5467 	version(Windows) {
5468 		WindowsIcon win32Icon;
5469 		HWND hwnd;
5470 
5471 		NOTIFYICONDATAW data;
5472 
5473 		NativeEventHandler getNativeEventHandler() {
5474 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5475 				if(msg == WM_USER) {
5476 					auto event = LOWORD(lParam);
5477 					auto iconId = HIWORD(lParam);
5478 					//auto x = GET_X_LPARAM(wParam);
5479 					//auto y = GET_Y_LPARAM(wParam);
5480 					switch(event) {
5481 						case WM_LBUTTONDOWN:
5482 							onClick()(MouseButton.left);
5483 						break;
5484 						case WM_RBUTTONDOWN:
5485 							onClick()(MouseButton.right);
5486 						break;
5487 						case WM_MBUTTONDOWN:
5488 							onClick()(MouseButton.middle);
5489 						break;
5490 						case WM_MOUSEMOVE:
5491 							// sent, we could use it.
5492 						break;
5493 						case WM_MOUSEWHEEL:
5494 							// NOT SENT
5495 						break;
5496 						//case NIN_KEYSELECT:
5497 						//case NIN_SELECT:
5498 						//break;
5499 						default: {}
5500 					}
5501 				}
5502 				return 0;
5503 			};
5504 		}
5505 
5506 		enum NIF_SHOWTIP = 0x00000080;
5507 
5508 		private static struct NOTIFYICONDATAW {
5509 			DWORD cbSize;
5510 			HWND  hWnd;
5511 			UINT  uID;
5512 			UINT  uFlags;
5513 			UINT  uCallbackMessage;
5514 			HICON hIcon;
5515 			WCHAR[128] szTip;
5516 			DWORD dwState;
5517 			DWORD dwStateMask;
5518 			WCHAR[256] szInfo;
5519 			union {
5520 				UINT uTimeout;
5521 				UINT uVersion;
5522 			}
5523 			WCHAR[64] szInfoTitle;
5524 			DWORD dwInfoFlags;
5525 			GUID  guidItem;
5526 			HICON hBalloonIcon;
5527 		}
5528 
5529 	}
5530 
5531 	/++
5532 		Note that on Windows, only left, right, and middle buttons are sent.
5533 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5534 		program is meant to be used on Windows too.
5535 	+/
5536 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5537 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5538 		// but on X, we need an Image, so its canonical ctor is there. They should
5539 		// forward to each other though.
5540 		version(X11) {
5541 			this.name = name;
5542 			this.onClick = onClick;
5543 			createXWin();
5544 			this.icon = icon;
5545 		} else version(Windows) {
5546 			this.onClick = onClick;
5547 			this.win32Icon = new WindowsIcon(icon);
5548 
5549 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5550 
5551 			static bool registered = false;
5552 			if(!registered) {
5553 				WNDCLASSEX wc;
5554 				wc.cbSize = wc.sizeof;
5555 				wc.hInstance = hInstance;
5556 				wc.lpfnWndProc = &WndProc;
5557 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5558 				if(!RegisterClassExW(&wc))
5559 					throw new WindowsApiException("RegisterClass", GetLastError());
5560 				registered = true;
5561 			}
5562 
5563 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5564 			if(hwnd is null)
5565 				throw new WindowsApiException("CreateWindow", GetLastError());
5566 
5567 			data.cbSize = data.sizeof;
5568 			data.hWnd = hwnd;
5569 			data.uID = cast(uint) cast(void*) this;
5570 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5571 				// NIF_INFO means show balloon
5572 			data.uCallbackMessage = WM_USER;
5573 			data.hIcon = this.win32Icon.hIcon;
5574 			data.szTip = ""; // FIXME
5575 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5576 			data.dwStateMask = NIS_HIDDEN; // windows vista
5577 
5578 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5579 
5580 
5581 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5582 
5583 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5584 		} else version(OSXCocoa) {
5585 			throw new NotYetImplementedException();
5586 		} else static assert(0);
5587 	}
5588 
5589 	/// ditto
5590 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5591 		version(X11) {
5592 			this.onClick = onClick;
5593 			this.name = name;
5594 			createXWin();
5595 			this.icon = icon;
5596 		} else version(Windows) {
5597 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5598 		} else version(OSXCocoa) {
5599 			throw new NotYetImplementedException();
5600 		} else static assert(0);
5601 	}
5602 
5603 	version(X11) {
5604 		/++
5605 			X-specific extension (for now at least)
5606 		+/
5607 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5608 			this.onClickEx = onClickEx;
5609 			createXWin();
5610 			if (icon !is null) this.icon = icon;
5611 		}
5612 
5613 		/// ditto
5614 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5615 			this.onClickEx = onClickEx;
5616 			createXWin();
5617 			this.icon = icon;
5618 		}
5619 	}
5620 
5621 	private void delegate (MouseButton button) onClick_;
5622 
5623 	///
5624 	@property final void delegate(MouseButton) onClick() {
5625 		if(onClick_ is null)
5626 			onClick_ = delegate void(MouseButton) {};
5627 		return onClick_;
5628 	}
5629 
5630 	/// ditto
5631 	@property final void onClick(void delegate(MouseButton) handler) {
5632 		// I made this a property setter so we can wrap smaller arg
5633 		// delegates and just forward all to onClickEx or something.
5634 		onClick_ = handler;
5635 	}
5636 
5637 
5638 	string name_;
5639 	@property void name(string n) {
5640 		name_ = n;
5641 	}
5642 
5643 	@property string name() {
5644 		return name_;
5645 	}
5646 
5647 	private MemoryImage originalMemoryImage;
5648 
5649 	///
5650 	@property void icon(MemoryImage i) {
5651 		version(X11) {
5652 			this.originalMemoryImage = i;
5653 			if (!active) return;
5654 			if (i !is null) {
5655 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5656 				if(!useAlpha)
5657 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5658 				// writeln("using pixmap ", clippixmap);
5659 				updateNetWmIcon();
5660 				redraw();
5661 			} else {
5662 				if (this.img !is null) {
5663 					this.img = null;
5664 					redraw();
5665 				}
5666 			}
5667 		} else version(Windows) {
5668 			this.win32Icon = new WindowsIcon(i);
5669 
5670 			data.uFlags = NIF_ICON;
5671 			data.hIcon = this.win32Icon.hIcon;
5672 
5673 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5674 		} else version(OSXCocoa) {
5675 			throw new NotYetImplementedException();
5676 		} else static assert(0);
5677 	}
5678 
5679 	/// ditto
5680 	@property void icon (Image i) {
5681 		version(X11) {
5682 			if (!active) return;
5683 			if (i !is img) {
5684 				originalMemoryImage = null;
5685 				img = i;
5686 				redraw();
5687 			}
5688 		} else version(Windows) {
5689 			this.icon(i is null ? null : i.toTrueColorImage());
5690 		} else version(OSXCocoa) {
5691 			throw new NotYetImplementedException();
5692 		} else static assert(0);
5693 	}
5694 
5695 	/++
5696 		Shows a balloon notification. You can only show one balloon at a time, if you call
5697 		it twice while one is already up, the first balloon will be replaced.
5698 
5699 
5700 		The user is free to block notifications and they will automatically disappear after
5701 		a timeout period.
5702 
5703 		Params:
5704 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5705 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5706 			icon = the icon to display with the notification. If null, it uses your existing icon.
5707 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5708 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5709 	+/
5710 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5711 		bool useCustom = true;
5712 		version(libnotify) {
5713 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5714 			try {
5715 				if(!active) return;
5716 
5717 				if(libnotify is null) {
5718 					libnotify = new C_DynamicLibrary("libnotify.so");
5719 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5720 				}
5721 
5722 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5723 
5724 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5725 
5726 				if(onclick) {
5727 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5728 					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);
5729 					libnotify_action_delegates_count++;
5730 				}
5731 
5732 				// FIXME icon
5733 
5734 				// set hint image-data
5735 				// set default action for onclick
5736 
5737 				void* error;
5738 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5739 
5740 				useCustom = false;
5741 			} catch(Exception e) {
5742 
5743 			}
5744 		}
5745 
5746 		version(X11) {
5747 		if(useCustom) {
5748 			if(!active) return;
5749 			if(balloon) {
5750 				hideBalloon();
5751 			}
5752 			// I know there are two specs for this, but one is never
5753 			// implemented by any window manager I have ever seen, and
5754 			// the other is a bloated mess and too complicated for simpledisplay...
5755 			// so doing my own little window instead.
5756 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5757 
5758 			int x, y, width, height;
5759 			getWindowRect(x, y, width, height);
5760 
5761 			int bx = x - balloon.width;
5762 			int by = y - balloon.height;
5763 			if(bx < 0)
5764 				bx = x + width + balloon.width;
5765 			if(by < 0)
5766 				by = y + height;
5767 
5768 			// just in case, make sure it is actually on scren
5769 			if(bx < 0)
5770 				bx = 0;
5771 			if(by < 0)
5772 				by = 0;
5773 
5774 			balloon.move(bx, by);
5775 			auto painter = balloon.draw();
5776 			painter.fillColor = Color(220, 220, 220);
5777 			painter.outlineColor = Color.black;
5778 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5779 			auto iconWidth = icon is null ? 0 : icon.width;
5780 			if(icon)
5781 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5782 			iconWidth += 6; // margin around the icon
5783 
5784 			// draw a close button
5785 			painter.outlineColor = Color(44, 44, 44);
5786 			painter.fillColor = Color(255, 255, 255);
5787 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5788 			painter.pen = Pen(Color.black, 3);
5789 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5790 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5791 			painter.pen = Pen(Color.black, 1);
5792 			painter.fillColor = Color(220, 220, 220);
5793 
5794 			// Draw the title and message
5795 			painter.drawText(Point(4 + iconWidth, 4), title);
5796 			painter.drawLine(
5797 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5798 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5799 			);
5800 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5801 
5802 			balloon.setEventHandlers(
5803 				(MouseEvent ev) {
5804 					if(ev.type == MouseEventType.buttonPressed) {
5805 						if(ev.x > balloon.width - 16 && ev.y < 16)
5806 							hideBalloon();
5807 						else if(onclick)
5808 							onclick();
5809 					}
5810 				}
5811 			);
5812 			balloon.show();
5813 
5814 			version(with_timer)
5815 			timer = new Timer(timeout, &hideBalloon);
5816 			else {} // FIXME
5817 		}
5818 		} else version(Windows) {
5819 			enum NIF_INFO = 0x00000010;
5820 
5821 			data.uFlags = NIF_INFO;
5822 
5823 			// FIXME: go back to the last valid unicode code point
5824 			if(title.length > 40)
5825 				title = title[0 .. 40];
5826 			if(message.length > 220)
5827 				message = message[0 .. 220];
5828 
5829 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5830 			enum NIIF_LARGE_ICON  = 0x00000020;
5831 			enum NIIF_NOSOUND = 0x00000010;
5832 			enum NIIF_USER = 0x00000004;
5833 			enum NIIF_ERROR = 0x00000003;
5834 			enum NIIF_WARNING = 0x00000002;
5835 			enum NIIF_INFO = 0x00000001;
5836 			enum NIIF_NONE = 0;
5837 
5838 			WCharzBuffer t = WCharzBuffer(title);
5839 			WCharzBuffer m = WCharzBuffer(message);
5840 
5841 			t.copyInto(data.szInfoTitle);
5842 			m.copyInto(data.szInfo);
5843 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5844 
5845 			if(icon !is null) {
5846 				auto i = new WindowsIcon(icon);
5847 				data.hBalloonIcon = i.hIcon;
5848 				data.dwInfoFlags |= NIIF_USER;
5849 			}
5850 
5851 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5852 		} else version(OSXCocoa) {
5853 			throw new NotYetImplementedException();
5854 		} else static assert(0);
5855 	}
5856 
5857 	///
5858 	//version(Windows)
5859 	void show() {
5860 		version(X11) {
5861 			if(!hidden)
5862 				return;
5863 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5864 			hidden = false;
5865 		} else version(Windows) {
5866 			data.uFlags = NIF_STATE;
5867 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5868 			data.dwStateMask = NIS_HIDDEN; // windows vista
5869 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5870 		} else version(OSXCocoa) {
5871 			throw new NotYetImplementedException();
5872 		} else static assert(0);
5873 	}
5874 
5875 	version(X11)
5876 		bool hidden = false;
5877 
5878 	///
5879 	//version(Windows)
5880 	void hide() {
5881 		version(X11) {
5882 			if(hidden)
5883 				return;
5884 			hidden = true;
5885 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5886 		} else version(Windows) {
5887 			data.uFlags = NIF_STATE;
5888 			data.dwState = NIS_HIDDEN; // windows vista
5889 			data.dwStateMask = NIS_HIDDEN; // windows vista
5890 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5891 		} else version(OSXCocoa) {
5892 			throw new NotYetImplementedException();
5893 		} else static assert(0);
5894 	}
5895 
5896 	///
5897 	void close () {
5898 		version(X11) {
5899 			if (active) {
5900 				active = false; // event handler will set this too, but meh
5901 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5902 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5903 				flushGui();
5904 			}
5905 		} else version(Windows) {
5906 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5907 		} else version(OSXCocoa) {
5908 			throw new NotYetImplementedException();
5909 		} else static assert(0);
5910 	}
5911 
5912 	~this() {
5913 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5914 		version(X11)
5915 			if(clippixmap != None)
5916 				XFreePixmap(XDisplayConnection.get, clippixmap);
5917 		close();
5918 	}
5919 }
5920 
5921 version(X11)
5922 /// Call `XFreePixmap` on the return value.
5923 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5924 	char[] data = new char[](i.width * i.height / 8 + 2);
5925 	data[] = 0;
5926 
5927 	int bitOffset = 0;
5928 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5929 		ubyte v = c.a > 128 ? 1 : 0;
5930 		data[bitOffset / 8] |= v << (bitOffset%8);
5931 		bitOffset++;
5932 	}
5933 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5934 	return handle;
5935 }
5936 
5937 
5938 // basic functions to make timers
5939 /**
5940 	A timer that will trigger your function on a given interval.
5941 
5942 
5943 	You create a timer with an interval and a callback. It will continue
5944 	to fire on the interval until it is destroyed.
5945 
5946 	There are currently no one-off timers (instead, just create one and
5947 	destroy it when it is triggered) nor are there pause/resume functions -
5948 	the timer must again be destroyed and recreated if you want to pause it.
5949 
5950 	---
5951 	auto timer = new Timer(50, { it happened!; });
5952 	timer.destroy();
5953 	---
5954 
5955 	Timers can only be expected to fire when the event loop is running and only
5956 	once per iteration through the event loop.
5957 
5958 	History:
5959 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5960 		slow could lock up the event loop. It now guarantees other things will
5961 		get a chance to run between timer calls, even if that means not keeping up
5962 		with the requested interval.
5963 */
5964 version(with_timer) {
5965 version(use_arsd_core)
5966 	alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api
5967 else
5968 class Timer {
5969 // FIXME: needs pause and unpause
5970 	// FIXME: I might add overloads for ones that take a count of
5971 	// how many elapsed since last time (on Windows, it will divide
5972 	// the ticks thing given, on Linux it is just available) and
5973 	// maybe one that takes an instance of the Timer itself too
5974 	/// Create a timer with a callback when it triggers.
5975 	this(int intervalInMilliseconds, void delegate() onPulse) @trusted {
5976 		assert(onPulse !is null);
5977 
5978 		this.intervalInMilliseconds = intervalInMilliseconds;
5979 		this.onPulse = onPulse;
5980 
5981 		version(Windows) {
5982 			/*
5983 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5984 			if(handle == 0)
5985 				throw new WindowsApiException("SetTimer", GetLastError());
5986 			*/
5987 
5988 			// thanks to Archival 998 for the WaitableTimer blocks
5989 			handle = CreateWaitableTimer(null, false, null);
5990 			long initialTime = -intervalInMilliseconds;
5991 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5992 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5993 
5994 			mapping[handle] = this;
5995 
5996 		} else version(Emscripten) {
5997 		} else version(linux) {
5998 			static import ep = core.sys.linux.epoll;
5999 
6000 			import core.sys.linux.timerfd;
6001 
6002 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
6003 			if(fd == -1)
6004 				throw new Exception("timer create failed");
6005 
6006 			mapping[fd] = this;
6007 
6008 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6009 
6010 			if(timerfd_settime(fd, 0, &value, null) == -1)
6011 				throw new Exception("couldn't make pulse timer");
6012 
6013 			version(with_eventloop) {
6014 				import arsd.eventloop;
6015 				addFileEventListeners(fd, &trigger, null, null);
6016 			} else {
6017 				prepareEventLoop();
6018 
6019 				ep.epoll_event ev = void;
6020 				ev.events = ep.EPOLLIN;
6021 				ev.data.fd = fd;
6022 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6023 			}
6024 		} else featureNotImplemented();
6025 	}
6026 
6027 	private int intervalInMilliseconds;
6028 
6029 	// just cuz I sometimes call it this.
6030 	alias dispose = destroy;
6031 
6032 	/// Stop and destroy the timer object.
6033 	void destroy() {
6034 		version(Windows) {
6035 			staticDestroy(handle);
6036 			handle = null;
6037 		} else version(linux) {
6038 			staticDestroy(fd);
6039 			fd = -1;
6040 		} else featureNotImplemented();
6041 	}
6042 
6043 	version(Windows)
6044 	static void staticDestroy(HANDLE handle) {
6045 		if(handle) {
6046 			// KillTimer(null, handle);
6047 			CancelWaitableTimer(cast(void*)handle);
6048 			mapping.remove(handle);
6049 			CloseHandle(handle);
6050 		}
6051 	}
6052 	else version(Emscripten)
6053 	static void staticDestroy(int fd) @system {
6054 		assert(0);
6055 	}
6056 	else version(linux)
6057 	static void staticDestroy(int fd) @system {
6058 		if(fd != -1) {
6059 			import unix = core.sys.posix.unistd;
6060 			static import ep = core.sys.linux.epoll;
6061 
6062 			version(with_eventloop) {
6063 				import arsd.eventloop;
6064 				removeFileEventListeners(fd);
6065 			} else {
6066 				ep.epoll_event ev = void;
6067 				ev.events = ep.EPOLLIN;
6068 				ev.data.fd = fd;
6069 
6070 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6071 			}
6072 			unix.close(fd);
6073 			mapping.remove(fd);
6074 		}
6075 	}
6076 
6077 	~this() {
6078 		version(Windows) { if(handle)
6079 			cleanupQueue.queue!staticDestroy(handle);
6080 		} else version(linux) { if(fd != -1)
6081 			cleanupQueue.queue!staticDestroy(fd);
6082 		}
6083 	}
6084 
6085 	void changeTime(int intervalInMilliseconds)
6086 	{
6087 		this.intervalInMilliseconds = intervalInMilliseconds;
6088 		version(Windows)
6089 		{
6090 			if(handle)
6091 			{
6092 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6093 				long initialTime = -intervalInMilliseconds;
6094 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6095 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
6096 			}
6097 		} else version(linux) {
6098 			import core.sys.linux.timerfd;
6099 
6100 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6101 			if(timerfd_settime(fd, 0, &value, null) == -1) {
6102 				throw new Exception("couldn't change pulse timer");
6103 			}
6104 		} else {
6105 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
6106 		}
6107 	}
6108 
6109 
6110 	private:
6111 
6112 	void delegate() onPulse;
6113 
6114 	int lastEventLoopRoundTriggered;
6115 
6116 	version(linux) {
6117 		static auto makeItimerspec(int intervalInMilliseconds) {
6118 			import core.sys.linux.timerfd;
6119 
6120 			itimerspec value;
6121 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6122 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6123 
6124 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6125 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6126 
6127 			return value;
6128 		}
6129 	}
6130 
6131 	void trigger() {
6132 		version(linux) {
6133 			import unix = core.sys.posix.unistd;
6134 			long val;
6135 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
6136 		} else version(Windows) {
6137 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
6138 				return; // never try to actually run faster than the event loop
6139 			lastEventLoopRoundTriggered = eventLoopRound;
6140 		} else featureNotImplemented();
6141 
6142 		onPulse();
6143 	}
6144 
6145 	version(Windows)
6146 	void rearm() {
6147 
6148 	}
6149 
6150 	version(Windows)
6151 		extern(Windows)
6152 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
6153 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
6154 			if(Timer* t = timer in mapping) {
6155 				try
6156 				(*t).trigger();
6157 				catch(Exception e) { sdpy_abort(e); assert(0); }
6158 			}
6159 		}
6160 
6161 	version(Windows) {
6162 		//UINT_PTR handle;
6163 		//static Timer[UINT_PTR] mapping;
6164 		HANDLE handle;
6165 		__gshared Timer[HANDLE] mapping;
6166 	} else version(linux) {
6167 		int fd = -1;
6168 		__gshared Timer[int] mapping;
6169 	} else version(OSXCocoa) {
6170 	} else static assert(0, "timer not supported");
6171 }
6172 }
6173 
6174 version(Windows)
6175 private int eventLoopRound;
6176 
6177 version(Windows)
6178 /// 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
6179 class WindowsHandleReader {
6180 	///
6181 	this(void delegate() onReady, HANDLE handle) {
6182 		this.onReady = onReady;
6183 		this.handle = handle;
6184 
6185 		mapping[handle] = this;
6186 
6187 		enable();
6188 	}
6189 
6190 	version(use_arsd_core)
6191 		ICoreEventLoop.UnregisterToken unregisterToken;
6192 
6193 	///
6194 	void enable() {
6195 		version(use_arsd_core) {
6196 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready));
6197 		} else {
6198 			auto el = EventLoop.get().impl;
6199 			el.handles ~= handle;
6200 		}
6201 	}
6202 
6203 	///
6204 	void disable() {
6205 		version(use_arsd_core) {
6206 			unregisterToken.unregister();
6207 		} else {
6208 			auto el = EventLoop.get().impl;
6209 			for(int i = 0; i < el.handles.length; i++) {
6210 				if(el.handles[i] is handle) {
6211 					el.handles[i] = el.handles[$-1];
6212 					el.handles = el.handles[0 .. $-1];
6213 					return;
6214 				}
6215 			}
6216 		}
6217 	}
6218 
6219 	void dispose() {
6220 		disable();
6221 		if(handle)
6222 			mapping.remove(handle);
6223 		handle = null;
6224 	}
6225 
6226 	void ready() {
6227 		if(onReady)
6228 			onReady();
6229 	}
6230 
6231 	HANDLE handle;
6232 	void delegate() onReady;
6233 
6234 	__gshared WindowsHandleReader[HANDLE] mapping;
6235 }
6236 
6237 version(Posix)
6238 /// Lets you add files to the event loop for reading. Use at your own risk.
6239 class PosixFdReader {
6240 	///
6241 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6242 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
6243 	}
6244 
6245 	///
6246 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6247 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
6248 	}
6249 
6250 	///
6251 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6252 		this.onReady = onReady;
6253 		this.fd = fd;
6254 		this.captureWrites = captureWrites;
6255 		this.captureReads = captureReads;
6256 
6257 		mapping[fd] = this;
6258 
6259 		version(with_eventloop) {
6260 			import arsd.eventloop;
6261 			addFileEventListeners(fd, &readyel);
6262 		} else {
6263 			enable();
6264 		}
6265 	}
6266 
6267 	bool captureReads;
6268 	bool captureWrites;
6269 
6270 	version(use_arsd_core) {
6271 		import arsd.core;
6272 		ICoreEventLoop.UnregisterToken unregisterToken;
6273 	}
6274 
6275 	version(with_eventloop) {} else
6276 	///
6277 	void enable() @system {
6278 		enabled = true;
6279 
6280 		version(use_arsd_core) {
6281 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper(
6282 				() { onReady(fd, true, false); }
6283 			));
6284 			// FIXME: what if it is writeable?
6285 
6286 		} else version(linux) {
6287 			prepareEventLoop();
6288 			static import ep = core.sys.linux.epoll;
6289 			ep.epoll_event ev = void;
6290 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6291 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
6292 			ev.data.fd = fd;
6293 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6294 		} else {
6295 
6296 		}
6297 	}
6298 
6299 	version(with_eventloop) {} else
6300 	///
6301 	void disable() @system {
6302 		enabled = false;
6303 
6304 		version(use_arsd_core) {
6305 			unregisterToken.unregister();
6306 		} else
6307 		version(linux) {
6308 			prepareEventLoop();
6309 			static import ep = core.sys.linux.epoll;
6310 			ep.epoll_event ev = void;
6311 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6312 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
6313 			ev.data.fd = fd;
6314 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6315 		}
6316 	}
6317 
6318 	version(with_eventloop) {} else
6319 	///
6320 	void dispose() {
6321 		if(enabled)
6322 			disable();
6323 		if(fd != -1)
6324 			mapping.remove(fd);
6325 		fd = -1;
6326 	}
6327 
6328 	void delegate(int, bool, bool) onReady;
6329 
6330 	version(with_eventloop)
6331 	void readyel() {
6332 		onReady(fd, true, true);
6333 	}
6334 
6335 	void ready(uint flags) {
6336 		version(Emscripten) {
6337 			assert(0);
6338 		} else version(linux) {
6339 			static import ep = core.sys.linux.epoll;
6340 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
6341 		} else {
6342 			import core.sys.posix.poll;
6343 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
6344 		}
6345 	}
6346 
6347 	void hup(uint flags) {
6348 		if(onHup)
6349 			onHup();
6350 	}
6351 
6352 	void delegate() onHup;
6353 
6354 	int fd = -1;
6355 	private bool enabled;
6356 	__gshared PosixFdReader[int] mapping;
6357 }
6358 
6359 // basic functions to access the clipboard
6360 /+
6361 
6362 
6363 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
6364 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
6365 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6366 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
6367 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
6368 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6369 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
6370 
6371 +/
6372 
6373 /++
6374 	this does a delegate because it is actually an async call on X...
6375 	the receiver may never be called if the clipboard is empty or unavailable
6376 	gets plain text from the clipboard.
6377 +/
6378 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system {
6379 	version(Windows) {
6380 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6381 		if(OpenClipboard(hwndOwner) == 0)
6382 			throw new WindowsApiException("OpenClipboard", GetLastError());
6383 		scope(exit)
6384 			CloseClipboard();
6385 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
6386 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
6387 
6388 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
6389 				scope(exit)
6390 					GlobalUnlock(dataHandle);
6391 
6392 				// FIXME: CR/LF conversions
6393 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
6394 				int len = 0;
6395 				auto d = data;
6396 				while(*d) {
6397 					d++;
6398 					len++;
6399 				}
6400 				string s;
6401 				s.reserve(len);
6402 				foreach(dchar ch; data[0 .. len]) {
6403 					s ~= ch;
6404 				}
6405 				receiver(s);
6406 			}
6407 		}
6408 	} else version(X11) {
6409 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6410 	} else version(OSXCocoa) {
6411 		throw new NotYetImplementedException();
6412 	} else version(Emscripten) {
6413 		throw new NotYetImplementedException();
6414 	} else static assert(0);
6415 }
6416 
6417 // FIXME: a clipboard listener might be cool btw
6418 
6419 /++
6420 	this does a delegate because it is actually an async call on X...
6421 	the receiver may never be called if the clipboard is empty or unavailable
6422 	gets image from the clipboard.
6423 
6424 	templated because it introduces an optional dependency on arsd.bmp
6425 +/
6426 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6427 	version(Windows) {
6428 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6429 		if(OpenClipboard(hwndOwner) == 0)
6430 			throw new WindowsApiException("OpenClipboard", GetLastError());
6431 		scope(exit)
6432 			CloseClipboard();
6433 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6434 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6435 				scope(exit)
6436 					GlobalUnlock(dataHandle);
6437 
6438 				auto len = GlobalSize(dataHandle);
6439 
6440 				import arsd.bmp;
6441 				auto img = readBmp(data[0 .. len], false);
6442 				receiver(img);
6443 			}
6444 		}
6445 	} else version(X11) {
6446 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6447 	} else version(OSXCocoa) {
6448 		throw new NotYetImplementedException();
6449 	} else version(Emscripten) {
6450 		throw new NotYetImplementedException();
6451 	} else static assert(0);
6452 }
6453 
6454 /// Copies some text to the clipboard.
6455 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6456 	assert(clipboardOwner !is null);
6457 	version(Windows) {
6458 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6459 			throw new WindowsApiException("OpenClipboard", GetLastError());
6460 		scope(exit)
6461 			CloseClipboard();
6462 		EmptyClipboard();
6463 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6464 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6465 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6466 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6467 			auto slice = data[0 .. sz];
6468 			scope(failure)
6469 				GlobalUnlock(handle);
6470 
6471 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6472 
6473 			GlobalUnlock(handle);
6474 			SetClipboardData(CF_UNICODETEXT, handle);
6475 		}
6476 	} else version(X11) {
6477 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6478 	} else version(OSXCocoa) {
6479 		throw new NotYetImplementedException();
6480 	} else version(Emscripten) {
6481 		throw new NotYetImplementedException();
6482 	} else static assert(0);
6483 }
6484 
6485 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6486 	assert(clipboardOwner !is null);
6487 	version(Windows) {
6488 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6489 			throw new WindowsApiException("OpenClipboard", GetLastError());
6490 		scope(exit)
6491 			CloseClipboard();
6492 		EmptyClipboard();
6493 
6494 
6495 		import arsd.bmp;
6496 		ubyte[] mdata;
6497 		mdata.reserve(img.width * img.height);
6498 		void sink(ubyte b) {
6499 			mdata ~= b;
6500 		}
6501 		writeBmpIndirect(img, &sink, false);
6502 
6503 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6504 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6505 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6506 			auto slice = data[0 .. mdata.length];
6507 			scope(failure)
6508 				GlobalUnlock(handle);
6509 
6510 			slice[] = mdata[];
6511 
6512 			GlobalUnlock(handle);
6513 			SetClipboardData(CF_DIB, handle);
6514 		}
6515 	} else version(X11) {
6516 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6517 			mixin X11SetSelectionHandler_Basics;
6518 			private const(ubyte)[] mdata;
6519 			private const(ubyte)[] mdata_original;
6520 			this(MemoryImage img) {
6521 				import arsd.bmp;
6522 
6523 				mdata.reserve(img.width * img.height);
6524 				void sink(ubyte b) {
6525 					mdata ~= b;
6526 				}
6527 				writeBmpIndirect(img, &sink, true);
6528 
6529 				mdata_original = mdata;
6530 			}
6531 
6532 			Atom[] availableFormats() {
6533 				auto display = XDisplayConnection.get;
6534 				return [
6535 					GetAtom!"image/bmp"(display),
6536 					GetAtom!"TARGETS"(display)
6537 				];
6538 			}
6539 
6540 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6541 				if(mdata.length < data.length) {
6542 					data[0 .. mdata.length] = mdata[];
6543 					auto ret = data[0 .. mdata.length];
6544 					mdata = mdata[$..$];
6545 					return ret;
6546 				} else {
6547 					data[] = mdata[0 .. data.length];
6548 					mdata = mdata[data.length .. $];
6549 					return data[];
6550 				}
6551 			}
6552 
6553 			void done() {
6554 				mdata = mdata_original;
6555 			}
6556 		}
6557 
6558 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6559 	} else version(OSXCocoa) {
6560 		throw new NotYetImplementedException();
6561 	} else version(Emscripten) {
6562 		throw new NotYetImplementedException();
6563 	} else static assert(0);
6564 }
6565 
6566 
6567 version(X11) {
6568 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6569 
6570 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6571 
6572 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6573 	/// Platform-specific for X11.
6574 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6575 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6576 		__gshared static Atom a;
6577 		if(!a) {
6578 			a = XInternAtom(display, name, !create);
6579 			// FIXME: might need to synchronize this and attach it to the actual object
6580 			interredAtoms ~= &a;
6581 		}
6582 		if(a == None)
6583 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6584 		return a;
6585 	}
6586 
6587 	/// Platform-specific for X11 - gets atom names as a string.
6588 	string getAtomName(Atom atom, Display* display) {
6589 		auto got = XGetAtomName(display, atom);
6590 		scope(exit) XFree(got);
6591 		import core.stdc.string;
6592 		string s = got[0 .. strlen(got)].idup;
6593 		return s;
6594 	}
6595 
6596 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6597 	void setPrimarySelection(SimpleWindow window, string text) {
6598 		setX11Selection!"PRIMARY"(window, text);
6599 	}
6600 
6601 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6602 	void setSecondarySelection(SimpleWindow window, string text) {
6603 		setX11Selection!"SECONDARY"(window, text);
6604 	}
6605 
6606 	interface X11SetSelectionHandler {
6607 		// should include TARGETS right now
6608 		Atom[] availableFormats();
6609 		// Return the slice of data you filled, empty slice if done.
6610 		// this is to support the incremental thing
6611 		ubyte[] getData(Atom format, return scope ubyte[] data);
6612 
6613 		void done();
6614 
6615 		void handleRequest(XEvent);
6616 
6617 		bool matchesIncr(Window, Atom);
6618 		void sendMoreIncr(XPropertyEvent*);
6619 	}
6620 
6621 	mixin template X11SetSelectionHandler_Basics() {
6622 		Window incrWindow;
6623 		Atom incrAtom;
6624 		Atom selectionAtom;
6625 		Atom formatAtom;
6626 		ubyte[] toSend;
6627 		bool matchesIncr(Window w, Atom a) {
6628 			return incrAtom && incrAtom == a && w == incrWindow;
6629 		}
6630 		void sendMoreIncr(XPropertyEvent* event) {
6631 			auto display = XDisplayConnection.get;
6632 
6633 			XChangeProperty (display,
6634 				incrWindow,
6635 				incrAtom,
6636 				formatAtom,
6637 				8 /* bits */, PropModeReplace,
6638 				toSend.ptr, cast(int) toSend.length);
6639 
6640 			if(toSend.length != 0) {
6641 				toSend = this.getData(formatAtom, toSend[]);
6642 			} else {
6643 				this.done();
6644 				incrWindow = None;
6645 				incrAtom = None;
6646 				selectionAtom = None;
6647 				formatAtom = None;
6648 				toSend = null;
6649 			}
6650 		}
6651 		void handleRequest(XEvent ev) {
6652 
6653 			auto display = XDisplayConnection.get;
6654 
6655 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6656 			XSelectionEvent selectionEvent;
6657 			selectionEvent.type = EventType.SelectionNotify;
6658 			selectionEvent.display = event.display;
6659 			selectionEvent.requestor = event.requestor;
6660 			selectionEvent.selection = event.selection;
6661 			selectionEvent.time = event.time;
6662 			selectionEvent.target = event.target;
6663 
6664 			bool supportedType() {
6665 				foreach(t; this.availableFormats())
6666 					if(t == event.target)
6667 						return true;
6668 				return false;
6669 			}
6670 
6671 			if(event.property == None) {
6672 				selectionEvent.property = event.target;
6673 
6674 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6675 				XFlush(display);
6676 			} if(event.target == GetAtom!"TARGETS"(display)) {
6677 				/* respond with the supported types */
6678 				auto tlist = this.availableFormats();
6679 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6680 				selectionEvent.property = event.property;
6681 
6682 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6683 				XFlush(display);
6684 			} else if(supportedType()) {
6685 				auto buffer = new ubyte[](1024 * 64);
6686 				auto toSend = this.getData(event.target, buffer[]);
6687 
6688 				if(toSend.length < 32 * 1024) {
6689 					// small enough to send directly...
6690 					selectionEvent.property = event.property;
6691 					XChangeProperty (display,
6692 						selectionEvent.requestor,
6693 						selectionEvent.property,
6694 						event.target,
6695 						8 /* bits */, 0 /* PropModeReplace */,
6696 						toSend.ptr, cast(int) toSend.length);
6697 
6698 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6699 					XFlush(display);
6700 				} else {
6701 					// large, let's send incrementally
6702 					arch_ulong l = toSend.length;
6703 
6704 					// if I wanted other events from this window don't want to clear that out....
6705 					XWindowAttributes xwa;
6706 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6707 
6708 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6709 
6710 					incrWindow = event.requestor;
6711 					incrAtom = event.property;
6712 					formatAtom = event.target;
6713 					selectionAtom = event.selection;
6714 					this.toSend = toSend;
6715 
6716 					selectionEvent.property = event.property;
6717 					XChangeProperty (display,
6718 						selectionEvent.requestor,
6719 						selectionEvent.property,
6720 						GetAtom!"INCR"(display),
6721 						32 /* bits */, PropModeReplace,
6722 						&l, 1);
6723 
6724 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6725 					XFlush(display);
6726 				}
6727 				//if(after)
6728 					//after();
6729 			} else {
6730 				debug(sdpy_clip) {
6731 					writeln("Unsupported data ", getAtomName(event.target, display));
6732 				}
6733 				selectionEvent.property = None; // I don't know how to handle this type...
6734 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6735 				XFlush(display);
6736 			}
6737 		}
6738 	}
6739 
6740 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6741 		mixin X11SetSelectionHandler_Basics;
6742 		private const(ubyte)[] text;
6743 		private const(ubyte)[] text_original;
6744 		this(string text) {
6745 			this.text = cast(const ubyte[]) text;
6746 			this.text_original = this.text;
6747 		}
6748 		Atom[] availableFormats() {
6749 			auto display = XDisplayConnection.get;
6750 			return [
6751 				GetAtom!"UTF8_STRING"(display),
6752 				GetAtom!"text/plain"(display),
6753 				XA_STRING,
6754 				GetAtom!"TARGETS"(display)
6755 			];
6756 		}
6757 
6758 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6759 			if(text.length < data.length) {
6760 				data[0 .. text.length] = text[];
6761 				return data[0 .. text.length];
6762 			} else {
6763 				data[] = text[0 .. data.length];
6764 				text = text[data.length .. $];
6765 				return data[];
6766 			}
6767 		}
6768 
6769 		void done() {
6770 			text = text_original;
6771 		}
6772 	}
6773 
6774 	/// 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?!)
6775 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6776 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6777 	}
6778 
6779 	private __gshared bool mightShortCircuitClipboard;
6780 
6781 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6782 		assert(window !is null);
6783 
6784 		auto display = XDisplayConnection.get();
6785 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6786 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6787 		else Atom a = GetAtom!atomName(display);
6788 
6789 		if(mightShortCircuitClipboard)
6790 		if(auto ptr = a in window.impl.setSelectionHandlers) {
6791 			// we already have it, don't even need to inform the X server
6792 			// sdpyPrintDebugString("short circuit in set");
6793 			*ptr = data;
6794 			return;
6795 		}
6796 
6797 		// we don't have it, tell X we want it
6798 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6799 		window.impl.setSelectionHandlers[a] = data;
6800 		mightShortCircuitClipboard = true;
6801 	}
6802 
6803 	/+
6804 	/++
6805 		History:
6806 			Added September 28, 2024
6807 	+/
6808 	bool hasX11Selection(string atomName)(SimpleWindow window) {
6809 		auto display = XDisplayConnection.get();
6810 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6811 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6812 		else Atom a = GetAtom!atomName(display);
6813 
6814 		if(a in window.impl.setSelectionHandlers)
6815 			return true;
6816 		else
6817 			return false;
6818 	}
6819 	+/
6820 
6821 	///
6822 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6823 		getX11Selection!"PRIMARY"(window, handler);
6824 	}
6825 
6826 	// added July 28, 2020
6827 	// undocumented as experimental tho
6828 	interface X11GetSelectionHandler {
6829 		void handleData(Atom target, in ubyte[] data);
6830 		Atom findBestFormat(Atom[] answer);
6831 
6832 		void prepareIncremental(Window, Atom);
6833 		bool matchesIncr(Window, Atom);
6834 		void handleIncrData(Atom, in ubyte[] data);
6835 	}
6836 
6837 	mixin template X11GetSelectionHandler_Basics() {
6838 		Window incrWindow;
6839 		Atom incrAtom;
6840 
6841 		void prepareIncremental(Window w, Atom a) {
6842 			incrWindow = w;
6843 			incrAtom = a;
6844 		}
6845 		bool matchesIncr(Window w, Atom a) {
6846 			return incrWindow == w && incrAtom == a;
6847 		}
6848 
6849 		Atom incrFormatAtom;
6850 		ubyte[] incrData;
6851 		void handleIncrData(Atom format, in ubyte[] data) {
6852 			incrFormatAtom = format;
6853 
6854 			if(data.length)
6855 				incrData ~= data;
6856 			else
6857 				handleData(incrFormatAtom, incrData);
6858 
6859 		}
6860 	}
6861 
6862 	static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6863 		this(void delegate(in char[]) handler) {
6864 			this.handler = handler;
6865 		}
6866 
6867 		mixin X11GetSelectionHandler_Basics;
6868 
6869 		void delegate(in char[]) handler;
6870 
6871 		void handleData(Atom target, in ubyte[] data) {
6872 			// import std.stdio; writeln(target, " ", data);
6873 			if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6874 				handler(cast(const char[]) data);
6875 			else if(target == None && data is null)
6876 				handler(null); // no suitable selection exists
6877 		}
6878 
6879 		Atom findBestFormat(Atom[] answer) {
6880 			Atom best = None;
6881 			foreach(option; answer) {
6882 				if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6883 					best = option;
6884 					break;
6885 				} else if(option == XA_STRING) {
6886 					best = option;
6887 				} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6888 					best = option;
6889 				}
6890 			}
6891 			return best;
6892 		}
6893 	}
6894 
6895 	///
6896 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6897 		assert(window !is null);
6898 
6899 		auto display = XDisplayConnection.get();
6900 
6901 		static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY;
6902 		else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY;
6903 		else Atom atom = GetAtom!atomName(display);
6904 
6905 		if(mightShortCircuitClipboard)
6906 		if(auto ptr = atom in window.impl.setSelectionHandlers) {
6907 			if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) {
6908 				// we already have it! short circuit everything
6909 
6910 				// sdpyPrintDebugString("short circuit in get");
6911 				handler(cast(char[]) txt.text_original);
6912 				return;
6913 			}
6914 		}
6915 
6916 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6917 
6918 		auto target = GetAtom!"TARGETS"(display);
6919 
6920 		// SDD_DATA is "simpledisplay.d data"
6921 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6922 	}
6923 
6924 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6925 	/// only supports bmps. using this function will import arsd.bmp.
6926 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6927 		assert(window !is null);
6928 
6929 		auto display = XDisplayConnection.get();
6930 		auto atom = GetAtom!atomName(display);
6931 
6932 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6933 			this(void delegate(MemoryImage) handler) {
6934 				this.handler = handler;
6935 			}
6936 
6937 			mixin X11GetSelectionHandler_Basics;
6938 
6939 			void delegate(MemoryImage) handler;
6940 
6941 			void handleData(Atom target, in ubyte[] data) {
6942 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6943 					import arsd.bmp;
6944 					handler(readBmp(data));
6945 				}
6946 			}
6947 
6948 			Atom findBestFormat(Atom[] answer) {
6949 				Atom best = None;
6950 				foreach(option; answer) {
6951 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6952 						best = option;
6953 					}
6954 				}
6955 				return best;
6956 			}
6957 
6958 		}
6959 
6960 
6961 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6962 
6963 		auto target = GetAtom!"TARGETS"(display);
6964 
6965 		// SDD_DATA is "simpledisplay.d data"
6966 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6967 	}
6968 
6969 
6970 	///
6971 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6972 		Atom actualType;
6973 		int actualFormat;
6974 		arch_ulong actualItems;
6975 		arch_ulong bytesRemaining;
6976 		void* data;
6977 
6978 		auto display = XDisplayConnection.get();
6979 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6980 			if(actualFormat == 0)
6981 				return null;
6982 			else {
6983 				int byteLength;
6984 				if(actualFormat == 32) {
6985 					// 32 means it is a C long... which is variable length
6986 					actualFormat = cast(int) arch_long.sizeof * 8;
6987 				}
6988 
6989 				// then it is just a bit count
6990 				byteLength = cast(int) (actualItems * actualFormat / 8);
6991 
6992 				auto d = new ubyte[](byteLength);
6993 				d[] = cast(ubyte[]) data[0 .. byteLength];
6994 				XFree(data);
6995 				return d;
6996 			}
6997 		}
6998 		return null;
6999 	}
7000 
7001 	/* defined in the systray spec */
7002 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
7003 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
7004 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
7005 
7006 
7007 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
7008 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
7009 	public class GlobalHotkey {
7010 		KeyEvent key;
7011 		void delegate () handler;
7012 
7013 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
7014 
7015 		/// Create from initialzed KeyEvent object
7016 		this (KeyEvent akey, void delegate () ahandler=null) {
7017 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
7018 			key = akey;
7019 			handler = ahandler;
7020 		}
7021 
7022 		/// Create from emacs-like key name ("C-M-Y", etc.)
7023 		this (const(char)[] akey, void delegate () ahandler=null) {
7024 			key = KeyEvent.parse(akey);
7025 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
7026 			handler = ahandler;
7027 		}
7028 
7029 	}
7030 
7031 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7032 		//conwriteln("failed to grab key");
7033 		GlobalHotkeyManager.ghfailed = true;
7034 		return 0;
7035 	}
7036 
7037 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7038 		Image.impl.xshmfailed = true;
7039 		return 0;
7040 	}
7041 
7042 	private __gshared int errorHappened;
7043 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7044 		import core.stdc.stdio;
7045 		char[265] buffer;
7046 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
7047 		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);
7048 		errorHappened = true;
7049 		return 0;
7050 	}
7051 
7052 	/++
7053 		Global hotkey manager. It contains static methods to manage global hotkeys.
7054 
7055 		---
7056 		 try {
7057 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
7058 		} catch (Exception e) {
7059 			conwriteln("ERROR registering hotkey!");
7060 		}
7061 		EventLoop.get.run();
7062 		---
7063 
7064 		The key strings are based on Emacs. In practical terms,
7065 		`M` means `alt` and `H` means the Windows logo key. `C`
7066 		is `ctrl`.
7067 
7068 		$(WARNING
7069 			This is X-specific right now. If you are on
7070 			Windows, try [registerHotKey] instead.
7071 
7072 			We will probably merge these into a single
7073 			interface later.
7074 		)
7075 	+/
7076 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
7077 		version(X11) {
7078 			void recreateAfterDisconnect() {
7079 				throw new Exception("NOT IMPLEMENTED");
7080 			}
7081 			void discardConnectionState() {
7082 				throw new Exception("NOT IMPLEMENTED");
7083 			}
7084 		}
7085 
7086 		private static immutable uint[8] masklist = [ 0,
7087 			KeyOrButtonMask.LockMask,
7088 			KeyOrButtonMask.Mod2Mask,
7089 			KeyOrButtonMask.Mod3Mask,
7090 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
7091 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
7092 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7093 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7094 		];
7095 		private __gshared GlobalHotkeyManager ghmanager;
7096 		private __gshared bool ghfailed = false;
7097 
7098 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
7099 			if (modmask == 0) return false;
7100 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
7101 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
7102 			return true;
7103 		}
7104 
7105 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
7106 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
7107 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
7108 			return modmask;
7109 		}
7110 
7111 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
7112 			uint keycode = cast(uint)ke.key;
7113 			auto dpy = XDisplayConnection.get;
7114 			return XKeysymToKeycode(dpy, keycode);
7115 		}
7116 
7117 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
7118 
7119 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
7120 
7121 		NativeEventHandler getNativeEventHandler () {
7122 			return delegate int (XEvent e) {
7123 				if (e.type != EventType.KeyPress) return 1;
7124 				auto kev = cast(const(XKeyEvent)*)&e;
7125 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
7126 				if (auto ghkp = hash in globalHotkeyList) {
7127 					try {
7128 						ghkp.doHandle();
7129 					} catch (Exception e) {
7130 						import core.stdc.stdio : stderr, fprintf;
7131 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
7132 					}
7133 				}
7134 				return 1;
7135 			};
7136 		}
7137 
7138 		private this () {
7139 			auto dpy = XDisplayConnection.get;
7140 			auto root = RootWindow(dpy, DefaultScreen(dpy));
7141 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
7142 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
7143 		}
7144 
7145 		/// Register new global hotkey with initialized `GlobalHotkey` object.
7146 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
7147 		static void register (GlobalHotkey gh) {
7148 			if (gh is null) return;
7149 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
7150 
7151 			auto dpy = XDisplayConnection.get;
7152 			immutable keycode = keyEvent2KeyCode(gh.key);
7153 
7154 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
7155 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
7156 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
7157 			XSync(dpy, 0/*False*/);
7158 
7159 			Window root = RootWindow(dpy, DefaultScreen(dpy));
7160 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7161 			ghfailed = false;
7162 			foreach (immutable uint ormask; masklist[]) {
7163 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
7164 			}
7165 			XSync(dpy, 0/*False*/);
7166 			XSetErrorHandler(savedErrorHandler);
7167 
7168 			if (ghfailed) {
7169 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7170 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
7171 				XSync(dpy, 0/*False*/);
7172 				XSetErrorHandler(savedErrorHandler);
7173 				throw new Exception("cannot register global hotkey");
7174 			}
7175 
7176 			globalHotkeyList[hash] = gh;
7177 		}
7178 
7179 		/// Ditto
7180 		static void register (const(char)[] akey, void delegate () ahandler) {
7181 			register(new GlobalHotkey(akey, ahandler));
7182 		}
7183 
7184 		private static void removeByHash (ulong hash) {
7185 			if (auto ghp = hash in globalHotkeyList) {
7186 				auto dpy = XDisplayConnection.get;
7187 				immutable keycode = keyEvent2KeyCode(ghp.key);
7188 				Window root = RootWindow(dpy, DefaultScreen(dpy));
7189 				XSync(dpy, 0/*False*/);
7190 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7191 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
7192 				XSync(dpy, 0/*False*/);
7193 				XSetErrorHandler(savedErrorHandler);
7194 				globalHotkeyList.remove(hash);
7195 			}
7196 		}
7197 
7198 		/// Register new global hotkey with previously used `GlobalHotkey` object.
7199 		/// It is safe to unregister unknown or invalid hotkey.
7200 		static void unregister (GlobalHotkey gh) {
7201 			//TODO: add second AA for faster search? prolly doesn't worth it.
7202 			if (gh is null) return;
7203 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
7204 				if (kv.value is gh) {
7205 					removeByHash(kv.key);
7206 					return;
7207 				}
7208 			}
7209 		}
7210 
7211 		/// Ditto.
7212 		static void unregister (const(char)[] key) {
7213 			auto kev = KeyEvent.parse(key);
7214 			immutable keycode = keyEvent2KeyCode(kev);
7215 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
7216 		}
7217 	}
7218 }
7219 
7220 version(Windows) {
7221 	/++
7222 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
7223 
7224 		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).
7225 	+/
7226 	void sendSyntheticInput(wstring s) {
7227 			INPUT[] inputs;
7228 			inputs.reserve(s.length * 2);
7229 
7230 			foreach(wchar c; s) {
7231 				INPUT input;
7232 				input.type = INPUT_KEYBOARD;
7233 				input.ki.wScan = c;
7234 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7235 				inputs ~= input;
7236 
7237 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7238 				inputs ~= input;
7239 			}
7240 
7241 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7242 				throw new WindowsApiException("SendInput", GetLastError());
7243 			}
7244 
7245 	}
7246 
7247 
7248 	// global hotkey helper function
7249 
7250 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
7251 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system {
7252 		__gshared int hotkeyId = 0;
7253 		int id = ++hotkeyId;
7254 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
7255 			throw new Exception("RegisterHotKey");
7256 
7257 		__gshared void delegate()[WPARAM][HWND] handlers;
7258 
7259 		handlers[window.impl.hwnd][id] = handler;
7260 
7261 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
7262 
7263 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7264 			switch(msg) {
7265 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
7266 				case WM_HOTKEY:
7267 					if(auto list = hwnd in handlers) {
7268 						if(auto h = wParam in *list) {
7269 							(*h)();
7270 							return 0;
7271 						}
7272 					}
7273 				goto default;
7274 				default:
7275 			}
7276 			if(oldHandler)
7277 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
7278 			return 1; // pass it on
7279 		};
7280 
7281 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
7282 			oldHandler = window.handleNativeEvent;
7283 			window.handleNativeEvent = nativeEventHandler;
7284 		}
7285 
7286 		return id;
7287 	}
7288 
7289 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
7290 	void unregisterHotKey(SimpleWindow window, int id) {
7291 		if(!UnregisterHotKey(window.impl.hwnd, id))
7292 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
7293 	}
7294 }
7295 
7296 version (X11) {
7297 	pragma(lib, "dl");
7298 	import core.sys.posix.dlfcn;
7299 }
7300 
7301 /++
7302 	Allows for sending synthetic input to the X server via the Xtst
7303 	extension or on Windows using SendInput.
7304 
7305 	Please remember user input is meant to be user - don't use this
7306 	if you have some other alternative!
7307 
7308 	History:
7309 		Added May 17, 2020 with the X implementation.
7310 
7311 		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.)
7312 	Bugs:
7313 		All methods on OSX Cocoa will throw not yet implemented exceptions.
7314 +/
7315 struct SyntheticInput {
7316 	@disable this();
7317 
7318 	private int* refcount;
7319 
7320 	version(X11) {
7321 		private void* lib;
7322 
7323 		private extern(C) {
7324 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
7325 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
7326 		}
7327 	}
7328 
7329 	/// The dummy param must be 0.
7330 	this(int dummy) {
7331 		version(X11) {
7332 			lib = dlopen("libXtst.so", RTLD_NOW);
7333 			if(lib is null)
7334 				throw new Exception("cannot load xtest lib extension");
7335 			scope(failure)
7336 				dlclose(lib);
7337 
7338 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
7339 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
7340 
7341 			if(XTestFakeKeyEvent is null)
7342 				throw new Exception("No XTestFakeKeyEvent");
7343 			if(XTestFakeButtonEvent is null)
7344 				throw new Exception("No XTestFakeButtonEvent");
7345 		}
7346 
7347 		refcount = new int;
7348 		*refcount = 1;
7349 	}
7350 
7351 	this(this) {
7352 		if(refcount)
7353 			*refcount += 1;
7354 	}
7355 
7356 	~this() {
7357 		if(refcount) {
7358 			*refcount -= 1;
7359 			if(*refcount == 0)
7360 				// I commented this because if I close the lib before
7361 				// XCloseDisplay, it is liable to segfault... so just
7362 				// gonna keep it loaded if it is loaded, no big deal
7363 				// anyway.
7364 				{} // dlclose(lib);
7365 		}
7366 	}
7367 
7368 	/++
7369 		Simulates typing a string into the keyboard.
7370 
7371 		Bugs:
7372 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
7373 
7374 			Not implemented except on Windows and X11.
7375 	+/
7376 	void sendSyntheticInput(string s) {
7377 		version(Windows) {
7378 			INPUT[] inputs;
7379 			inputs.reserve(s.length * 2);
7380 
7381 			auto ei = GetMessageExtraInfo();
7382 
7383 			foreach(wchar c; s) {
7384 				INPUT input;
7385 				input.type = INPUT_KEYBOARD;
7386 				input.ki.wScan = c;
7387 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7388 				input.ki.dwExtraInfo = ei;
7389 				inputs ~= input;
7390 
7391 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7392 				inputs ~= input;
7393 			}
7394 
7395 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7396 				throw new WindowsApiException("SendInput", GetLastError());
7397 			}
7398 		} else version(X11) {
7399 			int delay = 0;
7400 			foreach(ch; s) {
7401 				pressKey(cast(Key) ch, true, delay);
7402 				pressKey(cast(Key) ch, false, delay);
7403 				delay += 5;
7404 			}
7405 		} else throw new NotYetImplementedException();
7406 	}
7407 
7408 	/++
7409 		Sends a fake press or release key event.
7410 
7411 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7412 
7413 		Bugs:
7414 			The `delay` parameter is not implemented yet on Windows.
7415 
7416 			Not implemented except on Windows and X11.
7417 	+/
7418 	void pressKey(Key key, bool pressed, int delay = 0) {
7419 		version(Windows) {
7420 			INPUT input;
7421 			input.type = INPUT_KEYBOARD;
7422 			input.ki.wVk = cast(ushort) key;
7423 
7424 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
7425 			input.ki.dwExtraInfo = GetMessageExtraInfo();
7426 
7427 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7428 				throw new WindowsApiException("SendInput", GetLastError());
7429 			}
7430 		} else version(X11) {
7431 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
7432 		} else throw new NotYetImplementedException();
7433 	}
7434 
7435 	/++
7436 		Sends a fake mouse button press or release event.
7437 
7438 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7439 
7440 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
7441 
7442 		Bugs:
7443 			The `delay` parameter is not implemented yet on Windows.
7444 
7445 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
7446 
7447 			All arguments will throw NotYetImplementedException on OSX Cocoa.
7448 	+/
7449 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
7450 		version(Windows) {
7451 			INPUT input;
7452 			input.type = INPUT_MOUSE;
7453 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7454 
7455 			// input.mi.mouseData for a wheel event
7456 
7457 			switch(button) {
7458 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
7459 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
7460 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
7461 				case MouseButton.wheelUp:
7462 				case MouseButton.wheelDown:
7463 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
7464 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
7465 				break;
7466 				case MouseButton.backButton: throw new NotYetImplementedException();
7467 				case MouseButton.forwardButton: throw new NotYetImplementedException();
7468 				default:
7469 			}
7470 
7471 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7472 				throw new WindowsApiException("SendInput", GetLastError());
7473 			}
7474 		} else version(X11) {
7475 			int btn;
7476 
7477 			switch(button) {
7478 				case MouseButton.left: btn = 1; break;
7479 				case MouseButton.middle: btn = 2; break;
7480 				case MouseButton.right: btn = 3; break;
7481 				case MouseButton.wheelUp: btn = 4; break;
7482 				case MouseButton.wheelDown: btn = 5; break;
7483 				case MouseButton.backButton: btn = 8; break;
7484 				case MouseButton.forwardButton: btn = 9; break;
7485 				default:
7486 			}
7487 
7488 			assert(btn);
7489 
7490 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7491 		} else throw new NotYetImplementedException();
7492 	}
7493 
7494 	///
7495 	static void moveMouseArrowBy(int dx, int dy) {
7496 		version(Windows) {
7497 			INPUT input;
7498 			input.type = INPUT_MOUSE;
7499 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7500 			input.mi.dx = dx;
7501 			input.mi.dy = dy;
7502 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7503 
7504 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7505 				throw new WindowsApiException("SendInput", GetLastError());
7506 			}
7507 		} else version(X11) {
7508 			auto disp = XDisplayConnection.get();
7509 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7510 			XFlush(disp);
7511 		} else throw new NotYetImplementedException();
7512 	}
7513 
7514 	///
7515 	static void moveMouseArrowTo(int x, int y) {
7516 		version(Windows) {
7517 			INPUT input;
7518 			input.type = INPUT_MOUSE;
7519 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7520 			input.mi.dx = x;
7521 			input.mi.dy = y;
7522 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7523 
7524 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7525 				throw new WindowsApiException("SendInput", GetLastError());
7526 			}
7527 		} else version(X11) {
7528 			auto disp = XDisplayConnection.get();
7529 			auto root = RootWindow(disp, DefaultScreen(disp));
7530 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7531 			XFlush(disp);
7532 		} else throw new NotYetImplementedException();
7533 	}
7534 }
7535 
7536 
7537 
7538 /++
7539 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7540 
7541 	See_Also:
7542 	$(LIST
7543 		*[ScreenPainter]
7544 		*[ScreenPainter.rasterOp]
7545 	)
7546 +/
7547 enum RasterOp {
7548 	normal, /// Replaces the pixel.
7549 	xor, /// Uses bitwise xor to draw.
7550 }
7551 
7552 // being phobos-free keeps the size WAY down
7553 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7554 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7555 package(arsd) const(wchar)* toWStringz(string s) {
7556 	wstring r;
7557 	foreach(dchar c; s)
7558 		r ~= c;
7559 	r ~= '\0';
7560 	return r.ptr;
7561 }
7562 private string[] split(in void[] a, char c) {
7563 		string[] ret;
7564 		size_t previous = 0;
7565 		foreach(i, char ch; cast(ubyte[]) a) {
7566 			if(ch == c) {
7567 				ret ~= cast(string) a[previous .. i];
7568 				previous = i + 1;
7569 			}
7570 		}
7571 		if(previous != a.length)
7572 			ret ~= cast(string) a[previous .. $];
7573 		return ret;
7574 	}
7575 
7576 version(without_opengl) {
7577 	enum OpenGlOptions {
7578 		no,
7579 	}
7580 } else {
7581 	/++
7582 		Determines if you want an OpenGL context created on the new window.
7583 
7584 
7585 		See more: [#topics-3d|in the 3d topic].
7586 
7587 		---
7588 		import arsd.simpledisplay;
7589 		void main() {
7590 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7591 
7592 			// Set up the matrix
7593 			window.setAsCurrentOpenGlContext(); // make this window active
7594 
7595 			// This is called on each frame, we will draw our scene
7596 			window.redrawOpenGlScene = delegate() {
7597 
7598 			};
7599 
7600 			window.eventLoop(0);
7601 		}
7602 		---
7603 	+/
7604 	enum OpenGlOptions {
7605 		no, /// No OpenGL context is created
7606 		yes, /// Yes, create an OpenGL context
7607 	}
7608 
7609 	version(X11) {
7610 		static if (!SdpyIsUsingIVGLBinds) {
7611 
7612 
7613 			struct __GLXFBConfigRec {}
7614 			alias GLXFBConfig = __GLXFBConfigRec*;
7615 
7616 			//pragma(lib, "GL");
7617 			//pragma(lib, "GLU");
7618 			interface GLX {
7619 			extern(C) nothrow @nogc {
7620 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7621 						const int *attrib_list);
7622 
7623 				 void glXCopyContext(Display *dpy, GLXContext src,
7624 						GLXContext dst, arch_ulong mask);
7625 
7626 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7627 						GLXContext share_list, Bool direct);
7628 
7629 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7630 						Pixmap pixmap);
7631 
7632 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7633 
7634 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7635 
7636 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7637 						int attrib, int *value);
7638 
7639 				 GLXContext glXGetCurrentContext();
7640 
7641 				 GLXDrawable glXGetCurrentDrawable();
7642 
7643 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7644 
7645 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7646 						GLXContext ctx);
7647 
7648 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7649 
7650 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7651 
7652 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7653 
7654 				 void glXUseXFont(Font font, int first, int count, int list_base);
7655 
7656 				 void glXWaitGL();
7657 
7658 				 void glXWaitX();
7659 
7660 
7661 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7662 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7663 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7664 
7665 				char* glXQueryExtensionsString (Display*, int);
7666 				void* glXGetProcAddress (const(char)*);
7667 
7668 			}
7669 			}
7670 
7671 			version(OSX)
7672 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7673 			else
7674 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7675 			shared static this() {
7676 				glx.loadDynamicLibrary();
7677 			}
7678 
7679 			alias glbindGetProcAddress = glXGetProcAddress;
7680 		}
7681 	} else version(Windows) {
7682 		/* it is done below by interface GL */
7683 	} else
7684 		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.");
7685 }
7686 
7687 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7688 alias Resizablity = Resizability;
7689 
7690 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7691 enum Resizability {
7692 	fixedSize, /// the window cannot be resized. If it is resized anyway, simpledisplay will position and truncate your drawn content without necessarily informing your program, maintaining the API illusion of a non-resizable window.
7693 	allowResizing, /// the window can be resized. The buffer (if there is one) will automatically adjust size, but not stretch the contents. the windowResized delegate will be called so you can respond to the new size yourself. This allows most control for both user and you as the library consumer, but you also have to do the most work to handle it well.
7694 	/++
7695 		$(PITFALL
7696 			Planned for the future but not implemented.
7697 		)
7698 
7699 		Allow the user to resize the window, but try to maintain the original aspect ratio of the client area. The simpledisplay library may letterbox your content if necessary but will not stretch it. The windowResized delegate and width and height members will be updated with the size.
7700 
7701 		History:
7702 			Added November 11, 2022, but not yet implemented and may not be for some time.
7703 	+/
7704 	/*@__future*/ allowResizingMaintainingAspectRatio,
7705 	/++
7706 		If possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size, letterboxing if needed to keep the aspect ratio. If this is impossible, it will fallback to [fixedSize]. The simpledisplay library will always provide the illusion that your window is the same size you requested, even if it scales things for you, meaning [width] and [height] will never change.
7707 
7708 		History:
7709 			Prior to November 11, 2022, width and height would change, which made this mode harder to use than intended. While I had documented this as a possiblity, I still considered it a bug, a leaky abstraction, and changed the code to tighten it up. After that date, the width and height members, as well as mouse coordinates, are always scaled to maintain the illusion of a fixed canvas size.
7710 
7711 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7712 	+/
7713 	automaticallyScaleIfPossible,
7714 }
7715 /// ditto
7716 alias Resizeability = Resizability;
7717 
7718 
7719 /++
7720 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7721 +/
7722 enum TextAlignment : uint {
7723 	Left = 0, ///
7724 	Center = 1, ///
7725 	Right = 2, ///
7726 
7727 	VerticalTop = 0, ///
7728 	VerticalCenter = 4, ///
7729 	VerticalBottom = 8, ///
7730 }
7731 
7732 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7733 alias Rectangle = arsd.color.Rectangle;
7734 
7735 
7736 /++
7737 	Keyboard press and release events.
7738 +/
7739 struct KeyEvent {
7740 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7741 	Key key;
7742 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7743 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7744 
7745 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7746 
7747 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7748 
7749 	SimpleWindow window; /// associated Window
7750 
7751 	/++
7752 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7753 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7754 		to predict if char events are actually coming..
7755 
7756 		Only available on X systems since this information is not given ahead of time elsewhere.
7757 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7758 
7759 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7760 		and potential quirks I'd recommend avoiding it.
7761 
7762 		History:
7763 			Added April 26, 2021 (dub v9.5)
7764 	+/
7765 	version(X11)
7766 		dchar[] charsPossible;
7767 
7768 	// convert key event to simplified string representation a-la emacs
7769 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7770 		uint dpos = 0;
7771 		void put (const(char)[] s...) nothrow @trusted {
7772 			static if (growdest) {
7773 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7774 			} else {
7775 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7776 			}
7777 		}
7778 
7779 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7780 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7781 		}
7782 
7783 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7784 
7785 		// put modifiers
7786 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7787 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7788 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7789 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7790 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7791 
7792 		if (this.key) {
7793 			foreach (string kn; __traits(allMembers, Key)) {
7794 				if (this.key == __traits(getMember, Key, kn)) {
7795 					// HACK!
7796 					static if (kn == "N0") put("0");
7797 					else static if (kn == "N1") put("1");
7798 					else static if (kn == "N2") put("2");
7799 					else static if (kn == "N3") put("3");
7800 					else static if (kn == "N4") put("4");
7801 					else static if (kn == "N5") put("5");
7802 					else static if (kn == "N6") put("6");
7803 					else static if (kn == "N7") put("7");
7804 					else static if (kn == "N8") put("8");
7805 					else static if (kn == "N9") put("9");
7806 					else put(kn);
7807 					return dest[0..dpos];
7808 				}
7809 			}
7810 			put("Unknown");
7811 		} else {
7812 			if (dpos && dest[dpos-1] == '+') --dpos;
7813 		}
7814 		return dest[0..dpos];
7815 	}
7816 
7817 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7818 
7819 	/** Parse string into key name with modifiers. It accepts things like:
7820 	 *
7821 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7822 	 *
7823 	 * Ctrl+Win+1 -- windows style
7824 	 *
7825 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7826 	 *
7827 	 * Ctrl Win 1 -- and space
7828 	 *
7829 	 * and even "Win + 1 + Ctrl".
7830 	 */
7831 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7832 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7833 
7834 		// remove trailing spaces
7835 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7836 
7837 		// tokens delimited by blank, '+', or '-'
7838 		// null on eol
7839 		const(char)[] getToken () nothrow @trusted @nogc {
7840 			// remove leading spaces and delimiters
7841 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7842 			if (name.length == 0) return null; // oops, no more tokens
7843 			// get token
7844 			size_t epos = 0;
7845 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7846 			assert(epos > 0 && epos <= name.length);
7847 			auto res = name[0..epos];
7848 			name = name[epos..$];
7849 			return res;
7850 		}
7851 
7852 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7853 			if (s0.length != s1.length) return false;
7854 			foreach (immutable ci, char c0; s0) {
7855 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7856 				char c1 = s1[ci];
7857 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7858 				if (c0 != c1) return false;
7859 			}
7860 			return true;
7861 		}
7862 
7863 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7864 		if (updown !is null) *updown = -1;
7865 		KeyEvent res;
7866 		res.key = cast(Key)0; // just in case
7867 		const(char)[] tk, tkn; // last token
7868 		bool allowEmascStyle = true;
7869 		bool ignoreModifiers = false;
7870 		tokenloop: for (;;) {
7871 			tk = tkn;
7872 			tkn = getToken();
7873 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7874 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7875 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7876 			if (allowEmascStyle && tkn.length != 0) {
7877 				if (tk.length == 1) {
7878 					char mdc = tk[0];
7879 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7880 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7881 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7882 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7883 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7884 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7885 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7886 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7887 				}
7888 			}
7889 			allowEmascStyle = false;
7890 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7891 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7892 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7893 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7894 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7895 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7896 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7897 			if (tk.length == 0) continue;
7898 			// try key name
7899 			if (res.key == 0) {
7900 				// little hack
7901 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7902 					final switch (tk[0]) {
7903 						case '0': tk = "N0"; break;
7904 						case '1': tk = "N1"; break;
7905 						case '2': tk = "N2"; break;
7906 						case '3': tk = "N3"; break;
7907 						case '4': tk = "N4"; break;
7908 						case '5': tk = "N5"; break;
7909 						case '6': tk = "N6"; break;
7910 						case '7': tk = "N7"; break;
7911 						case '8': tk = "N8"; break;
7912 						case '9': tk = "N9"; break;
7913 					}
7914 				}
7915 				foreach (string kn; __traits(allMembers, Key)) {
7916 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7917 				}
7918 			}
7919 			// unknown or duplicate key name, get out of here
7920 			break;
7921 		}
7922 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7923 		return res; // something
7924 	}
7925 
7926 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7927 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7928 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7929 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7930 		}
7931 		bool ignoreMods;
7932 		int updown;
7933 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7934 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7935 		if (this.key != ke.key) {
7936 			// things like "ctrl+alt" are complicated
7937 			uint tkm = this.modifierState&modmask;
7938 			uint kkm = ke.modifierState&modmask;
7939 			Key tk = this.key;
7940 			// ke
7941 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7942 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7943 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7944 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7945 			// this
7946 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7947 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7948 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7949 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7950 			return (tk == ke.key && tkm == kkm);
7951 		}
7952 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7953 	}
7954 }
7955 
7956 /// Sets the application name.
7957 @property string ApplicationName(string name) {
7958 	return _applicationName = name;
7959 }
7960 
7961 string _applicationName;
7962 
7963 /// ditto
7964 @property string ApplicationName() {
7965 	if(_applicationName is null) {
7966 		import core.runtime;
7967 		return Runtime.args[0];
7968 	}
7969 	return _applicationName;
7970 }
7971 
7972 
7973 /// Type of a [MouseEvent].
7974 enum MouseEventType : int {
7975 	motion = 0, /// The mouse moved inside the window
7976 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7977 	buttonReleased = 2, /// A mouse button was released
7978 }
7979 
7980 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7981 /++
7982 	Listen for this on your event listeners if you are interested in mouse action.
7983 
7984 	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.
7985 
7986 	Examples:
7987 
7988 	This will draw boxes on the window with the mouse as you hold the left button.
7989 	---
7990 	import arsd.simpledisplay;
7991 
7992 	void main() {
7993 		auto window = new SimpleWindow();
7994 
7995 		window.eventLoop(0,
7996 			(MouseEvent ev) {
7997 				if(ev.modifierState & ModifierState.leftButtonDown) {
7998 					auto painter = window.draw();
7999 					painter.fillColor = Color.red;
8000 					painter.outlineColor = Color.black;
8001 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
8002 				}
8003 			}
8004 		);
8005 	}
8006 	---
8007 +/
8008 struct MouseEvent {
8009 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
8010 
8011 	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.
8012 	int y; /// Current Y position of the cursor when the event fired.
8013 
8014 	int dx; /// Change in X position since last report
8015 	int dy; /// Change in Y position since last report
8016 
8017 	MouseButton button; /// See [MouseButton]
8018 	int modifierState; /// See [ModifierState]
8019 
8020 	version(X11)
8021 		private Time timestamp;
8022 
8023 	/// Returns a linear representation of mouse button,
8024 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
8025 	///
8026 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
8027 	@property ubyte buttonLinear() const {
8028 		import core.bitop;
8029 		if(button == 0)
8030 			return 0;
8031 		return (bsf(button) + 1) & 0b1111;
8032 	}
8033 
8034 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
8035 
8036 	SimpleWindow window; /// The window in which the event happened.
8037 
8038 	Point globalCoordinates() {
8039 		Point p;
8040 		if(window is null)
8041 			throw new Exception("wtf");
8042 		static if(UsingSimpledisplayX11) {
8043 			Window child;
8044 			XTranslateCoordinates(
8045 				XDisplayConnection.get,
8046 				window.impl.window,
8047 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
8048 				x, y, &p.x, &p.y, &child);
8049 			return p;
8050 		} else version(Windows) {
8051 			POINT[1] points;
8052 			points[0].x = x;
8053 			points[0].y = y;
8054 			MapWindowPoints(
8055 				window.impl.hwnd,
8056 				null,
8057 				points.ptr,
8058 				points.length
8059 			);
8060 			p.x = points[0].x;
8061 			p.y = points[0].y;
8062 
8063 			return p;
8064 		} else version(OSXCocoa) {
8065 			throw new NotYetImplementedException();
8066 		} else version(Emscripten) {
8067 			throw new NotYetImplementedException();
8068 		} else static assert(0);
8069 	}
8070 
8071 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
8072 
8073 	/**
8074 	can contain emacs-like modifier prefix
8075 	case-insensitive names:
8076 		lmbX/leftX
8077 		rmbX/rightX
8078 		mmbX/middleX
8079 		wheelX
8080 		motion (no prefix allowed)
8081 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
8082 	*/
8083 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
8084 		if (str.length == 0) return false; // just in case
8085 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
8086 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
8087 		auto anchor = str;
8088 		uint mods = 0; // uint.max == any
8089 		// interesting bits in kmod
8090 		uint kmodmask =
8091 			ModifierState.shift|
8092 			ModifierState.ctrl|
8093 			ModifierState.alt|
8094 			ModifierState.windows|
8095 			ModifierState.leftButtonDown|
8096 			ModifierState.middleButtonDown|
8097 			ModifierState.rightButtonDown|
8098 			0;
8099 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
8100 		bool wasButtons = false;
8101 		while (str.length) {
8102 			if (str.ptr[0] <= ' ') {
8103 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
8104 				continue;
8105 			}
8106 			// one-letter modifier?
8107 			if (str.length >= 2 && str.ptr[1] == '-') {
8108 				switch (str.ptr[0]) {
8109 					case '*': // "any" modifier (cannot be undone)
8110 						mods = mods.max;
8111 						break;
8112 					case 'C': case 'c': // emacs "ctrl"
8113 						if (mods != mods.max) mods |= ModifierState.ctrl;
8114 						break;
8115 					case 'M': case 'm': // emacs "meta"
8116 						if (mods != mods.max) mods |= ModifierState.alt;
8117 						break;
8118 					case 'S': case 's': // emacs "shift"
8119 						if (mods != mods.max) mods |= ModifierState.shift;
8120 						break;
8121 					case 'H': case 'h': // emacs "hyper" (aka winkey)
8122 						if (mods != mods.max) mods |= ModifierState.windows;
8123 						break;
8124 					default:
8125 						return false; // unknown modifier
8126 				}
8127 				str = str[2..$];
8128 				continue;
8129 			}
8130 			// word
8131 			char[16] buf = void; // locased
8132 			auto wep = 0;
8133 			while (str.length) {
8134 				immutable char ch = str.ptr[0];
8135 				if (ch <= ' ' || ch == '-') break;
8136 				str = str[1..$];
8137 				if (wep > buf.length) return false; // too long
8138 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8139 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8140 				else return false; // invalid char
8141 			}
8142 			if (wep == 0) return false; // just in case
8143 			uint bnum;
8144 			enum UpDown { None = -1, Up, Down, Any }
8145 			auto updown = UpDown.None; // 0: up; 1: down
8146 			switch (buf[0..wep]) {
8147 				// left button
8148 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
8149 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
8150 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
8151 				case "lmb": case "left": bnum = 0; break;
8152 				// middle button
8153 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
8154 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
8155 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
8156 				case "mmb": case "middle": bnum = 1; break;
8157 				// right button
8158 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
8159 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
8160 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
8161 				case "rmb": case "right": bnum = 2; break;
8162 				// wheel
8163 				case "wheelup": updown = UpDown.Up; goto case "wheel";
8164 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
8165 				case "wheelany": updown = UpDown.Any; goto case "wheel";
8166 				case "wheel": bnum = 3; break;
8167 				// motion
8168 				case "motion": bnum = 7; break;
8169 				// unknown
8170 				default: return false;
8171 			}
8172 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8173 			// parse possible "-up" or "-down"
8174 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
8175 				wep = 0;
8176 				foreach (immutable idx, immutable char ch; str[1..$]) {
8177 					if (ch <= ' ' || ch == '-') break;
8178 					assert(idx == wep); // for now; trick
8179 					if (wep > buf.length) { wep = 0; break; } // too long
8180 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8181 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8182 					else { wep = 0; break; } // invalid char
8183 				}
8184 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
8185 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
8186 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
8187 				// remove parsed part
8188 				if (updown != UpDown.None) str = str[wep+1..$];
8189 			}
8190 			if (updown == UpDown.None) {
8191 				updown = UpDown.Down;
8192 			}
8193 			wasButtons = wasButtons || (bnum <= 2);
8194 			//assert(updown != UpDown.None);
8195 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8196 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
8197 			if (lastButt != lastButt.max) {
8198 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
8199 				if (mods != mods.max) {
8200 					uint butbit = 0;
8201 					final switch (lastButt&0x03) {
8202 						case 0: butbit = ModifierState.leftButtonDown; break;
8203 						case 1: butbit = ModifierState.middleButtonDown; break;
8204 						case 2: butbit = ModifierState.rightButtonDown; break;
8205 					}
8206 					     if (lastButt&Flag.Down) mods |= butbit;
8207 					else if (lastButt&Flag.Up) mods &= ~butbit;
8208 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
8209 				}
8210 			}
8211 			// remember last button
8212 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
8213 		}
8214 		// no button -- nothing to do
8215 		if (lastButt == lastButt.max) return false;
8216 		// done parsing, check if something's left
8217 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
8218 		// remove action button from mask
8219 		if ((lastButt&0xff) < 3) {
8220 			final switch (lastButt&0x03) {
8221 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
8222 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
8223 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
8224 			}
8225 		}
8226 		// special case: "Motion" means "ignore buttons"
8227 		if ((lastButt&0xff) == 7 && !wasButtons) {
8228 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
8229 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
8230 		}
8231 		uint kmod = event.modifierState&kmodmask;
8232 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
8233 		// check modifier state
8234 		if (mods != mods.max) {
8235 			if (kmod != mods) return false;
8236 		}
8237 		// now check type
8238 		if ((lastButt&0xff) == 7) {
8239 			// motion
8240 			if (event.type != MouseEventType.motion) return false;
8241 		} else if ((lastButt&0xff) == 3) {
8242 			// wheel
8243 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
8244 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
8245 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
8246 			return false;
8247 		} else {
8248 			// buttons
8249 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
8250 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
8251 			{
8252 				return false;
8253 			}
8254 			// button number
8255 			switch (lastButt&0x03) {
8256 				case 0: if (event.button != MouseButton.left) return false; break;
8257 				case 1: if (event.button != MouseButton.middle) return false; break;
8258 				case 2: if (event.button != MouseButton.right) return false; break;
8259 				default: return false;
8260 			}
8261 		}
8262 		return true;
8263 	}
8264 }
8265 
8266 version(arsd_mevent_strcmp_test) unittest {
8267 	MouseEvent event;
8268 	event.type = MouseEventType.buttonPressed;
8269 	event.button = MouseButton.left;
8270 	event.modifierState = ModifierState.ctrl;
8271 	assert(event == "C-LMB");
8272 	assert(event != "C-LMBUP");
8273 	assert(event != "C-LMB-UP");
8274 	assert(event != "C-S-LMB");
8275 	assert(event == "*-LMB");
8276 	assert(event != "*-LMB-UP");
8277 
8278 	event.type = MouseEventType.buttonReleased;
8279 	assert(event != "C-LMB");
8280 	assert(event == "C-LMBUP");
8281 	assert(event == "C-LMB-UP");
8282 	assert(event != "C-S-LMB");
8283 	assert(event != "*-LMB");
8284 	assert(event == "*-LMB-UP");
8285 
8286 	event.button = MouseButton.right;
8287 	event.modifierState |= ModifierState.shift;
8288 	event.type = MouseEventType.buttonPressed;
8289 	assert(event != "C-LMB");
8290 	assert(event != "C-LMBUP");
8291 	assert(event != "C-LMB-UP");
8292 	assert(event != "C-S-LMB");
8293 	assert(event != "*-LMB");
8294 	assert(event != "*-LMB-UP");
8295 
8296 	assert(event != "C-RMB");
8297 	assert(event != "C-RMBUP");
8298 	assert(event != "C-RMB-UP");
8299 	assert(event == "C-S-RMB");
8300 	assert(event == "*-RMB");
8301 	assert(event != "*-RMB-UP");
8302 }
8303 
8304 /// This gives a few more options to drawing lines and such
8305 struct Pen {
8306 	Color color; /// the foreground color
8307 	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.
8308 	Style style; /// See [Style]
8309 /+
8310 // From X.h
8311 
8312 #define LineSolid		0
8313 #define LineOnOffDash		1
8314 #define LineDoubleDash		2
8315        LineDou-        The full path of the line is drawn, but the
8316        bleDash         even dashes are filled differently from the
8317                        odd dashes (see fill-style) with CapButt
8318                        style used where even and odd dashes meet.
8319 
8320 
8321 
8322 /* capStyle */
8323 
8324 #define CapNotLast		0
8325 #define CapButt			1
8326 #define CapRound		2
8327 #define CapProjecting		3
8328 
8329 /* joinStyle */
8330 
8331 #define JoinMiter		0
8332 #define JoinRound		1
8333 #define JoinBevel		2
8334 
8335 /* fillStyle */
8336 
8337 #define FillSolid		0
8338 #define FillTiled		1
8339 #define FillStippled		2
8340 #define FillOpaqueStippled	3
8341 
8342 
8343 +/
8344 	/// Style of lines drawn
8345 	enum Style {
8346 		Solid, /// a solid line
8347 		Dashed, /// a dashed line
8348 		Dotted, /// a dotted line
8349 	}
8350 }
8351 
8352 
8353 /++
8354 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
8355 
8356 
8357 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
8358 
8359 	$(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.)
8360 
8361 	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.
8362 
8363 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
8364 
8365 	$(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.
8366 
8367 	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!
8368 
8369 	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!)
8370 
8371 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
8372 
8373 	---
8374 		auto image = new Image(256, 256);
8375 		scope(exit) destroy(image);
8376 	---
8377 
8378 	As long as you don't hold on to it outside the scope.
8379 
8380 	I might change it to be an owned pointer at some point in the future.
8381 
8382 	)
8383 
8384 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
8385 	you can also often get a fair amount of speedup by getting the raw data format and
8386 	writing some custom code.
8387 
8388 	FIXME INSERT EXAMPLES HERE
8389 
8390 
8391 +/
8392 final class Image {
8393 	///
8394 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
8395 		this.width = width;
8396 		this.height = height;
8397 		this.enableAlpha = enableAlpha;
8398 
8399 		impl.createImage(width, height, forcexshm, enableAlpha);
8400 	}
8401 
8402 	///
8403 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
8404 		this(size.width, size.height, forcexshm, enableAlpha);
8405 	}
8406 
8407 	private bool suppressDestruction;
8408 
8409 	version(X11)
8410 	this(XImage* handle) {
8411 		this.handle = handle;
8412 		this.rawData = cast(ubyte*) handle.data;
8413 		this.width = handle.width;
8414 		this.height = handle.height;
8415 		this.enableAlpha = handle.depth == 32;
8416 		suppressDestruction = true;
8417 	}
8418 
8419 	~this() {
8420 		if(suppressDestruction) return;
8421 		impl.dispose();
8422 	}
8423 
8424 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
8425 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
8426 	pure const @system nothrow {
8427 		/*
8428 			To use these to draw a blue rectangle with size WxH at position X,Y...
8429 
8430 			// make certain that it will fit before we proceed
8431 			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!
8432 
8433 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
8434 			// (though calculating them isn't really that expensive).
8435 			auto nextLineAdjustment = img.adjustmentForNextLine();
8436 			auto offR = img.redByteOffset();
8437 			auto offB = img.blueByteOffset();
8438 			auto offG = img.greenByteOffset();
8439 			auto bpp = img.bytesPerPixel();
8440 
8441 			auto data = img.getDataPointer();
8442 
8443 			// figure out the starting byte offset
8444 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
8445 
8446 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
8447 
8448 			// and now our drawing loop for the rectangle
8449 			foreach(y; 0 .. H) {
8450 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
8451 				foreach(x; 0 .. W) {
8452 					// write our color
8453 					data[offR] = 0;
8454 					data[offG] = 0;
8455 					data[offB] = 255;
8456 
8457 					data += bpp; // moving to the next pixel is just an addition...
8458 				}
8459 				startOfLine += nextLineAdjustment;
8460 			}
8461 
8462 
8463 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
8464 
8465 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
8466 			can be made into a bitmask or something so we can write them as *uint...
8467 		*/
8468 
8469 		///
8470 		int offsetForTopLeftPixel() {
8471 			version(X11) {
8472 				return 0;
8473 			} else version(Windows) {
8474 				if(enableAlpha) {
8475 					return (width * 4) * (height - 1);
8476 				} else {
8477 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
8478 				}
8479 			} else version(OSXCocoa) {
8480 				return 0 ; //throw new NotYetImplementedException();
8481 			} else version(Emscripten) {
8482 				return 0;
8483 			} else static assert(0, "fill in this info for other OSes");
8484 		}
8485 
8486 		///
8487 		int offsetForPixel(int x, int y) {
8488 			version(X11) {
8489 				auto offset = (y * width + x) * 4;
8490 				return offset;
8491 			} else version(Windows) {
8492 				if(enableAlpha) {
8493 					auto itemsPerLine = width * 4;
8494 					// remember, bmps are upside down
8495 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8496 					return offset;
8497 				} else {
8498 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8499 					// remember, bmps are upside down
8500 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8501 					return offset;
8502 				}
8503 			} else version(OSXCocoa) {
8504 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8505 			} else version(Emscripten) {
8506 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8507 			} else static assert(0, "fill in this info for other OSes");
8508 		}
8509 
8510 		///
8511 		int adjustmentForNextLine() {
8512 			version(X11) {
8513 				return width * 4;
8514 			} else version(Windows) {
8515 				// windows bmps are upside down, so the adjustment is actually negative
8516 				if(enableAlpha)
8517 					return - (cast(int) width * 4);
8518 				else
8519 					return -((cast(int) width * 3 + 3) / 4) * 4;
8520 			} else version(OSXCocoa) {
8521 				return width * 4 ; //throw new NotYetImplementedException();
8522 			} else version(Emscripten) {
8523 				return width * 4 ; //throw new NotYetImplementedException();
8524 			} else static assert(0, "fill in this info for other OSes");
8525 		}
8526 
8527 		/// once you have the position of a pixel, use these to get to the proper color
8528 		int redByteOffset() {
8529 			version(X11) {
8530 				return 2;
8531 			} else version(Windows) {
8532 				return 2;
8533 			} else version(OSXCocoa) {
8534 				return 2 ; //throw new NotYetImplementedException();
8535 			} else version(Emscripten) {
8536 				return 2 ; //throw new NotYetImplementedException();
8537 			} else static assert(0, "fill in this info for other OSes");
8538 		}
8539 
8540 		///
8541 		int greenByteOffset() {
8542 			version(X11) {
8543 				return 1;
8544 			} else version(Windows) {
8545 				return 1;
8546 			} else version(OSXCocoa) {
8547 				return 1 ; //throw new NotYetImplementedException();
8548 			} else version(Emscripten) {
8549 				return 1 ; //throw new NotYetImplementedException();
8550 			} else static assert(0, "fill in this info for other OSes");
8551 		}
8552 
8553 		///
8554 		int blueByteOffset() {
8555 			version(X11) {
8556 				return 0;
8557 			} else version(Windows) {
8558 				return 0;
8559 			} else version(OSXCocoa) {
8560 				return 0 ; //throw new NotYetImplementedException();
8561 			} else version(Emscripten) {
8562 				return 0 ; //throw new NotYetImplementedException();
8563 			} else static assert(0, "fill in this info for other OSes");
8564 		}
8565 
8566 		/// Only valid if [enableAlpha] is true
8567 		int alphaByteOffset() {
8568 			version(X11) {
8569 				return 3;
8570 			} else version(Windows) {
8571 				return 3;
8572 			} else version(OSXCocoa) {
8573 				return 3; //throw new NotYetImplementedException();
8574 			} else version(Emscripten) {
8575 				return 3 ; //throw new NotYetImplementedException();
8576 			} else static assert(0, "fill in this info for other OSes");
8577 		}
8578 	}
8579 
8580 	///
8581 	final void putPixel(int x, int y, Color c) {
8582 		if(x < 0 || x >= width)
8583 			return;
8584 		if(y < 0 || y >= height)
8585 			return;
8586 
8587 		impl.setPixel(x, y, c);
8588 	}
8589 
8590 	///
8591 	final Color getPixel(int x, int y) {
8592 		if(x < 0 || x >= width)
8593 			return Color.transparent;
8594 		if(y < 0 || y >= height)
8595 			return Color.transparent;
8596 
8597 		version(OSXCocoa) throw new NotYetImplementedException(); else
8598 		return impl.getPixel(x, y);
8599 	}
8600 
8601 	///
8602 	final void opIndexAssign(Color c, int x, int y) {
8603 		putPixel(x, y, c);
8604 	}
8605 
8606 	///
8607 	TrueColorImage toTrueColorImage() {
8608 		auto tci = new TrueColorImage(width, height);
8609 		convertToRgbaBytes(tci.imageData.bytes);
8610 		return tci;
8611 	}
8612 
8613 	///
8614 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8615 		auto tci = i.getAsTrueColorImage();
8616 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8617 		static if(UsingSimpledisplayX11)
8618 			img.premultiply = premultiply;
8619 		img.setRgbaBytes(tci.imageData.bytes);
8620 		return img;
8621 	}
8622 
8623 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8624 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8625 	/// if you pass null, it will allocate a new one.
8626 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8627 		if(where is null)
8628 			where = new ubyte[this.width*this.height*4];
8629 		convertToRgbaBytes(where);
8630 		return where;
8631 	}
8632 
8633 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8634 	void setRgbaBytes(in ubyte[] from ) {
8635 		assert(from.length == this.width * this.height * 4);
8636 		setFromRgbaBytes(from);
8637 	}
8638 
8639 	// FIXME: make properly cross platform by getting rgba right
8640 
8641 	/// warning: this is not portable across platforms because the data format can change
8642 	ubyte* getDataPointer() {
8643 		return impl.rawData;
8644 	}
8645 
8646 	/// for use with getDataPointer
8647 	final int bytesPerLine() const pure @safe nothrow {
8648 		version(Windows)
8649 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8650 		else version(X11)
8651 			return 4 * width;
8652 		else version(OSXCocoa)
8653 			return 4 * width;
8654 		else static assert(0);
8655 	}
8656 
8657 	/// for use with getDataPointer
8658 	final int bytesPerPixel() const pure @safe nothrow {
8659 		version(Windows)
8660 			return enableAlpha ? 4 : 3;
8661 		else version(X11)
8662 			return 4;
8663 		else version(OSXCocoa)
8664 			return 4;
8665 		else static assert(0);
8666 	}
8667 
8668 	///
8669 	immutable int width;
8670 
8671 	///
8672 	immutable int height;
8673 
8674 	///
8675 	immutable bool enableAlpha;
8676     //private:
8677 	mixin NativeImageImplementation!() impl;
8678 }
8679 
8680 /++
8681 	A convenience function to pop up a window displaying the image.
8682 	If you pass a win, it will draw the image in it. Otherwise, it will
8683 	create a window with the size of the image and run its event loop, closing
8684 	when a key is pressed.
8685 
8686 	History:
8687 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8688 		always block until the application quit which could cause bizarre behavior
8689 		inside a more complex application. Now, the default is to block until
8690 		this window closes if it is the only event loop running, and otherwise,
8691 		not to block at all and just pop up the display window asynchronously.
8692 +/
8693 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8694 	if(win is null) {
8695 		win = new SimpleWindow(image);
8696 		{
8697 			auto p = win.draw;
8698 			p.drawImage(Point(0, 0), image);
8699 		}
8700 		win.eventLoopWithBlockingMode(
8701 			bm, 0,
8702 			(KeyEvent ev) {
8703 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8704 			} );
8705 	} else {
8706 		win.image = image;
8707 	}
8708 }
8709 
8710 enum FontWeight : int {
8711 	dontcare = 0,
8712 	thin = 100,
8713 	extralight = 200,
8714 	light = 300,
8715 	regular = 400,
8716 	medium = 500,
8717 	semibold = 600,
8718 	bold = 700,
8719 	extrabold = 800,
8720 	heavy = 900
8721 }
8722 
8723 /++
8724 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8725 
8726 	History:
8727 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8728 +/
8729 interface MeasurableFont {
8730 	/++
8731 		Returns true if it is a monospace font, meaning each of the
8732 		glyphs (at least the ascii characters) have matching width
8733 		and no kerning, so you can determine the display width of some
8734 		strings by simply multiplying the string width by [averageWidth].
8735 
8736 		(Please note that multiply doesn't $(I actually) work in general,
8737 		consider characters like tab and newline, but it does sometimes.)
8738 	+/
8739 	bool isMonospace();
8740 
8741 	/++
8742 		The average width of glyphs in the font, traditionally equal to the
8743 		width of the lowercase x. Can be used to estimate bounding boxes,
8744 		especially if the font [isMonospace].
8745 
8746 		Given in pixels.
8747 	+/
8748 	int averageWidth();
8749 	/++
8750 		The height of the bounding box of a line.
8751 	+/
8752 	int height();
8753 	/++
8754 		The maximum ascent of a glyph above the baseline.
8755 
8756 		Given in pixels.
8757 	+/
8758 	int ascent();
8759 	/++
8760 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8761 
8762 		Given in pixels.
8763 	+/
8764 	int descent();
8765 	/++
8766 		The display width of the given string, and if you provide a window, it will use it to
8767 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8768 
8769 		Given in pixels.
8770 	+/
8771 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8772 
8773 }
8774 
8775 // FIXME: i need a font cache and it needs to handle disconnects.
8776 
8777 /++
8778 	Represents a font loaded off the operating system or the X server.
8779 
8780 
8781 	While the api here is unified cross platform, the fonts are not necessarily
8782 	available, even across machines of the same platform, so be sure to always check
8783 	for null (using [isNull]) and have a fallback plan.
8784 
8785 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8786 
8787 	Worst case, a null font will automatically fall back to the default font loaded
8788 	for your system.
8789 +/
8790 class OperatingSystemFont : MeasurableFont {
8791 	// FIXME: when the X Connection is lost, these need to be invalidated!
8792 	// that means I need to store the original stuff again to reconstruct it too.
8793 
8794 	version(Emscripten) {
8795 		void* font;
8796 	} else version(X11) {
8797 		XFontStruct* font;
8798 		XFontSet fontset;
8799 
8800 		version(with_xft) {
8801 			XftFont* xftFont;
8802 			bool isXft;
8803 		}
8804 	} else version(Windows) {
8805 		HFONT font;
8806 		int width_;
8807 		int height_;
8808 	} else version(OSXCocoa) {
8809 		NSFont font;
8810 	} else static assert(0);
8811 
8812 	/++
8813 		Constructs the class and immediately calls [load].
8814 	+/
8815 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8816 		load(name, size, weight, italic);
8817 	}
8818 
8819 	/++
8820 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8821 
8822 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8823 
8824 		History:
8825 			Added January 24, 2021.
8826 	+/
8827 	this() {
8828 		// this space intentionally left blank
8829 	}
8830 
8831 	/++
8832 		Constructs a copy of the given font object.
8833 
8834 		History:
8835 			Added January 7, 2023.
8836 	+/
8837 	this(OperatingSystemFont font) {
8838 		if(font is null || font.loadedInfo is LoadedInfo.init)
8839 			loadDefault();
8840 		else
8841 			load(font.loadedInfo.tupleof);
8842 	}
8843 
8844 	/++
8845 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8846 
8847 		History:
8848 			Added November 13, 2020.
8849 	+/
8850 	version(with_xft)
8851 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8852 		unload();
8853 
8854 		if(!XftLibrary.attempted) {
8855 			XftLibrary.loadDynamicLibrary();
8856 		}
8857 
8858 		if(!XftLibrary.loadSuccessful)
8859 			return false;
8860 
8861 		auto display = XDisplayConnection.get;
8862 
8863 		char[256] nameBuffer = void;
8864 		int nbp = 0;
8865 
8866 		void add(in char[] a) {
8867 			nameBuffer[nbp .. nbp + a.length] = a[];
8868 			nbp += a.length;
8869 		}
8870 		add(name);
8871 
8872 		if(size) {
8873 			add(":size=");
8874 			add(toInternal!string(size));
8875 		}
8876 		if(weight != FontWeight.dontcare && weight != 400) {
8877 			if(weight < 400)
8878 				add(":style=Light");
8879 			else
8880 				add(":style=Bold");
8881 			add(":weight=");
8882 			add(weightToString(weight));
8883 		}
8884 		if(italic) {
8885 			if(weight == FontWeight.dontcare)
8886 				add(":style=Italic");
8887 			add(":slant=100");
8888 		}
8889 
8890 		nameBuffer[nbp] = 0;
8891 
8892 		this.xftFont = XftFontOpenName(
8893 			display,
8894 			DefaultScreen(display),
8895 			nameBuffer.ptr
8896 		);
8897 
8898 		this.isXft = true;
8899 
8900 		if(xftFont !is null) {
8901 			isMonospace_ = stringWidth("x") == stringWidth("M");
8902 			ascent_ = xftFont.ascent;
8903 			descent_ = xftFont.descent;
8904 		}
8905 
8906 		return !isNull();
8907 	}
8908 
8909 	/++
8910 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8911 
8912 
8913 		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.
8914 
8915 		If `pattern` is null, it returns all available font families.
8916 
8917 		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.
8918 
8919 		The format of the pattern is platform-specific.
8920 
8921 		History:
8922 			Added May 1, 2021 (dub v9.5)
8923 	+/
8924 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8925 		version(Windows) {
8926 			auto hdc = GetDC(null);
8927 			scope(exit) ReleaseDC(null, hdc);
8928 			LOGFONT logfont;
8929 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8930 				auto localHandler = *(cast(typeof(handler)*) p);
8931 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8932 			}
8933 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8934 		} else version(X11) {
8935 			//import core.stdc.stdio;
8936 			bool done = false;
8937 			version(with_xft) {
8938 				if(!XftLibrary.attempted) {
8939 					XftLibrary.loadDynamicLibrary();
8940 				}
8941 
8942 				if(!XftLibrary.loadSuccessful)
8943 					goto skipXft;
8944 
8945 				if(!FontConfigLibrary.attempted)
8946 					FontConfigLibrary.loadDynamicLibrary();
8947 				if(!FontConfigLibrary.loadSuccessful)
8948 					goto skipXft;
8949 
8950 				{
8951 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8952 					if(got is null)
8953 						goto skipXft;
8954 					scope(exit) FcFontSetDestroy(got);
8955 
8956 					auto fontPatterns = got.fonts[0 .. got.nfont];
8957 					foreach(candidate; fontPatterns) {
8958 						char* where, whereStyle;
8959 
8960 						char* pmg = FcNameUnparse(candidate);
8961 
8962 						//FcPatternGetString(candidate, "family", 0, &where);
8963 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8964 						//if(where && whereStyle) {
8965 						if(pmg) {
8966 							if(!handler(pmg.sliceCString))
8967 								return;
8968 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8969 						}
8970 					}
8971 				}
8972 			}
8973 
8974 			skipXft:
8975 
8976 			if(pattern is null)
8977 				pattern = "*";
8978 
8979 			int count;
8980 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8981 			scope(exit) XFreeFontNames(coreFontsRaw);
8982 
8983 			auto coreFonts = coreFontsRaw[0 .. count];
8984 
8985 			foreach(font; coreFonts) {
8986 				char[128] tmp;
8987 				tmp[0 ..5] = "core:";
8988 				auto cf = font.sliceCString;
8989 				if(5 + cf.length > tmp.length)
8990 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8991 				tmp[5 .. 5 + cf.length] = cf;
8992 				if(!handler(tmp[0 .. 5 + cf.length]))
8993 					return;
8994 			}
8995 		}
8996 	}
8997 
8998 	/++
8999 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
9000 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
9001 
9002 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
9003 		underlying system doesn't support returning the raw bytes.
9004 
9005 		History:
9006 			Added September 10, 2021 (dub v10.3)
9007 	+/
9008 	ubyte[] getTtfBytes() {
9009 		if(isNull)
9010 			return null;
9011 
9012 		version(Windows) {
9013 			auto dc = GetDC(null);
9014 			auto orig = SelectObject(dc, font);
9015 
9016 			scope(exit) {
9017 				SelectObject(dc, orig);
9018 				ReleaseDC(null, dc);
9019 			}
9020 
9021 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
9022 			if(res == GDI_ERROR)
9023 				return null;
9024 
9025 			ubyte[] buffer = new ubyte[](res);
9026 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
9027 			if(res == GDI_ERROR)
9028 				return null; // wtf really tbh
9029 
9030 			return buffer;
9031 		} else version(with_xft) {
9032 			if(isXft && xftFont) {
9033 				if(!FontConfigLibrary.attempted)
9034 					FontConfigLibrary.loadDynamicLibrary();
9035 				if(!FontConfigLibrary.loadSuccessful)
9036 					return null;
9037 
9038 				char* file;
9039 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
9040 					if (file !is null && file[0]) {
9041 						import core.stdc.stdio;
9042 						auto fp = fopen(file, "rb");
9043 						if(fp is null)
9044 							return null;
9045 						scope(exit)
9046 							fclose(fp);
9047 						fseek(fp, 0, SEEK_END);
9048 						ubyte[] buffer = new ubyte[](ftell(fp));
9049 						fseek(fp, 0, SEEK_SET);
9050 
9051 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
9052 						if(got != buffer.length)
9053 							return null;
9054 
9055 						return buffer;
9056 					}
9057 				}
9058 			}
9059 			return null;
9060 		} else throw new NotYetImplementedException();
9061 	}
9062 
9063 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
9064 
9065 	private string weightToString(FontWeight weight) {
9066 		with(FontWeight)
9067 		final switch(weight) {
9068 			case dontcare: return "*";
9069 			case thin: return "extralight";
9070 			case extralight: return "extralight";
9071 			case light: return "light";
9072 			case regular: return "regular";
9073 			case medium: return "medium";
9074 			case semibold: return "demibold";
9075 			case bold: return "bold";
9076 			case extrabold: return "demibold";
9077 			case heavy: return "black";
9078 		}
9079 	}
9080 
9081 	/++
9082 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
9083 
9084 		History:
9085 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9086 	+/
9087 	version(X11)
9088 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9089 		unload();
9090 
9091 		string xfontstr;
9092 
9093 		if(name.length > 3 && name[0 .. 3] == "-*-") {
9094 			// this is kinda a disgusting hack but if the user sends an exact
9095 			// string I'd like to honor it...
9096 			xfontstr = name;
9097 		} else {
9098 			string weightstr = weightToString(weight);
9099 			string sizestr;
9100 			if(size == 0)
9101 				sizestr = "*";
9102 			else
9103 				sizestr = toInternal!string(size);
9104 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
9105 		}
9106 
9107 		// writeln(xfontstr);
9108 
9109 		auto display = XDisplayConnection.get;
9110 
9111 		font = XLoadQueryFont(display, xfontstr.ptr);
9112 		if(font is null)
9113 			return false;
9114 
9115 		char** lol;
9116 		int lol2;
9117 		char* lol3;
9118 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
9119 
9120 		prepareFontInfo();
9121 
9122 		return !isNull();
9123 	}
9124 
9125 	version(X11)
9126 	private void prepareFontInfo() {
9127 		if(font !is null) {
9128 			isMonospace_ = stringWidth("l") == stringWidth("M");
9129 			ascent_ = font.max_bounds.ascent;
9130 			descent_ = font.max_bounds.descent;
9131 		}
9132 	}
9133 
9134 	version(OSXCocoa)
9135 	private void prepareFontInfo() {
9136 		if(font !is null) {
9137 			isMonospace_ = font.isFixedPitch;
9138 			ascent_ = cast(int) font.ascender;
9139 			descent_ = cast(int) - font.descender;
9140 		}
9141 	}
9142 
9143 
9144 	/++
9145 		Loads a Windows font. You probably want to use [load] instead to be more generic.
9146 
9147 		History:
9148 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9149 	+/
9150 	version(Windows)
9151 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
9152 		unload();
9153 
9154 		WCharzBuffer buffer = WCharzBuffer(name);
9155 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
9156 
9157 		prepareFontInfo(hdc);
9158 
9159 		return !isNull();
9160 	}
9161 
9162 	version(Windows)
9163 	void prepareFontInfo(HDC hdc = null) {
9164 		if(font is null)
9165 			return;
9166 
9167 		TEXTMETRIC tm;
9168 		auto dc = hdc ? hdc : GetDC(null);
9169 		auto orig = SelectObject(dc, font);
9170 		GetTextMetrics(dc, &tm);
9171 		SelectObject(dc, orig);
9172 		if(hdc is null)
9173 			ReleaseDC(null, dc);
9174 
9175 		width_ = tm.tmAveCharWidth;
9176 		height_ = tm.tmHeight;
9177 		ascent_ = tm.tmAscent;
9178 		descent_ = tm.tmDescent;
9179 		// 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.
9180 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
9181 	}
9182 
9183 
9184 	/++
9185 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
9186 
9187 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
9188 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
9189 
9190 		On Windows, it forwards directly to [loadWin32].
9191 
9192 		Params:
9193 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
9194 			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.
9195 			weight = approximate boldness, results may vary.
9196 			italic = try to get a slanted version of the given font.
9197 
9198 		History:
9199 			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.
9200 	+/
9201 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9202 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
9203 		version(X11) {
9204 			version(with_xft) {
9205 				if(name.length > 5 && name[0 .. 5] == "core:") {
9206 					goto core;
9207 				}
9208 
9209 				if(loadXft(name, size, weight, italic))
9210 					return true;
9211 				// if xft fails, fallback to core to avoid breaking
9212 				// code that already depended on this.
9213 			}
9214 
9215 			core:
9216 
9217 			if(name.length > 5 && name[0 .. 5] == "core:") {
9218 				name = name[5 .. $];
9219 			}
9220 
9221 			return loadCoreX(name, size, weight, italic);
9222 		} else version(Windows) {
9223 			return loadWin32(name, size, weight, italic);
9224 		} else version(OSXCocoa) {
9225 			return loadCocoa(name, size, weight, italic);
9226 		} else static assert(0);
9227 	}
9228 
9229 	version(OSXCocoa)
9230 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
9231 		unload();
9232 
9233 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
9234 		prepareFontInfo();
9235 
9236 		return !isNull();
9237 	}
9238 
9239 	private struct LoadedInfo {
9240 		string name;
9241 		int size;
9242 		FontWeight weight;
9243 		bool italic;
9244 	}
9245 	private LoadedInfo loadedInfo;
9246 
9247 	///
9248 	void unload() {
9249 		if(isNull())
9250 			return;
9251 
9252 		version(X11) {
9253 			auto display = XDisplayConnection.display;
9254 
9255 			if(display is null)
9256 				return;
9257 
9258 			version(with_xft) {
9259 				if(isXft) {
9260 					if(xftFont)
9261 						XftFontClose(display, xftFont);
9262 					isXft = false;
9263 					xftFont = null;
9264 					return;
9265 				}
9266 			}
9267 
9268 			if(font && font !is ScreenPainterImplementation.defaultfont)
9269 				XFreeFont(display, font);
9270 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
9271 				XFreeFontSet(display, fontset);
9272 
9273 			font = null;
9274 			fontset = null;
9275 		} else version(Windows) {
9276 			DeleteObject(font);
9277 			font = null;
9278 		} else version(OSXCocoa) {
9279 			font.release();
9280 			font = null;
9281 		} else static assert(0);
9282 	}
9283 
9284 	private bool isMonospace_;
9285 
9286 	/++
9287 		History:
9288 			Added January 16, 2021
9289 	+/
9290 	bool isMonospace() {
9291 		return isMonospace_;
9292 	}
9293 
9294 	/++
9295 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
9296 
9297 		History:
9298 			Added March 26, 2020
9299 			Documented January 16, 2021
9300 	+/
9301 	int averageWidth() {
9302 		version(X11) {
9303 			return stringWidth("x");
9304 		} version(OSXCocoa) {
9305 			return stringWidth("x");
9306 		} else version(Windows)
9307 			return width_;
9308 		else assert(0);
9309 	}
9310 
9311 	/++
9312 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
9313 
9314 		History:
9315 			Added January 16, 2021
9316 	+/
9317 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
9318 	// FIXME: what about tab?
9319 		if(isNull)
9320 			return 0;
9321 
9322 		version(X11) {
9323 			version(with_xft)
9324 				if(isXft && xftFont !is null) {
9325 					//return xftFont.max_advance_width;
9326 					XGlyphInfo extents;
9327 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
9328 					// writeln(extents);
9329 					return extents.xOff;
9330 				}
9331 			if(font is null)
9332 				return 0;
9333 			else if(fontset) {
9334 				XRectangle rect;
9335 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
9336 
9337 				return rect.width;
9338 			} else {
9339 				return XTextWidth(font, s.ptr, cast(int) s.length);
9340 			}
9341 		} else version(Windows) {
9342 			WCharzBuffer buffer = WCharzBuffer(s);
9343 
9344 			return stringWidth(buffer.slice, window);
9345 		} else version(OSXCocoa) {
9346 			/+
9347 			int charCount = [string length];
9348 			CGGlyph glyphs[charCount];
9349 			CGRect rects[charCount];
9350 
9351 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
9352 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
9353 
9354 			int totalwidth = 0, maxheight = 0;
9355 			for (int i=0; i < charCount; i++)
9356 			{
9357 				totalwidth += rects[i].size.width;
9358 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
9359 			}
9360 
9361 			dim = CGSizeMake(totalwidth, maxheight);
9362 			+/
9363 
9364 			return 16; // FIXME
9365 		}
9366 		else assert(0);
9367 	}
9368 
9369 	version(Windows)
9370 	/// ditto
9371 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
9372 		if(isNull)
9373 			return 0;
9374 		version(Windows) {
9375 			SIZE size;
9376 
9377 			prepareContext(window);
9378 			scope(exit) releaseContext();
9379 
9380 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
9381 
9382 			return size.cx;
9383 		} else {
9384 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
9385 			static assert(0, "not implemented yet");
9386 			//return stringWidth(s, window);
9387 		}
9388 	}
9389 
9390 	private {
9391 		int prepRefcount;
9392 
9393 		version(Windows) {
9394 			HDC dc;
9395 			HANDLE orig;
9396 			HWND hwnd;
9397 		}
9398 	}
9399 	/++
9400 		[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.
9401 
9402 		History:
9403 			Added January 23, 2021
9404 	+/
9405 	void prepareContext(SimpleWindow window = null) {
9406 		prepRefcount++;
9407 		if(prepRefcount == 1) {
9408 			version(Windows) {
9409 				hwnd = window is null ? null : window.impl.hwnd;
9410 				dc = GetDC(hwnd);
9411 				orig = SelectObject(dc, font);
9412 			}
9413 		}
9414 	}
9415 	/// ditto
9416 	void releaseContext() {
9417 		prepRefcount--;
9418 		if(prepRefcount == 0) {
9419 			version(Windows) {
9420 				SelectObject(dc, orig);
9421 				ReleaseDC(hwnd, dc);
9422 				hwnd = null;
9423 				dc = null;
9424 				orig = null;
9425 			}
9426 		}
9427 	}
9428 
9429 	/+
9430 		FIXME: I think I need advance and kerning pair
9431 
9432 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
9433 	+/
9434 
9435 	/++
9436 		Returns the height of the font.
9437 
9438 		History:
9439 			Added March 26, 2020
9440 			Documented January 16, 2021
9441 	+/
9442 	int height() {
9443 		version(X11) {
9444 			version(with_xft)
9445 				if(isXft && xftFont !is null) {
9446 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
9447 				}
9448 			if(font is null)
9449 				return 0;
9450 			return font.max_bounds.ascent + font.max_bounds.descent;
9451 		} else version(Windows) {
9452 			return height_;
9453 		} else version(OSXCocoa) {
9454 			if(font is null)
9455 				return 0;
9456 			return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight
9457 		}
9458 		else assert(0);
9459 	}
9460 
9461 	private int ascent_;
9462 	private int descent_;
9463 
9464 	/++
9465 		Max ascent above the baseline.
9466 
9467 		History:
9468 			Added January 22, 2021
9469 	+/
9470 	int ascent() {
9471 		return ascent_;
9472 	}
9473 
9474 	/++
9475 		Max descent below the baseline.
9476 
9477 		History:
9478 			Added January 22, 2021
9479 	+/
9480 	int descent() {
9481 		return descent_;
9482 	}
9483 
9484 	/++
9485 		Loads the default font used by [ScreenPainter] if none others are loaded.
9486 
9487 		Returns:
9488 			This method mutates the `this` object, but then returns `this` for
9489 			easy chaining like:
9490 
9491 			---
9492 			auto font = foo.isNull ? foo : foo.loadDefault
9493 			---
9494 
9495 		History:
9496 			Added previously, but left unimplemented until January 24, 2021.
9497 	+/
9498 	OperatingSystemFont loadDefault() {
9499 		unload();
9500 
9501 		loadedInfo = LoadedInfo.init;
9502 
9503 		version(X11) {
9504 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9505 			// but meh since sdpy does its own thing, this should be ok too
9506 
9507 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9508 			this.font = ScreenPainterImplementation.defaultfont;
9509 			this.fontset = ScreenPainterImplementation.defaultfontset;
9510 
9511 			prepareFontInfo();
9512 			return this;
9513 		} else version(Windows) {
9514 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9515 			this.font = ScreenPainterImplementation.defaultGuiFont;
9516 
9517 			prepareFontInfo();
9518 			return this;
9519 		} else version(OSXCocoa) {
9520 			this.font = NSFont.systemFontOfSize(15);
9521 
9522 			prepareFontInfo();
9523 
9524 			// import std.stdio; writeln("Load default: ", this.height());
9525 			return this;
9526 		} else throw new NotYetImplementedException();
9527 	}
9528 
9529 	///
9530 	bool isNull() {
9531 		version(with_xft)
9532 			if(isXft)
9533 				return xftFont is null;
9534 		return font is null;
9535 	}
9536 
9537 	/* Metrics */
9538 	/+
9539 		GetABCWidth
9540 		GetKerningPairs
9541 
9542 		if I do it right, I can size it all here, and match
9543 		what happens when I draw the full string with the OS functions.
9544 
9545 		subclasses might do the same thing while getting the glyphs on images
9546 	struct GlyphInfo {
9547 		int glyph;
9548 
9549 		size_t stringIdxStart;
9550 		size_t stringIdxEnd;
9551 
9552 		Rectangle boundingBox;
9553 	}
9554 	GlyphInfo[] getCharBoxes() {
9555 		// XftTextExtentsUtf8
9556 		return null;
9557 
9558 	}
9559 	+/
9560 
9561 	~this() {
9562 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9563 		unload();
9564 	}
9565 }
9566 
9567 version(Windows)
9568 private string sliceCString(const(wchar)[] w) {
9569 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9570 }
9571 
9572 private inout(char)[] sliceCString(inout(char)* s) {
9573 	import core.stdc.string;
9574 	auto len = strlen(s);
9575 	return s[0 .. len];
9576 }
9577 
9578 version(OSXCocoa)
9579 	alias PaintingHandle = NSObject;
9580 else
9581 	alias PaintingHandle = NativeWindowHandle;
9582 
9583 /**
9584 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9585 	than constructing it directly. Then, it is reference counted so you can pass it
9586 	at around and when the last ref goes out of scope, the buffered drawing activities
9587 	are all carried out.
9588 
9589 
9590 	Most functions use the outlineColor instead of taking a color themselves.
9591 	ScreenPainter is reference counted and draws its buffer to the screen when its
9592 	final reference goes out of scope.
9593 */
9594 struct ScreenPainter {
9595 	CapableOfBeingDrawnUpon window;
9596 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9597 		this.window = window;
9598 		if(window.closed)
9599 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9600 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9601 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9602 		if(window.activeScreenPainter !is null) {
9603 			impl = window.activeScreenPainter;
9604 			if(impl.referenceCount == 0) {
9605 				impl.window = window;
9606 				impl.create(handle);
9607 			}
9608 			impl.manualInvalidations = manualInvalidations;
9609 			impl.referenceCount++;
9610 		//	writeln("refcount ++ ", impl.referenceCount);
9611 		} else {
9612 			impl = new ScreenPainterImplementation;
9613 			impl.window = window;
9614 			impl.create(handle);
9615 			impl.referenceCount = 1;
9616 			impl.manualInvalidations = manualInvalidations;
9617 			window.activeScreenPainter = impl;
9618 			// writeln("constructed");
9619 		}
9620 
9621 		copyActiveOriginals();
9622 	}
9623 
9624 	/++
9625 		EXPERIMENTAL. subject to change.
9626 
9627 		When you draw a cursor, you can draw this to notify your window of where it is,
9628 		for IME systems to use.
9629 	+/
9630 	void notifyCursorPosition(int x, int y, int width, int height) {
9631 		if(auto w = cast(SimpleWindow) window) {
9632 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9633 		}
9634 	}
9635 
9636 	/++
9637 		If you are using manual invalidations, this informs the
9638 		window system that a section needs to be redrawn.
9639 
9640 		If you didn't opt into manual invalidation, you don't
9641 		have to call this.
9642 
9643 		History:
9644 			Added December 30, 2021 (dub v10.5)
9645 	+/
9646 	void invalidateRect(Rectangle rect) {
9647 		if(impl is null) return;
9648 
9649 		// transform(rect)
9650 		rect.left += _originX;
9651 		rect.right += _originX;
9652 		rect.top += _originY;
9653 		rect.bottom += _originY;
9654 
9655 		impl.invalidateRect(rect);
9656 	}
9657 
9658 	private Pen originalPen;
9659 	private Color originalFillColor;
9660 	private arsd.color.Rectangle originalClipRectangle;
9661 	private OperatingSystemFont originalFont;
9662 	void copyActiveOriginals() {
9663 		if(impl is null) return;
9664 		originalPen = impl._activePen;
9665 		originalFillColor = impl._fillColor;
9666 		originalClipRectangle = impl._clipRectangle;
9667 		version(OSXCocoa) {} else
9668 		originalFont = impl._activeFont;
9669 	}
9670 
9671 	~this() {
9672 		if(impl is null) return;
9673 		impl.referenceCount--;
9674 		//writeln("refcount -- ", impl.referenceCount);
9675 		if(impl.referenceCount == 0) {
9676 			// writeln("destructed");
9677 			impl.dispose();
9678 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9679 			// writeln("paint finished");
9680 		} else {
9681 			// there is still an active reference, reset stuff so the
9682 			// next user doesn't get weirdness via the reference
9683 			this.rasterOp = RasterOp.normal;
9684 			pen = originalPen;
9685 			fillColor = originalFillColor;
9686 			if(originalFont)
9687 				setFont(originalFont);
9688 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9689 		}
9690 	}
9691 
9692 	this(this) {
9693 		if(impl is null) return;
9694 		impl.referenceCount++;
9695 		//writeln("refcount ++ ", impl.referenceCount);
9696 
9697 		copyActiveOriginals();
9698 	}
9699 
9700 	private int _originX;
9701 	private int _originY;
9702 	@property int originX() { return _originX; }
9703 	@property int originY() { return _originY; }
9704 	@property int originX(int a) {
9705 		_originX = a;
9706 		return _originX;
9707 	}
9708 	@property int originY(int a) {
9709 		_originY = a;
9710 		return _originY;
9711 	}
9712 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9713 	private void transform(ref Point p) {
9714 		if(impl is null) return;
9715 		p.x += _originX;
9716 		p.y += _originY;
9717 	}
9718 
9719 	// this needs to be checked BEFORE the originX/Y transformation
9720 	private bool isClipped(Point p) {
9721 		return !currentClipRectangle.contains(p);
9722 	}
9723 	private bool isClipped(Point p, int width, int height) {
9724 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9725 	}
9726 	private bool isClipped(Point p, Size s) {
9727 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9728 	}
9729 	private bool isClipped(Point p, Point p2) {
9730 		// need to ensure the end points are actually included inside, so the +1 does that
9731 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9732 	}
9733 
9734 
9735 	/++
9736 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9737 
9738 		Returns:
9739 			The old clip rectangle.
9740 
9741 		History:
9742 			Return value was `void` prior to May 10, 2021.
9743 
9744 	+/
9745 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9746 		if(impl is null) return currentClipRectangle;
9747 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9748 			return currentClipRectangle; // no need to do anything
9749 		auto old = currentClipRectangle;
9750 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9751 		transform(pt);
9752 
9753 		impl.setClipRectangle(pt.x, pt.y, width, height);
9754 
9755 		return old;
9756 	}
9757 
9758 	/// ditto
9759 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9760 		if(impl is null) return currentClipRectangle;
9761 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9762 	}
9763 
9764 	///
9765 	void setFont(OperatingSystemFont font) {
9766 		if(impl is null) return;
9767 		impl.setFont(font);
9768 	}
9769 
9770 	///
9771 	int fontHeight() {
9772 		if(impl is null) return 0;
9773 		return impl.fontHeight();
9774 	}
9775 
9776 	private Pen activePen;
9777 
9778 	///
9779 	@property void pen(Pen p) {
9780 		if(impl is null) return;
9781 		activePen = p;
9782 		impl.pen(p);
9783 	}
9784 
9785 	///
9786 	@scriptable
9787 	@property void outlineColor(Color c) {
9788 		if(impl is null) return;
9789 		if(activePen.color == c)
9790 			return;
9791 		activePen.color = c;
9792 		impl.pen(activePen);
9793 	}
9794 
9795 	///
9796 	@scriptable
9797 	@property void fillColor(Color c) {
9798 		if(impl is null) return;
9799 		impl.fillColor(c);
9800 	}
9801 
9802 	///
9803 	@property void rasterOp(RasterOp op) {
9804 		if(impl is null) return;
9805 		impl.rasterOp(op);
9806 	}
9807 
9808 
9809 	void updateDisplay() {
9810 		// FIXME this should do what the dtor does
9811 	}
9812 
9813 	/// 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)
9814 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9815 		if(impl is null) return;
9816 		if(isClipped(upperLeft, width, height)) return;
9817 		transform(upperLeft);
9818 		version(Windows) {
9819 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9820 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9821 			RECT clip = scroll;
9822 			RECT uncovered;
9823 			HRGN hrgn;
9824 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9825 				throw new WindowsApiException("ScrollDC", GetLastError());
9826 
9827 		} else version(X11) {
9828 			// FIXME: clip stuff outside this rectangle
9829 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9830 		} else version(OSXCocoa) {
9831 			throw new NotYetImplementedException();
9832 		} else static assert(0);
9833 	}
9834 
9835 	///
9836 	void clear(Color color = Color.white()) {
9837 		if(impl is null) return;
9838 		fillColor = color;
9839 		outlineColor = color;
9840 		drawRectangle(Point(0, 0), window.width, window.height);
9841 	}
9842 
9843 	/++
9844 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9845 
9846 		Params:
9847 			upperLeft = point on the window where the upper left corner of the image will be drawn
9848 			imageUpperLeft = point on the image to start the slice to draw
9849 			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.
9850 		History:
9851 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9852 	+/
9853 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9854 		if(impl is null) return;
9855 		if(isClipped(upperLeft, s.width, s.height)) return;
9856 		transform(upperLeft);
9857 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9858 	}
9859 
9860 	///
9861 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9862 		if(impl is null) return;
9863 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9864 		transform(upperLeft);
9865 		if(w == 0 || w > i.width)
9866 			w = i.width;
9867 		if(h == 0 || h > i.height)
9868 			h = i.height;
9869 		if(upperLeftOfImage.x < 0)
9870 			upperLeftOfImage.x = 0;
9871 		if(upperLeftOfImage.y < 0)
9872 			upperLeftOfImage.y = 0;
9873 
9874 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9875 	}
9876 
9877 	///
9878 	Size textSize(in char[] text) {
9879 		if(impl is null) return Size(0, 0);
9880 		return impl.textSize(text);
9881 	}
9882 
9883 	/++
9884 		Draws a string in the window with the set font (see [setFont] to change it).
9885 
9886 		Params:
9887 			upperLeft = the upper left point of the bounding box of the text
9888 			text = the string to draw
9889 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9890 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9891 	+/
9892 	@scriptable
9893 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9894 		if(impl is null) return;
9895 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9896 			if(isClipped(upperLeft, lowerRight)) return;
9897 			transform(lowerRight);
9898 		} else {
9899 			if(isClipped(upperLeft, textSize(text))) return;
9900 		}
9901 		transform(upperLeft);
9902 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9903 	}
9904 
9905 	/++
9906 		Draws text using a custom font.
9907 
9908 		This is still MAJOR work in progress.
9909 
9910 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9911 	+/
9912 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9913 		if(impl is null) return;
9914 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9915 		transform(upperLeft);
9916 		font.drawString(this, upperLeft, text);
9917 	}
9918 
9919 	version(Windows)
9920 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9921 		if(impl is null) return;
9922 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9923 		transform(upperLeft);
9924 
9925 		if(text.length && text[$-1] == '\n')
9926 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9927 
9928 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9929 	}
9930 
9931 	static struct TextDrawingContext {
9932 		Point boundingBoxUpperLeft;
9933 		Point boundingBoxLowerRight;
9934 
9935 		Point currentLocation;
9936 
9937 		Point lastDrewUpperLeft;
9938 		Point lastDrewLowerRight;
9939 
9940 		// how do i do right aligned rich text?
9941 		// i kinda want to do a pre-made drawing then right align
9942 		// draw the whole block.
9943 		//
9944 		// That's exactly the diff: inline vs block stuff.
9945 
9946 		// I need to get coordinates of an inline section out too,
9947 		// not just a bounding box, but a series of bounding boxes
9948 		// should be ok. Consider what's needed to detect a click
9949 		// on a link in the middle of a paragraph breaking a line.
9950 		//
9951 		// Generally, we should be able to get the rectangles of
9952 		// any portion we draw.
9953 		//
9954 		// It also needs to tell what text is left if it overflows
9955 		// out of the box, so we can do stuff like float images around
9956 		// it. It should not attempt to draw a letter that would be
9957 		// clipped.
9958 		//
9959 		// I might also turn off word wrap stuff.
9960 	}
9961 
9962 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9963 		if(impl is null) return;
9964 		// FIXME
9965 	}
9966 
9967 	/// Drawing an individual pixel is slow. Avoid it if possible.
9968 	void drawPixel(Point where) {
9969 		if(impl is null) return;
9970 		if(isClipped(where)) return;
9971 		transform(where);
9972 		impl.drawPixel(where.x, where.y);
9973 	}
9974 
9975 
9976 	/// Draws a pen using the current pen / outlineColor
9977 	@scriptable
9978 	void drawLine(Point starting, Point ending) {
9979 		if(impl is null) return;
9980 		if(isClipped(starting, ending)) return;
9981 		transform(starting);
9982 		transform(ending);
9983 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9984 	}
9985 
9986 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9987 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9988 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9989 	@scriptable
9990 	void drawRectangle(Point upperLeft, int width, int height) {
9991 		if(impl is null) return;
9992 		if(isClipped(upperLeft, width, height)) return;
9993 		transform(upperLeft);
9994 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9995 	}
9996 
9997 	/// ditto
9998 	void drawRectangle(Point upperLeft, Size size) {
9999 		if(impl is null) return;
10000 		if(isClipped(upperLeft, size.width, size.height)) return;
10001 		transform(upperLeft);
10002 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
10003 	}
10004 
10005 	/// ditto
10006 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
10007 		if(impl is null) return;
10008 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
10009 		transform(upperLeft);
10010 		transform(lowerRightInclusive);
10011 		impl.drawRectangle(upperLeft.x, upperLeft.y,
10012 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
10013 	}
10014 
10015 	// overload added on May 12, 2021
10016 	/// ditto
10017 	void drawRectangle(Rectangle rect) {
10018 		drawRectangle(rect.upperLeft, rect.size);
10019 	}
10020 
10021 	/// Arguments are the points of the bounding rectangle
10022 	void drawEllipse(Point upperLeft, Point lowerRight) {
10023 		if(impl is null) return;
10024 		if(isClipped(upperLeft, lowerRight)) return;
10025 		transform(upperLeft);
10026 		transform(lowerRight);
10027 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
10028 	}
10029 
10030 	/++
10031 		Draws an arc inside the bounding box given by `upperLeft`, `width`, and `height`, from the angle (`start` / 64) degrees for (`length` / 64) degrees of rotation.
10032 
10033 
10034 		If `length` is positive, it travels counter-clockwise from `start`. If negative, it goes clockwise.  `start` == 0 at the three o'clock position of the bounding box - the center of the line at the right-hand side of the screen.
10035 
10036 		The arc is outlined with the current pen and filled with the current fill. On Windows, the line segments back to the middle are also drawn unless you have a full length ellipse.
10037 
10038 		Bugs:
10039 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
10040 
10041 			The arc outline on Linux sometimes goes over the target.
10042 
10043 			The fill on Windows sometimes stops short.
10044 
10045 		History:
10046 			This function was broken af, totally inconsistent on platforms until September 24, 2021.
10047 
10048 			The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024.
10049 	+/
10050 	void drawArc(Point upperLeft, int width, int height, int start, int length) {
10051 		if(impl is null) return;
10052 		// FIXME: not actually implemented
10053 		if(isClipped(upperLeft, width, height)) return;
10054 		transform(upperLeft);
10055 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length);
10056 	}
10057 
10058 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
10059 	void drawCircle(Point upperLeft, int diameter) {
10060 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
10061 	}
10062 
10063 	/++
10064 		Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush.
10065 
10066 
10067 		Bugs:
10068 			Not implemented on Mac; it will instead draw a non-rounded rectangle for now.
10069 
10070 		History:
10071 			Added August 3, 2024
10072 	+/
10073 	void drawRectangleRounded(Rectangle rect, int borderRadius) {
10074 		drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius);
10075 	}
10076 
10077 	/// ditto
10078 	void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) {
10079 		drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius);
10080 	}
10081 
10082 	/// ditto
10083 	void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
10084 		if(borderRadius <= 0) {
10085 			drawRectangle(upperLeft, lowerRight);
10086 			return;
10087 		}
10088 
10089 		transform(upperLeft);
10090 		transform(lowerRight);
10091 
10092 		impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius);
10093 	}
10094 
10095 	/// .
10096 	void drawPolygon(Point[] vertexes) {
10097 		if(impl is null) return;
10098 		assert(vertexes.length);
10099 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
10100 		foreach(ref vertex; vertexes) {
10101 			if(vertex.x < minX)
10102 				minX = vertex.x;
10103 			if(vertex.y < minY)
10104 				minY = vertex.y;
10105 			if(vertex.x > maxX)
10106 				maxX = vertex.x;
10107 			if(vertex.y > maxY)
10108 				maxY = vertex.y;
10109 			transform(vertex);
10110 		}
10111 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
10112 		impl.drawPolygon(vertexes);
10113 	}
10114 
10115 	/// ditto
10116 	void drawPolygon(Point[] vertexes...) {
10117 		if(impl is null) return;
10118 		drawPolygon(vertexes);
10119 	}
10120 
10121 
10122 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
10123 
10124 	//mixin NativeScreenPainterImplementation!() impl;
10125 
10126 
10127 	// HACK: if I mixin the impl directly, it won't let me override the copy
10128 	// constructor! The linker complains about there being multiple definitions.
10129 	// I'll make the best of it and reference count it though.
10130 	ScreenPainterImplementation* impl;
10131 }
10132 
10133 	// HACK: I need a pointer to the implementation so it's separate
10134 	struct ScreenPainterImplementation {
10135 		CapableOfBeingDrawnUpon window;
10136 		int referenceCount;
10137 		mixin NativeScreenPainterImplementation!();
10138 	}
10139 
10140 // FIXME: i haven't actually tested the sprite class on MS Windows
10141 
10142 /**
10143 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
10144 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
10145 
10146 
10147 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
10148 	though I'm not sure that's ideal and the implementation might change.
10149 
10150 	You create one by giving a window and an image. It optimizes for that window,
10151 	and copies the image into it to use as the initial picture. Creating a sprite
10152 	can be quite slow (especially over a network connection) so you should do it
10153 	as little as possible and just hold on to your sprite handles after making them.
10154 	simpledisplay does try to do its best though, using the XSHM extension if available,
10155 	but you should still write your code as if it will always be slow.
10156 
10157 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
10158 	a fast operation - much faster than drawing the Image itself every time.
10159 
10160 	`Sprite` represents a scarce resource which should be freed when you
10161 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
10162 	after it has been disposed. If you are unsure about this, don't take chances,
10163 	just let the garbage collector do it for you. But ideally, you can manage its
10164 	lifetime more efficiently.
10165 
10166 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
10167 	support alpha blending in its drawing at this time. That might change in the
10168 	future, but if you need alpha blending right now, use OpenGL instead. See
10169 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
10170 
10171 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
10172 	in by setting the enableAlpha = true in the constructor.
10173 */
10174 class Sprite : CapableOfBeingDrawnUpon {
10175 
10176 	///
10177 	ScreenPainter draw() {
10178 		return ScreenPainter(this, handle, false);
10179 	}
10180 
10181 	/++
10182 		Copies the sprite's current state into a [TrueColorImage].
10183 
10184 		Be warned: this can be a very slow operation
10185 
10186 		History:
10187 			Actually implemented on March 14, 2021
10188 	+/
10189 	TrueColorImage takeScreenshot() {
10190 		return trueColorImageFromNativeHandle(handle, width, height);
10191 	}
10192 
10193 	void delegate() paintingFinishedDg() { return null; }
10194 	bool closed() { return false; }
10195 	ScreenPainterImplementation* activeScreenPainter_;
10196 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
10197 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
10198 
10199 	version(Windows)
10200 		private ubyte* rawData;
10201 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
10202 	// ditto on the XPicture stuff
10203 
10204 	version(X11) {
10205 		private static XRenderPictFormat* RGB24;
10206 		private static XRenderPictFormat* ARGB32;
10207 
10208 		private Picture xrenderPicture;
10209 	}
10210 
10211 	version(X11)
10212 	private static void requireXRender() {
10213 		if(!XRenderLibrary.loadAttempted) {
10214 			XRenderLibrary.loadDynamicLibrary();
10215 		}
10216 
10217 		if(!XRenderLibrary.loadSuccessful)
10218 			throw new Exception("XRender library load failure");
10219 
10220 		auto display = XDisplayConnection.get;
10221 
10222 		// FIXME: if we migrate X displays, these need to be changed
10223 		if(RGB24 is null)
10224 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
10225 		if(ARGB32 is null)
10226 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
10227 	}
10228 
10229 	protected this() {}
10230 
10231 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
10232 		this._width = width;
10233 		this._height = height;
10234 		this.enableAlpha = enableAlpha;
10235 
10236 		version(X11) {
10237 			auto display = XDisplayConnection.get();
10238 
10239 			if(enableAlpha) {
10240 				requireXRender();
10241 			}
10242 
10243 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
10244 
10245 			if(enableAlpha) {
10246 				XRenderPictureAttributes attrs;
10247 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
10248 			}
10249 		} else version(Windows) {
10250 			version(CRuntime_DigitalMars) {
10251 				//if(enableAlpha)
10252 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
10253 			}
10254 
10255 			BITMAPINFO infoheader;
10256 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10257 			infoheader.bmiHeader.biWidth = width;
10258 			infoheader.bmiHeader.biHeight = height;
10259 			infoheader.bmiHeader.biPlanes = 1;
10260 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
10261 			infoheader.bmiHeader.biCompression = BI_RGB;
10262 
10263 			// FIXME: this should prolly be a device dependent bitmap...
10264 			handle = CreateDIBSection(
10265 				null,
10266 				&infoheader,
10267 				DIB_RGB_COLORS,
10268 				cast(void**) &rawData,
10269 				null,
10270 				0);
10271 
10272 			if(handle is null)
10273 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
10274 		}
10275 	}
10276 
10277 	/// Makes a sprite based on the image with the initial contents from the Image
10278 	this(SimpleWindow win, Image i) {
10279 		this(win, i.width, i.height, i.enableAlpha);
10280 
10281 		version(X11) {
10282 			auto display = XDisplayConnection.get();
10283 			auto gc = XCreateGC(display, this.handle, 0, null);
10284 			scope(exit) XFreeGC(display, gc);
10285 			if(i.usingXshm)
10286 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
10287 			else
10288 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
10289 		} else version(Windows) {
10290 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
10291 			auto arrLength = itemsPerLine * height;
10292 			rawData[0..arrLength] = i.rawData[0..arrLength];
10293 		} else version(OSXCocoa) {
10294 			// FIXME: I have no idea if this is even any good
10295 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
10296 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
10297 				colorSpace,
10298 				kCGImageAlphaPremultipliedLast
10299 				|kCGBitmapByteOrder32Big);
10300 			CGColorSpaceRelease(colorSpace);
10301 			auto rawData = CGBitmapContextGetData(handle);
10302 
10303 			auto rdl = (width * height * 4);
10304 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
10305 		} else static assert(0);
10306 	}
10307 
10308 	/++
10309 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
10310 
10311 		Params:
10312 			where = point on the window where the upper left corner of the image will be drawn
10313 			imageUpperLeft = point on the image to start the slice to draw
10314 			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.
10315 		History:
10316 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
10317 	+/
10318 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
10319 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
10320 	}
10321 
10322 	/// Call this when you're ready to get rid of it
10323 	void dispose() {
10324 		version(X11) {
10325 			staticDispose(xrenderPicture, handle);
10326 			xrenderPicture = None;
10327 			handle = None;
10328 		} else version(Windows) {
10329 			staticDispose(handle);
10330 			handle = null;
10331 		} else version(OSXCocoa) {
10332 			staticDispose(handle);
10333 			handle = null;
10334 		} else static assert(0);
10335 
10336 	}
10337 
10338 	version(X11)
10339 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
10340 		if(xrenderPicture)
10341 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
10342 		if(handle)
10343 			XFreePixmap(XDisplayConnection.get(), handle);
10344 	}
10345 	else version(Windows)
10346 	static void staticDispose(HBITMAP handle) {
10347 		if(handle)
10348 			DeleteObject(handle);
10349 	}
10350 	else version(OSXCocoa)
10351 	static void staticDispose(CGContextRef context) {
10352 		if(context)
10353 			CGContextRelease(context);
10354 	}
10355 
10356 	~this() {
10357 		version(X11) { if(xrenderPicture || handle)
10358 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
10359 		} else version(Windows) { if(handle)
10360 			cleanupQueue.queue!staticDispose(handle);
10361 		} else version(OSXCocoa) { if(handle)
10362 			cleanupQueue.queue!staticDispose(handle);
10363 		} else static assert(0);
10364 	}
10365 
10366 	///
10367 	final @property int width() { return _width; }
10368 
10369 	///
10370 	final @property int height() { return _height; }
10371 
10372 	///
10373 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
10374 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
10375 	}
10376 
10377 	auto nativeHandle() {
10378 		return handle;
10379 	}
10380 
10381 	private:
10382 
10383 	int _width;
10384 	int _height;
10385 	bool enableAlpha;
10386 	version(X11)
10387 		Pixmap handle;
10388 	else version(Windows)
10389 		HBITMAP handle;
10390 	else version(OSXCocoa)
10391 		CGContextRef handle;
10392 	else version(Emscripten)
10393 		void* handle;
10394 	else static assert(0);
10395 }
10396 
10397 /++
10398 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
10399 
10400 	History:
10401 		Added November 20, 2021 (dub v10.4)
10402 +/
10403 version(OSXCocoa) {} else // NotYetImplementedException
10404 abstract class Gradient : Sprite {
10405 	protected this(int w, int h) {
10406 		version(X11) {
10407 			Sprite.requireXRender();
10408 
10409 			super();
10410 			enableAlpha = true;
10411 			_width = w;
10412 			_height = h;
10413 		} else version(Windows) {
10414 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
10415 		}
10416 	}
10417 
10418 	version(Windows)
10419 	final void forEachPixel(scope Color delegate(int x, int y) dg) @system {
10420 		auto ptr = rawData;
10421 		foreach(j; 0 .. _height)
10422 		foreach(i; 0 .. _width) {
10423 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
10424 			*rawData = (color.a * color.b) / 255; rawData++;
10425 			*rawData = (color.a * color.g) / 255; rawData++;
10426 			*rawData = (color.a * color.r) / 255; rawData++;
10427 			*rawData = color.a; rawData++;
10428 		}
10429 	}
10430 
10431 	version(X11)
10432 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
10433 		assert(stops.length > 0);
10434 		assert(stops.length <= 16, "I got lazy with buffers");
10435 
10436 		XFixed[16] stopsPositions = void;
10437 		XRenderColor[16] colors = void;
10438 
10439 		foreach(idx, stop; stops) {
10440 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
10441 			auto c = stop.c;
10442 			colors[idx] = XRenderColor(
10443 				cast(ushort)(c.r * ushort.max / 255),
10444 				cast(ushort)(c.g * ushort.max / 255),
10445 				cast(ushort)(c.b * ushort.max / 255),
10446 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
10447 			);
10448 		}
10449 
10450 		xrenderPicture = dg(stopsPositions, colors);
10451 	}
10452 
10453 	///
10454 	static struct Stop {
10455 		float percentage; /// between 0 and 1.0
10456 		Color c;
10457 	}
10458 }
10459 
10460 /++
10461 	Creates a linear gradient between p1 and p2.
10462 
10463 	X ONLY RIGHT NOW
10464 
10465 	History:
10466 		Added November 20, 2021 (dub v10.4)
10467 
10468 	Bugs:
10469 		Not yet implemented on Windows.
10470 +/
10471 version(OSXCocoa) {} else // NotYetImplementedException
10472 class LinearGradient : Gradient {
10473 	/++
10474 
10475 	+/
10476 	this(Point p1, Point p2, Stop[] stops...) {
10477 		super(p2.x, p2.y);
10478 
10479 		version(X11) {
10480 			XLinearGradient gradient;
10481 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
10482 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
10483 
10484 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10485 				return XRenderCreateLinearGradient(
10486 					XDisplayConnection.get,
10487 					&gradient,
10488 					stopsPositions.ptr,
10489 					colors.ptr,
10490 					cast(int) stops.length);
10491 			});
10492 		} else version(Windows) {
10493 			// FIXME
10494 			forEachPixel((int x, int y) {
10495 				import core.stdc.math;
10496 
10497 				//sqrtf(
10498 
10499 				return Color.transparent;
10500 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10501 			});
10502 		}
10503 	}
10504 }
10505 
10506 /++
10507 	A conical gradient goes from color to color around a circumference from a center point.
10508 
10509 	X ONLY RIGHT NOW
10510 
10511 	History:
10512 		Added November 20, 2021 (dub v10.4)
10513 
10514 	Bugs:
10515 		Not yet implemented on Windows.
10516 +/
10517 version(OSXCocoa) {} else // NotYetImplementedException
10518 class ConicalGradient : Gradient {
10519 	/++
10520 
10521 	+/
10522 	this(Point center, float angleInDegrees, Stop[] stops...) {
10523 		super(center.x * 2, center.y * 2);
10524 
10525 		version(X11) {
10526 			XConicalGradient gradient;
10527 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
10528 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
10529 
10530 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10531 				return XRenderCreateConicalGradient(
10532 					XDisplayConnection.get,
10533 					&gradient,
10534 					stopsPositions.ptr,
10535 					colors.ptr,
10536 					cast(int) stops.length);
10537 			});
10538 		} else version(Windows) {
10539 			// FIXME
10540 			forEachPixel((int x, int y) {
10541 				import core.stdc.math;
10542 
10543 				//sqrtf(
10544 
10545 				return Color.transparent;
10546 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10547 			});
10548 
10549 		}
10550 	}
10551 }
10552 
10553 /++
10554 	A radial gradient goes from color to color based on distance from the center.
10555 	It is like rings of color.
10556 
10557 	X ONLY RIGHT NOW
10558 
10559 
10560 	More specifically, you create two circles: an inner circle and an outer circle.
10561 	The gradient is only drawn in the area outside the inner circle but inside the outer
10562 	circle. The closest line between those two circles forms the line for the gradient
10563 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10564 
10565 	History:
10566 		Added November 20, 2021 (dub v10.4)
10567 
10568 	Bugs:
10569 		Not yet implemented on Windows.
10570 +/
10571 version(OSXCocoa) {} else // NotYetImplementedException
10572 class RadialGradient : Gradient {
10573 	/++
10574 
10575 	+/
10576 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10577 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10578 
10579 		version(X11) {
10580 			XRadialGradient gradient;
10581 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10582 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10583 
10584 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10585 				return XRenderCreateRadialGradient(
10586 					XDisplayConnection.get,
10587 					&gradient,
10588 					stopsPositions.ptr,
10589 					colors.ptr,
10590 					cast(int) stops.length);
10591 			});
10592 		} else version(Windows) {
10593 			// FIXME
10594 			forEachPixel((int x, int y) {
10595 				import core.stdc.math;
10596 
10597 				//sqrtf(
10598 
10599 				return Color.transparent;
10600 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10601 			});
10602 		}
10603 	}
10604 }
10605 
10606 
10607 
10608 /+
10609 	NOT IMPLEMENTED
10610 
10611 	A display-stored image optimized for relatively quick drawing, like
10612 	[Sprite], but this one supports alpha channel blending and does NOT
10613 	support direct drawing upon it with a [ScreenPainter].
10614 
10615 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10616 	plain [ScreenPainter]... sort of.
10617 
10618 	On X11, it requires the Xrender extension and library. This is available
10619 	almost everywhere though.
10620 
10621 	History:
10622 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10623 +/
10624 version(none)
10625 class AlphaSprite {
10626 	/++
10627 		Copies the given image into it.
10628 	+/
10629 	this(MemoryImage img) {
10630 
10631 		if(!XRenderLibrary.loadAttempted) {
10632 			XRenderLibrary.loadDynamicLibrary();
10633 
10634 			// FIXME: this needs to be reconstructed when the X server changes
10635 			repopulateX();
10636 		}
10637 		if(!XRenderLibrary.loadSuccessful)
10638 			throw new Exception("XRender library load failure");
10639 
10640 		// I probably need to put the alpha mask in a separate Picture
10641 		// ugh
10642 		// maybe the Sprite itself can have an alpha bitmask anyway
10643 
10644 
10645 		auto display = XDisplayConnection.get();
10646 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10647 
10648 
10649 		XRenderPictureAttributes attrs;
10650 
10651 		handle = XRenderCreatePicture(
10652 			XDisplayConnection.get,
10653 			pixmap,
10654 			RGBA,
10655 			0,
10656 			&attrs
10657 		);
10658 
10659 	}
10660 
10661 	// maybe i'll use the create gradient functions too with static factories..
10662 
10663 	void drawAt(ScreenPainter painter, Point where) {
10664 		//painter.drawPixmap(this, where);
10665 
10666 		XRenderPictureAttributes attrs;
10667 
10668 		auto pic = XRenderCreatePicture(
10669 			XDisplayConnection.get,
10670 			painter.impl.d,
10671 			RGB,
10672 			0,
10673 			&attrs
10674 		);
10675 
10676 		XRenderComposite(
10677 			XDisplayConnection.get,
10678 			3, // PictOpOver
10679 			handle,
10680 			None,
10681 			pic,
10682 			0, // src
10683 			0,
10684 			0, // mask
10685 			0,
10686 			10, // dest
10687 			10,
10688 			100, // width
10689 			100
10690 		);
10691 
10692 		/+
10693 		XRenderFreePicture(
10694 			XDisplayConnection.get,
10695 			pic
10696 		);
10697 
10698 		XRenderFreePicture(
10699 			XDisplayConnection.get,
10700 			fill
10701 		);
10702 		+/
10703 		// on Windows you can stretch but Xrender still can't :(
10704 	}
10705 
10706 	static XRenderPictFormat* RGB;
10707 	static XRenderPictFormat* RGBA;
10708 	static void repopulateX() {
10709 		auto display = XDisplayConnection.get;
10710 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10711 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10712 	}
10713 
10714 	XPixmap pixmap;
10715 	Picture handle;
10716 }
10717 
10718 ///
10719 interface CapableOfBeingDrawnUpon {
10720 	///
10721 	ScreenPainter draw();
10722 	///
10723 	int width();
10724 	///
10725 	int height();
10726 	protected ScreenPainterImplementation* activeScreenPainter();
10727 	protected void activeScreenPainter(ScreenPainterImplementation*);
10728 	bool closed();
10729 
10730 	void delegate() paintingFinishedDg();
10731 
10732 	/// Be warned: this can be a very slow operation
10733 	TrueColorImage takeScreenshot();
10734 }
10735 
10736 /// 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].
10737 void flushGui() {
10738 	version(X11) {
10739 		auto dpy = XDisplayConnection.get();
10740 		XLockDisplay(dpy);
10741 		scope(exit) XUnlockDisplay(dpy);
10742 		XFlush(dpy);
10743 	}
10744 }
10745 
10746 /++
10747 	Runs the given code in the GUI thread when its event loop
10748 	is available, blocking until it completes. This allows you
10749 	to create and manipulate windows from another thread without
10750 	invoking undefined behavior.
10751 
10752 	If this is the gui thread, it runs the code immediately.
10753 
10754 	If no gui thread exists yet, the current thread is assumed
10755 	to be it. Attempting to create windows or run the event loop
10756 	in any other thread will cause an assertion failure.
10757 
10758 
10759 	$(TIP
10760 		Did you know you can use UFCS on delegate literals?
10761 
10762 		() {
10763 			// code here
10764 		}.runInGuiThread;
10765 	)
10766 
10767 	Returns:
10768 		`true` if the function was called, `false` if it was not.
10769 		The function may not be called because the gui thread had
10770 		already terminated by the time you called this.
10771 
10772 	History:
10773 		Added April 10, 2020 (v7.2.0)
10774 
10775 		Return value added and implementation tweaked to avoid locking
10776 		at program termination on February 24, 2021 (v9.2.1).
10777 +/
10778 bool runInGuiThread(scope void delegate() dg) @trusted {
10779 	claimGuiThread();
10780 
10781 	if(thisIsGuiThread) {
10782 		dg();
10783 		return true;
10784 	}
10785 
10786 	if(guiThreadTerminating)
10787 		return false;
10788 
10789 	import core.sync.semaphore;
10790 	static Semaphore sc;
10791 	if(sc is null)
10792 		sc = new Semaphore();
10793 
10794 	static RunQueueMember* rqm;
10795 	if(rqm is null)
10796 		rqm = new RunQueueMember;
10797 	rqm.dg = cast(typeof(rqm.dg)) dg;
10798 	rqm.signal = sc;
10799 	rqm.thrown = null;
10800 
10801 	synchronized(runInGuiThreadLock) {
10802 		runInGuiThreadQueue ~= rqm;
10803 	}
10804 
10805 	if(!SimpleWindow.eventWakeUp())
10806 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10807 
10808 	rqm.signal.wait();
10809 	auto t = rqm.thrown;
10810 
10811 	if(t)
10812 		throw t;
10813 
10814 	return true;
10815 }
10816 
10817 // note it runs sync if this is the gui thread....
10818 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10819 	claimGuiThread();
10820 
10821 	try {
10822 
10823 		if(thisIsGuiThread) {
10824 			dg();
10825 			return;
10826 		}
10827 
10828 		if(guiThreadTerminating)
10829 			return;
10830 
10831 		RunQueueMember* rqm = new RunQueueMember;
10832 		rqm.dg = cast(typeof(rqm.dg)) dg;
10833 		rqm.signal = null;
10834 		rqm.thrown = null;
10835 
10836 		synchronized(runInGuiThreadLock) {
10837 			runInGuiThreadQueue ~= rqm;
10838 		}
10839 
10840 		if(!SimpleWindow.eventWakeUp())
10841 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10842 	} catch(Exception e) {
10843 		if(handleError)
10844 			handleError(e);
10845 	}
10846 }
10847 
10848 private void runPendingRunInGuiThreadDelegates() {
10849 	more:
10850 	RunQueueMember* next;
10851 	synchronized(runInGuiThreadLock) {
10852 		if(runInGuiThreadQueue.length) {
10853 			next = runInGuiThreadQueue[0];
10854 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10855 		} else {
10856 			next = null;
10857 		}
10858 	}
10859 
10860 	if(next) {
10861 		try {
10862 			next.dg();
10863 			next.thrown = null;
10864 		} catch(Throwable t) {
10865 			next.thrown = t;
10866 		}
10867 
10868 		if(next.signal)
10869 			next.signal.notify();
10870 
10871 		goto more;
10872 	}
10873 }
10874 
10875 private void claimGuiThread() nothrow {
10876 	import core.atomic;
10877 	if(cas(&guiThreadExists_, false, true))
10878 		thisIsGuiThread = true;
10879 }
10880 
10881 private struct RunQueueMember {
10882 	void delegate() dg;
10883 	import core.sync.semaphore;
10884 	Semaphore signal;
10885 	Throwable thrown;
10886 }
10887 
10888 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10889 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10890 private bool thisIsGuiThread = false;
10891 private shared bool guiThreadExists_ = false;
10892 private shared bool guiThreadTerminating = false;
10893 
10894 /++
10895 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10896 	event loop. All windows must be exclusively created and managed by a single thread.
10897 
10898 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10899 	when you call one of its constructors.
10900 
10901 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10902 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10903 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10904 
10905 	The reason this function is available is in case you want to message pass between a gui
10906 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10907 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10908 
10909 	History:
10910 		Added December 3, 2021 (dub v10.5)
10911 +/
10912 public bool guiThreadExists() {
10913 	return guiThreadExists_;
10914 }
10915 
10916 /++
10917 	Returns `true` if this thread is either running or set to be running the
10918 	simpledisplay.d gui core event loop because it owns windows.
10919 
10920 	It is important to keep gui-related functionality in the right thread, so you will
10921 	want to `runInGuiThread` when you call them (with some specific exceptions called
10922 	out in those specific functions' documentation). Notably, all windows must be
10923 	created and managed only from the gui thread.
10924 
10925 	Will return false if simpledisplay's other functions haven't been called
10926 	yet; check [guiThreadExists] in addition to this.
10927 
10928 	History:
10929 		Added December 3, 2021 (dub v10.5)
10930 +/
10931 public bool thisThreadRunningGui() {
10932 	return thisIsGuiThread;
10933 }
10934 
10935 /++
10936 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10937 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10938 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10939 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10940 	file instead if you are in one of those situations).
10941 
10942 	It does not support outputting very many types; just strings and ints are likely to actually work.
10943 
10944 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10945 	is unspecified meaning I can change it at any time. The only point of this function is to help
10946 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10947 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10948 	in those contexts.
10949 
10950 	$(WARNING
10951 		I reserve the right to change this function at any time. You can use it if it helps you
10952 		but do not rely on it for anything permanent.
10953 	)
10954 
10955 	History:
10956 		Added December 3, 2021. Not formally supported under any stable tag.
10957 +/
10958 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10959 	try {
10960 		version(Windows) {
10961 			import core.sys.windows.wincon;
10962 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10963 				AllocConsole();
10964 			const(char)* fn = "CONOUT$";
10965 		} else version(Posix) {
10966 			const(char)* fn = "/dev/tty";
10967 		} else static assert(0, "Function not implemented for your system");
10968 
10969 		if(fileOverride.length)
10970 			fn = fileOverride.ptr;
10971 
10972 		import core.stdc.stdio;
10973 		auto fp = fopen(fn, "wt");
10974 		if(fp is null) return;
10975 		scope(exit) fclose(fp);
10976 
10977 		string str;
10978 		foreach(item; t) {
10979 			static if(is(typeof(item) : const(char)[]))
10980 				str ~= item;
10981 			else
10982 				str ~= toInternal!string(item);
10983 			str ~= " ";
10984 		}
10985 		str ~= "\n";
10986 
10987 		fwrite(str.ptr, 1, str.length, fp);
10988 		fflush(fp);
10989 	} catch(Exception e) {
10990 		// sorry no hope
10991 	}
10992 }
10993 
10994 private void guiThreadFinalize() {
10995 	assert(thisIsGuiThread);
10996 
10997 	guiThreadTerminating = true; // don't add any more from this point on
10998 	runPendingRunInGuiThreadDelegates();
10999 }
11000 
11001 /+
11002 interface IPromise {
11003 	void reportProgress(int current, int max, string message);
11004 
11005 	/+ // not formally in cuz of templates but still
11006 	IPromise Then();
11007 	IPromise Catch();
11008 	IPromise Finally();
11009 	+/
11010 }
11011 
11012 /+
11013 	auto promise = async({ ... });
11014 	promise.Then(whatever).
11015 		Then(whateverelse).
11016 		Catch((exception) { });
11017 
11018 
11019 	A promise is run inside a fiber and it looks something like:
11020 
11021 	try {
11022 		auto res = whatever();
11023 		auto res2 = whateverelse(res);
11024 	} catch(Exception e) {
11025 		{ }(e);
11026 	}
11027 
11028 	When a thing succeeds, it is passed as an arg to the next
11029 +/
11030 class Promise(T) : IPromise {
11031 	auto Then() { return null; }
11032 	auto Catch() { return null; }
11033 	auto Finally() { return null; }
11034 
11035 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
11036 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
11037 	T await();
11038 }
11039 
11040 interface Task {
11041 }
11042 
11043 interface Resolvable(T) : Task {
11044 	void run();
11045 
11046 	void resolve(T);
11047 
11048 	Resolvable!T then(void delegate(T)); // returns a new promise
11049 	Resolvable!T error(Throwable); // js catch
11050 	Resolvable!T completed(); // js finally
11051 
11052 }
11053 
11054 /++
11055 	Runs `work` in a helper thread and sends its return value back to the main gui
11056 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
11057 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
11058 	kill the program.
11059 
11060 	You can call reportProgress(position, max, message) to update your parent window
11061 	on your progress.
11062 
11063 	I should also use `shared` methods. FIXME
11064 
11065 	History:
11066 		Added March 6, 2021 (dub version 9.3).
11067 +/
11068 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
11069 	uponCompletion(work(null));
11070 }
11071 
11072 +/
11073 
11074 /// Used internal to dispatch events to various classes.
11075 interface CapableOfHandlingNativeEvent {
11076 	NativeEventHandler getNativeEventHandler();
11077 
11078 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
11079 
11080 	version(X11) {
11081 		// if this is impossible, you are allowed to just throw from it
11082 		// Note: if you call it from another object, set a flag cuz the manger will call you again
11083 		void recreateAfterDisconnect();
11084 		// discard any *connection specific* state, but keep enough that you
11085 		// can be recreated if possible. discardConnectionState() is always called immediately
11086 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
11087 		// you need initialization order
11088 		void discardConnectionState();
11089 	}
11090 }
11091 
11092 version(X11)
11093 /++
11094 	State of keys on mouse events, especially motion.
11095 
11096 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
11097 +/
11098 enum ModifierState : uint {
11099 	shift = 1, ///
11100 	capsLock = 2, ///
11101 	ctrl = 4, ///
11102 	alt = 8, /// Not always available on Windows
11103 	windows = 64, /// ditto
11104 	numLock = 16, ///
11105 
11106 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11107 	middleButtonDown = 512, /// ditto
11108 	rightButtonDown = 1024, /// ditto
11109 }
11110 else version(Emscripten)
11111 enum ModifierState : uint {
11112 	shift = 1, ///
11113 	capsLock = 2, ///
11114 	ctrl = 4, ///
11115 	alt = 8, /// Not always available on Windows
11116 	windows = 64, /// ditto
11117 	numLock = 16, ///
11118 
11119 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11120 	middleButtonDown = 512, /// ditto
11121 	rightButtonDown = 1024, /// ditto
11122 }
11123 else version(Windows)
11124 /// ditto
11125 enum ModifierState : uint {
11126 	shift = 4, ///
11127 	ctrl = 8, ///
11128 
11129 	// i'm not sure if the next two are available
11130 	alt = 256, /// not always available on Windows
11131 	windows = 512, /// ditto
11132 
11133 	capsLock = 1024, ///
11134 	numLock = 2048, ///
11135 
11136 	leftButtonDown = 1, /// not available on key events
11137 	middleButtonDown = 16, /// ditto
11138 	rightButtonDown = 2, /// ditto
11139 
11140 	backButtonDown = 0x20, /// not available on X
11141 	forwardButtonDown = 0x40, /// ditto
11142 }
11143 else version(OSXCocoa)
11144 // FIXME FIXME NotYetImplementedException
11145 enum ModifierState : uint {
11146 	shift = 1, ///
11147 	capsLock = 2, ///
11148 	ctrl = 4, ///
11149 	alt = 8, /// Not always available on Windows
11150 	windows = 64, /// ditto
11151 	numLock = 16, ///
11152 
11153 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11154 	middleButtonDown = 512, /// ditto
11155 	rightButtonDown = 1024, /// ditto
11156 }
11157 
11158 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
11159 enum MouseButton : int {
11160 	none = 0,
11161 	left = 1, ///
11162 	right = 2, ///
11163 	middle = 4, ///
11164 	wheelUp = 8, ///
11165 	wheelDown = 16, ///
11166 	backButton = 32, /// often found on the thumb and used for back in browsers
11167 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
11168 }
11169 
11170 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
11171 enum MouseButtonLinear : ubyte {
11172 	left = 1, ///
11173 	right, ///
11174 	middle, ///
11175 	wheelUp, ///
11176 	wheelDown, ///
11177 	backButton, /// often found on the thumb and used for back in browsers
11178 	forwardButton, /// often found on the thumb and used for forward in browsers
11179 }
11180 
11181 version(WebAssembly) {
11182 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11183 	enum Key {
11184 		Escape = 0xff1b, ///
11185 		F1 = 0xffbe, ///
11186 		F2 = 0xffbf, ///
11187 		F3 = 0xffc0, ///
11188 		F4 = 0xffc1, ///
11189 		F5 = 0xffc2, ///
11190 		F6 = 0xffc3, ///
11191 		F7 = 0xffc4, ///
11192 		F8 = 0xffc5, ///
11193 		F9 = 0xffc6, ///
11194 		F10 = 0xffc7, ///
11195 		F11 = 0xffc8, ///
11196 		F12 = 0xffc9, ///
11197 		PrintScreen = 0xff61, ///
11198 		ScrollLock = 0xff14, ///
11199 		Pause = 0xff13, ///
11200 		Grave = 0x60, /// The $(BACKTICK) ~ key
11201 		// number keys across the top of the keyboard
11202 		N1 = 0x31, /// Number key atop the keyboard
11203 		N2 = 0x32, ///
11204 		N3 = 0x33, ///
11205 		N4 = 0x34, ///
11206 		N5 = 0x35, ///
11207 		N6 = 0x36, ///
11208 		N7 = 0x37, ///
11209 		N8 = 0x38, ///
11210 		N9 = 0x39, ///
11211 		N0 = 0x30, ///
11212 		Dash = 0x2d, ///
11213 		Equals = 0x3d, ///
11214 		Backslash = 0x5c, /// The \ | key
11215 		Backspace = 0xff08, ///
11216 		Insert = 0xff63, ///
11217 		Home = 0xff50, ///
11218 		PageUp = 0xff55, ///
11219 		Delete = 0xffff, ///
11220 		End = 0xff57, ///
11221 		PageDown = 0xff56, ///
11222 		Up = 0xff52, ///
11223 		Down = 0xff54, ///
11224 		Left = 0xff51, ///
11225 		Right = 0xff53, ///
11226 
11227 		Tab = 0xff09, ///
11228 		Q = 0x71, ///
11229 		W = 0x77, ///
11230 		E = 0x65, ///
11231 		R = 0x72, ///
11232 		T = 0x74, ///
11233 		Y = 0x79, ///
11234 		U = 0x75, ///
11235 		I = 0x69, ///
11236 		O = 0x6f, ///
11237 		P = 0x70, ///
11238 		LeftBracket = 0x5b, /// the [ { key
11239 		RightBracket = 0x5d, /// the ] } key
11240 		CapsLock = 0xffe5, ///
11241 		A = 0x61, ///
11242 		S = 0x73, ///
11243 		D = 0x64, ///
11244 		F = 0x66, ///
11245 		G = 0x67, ///
11246 		H = 0x68, ///
11247 		J = 0x6a, ///
11248 		K = 0x6b, ///
11249 		L = 0x6c, ///
11250 		Semicolon = 0x3b, ///
11251 		Apostrophe = 0x27, ///
11252 		Enter = 0xff0d, ///
11253 		Shift = 0xffe1, ///
11254 		Z = 0x7a, ///
11255 		X = 0x78, ///
11256 		C = 0x63, ///
11257 		V = 0x76, ///
11258 		B = 0x62, ///
11259 		N = 0x6e, ///
11260 		M = 0x6d, ///
11261 		Comma = 0x2c, ///
11262 		Period = 0x2e, ///
11263 		Slash = 0x2f, /// the / ? key
11264 		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
11265 		Ctrl = 0xffe3, ///
11266 		Windows = 0xffeb, ///
11267 		Alt = 0xffe9, ///
11268 		Space = 0x20, ///
11269 		Alt_r = 0xffea, /// ditto of shift_r
11270 		Windows_r = 0xffec, ///
11271 		Menu = 0xff67, ///
11272 		Ctrl_r = 0xffe4, ///
11273 
11274 		NumLock = 0xff7f, ///
11275 		Divide = 0xffaf, /// The / key on the number pad
11276 		Multiply = 0xffaa, /// The * key on the number pad
11277 		Minus = 0xffad, /// The - key on the number pad
11278 		Plus = 0xffab, /// The + key on the number pad
11279 		PadEnter = 0xff8d, /// Numberpad enter key
11280 		Pad1 = 0xff9c, /// Numberpad keys
11281 		Pad2 = 0xff99, ///
11282 		Pad3 = 0xff9b, ///
11283 		Pad4 = 0xff96, ///
11284 		Pad5 = 0xff9d, ///
11285 		Pad6 = 0xff98, ///
11286 		Pad7 = 0xff95, ///
11287 		Pad8 = 0xff97, ///
11288 		Pad9 = 0xff9a, ///
11289 		Pad0 = 0xff9e, ///
11290 		PadDot = 0xff9f, ///
11291 	}
11292 } version(X11) {
11293 	// FIXME: match ASCII whenever we can. Most of it is already there,
11294 	// but there's a few exceptions and mismatches with Windows
11295 
11296 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11297 	enum Key {
11298 		Escape = 0xff1b, ///
11299 		F1 = 0xffbe, ///
11300 		F2 = 0xffbf, ///
11301 		F3 = 0xffc0, ///
11302 		F4 = 0xffc1, ///
11303 		F5 = 0xffc2, ///
11304 		F6 = 0xffc3, ///
11305 		F7 = 0xffc4, ///
11306 		F8 = 0xffc5, ///
11307 		F9 = 0xffc6, ///
11308 		F10 = 0xffc7, ///
11309 		F11 = 0xffc8, ///
11310 		F12 = 0xffc9, ///
11311 		PrintScreen = 0xff61, ///
11312 		ScrollLock = 0xff14, ///
11313 		Pause = 0xff13, ///
11314 		Grave = 0x60, /// The $(BACKTICK) ~ key
11315 		// number keys across the top of the keyboard
11316 		N1 = 0x31, /// Number key atop the keyboard
11317 		N2 = 0x32, ///
11318 		N3 = 0x33, ///
11319 		N4 = 0x34, ///
11320 		N5 = 0x35, ///
11321 		N6 = 0x36, ///
11322 		N7 = 0x37, ///
11323 		N8 = 0x38, ///
11324 		N9 = 0x39, ///
11325 		N0 = 0x30, ///
11326 		Dash = 0x2d, ///
11327 		Equals = 0x3d, ///
11328 		Backslash = 0x5c, /// The \ | key
11329 		Backspace = 0xff08, ///
11330 		Insert = 0xff63, ///
11331 		Home = 0xff50, ///
11332 		PageUp = 0xff55, ///
11333 		Delete = 0xffff, ///
11334 		End = 0xff57, ///
11335 		PageDown = 0xff56, ///
11336 		Up = 0xff52, ///
11337 		Down = 0xff54, ///
11338 		Left = 0xff51, ///
11339 		Right = 0xff53, ///
11340 
11341 		Tab = 0xff09, ///
11342 		Q = 0x71, ///
11343 		W = 0x77, ///
11344 		E = 0x65, ///
11345 		R = 0x72, ///
11346 		T = 0x74, ///
11347 		Y = 0x79, ///
11348 		U = 0x75, ///
11349 		I = 0x69, ///
11350 		O = 0x6f, ///
11351 		P = 0x70, ///
11352 		LeftBracket = 0x5b, /// the [ { key
11353 		RightBracket = 0x5d, /// the ] } key
11354 		CapsLock = 0xffe5, ///
11355 		A = 0x61, ///
11356 		S = 0x73, ///
11357 		D = 0x64, ///
11358 		F = 0x66, ///
11359 		G = 0x67, ///
11360 		H = 0x68, ///
11361 		J = 0x6a, ///
11362 		K = 0x6b, ///
11363 		L = 0x6c, ///
11364 		Semicolon = 0x3b, ///
11365 		Apostrophe = 0x27, ///
11366 		Enter = 0xff0d, ///
11367 		Shift = 0xffe1, ///
11368 		Z = 0x7a, ///
11369 		X = 0x78, ///
11370 		C = 0x63, ///
11371 		V = 0x76, ///
11372 		B = 0x62, ///
11373 		N = 0x6e, ///
11374 		M = 0x6d, ///
11375 		Comma = 0x2c, ///
11376 		Period = 0x2e, ///
11377 		Slash = 0x2f, /// the / ? key
11378 		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
11379 		Ctrl = 0xffe3, ///
11380 		Windows = 0xffeb, ///
11381 		Alt = 0xffe9, ///
11382 		Space = 0x20, ///
11383 		Alt_r = 0xffea, /// ditto of shift_r
11384 		Windows_r = 0xffec, ///
11385 		Menu = 0xff67, ///
11386 		Ctrl_r = 0xffe4, ///
11387 
11388 		NumLock = 0xff7f, ///
11389 		Divide = 0xffaf, /// The / key on the number pad
11390 		Multiply = 0xffaa, /// The * key on the number pad
11391 		Minus = 0xffad, /// The - key on the number pad
11392 		Plus = 0xffab, /// The + key on the number pad
11393 		PadEnter = 0xff8d, /// Numberpad enter key
11394 		Pad1 = 0xff9c, /// Numberpad keys
11395 		Pad2 = 0xff99, ///
11396 		Pad3 = 0xff9b, ///
11397 		Pad4 = 0xff96, ///
11398 		Pad5 = 0xff9d, ///
11399 		Pad6 = 0xff98, ///
11400 		Pad7 = 0xff95, ///
11401 		Pad8 = 0xff97, ///
11402 		Pad9 = 0xff9a, ///
11403 		Pad0 = 0xff9e, ///
11404 		PadDot = 0xff9f, ///
11405 	}
11406 } else version(Windows) {
11407 	// the character here is for en-us layouts and for illustration only
11408 	// if you actually want to get characters, wait for character events
11409 	// (the argument to your event handler is simply a dchar)
11410 	// those will be converted by the OS for the right locale.
11411 
11412 	enum Key {
11413 		Escape = 0x1b,
11414 		F1 = 0x70,
11415 		F2 = 0x71,
11416 		F3 = 0x72,
11417 		F4 = 0x73,
11418 		F5 = 0x74,
11419 		F6 = 0x75,
11420 		F7 = 0x76,
11421 		F8 = 0x77,
11422 		F9 = 0x78,
11423 		F10 = 0x79,
11424 		F11 = 0x7a,
11425 		F12 = 0x7b,
11426 		PrintScreen = 0x2c,
11427 		ScrollLock = 0x91,
11428 		Pause = 0x13,
11429 		Grave = 0xc0,
11430 		// number keys across the top of the keyboard
11431 		N1 = 0x31,
11432 		N2 = 0x32,
11433 		N3 = 0x33,
11434 		N4 = 0x34,
11435 		N5 = 0x35,
11436 		N6 = 0x36,
11437 		N7 = 0x37,
11438 		N8 = 0x38,
11439 		N9 = 0x39,
11440 		N0 = 0x30,
11441 		Dash = 0xbd,
11442 		Equals = 0xbb,
11443 		Backslash = 0xdc,
11444 		Backspace = 0x08,
11445 		Insert = 0x2d,
11446 		Home = 0x24,
11447 		PageUp = 0x21,
11448 		Delete = 0x2e,
11449 		End = 0x23,
11450 		PageDown = 0x22,
11451 		Up = 0x26,
11452 		Down = 0x28,
11453 		Left = 0x25,
11454 		Right = 0x27,
11455 
11456 		Tab = 0x09,
11457 		Q = 0x51,
11458 		W = 0x57,
11459 		E = 0x45,
11460 		R = 0x52,
11461 		T = 0x54,
11462 		Y = 0x59,
11463 		U = 0x55,
11464 		I = 0x49,
11465 		O = 0x4f,
11466 		P = 0x50,
11467 		LeftBracket = 0xdb,
11468 		RightBracket = 0xdd,
11469 		CapsLock = 0x14,
11470 		A = 0x41,
11471 		S = 0x53,
11472 		D = 0x44,
11473 		F = 0x46,
11474 		G = 0x47,
11475 		H = 0x48,
11476 		J = 0x4a,
11477 		K = 0x4b,
11478 		L = 0x4c,
11479 		Semicolon = 0xba,
11480 		Apostrophe = 0xde,
11481 		Enter = 0x0d,
11482 		Shift = 0x10,
11483 		Z = 0x5a,
11484 		X = 0x58,
11485 		C = 0x43,
11486 		V = 0x56,
11487 		B = 0x42,
11488 		N = 0x4e,
11489 		M = 0x4d,
11490 		Comma = 0xbc,
11491 		Period = 0xbe,
11492 		Slash = 0xbf,
11493 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11494 		Ctrl = 0x11,
11495 		Windows = 0x5b,
11496 		Alt = -5, // FIXME
11497 		Space = 0x20,
11498 		Alt_r = 0xffea, // ditto of shift_r
11499 		Windows_r = 0x5c, // ditto of shift_r
11500 		Menu = 0x5d,
11501 		Ctrl_r = 0xa3, // ditto of shift_r
11502 
11503 		NumLock = 0x90,
11504 		Divide = 0x6f,
11505 		Multiply = 0x6a,
11506 		Minus = 0x6d,
11507 		Plus = 0x6b,
11508 		PadEnter = -8, // FIXME
11509 		Pad1 = 0x61,
11510 		Pad2 = 0x62,
11511 		Pad3 = 0x63,
11512 		Pad4 = 0x64,
11513 		Pad5 = 0x65,
11514 		Pad6 = 0x66,
11515 		Pad7 = 0x67,
11516 		Pad8 = 0x68,
11517 		Pad9 = 0x69,
11518 		Pad0 = 0x60,
11519 		PadDot = 0x6e,
11520 	}
11521 
11522 	// I'm keeping this around for reference purposes
11523 	// ideally all these buttons will be listed for all platforms,
11524 	// but now now I'm just focusing on my US keyboard
11525 	version(none)
11526 	enum Key {
11527 		LBUTTON = 0x01,
11528 		RBUTTON = 0x02,
11529 		CANCEL = 0x03,
11530 		MBUTTON = 0x04,
11531 		//static if (_WIN32_WINNT > =  0x500) {
11532 		XBUTTON1 = 0x05,
11533 		XBUTTON2 = 0x06,
11534 		//}
11535 		BACK = 0x08,
11536 		TAB = 0x09,
11537 		CLEAR = 0x0C,
11538 		RETURN = 0x0D,
11539 		SHIFT = 0x10,
11540 		CONTROL = 0x11,
11541 		MENU = 0x12,
11542 		PAUSE = 0x13,
11543 		CAPITAL = 0x14,
11544 		KANA = 0x15,
11545 		HANGEUL = 0x15,
11546 		HANGUL = 0x15,
11547 		JUNJA = 0x17,
11548 		FINAL = 0x18,
11549 		HANJA = 0x19,
11550 		KANJI = 0x19,
11551 		ESCAPE = 0x1B,
11552 		CONVERT = 0x1C,
11553 		NONCONVERT = 0x1D,
11554 		ACCEPT = 0x1E,
11555 		MODECHANGE = 0x1F,
11556 		SPACE = 0x20,
11557 		PRIOR = 0x21,
11558 		NEXT = 0x22,
11559 		END = 0x23,
11560 		HOME = 0x24,
11561 		LEFT = 0x25,
11562 		UP = 0x26,
11563 		RIGHT = 0x27,
11564 		DOWN = 0x28,
11565 		SELECT = 0x29,
11566 		PRINT = 0x2A,
11567 		EXECUTE = 0x2B,
11568 		SNAPSHOT = 0x2C,
11569 		INSERT = 0x2D,
11570 		DELETE = 0x2E,
11571 		HELP = 0x2F,
11572 		LWIN = 0x5B,
11573 		RWIN = 0x5C,
11574 		APPS = 0x5D,
11575 		SLEEP = 0x5F,
11576 		NUMPAD0 = 0x60,
11577 		NUMPAD1 = 0x61,
11578 		NUMPAD2 = 0x62,
11579 		NUMPAD3 = 0x63,
11580 		NUMPAD4 = 0x64,
11581 		NUMPAD5 = 0x65,
11582 		NUMPAD6 = 0x66,
11583 		NUMPAD7 = 0x67,
11584 		NUMPAD8 = 0x68,
11585 		NUMPAD9 = 0x69,
11586 		MULTIPLY = 0x6A,
11587 		ADD = 0x6B,
11588 		SEPARATOR = 0x6C,
11589 		SUBTRACT = 0x6D,
11590 		DECIMAL = 0x6E,
11591 		DIVIDE = 0x6F,
11592 		F1 = 0x70,
11593 		F2 = 0x71,
11594 		F3 = 0x72,
11595 		F4 = 0x73,
11596 		F5 = 0x74,
11597 		F6 = 0x75,
11598 		F7 = 0x76,
11599 		F8 = 0x77,
11600 		F9 = 0x78,
11601 		F10 = 0x79,
11602 		F11 = 0x7A,
11603 		F12 = 0x7B,
11604 		F13 = 0x7C,
11605 		F14 = 0x7D,
11606 		F15 = 0x7E,
11607 		F16 = 0x7F,
11608 		F17 = 0x80,
11609 		F18 = 0x81,
11610 		F19 = 0x82,
11611 		F20 = 0x83,
11612 		F21 = 0x84,
11613 		F22 = 0x85,
11614 		F23 = 0x86,
11615 		F24 = 0x87,
11616 		NUMLOCK = 0x90,
11617 		SCROLL = 0x91,
11618 		LSHIFT = 0xA0,
11619 		RSHIFT = 0xA1,
11620 		LCONTROL = 0xA2,
11621 		RCONTROL = 0xA3,
11622 		LMENU = 0xA4,
11623 		RMENU = 0xA5,
11624 		//static if (_WIN32_WINNT > =  0x500) {
11625 		BROWSER_BACK = 0xA6,
11626 		BROWSER_FORWARD = 0xA7,
11627 		BROWSER_REFRESH = 0xA8,
11628 		BROWSER_STOP = 0xA9,
11629 		BROWSER_SEARCH = 0xAA,
11630 		BROWSER_FAVORITES = 0xAB,
11631 		BROWSER_HOME = 0xAC,
11632 		VOLUME_MUTE = 0xAD,
11633 		VOLUME_DOWN = 0xAE,
11634 		VOLUME_UP = 0xAF,
11635 		MEDIA_NEXT_TRACK = 0xB0,
11636 		MEDIA_PREV_TRACK = 0xB1,
11637 		MEDIA_STOP = 0xB2,
11638 		MEDIA_PLAY_PAUSE = 0xB3,
11639 		LAUNCH_MAIL = 0xB4,
11640 		LAUNCH_MEDIA_SELECT = 0xB5,
11641 		LAUNCH_APP1 = 0xB6,
11642 		LAUNCH_APP2 = 0xB7,
11643 		//}
11644 		OEM_1 = 0xBA,
11645 		//static if (_WIN32_WINNT > =  0x500) {
11646 		OEM_PLUS = 0xBB,
11647 		OEM_COMMA = 0xBC,
11648 		OEM_MINUS = 0xBD,
11649 		OEM_PERIOD = 0xBE,
11650 		//}
11651 		OEM_2 = 0xBF,
11652 		OEM_3 = 0xC0,
11653 		OEM_4 = 0xDB,
11654 		OEM_5 = 0xDC,
11655 		OEM_6 = 0xDD,
11656 		OEM_7 = 0xDE,
11657 		OEM_8 = 0xDF,
11658 		//static if (_WIN32_WINNT > =  0x500) {
11659 		OEM_102 = 0xE2,
11660 		//}
11661 		PROCESSKEY = 0xE5,
11662 		//static if (_WIN32_WINNT > =  0x500) {
11663 		PACKET = 0xE7,
11664 		//}
11665 		ATTN = 0xF6,
11666 		CRSEL = 0xF7,
11667 		EXSEL = 0xF8,
11668 		EREOF = 0xF9,
11669 		PLAY = 0xFA,
11670 		ZOOM = 0xFB,
11671 		NONAME = 0xFC,
11672 		PA1 = 0xFD,
11673 		OEM_CLEAR = 0xFE,
11674 	}
11675 
11676 } else version(OSXCocoa) {
11677 	enum Key {
11678 		Escape = 53,
11679 		F1 = 122,
11680 		F2 = 120,
11681 		F3 = 99,
11682 		F4 = 118,
11683 		F5 = 96,
11684 		F6 = 97,
11685 		F7 = 98,
11686 		F8 = 100,
11687 		F9 = 101,
11688 		F10 = 109,
11689 		F11 = 103,
11690 		F12 = 111,
11691 		PrintScreen = 105,
11692 		ScrollLock = 107,
11693 		Pause = 113,
11694 		Grave = 50,
11695 		// number keys across the top of the keyboard
11696 		N1 = 18,
11697 		N2 = 19,
11698 		N3 = 20,
11699 		N4 = 21,
11700 		N5 = 23,
11701 		N6 = 22,
11702 		N7 = 26,
11703 		N8 = 28,
11704 		N9 = 25,
11705 		N0 = 29,
11706 		Dash = 27,
11707 		Equals = 24,
11708 		Backslash = 42,
11709 		Backspace = 51,
11710 		Insert = 114,
11711 		Home = 115,
11712 		PageUp = 116,
11713 		Delete = 117,
11714 		End = 119,
11715 		PageDown = 121,
11716 		Up = 126,
11717 		Down = 125,
11718 		Left = 123,
11719 		Right = 124,
11720 
11721 		Tab = 48,
11722 		Q = 12,
11723 		W = 13,
11724 		E = 14,
11725 		R = 15,
11726 		T = 17,
11727 		Y = 16,
11728 		U = 32,
11729 		I = 34,
11730 		O = 31,
11731 		P = 35,
11732 		LeftBracket = 33,
11733 		RightBracket = 30,
11734 		CapsLock = 57,
11735 		A = 0,
11736 		S = 1,
11737 		D = 2,
11738 		F = 3,
11739 		G = 5,
11740 		H = 4,
11741 		J = 38,
11742 		K = 40,
11743 		L = 37,
11744 		Semicolon = 41,
11745 		Apostrophe = 39,
11746 		Enter = 36,
11747 		Shift = 56,
11748 		Z = 6,
11749 		X = 7,
11750 		C = 8,
11751 		V = 9,
11752 		B = 11,
11753 		N = 45,
11754 		M = 46,
11755 		Comma = 43,
11756 		Period = 47,
11757 		Slash = 44,
11758 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11759 		Ctrl = 59,
11760 		Windows = 55,
11761 		Alt = 58,
11762 		Space = 49,
11763 		Alt_r = -3, // ditto of shift_r
11764 		Windows_r = -2,
11765 		Menu = 110,
11766 		Ctrl_r = -1,
11767 
11768 		NumLock = 1,
11769 		Divide = 75,
11770 		Multiply = 67,
11771 		Minus = 78,
11772 		Plus = 69,
11773 		PadEnter = 76,
11774 		Pad1 = 83,
11775 		Pad2 = 84,
11776 		Pad3 = 85,
11777 		Pad4 = 86,
11778 		Pad5 = 87,
11779 		Pad6 = 88,
11780 		Pad7 = 89,
11781 		Pad8 = 91,
11782 		Pad9 = 92,
11783 		Pad0 = 82,
11784 		PadDot = 65,
11785 	}
11786 
11787 }
11788 
11789 /* Additional utilities */
11790 
11791 
11792 Color fromHsl(real h, real s, real l) {
11793 	return arsd.color.fromHsl([h,s,l]);
11794 }
11795 
11796 
11797 
11798 /* ********** What follows is the system-specific implementations *********/
11799 version(Windows) {
11800 
11801 
11802 	// helpers for making HICONs from MemoryImages
11803 	class WindowsIcon {
11804 		struct Win32Icon {
11805 			align(1):
11806 			uint biSize;
11807 			int biWidth;
11808 			int biHeight;
11809 			ushort biPlanes;
11810 			ushort biBitCount;
11811 			uint biCompression;
11812 			uint biSizeImage;
11813 			int biXPelsPerMeter;
11814 			int biYPelsPerMeter;
11815 			uint biClrUsed;
11816 			uint biClrImportant;
11817 			// RGBQUAD[colorCount] biColors;
11818 			/* Pixels:
11819 			Uint8 pixels[]
11820 			*/
11821 			/* Mask:
11822 			Uint8 mask[]
11823 			*/
11824 		}
11825 
11826 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11827 
11828 			assert(mi.width <= 256, "image too wide");
11829 			assert(mi.height <= 256, "image too tall");
11830 			assert(mi.width % 8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy
11831 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11832 
11833 			int icon_plen = mi.width * mi.height * 4;
11834 			int icon_mlen = mi.width * mi.height / 8;
11835 
11836 			int colorCount = 0;
11837 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11838 
11839 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11840 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11841 
11842 			auto data = memory[Win32Icon.sizeof .. $];
11843 
11844 			width = mi.width;
11845 			height = mi.height;
11846 
11847 			auto trueColorImage = mi.getAsTrueColorImage();
11848 
11849 			icon_win32.biSize = 40;
11850 			icon_win32.biWidth = mi.width;
11851 			icon_win32.biHeight = mi.height*2;
11852 			icon_win32.biPlanes = 1;
11853 			icon_win32.biBitCount = 32;
11854 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11855 
11856 			int offset = 0;
11857 			int andOff = icon_plen * 8; // the and offset is in bits
11858 
11859 			// leaving the and mask as the default 0 so the rgba alpha blend
11860 			// does its thing instead
11861 			for(int y = height - 1; y >= 0; y--) {
11862 				int off2 = y * width * 4;
11863 				foreach(x; 0 .. width) {
11864 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11865 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11866 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11867 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11868 
11869 					offset += 4;
11870 					off2 += 4;
11871 				}
11872 			}
11873 
11874 			return memory;
11875 		}
11876 
11877 		this(MemoryImage mi) {
11878 			int icon_len, width, height;
11879 
11880 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11881 
11882 			/*
11883 			PNG* png = readPnpngData);
11884 			PNGHeader pngh = getHeader(png);
11885 			void* icon_win32;
11886 			if(pngh.depth == 4) {
11887 				auto i = new Win32Icon!(16);
11888 				i.fromPNG(png, pngh, icon_len, width, height);
11889 				icon_win32 = i;
11890 			}
11891 			else if(pngh.depth == 8) {
11892 				auto i = new Win32Icon!(256);
11893 				i.fromPNG(png, pngh, icon_len, width, height);
11894 				icon_win32 = i;
11895 			} else assert(0);
11896 			*/
11897 
11898 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11899 
11900 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11901 		}
11902 
11903 		~this() {
11904 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11905 			DestroyIcon(hIcon);
11906 		}
11907 
11908 		HICON hIcon;
11909 	}
11910 
11911 
11912 
11913 
11914 
11915 
11916 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11917 	alias HWND NativeWindowHandle;
11918 
11919 	extern(Windows)
11920 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11921 		try {
11922 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11923 				// it returns zero if the message is handled, so we won't do anything more there
11924 				// do I like that though?
11925 				int mustReturn;
11926 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11927 				if(mustReturn)
11928 					return ret;
11929 			}
11930 
11931 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11932 				if(window.getNativeEventHandler !is null) {
11933 					int mustReturn;
11934 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11935 					if(mustReturn)
11936 						return ret;
11937 				}
11938 				if(auto w = cast(SimpleWindow) (*window))
11939 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11940 				else
11941 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11942 			} else {
11943 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11944 			}
11945 		} catch (Exception e) {
11946 			try {
11947 				sdpy_abort(e);
11948 				return 0;
11949 			} catch(Exception e) { assert(0); }
11950 		}
11951 	}
11952 
11953 	void sdpy_abort(Throwable e) nothrow {
11954 		try
11955 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11956 		catch(Exception e)
11957 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11958 		ExitProcess(1);
11959 	}
11960 
11961 	mixin template NativeScreenPainterImplementation() {
11962 		HDC hdc;
11963 		HWND hwnd;
11964 		//HDC windowHdc;
11965 		HBITMAP oldBmp;
11966 
11967 		void create(PaintingHandle window) {
11968 			hwnd = window;
11969 
11970 			if(auto sw = cast(SimpleWindow) this.window) {
11971 				// drawing on a window, double buffer
11972 				auto windowHdc = GetDC(hwnd);
11973 
11974 				auto buffer = sw.impl.buffer;
11975 				if(buffer is null) {
11976 					hdc = windowHdc;
11977 					windowDc = true;
11978 				} else {
11979 					hdc = CreateCompatibleDC(windowHdc);
11980 
11981 					ReleaseDC(hwnd, windowHdc);
11982 
11983 					oldBmp = SelectObject(hdc, buffer);
11984 				}
11985 			} else {
11986 				// drawing on something else, draw directly
11987 				hdc = CreateCompatibleDC(null);
11988 				SelectObject(hdc, window);
11989 			}
11990 
11991 			// X doesn't draw a text background, so neither should we
11992 			SetBkMode(hdc, TRANSPARENT);
11993 
11994 			ensureDefaultFontLoaded();
11995 
11996 			if(defaultGuiFont) {
11997 				SelectObject(hdc, defaultGuiFont);
11998 				// DeleteObject(defaultGuiFont);
11999 			}
12000 		}
12001 
12002 		static HFONT defaultGuiFont;
12003 		static void ensureDefaultFontLoaded() {
12004 			static bool triedDefaultGuiFont = false;
12005 			if(!triedDefaultGuiFont) {
12006 				NONCLIENTMETRICS params;
12007 				params.cbSize = params.sizeof;
12008 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
12009 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
12010 				}
12011 				triedDefaultGuiFont = true;
12012 			}
12013 		}
12014 
12015 		private OperatingSystemFont _activeFont;
12016 
12017 		void setFont(OperatingSystemFont font) {
12018 			_activeFont = font;
12019 			if(font && font.font) {
12020 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
12021 					// error... how to handle tho?
12022 				} else {
12023 
12024 				}
12025 			}
12026 			else if(defaultGuiFont)
12027 				SelectObject(hdc, defaultGuiFont);
12028 		}
12029 
12030 		arsd.color.Rectangle _clipRectangle;
12031 
12032 		void setClipRectangle(int x, int y, int width, int height) {
12033 			auto old = _clipRectangle;
12034 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12035 			if(old == _clipRectangle)
12036 				return;
12037 
12038 			if(width == 0 || height == 0) {
12039 				SelectClipRgn(hdc, null);
12040 			} else {
12041 				auto region = CreateRectRgn(x, y, x + width, y + height);
12042 				SelectClipRgn(hdc, region);
12043 				DeleteObject(region);
12044 			}
12045 		}
12046 
12047 
12048 		// just because we can on Windows...
12049 		//void create(Image image);
12050 
12051 		void invalidateRect(Rectangle invalidRect) {
12052 			RECT rect;
12053 			rect.left = invalidRect.left;
12054 			rect.right = invalidRect.right;
12055 			rect.top = invalidRect.top;
12056 			rect.bottom = invalidRect.bottom;
12057 			InvalidateRect(hwnd, &rect, false);
12058 		}
12059 		bool manualInvalidations;
12060 
12061 		void dispose() {
12062 			// FIXME: this.window.width/height is probably wrong
12063 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
12064 			// ReleaseDC(hwnd, windowHdc);
12065 
12066 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
12067 			if(cast(SimpleWindow) this.window) {
12068 				if(!manualInvalidations)
12069 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
12070 			}
12071 
12072 			if(originalPen !is null)
12073 				SelectObject(hdc, originalPen);
12074 			if(currentPen !is null)
12075 				DeleteObject(currentPen);
12076 			if(originalBrush !is null)
12077 				SelectObject(hdc, originalBrush);
12078 			if(currentBrush !is null)
12079 				DeleteObject(currentBrush);
12080 
12081 			SelectObject(hdc, oldBmp);
12082 
12083 			if(windowDc)
12084 				ReleaseDC(hwnd, hdc);
12085 			else
12086 				DeleteDC(hdc);
12087 
12088 			if(window.paintingFinishedDg !is null)
12089 				window.paintingFinishedDg()();
12090 		}
12091 
12092 		bool windowDc;
12093 		HPEN originalPen;
12094 		HPEN currentPen;
12095 
12096 		Pen _activePen;
12097 
12098 		Color _outlineColor;
12099 
12100 		@property void pen(Pen p) {
12101 			_activePen = p;
12102 			_outlineColor = p.color;
12103 
12104 			HPEN pen;
12105 			if(p.color.a == 0) {
12106 				pen = GetStockObject(NULL_PEN);
12107 			} else {
12108 				int style = PS_SOLID;
12109 				final switch(p.style) {
12110 					case Pen.Style.Solid:
12111 						style = PS_SOLID;
12112 					break;
12113 					case Pen.Style.Dashed:
12114 						style = PS_DASH;
12115 					break;
12116 					case Pen.Style.Dotted:
12117 						style = PS_DOT;
12118 					break;
12119 				}
12120 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
12121 			}
12122 			auto orig = SelectObject(hdc, pen);
12123 			if(originalPen is null)
12124 				originalPen = orig;
12125 
12126 			if(currentPen !is null)
12127 				DeleteObject(currentPen);
12128 
12129 			currentPen = pen;
12130 
12131 			// the outline is like a foreground since it's done that way on X
12132 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
12133 
12134 		}
12135 
12136 		@property void rasterOp(RasterOp op) {
12137 			int mode;
12138 			final switch(op) {
12139 				case RasterOp.normal:
12140 					mode = R2_COPYPEN;
12141 				break;
12142 				case RasterOp.xor:
12143 					mode = R2_XORPEN;
12144 				break;
12145 			}
12146 			SetROP2(hdc, mode);
12147 		}
12148 
12149 		HBRUSH originalBrush;
12150 		HBRUSH currentBrush;
12151 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
12152 		@property void fillColor(Color c) {
12153 			if(c == _fillColor)
12154 				return;
12155 			_fillColor = c;
12156 			HBRUSH brush;
12157 			if(c.a == 0) {
12158 				brush = GetStockObject(HOLLOW_BRUSH);
12159 			} else {
12160 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12161 			}
12162 			auto orig = SelectObject(hdc, brush);
12163 			if(originalBrush is null)
12164 				originalBrush = orig;
12165 
12166 			if(currentBrush !is null)
12167 				DeleteObject(currentBrush);
12168 
12169 			currentBrush = brush;
12170 
12171 			// background color is NOT set because X doesn't draw text backgrounds
12172 			//   SetBkColor(hdc, RGB(255, 255, 255));
12173 		}
12174 
12175 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12176 			BITMAP bm;
12177 
12178 			HDC hdcMem = CreateCompatibleDC(hdc);
12179 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
12180 
12181 			GetObject(i.handle, bm.sizeof, &bm);
12182 
12183 			// or should I AlphaBlend!??!?!
12184 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
12185 
12186 			SelectObject(hdcMem, hbmOld);
12187 			DeleteDC(hdcMem);
12188 		}
12189 
12190 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12191 			BITMAP bm;
12192 
12193 			HDC hdcMem = CreateCompatibleDC(hdc);
12194 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
12195 
12196 			GetObject(s.handle, bm.sizeof, &bm);
12197 
12198 			version(CRuntime_DigitalMars) goto noalpha;
12199 
12200 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
12201 			if(s.enableAlpha) {
12202 				auto dw = w ? w : bm.bmWidth;
12203 				auto dh = h ? h : bm.bmHeight;
12204 				BLENDFUNCTION bf;
12205 				bf.BlendOp = AC_SRC_OVER;
12206 				bf.SourceConstantAlpha = 255;
12207 				bf.AlphaFormat = AC_SRC_ALPHA;
12208 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
12209 			} else {
12210 				noalpha:
12211 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
12212 			}
12213 
12214 			SelectObject(hdcMem, hbmOld);
12215 			DeleteDC(hdcMem);
12216 		}
12217 
12218 		Size textSize(scope const(char)[] text) {
12219 			bool dummyX;
12220 			if(text.length == 0) {
12221 				text = " ";
12222 				dummyX = true;
12223 			}
12224 			RECT rect;
12225 			WCharzBuffer buffer = WCharzBuffer(text);
12226 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
12227 			return Size(dummyX ? 0 : rect.right, rect.bottom);
12228 		}
12229 
12230 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
12231 			if(text.length && text[$-1] == '\n')
12232 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
12233 			if(text.length && text[$-1] == '\r')
12234 				text = text[0 .. $-1];
12235 
12236 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
12237 			if(x2 == 0 && y2 == 0) {
12238 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
12239 			} else {
12240 				RECT rect;
12241 				rect.left = x;
12242 				rect.top = y;
12243 				rect.right = x2;
12244 				rect.bottom = y2;
12245 
12246 				uint mode = DT_LEFT;
12247 				if(alignment & TextAlignment.Right)
12248 					mode = DT_RIGHT;
12249 				else if(alignment & TextAlignment.Center)
12250 					mode = DT_CENTER;
12251 
12252 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
12253 				if(alignment & TextAlignment.VerticalCenter)
12254 					mode |= DT_VCENTER | DT_SINGLELINE;
12255 
12256 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
12257 			}
12258 
12259 			/*
12260 			uint mode;
12261 
12262 			if(alignment & TextAlignment.Center)
12263 				mode = TA_CENTER;
12264 
12265 			SetTextAlign(hdc, mode);
12266 			*/
12267 		}
12268 
12269 		int fontHeight() {
12270 			TEXTMETRIC metric;
12271 			if(GetTextMetricsW(hdc, &metric)) {
12272 				return metric.tmHeight;
12273 			}
12274 
12275 			return 16; // idk just guessing here, maybe we should throw
12276 		}
12277 
12278 		void drawPixel(int x, int y) {
12279 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
12280 		}
12281 
12282 		// The basic shapes, outlined
12283 
12284 		void drawLine(int x1, int y1, int x2, int y2) {
12285 			MoveToEx(hdc, x1, y1, null);
12286 			LineTo(hdc, x2, y2);
12287 		}
12288 
12289 		void drawRectangle(int x, int y, int width, int height) {
12290 			// FIXME: with a wider pen this might not draw quite right. im not sure.
12291 			gdi.Rectangle(hdc, x, y, x + width, y + height);
12292 		}
12293 
12294 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
12295 			RoundRect(
12296 				hdc,
12297 				upperLeft.x, upperLeft.y,
12298 				lowerRight.x, lowerRight.y,
12299 				borderRadius, borderRadius
12300 			);
12301 		}
12302 
12303 		/// Arguments are the points of the bounding rectangle
12304 		void drawEllipse(int x1, int y1, int x2, int y2) {
12305 			Ellipse(hdc, x1, y1, x2, y2);
12306 		}
12307 
12308 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
12309 			//if(length > 360*64)
12310 				//length = 360*64;
12311 
12312 			if((start == 0 && length == 360*64)) {
12313 				drawEllipse(x1, y1, x1 + width, y1 + height);
12314 			} else {
12315 				import core.stdc.math;
12316 
12317 				bool clockwise = false;
12318 				if(length < 0) {
12319 					clockwise = true;
12320 					length = -length;
12321 				}
12322 
12323 				double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323;
12324 				double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323;
12325 
12326 				auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12327 				auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12328 				auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12329 				auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12330 
12331 				if(clockwise) {
12332 					auto t1 = c1;
12333 					auto t2 = c2;
12334 					c1 = c3;
12335 					c2 = c4;
12336 					c3 = t1;
12337 					c4 = t2;
12338 				}
12339 
12340 				//if(_activePen.color.a)
12341 					//Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12342 				//if(_fillColor.a)
12343 
12344 				Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12345 			}
12346 		}
12347 
12348 		void drawPolygon(Point[] vertexes) {
12349 			POINT[] points;
12350 			points.length = vertexes.length;
12351 
12352 			foreach(i, p; vertexes) {
12353 				points[i].x = p.x;
12354 				points[i].y = p.y;
12355 			}
12356 
12357 			Polygon(hdc, points.ptr, cast(int) points.length);
12358 		}
12359 	}
12360 
12361 
12362 	// Mix this into the SimpleWindow class
12363 	mixin template NativeSimpleWindowImplementation() {
12364 		int curHidden = 0; // counter
12365 		__gshared static bool[string] knownWinClasses;
12366 		static bool altPressed = false;
12367 
12368 		HANDLE oldCursor;
12369 
12370 		void hideCursor () {
12371 			if(curHidden == 0)
12372 				oldCursor = SetCursor(null);
12373 			++curHidden;
12374 		}
12375 
12376 		void showCursor () {
12377 			--curHidden;
12378 			if(curHidden == 0) {
12379 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
12380 			}
12381 		}
12382 
12383 
12384 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
12385 
12386 		void setMinSize (int minwidth, int minheight) {
12387 			minWidth = minwidth;
12388 			minHeight = minheight;
12389 		}
12390 		void setMaxSize (int maxwidth, int maxheight) {
12391 			maxWidth = maxwidth;
12392 			maxHeight = maxheight;
12393 		}
12394 
12395 		// FIXME i'm not sure that Windows has this functionality
12396 		// though it is nonessential anyway.
12397 		void setResizeGranularity (int granx, int grany) {}
12398 
12399 		ScreenPainter getPainter(bool manualInvalidations) {
12400 			return ScreenPainter(this, hwnd, manualInvalidations);
12401 		}
12402 
12403 		HBITMAP buffer;
12404 
12405 		void setTitle(string title) {
12406 			WCharzBuffer bfr = WCharzBuffer(title);
12407 			SetWindowTextW(hwnd, bfr.ptr);
12408 		}
12409 
12410 		string getTitle() {
12411 			auto len = GetWindowTextLengthW(hwnd);
12412 			if (!len)
12413 				return null;
12414 			wchar[256] tmpBuffer;
12415 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
12416 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12417 			auto str = buffer[0 .. len2];
12418 			return makeUtf8StringFromWindowsString(str);
12419 		}
12420 
12421 		void move(int x, int y) {
12422 			RECT rect;
12423 			GetWindowRect(hwnd, &rect);
12424 			// move it while maintaining the same size...
12425 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
12426 		}
12427 
12428 		void resize(int w, int h) {
12429 			RECT rect;
12430 			GetWindowRect(hwnd, &rect);
12431 
12432 			RECT client;
12433 			GetClientRect(hwnd, &client);
12434 
12435 			rect.right = rect.right - client.right + w;
12436 			rect.bottom = rect.bottom - client.bottom + h;
12437 
12438 			// same position, new size for the client rectangle
12439 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
12440 
12441 			updateOpenglViewportIfNeeded(w, h);
12442 		}
12443 
12444 		void moveResize (int x, int y, int w, int h) {
12445 			// what's given is the client rectangle, we need to adjust
12446 
12447 			RECT rect;
12448 			rect.left = x;
12449 			rect.top = y;
12450 			rect.right = w + x;
12451 			rect.bottom = h + y;
12452 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
12453 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
12454 
12455 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
12456 			updateOpenglViewportIfNeeded(w, h);
12457 			if (windowResized !is null) windowResized(w, h);
12458 		}
12459 
12460 		version(without_opengl) {} else {
12461 			HGLRC ghRC;
12462 			HDC ghDC;
12463 		}
12464 
12465 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
12466 			string cnamec;
12467 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
12468 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
12469 				cnamec = "DSimpleWindow";
12470 			} else {
12471 				cnamec = sdpyWindowClass;
12472 			}
12473 
12474 			WCharzBuffer cn = WCharzBuffer(cnamec);
12475 
12476 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
12477 
12478 			if(cnamec !in knownWinClasses) {
12479 				WNDCLASSEX wc;
12480 
12481 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
12482 				// to the object. Maybe.
12483 				wc.cbSize = wc.sizeof;
12484 				wc.cbClsExtra = 0;
12485 				wc.cbWndExtra = 0;
12486 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
12487 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
12488 				wc.hIcon = LoadIcon(hInstance, null);
12489 				wc.hInstance = hInstance;
12490 				wc.lpfnWndProc = &WndProc;
12491 				wc.lpszClassName = cn.ptr;
12492 				wc.hIconSm = null;
12493 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
12494 				if(!RegisterClassExW(&wc))
12495 					throw new WindowsApiException("RegisterClassExW", GetLastError());
12496 				knownWinClasses[cnamec] = true;
12497 			}
12498 
12499 			int style;
12500 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
12501 
12502 			// FIXME: windowType and customizationFlags
12503 			final switch(windowType) {
12504 				case WindowTypes.normal:
12505 					if(resizability == Resizability.fixedSize) {
12506 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
12507 					} else {
12508 						style = WS_OVERLAPPEDWINDOW;
12509 					}
12510 				break;
12511 				case WindowTypes.undecorated:
12512 					style = WS_POPUP | WS_SYSMENU;
12513 				break;
12514 				case WindowTypes.eventOnly:
12515 					_hidden = true;
12516 				break;
12517 				case WindowTypes.dropdownMenu:
12518 				case WindowTypes.popupMenu:
12519 				case WindowTypes.notification:
12520 					style = WS_POPUP;
12521 					flags |= WS_EX_NOACTIVATE;
12522 				break;
12523 				case WindowTypes.dialog:
12524 					style = WS_OVERLAPPEDWINDOW;
12525 				break;
12526 				case WindowTypes.nestedChild:
12527 					style = WS_CHILD;
12528 				break;
12529 				case WindowTypes.minimallyWrapped:
12530 					assert(0, "construct minimally wrapped through the other ctor overlad");
12531 			}
12532 
12533 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12534 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
12535 
12536 			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
12537 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
12538 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
12539 
12540 			if(!hwnd)
12541 				throw new WindowsApiException("CreateWindowEx", GetLastError());
12542 
12543 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12544 				setOpacity(255);
12545 
12546 			SimpleWindow.nativeMapping[hwnd] = this;
12547 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
12548 
12549 			if(windowType == WindowTypes.eventOnly)
12550 				return;
12551 
12552 			HDC hdc = GetDC(hwnd);
12553 
12554 			if(!hdc)
12555 				throw new WindowsApiException("GetDC", GetLastError());
12556 
12557 			version(without_opengl) {}
12558 			else {
12559 				if(opengl == OpenGlOptions.yes) {
12560 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12561 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12562 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
12563 					ghDC = hdc;
12564 					PIXELFORMATDESCRIPTOR pfd;
12565 
12566 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
12567 					pfd.nVersion = 1;
12568 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
12569 					pfd.dwLayerMask = PFD_MAIN_PLANE;
12570 					pfd.iPixelType = PFD_TYPE_RGBA;
12571 					pfd.cColorBits = 24;
12572 					pfd.cDepthBits = 24;
12573 					pfd.cAccumBits = 0;
12574 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
12575 
12576 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
12577 
12578 					if (pixelformat == 0)
12579 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
12580 
12581 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
12582 						throw new WindowsApiException("SetPixelFormat", GetLastError());
12583 
12584 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
12585 						// windoze is idiotic: we have to have OpenGL context to get function addresses
12586 						// so we will create fake context to get that stupid address
12587 						auto tmpcc = wglCreateContext(ghDC);
12588 						if (tmpcc !is null) {
12589 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
12590 							wglMakeCurrent(ghDC, tmpcc);
12591 							wglInitOtherFunctions();
12592 						}
12593 					}
12594 
12595 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
12596 						int[9] contextAttribs = [
12597 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
12598 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
12599 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
12600 							// for modern context, set "forward compatibility" flag too
12601 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
12602 							0/*None*/,
12603 						];
12604 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
12605 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
12606 							// activate fallback mode
12607 							// 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;
12608 							ghRC = wglCreateContext(ghDC);
12609 						}
12610 						if (ghRC is null)
12611 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
12612 					} else {
12613 						// try to do at least something
12614 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
12615 							sdpyOpenGLContextVersion = 0;
12616 							ghRC = wglCreateContext(ghDC);
12617 						}
12618 						if (ghRC is null)
12619 							throw new WindowsApiException("wglCreateContext", GetLastError());
12620 					}
12621 				}
12622 			}
12623 
12624 			if(opengl == OpenGlOptions.no) {
12625 				buffer = CreateCompatibleBitmap(hdc, width, height);
12626 
12627 				auto hdcBmp = CreateCompatibleDC(hdc);
12628 				// make sure it's filled with a blank slate
12629 				auto oldBmp = SelectObject(hdcBmp, buffer);
12630 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
12631 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
12632 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
12633 				SelectObject(hdcBmp, oldBmp);
12634 				SelectObject(hdcBmp, oldBrush);
12635 				SelectObject(hdcBmp, oldPen);
12636 				DeleteDC(hdcBmp);
12637 
12638 				bmpWidth = width;
12639 				bmpHeight = height;
12640 
12641 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
12642 			}
12643 
12644 			// We want the window's client area to match the image size
12645 			RECT rcClient, rcWindow;
12646 			POINT ptDiff;
12647 			GetClientRect(hwnd, &rcClient);
12648 			GetWindowRect(hwnd, &rcWindow);
12649 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
12650 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
12651 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
12652 
12653 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
12654 				ShowWindow(hwnd, SW_SHOWNORMAL);
12655 			} else {
12656 				_hidden = true;
12657 			}
12658 			this._visibleForTheFirstTimeCalled = false; // hack!
12659 		}
12660 
12661 
12662 		void dispose() {
12663 			if(buffer)
12664 				DeleteObject(buffer);
12665 		}
12666 
12667 		void closeWindow() {
12668 			if(ghRC) {
12669 				wglDeleteContext(ghRC);
12670 				ghRC = null;
12671 			}
12672 			DestroyWindow(hwnd);
12673 		}
12674 
12675 		bool setOpacity(ubyte alpha) {
12676 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
12677 		}
12678 
12679 		HANDLE currentCursor;
12680 
12681 		// returns zero if it recognized the event
12682 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
12683 			MouseEvent mouse;
12684 
12685 			void mouseEvent(bool isScreen, ulong mods) {
12686 				auto x = LOWORD(lParam);
12687 				auto y = HIWORD(lParam);
12688 				if(isScreen) {
12689 					POINT p;
12690 					p.x = x;
12691 					p.y = y;
12692 					ScreenToClient(hwnd, &p);
12693 					x = cast(ushort) p.x;
12694 					y = cast(ushort) p.y;
12695 				}
12696 
12697 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
12698 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
12699 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
12700 				}
12701 
12702 				mouse.x = x + offsetX;
12703 				mouse.y = y + offsetY;
12704 
12705 				wind.mdx(mouse);
12706 				mouse.modifierState = cast(int) mods;
12707 				mouse.window = wind;
12708 
12709 				if(wind.handleMouseEvent)
12710 					wind.handleMouseEvent(mouse);
12711 			}
12712 
12713 			switch(msg) {
12714 				case WM_GETMINMAXINFO:
12715 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12716 
12717 					if(wind.minWidth > 0) {
12718 						RECT rect;
12719 						rect.left = 100;
12720 						rect.top = 100;
12721 						rect.right = wind.minWidth + 100;
12722 						rect.bottom = wind.minHeight + 100;
12723 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12724 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12725 
12726 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12727 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12728 					}
12729 
12730 					if(wind.maxWidth < int.max) {
12731 						RECT rect;
12732 						rect.left = 100;
12733 						rect.top = 100;
12734 						rect.right = wind.maxWidth + 100;
12735 						rect.bottom = wind.maxHeight + 100;
12736 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12737 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12738 
12739 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12740 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12741 					}
12742 				break;
12743 				case WM_CHAR:
12744 					wchar c = cast(wchar) wParam;
12745 					if(wind.handleCharEvent)
12746 						wind.handleCharEvent(cast(dchar) c);
12747 				break;
12748 				  case WM_SETFOCUS:
12749 				  case WM_KILLFOCUS:
12750 					wind._focused = (msg == WM_SETFOCUS);
12751 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12752 					if(wind.onFocusChange)
12753 						wind.onFocusChange(msg == WM_SETFOCUS);
12754 				  break;
12755 
12756 				case WM_SYSKEYDOWN:
12757 					goto case;
12758 				case WM_SYSKEYUP:
12759 					if(lParam & (1 << 29)) {
12760 						goto case;
12761 					} else {
12762 						// no window has keyboard focus
12763 						goto default;
12764 					}
12765 				case WM_KEYDOWN:
12766 				case WM_KEYUP:
12767 					KeyEvent ev;
12768 					ev.key = cast(Key) wParam;
12769 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12770 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12771 
12772 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12773 
12774 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12775 						ev.modifierState |= ModifierState.shift;
12776 					//k8: this doesn't work; thanks for nothing, windows
12777 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12778 						ev.modifierState |= ModifierState.alt;*/
12779 					// this never seems to actually be set
12780 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12781 
12782 					if (wParam == 0x12) {
12783 						altPressed = (msg == WM_SYSKEYDOWN);
12784 					}
12785 
12786 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12787 						altPressed = false;
12788 					}
12789 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12790 
12791 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12792 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12793 						ev.modifierState |= ModifierState.ctrl;
12794 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12795 						ev.modifierState |= ModifierState.windows;
12796 					if(GetKeyState(Key.NumLock))
12797 						ev.modifierState |= ModifierState.numLock;
12798 					if(GetKeyState(Key.CapsLock))
12799 						ev.modifierState |= ModifierState.capsLock;
12800 
12801 					/+
12802 					// we always want to send the character too, so let's convert it
12803 					ubyte[256] state;
12804 					wchar[16] buffer;
12805 					GetKeyboardState(state.ptr);
12806 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12807 
12808 					foreach(dchar d; buffer) {
12809 						ev.character = d;
12810 						break;
12811 					}
12812 					+/
12813 
12814 					ev.window = wind;
12815 					if(wind.handleKeyEvent)
12816 						wind.handleKeyEvent(ev);
12817 				break;
12818 				case 0x020a /*WM_MOUSEWHEEL*/:
12819 					// send click
12820 					mouse.type = cast(MouseEventType) 1;
12821 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12822 					mouseEvent(true, LOWORD(wParam));
12823 
12824 					// also send release
12825 					mouse.type = cast(MouseEventType) 2;
12826 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12827 					mouseEvent(true, LOWORD(wParam));
12828 				break;
12829 				case WM_MOUSEMOVE:
12830 					mouse.type = cast(MouseEventType) 0;
12831 					mouseEvent(false, wParam);
12832 				break;
12833 				case WM_LBUTTONDOWN:
12834 				case WM_LBUTTONDBLCLK:
12835 					mouse.type = cast(MouseEventType) 1;
12836 					mouse.button = MouseButton.left;
12837 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12838 					mouseEvent(false, wParam);
12839 				break;
12840 				case WM_LBUTTONUP:
12841 					mouse.type = cast(MouseEventType) 2;
12842 					mouse.button = MouseButton.left;
12843 					mouseEvent(false, wParam);
12844 				break;
12845 				case WM_RBUTTONDOWN:
12846 				case WM_RBUTTONDBLCLK:
12847 					mouse.type = cast(MouseEventType) 1;
12848 					mouse.button = MouseButton.right;
12849 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12850 					mouseEvent(false, wParam);
12851 				break;
12852 				case WM_RBUTTONUP:
12853 					mouse.type = cast(MouseEventType) 2;
12854 					mouse.button = MouseButton.right;
12855 					mouseEvent(false, wParam);
12856 				break;
12857 				case WM_MBUTTONDOWN:
12858 				case WM_MBUTTONDBLCLK:
12859 					mouse.type = cast(MouseEventType) 1;
12860 					mouse.button = MouseButton.middle;
12861 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12862 					mouseEvent(false, wParam);
12863 				break;
12864 				case WM_MBUTTONUP:
12865 					mouse.type = cast(MouseEventType) 2;
12866 					mouse.button = MouseButton.middle;
12867 					mouseEvent(false, wParam);
12868 				break;
12869 				case WM_XBUTTONDOWN:
12870 				case WM_XBUTTONDBLCLK:
12871 					mouse.type = cast(MouseEventType) 1;
12872 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12873 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12874 					mouseEvent(false, wParam);
12875 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12876 				case WM_XBUTTONUP:
12877 					mouse.type = cast(MouseEventType) 2;
12878 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12879 					mouseEvent(false, wParam);
12880 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12881 
12882 				default: return 1;
12883 			}
12884 			return 0;
12885 		}
12886 
12887 		HWND hwnd;
12888 		private int oldWidth;
12889 		private int oldHeight;
12890 		private bool inSizeMove;
12891 
12892 		/++
12893 			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.
12894 
12895 			History:
12896 				Added November 23, 2021
12897 
12898 				Not fully stable, may be moved out of the impl struct.
12899 
12900 				Default value changed to `true` on February 15, 2021
12901 		+/
12902 		bool doLiveResizing = true;
12903 
12904 		package int bmpWidth;
12905 		package int bmpHeight;
12906 
12907 		// the extern(Windows) wndproc should just forward to this
12908 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12909 		try {
12910 			assert(hwnd is this.hwnd);
12911 
12912 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12913 			switch(msg) {
12914 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12915 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12916 					// The main things we can do are select, execute, close, or ignore
12917 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12918 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12919 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12920 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12921 
12922 					// returns the value in the *high order word* of the return value
12923 					// hence the << 16
12924 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12925 				case WM_SETCURSOR:
12926 					if(cast(HWND) wParam !is hwnd)
12927 						return 0; // further processing elsewhere
12928 
12929 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12930 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12931 						return 1;
12932 					} else {
12933 						return DefWindowProc(hwnd, msg, wParam, lParam);
12934 					}
12935 				//break;
12936 
12937 				case WM_CLOSE:
12938 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12939 				break;
12940 				case WM_DESTROY:
12941 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12942 					SimpleWindow.nativeMapping.remove(hwnd);
12943 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12944 
12945 					bool anyImportant = false;
12946 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12947 						if(w.beingOpenKeepsAppOpen) {
12948 							anyImportant = true;
12949 							break;
12950 						}
12951 					if(!anyImportant) {
12952 						PostQuitMessage(0);
12953 					}
12954 				break;
12955 				case 0x02E0 /*WM_DPICHANGED*/:
12956 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12957 
12958 					RECT* prcNewWindow = cast(RECT*)lParam;
12959 					// docs say this is the recommended position and we should honor it
12960 					SetWindowPos(hwnd,
12961 							null,
12962 							prcNewWindow.left,
12963 							prcNewWindow.top,
12964 							prcNewWindow.right - prcNewWindow.left,
12965 							prcNewWindow.bottom - prcNewWindow.top,
12966 							SWP_NOZORDER | SWP_NOACTIVATE);
12967 
12968 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12969 					// im not sure it is completely correct
12970 					// but without it the tabs and such do look weird as things change.
12971 					if(SystemParametersInfoForDpi) {
12972 						LOGFONT lfText;
12973 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12974 						HFONT hFontNew = CreateFontIndirect(&lfText);
12975 						if (hFontNew)
12976 						{
12977 							//DeleteObject(hFontOld);
12978 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12979 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12980 								return TRUE;
12981 							}
12982 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12983 						}
12984 					}
12985 
12986 					if(this.onDpiChanged)
12987 						this.onDpiChanged();
12988 				break;
12989 				case WM_ENTERIDLE:
12990 					// when a menu is up, it stops normal event processing (modal message loop)
12991 					// but this at least gives us a chance to SOMETIMES catch up
12992 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12993 					SimpleWindow.processAllCustomEvents;
12994 					SimpleWindow.processAllCustomEvents;
12995 					SleepEx(0, true);
12996 					break;
12997 				case WM_SIZE:
12998 					if(wParam == 1 /* SIZE_MINIMIZED */)
12999 						break;
13000 					_width = LOWORD(lParam);
13001 					_height = HIWORD(lParam);
13002 
13003 					// I want to avoid tearing in the windows (my code is inefficient
13004 					// so this is a hack around that) so while sizing, we don't trigger,
13005 					// but we do want to trigger on events like mazimize.
13006 					if(!inSizeMove || doLiveResizing)
13007 						goto size_changed;
13008 				break;
13009 				/+
13010 				case WM_SIZING:
13011 					writeln("size");
13012 				break;
13013 				+/
13014 				// I don't like the tearing I get when redrawing on WM_SIZE
13015 				// (I know there's other ways to fix that but I don't like that behavior anyway)
13016 				// so instead it is going to redraw only at the end of a size.
13017 				case 0x0231: /* WM_ENTERSIZEMOVE */
13018 					inSizeMove = true;
13019 				break;
13020 				case 0x0232: /* WM_EXITSIZEMOVE */
13021 					inSizeMove = false;
13022 
13023 					size_changed:
13024 
13025 					// nothing relevant changed, don't bother redrawing
13026 					if(oldWidth == _width && oldHeight == _height) {
13027 						if(msg == 0x0232)
13028 							goto finalize_resize;
13029 						break;
13030 					}
13031 
13032 					// note: OpenGL windows don't use a backing bmp, so no need to change them
13033 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
13034 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
13035 						// gotta get the double buffer bmp to match the window
13036 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
13037 
13038 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
13039 						if(resizability != Resizability.automaticallyScaleIfPossible)
13040 						if(_width > bmpWidth || _height > bmpHeight) {
13041 							auto hdc = GetDC(hwnd);
13042 							auto oldBuffer = buffer;
13043 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
13044 
13045 							auto hdcBmp = CreateCompatibleDC(hdc);
13046 							auto oldBmp = SelectObject(hdcBmp, buffer);
13047 
13048 							auto hdcOldBmp = CreateCompatibleDC(hdc);
13049 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
13050 
13051 							/+
13052 							RECT r;
13053 							r.left = 0;
13054 							r.top = 0;
13055 							r.right = width;
13056 							r.bottom = height;
13057 							auto c = Color.green;
13058 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
13059 							FillRect(hdcBmp, &r, brush);
13060 							DeleteObject(brush);
13061 							+/
13062 
13063 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
13064 
13065 							bmpWidth = _width;
13066 							bmpHeight = _height;
13067 
13068 							SelectObject(hdcOldBmp, oldOldBmp);
13069 							DeleteDC(hdcOldBmp);
13070 
13071 							SelectObject(hdcBmp, oldBmp);
13072 							DeleteDC(hdcBmp);
13073 
13074 							ReleaseDC(hwnd, hdc);
13075 
13076 							DeleteObject(oldBuffer);
13077 						}
13078 					}
13079 
13080 					updateOpenglViewportIfNeeded(_width, _height);
13081 
13082 					if(resizability != Resizability.automaticallyScaleIfPossible)
13083 					if(windowResized !is null)
13084 						windowResized(_width, _height);
13085 
13086 					/+
13087 					if(inSizeMove) {
13088 						// SimpleWindow.processAllCustomEvents();
13089 						// SimpleWindow.processAllCustomEvents();
13090 
13091 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13092 						//sdpyPrintDebugString("redraw b");
13093 					} else {
13094 					+/ {
13095 						finalize_resize:
13096 						// when it is all done, make sure everything is freshly drawn or there might be
13097 						// weird bugs left.
13098 						SimpleWindow.processAllCustomEvents();
13099 						SimpleWindow.processAllCustomEvents();
13100 
13101 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13102 						// sdpyPrintDebugString("redraw");
13103 					}
13104 
13105 					oldWidth = this._width;
13106 					oldHeight = this._height;
13107 				break;
13108 				case WM_ERASEBKGND:
13109 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
13110 					if (!this._visibleForTheFirstTimeCalled) {
13111 						this._visibleForTheFirstTimeCalled = true;
13112 						if (this.visibleForTheFirstTime !is null) {
13113 							this.visibleForTheFirstTime();
13114 						}
13115 					}
13116 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
13117 					version(without_opengl) {} else {
13118 						if (openglMode == OpenGlOptions.yes) return 1;
13119 					}
13120 					// call windows default handler, so it can paint standard controls
13121 					goto default;
13122 				case WM_CTLCOLORBTN:
13123 				case WM_CTLCOLORSTATIC:
13124 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
13125 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
13126 					GetSysColorBrush(COLOR_3DFACE);
13127 				//break;
13128 				case WM_SHOWWINDOW:
13129 					this._visible = (wParam != 0);
13130 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
13131 						this._visibleForTheFirstTimeCalled = true;
13132 						if (this.visibleForTheFirstTime !is null) {
13133 							this.visibleForTheFirstTime();
13134 						}
13135 					}
13136 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
13137 					break;
13138 				case WM_PAINT: {
13139 					if (!this._visibleForTheFirstTimeCalled) {
13140 						this._visibleForTheFirstTimeCalled = true;
13141 						if (this.visibleForTheFirstTime !is null) {
13142 							this.visibleForTheFirstTime();
13143 						}
13144 					}
13145 
13146 					BITMAP bm;
13147 					PAINTSTRUCT ps;
13148 
13149 					HDC hdc = BeginPaint(hwnd, &ps);
13150 
13151 					if(openglMode == OpenGlOptions.no) {
13152 
13153 						HDC hdcMem = CreateCompatibleDC(hdc);
13154 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
13155 
13156 						GetObject(buffer, bm.sizeof, &bm);
13157 
13158 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
13159 						if(resizability == Resizability.automaticallyScaleIfPossible)
13160 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
13161 						else
13162 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
13163 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
13164 
13165 						SelectObject(hdcMem, hbmOld);
13166 						DeleteDC(hdcMem);
13167 						EndPaint(hwnd, &ps);
13168 					} else {
13169 						EndPaint(hwnd, &ps);
13170 						version(without_opengl) {} else
13171 							redrawOpenGlSceneSoon();
13172 					}
13173 				} break;
13174 				  default:
13175 					return DefWindowProc(hwnd, msg, wParam, lParam);
13176 			}
13177 			 return 0;
13178 
13179 		}
13180 		catch(Throwable t) {
13181 			sdpyPrintDebugString(t.toString);
13182 			return 0;
13183 		}
13184 		}
13185 	}
13186 
13187 	mixin template NativeImageImplementation() {
13188 		HBITMAP handle;
13189 		ubyte* rawData;
13190 
13191 	final:
13192 
13193 		Color getPixel(int x, int y) @system {
13194 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13195 			// remember, bmps are upside down
13196 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13197 
13198 			Color c;
13199 			if(enableAlpha)
13200 				c.a = rawData[offset + 3];
13201 			else
13202 				c.a = 255;
13203 			c.b = rawData[offset + 0];
13204 			c.g = rawData[offset + 1];
13205 			c.r = rawData[offset + 2];
13206 			c.unPremultiply();
13207 			return c;
13208 		}
13209 
13210 		void setPixel(int x, int y, Color c) @system {
13211 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13212 			// remember, bmps are upside down
13213 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13214 
13215 			if(enableAlpha)
13216 				c.premultiply();
13217 
13218 			rawData[offset + 0] = c.b;
13219 			rawData[offset + 1] = c.g;
13220 			rawData[offset + 2] = c.r;
13221 			if(enableAlpha)
13222 				rawData[offset + 3] = c.a;
13223 		}
13224 
13225 		void convertToRgbaBytes(ubyte[] where) @system {
13226 			assert(where.length == this.width * this.height * 4);
13227 
13228 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13229 			int idx = 0;
13230 			int offset = itemsPerLine * (height - 1);
13231 			// remember, bmps are upside down
13232 			for(int y = height - 1; y >= 0; y--) {
13233 				auto offsetStart = offset;
13234 				for(int x = 0; x < width; x++) {
13235 					where[idx + 0] = rawData[offset + 2]; // r
13236 					where[idx + 1] = rawData[offset + 1]; // g
13237 					where[idx + 2] = rawData[offset + 0]; // b
13238 					if(enableAlpha) {
13239 						where[idx + 3] = rawData[offset + 3]; // a
13240 						unPremultiplyRgba(where[idx .. idx + 4]);
13241 						offset++;
13242 					} else
13243 						where[idx + 3] = 255; // a
13244 					idx += 4;
13245 					offset += 3;
13246 				}
13247 
13248 				offset = offsetStart - itemsPerLine;
13249 			}
13250 		}
13251 
13252 		void setFromRgbaBytes(in ubyte[] what) @system {
13253 			assert(what.length == this.width * this.height * 4);
13254 
13255 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
13256 			int idx = 0;
13257 			int offset = itemsPerLine * (height - 1);
13258 			// remember, bmps are upside down
13259 			for(int y = height - 1; y >= 0; y--) {
13260 				auto offsetStart = offset;
13261 				for(int x = 0; x < width; x++) {
13262 					if(enableAlpha) {
13263 						auto a = what[idx + 3];
13264 
13265 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
13266 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
13267 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
13268 						rawData[offset + 3] = a; // a
13269 						//premultiplyBgra(rawData[offset .. offset + 4]);
13270 						offset++;
13271 					} else {
13272 						rawData[offset + 2] = what[idx + 0]; // r
13273 						rawData[offset + 1] = what[idx + 1]; // g
13274 						rawData[offset + 0] = what[idx + 2]; // b
13275 					}
13276 					idx += 4;
13277 					offset += 3;
13278 				}
13279 
13280 				offset = offsetStart - itemsPerLine;
13281 			}
13282 		}
13283 
13284 
13285 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
13286 			BITMAPINFO infoheader;
13287 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
13288 			infoheader.bmiHeader.biWidth = width;
13289 			infoheader.bmiHeader.biHeight = height;
13290 			infoheader.bmiHeader.biPlanes = 1;
13291 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
13292 			infoheader.bmiHeader.biCompression = BI_RGB;
13293 
13294 			handle = CreateDIBSection(
13295 				null,
13296 				&infoheader,
13297 				DIB_RGB_COLORS,
13298 				cast(void**) &rawData,
13299 				null,
13300 				0);
13301 			if(handle is null)
13302 				throw new WindowsApiException("create image failed", GetLastError());
13303 
13304 		}
13305 
13306 		void dispose() {
13307 			DeleteObject(handle);
13308 		}
13309 	}
13310 
13311 	enum KEY_ESCAPE = 27;
13312 }
13313 
13314 version(Emscripten) {
13315 	alias int delegate(void*) NativeEventHandler;
13316 	alias void* NativeWindowHandle;
13317 
13318 	mixin template NativeSimpleWindowImplementation() { }
13319 	mixin template NativeScreenPainterImplementation() { }
13320 	mixin template NativeImageImplementation() { }
13321 }
13322 
13323 version(X11) {
13324 	/// This is the default font used. You might change this before doing anything else with
13325 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
13326 	/// for cross-platform compatibility.
13327 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13328 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13329 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
13330 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
13331 
13332 	alias int delegate(XEvent) NativeEventHandler;
13333 	alias Window NativeWindowHandle;
13334 
13335 	enum KEY_ESCAPE = 9;
13336 
13337 	mixin template NativeScreenPainterImplementation() {
13338 		Display* display;
13339 		Drawable d;
13340 		Drawable destiny;
13341 
13342 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
13343 		GC gc;
13344 
13345 		__gshared bool fontAttempted;
13346 
13347 		__gshared XFontStruct* defaultfont;
13348 		__gshared XFontSet defaultfontset;
13349 
13350 		XFontStruct* font;
13351 		XFontSet fontset;
13352 
13353 		void create(PaintingHandle window) {
13354 			this.display = XDisplayConnection.get();
13355 
13356 			Drawable buffer = None;
13357 			if(auto sw = cast(SimpleWindow) this.window) {
13358 				buffer = sw.impl.buffer;
13359 				this.destiny = cast(Drawable) window;
13360 			} else {
13361 				buffer = cast(Drawable) window;
13362 				this.destiny = None;
13363 			}
13364 
13365 			this.d = cast(Drawable) buffer;
13366 
13367 			auto dgc = DefaultGC(display, DefaultScreen(display));
13368 
13369 			this.gc = XCreateGC(display, d, 0, null);
13370 
13371 			XCopyGC(display, dgc, 0xffffffff, this.gc);
13372 
13373 			ensureDefaultFontLoaded();
13374 
13375 			font = defaultfont;
13376 			fontset = defaultfontset;
13377 
13378 			if(font) {
13379 				XSetFont(display, gc, font.fid);
13380 			}
13381 		}
13382 
13383 		static void ensureDefaultFontLoaded() {
13384 			if(!fontAttempted) {
13385 				auto display = XDisplayConnection.get;
13386 				auto font = XLoadQueryFont(display, xfontstr.ptr);
13387 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
13388 				if(font is null) {
13389 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
13390 					font = XLoadQueryFont(display, xfontstr.ptr);
13391 				}
13392 
13393 				char** lol;
13394 				int lol2;
13395 				char* lol3;
13396 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
13397 
13398 				fontAttempted = true;
13399 
13400 				defaultfont = font;
13401 				defaultfontset = fontset;
13402 			}
13403 		}
13404 
13405 		arsd.color.Rectangle _clipRectangle;
13406 		void setClipRectangle(int x, int y, int width, int height) {
13407 			auto old = _clipRectangle;
13408 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
13409 			if(old == _clipRectangle)
13410 				return;
13411 
13412 			if(width == 0 || height == 0) {
13413 				XSetClipMask(display, gc, None);
13414 
13415 				if(xrenderPicturePainter) {
13416 
13417 					XRectangle[1] rects;
13418 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
13419 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13420 				}
13421 
13422 				version(with_xft) {
13423 					if(xftFont is null || xftDraw is null)
13424 						return;
13425 					XftDrawSetClip(xftDraw, null);
13426 				}
13427 			} else {
13428 				XRectangle[1] rects;
13429 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
13430 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
13431 
13432 				if(xrenderPicturePainter)
13433 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13434 
13435 				version(with_xft) {
13436 					if(xftFont is null || xftDraw is null)
13437 						return;
13438 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
13439 				}
13440 			}
13441 		}
13442 
13443 		version(with_xft) {
13444 			XftFont* xftFont;
13445 			XftDraw* xftDraw;
13446 
13447 			XftColor xftColor;
13448 
13449 			void updateXftColor() {
13450 				if(xftFont is null)
13451 					return;
13452 
13453 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
13454 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
13455 
13456 				XftColorAllocValue(
13457 					display,
13458 					DefaultVisual(display, DefaultScreen(display)),
13459 					DefaultColormap(display, 0),
13460 					&colorIn,
13461 					&xftColor
13462 				);
13463 			}
13464 		}
13465 
13466 		private OperatingSystemFont _activeFont;
13467 		void setFont(OperatingSystemFont font) {
13468 			_activeFont = font;
13469 			version(with_xft) {
13470 				if(font && font.isXft && font.xftFont)
13471 					this.xftFont = font.xftFont;
13472 				else
13473 					this.xftFont = null;
13474 
13475 				if(this.xftFont) {
13476 					if(xftDraw is null) {
13477 						xftDraw = XftDrawCreate(
13478 							display,
13479 							d,
13480 							DefaultVisual(display, DefaultScreen(display)),
13481 							DefaultColormap(display, 0)
13482 						);
13483 
13484 						updateXftColor();
13485 					}
13486 
13487 					return;
13488 				}
13489 			}
13490 
13491 			if(font && font.font) {
13492 				this.font = font.font;
13493 				this.fontset = font.fontset;
13494 				XSetFont(display, gc, font.font.fid);
13495 			} else {
13496 				this.font = defaultfont;
13497 				this.fontset = defaultfontset;
13498 			}
13499 
13500 		}
13501 
13502 		private Picture xrenderPicturePainter;
13503 
13504 		bool manualInvalidations;
13505 		void invalidateRect(Rectangle invalidRect) {
13506 			// FIXME if manualInvalidations
13507 		}
13508 
13509 		void dispose() {
13510 			this.rasterOp = RasterOp.normal;
13511 
13512 			if(xrenderPicturePainter) {
13513 				XRenderFreePicture(display, xrenderPicturePainter);
13514 				xrenderPicturePainter = None;
13515 			}
13516 
13517 			// FIXME: this.window.width/height is probably wrong
13518 
13519 			// src x,y     then dest x, y
13520 			if(destiny != None) {
13521 				// FIXME: if manual invalidations we can actually only copy some of the area.
13522 				// if(manualInvalidations)
13523 				XSetClipMask(display, gc, None);
13524 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
13525 			}
13526 
13527 			XFreeGC(display, gc);
13528 
13529 			version(with_xft)
13530 			if(xftDraw) {
13531 				XftDrawDestroy(xftDraw);
13532 				xftDraw = null;
13533 			}
13534 
13535 			/+
13536 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
13537 			if(font && font !is defaultfont) {
13538 				XFreeFont(display, font);
13539 				font = null;
13540 			}
13541 			if(fontset && fontset !is defaultfontset) {
13542 				XFreeFontSet(display, fontset);
13543 				fontset = null;
13544 			}
13545 			+/
13546 			XFlush(display);
13547 
13548 			if(window.paintingFinishedDg !is null)
13549 				window.paintingFinishedDg()();
13550 		}
13551 
13552 		bool backgroundIsNotTransparent = true;
13553 		bool foregroundIsNotTransparent = true;
13554 
13555 		bool _penInitialized = false;
13556 		Pen _activePen;
13557 
13558 		Color _outlineColor;
13559 		Color _fillColor;
13560 
13561 		@property void pen(Pen p) {
13562 			if(_penInitialized && p == _activePen) {
13563 				return;
13564 			}
13565 			_penInitialized = true;
13566 			_activePen = p;
13567 			_outlineColor = p.color;
13568 
13569 			int style;
13570 
13571 			byte dashLength;
13572 
13573 			final switch(p.style) {
13574 				case Pen.Style.Solid:
13575 					style = 0 /*LineSolid*/;
13576 				break;
13577 				case Pen.Style.Dashed:
13578 					style = 1 /*LineOnOffDash*/;
13579 					dashLength = 4;
13580 				break;
13581 				case Pen.Style.Dotted:
13582 					style = 1 /*LineOnOffDash*/;
13583 					dashLength = 1;
13584 				break;
13585 			}
13586 
13587 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
13588 			if(dashLength)
13589 				XSetDashes(display, gc, 0, &dashLength, 1);
13590 
13591 			if(p.color.a == 0) {
13592 				foregroundIsNotTransparent = false;
13593 				return;
13594 			}
13595 
13596 			foregroundIsNotTransparent = true;
13597 
13598 			XSetForeground(display, gc, colorToX(p.color, display));
13599 
13600 			version(with_xft)
13601 				updateXftColor();
13602 		}
13603 
13604 		RasterOp _currentRasterOp;
13605 		bool _currentRasterOpInitialized = false;
13606 		@property void rasterOp(RasterOp op) {
13607 			if(_currentRasterOpInitialized && _currentRasterOp == op)
13608 				return;
13609 			_currentRasterOp = op;
13610 			_currentRasterOpInitialized = true;
13611 			int mode;
13612 			final switch(op) {
13613 				case RasterOp.normal:
13614 					mode = GXcopy;
13615 				break;
13616 				case RasterOp.xor:
13617 					mode = GXxor;
13618 				break;
13619 			}
13620 			XSetFunction(display, gc, mode);
13621 		}
13622 
13623 
13624 		bool _fillColorInitialized = false;
13625 
13626 		@property void fillColor(Color c) {
13627 			if(_fillColorInitialized && _fillColor == c)
13628 				return; // already good, no need to waste time calling it
13629 			_fillColor = c;
13630 			_fillColorInitialized = true;
13631 			if(c.a == 0) {
13632 				backgroundIsNotTransparent = false;
13633 				return;
13634 			}
13635 
13636 			backgroundIsNotTransparent = true;
13637 
13638 			XSetBackground(display, gc, colorToX(c, display));
13639 
13640 		}
13641 
13642 		void swapColors() {
13643 			auto tmp = _fillColor;
13644 			fillColor = _outlineColor;
13645 			auto newPen = _activePen;
13646 			newPen.color = tmp;
13647 			pen(newPen);
13648 		}
13649 
13650 		uint colorToX(Color c, Display* display) {
13651 			auto visual = DefaultVisual(display, DefaultScreen(display));
13652 			import core.bitop;
13653 			uint color = 0;
13654 			{
13655 			auto startBit = bsf(visual.red_mask);
13656 			auto lastBit = bsr(visual.red_mask);
13657 			auto r = cast(uint) c.r;
13658 			r >>= 7 - (lastBit - startBit);
13659 			r <<= startBit;
13660 			color |= r;
13661 			}
13662 			{
13663 			auto startBit = bsf(visual.green_mask);
13664 			auto lastBit = bsr(visual.green_mask);
13665 			auto g = cast(uint) c.g;
13666 			g >>= 7 - (lastBit - startBit);
13667 			g <<= startBit;
13668 			color |= g;
13669 			}
13670 			{
13671 			auto startBit = bsf(visual.blue_mask);
13672 			auto lastBit = bsr(visual.blue_mask);
13673 			auto b = cast(uint) c.b;
13674 			b >>= 7 - (lastBit - startBit);
13675 			b <<= startBit;
13676 			color |= b;
13677 			}
13678 
13679 
13680 
13681 			return color;
13682 		}
13683 
13684 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
13685 			// source x, source y
13686 			if(ix >= i.width) return;
13687 			if(iy >= i.height) return;
13688 			if(ix + w > i.width) w = i.width - ix;
13689 			if(iy + h > i.height) h = i.height - iy;
13690 			if(i.usingXshm)
13691 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
13692 			else
13693 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
13694 		}
13695 
13696 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
13697 			if(s.enableAlpha) {
13698 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
13699 				if(this.xrenderPicturePainter == None) {
13700 					XRenderPictureAttributes attrs;
13701 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
13702 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
13703 
13704 					// need to initialize the clip
13705 					XRectangle[1] rects;
13706 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
13707 
13708 					if(_clipRectangle != Rectangle.init)
13709 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13710 				}
13711 
13712 				XRenderComposite(
13713 					display,
13714 					3, // PicOpOver
13715 					s.xrenderPicture,
13716 					None,
13717 					this.xrenderPicturePainter,
13718 					ix,
13719 					iy,
13720 					0,
13721 					0,
13722 					x,
13723 					y,
13724 					w ? w : s.width,
13725 					h ? h : s.height
13726 				);
13727 			} else {
13728 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13729 			}
13730 		}
13731 
13732 		int fontHeight() {
13733 			version(with_xft)
13734 				if(xftFont !is null)
13735 					return xftFont.height;
13736 			if(font)
13737 				return font.max_bounds.ascent + font.max_bounds.descent;
13738 			return 12; // pretty common default...
13739 		}
13740 
13741 		int textWidth(in char[] line) {
13742 			version(with_xft)
13743 			if(xftFont) {
13744 				if(line.length == 0)
13745 					return 0;
13746 				XGlyphInfo extents;
13747 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13748 				return extents.width;
13749 			}
13750 
13751 			if(fontset) {
13752 				if(line.length == 0)
13753 					return 0;
13754 				XRectangle rect;
13755 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13756 
13757 				return rect.width;
13758 			}
13759 
13760 			if(font)
13761 				// FIXME: unicode
13762 				return XTextWidth( font, line.ptr, cast(int) line.length);
13763 			else
13764 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13765 		}
13766 
13767 		Size textSize(in char[] text) {
13768 			auto maxWidth = 0;
13769 			auto lineHeight = fontHeight;
13770 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13771 			foreach(line; text.split('\n')) {
13772 				int textWidth = this.textWidth(line);
13773 				if(textWidth > maxWidth)
13774 					maxWidth = textWidth;
13775 				h += lineHeight + 4;
13776 			}
13777 			return Size(maxWidth, h);
13778 		}
13779 
13780 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13781 			const(char)[] text;
13782 			version(with_xft)
13783 			if(xftFont) {
13784 				text = originalText;
13785 				goto loaded;
13786 			}
13787 
13788 			if(fontset)
13789 				text = originalText;
13790 			else {
13791 				text.reserve(originalText.length);
13792 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13793 				// then strip the rest so there isn't garbage
13794 				foreach(dchar ch; originalText)
13795 					if(ch < 256)
13796 						text ~= cast(ubyte) ch;
13797 					else
13798 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13799 			}
13800 			loaded:
13801 			if(text.length == 0)
13802 				return;
13803 
13804 			// FIXME: should we clip it to the bounding box?
13805 			int textHeight = fontHeight;
13806 
13807 			auto lines = text.split('\n');
13808 
13809 			const lineHeight = textHeight;
13810 			textHeight *= lines.length;
13811 
13812 			int cy = y;
13813 
13814 			if(alignment & TextAlignment.VerticalBottom) {
13815 				if(y2 <= 0)
13816 					return;
13817 				auto h = y2 - y;
13818 				if(h > textHeight) {
13819 					cy += h - textHeight;
13820 					cy -= lineHeight / 2;
13821 				}
13822 			} else if(alignment & TextAlignment.VerticalCenter) {
13823 				if(y2 <= 0)
13824 					return;
13825 				auto h = y2 - y;
13826 				if(textHeight < h) {
13827 					cy += (h - textHeight) / 2;
13828 					//cy -= lineHeight / 4;
13829 				}
13830 			}
13831 
13832 			foreach(line; text.split('\n')) {
13833 				int textWidth = this.textWidth(line);
13834 
13835 				int px = x, py = cy;
13836 
13837 				if(alignment & TextAlignment.Center) {
13838 					if(x2 <= 0)
13839 						return;
13840 					auto w = x2 - x;
13841 					if(w > textWidth)
13842 						px += (w - textWidth) / 2;
13843 				} else if(alignment & TextAlignment.Right) {
13844 					if(x2 <= 0)
13845 						return;
13846 					auto pos = x2 - textWidth;
13847 					if(pos > x)
13848 						px = pos;
13849 				}
13850 
13851 				version(with_xft)
13852 				if(xftFont) {
13853 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13854 
13855 					goto carry_on;
13856 				}
13857 
13858 				if(fontset)
13859 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13860 				else
13861 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13862 				carry_on:
13863 				cy += lineHeight + 4;
13864 			}
13865 		}
13866 
13867 		void drawPixel(int x, int y) {
13868 			XDrawPoint(display, d, gc, x, y);
13869 		}
13870 
13871 		// The basic shapes, outlined
13872 
13873 		void drawLine(int x1, int y1, int x2, int y2) {
13874 			if(foregroundIsNotTransparent)
13875 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13876 		}
13877 
13878 		void drawRectangle(int x, int y, int width, int height) {
13879 			if(backgroundIsNotTransparent) {
13880 				swapColors();
13881 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13882 				swapColors();
13883 			}
13884 			// 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
13885 			if(foregroundIsNotTransparent)
13886 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13887 		}
13888 
13889 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
13890 			int[4] radii = borderRadius;
13891 			auto r = Rectangle(upperLeft, lowerRight);
13892 
13893 			if(backgroundIsNotTransparent) {
13894 				swapColors();
13895 				// FIXME these overlap and thus draw the pixels multiple times
13896 				XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius);
13897 				XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13898 				swapColors();
13899 			}
13900 
13901 			drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top);
13902 			drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1);
13903 			drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2);
13904 			drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2);
13905 
13906 			//drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13907 
13908 			drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64);
13909 			drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64);
13910 			drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64);
13911 			drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64);
13912 		}
13913 
13914 
13915 		/// Arguments are the points of the bounding rectangle
13916 		void drawEllipse(int x1, int y1, int x2, int y2) {
13917 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13918 		}
13919 
13920 		// NOTE: start and finish are in units of degrees * 64
13921 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
13922 			if(backgroundIsNotTransparent) {
13923 				swapColors();
13924 				XFillArc(display, d, gc, x1, y1, width, height, start, length);
13925 				swapColors();
13926 			}
13927 			if(foregroundIsNotTransparent) {
13928 				XDrawArc(display, d, gc, x1, y1, width, height, start, length);
13929 
13930 				// Windows draws the straight lines on the edges too so FIXME sort of
13931 			}
13932 		}
13933 
13934 		void drawPolygon(Point[] vertexes) {
13935 			XPoint[16] pointsBuffer;
13936 			XPoint[] points;
13937 			if(vertexes.length <= pointsBuffer.length)
13938 				points = pointsBuffer[0 .. vertexes.length];
13939 			else
13940 				points.length = vertexes.length;
13941 
13942 			foreach(i, p; vertexes) {
13943 				points[i].x = cast(short) p.x;
13944 				points[i].y = cast(short) p.y;
13945 			}
13946 
13947 			if(backgroundIsNotTransparent) {
13948 				swapColors();
13949 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13950 				swapColors();
13951 			}
13952 			if(foregroundIsNotTransparent) {
13953 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13954 			}
13955 		}
13956 	}
13957 
13958 	/* XRender { */
13959 
13960 	struct XRenderColor {
13961 		ushort red;
13962 		ushort green;
13963 		ushort blue;
13964 		ushort alpha;
13965 	}
13966 
13967 	alias Picture = XID;
13968 	alias PictFormat = XID;
13969 
13970 	struct XGlyphInfo {
13971 		ushort width;
13972 		ushort height;
13973 		short x;
13974 		short y;
13975 		short xOff;
13976 		short yOff;
13977 	}
13978 
13979 struct XRenderDirectFormat {
13980     short   red;
13981     short   redMask;
13982     short   green;
13983     short   greenMask;
13984     short   blue;
13985     short   blueMask;
13986     short   alpha;
13987     short   alphaMask;
13988 }
13989 
13990 struct XRenderPictFormat {
13991     PictFormat		id;
13992     int			type;
13993     int			depth;
13994     XRenderDirectFormat	direct;
13995     Colormap		colormap;
13996 }
13997 
13998 enum PictFormatID	=   (1 << 0);
13999 enum PictFormatType	=   (1 << 1);
14000 enum PictFormatDepth	=   (1 << 2);
14001 enum PictFormatRed	=   (1 << 3);
14002 enum PictFormatRedMask  =(1 << 4);
14003 enum PictFormatGreen	=   (1 << 5);
14004 enum PictFormatGreenMask=(1 << 6);
14005 enum PictFormatBlue	=   (1 << 7);
14006 enum PictFormatBlueMask =(1 << 8);
14007 enum PictFormatAlpha	=   (1 << 9);
14008 enum PictFormatAlphaMask=(1 << 10);
14009 enum PictFormatColormap =(1 << 11);
14010 
14011 struct XRenderPictureAttributes {
14012 	int 		repeat;
14013 	Picture		alpha_map;
14014 	int			alpha_x_origin;
14015 	int			alpha_y_origin;
14016 	int			clip_x_origin;
14017 	int			clip_y_origin;
14018 	Pixmap		clip_mask;
14019 	Bool		graphics_exposures;
14020 	int			subwindow_mode;
14021 	int			poly_edge;
14022 	int			poly_mode;
14023 	Atom		dither;
14024 	Bool		component_alpha;
14025 }
14026 
14027 alias int XFixed;
14028 
14029 struct XPointFixed {
14030     XFixed  x, y;
14031 }
14032 
14033 struct XCircle {
14034     XFixed x;
14035     XFixed y;
14036     XFixed radius;
14037 }
14038 
14039 struct XTransform {
14040     XFixed[3][3]  matrix;
14041 }
14042 
14043 struct XFilters {
14044     int	    nfilter;
14045     char    **filter;
14046     int	    nalias;
14047     short   *alias_;
14048 }
14049 
14050 struct XIndexValue {
14051     c_ulong    pixel;
14052     ushort   red, green, blue, alpha;
14053 }
14054 
14055 struct XAnimCursor {
14056     Cursor	    cursor;
14057     c_ulong   delay;
14058 }
14059 
14060 struct XLinearGradient {
14061     XPointFixed p1;
14062     XPointFixed p2;
14063 }
14064 
14065 struct XRadialGradient {
14066     XCircle inner;
14067     XCircle outer;
14068 }
14069 
14070 struct XConicalGradient {
14071     XPointFixed center;
14072     XFixed angle; /* in degrees */
14073 }
14074 
14075 enum PictStandardARGB32  = 0;
14076 enum PictStandardRGB24   = 1;
14077 enum PictStandardA8	 =  2;
14078 enum PictStandardA4	 =  3;
14079 enum PictStandardA1	 =  4;
14080 enum PictStandardNUM	 =  5;
14081 
14082 interface XRender {
14083 extern(C) @nogc:
14084 
14085 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
14086 
14087 	Status XRenderQueryVersion (Display *dpy,
14088 			int     *major_versionp,
14089 			int     *minor_versionp);
14090 
14091 	Status XRenderQueryFormats (Display *dpy);
14092 
14093 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
14094 
14095 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
14096 
14097 	XRenderPictFormat *
14098 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
14099 
14100 	XRenderPictFormat *
14101 		XRenderFindFormat (Display			*dpy,
14102 				c_ulong		mask,
14103 				const XRenderPictFormat	*templ,
14104 				int				count);
14105 	XRenderPictFormat *
14106 		XRenderFindStandardFormat (Display		*dpy,
14107 				int			format);
14108 
14109 	XIndexValue *
14110 		XRenderQueryPictIndexValues(Display			*dpy,
14111 				const XRenderPictFormat	*format,
14112 				int				*num);
14113 
14114 	Picture XRenderCreatePicture(
14115 		Display *dpy,
14116 		Drawable drawable,
14117 		const XRenderPictFormat *format,
14118 		c_ulong valuemask,
14119 		const XRenderPictureAttributes *attributes);
14120 
14121 	void XRenderChangePicture (Display				*dpy,
14122 				Picture				picture,
14123 				c_ulong			valuemask,
14124 				const XRenderPictureAttributes  *attributes);
14125 
14126 	void
14127 		XRenderSetPictureClipRectangles (Display	    *dpy,
14128 				Picture	    picture,
14129 				int		    xOrigin,
14130 				int		    yOrigin,
14131 				const XRectangle *rects,
14132 				int		    n);
14133 
14134 	void
14135 		XRenderSetPictureClipRegion (Display	    *dpy,
14136 				Picture	    picture,
14137 				Region	    r);
14138 
14139 	void
14140 		XRenderSetPictureTransform (Display	    *dpy,
14141 				Picture	    picture,
14142 				XTransform	    *transform);
14143 
14144 	void
14145 		XRenderFreePicture (Display                   *dpy,
14146 				Picture                   picture);
14147 
14148 	void
14149 		XRenderComposite (Display   *dpy,
14150 				int	    op,
14151 				Picture   src,
14152 				Picture   mask,
14153 				Picture   dst,
14154 				int	    src_x,
14155 				int	    src_y,
14156 				int	    mask_x,
14157 				int	    mask_y,
14158 				int	    dst_x,
14159 				int	    dst_y,
14160 				uint	width,
14161 				uint	height);
14162 
14163 
14164 	Picture XRenderCreateSolidFill (Display *dpy,
14165 			const XRenderColor *color);
14166 
14167 	Picture XRenderCreateLinearGradient (Display *dpy,
14168 			const XLinearGradient *gradient,
14169 			const XFixed *stops,
14170 			const XRenderColor *colors,
14171 			int nstops);
14172 
14173 	Picture XRenderCreateRadialGradient (Display *dpy,
14174 			const XRadialGradient *gradient,
14175 			const XFixed *stops,
14176 			const XRenderColor *colors,
14177 			int nstops);
14178 
14179 	Picture XRenderCreateConicalGradient (Display *dpy,
14180 			const XConicalGradient *gradient,
14181 			const XFixed *stops,
14182 			const XRenderColor *colors,
14183 			int nstops);
14184 
14185 
14186 
14187 	Cursor
14188 		XRenderCreateCursor (Display	    *dpy,
14189 				Picture	    source,
14190 				uint   x,
14191 				uint   y);
14192 
14193 	XFilters *
14194 		XRenderQueryFilters (Display *dpy, Drawable drawable);
14195 
14196 	void
14197 		XRenderSetPictureFilter (Display    *dpy,
14198 				Picture    picture,
14199 				const char *filter,
14200 				XFixed	    *params,
14201 				int	    nparams);
14202 
14203 	Cursor
14204 		XRenderCreateAnimCursor (Display	*dpy,
14205 				int		ncursor,
14206 				XAnimCursor	*cursors);
14207 }
14208 
14209 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
14210 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
14211 
14212 	/* XRender } */
14213 
14214 	/* Xrandr { */
14215 
14216 struct XRRMonitorInfo {
14217     Atom name;
14218     Bool primary;
14219     Bool automatic;
14220     int noutput;
14221     int x;
14222     int y;
14223     int width;
14224     int height;
14225     int mwidth;
14226     int mheight;
14227     /*RROutput*/ void *outputs;
14228 }
14229 
14230 struct XRRScreenChangeNotifyEvent {
14231     int type;                   /* event base */
14232     c_ulong serial;       /* # of last request processed by server */
14233     Bool send_event;            /* true if this came from a SendEvent request */
14234     Display *display;           /* Display the event was read from */
14235     Window window;              /* window which selected for this event */
14236     Window root;                /* Root window for changed screen */
14237     Time timestamp;             /* when the screen change occurred */
14238     Time config_timestamp;      /* when the last configuration change */
14239     ushort/*SizeID*/ size_index;
14240     ushort/*SubpixelOrder*/ subpixel_order;
14241     ushort/*Rotation*/ rotation;
14242     int width;
14243     int height;
14244     int mwidth;
14245     int mheight;
14246 }
14247 
14248 enum RRScreenChangeNotify = 0;
14249 
14250 enum RRScreenChangeNotifyMask = 1;
14251 
14252 __gshared int xrrEventBase = -1;
14253 
14254 
14255 interface XRandr {
14256 extern(C) @nogc:
14257 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
14258 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
14259 
14260 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
14261 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
14262 
14263 	void XRRSelectInput(Display *dpy, Window window, int mask);
14264 }
14265 
14266 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
14267 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
14268 	/* Xrandr } */
14269 
14270 	/* Xft { */
14271 
14272 	// actually freetype
14273 	alias void FT_Face;
14274 
14275 	// actually fontconfig
14276 	private alias FcBool = int;
14277 	alias void FcCharSet;
14278 	alias void FcPattern;
14279 	alias void FcResult;
14280 	enum FcEndian { FcEndianBig, FcEndianLittle }
14281 	struct FcFontSet {
14282 		int nfont;
14283 		int sfont;
14284 		FcPattern** fonts;
14285 	}
14286 
14287 	// actually XRegion
14288 	struct BOX {
14289 		short x1, x2, y1, y2;
14290 	}
14291 	struct _XRegion {
14292 		c_long size;
14293 		c_long numRects;
14294 		BOX* rects;
14295 		BOX extents;
14296 	}
14297 
14298 	alias Region = _XRegion*;
14299 
14300 	// ok actually Xft
14301 
14302 	struct XftFontInfo;
14303 
14304 	struct XftFont {
14305 		int         ascent;
14306 		int         descent;
14307 		int         height;
14308 		int         max_advance_width;
14309 		FcCharSet*  charset;
14310 		FcPattern*  pattern;
14311 	}
14312 
14313 	struct XftDraw;
14314 
14315 	struct XftColor {
14316 		c_ulong pixel;
14317 		XRenderColor color;
14318 	}
14319 
14320 	struct XftCharSpec {
14321 		dchar           ucs4;
14322 		short           x;
14323 		short           y;
14324 	}
14325 
14326 	struct XftCharFontSpec {
14327 		XftFont         *font;
14328 		dchar           ucs4;
14329 		short           x;
14330 		short           y;
14331 	}
14332 
14333 	struct XftGlyphSpec {
14334 		uint            glyph;
14335 		short           x;
14336 		short           y;
14337 	}
14338 
14339 	struct XftGlyphFontSpec {
14340 		XftFont         *font;
14341 		uint            glyph;
14342 		short           x;
14343 		short           y;
14344 	}
14345 
14346 	interface Xft {
14347 	extern(C) @nogc pure:
14348 
14349 	Bool XftColorAllocName (Display  *dpy,
14350 				const Visual   *visual,
14351 				Colormap cmap,
14352 				const char     *name,
14353 				XftColor *result);
14354 
14355 	Bool XftColorAllocValue (Display         *dpy,
14356 				Visual          *visual,
14357 				Colormap        cmap,
14358 				const XRenderColor    *color,
14359 				XftColor        *result);
14360 
14361 	void XftColorFree (Display   *dpy,
14362 				Visual    *visual,
14363 				Colormap  cmap,
14364 				XftColor  *color);
14365 
14366 	Bool XftDefaultHasRender (Display *dpy);
14367 
14368 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
14369 
14370 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
14371 
14372 	XftDraw * XftDrawCreate (Display   *dpy,
14373 		       Drawable  drawable,
14374 		       Visual    *visual,
14375 		       Colormap  colormap);
14376 
14377 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
14378 			     Pixmap   bitmap);
14379 
14380 	XftDraw * XftDrawCreateAlpha (Display *dpy,
14381 			    Pixmap  pixmap,
14382 			    int     depth);
14383 
14384 	void XftDrawChange (XftDraw  *draw,
14385 		       Drawable drawable);
14386 
14387 	Display * XftDrawDisplay (XftDraw *draw);
14388 
14389 	Drawable XftDrawDrawable (XftDraw *draw);
14390 
14391 	Colormap XftDrawColormap (XftDraw *draw);
14392 
14393 	Visual * XftDrawVisual (XftDraw *draw);
14394 
14395 	void XftDrawDestroy (XftDraw *draw);
14396 
14397 	Picture XftDrawPicture (XftDraw *draw);
14398 
14399 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
14400 
14401 	void XftDrawGlyphs (XftDraw          *draw,
14402 				const XftColor *color,
14403 				XftFont          *pub,
14404 				int              x,
14405 				int              y,
14406 				const uint  *glyphs,
14407 				int              nglyphs);
14408 
14409 	void XftDrawString8 (XftDraw             *draw,
14410 				const XftColor    *color,
14411 				XftFont             *pub,
14412 				int                 x,
14413 				int                 y,
14414 				const char     *string,
14415 				int                 len);
14416 
14417 	void XftDrawString16 (XftDraw            *draw,
14418 				const XftColor   *color,
14419 				XftFont            *pub,
14420 				int                x,
14421 				int                y,
14422 				const wchar   *string,
14423 				int                len);
14424 
14425 	void XftDrawString32 (XftDraw            *draw,
14426 				const XftColor   *color,
14427 				XftFont            *pub,
14428 				int                x,
14429 				int                y,
14430 				const dchar   *string,
14431 				int                len);
14432 
14433 	void XftDrawStringUtf8 (XftDraw          *draw,
14434 				const XftColor *color,
14435 				XftFont          *pub,
14436 				int              x,
14437 				int              y,
14438 				const char  *string,
14439 				int              len);
14440 	void XftDrawStringUtf16 (XftDraw             *draw,
14441 				const XftColor    *color,
14442 				XftFont             *pub,
14443 				int                 x,
14444 				int                 y,
14445 				const char     *string,
14446 				FcEndian            endian,
14447 				int                 len);
14448 
14449 	void XftDrawCharSpec (XftDraw                *draw,
14450 				const XftColor       *color,
14451 				XftFont                *pub,
14452 				const XftCharSpec    *chars,
14453 				int                    len);
14454 
14455 	void XftDrawCharFontSpec (XftDraw                    *draw,
14456 				const XftColor           *color,
14457 				const XftCharFontSpec    *chars,
14458 				int                        len);
14459 
14460 	void XftDrawGlyphSpec (XftDraw               *draw,
14461 				const XftColor      *color,
14462 				XftFont               *pub,
14463 				const XftGlyphSpec  *glyphs,
14464 				int                   len);
14465 
14466 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
14467 				const XftColor          *color,
14468 				const XftGlyphFontSpec  *glyphs,
14469 				int                       len);
14470 
14471 	void XftDrawRect (XftDraw            *draw,
14472 				const XftColor   *color,
14473 				int                x,
14474 				int                y,
14475 				uint       width,
14476 				uint       height);
14477 
14478 	Bool XftDrawSetClip (XftDraw     *draw,
14479 				Region      r);
14480 
14481 
14482 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
14483 				int                   xOrigin,
14484 				int                   yOrigin,
14485 				const XRectangle    *rects,
14486 				int                   n);
14487 
14488 	void XftDrawSetSubwindowMode (XftDraw    *draw,
14489 				int        mode);
14490 
14491 	void XftGlyphExtents (Display            *dpy,
14492 				XftFont            *pub,
14493 				const uint    *glyphs,
14494 				int                nglyphs,
14495 				XGlyphInfo         *extents);
14496 
14497 	void XftTextExtents8 (Display            *dpy,
14498 				XftFont            *pub,
14499 				const char    *string,
14500 				int                len,
14501 				XGlyphInfo         *extents);
14502 
14503 	void XftTextExtents16 (Display           *dpy,
14504 				XftFont           *pub,
14505 				const wchar  *string,
14506 				int               len,
14507 				XGlyphInfo        *extents);
14508 
14509 	void XftTextExtents32 (Display           *dpy,
14510 				XftFont           *pub,
14511 				const dchar  *string,
14512 				int               len,
14513 				XGlyphInfo        *extents);
14514 
14515 	void XftTextExtentsUtf8 (Display         *dpy,
14516 				XftFont         *pub,
14517 				const char *string,
14518 				int             len,
14519 				XGlyphInfo      *extents);
14520 
14521 	void XftTextExtentsUtf16 (Display            *dpy,
14522 				XftFont            *pub,
14523 				const char    *string,
14524 				FcEndian           endian,
14525 				int                len,
14526 				XGlyphInfo         *extents);
14527 
14528 	FcPattern * XftFontMatch (Display           *dpy,
14529 				int               screen,
14530 				const FcPattern *pattern,
14531 				FcResult          *result);
14532 
14533 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
14534 
14535 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
14536 
14537 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
14538 
14539 	FT_Face XftLockFace (XftFont *pub);
14540 
14541 	void XftUnlockFace (XftFont *pub);
14542 
14543 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
14544 
14545 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
14546 
14547 	dchar XftFontInfoHash (const XftFontInfo *fi);
14548 
14549 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
14550 
14551 	XftFont * XftFontOpenInfo (Display        *dpy,
14552 				FcPattern      *pattern,
14553 				XftFontInfo    *fi);
14554 
14555 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
14556 
14557 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
14558 
14559 	void XftFontClose (Display *dpy, XftFont *pub);
14560 
14561 	FcBool XftInitFtLibrary();
14562 	void XftFontLoadGlyphs (Display          *dpy,
14563 				XftFont          *pub,
14564 				FcBool           need_bitmaps,
14565 				const uint  *glyphs,
14566 				int              nglyph);
14567 
14568 	void XftFontUnloadGlyphs (Display            *dpy,
14569 				XftFont            *pub,
14570 				const uint    *glyphs,
14571 				int                nglyph);
14572 
14573 	FcBool XftFontCheckGlyph (Display  *dpy,
14574 				XftFont  *pub,
14575 				FcBool   need_bitmaps,
14576 				uint  glyph,
14577 				uint  *missing,
14578 				int      *nmissing);
14579 
14580 	FcBool XftCharExists (Display      *dpy,
14581 				XftFont      *pub,
14582 				dchar    ucs4);
14583 
14584 	uint XftCharIndex (Display       *dpy,
14585 				XftFont       *pub,
14586 				dchar      ucs4);
14587 	FcBool XftInit (const char *config);
14588 
14589 	int XftGetVersion ();
14590 
14591 	FcFontSet * XftListFonts (Display   *dpy,
14592 				int       screen,
14593 				...);
14594 
14595 	FcPattern *XftNameParse (const char *name);
14596 
14597 	void XftGlyphRender (Display         *dpy,
14598 				int             op,
14599 				Picture         src,
14600 				XftFont         *pub,
14601 				Picture         dst,
14602 				int             srcx,
14603 				int             srcy,
14604 				int             x,
14605 				int             y,
14606 				const uint *glyphs,
14607 				int             nglyphs);
14608 
14609 	void XftGlyphSpecRender (Display                 *dpy,
14610 				int                     op,
14611 				Picture                 src,
14612 				XftFont                 *pub,
14613 				Picture                 dst,
14614 				int                     srcx,
14615 				int                     srcy,
14616 				const XftGlyphSpec    *glyphs,
14617 				int                     nglyphs);
14618 
14619 	void XftCharSpecRender (Display              *dpy,
14620 				int                  op,
14621 				Picture              src,
14622 				XftFont              *pub,
14623 				Picture              dst,
14624 				int                  srcx,
14625 				int                  srcy,
14626 				const XftCharSpec  *chars,
14627 				int                  len);
14628 	void XftGlyphFontSpecRender (Display                     *dpy,
14629 				int                         op,
14630 				Picture                     src,
14631 				Picture                     dst,
14632 				int                         srcx,
14633 				int                         srcy,
14634 				const XftGlyphFontSpec    *glyphs,
14635 				int                         nglyphs);
14636 
14637 	void XftCharFontSpecRender (Display                  *dpy,
14638 				int                      op,
14639 				Picture                  src,
14640 				Picture                  dst,
14641 				int                      srcx,
14642 				int                      srcy,
14643 				const XftCharFontSpec  *chars,
14644 				int                      len);
14645 
14646 	void XftTextRender8 (Display         *dpy,
14647 				int             op,
14648 				Picture         src,
14649 				XftFont         *pub,
14650 				Picture         dst,
14651 				int             srcx,
14652 				int             srcy,
14653 				int             x,
14654 				int             y,
14655 				const char *string,
14656 				int             len);
14657 	void XftTextRender16 (Display            *dpy,
14658 				int                op,
14659 				Picture            src,
14660 				XftFont            *pub,
14661 				Picture            dst,
14662 				int                srcx,
14663 				int                srcy,
14664 				int                x,
14665 				int                y,
14666 				const wchar   *string,
14667 				int                len);
14668 
14669 	void XftTextRender16BE (Display          *dpy,
14670 				int              op,
14671 				Picture          src,
14672 				XftFont          *pub,
14673 				Picture          dst,
14674 				int              srcx,
14675 				int              srcy,
14676 				int              x,
14677 				int              y,
14678 				const char  *string,
14679 				int              len);
14680 
14681 	void XftTextRender16LE (Display          *dpy,
14682 				int              op,
14683 				Picture          src,
14684 				XftFont          *pub,
14685 				Picture          dst,
14686 				int              srcx,
14687 				int              srcy,
14688 				int              x,
14689 				int              y,
14690 				const char  *string,
14691 				int              len);
14692 
14693 	void XftTextRender32 (Display            *dpy,
14694 				int                op,
14695 				Picture            src,
14696 				XftFont            *pub,
14697 				Picture            dst,
14698 				int                srcx,
14699 				int                srcy,
14700 				int                x,
14701 				int                y,
14702 				const dchar   *string,
14703 				int                len);
14704 
14705 	void XftTextRender32BE (Display          *dpy,
14706 				int              op,
14707 				Picture          src,
14708 				XftFont          *pub,
14709 				Picture          dst,
14710 				int              srcx,
14711 				int              srcy,
14712 				int              x,
14713 				int              y,
14714 				const char  *string,
14715 				int              len);
14716 
14717 	void XftTextRender32LE (Display          *dpy,
14718 				int              op,
14719 				Picture          src,
14720 				XftFont          *pub,
14721 				Picture          dst,
14722 				int              srcx,
14723 				int              srcy,
14724 				int              x,
14725 				int              y,
14726 				const char  *string,
14727 				int              len);
14728 
14729 	void XftTextRenderUtf8 (Display          *dpy,
14730 				int              op,
14731 				Picture          src,
14732 				XftFont          *pub,
14733 				Picture          dst,
14734 				int              srcx,
14735 				int              srcy,
14736 				int              x,
14737 				int              y,
14738 				const char  *string,
14739 				int              len);
14740 
14741 	void XftTextRenderUtf16 (Display         *dpy,
14742 				int             op,
14743 				Picture         src,
14744 				XftFont         *pub,
14745 				Picture         dst,
14746 				int             srcx,
14747 				int             srcy,
14748 				int             x,
14749 				int             y,
14750 				const char *string,
14751 				FcEndian        endian,
14752 				int             len);
14753 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14754 
14755 	}
14756 
14757 	interface FontConfig {
14758 	extern(C) @nogc pure:
14759 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14760 		void FcFontSetDestroy(FcFontSet*);
14761 		char* FcNameUnparse(const FcPattern *);
14762 	}
14763 
14764 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14765 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14766 
14767 
14768 	/* Xft } */
14769 
14770 	class XDisconnectException : Exception {
14771 		bool userRequested;
14772 		this(bool userRequested = true) {
14773 			this.userRequested = userRequested;
14774 			super("X disconnected");
14775 		}
14776 	}
14777 
14778 	/++
14779 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14780 
14781 		Please note that it returns
14782 	+/
14783 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14784 
14785 		static XErrorEvent[] errorBuffer;
14786 
14787 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14788 			errorBuffer ~= *evt;
14789 			return 0;
14790 		}
14791 
14792 		auto savedErrorHandler = XSetErrorHandler(&handler);
14793 
14794 		try {
14795 			dg();
14796 		} finally {
14797 			XSync(XDisplayConnection.get, 0/*False*/);
14798 			XSetErrorHandler(savedErrorHandler);
14799 		}
14800 
14801 		auto bfr = errorBuffer;
14802 		errorBuffer = null;
14803 
14804 		return bfr;
14805 	}
14806 
14807 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14808 	class XDisplayConnection {
14809 		private __gshared Display* display;
14810 		private __gshared XIM xim;
14811 		private __gshared char* displayName;
14812 
14813 		private __gshared int connectionSequence_;
14814 		private __gshared bool isLocal_;
14815 
14816 		/// use this for lazy caching when reconnection
14817 		static int connectionSequenceNumber() { return connectionSequence_; }
14818 
14819 		/++
14820 			Guesses if the connection appears to be local.
14821 
14822 			History:
14823 				Added June 3, 2021
14824 		+/
14825 		static @property bool isLocal() nothrow @trusted @nogc {
14826 			return isLocal_;
14827 		}
14828 
14829 		/// Attempts recreation of state, may require application assistance
14830 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14831 		/// then call this, and if successful, reenter the loop.
14832 		static void discardAndRecreate(string newDisplayString = null) {
14833 			if(insideXEventLoop)
14834 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14835 
14836 			// 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
14837 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14838 
14839 			foreach(handle; chnenhm) {
14840 				handle.discardConnectionState();
14841 			}
14842 
14843 			discardState();
14844 
14845 			if(newDisplayString !is null)
14846 				setDisplayName(newDisplayString);
14847 
14848 			auto display = get();
14849 
14850 			foreach(handle; chnenhm) {
14851 				handle.recreateAfterDisconnect();
14852 			}
14853 		}
14854 
14855 		private __gshared EventMask rootEventMask;
14856 
14857 		/++
14858 			Requests the specified input from the root window on the connection, in addition to any other request.
14859 
14860 
14861 			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.
14862 
14863 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14864 		+/
14865 		static void addRootInput(EventMask mask) {
14866 			auto old = rootEventMask;
14867 			rootEventMask |= mask;
14868 			get(); // to ensure display connected
14869 			if(display !is null && rootEventMask != old)
14870 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14871 		}
14872 
14873 		static void discardState() {
14874 			freeImages();
14875 
14876 			foreach(atomPtr; interredAtoms)
14877 				*atomPtr = 0;
14878 			interredAtoms = null;
14879 			interredAtoms.assumeSafeAppend();
14880 
14881 			ScreenPainterImplementation.fontAttempted = false;
14882 			ScreenPainterImplementation.defaultfont = null;
14883 			ScreenPainterImplementation.defaultfontset = null;
14884 
14885 			Image.impl.xshmQueryCompleted = false;
14886 			Image.impl._xshmAvailable = false;
14887 
14888 			SimpleWindow.nativeMapping = null;
14889 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14890 			// GlobalHotkeyManager
14891 
14892 			display = null;
14893 			xim = null;
14894 		}
14895 
14896 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14897 		private static void createXIM () {
14898 			import core.stdc.locale : setlocale, LC_ALL;
14899 			import core.stdc.stdio : stderr, fprintf;
14900 			import core.stdc.stdlib : free;
14901 			import core.stdc.string : strdup;
14902 
14903 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14904 
14905 			auto olocale = strdup(setlocale(LC_ALL, null));
14906 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14907 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14908 
14909 			//fprintf(stderr, "opening IM...\n");
14910 			foreach (string s; mtry) {
14911 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14912 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14913 			}
14914 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14915 		}
14916 
14917 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14918 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14919 		static struct ImgList {
14920 			size_t img; // class; hide it from GC
14921 			ImgList* next;
14922 		}
14923 
14924 		static __gshared ImgList* imglist = null;
14925 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14926 
14927 		static void registerImage (Image img) {
14928 			if (!imglistLocked && img !is null) {
14929 				import core.stdc.stdlib : malloc;
14930 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14931 				assert(it !is null); // do proper checks
14932 				it.img = cast(size_t)cast(void*)img;
14933 				it.next = imglist;
14934 				imglist = it;
14935 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14936 			}
14937 		}
14938 
14939 		static void unregisterImage (Image img) {
14940 			if (!imglistLocked && img !is null) {
14941 				import core.stdc.stdlib : free;
14942 				ImgList* prev = null;
14943 				ImgList* cur = imglist;
14944 				while (cur !is null) {
14945 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14946 					prev = cur;
14947 					cur = cur.next;
14948 				}
14949 				if (cur !is null) {
14950 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14951 					free(cur);
14952 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14953 				} else {
14954 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14955 				}
14956 			}
14957 		}
14958 
14959 		static void freeImages () { // needed for discardAndRecreate
14960 			imglistLocked = true;
14961 			scope(exit) imglistLocked = false;
14962 			ImgList* cur = imglist;
14963 			ImgList* next = null;
14964 			while (cur !is null) {
14965 				import core.stdc.stdlib : free;
14966 				next = cur.next;
14967 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14968 				(cast(Image)cast(void*)cur.img).dispose();
14969 				free(cur);
14970 				cur = next;
14971 			}
14972 			imglist = null;
14973 		}
14974 
14975 		/// can be used to override normal handling of display name
14976 		/// from environment and/or command line
14977 		static setDisplayName(string newDisplayName) {
14978 			displayName = cast(char*) (newDisplayName ~ '\0');
14979 		}
14980 
14981 		/// resets to the default display string
14982 		static resetDisplayName() {
14983 			displayName = null;
14984 		}
14985 
14986 		///
14987 		static Display* get() {
14988 			if(display is null) {
14989 				if(!librariesSuccessfullyLoaded)
14990 					throw new Exception("Unable to load X11 client libraries");
14991 				display = XOpenDisplay(displayName);
14992 
14993 				isLocal_ = false;
14994 
14995 				connectionSequence_++;
14996 				if(display is null)
14997 					throw new Exception("Unable to open X display");
14998 
14999 				auto str = display.display_name;
15000 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
15001 				// and otherwise it probably isn't
15002 				if(str is null || (str[0] != ':' && str[0] != '/'))
15003 					isLocal_ = false;
15004 				else
15005 					isLocal_ = true;
15006 
15007 				XSetErrorHandler(&adrlogger);
15008 
15009 				debug(sdpy_x_errors) {
15010 					XSynchronize(display, true);
15011 
15012 					extern(C) int wtf() {
15013 						if(errorHappened) {
15014 							asm { int 3; }
15015 							errorHappened = false;
15016 						}
15017 						return 0;
15018 					}
15019 					XSetAfterFunction(display, &wtf);
15020 				}
15021 
15022 
15023 				XSetIOErrorHandler(&x11ioerrCB);
15024 				Bool sup;
15025 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
15026 				createXIM();
15027 				version(with_eventloop) {
15028 					import arsd.eventloop;
15029 					addFileEventListeners(display.fd, &eventListener, null, null);
15030 				}
15031 			}
15032 
15033 			return display;
15034 		}
15035 
15036 		extern(C)
15037 		static int x11ioerrCB(Display* dpy) {
15038 			throw new XDisconnectException(false);
15039 		}
15040 
15041 		version(with_eventloop) {
15042 			import arsd.eventloop;
15043 			static void eventListener(OsFileHandle fd) {
15044 				//this.mtLock();
15045 				//scope(exit) this.mtUnlock();
15046 				while(XPending(display))
15047 					doXNextEvent(display);
15048 			}
15049 		}
15050 
15051 		// close connection on program exit -- we need this to properly free all images
15052 		static ~this () {
15053 			// the gui thread must clean up after itself or else Xlib might deadlock
15054 			// using this flag on any thread destruction is the easiest way i know of
15055 			// (shared static this is run by the LAST thread to exit, which may not be
15056 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
15057 			if(thisIsGuiThread)
15058 				close();
15059 		}
15060 
15061 		///
15062 		static void close() {
15063 			if(display is null)
15064 				return;
15065 
15066 			version(with_eventloop) {
15067 				import arsd.eventloop;
15068 				removeFileEventListeners(display.fd);
15069 			}
15070 
15071 			// now remove all registered images to prevent shared memory leaks
15072 			freeImages();
15073 
15074 			// tbh I don't know why it is doing this but like if this happens to run
15075 			// from the other thread there's frequent hanging inside here.
15076 			if(thisIsGuiThread)
15077 				XCloseDisplay(display);
15078 			display = null;
15079 		}
15080 	}
15081 
15082 	mixin template NativeImageImplementation() {
15083 		XImage* handle;
15084 		ubyte* rawData;
15085 
15086 		XShmSegmentInfo shminfo;
15087 		bool premultiply = true;
15088 
15089 		__gshared bool xshmQueryCompleted;
15090 		__gshared bool _xshmAvailable;
15091 		public static @property bool xshmAvailable() {
15092 			if(!xshmQueryCompleted) {
15093 				int i1, i2, i3;
15094 				xshmQueryCompleted = true;
15095 
15096 				if(!XDisplayConnection.isLocal)
15097 					_xshmAvailable = false;
15098 				else
15099 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
15100 			}
15101 			return _xshmAvailable;
15102 		}
15103 
15104 		bool usingXshm;
15105 	final:
15106 
15107 		private __gshared bool xshmfailed;
15108 
15109 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
15110 			auto display = XDisplayConnection.get();
15111 			assert(display !is null);
15112 			auto screen = DefaultScreen(display);
15113 
15114 			// it will only use shared memory for somewhat largish images,
15115 			// since otherwise we risk wasting shared memory handles on a lot of little ones
15116 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
15117 
15118 
15119 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
15120 				// the actual use still fails. For example, if the program is in a container and permission denied
15121 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
15122 				//
15123 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
15124 
15125 
15126 				// synchronize so preexisting buffers are clear
15127 				XSync(display, false);
15128 				xshmfailed = false;
15129 
15130 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
15131 
15132 
15133 				usingXshm = true;
15134 				handle = XShmCreateImage(
15135 					display,
15136 					DefaultVisual(display, screen),
15137 					enableAlpha ? 32: 24,
15138 					ImageFormat.ZPixmap,
15139 					null,
15140 					&shminfo,
15141 					width, height);
15142 				if(handle is null)
15143 					goto abortXshm1;
15144 
15145 				if(handle.bytes_per_line != 4 * width)
15146 					goto abortXshm2;
15147 
15148 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
15149 				if(shminfo.shmid < 0)
15150 					goto abortXshm3;
15151 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
15152 				if(rawData == cast(ubyte*) -1)
15153 					goto abortXshm4;
15154 				shminfo.readOnly = 0;
15155 				XShmAttach(display, &shminfo);
15156 
15157 				// and now to the final error check to ensure it actually worked.
15158 				XSync(display, false);
15159 				if(xshmfailed)
15160 					goto abortXshm5;
15161 
15162 				XSetErrorHandler(oldErrorHandler);
15163 
15164 				XDisplayConnection.registerImage(this);
15165 				// if I don't flush here there's a chance the dtor will run before the
15166 				// ctor and lead to a bad value X error. While this hurts the efficiency
15167 				// it is local anyway so prolly better to keep it simple
15168 				XFlush(display);
15169 
15170 				return;
15171 
15172 				abortXshm5:
15173 					shmdt(shminfo.shmaddr);
15174 					rawData = null;
15175 
15176 				abortXshm4:
15177 					shmctl(shminfo.shmid, IPC_RMID, null);
15178 
15179 				abortXshm3:
15180 					// nothing needed, the shmget failed so there's nothing to free
15181 
15182 				abortXshm2:
15183 					XDestroyImage(handle);
15184 					handle = null;
15185 
15186 				abortXshm1:
15187 					XSetErrorHandler(oldErrorHandler);
15188 					usingXshm = false;
15189 					handle = null;
15190 
15191 					shminfo = typeof(shminfo).init;
15192 
15193 					_xshmAvailable = false; // don't try again in the future
15194 
15195 					// writeln("fallingback");
15196 
15197 					goto fallback;
15198 
15199 			} else {
15200 				fallback:
15201 
15202 				if (forcexshm) throw new Exception("can't create XShm Image");
15203 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
15204 				import core.stdc.stdlib : malloc;
15205 				rawData = cast(ubyte*) malloc(width * height * 4);
15206 
15207 				handle = XCreateImage(
15208 					display,
15209 					DefaultVisual(display, screen),
15210 					enableAlpha ? 32 : 24, // bpp
15211 					ImageFormat.ZPixmap,
15212 					0, // offset
15213 					rawData,
15214 					width, height,
15215 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
15216 			}
15217 		}
15218 
15219 		void dispose() {
15220 			// note: this calls free(rawData) for us
15221 			if(handle) {
15222 				if (usingXshm) {
15223 					XDisplayConnection.unregisterImage(this);
15224 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
15225 				}
15226 				XDestroyImage(handle);
15227 				if(usingXshm) {
15228 					shmdt(shminfo.shmaddr);
15229 					shmctl(shminfo.shmid, IPC_RMID, null);
15230 				}
15231 				handle = null;
15232 			}
15233 		}
15234 
15235 		Color getPixel(int x, int y) @system {
15236 			auto offset = (y * width + x) * 4;
15237 			Color c;
15238 			c.a = enableAlpha ? rawData[offset + 3] : 255;
15239 			c.b = rawData[offset + 0];
15240 			c.g = rawData[offset + 1];
15241 			c.r = rawData[offset + 2];
15242 			if(enableAlpha && premultiply)
15243 				c.unPremultiply;
15244 			return c;
15245 		}
15246 
15247 		void setPixel(int x, int y, Color c) @system {
15248 			if(enableAlpha && premultiply)
15249 				c.premultiply();
15250 			auto offset = (y * width + x) * 4;
15251 			rawData[offset + 0] = c.b;
15252 			rawData[offset + 1] = c.g;
15253 			rawData[offset + 2] = c.r;
15254 			if(enableAlpha)
15255 				rawData[offset + 3] = c.a;
15256 		}
15257 
15258 		void convertToRgbaBytes(ubyte[] where) @system {
15259 			assert(where.length == this.width * this.height * 4);
15260 
15261 			// if rawData had a length....
15262 			//assert(rawData.length == where.length);
15263 			for(int idx = 0; idx < where.length; idx += 4) {
15264 				where[idx + 0] = rawData[idx + 2]; // r
15265 				where[idx + 1] = rawData[idx + 1]; // g
15266 				where[idx + 2] = rawData[idx + 0]; // b
15267 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
15268 
15269 				if(enableAlpha && premultiply)
15270 					unPremultiplyRgba(where[idx .. idx + 4]);
15271 			}
15272 		}
15273 
15274 		void setFromRgbaBytes(in ubyte[] where) @system {
15275 			assert(where.length == this.width * this.height * 4);
15276 
15277 			// if rawData had a length....
15278 			//assert(rawData.length == where.length);
15279 			for(int idx = 0; idx < where.length; idx += 4) {
15280 				rawData[idx + 2] = where[idx + 0]; // r
15281 				rawData[idx + 1] = where[idx + 1]; // g
15282 				rawData[idx + 0] = where[idx + 2]; // b
15283 				if(enableAlpha) {
15284 					rawData[idx + 3] = where[idx + 3]; // a
15285 					if(premultiply)
15286 						premultiplyBgra(rawData[idx .. idx + 4]);
15287 				}
15288 			}
15289 		}
15290 
15291 	}
15292 
15293 	mixin template NativeSimpleWindowImplementation() {
15294 		GC gc;
15295 		Window window;
15296 		Display* display;
15297 
15298 		Pixmap buffer;
15299 		int bufferw, bufferh; // size of the buffer; can be bigger than window
15300 		XIC xic; // input context
15301 		int curHidden = 0; // counter
15302 		Cursor blankCurPtr = 0;
15303 		int cursorSequenceNumber = 0;
15304 		int warpEventCount = 0; // number of mouse movement events to eat
15305 
15306 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS
15307 		X11GetSelectionHandler[Atom] getSelectionHandlers;
15308 
15309 		version(without_opengl) {} else
15310 		GLXContext glc;
15311 
15312 		private void fixFixedSize(bool forced=false) (int width, int height) {
15313 			if (forced || this.resizability == Resizability.fixedSize) {
15314 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
15315 				XSizeHints sh;
15316 				static if (!forced) {
15317 					c_long spr;
15318 					XGetWMNormalHints(display, window, &sh, &spr);
15319 					sh.flags |= PMaxSize | PMinSize;
15320 				} else {
15321 					sh.flags = PMaxSize | PMinSize;
15322 				}
15323 				sh.min_width = width;
15324 				sh.min_height = height;
15325 				sh.max_width = width;
15326 				sh.max_height = height;
15327 				XSetWMNormalHints(display, window, &sh);
15328 				//XFlush(display);
15329 			}
15330 		}
15331 
15332 		ScreenPainter getPainter(bool manualInvalidations) {
15333 			return ScreenPainter(this, window, manualInvalidations);
15334 		}
15335 
15336 		void move(int x, int y) {
15337 			XMoveWindow(display, window, x, y);
15338 		}
15339 
15340 		void resize(int w, int h) {
15341 			if (w < 1) w = 1;
15342 			if (h < 1) h = 1;
15343 			XResizeWindow(display, window, w, h);
15344 
15345 			// calling this now to avoid waiting for the server to
15346 			// acknowledge the resize; draws without returning to the
15347 			// event loop will thus actually work. the server's event
15348 			// btw might overrule this and resize it again
15349 			recordX11Resize(display, this, w, h);
15350 
15351 			updateOpenglViewportIfNeeded(w, h);
15352 		}
15353 
15354 		void moveResize (int x, int y, int w, int h) {
15355 			if (w < 1) w = 1;
15356 			if (h < 1) h = 1;
15357 			XMoveResizeWindow(display, window, x, y, w, h);
15358 			updateOpenglViewportIfNeeded(w, h);
15359 		}
15360 
15361 		void hideCursor () {
15362 			if (curHidden++ == 0) {
15363 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
15364 					static const(char)[1] cmbmp = 0;
15365 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
15366 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
15367 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
15368 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
15369 					XFreePixmap(display, pm);
15370 				}
15371 				XDefineCursor(display, window, blankCurPtr);
15372 			}
15373 		}
15374 
15375 		void showCursor () {
15376 			if (--curHidden == 0) XUndefineCursor(display, window);
15377 		}
15378 
15379 		void warpMouse (int x, int y) {
15380 			// here i will send dummy "ignore next mouse motion" event,
15381 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
15382 			// and we don't need to report it to the user (as warping is
15383 			// used when the user needs movement deltas).
15384 			//XClientMessageEvent xclient;
15385 			XEvent e;
15386 			e.xclient.type = EventType.ClientMessage;
15387 			e.xclient.window = window;
15388 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15389 			e.xclient.format = 32;
15390 			e.xclient.data.l[0] = 0;
15391 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
15392 			//{ 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]); }
15393 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15394 			// now warp pointer...
15395 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
15396 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
15397 			// ...and flush
15398 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
15399 			XFlush(display);
15400 		}
15401 
15402 		void sendDummyEvent () {
15403 			// here i will send dummy event to ping event queue
15404 			XEvent e;
15405 			e.xclient.type = EventType.ClientMessage;
15406 			e.xclient.window = window;
15407 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15408 			e.xclient.format = 32;
15409 			e.xclient.data.l[0] = 0;
15410 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15411 			XFlush(display);
15412 		}
15413 
15414 		void setTitle(string title) {
15415 			if (title.ptr is null) title = "";
15416 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15417 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15418 			XTextProperty windowName;
15419 			windowName.value = title.ptr;
15420 			windowName.encoding = XA_UTF8; //XA_STRING;
15421 			windowName.format = 8;
15422 			windowName.nitems = cast(uint)title.length;
15423 			XSetWMName(display, window, &windowName);
15424 			char[1024] namebuf = 0;
15425 			auto maxlen = namebuf.length-1;
15426 			if (maxlen > title.length) maxlen = title.length;
15427 			namebuf[0..maxlen] = title[0..maxlen];
15428 			XStoreName(display, window, namebuf.ptr);
15429 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
15430 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
15431 		}
15432 
15433 		string[] getTitles() {
15434 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15435 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15436 			XTextProperty textProp;
15437 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
15438 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
15439 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
15440 				} else
15441 					return [];
15442 			} else
15443 				return null;
15444 		}
15445 
15446 		string getTitle() {
15447 			auto titles = getTitles();
15448 			return titles.length ? titles[0] : null;
15449 		}
15450 
15451 		void setMinSize (int minwidth, int minheight) {
15452 			import core.stdc.config : c_long;
15453 			if (minwidth < 1) minwidth = 1;
15454 			if (minheight < 1) minheight = 1;
15455 			XSizeHints sh;
15456 			c_long spr;
15457 			XGetWMNormalHints(display, window, &sh, &spr);
15458 			sh.min_width = minwidth;
15459 			sh.min_height = minheight;
15460 			sh.flags |= PMinSize;
15461 			XSetWMNormalHints(display, window, &sh);
15462 			flushGui();
15463 		}
15464 
15465 		void setMaxSize (int maxwidth, int maxheight) {
15466 			import core.stdc.config : c_long;
15467 			if (maxwidth < 1) maxwidth = 1;
15468 			if (maxheight < 1) maxheight = 1;
15469 			XSizeHints sh;
15470 			c_long spr;
15471 			XGetWMNormalHints(display, window, &sh, &spr);
15472 			sh.max_width = maxwidth;
15473 			sh.max_height = maxheight;
15474 			sh.flags |= PMaxSize;
15475 			XSetWMNormalHints(display, window, &sh);
15476 			flushGui();
15477 		}
15478 
15479 		void setResizeGranularity (int granx, int grany) {
15480 			import core.stdc.config : c_long;
15481 			if (granx < 1) granx = 1;
15482 			if (grany < 1) grany = 1;
15483 			XSizeHints sh;
15484 			c_long spr;
15485 			XGetWMNormalHints(display, window, &sh, &spr);
15486 			sh.width_inc = granx;
15487 			sh.height_inc = grany;
15488 			sh.flags |= PResizeInc;
15489 			XSetWMNormalHints(display, window, &sh);
15490 			flushGui();
15491 		}
15492 
15493 		void setOpacity (uint opacity) {
15494 			arch_ulong o = opacity;
15495 			if (opacity == uint.max)
15496 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
15497 			else
15498 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
15499 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
15500 		}
15501 
15502 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted {
15503 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
15504 			display = XDisplayConnection.get();
15505 			auto screen = DefaultScreen(display);
15506 
15507 			bool overrideRedirect = false;
15508 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
15509 				overrideRedirect = true;
15510 
15511 			version(without_opengl) {}
15512 			else {
15513 				if(opengl == OpenGlOptions.yes) {
15514 					GLXFBConfig fbconf = null;
15515 					XVisualInfo* vi = null;
15516 					bool useLegacy = false;
15517 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
15518 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
15519 						int[23] visualAttribs = [
15520 							GLX_X_RENDERABLE , 1/*True*/,
15521 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
15522 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
15523 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
15524 							GLX_RED_SIZE     , 8,
15525 							GLX_GREEN_SIZE   , 8,
15526 							GLX_BLUE_SIZE    , 8,
15527 							GLX_ALPHA_SIZE   , 8,
15528 							GLX_DEPTH_SIZE   , 24,
15529 							GLX_STENCIL_SIZE , 8,
15530 							GLX_DOUBLEBUFFER , 1/*True*/,
15531 							0/*None*/,
15532 						];
15533 						int fbcount;
15534 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
15535 						if (fbcount == 0) {
15536 							useLegacy = true; // try to do at least something
15537 						} else {
15538 							// pick the FB config/visual with the most samples per pixel
15539 							int bestidx = -1, bestns = -1;
15540 							foreach (int fbi; 0..fbcount) {
15541 								int sb, samples;
15542 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
15543 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
15544 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
15545 							}
15546 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
15547 							fbconf = fbc[bestidx];
15548 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
15549 							XFree(fbc);
15550 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
15551 						}
15552 					}
15553 					if (vi is null || useLegacy) {
15554 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
15555 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
15556 						useLegacy = true;
15557 					}
15558 					if (vi is null) throw new Exception("no open gl visual found");
15559 
15560 					XSetWindowAttributes swa;
15561 					auto root = RootWindow(display, screen);
15562 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
15563 
15564 					swa.override_redirect = overrideRedirect;
15565 
15566 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15567 						0, 0, width, height,
15568 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
15569 
15570 					// now try to use `glXCreateContextAttribsARB()` if it's here
15571 					if (!useLegacy) {
15572 						// request fairly advanced context, even with stencil buffer!
15573 						int[9] contextAttribs = [
15574 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
15575 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
15576 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
15577 							// for modern context, set "forward compatibility" flag too
15578 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
15579 							0/*None*/,
15580 						];
15581 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
15582 						if (glc is null && sdpyOpenGLContextAllowFallback) {
15583 							sdpyOpenGLContextVersion = 0;
15584 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15585 						}
15586 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
15587 					} else {
15588 						// fallback to old GLX call
15589 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
15590 							sdpyOpenGLContextVersion = 0;
15591 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15592 						}
15593 					}
15594 					// sync to ensure any errors generated are processed
15595 					XSync(display, 0/*False*/);
15596 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
15597 					if(glc is null)
15598 						throw new Exception("glc");
15599 				}
15600 			}
15601 
15602 			if(opengl == OpenGlOptions.no) {
15603 
15604 				XSetWindowAttributes swa;
15605 				swa.background_pixel = WhitePixel(display, screen);
15606 				swa.border_pixel = BlackPixel(display, screen);
15607 				swa.override_redirect = overrideRedirect;
15608 				auto root = RootWindow(display, screen);
15609 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
15610 
15611 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15612 					0, 0, width, height,
15613 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
15614 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
15615 
15616 
15617 
15618 				/*
15619 				window = XCreateSimpleWindow(
15620 					display,
15621 					parent is null ? RootWindow(display, screen) : parent.impl.window,
15622 					0, 0, // x, y
15623 					width, height,
15624 					1, // border width
15625 					BlackPixel(display, screen), // border
15626 					WhitePixel(display, screen)); // background
15627 				*/
15628 
15629 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
15630 				bufferw = width;
15631 				bufferh = height;
15632 
15633 				gc = DefaultGC(display, screen);
15634 
15635 				// clear out the buffer to get us started...
15636 				XSetForeground(display, gc, WhitePixel(display, screen));
15637 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
15638 				XSetForeground(display, gc, BlackPixel(display, screen));
15639 			}
15640 
15641 			// input context
15642 			//TODO: create this only for top-level windows, and reuse that?
15643 			populateXic();
15644 
15645 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
15646 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
15647 			// window class
15648 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
15649 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
15650 				XClassHint klass;
15651 				XWMHints wh;
15652 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15653 					wh.input = true;
15654 					wh.flags |= InputHint;
15655 				}
15656 				XSizeHints size;
15657 				klass.res_name = sdpyWindowClassStr;
15658 				klass.res_class = sdpyWindowClassStr;
15659 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
15660 			}
15661 
15662 			setTitle(title);
15663 			SimpleWindow.nativeMapping[window] = this;
15664 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
15665 
15666 			// This gives our window a close button
15667 			if (windowType != WindowTypes.eventOnly) {
15668 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
15669 				int useAtoms;
15670 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15671 					useAtoms = 2;
15672 				} else {
15673 					useAtoms = 1;
15674 				}
15675 				assert(useAtoms <= atoms.length);
15676 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
15677 			}
15678 
15679 			// FIXME: windowType and customizationFlags
15680 			Atom[8] wsatoms; // here, due to goto
15681 			int wmsacount = 0; // here, due to goto
15682 
15683 			try
15684 			final switch(windowType) {
15685 				case WindowTypes.normal:
15686 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15687 				break;
15688 				case WindowTypes.undecorated:
15689 					motifHideDecorations();
15690 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15691 				break;
15692 				case WindowTypes.eventOnly:
15693 					_hidden = true;
15694 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
15695 					goto hiddenWindow;
15696 				//break;
15697 				case WindowTypes.nestedChild:
15698 					// handled in XCreateWindow calls
15699 				break;
15700 
15701 				case WindowTypes.dropdownMenu:
15702 					motifHideDecorations();
15703 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
15704 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15705 				break;
15706 				case WindowTypes.popupMenu:
15707 					motifHideDecorations();
15708 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
15709 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15710 				break;
15711 				case WindowTypes.notification:
15712 					motifHideDecorations();
15713 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
15714 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15715 				break;
15716 				case WindowTypes.minimallyWrapped:
15717 					assert(0, "don't create a minimallyWrapped thing explicitly!");
15718 
15719 				case WindowTypes.dialog:
15720 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display));
15721 				break;
15722 				/+
15723 				case WindowTypes.menu:
15724 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15725 					motifHideDecorations();
15726 				break;
15727 				case WindowTypes.desktop:
15728 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
15729 				break;
15730 				case WindowTypes.dock:
15731 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
15732 				break;
15733 				case WindowTypes.toolbar:
15734 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
15735 				break;
15736 				case WindowTypes.menu:
15737 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15738 				break;
15739 				case WindowTypes.utility:
15740 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
15741 				break;
15742 				case WindowTypes.splash:
15743 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
15744 				break;
15745 				case WindowTypes.tooltip:
15746 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
15747 				break;
15748 				case WindowTypes.notification:
15749 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
15750 				break;
15751 				case WindowTypes.combo:
15752 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
15753 				break;
15754 				case WindowTypes.dnd:
15755 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
15756 				break;
15757 				+/
15758 			}
15759 			catch(Exception e) {
15760 				// XInternAtom failed, prolly a WM
15761 				// that doesn't support these things
15762 			}
15763 
15764 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15765 			// the two following flags may be ignored by WM
15766 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15767 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15768 
15769 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15770 
15771 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15772 
15773 			// What would be ideal here is if they only were
15774 			// selected if there was actually an event handler
15775 			// for them...
15776 
15777 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15778 
15779 			hiddenWindow:
15780 
15781 			// set the pid property for lookup later by window managers
15782 			// a standard convenience
15783 			import core.sys.posix.unistd;
15784 			arch_ulong pid = getpid();
15785 
15786 			XChangeProperty(
15787 				display,
15788 				impl.window,
15789 				GetAtom!("_NET_WM_PID", true)(display),
15790 				XA_CARDINAL,
15791 				32 /* bits */,
15792 				0 /*PropModeReplace*/,
15793 				&pid,
15794 				1);
15795 
15796 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15797 				if(parent is null) assert(0);
15798 				// sdpyPrintDebugString("transient");
15799 				XChangeProperty(
15800 					display,
15801 					impl.window,
15802 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15803 					XA_WINDOW,
15804 					32 /* bits */,
15805 					0 /*PropModeReplace*/,
15806 					&parent.impl.window,
15807 					1);
15808 
15809 			}
15810 
15811 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15812 				XMapWindow(display, window);
15813 			} else {
15814 				_hidden = true;
15815 			}
15816 		}
15817 
15818 		void populateXic() {
15819 			if (XDisplayConnection.xim !is null) {
15820 				xic = XCreateIC(XDisplayConnection.xim,
15821 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15822 						/*XNClientWindow*/"clientWindow".ptr, window,
15823 						/*XNFocusWindow*/"focusWindow".ptr, window,
15824 						null);
15825 				if (xic is null) {
15826 					import core.stdc.stdio : stderr, fprintf;
15827 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15828 				}
15829 			}
15830 		}
15831 
15832 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15833 			auto mask = EventMask.ExposureMask |
15834 				EventMask.KeyPressMask |
15835 				EventMask.KeyReleaseMask |
15836 				EventMask.PropertyChangeMask |
15837 				EventMask.FocusChangeMask |
15838 				EventMask.StructureNotifyMask |
15839 				EventMask.SubstructureNotifyMask |
15840 				EventMask.VisibilityChangeMask
15841 				| EventMask.ButtonPressMask
15842 				| EventMask.ButtonReleaseMask
15843 			;
15844 
15845 			// xshm is our shortcut for local connections
15846 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15847 				mask |= EventMask.PointerMotionMask;
15848 			else
15849 				mask |= EventMask.ButtonMotionMask;
15850 
15851 			XSelectInput(display, window, mask);
15852 		}
15853 
15854 
15855 		void setNetWMWindowType(Atom type) {
15856 			Atom[2] atoms;
15857 
15858 			atoms[0] = type;
15859 			// generic fallback
15860 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15861 
15862 			XChangeProperty(
15863 				display,
15864 				impl.window,
15865 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15866 				XA_ATOM,
15867 				32 /* bits */,
15868 				0 /*PropModeReplace*/,
15869 				atoms.ptr,
15870 				cast(int) atoms.length);
15871 		}
15872 
15873 		void motifHideDecorations(bool hide = true) {
15874 			MwmHints hints;
15875 			hints.flags = MWM_HINTS_DECORATIONS;
15876 			hints.decorations = hide ? 0 : 1;
15877 
15878 			XChangeProperty(
15879 				display,
15880 				impl.window,
15881 				GetAtom!"_MOTIF_WM_HINTS"(display),
15882 				GetAtom!"_MOTIF_WM_HINTS"(display),
15883 				32 /* bits */,
15884 				0 /*PropModeReplace*/,
15885 				&hints,
15886 				hints.sizeof / 4);
15887 		}
15888 
15889 		/*k8: unused
15890 		void createOpenGlContext() {
15891 
15892 		}
15893 		*/
15894 
15895 		void closeWindow() {
15896 			// I can't close this or a child window closing will
15897 			// break events for everyone. So I'm just leaking it right
15898 			// now and that is probably perfectly fine...
15899 			version(none)
15900 			if (customEventFDRead != -1) {
15901 				import core.sys.posix.unistd : close;
15902 				auto same = customEventFDRead == customEventFDWrite;
15903 
15904 				close(customEventFDRead);
15905 				if(!same)
15906 					close(customEventFDWrite);
15907 				customEventFDRead = -1;
15908 				customEventFDWrite = -1;
15909 			}
15910 
15911 			version(without_opengl) {} else
15912 			if(glc !is null) {
15913 				glXDestroyContext(display, glc);
15914 				glc = null;
15915 			}
15916 
15917 			if(buffer)
15918 				XFreePixmap(display, buffer);
15919 			bufferw = bufferh = 0;
15920 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15921 			XDestroyWindow(display, window);
15922 			XFlush(display);
15923 		}
15924 
15925 		void dispose() {
15926 		}
15927 
15928 		bool destroyed = false;
15929 	}
15930 
15931 	bool insideXEventLoop;
15932 }
15933 
15934 version(X11) {
15935 
15936 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15937 
15938 	private class ResizeEvent {
15939 		int width, height;
15940 	}
15941 
15942 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15943 		if(win.windowType == WindowTypes.minimallyWrapped)
15944 			return;
15945 
15946 		if(win.pendingResizeEvent is null) {
15947 			win.pendingResizeEvent = new ResizeEvent();
15948 			win.addEventListener((ResizeEvent re) {
15949 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15950 			});
15951 		}
15952 		win.pendingResizeEvent.width = width;
15953 		win.pendingResizeEvent.height = height;
15954 		if(!win.eventQueued!ResizeEvent) {
15955 			win.postEvent(win.pendingResizeEvent);
15956 		}
15957 	}
15958 
15959 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15960 		if(win.windowType == WindowTypes.minimallyWrapped)
15961 			return;
15962 		if(win.closed)
15963 			return;
15964 
15965 		if(width != win.width || height != win.height) {
15966 
15967 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15968 			win._width = width;
15969 			win._height = height;
15970 
15971 			if(win.openglMode == OpenGlOptions.no) {
15972 				// FIXME: could this be more efficient?
15973 
15974 				if (win.bufferw < width || win.bufferh < height) {
15975 					//{ 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); }
15976 					// grow the internal buffer to match the window...
15977 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15978 					{
15979 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15980 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15981 						scope(exit) XFreeGC(win.display, xgc);
15982 						XSetClipMask(win.display, xgc, None);
15983 						XSetForeground(win.display, xgc, 0);
15984 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15985 					}
15986 					XCopyArea(display,
15987 						cast(Drawable) win.buffer,
15988 						cast(Drawable) newPixmap,
15989 						win.gc, 0, 0,
15990 						win.bufferw < width ? win.bufferw : win.width,
15991 						win.bufferh < height ? win.bufferh : win.height,
15992 						0, 0);
15993 
15994 					XFreePixmap(display, win.buffer);
15995 					win.buffer = newPixmap;
15996 					win.bufferw = width;
15997 					win.bufferh = height;
15998 				}
15999 
16000 				// clear unused parts of the buffer
16001 				if (win.bufferw > width || win.bufferh > height) {
16002 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
16003 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
16004 					scope(exit) XFreeGC(win.display, xgc);
16005 					XSetClipMask(win.display, xgc, None);
16006 					XSetForeground(win.display, xgc, 0);
16007 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
16008 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
16009 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
16010 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
16011 				}
16012 
16013 			}
16014 
16015 			win.updateOpenglViewportIfNeeded(width, height);
16016 
16017 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
16018 
16019 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
16020 			if(win.windowResized !is null) {
16021 				XUnlockDisplay(display);
16022 				scope(exit) XLockDisplay(display);
16023 				win.windowResized(width, height);
16024 			}
16025 		}
16026 	}
16027 
16028 
16029 	/// Platform-specific, you might use it when doing a custom event loop.
16030 	bool doXNextEvent(Display* display) {
16031 		bool done;
16032 		XEvent e;
16033 		XNextEvent(display, &e);
16034 		version(sddddd) {
16035 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16036 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
16037 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
16038 			}
16039 		}
16040 
16041 		// filter out compose events
16042 		if (XFilterEvent(&e, None)) {
16043 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
16044 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
16045 			return false;
16046 		}
16047 		// process keyboard mapping changes
16048 		if (e.type == EventType.KeymapNotify) {
16049 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
16050 			XRefreshKeyboardMapping(&e.xmapping);
16051 			return false;
16052 		}
16053 
16054 		version(with_eventloop)
16055 			import arsd.eventloop;
16056 
16057 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
16058 			// see windows impl's comments
16059 			XUnlockDisplay(display);
16060 			scope(exit) XLockDisplay(display);
16061 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
16062 			if(ret == 0)
16063 				return done;
16064 		}
16065 
16066 
16067 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16068 			if(win.getNativeEventHandler !is null) {
16069 				XUnlockDisplay(display);
16070 				scope(exit) XLockDisplay(display);
16071 				auto ret = win.getNativeEventHandler()(e);
16072 				if(ret == 0)
16073 					return done;
16074 			}
16075 		}
16076 
16077 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
16078 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
16079 				// we get this because of the RRScreenChangeNotifyMask
16080 
16081 				// this isn't actually an ideal way to do it since it wastes time
16082 				// but meh it is simple and it works.
16083 				win.actualDpiLoadAttempted = false;
16084 				SimpleWindow.xRandrInfoLoadAttemped = false;
16085 				win.updateActualDpi(); // trigger a reload
16086 			}
16087 		}
16088 
16089 		switch(e.type) {
16090 		  case EventType.SelectionClear:
16091 		  	// writeln("SelectionClear");
16092 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
16093 				// FIXME so it is supposed to finish any in progress transfers... but idk...
16094 				// writeln("SelectionClear");
16095 			}
16096 			SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
16097 			mightShortCircuitClipboard = false;
16098 		  break;
16099 		  case EventType.SelectionRequest:
16100 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
16101 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
16102 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
16103 				XUnlockDisplay(display);
16104 				scope(exit) XLockDisplay(display);
16105 				(*ssh).handleRequest(e);
16106 			}
16107 		  break;
16108 		  case EventType.PropertyNotify:
16109 			// import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
16110 
16111 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
16112 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
16113 					ssh.sendMoreIncr(&e.xproperty);
16114 			}
16115 
16116 
16117 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
16118 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16119 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
16120 					Atom target;
16121 					int format;
16122 					arch_ulong bytesafter, length;
16123 					void* value;
16124 
16125 					ubyte[] s;
16126 					Atom targetToKeep;
16127 
16128 					XGetWindowProperty(
16129 						e.xproperty.display,
16130 						e.xproperty.window,
16131 						e.xproperty.atom,
16132 						0,
16133 						100000 /* length */,
16134 						true, /* erase it to signal we got it and want more */
16135 						0 /*AnyPropertyType*/,
16136 						&target, &format, &length, &bytesafter, &value);
16137 
16138 					if(!targetToKeep)
16139 						targetToKeep = target;
16140 
16141 					auto id = (cast(ubyte*) value)[0 .. length];
16142 
16143 					handler.handleIncrData(targetToKeep, id);
16144 					if(length == 0) {
16145 						win.getSelectionHandlers.remove(e.xproperty.atom);
16146 					}
16147 
16148 					XFree(value);
16149 				}
16150 			}
16151 		  break;
16152 		  case EventType.SelectionNotify:
16153 		  	// import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom);
16154 			if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
16155 			if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16156 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
16157 					XUnlockDisplay(display);
16158 					scope(exit) XLockDisplay(display);
16159 					handler.handleData(None, null);
16160 					win.getSelectionHandlers.remove(e.xproperty.atom);
16161 				} else {
16162 					Atom target;
16163 					int format;
16164 					arch_ulong bytesafter, length;
16165 					void* value;
16166 					XGetWindowProperty(
16167 						e.xselection.display,
16168 						e.xselection.requestor,
16169 						e.xselection.property,
16170 						0,
16171 						100000 /* length */,
16172 						//false, /* don't erase it */
16173 						true, /* do erase it lol */
16174 						0 /*AnyPropertyType*/,
16175 						&target, &format, &length, &bytesafter, &value);
16176 
16177 					// FIXME: I don't have to copy it now since it is in char[] instead of string
16178 
16179 					{
16180 						XUnlockDisplay(display);
16181 						scope(exit) XLockDisplay(display);
16182 
16183 						if(target == XA_ATOM) {
16184 							// initial request, see what they are able to work with and request the best one
16185 							// we can handle, if available
16186 
16187 							Atom[] answer = (cast(Atom*) value)[0 .. length];
16188 							Atom best = handler.findBestFormat(answer);
16189 
16190 							/+
16191 							writeln("got ", answer);
16192 							foreach(a; answer)
16193 								writeln(XGetAtomName(display, a).stringz);
16194 							writeln("best ", best);
16195 							+/
16196 
16197 							if(best != None) {
16198 								// actually request the best format
16199 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
16200 							}
16201 						} else if(target == GetAtom!"INCR"(display)) {
16202 							// incremental
16203 
16204 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
16205 
16206 							// signal the sending program that we see
16207 							// the incr and are ready to receive more.
16208 							XDeleteProperty(
16209 								e.xselection.display,
16210 								e.xselection.requestor,
16211 								e.xselection.property);
16212 						} else {
16213 							// unsupported type... maybe, forward, then we done with it
16214 							if(target != None) {
16215 								handler.handleData(target, cast(ubyte[]) value[0 .. length]);
16216 								win.getSelectionHandlers.remove(e.xproperty.atom);
16217 							}
16218 						}
16219 					}
16220 					XFree(value);
16221 					/*
16222 					XDeleteProperty(
16223 						e.xselection.display,
16224 						e.xselection.requestor,
16225 						e.xselection.property);
16226 					*/
16227 				}
16228 			}
16229 			break;
16230 		  case EventType.ConfigureNotify:
16231 			auto event = e.xconfigure;
16232 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
16233 				if(win.windowType == WindowTypes.minimallyWrapped)
16234 					break;
16235 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
16236 
16237 				/+
16238 					The ICCCM says window managers must send a synthetic event when the window
16239 					is moved but NOT when it is resized. In the resize case, an event is sent
16240 					with position (0, 0) which can be wrong and break the dpi calculations.
16241 
16242 					So we only consider the synthetic events from the WM and otherwise
16243 					need to wait for some other event to get the position which... sucks.
16244 
16245 					I'd rather not have windows changing their layout on mouse motion after
16246 					switching monitors... might be forced to but for now just ignoring it.
16247 
16248 					Easiest way to switch monitors without sending a size position is by
16249 					maximize or fullscreen in a setup like mine, but on most setups those
16250 					work on the monitor it is already living on, so it should be ok most the
16251 					time.
16252 				+/
16253 				if(event.send_event) {
16254 					win.screenPositionKnown = true;
16255 					win.screenPositionX = event.x;
16256 					win.screenPositionY = event.y;
16257 					win.updateActualDpi();
16258 				}
16259 
16260 				win.updateIMEPopupLocation();
16261 				recordX11ResizeAsync(display, *win, event.width, event.height);
16262 			}
16263 		  break;
16264 		  case EventType.Expose:
16265 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
16266 				if(win.windowType == WindowTypes.minimallyWrapped)
16267 					break;
16268 				// if it is closing from a popup menu, it can get
16269 				// an Expose event right by the end and trigger a
16270 				// BadDrawable error ... we'll just check
16271 				// closed to handle that.
16272 				if((*win).closed) break;
16273 				if((*win).openglMode == OpenGlOptions.no) {
16274 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
16275 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
16276 					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);
16277 				} else {
16278 					// need to redraw the scene somehow
16279 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
16280 						XUnlockDisplay(display);
16281 						scope(exit) XLockDisplay(display);
16282 						version(without_opengl) {} else
16283 						win.redrawOpenGlSceneSoon();
16284 					}
16285 				}
16286 			}
16287 		  break;
16288 		  case EventType.FocusIn:
16289 		  case EventType.FocusOut:
16290 			mightShortCircuitClipboard = false; // if the focus has changed, good chance the clipboard cache is invalidated too, kinda hacky but always better to skip when unnecessary than use when we shouldn't have
16291 
16292 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16293 				/+
16294 
16295 				void info(string detail) {
16296 					string s;
16297 					// import std.conv;
16298 					// import std.datetime;
16299 					s ~= to!string(Clock.currTime);
16300 					s ~= " ";
16301 					s ~= e.type == EventType.FocusIn ? "in " : "out";
16302 					s ~= " ";
16303 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
16304 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
16305 					s ~= detail;
16306 					s ~= " ";
16307 
16308 					sdpyPrintDebugString(s);
16309 
16310 				}
16311 
16312 				switch(e.xfocus.detail) {
16313 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
16314 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
16315 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
16316 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
16317 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
16318 					case NotifyDetail.NotifyPointer: info("pointer"); break;
16319 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
16320 					case NotifyDetail.NotifyDetailNone: info("none"); break;
16321 					default:
16322 
16323 				}
16324 				+/
16325 
16326 
16327 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
16328 					break; // just ignore these they seem irrelevant
16329 
16330 				auto old = win._focused;
16331 				win._focused = e.type == EventType.FocusIn;
16332 
16333 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
16334 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
16335 					win._focused = true;
16336 
16337 				if(win.demandingAttention)
16338 					demandAttention(*win, false);
16339 
16340 				win.updateIMEFocused();
16341 
16342 				if(old != win._focused && win.onFocusChange) {
16343 					XUnlockDisplay(display);
16344 					scope(exit) XLockDisplay(display);
16345 					win.onFocusChange(win._focused);
16346 				}
16347 			}
16348 		  break;
16349 		  case EventType.VisibilityNotify:
16350 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16351 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
16352 						if (win.visibilityChanged !is null) {
16353 								XUnlockDisplay(display);
16354 								scope(exit) XLockDisplay(display);
16355 								win.visibilityChanged(false);
16356 							}
16357 					} else {
16358 						if (win.visibilityChanged !is null) {
16359 							XUnlockDisplay(display);
16360 							scope(exit) XLockDisplay(display);
16361 							win.visibilityChanged(true);
16362 						}
16363 					}
16364 				}
16365 				break;
16366 		  case EventType.ClientMessage:
16367 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
16368 					// "ignore next mouse motion" event, increment ignore counter for teh window
16369 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16370 						++(*win).warpEventCount;
16371 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
16372 					} else {
16373 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
16374 					}
16375 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
16376 					// user clicked the close button on the window manager
16377 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16378 						XUnlockDisplay(display);
16379 						scope(exit) XLockDisplay(display);
16380 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
16381 					}
16382 
16383 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
16384 					// writeln("HAPPENED");
16385 					// user clicked the close button on the window manager
16386 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16387 						XUnlockDisplay(display);
16388 						scope(exit) XLockDisplay(display);
16389 
16390 						auto setTo = *win;
16391 
16392 						if(win.setRequestedInputFocus !is null) {
16393 							auto s = win.setRequestedInputFocus();
16394 							if(s !is null) {
16395 								setTo = s;
16396 							}
16397 						}
16398 
16399 						assert(setTo !is null);
16400 
16401 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
16402 
16403 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
16404 					}
16405 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
16406 					foreach(nai; NotificationAreaIcon.activeIcons)
16407 						nai.newManager();
16408 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16409 
16410 					bool xDragWindow = true;
16411 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
16412 						//XDefineCursor(display, xDragWindow.impl.window,
16413 							//writeln("XdndStatus ", e.xclient.data.l);
16414 					}
16415 					if(auto dh = win.dropHandler) {
16416 
16417 						static Atom[3] xFormatsBuffer;
16418 						static Atom[] xFormats;
16419 
16420 						void resetXFormats() {
16421 							xFormatsBuffer[] = 0;
16422 							xFormats = xFormatsBuffer[];
16423 						}
16424 
16425 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
16426 							// on Windows it is supposed to return the effect you actually do FIXME
16427 
16428 							auto sourceWindow =  e.xclient.data.l[0];
16429 
16430 							xFormatsBuffer[0] = e.xclient.data.l[2];
16431 							xFormatsBuffer[1] = e.xclient.data.l[3];
16432 							xFormatsBuffer[2] = e.xclient.data.l[4];
16433 
16434 							if(e.xclient.data.l[1] & 1) {
16435 								// can just grab it all but like we don't necessarily need them...
16436 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
16437 							} else {
16438 								int len;
16439 								foreach(fmt; xFormatsBuffer)
16440 									if(fmt) len++;
16441 								xFormats = xFormatsBuffer[0 .. len];
16442 							}
16443 
16444 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
16445 
16446 							dh.dragEnter(&pkg);
16447 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
16448 
16449 							auto pack = e.xclient.data.l[2];
16450 
16451 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
16452 
16453 
16454 							XClientMessageEvent xclient;
16455 
16456 							xclient.type = EventType.ClientMessage;
16457 							xclient.window = e.xclient.data.l[0];
16458 							xclient.message_type = GetAtom!"XdndStatus"(display);
16459 							xclient.format = 32;
16460 							xclient.data.l[0] = win.impl.window;
16461 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
16462 							auto r = result.consistentWithin;
16463 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
16464 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
16465 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
16466 
16467 							XSendEvent(
16468 								display,
16469 								e.xclient.data.l[0],
16470 								false,
16471 								EventMask.NoEventMask,
16472 								cast(XEvent*) &xclient
16473 							);
16474 
16475 
16476 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
16477 							//writeln("XdndLeave");
16478 							// drop cancelled.
16479 							// data.l[0] is the source window
16480 							dh.dragLeave();
16481 
16482 							resetXFormats();
16483 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
16484 							// drop happening, should fetch data, then send finished
16485 							// writeln("XdndDrop");
16486 
16487 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
16488 
16489 							dh.drop(&pkg);
16490 
16491 							resetXFormats();
16492 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
16493 							// writeln("XdndFinished");
16494 
16495 							dh.finish();
16496 						}
16497 
16498 					}
16499 				}
16500 		  break;
16501 		  case EventType.MapNotify:
16502 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
16503 					(*win)._visible = true;
16504 					if (!(*win)._visibleForTheFirstTimeCalled) {
16505 						(*win)._visibleForTheFirstTimeCalled = true;
16506 						if ((*win).visibleForTheFirstTime !is null) {
16507 							XUnlockDisplay(display);
16508 							scope(exit) XLockDisplay(display);
16509 							(*win).visibleForTheFirstTime();
16510 						}
16511 					}
16512 					if ((*win).visibilityChanged !is null) {
16513 						XUnlockDisplay(display);
16514 						scope(exit) XLockDisplay(display);
16515 						(*win).visibilityChanged(true);
16516 					}
16517 				}
16518 		  break;
16519 		  case EventType.UnmapNotify:
16520 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
16521 					win._visible = false;
16522 					if (win.visibilityChanged !is null) {
16523 						XUnlockDisplay(display);
16524 						scope(exit) XLockDisplay(display);
16525 						win.visibilityChanged(false);
16526 					}
16527 			}
16528 		  break;
16529 		  case EventType.DestroyNotify:
16530 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
16531 				if(win.destroyed)
16532 					break; // might get a notification both for itself and from its parent
16533 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
16534 				win._closed = true; // just in case
16535 				win.destroyed = true;
16536 				if (win.xic !is null) {
16537 					XDestroyIC(win.xic);
16538 					win.xic = null; // just in case
16539 				}
16540 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
16541 				bool anyImportant = false;
16542 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
16543 					if(w.beingOpenKeepsAppOpen) {
16544 						anyImportant = true;
16545 						break;
16546 					}
16547 				if(!anyImportant) {
16548 					EventLoop.quitApplication();
16549 					done = true;
16550 				}
16551 			}
16552 			auto window = e.xdestroywindow.window;
16553 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
16554 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
16555 
16556 			version(with_eventloop) {
16557 				if(done) exit();
16558 			}
16559 		  break;
16560 
16561 		  case EventType.MotionNotify:
16562 			MouseEvent mouse;
16563 			auto event = e.xmotion;
16564 
16565 			mouse.type = MouseEventType.motion;
16566 			mouse.x = event.x;
16567 			mouse.y = event.y;
16568 			mouse.modifierState = event.state;
16569 
16570 			mouse.timestamp = event.time;
16571 
16572 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
16573 				mouse.window = *win;
16574 				if (win.warpEventCount > 0) {
16575 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
16576 					--(*win).warpEventCount;
16577 					(*win).mdx(mouse); // so deltas will be correctly updated
16578 				} else {
16579 					win.warpEventCount = 0; // just in case
16580 					(*win).mdx(mouse);
16581 					if((*win).handleMouseEvent) {
16582 						XUnlockDisplay(display);
16583 						scope(exit) XLockDisplay(display);
16584 						(*win).handleMouseEvent(mouse);
16585 					}
16586 				}
16587 			}
16588 
16589 		  	version(with_eventloop)
16590 				send(mouse);
16591 		  break;
16592 		  case EventType.ButtonPress:
16593 		  case EventType.ButtonRelease:
16594 			MouseEvent mouse;
16595 			auto event = e.xbutton;
16596 
16597 			mouse.timestamp = event.time;
16598 
16599 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
16600 			mouse.x = event.x;
16601 			mouse.y = event.y;
16602 
16603 			static Time lastMouseDownTime = 0;
16604 			static int lastMouseDownButton = -1;
16605 
16606 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
16607 			if(e.type == EventType.ButtonPress) {
16608 				lastMouseDownTime = event.time;
16609 				lastMouseDownButton = event.button;
16610 			}
16611 
16612 			switch(event.button) {
16613 				case 1: mouse.button = MouseButton.left; break; // left
16614 				case 2: mouse.button = MouseButton.middle; break; // middle
16615 				case 3: mouse.button = MouseButton.right; break; // right
16616 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
16617 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
16618 				case 6: break; // idk
16619 				case 7: break; // idk
16620 				case 8: mouse.button = MouseButton.backButton; break;
16621 				case 9: mouse.button = MouseButton.forwardButton; break;
16622 				default:
16623 			}
16624 
16625 			// FIXME: double check this
16626 			mouse.modifierState = event.state;
16627 
16628 			//mouse.modifierState = event.detail;
16629 
16630 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
16631 				mouse.window = *win;
16632 				(*win).mdx(mouse);
16633 				if((*win).handleMouseEvent) {
16634 					XUnlockDisplay(display);
16635 					scope(exit) XLockDisplay(display);
16636 					(*win).handleMouseEvent(mouse);
16637 				}
16638 			}
16639 			version(with_eventloop)
16640 				send(mouse);
16641 		  break;
16642 
16643 		  case EventType.KeyPress:
16644 		  case EventType.KeyRelease:
16645 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
16646 			KeyEvent ke;
16647 			ke.pressed = e.type == EventType.KeyPress;
16648 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
16649 
16650 			auto sym = XKeycodeToKeysym(
16651 				XDisplayConnection.get(),
16652 				e.xkey.keycode,
16653 				0);
16654 
16655 			ke.key = cast(Key) sym;//e.xkey.keycode;
16656 
16657 			ke.modifierState = e.xkey.state;
16658 
16659 			// writefln("%x", sym);
16660 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
16661 			int charbuflen = 0; // return value of XwcLookupString
16662 			if (ke.pressed) {
16663 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
16664 				if (win !is null && win.xic !is null) {
16665 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
16666 					Status status;
16667 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
16668 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
16669 				} else {
16670 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
16671 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
16672 					char[16] buffer;
16673 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
16674 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
16675 				}
16676 			}
16677 
16678 			// if there's no char, subst one
16679 			if (charbuflen == 0) {
16680 				switch (sym) {
16681 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
16682 					case 0xff8d: // keypad enter
16683 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
16684 					default : // ignore
16685 				}
16686 			}
16687 
16688 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
16689 				ke.window = *win;
16690 
16691 
16692 				if(win.inputProxy)
16693 					win = &win.inputProxy;
16694 
16695 				// char events are separate since they are on Windows too
16696 				// also, xcompose can generate long char sequences
16697 				// don't send char events if Meta and/or Hyper is pressed
16698 				// TODO: ctrl+char should only send control chars; not yet
16699 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
16700 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
16701 				}
16702 
16703 				dchar[32] charsComingBuffer;
16704 				int charsComingPosition;
16705 				dchar[] charsComing = charsComingBuffer[];
16706 
16707 				if (ke.pressed && charbuflen > 0) {
16708 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
16709 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
16710 						if(charsComingPosition >= charsComing.length)
16711 							charsComing.length = charsComingPosition + 8;
16712 
16713 						charsComing[charsComingPosition++] = ch;
16714 					}
16715 
16716 					charsComing = charsComing[0 .. charsComingPosition];
16717 				} else {
16718 					charsComing = null;
16719 				}
16720 
16721 				ke.charsPossible = charsComing;
16722 
16723 				if (win.handleKeyEvent) {
16724 					XUnlockDisplay(display);
16725 					scope(exit) XLockDisplay(display);
16726 					win.handleKeyEvent(ke);
16727 				}
16728 
16729 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
16730 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
16731 					XUnlockDisplay(display);
16732 					scope(exit) XLockDisplay(display);
16733 					foreach(ch; charsComing)
16734 						win.handleCharEvent(ch);
16735 				}
16736 			}
16737 
16738 			version(with_eventloop)
16739 				send(ke);
16740 		  break;
16741 		  default:
16742 		}
16743 
16744 		return done;
16745 	}
16746 }
16747 
16748 /* *************************************** */
16749 /*      Done with simpledisplay stuff      */
16750 /* *************************************** */
16751 
16752 // Necessary C library bindings follow
16753 version(Windows) {} else
16754 version(Emscripten) {} else
16755 version(X11) {
16756 
16757 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
16758 
16759 // X11 bindings needed here
16760 /*
16761 	A little of this is from the bindings project on
16762 	D Source and some of it is copy/paste from the C
16763 	header.
16764 
16765 	The DSource listing consistently used D's long
16766 	where C used long. That's wrong - C long is 32 bit, so
16767 	it should be int in D. I changed that here.
16768 
16769 	Note:
16770 	This isn't complete, just took what I needed for myself.
16771 */
16772 
16773 import core.stdc.stddef : wchar_t;
16774 
16775 interface XLib {
16776 extern(C) nothrow @nogc {
16777 	char* XResourceManagerString(Display*);
16778 	void XrmInitialize();
16779 	XrmDatabase XrmGetStringDatabase(char* data);
16780 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16781 
16782 	Cursor XCreateFontCursor(Display*, uint shape);
16783 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16784 	int XUndefineCursor(Display* display, Window w);
16785 
16786 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16787 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16788 	int XFreeCursor(Display* display, Cursor cursor);
16789 
16790 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16791 
16792 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16793 
16794 	XVaNestedList XVaCreateNestedList(int unused, ...);
16795 
16796 	char *XKeysymToString(KeySym keysym);
16797 	KeySym XKeycodeToKeysym(
16798 		Display*		/* display */,
16799 		KeyCode		/* keycode */,
16800 		int			/* index */
16801 	);
16802 
16803 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16804 
16805 	int XFree(void*);
16806 	int XDeleteProperty(Display *display, Window w, Atom property);
16807 
16808 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16809 
16810 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16811 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16812 		*actual_type_return, int *actual_format_return, arch_ulong
16813 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16814 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16815 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16816 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16817 
16818 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16819 
16820 	Window XGetSelectionOwner(Display *display, Atom selection);
16821 
16822 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16823 
16824 	char** XListFonts(Display*, const char*, int, int*);
16825 	void XFreeFontNames(char**);
16826 
16827 	Display* XOpenDisplay(const char*);
16828 	int XCloseDisplay(Display*);
16829 
16830 	int function() XSynchronize(Display*, bool);
16831 	int function() XSetAfterFunction(Display*, int function() proc);
16832 
16833 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16834 
16835 	Bool XSupportsLocale();
16836 	char* XSetLocaleModifiers(const(char)* modifier_list);
16837 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16838 	Status XCloseOM(XOM om);
16839 
16840 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16841 	Status XCloseIM(XIM im);
16842 
16843 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16844 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16845 	Display* XDisplayOfIM(XIM im);
16846 	char* XLocaleOfIM(XIM im);
16847 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16848 	void XDestroyIC(XIC ic);
16849 	void XSetICFocus(XIC ic);
16850 	void XUnsetICFocus(XIC ic);
16851 	//wchar_t* XwcResetIC(XIC ic);
16852 	char* XmbResetIC(XIC ic);
16853 	char* Xutf8ResetIC(XIC ic);
16854 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16855 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16856 	XIM XIMOfIC(XIC ic);
16857 
16858 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16859 
16860 
16861 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16862 	int XFreeFont(Display *display, XFontStruct *font_struct);
16863 	int XSetFont(Display* display, GC gc, Font font);
16864 	int XTextWidth(XFontStruct*, scope const char*, int);
16865 
16866 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16867 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16868 
16869 	Window XCreateSimpleWindow(
16870 		Display*	/* display */,
16871 		Window		/* parent */,
16872 		int			/* x */,
16873 		int			/* y */,
16874 		uint		/* width */,
16875 		uint		/* height */,
16876 		uint		/* border_width */,
16877 		uint		/* border */,
16878 		uint		/* background */
16879 	);
16880 	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);
16881 
16882 	int XReparentWindow(Display*, Window, Window, int, int);
16883 	int XClearWindow(Display*, Window);
16884 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16885 	int XMoveWindow(Display*, Window, int, int);
16886 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16887 
16888 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16889 
16890 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16891 
16892 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16893 
16894 	XImage *XCreateImage(
16895 		Display*		/* display */,
16896 		Visual*		/* visual */,
16897 		uint	/* depth */,
16898 		int			/* format */,
16899 		int			/* offset */,
16900 		ubyte*		/* data */,
16901 		uint	/* width */,
16902 		uint	/* height */,
16903 		int			/* bitmap_pad */,
16904 		int			/* bytes_per_line */
16905 	);
16906 
16907 	Status XInitImage (XImage* image);
16908 
16909 	Atom XInternAtom(
16910 		Display*		/* display */,
16911 		const char*	/* atom_name */,
16912 		Bool		/* only_if_exists */
16913 	);
16914 
16915 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16916 	char* XGetAtomName(Display*, Atom);
16917 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16918 
16919 	int XPutImage(
16920 		Display*	/* display */,
16921 		Drawable	/* d */,
16922 		GC			/* gc */,
16923 		XImage*	/* image */,
16924 		int			/* src_x */,
16925 		int			/* src_y */,
16926 		int			/* dest_x */,
16927 		int			/* dest_y */,
16928 		uint		/* width */,
16929 		uint		/* height */
16930 	);
16931 
16932 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16933 
16934 
16935 	int XDestroyWindow(
16936 		Display*	/* display */,
16937 		Window		/* w */
16938 	);
16939 
16940 	int XDestroyImage(XImage*);
16941 
16942 	int XSelectInput(
16943 		Display*	/* display */,
16944 		Window		/* w */,
16945 		EventMask	/* event_mask */
16946 	);
16947 
16948 	int XMapWindow(
16949 		Display*	/* display */,
16950 		Window		/* w */
16951 	);
16952 
16953 	Status XIconifyWindow(Display*, Window, int);
16954 	int XMapRaised(Display*, Window);
16955 	int XMapSubwindows(Display*, Window);
16956 
16957 	int XNextEvent(
16958 		Display*	/* display */,
16959 		XEvent*		/* event_return */
16960 	);
16961 
16962 	int XMaskEvent(Display*, arch_long, XEvent*);
16963 
16964 	Bool XFilterEvent(XEvent *event, Window window);
16965 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16966 
16967 	Status XSetWMProtocols(
16968 		Display*	/* display */,
16969 		Window		/* w */,
16970 		Atom*		/* protocols */,
16971 		int			/* count */
16972 	);
16973 
16974 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16975 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16976 
16977 
16978 	Status XInitThreads();
16979 	void XLockDisplay (Display* display);
16980 	void XUnlockDisplay (Display* display);
16981 
16982 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16983 
16984 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16985 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16986 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16987 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16988 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16989 
16990 
16991 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16992 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16993 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16994 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16995 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16996 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16997 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16998 	int XDrawPoint(Display*, Drawable, GC, int, int);
16999 	int XSetForeground(Display*, GC, uint);
17000 	int XSetBackground(Display*, GC, uint);
17001 
17002 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
17003 	void XFreeFontSet(Display*, XFontSet);
17004 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
17005 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
17006 
17007 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
17008 
17009 
17010 //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);
17011 
17012 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
17013 	int XSetFunction(Display*, GC, int);
17014 
17015 	GC XCreateGC(Display*, Drawable, uint, void*);
17016 	int XCopyGC(Display*, GC, uint, GC);
17017 	int XFreeGC(Display*, GC);
17018 
17019 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
17020 	bool XCheckMaskEvent(Display*, int, XEvent*);
17021 
17022 	int XPending(Display*);
17023 	int XEventsQueued(Display* display, int mode);
17024 
17025 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
17026 	int XFreePixmap(Display*, Pixmap);
17027 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
17028 	int XFlush(Display*);
17029 	int XBell(Display*, int);
17030 	int XSync(Display*, bool);
17031 
17032 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
17033 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
17034 
17035 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
17036 	int XUngrabKeyboard(Display*, Time);
17037 
17038 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
17039 
17040 	KeySym XStringToKeysym(const char *string);
17041 
17042 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
17043 
17044 	Window XDefaultRootWindow(Display*);
17045 
17046 	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);
17047 
17048 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
17049 
17050 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
17051 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
17052 
17053 	Status XAllocColor(Display*, Colormap, XColor*);
17054 
17055 	int XWithdrawWindow(Display*, Window, int);
17056 	int XUnmapWindow(Display*, Window);
17057 	int XLowerWindow(Display*, Window);
17058 	int XRaiseWindow(Display*, Window);
17059 
17060 	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);
17061 	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);
17062 
17063 	int XGetInputFocus(Display*, Window*, int*);
17064 	int XSetInputFocus(Display*, Window, int, Time);
17065 
17066 	XErrorHandler XSetErrorHandler(XErrorHandler);
17067 
17068 	int XGetErrorText(Display*, int, char*, int);
17069 
17070 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
17071 
17072 
17073 	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);
17074 	int XUngrabPointer(Display *display, Time time);
17075 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
17076 
17077 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
17078 
17079 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
17080 	int XSetClipMask(Display*, GC, Pixmap);
17081 	int XSetClipOrigin(Display*, GC, int, int);
17082 
17083 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
17084 
17085 	void XSetWMName(Display*, Window, XTextProperty*);
17086 	Status XGetWMName(Display*, Window, XTextProperty*);
17087 	int XStoreName(Display* display, Window w, const(char)* window_name);
17088 
17089 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
17090 
17091 }
17092 }
17093 
17094 interface Xext {
17095 extern(C) nothrow @nogc {
17096 	Status XShmAttach(Display*, XShmSegmentInfo*);
17097 	Status XShmDetach(Display*, XShmSegmentInfo*);
17098 	Status XShmPutImage(
17099 		Display*            /* dpy */,
17100 		Drawable            /* d */,
17101 		GC                  /* gc */,
17102 		XImage*             /* image */,
17103 		int                 /* src_x */,
17104 		int                 /* src_y */,
17105 		int                 /* dst_x */,
17106 		int                 /* dst_y */,
17107 		uint        /* src_width */,
17108 		uint        /* src_height */,
17109 		Bool                /* send_event */
17110 	);
17111 
17112 	Status XShmQueryExtension(Display*);
17113 
17114 	XImage *XShmCreateImage(
17115 		Display*            /* dpy */,
17116 		Visual*             /* visual */,
17117 		uint        /* depth */,
17118 		int                 /* format */,
17119 		char*               /* data */,
17120 		XShmSegmentInfo*    /* shminfo */,
17121 		uint        /* width */,
17122 		uint        /* height */
17123 	);
17124 
17125 	Pixmap XShmCreatePixmap(
17126 		Display*            /* dpy */,
17127 		Drawable            /* d */,
17128 		char*               /* data */,
17129 		XShmSegmentInfo*    /* shminfo */,
17130 		uint        /* width */,
17131 		uint        /* height */,
17132 		uint        /* depth */
17133 	);
17134 
17135 }
17136 }
17137 
17138 	// this requires -lXpm
17139 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
17140 
17141 
17142 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
17143 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
17144 shared static this() {
17145 	xlib.loadDynamicLibrary();
17146 	xext.loadDynamicLibrary();
17147 }
17148 
17149 
17150 extern(C) nothrow @nogc {
17151 
17152 alias XrmDatabase = void*;
17153 struct XrmValue {
17154 	uint size;
17155 	void* addr;
17156 }
17157 
17158 struct XVisualInfo {
17159 	Visual* visual;
17160 	VisualID visualid;
17161 	int screen;
17162 	uint depth;
17163 	int c_class;
17164 	c_ulong red_mask;
17165 	c_ulong green_mask;
17166 	c_ulong blue_mask;
17167 	int colormap_size;
17168 	int bits_per_rgb;
17169 }
17170 
17171 enum VisualNoMask=	0x0;
17172 enum VisualIDMask=	0x1;
17173 enum VisualScreenMask=0x2;
17174 enum VisualDepthMask=	0x4;
17175 enum VisualClassMask=	0x8;
17176 enum VisualRedMaskMask=0x10;
17177 enum VisualGreenMaskMask=0x20;
17178 enum VisualBlueMaskMask=0x40;
17179 enum VisualColormapSizeMask=0x80;
17180 enum VisualBitsPerRGBMask=0x100;
17181 enum VisualAllMask=	0x1FF;
17182 
17183 enum AnyKey = 0;
17184 enum AnyModifier = 1 << 15;
17185 
17186 // XIM and other crap
17187 struct _XOM {}
17188 struct _XIM {}
17189 struct _XIC {}
17190 alias XOM = _XOM*;
17191 alias XIM = _XIM*;
17192 alias XIC = _XIC*;
17193 
17194 alias XVaNestedList = void*;
17195 
17196 alias XIMStyle = arch_ulong;
17197 enum : arch_ulong {
17198 	XIMPreeditArea      = 0x0001,
17199 	XIMPreeditCallbacks = 0x0002,
17200 	XIMPreeditPosition  = 0x0004,
17201 	XIMPreeditNothing   = 0x0008,
17202 	XIMPreeditNone      = 0x0010,
17203 	XIMStatusArea       = 0x0100,
17204 	XIMStatusCallbacks  = 0x0200,
17205 	XIMStatusNothing    = 0x0400,
17206 	XIMStatusNone       = 0x0800,
17207 }
17208 
17209 
17210 /* X Shared Memory Extension functions */
17211 	//pragma(lib, "Xshm");
17212 	alias arch_ulong ShmSeg;
17213 	struct XShmSegmentInfo {
17214 		ShmSeg shmseg;
17215 		int shmid;
17216 		ubyte* shmaddr;
17217 		Bool readOnly;
17218 	}
17219 
17220 	// and the necessary OS functions
17221 	int shmget(int, size_t, int);
17222 	void* shmat(int, scope const void*, int);
17223 	int shmdt(scope const void*);
17224 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
17225 
17226 	enum IPC_PRIVATE = 0;
17227 	enum IPC_CREAT = 512;
17228 	enum IPC_RMID = 0;
17229 
17230 /* MIT-SHM end */
17231 
17232 
17233 enum MappingType:int {
17234 	MappingModifier		=0,
17235 	MappingKeyboard		=1,
17236 	MappingPointer		=2
17237 }
17238 
17239 /* ImageFormat -- PutImage, GetImage */
17240 enum ImageFormat:int {
17241 	XYBitmap	=0,	/* depth 1, XYFormat */
17242 	XYPixmap	=1,	/* depth == drawable depth */
17243 	ZPixmap	=2	/* depth == drawable depth */
17244 }
17245 
17246 enum ModifierName:int {
17247 	ShiftMapIndex	=0,
17248 	LockMapIndex	=1,
17249 	ControlMapIndex	=2,
17250 	Mod1MapIndex	=3,
17251 	Mod2MapIndex	=4,
17252 	Mod3MapIndex	=5,
17253 	Mod4MapIndex	=6,
17254 	Mod5MapIndex	=7
17255 }
17256 
17257 enum ButtonMask:int {
17258 	Button1Mask	=1<<8,
17259 	Button2Mask	=1<<9,
17260 	Button3Mask	=1<<10,
17261 	Button4Mask	=1<<11,
17262 	Button5Mask	=1<<12,
17263 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17264 }
17265 
17266 enum KeyOrButtonMask:uint {
17267 	ShiftMask	=1<<0,
17268 	LockMask	=1<<1,
17269 	ControlMask	=1<<2,
17270 	Mod1Mask	=1<<3,
17271 	Mod2Mask	=1<<4,
17272 	Mod3Mask	=1<<5,
17273 	Mod4Mask	=1<<6,
17274 	Mod5Mask	=1<<7,
17275 	Button1Mask	=1<<8,
17276 	Button2Mask	=1<<9,
17277 	Button3Mask	=1<<10,
17278 	Button4Mask	=1<<11,
17279 	Button5Mask	=1<<12,
17280 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17281 }
17282 
17283 enum ButtonName:int {
17284 	Button1	=1,
17285 	Button2	=2,
17286 	Button3	=3,
17287 	Button4	=4,
17288 	Button5	=5
17289 }
17290 
17291 /* Notify modes */
17292 enum NotifyModes:int
17293 {
17294 	NotifyNormal		=0,
17295 	NotifyGrab			=1,
17296 	NotifyUngrab		=2,
17297 	NotifyWhileGrabbed	=3
17298 }
17299 enum NotifyHint = 1;	/* for MotionNotify events */
17300 
17301 /* Notify detail */
17302 enum NotifyDetail:int
17303 {
17304 	NotifyAncestor			=0,
17305 	NotifyVirtual			=1,
17306 	NotifyInferior			=2,
17307 	NotifyNonlinear			=3,
17308 	NotifyNonlinearVirtual	=4,
17309 	NotifyPointer			=5,
17310 	NotifyPointerRoot		=6,
17311 	NotifyDetailNone		=7
17312 }
17313 
17314 /* Visibility notify */
17315 
17316 enum VisibilityNotify:int
17317 {
17318 VisibilityUnobscured		=0,
17319 VisibilityPartiallyObscured	=1,
17320 VisibilityFullyObscured		=2
17321 }
17322 
17323 
17324 enum WindowStackingMethod:int
17325 {
17326 	Above		=0,
17327 	Below		=1,
17328 	TopIf		=2,
17329 	BottomIf	=3,
17330 	Opposite	=4
17331 }
17332 
17333 /* Circulation request */
17334 enum CirculationRequest:int
17335 {
17336 	PlaceOnTop		=0,
17337 	PlaceOnBottom	=1
17338 }
17339 
17340 enum PropertyNotification:int
17341 {
17342 	PropertyNewValue	=0,
17343 	PropertyDelete		=1
17344 }
17345 
17346 enum ColorMapNotification:int
17347 {
17348 	ColormapUninstalled	=0,
17349 	ColormapInstalled		=1
17350 }
17351 
17352 
17353 	struct _XPrivate {}
17354 	struct _XrmHashBucketRec {}
17355 
17356 	alias void* XPointer;
17357 	alias void* XExtData;
17358 
17359 	version( X86_64 ) {
17360 		alias ulong XID;
17361 		alias ulong arch_ulong;
17362 		alias long arch_long;
17363 	} else version (AArch64) {
17364 		alias ulong XID;
17365 		alias ulong arch_ulong;
17366 		alias long arch_long;
17367 	} else {
17368 		alias uint XID;
17369 		alias uint arch_ulong;
17370 		alias int arch_long;
17371 	}
17372 
17373 	alias XID Window;
17374 	alias XID Drawable;
17375 	alias XID Pixmap;
17376 
17377 	alias arch_ulong Atom;
17378 	alias int Bool;
17379 	alias Display XDisplay;
17380 
17381 	alias int ByteOrder;
17382 	alias arch_ulong Time;
17383 	alias void ScreenFormat;
17384 
17385 	struct XImage {
17386 		int width, height;			/* size of image */
17387 		int xoffset;				/* number of pixels offset in X direction */
17388 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
17389 		void *data;					/* pointer to image data */
17390 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
17391 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
17392 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
17393 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
17394 		int depth;					/* depth of image */
17395 		int bytes_per_line;			/* accelarator to next line */
17396 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
17397 		arch_ulong red_mask;	/* bits in z arrangment */
17398 		arch_ulong green_mask;
17399 		arch_ulong blue_mask;
17400 		XPointer obdata;			/* hook for the object routines to hang on */
17401 		static struct F {				/* image manipulation routines */
17402 			XImage* function(
17403 				XDisplay* 			/* display */,
17404 				Visual*				/* visual */,
17405 				uint				/* depth */,
17406 				int					/* format */,
17407 				int					/* offset */,
17408 				ubyte*				/* data */,
17409 				uint				/* width */,
17410 				uint				/* height */,
17411 				int					/* bitmap_pad */,
17412 				int					/* bytes_per_line */) create_image;
17413 			int function(XImage *) destroy_image;
17414 			arch_ulong function(XImage *, int, int) get_pixel;
17415 			int function(XImage *, int, int, arch_ulong) put_pixel;
17416 			XImage* function(XImage *, int, int, uint, uint) sub_image;
17417 			int function(XImage *, arch_long) add_pixel;
17418 		}
17419 		F f;
17420 	}
17421 	version(X86_64) static assert(XImage.sizeof == 136);
17422 	else version(X86) static assert(XImage.sizeof == 88);
17423 
17424 struct XCharStruct {
17425 	short       lbearing;       /* origin to left edge of raster */
17426 	short       rbearing;       /* origin to right edge of raster */
17427 	short       width;          /* advance to next char's origin */
17428 	short       ascent;         /* baseline to top edge of raster */
17429 	short       descent;        /* baseline to bottom edge of raster */
17430 	ushort attributes;  /* per char flags (not predefined) */
17431 }
17432 
17433 /*
17434  * To allow arbitrary information with fonts, there are additional properties
17435  * returned.
17436  */
17437 struct XFontProp {
17438 	Atom name;
17439 	arch_ulong card32;
17440 }
17441 
17442 alias Atom Font;
17443 
17444 struct XFontStruct {
17445 	XExtData *ext_data;           /* Hook for extension to hang data */
17446 	Font fid;                     /* Font ID for this font */
17447 	uint direction;           /* Direction the font is painted */
17448 	uint min_char_or_byte2;   /* First character */
17449 	uint max_char_or_byte2;   /* Last character */
17450 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
17451 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
17452 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
17453 	uint default_char;        /* Char to print for undefined character */
17454 	int n_properties;             /* How many properties there are */
17455 	XFontProp *properties;        /* Pointer to array of additional properties*/
17456 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
17457 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
17458 	XCharStruct *per_char;        /* first_char to last_char information */
17459 	int ascent;                   /* Max extent above baseline for spacing */
17460 	int descent;                  /* Max descent below baseline for spacing */
17461 }
17462 
17463 
17464 /*
17465  * Definitions of specific events.
17466  */
17467 struct XKeyEvent
17468 {
17469 	int type;			/* of event */
17470 	arch_ulong serial;		/* # of last request processed by server */
17471 	Bool send_event;	/* true if this came from a SendEvent request */
17472 	Display *display;	/* Display the event was read from */
17473 	Window window;	        /* "event" window it is reported relative to */
17474 	Window root;	        /* root window that the event occurred on */
17475 	Window subwindow;	/* child window */
17476 	Time time;		/* milliseconds */
17477 	int x, y;		/* pointer x, y coordinates in event window */
17478 	int x_root, y_root;	/* coordinates relative to root */
17479 	KeyOrButtonMask state;	/* key or button mask */
17480 	uint keycode;	/* detail */
17481 	Bool same_screen;	/* same screen flag */
17482 }
17483 version(X86_64) static assert(XKeyEvent.sizeof == 96);
17484 alias XKeyEvent XKeyPressedEvent;
17485 alias XKeyEvent XKeyReleasedEvent;
17486 
17487 struct XButtonEvent
17488 {
17489 	int type;		/* of event */
17490 	arch_ulong serial;	/* # of last request processed by server */
17491 	Bool send_event;	/* true if this came from a SendEvent request */
17492 	Display *display;	/* Display the event was read from */
17493 	Window window;	        /* "event" window it is reported relative to */
17494 	Window root;	        /* root window that the event occurred on */
17495 	Window subwindow;	/* child window */
17496 	Time time;		/* milliseconds */
17497 	int x, y;		/* pointer x, y coordinates in event window */
17498 	int x_root, y_root;	/* coordinates relative to root */
17499 	KeyOrButtonMask state;	/* key or button mask */
17500 	uint button;	/* detail */
17501 	Bool same_screen;	/* same screen flag */
17502 }
17503 alias XButtonEvent XButtonPressedEvent;
17504 alias XButtonEvent XButtonReleasedEvent;
17505 
17506 struct XMotionEvent{
17507 	int type;		/* of event */
17508 	arch_ulong serial;	/* # of last request processed by server */
17509 	Bool send_event;	/* true if this came from a SendEvent request */
17510 	Display *display;	/* Display the event was read from */
17511 	Window window;	        /* "event" window reported relative to */
17512 	Window root;	        /* root window that the event occurred on */
17513 	Window subwindow;	/* child window */
17514 	Time time;		/* milliseconds */
17515 	int x, y;		/* pointer x, y coordinates in event window */
17516 	int x_root, y_root;	/* coordinates relative to root */
17517 	KeyOrButtonMask state;	/* key or button mask */
17518 	byte is_hint;		/* detail */
17519 	Bool same_screen;	/* same screen flag */
17520 }
17521 alias XMotionEvent XPointerMovedEvent;
17522 
17523 struct XCrossingEvent{
17524 	int type;		/* of event */
17525 	arch_ulong serial;	/* # of last request processed by server */
17526 	Bool send_event;	/* true if this came from a SendEvent request */
17527 	Display *display;	/* Display the event was read from */
17528 	Window window;	        /* "event" window reported relative to */
17529 	Window root;	        /* root window that the event occurred on */
17530 	Window subwindow;	/* child window */
17531 	Time time;		/* milliseconds */
17532 	int x, y;		/* pointer x, y coordinates in event window */
17533 	int x_root, y_root;	/* coordinates relative to root */
17534 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
17535 	NotifyDetail detail;
17536 	/*
17537 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17538 	 * NotifyNonlinear,NotifyNonlinearVirtual
17539 	 */
17540 	Bool same_screen;	/* same screen flag */
17541 	Bool focus;		/* Boolean focus */
17542 	KeyOrButtonMask state;	/* key or button mask */
17543 }
17544 alias XCrossingEvent XEnterWindowEvent;
17545 alias XCrossingEvent XLeaveWindowEvent;
17546 
17547 struct XFocusChangeEvent{
17548 	int type;		/* FocusIn or FocusOut */
17549 	arch_ulong serial;	/* # of last request processed by server */
17550 	Bool send_event;	/* true if this came from a SendEvent request */
17551 	Display *display;	/* Display the event was read from */
17552 	Window window;		/* window of event */
17553 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
17554 				   NotifyGrab, NotifyUngrab */
17555 	NotifyDetail detail;
17556 	/*
17557 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17558 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
17559 	 * NotifyPointerRoot, NotifyDetailNone
17560 	 */
17561 }
17562 alias XFocusChangeEvent XFocusInEvent;
17563 alias XFocusChangeEvent XFocusOutEvent;
17564 
17565 enum CWBackPixmap              = (1L<<0);
17566 enum CWBackPixel               = (1L<<1);
17567 enum CWBorderPixmap            = (1L<<2);
17568 enum CWBorderPixel             = (1L<<3);
17569 enum CWBitGravity              = (1L<<4);
17570 enum CWWinGravity              = (1L<<5);
17571 enum CWBackingStore            = (1L<<6);
17572 enum CWBackingPlanes           = (1L<<7);
17573 enum CWBackingPixel            = (1L<<8);
17574 enum CWOverrideRedirect        = (1L<<9);
17575 enum CWSaveUnder               = (1L<<10);
17576 enum CWEventMask               = (1L<<11);
17577 enum CWDontPropagate           = (1L<<12);
17578 enum CWColormap                = (1L<<13);
17579 enum CWCursor                  = (1L<<14);
17580 
17581 struct XWindowAttributes {
17582 	int x, y;			/* location of window */
17583 	int width, height;		/* width and height of window */
17584 	int border_width;		/* border width of window */
17585 	int depth;			/* depth of window */
17586 	Visual *visual;			/* the associated visual structure */
17587 	Window root;			/* root of screen containing window */
17588 	int class_;			/* InputOutput, InputOnly*/
17589 	int bit_gravity;		/* one of the bit gravity values */
17590 	int win_gravity;		/* one of the window gravity values */
17591 	int backing_store;		/* NotUseful, WhenMapped, Always */
17592 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
17593 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
17594 	Bool save_under;		/* boolean, should bits under be saved? */
17595 	Colormap colormap;		/* color map to be associated with window */
17596 	Bool map_installed;		/* boolean, is color map currently installed*/
17597 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
17598 	arch_long all_event_masks;		/* set of events all people have interest in*/
17599 	arch_long your_event_mask;		/* my event mask */
17600 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
17601 	Bool override_redirect;		/* boolean value for override-redirect */
17602 	Screen *screen;			/* back pointer to correct screen */
17603 }
17604 
17605 enum IsUnmapped = 0;
17606 enum IsUnviewable = 1;
17607 enum IsViewable = 2;
17608 
17609 struct XSetWindowAttributes {
17610 	Pixmap background_pixmap;/* background, None, or ParentRelative */
17611 	arch_ulong background_pixel;/* background pixel */
17612 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
17613 	arch_ulong border_pixel;/* border pixel value */
17614 	int bit_gravity;         /* one of bit gravity values */
17615 	int win_gravity;         /* one of the window gravity values */
17616 	int backing_store;       /* NotUseful, WhenMapped, Always */
17617 	arch_ulong backing_planes;/* planes to be preserved if possible */
17618 	arch_ulong backing_pixel;/* value to use in restoring planes */
17619 	Bool save_under;         /* should bits under be saved? (popups) */
17620 	arch_long event_mask;         /* set of events that should be saved */
17621 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
17622 	Bool override_redirect;  /* boolean value for override_redirect */
17623 	Colormap colormap;       /* color map to be associated with window */
17624 	Cursor cursor;           /* cursor to be displayed (or None) */
17625 }
17626 
17627 
17628 alias int Status;
17629 
17630 
17631 enum EventMask:int
17632 {
17633 	NoEventMask				=0,
17634 	KeyPressMask			=1<<0,
17635 	KeyReleaseMask			=1<<1,
17636 	ButtonPressMask			=1<<2,
17637 	ButtonReleaseMask		=1<<3,
17638 	EnterWindowMask			=1<<4,
17639 	LeaveWindowMask			=1<<5,
17640 	PointerMotionMask		=1<<6,
17641 	PointerMotionHintMask	=1<<7,
17642 	Button1MotionMask		=1<<8,
17643 	Button2MotionMask		=1<<9,
17644 	Button3MotionMask		=1<<10,
17645 	Button4MotionMask		=1<<11,
17646 	Button5MotionMask		=1<<12,
17647 	ButtonMotionMask		=1<<13,
17648 	KeymapStateMask		=1<<14,
17649 	ExposureMask			=1<<15,
17650 	VisibilityChangeMask	=1<<16,
17651 	StructureNotifyMask		=1<<17,
17652 	ResizeRedirectMask		=1<<18,
17653 	SubstructureNotifyMask	=1<<19,
17654 	SubstructureRedirectMask=1<<20,
17655 	FocusChangeMask			=1<<21,
17656 	PropertyChangeMask		=1<<22,
17657 	ColormapChangeMask		=1<<23,
17658 	OwnerGrabButtonMask		=1<<24
17659 }
17660 
17661 struct MwmHints {
17662 	c_ulong flags;
17663 	c_ulong functions;
17664 	c_ulong decorations;
17665 	c_long input_mode;
17666 	c_ulong status;
17667 }
17668 
17669 enum {
17670 	MWM_HINTS_FUNCTIONS = (1L << 0),
17671 	MWM_HINTS_DECORATIONS =  (1L << 1),
17672 
17673 	MWM_FUNC_ALL = (1L << 0),
17674 	MWM_FUNC_RESIZE = (1L << 1),
17675 	MWM_FUNC_MOVE = (1L << 2),
17676 	MWM_FUNC_MINIMIZE = (1L << 3),
17677 	MWM_FUNC_MAXIMIZE = (1L << 4),
17678 	MWM_FUNC_CLOSE = (1L << 5),
17679 
17680 	MWM_DECOR_ALL = (1L << 0),
17681 	MWM_DECOR_BORDER = (1L << 1),
17682 	MWM_DECOR_RESIZEH = (1L << 2),
17683 	MWM_DECOR_TITLE = (1L << 3),
17684 	MWM_DECOR_MENU = (1L << 4),
17685 	MWM_DECOR_MINIMIZE = (1L << 5),
17686 	MWM_DECOR_MAXIMIZE = (1L << 6),
17687 }
17688 
17689 import core.stdc.config : c_long, c_ulong;
17690 
17691 	/* Size hints mask bits */
17692 
17693 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
17694 	enum   USSize      = (1L << 1)          /* user specified width, height */;
17695 	enum   PPosition   = (1L << 2)          /* program specified position */;
17696 	enum   PSize       = (1L << 3)          /* program specified size */;
17697 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
17698 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
17699 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
17700 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
17701 	enum   PBaseSize   = (1L << 8);
17702 	enum   PWinGravity = (1L << 9);
17703 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
17704 	struct XSizeHints {
17705 		arch_long flags;         /* marks which fields in this structure are defined */
17706 		int x, y;           /* Obsolete */
17707 		int width, height;  /* Obsolete */
17708 		int min_width, min_height;
17709 		int max_width, max_height;
17710 		int width_inc, height_inc;
17711 		struct Aspect {
17712 			int x;       /* numerator */
17713 			int y;       /* denominator */
17714 		}
17715 
17716 		Aspect min_aspect;
17717 		Aspect max_aspect;
17718 		int base_width, base_height;
17719 		int win_gravity;
17720 		/* this structure may be extended in the future */
17721 	}
17722 
17723 
17724 
17725 enum EventType:int
17726 {
17727 	KeyPress			=2,
17728 	KeyRelease			=3,
17729 	ButtonPress			=4,
17730 	ButtonRelease		=5,
17731 	MotionNotify		=6,
17732 	EnterNotify			=7,
17733 	LeaveNotify			=8,
17734 	FocusIn				=9,
17735 	FocusOut			=10,
17736 	KeymapNotify		=11,
17737 	Expose				=12,
17738 	GraphicsExpose		=13,
17739 	NoExpose			=14,
17740 	VisibilityNotify	=15,
17741 	CreateNotify		=16,
17742 	DestroyNotify		=17,
17743 	UnmapNotify		=18,
17744 	MapNotify			=19,
17745 	MapRequest			=20,
17746 	ReparentNotify		=21,
17747 	ConfigureNotify		=22,
17748 	ConfigureRequest	=23,
17749 	GravityNotify		=24,
17750 	ResizeRequest		=25,
17751 	CirculateNotify		=26,
17752 	CirculateRequest	=27,
17753 	PropertyNotify		=28,
17754 	SelectionClear		=29,
17755 	SelectionRequest	=30,
17756 	SelectionNotify		=31,
17757 	ColormapNotify		=32,
17758 	ClientMessage		=33,
17759 	MappingNotify		=34,
17760 	LASTEvent			=35	/* must be bigger than any event # */
17761 }
17762 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
17763 struct XKeymapEvent
17764 {
17765 	int type;
17766 	arch_ulong serial;	/* # of last request processed by server */
17767 	Bool send_event;	/* true if this came from a SendEvent request */
17768 	Display *display;	/* Display the event was read from */
17769 	Window window;
17770 	byte[32] key_vector;
17771 }
17772 
17773 struct XExposeEvent
17774 {
17775 	int type;
17776 	arch_ulong serial;	/* # of last request processed by server */
17777 	Bool send_event;	/* true if this came from a SendEvent request */
17778 	Display *display;	/* Display the event was read from */
17779 	Window window;
17780 	int x, y;
17781 	int width, height;
17782 	int count;		/* if non-zero, at least this many more */
17783 }
17784 
17785 struct XGraphicsExposeEvent{
17786 	int type;
17787 	arch_ulong serial;	/* # of last request processed by server */
17788 	Bool send_event;	/* true if this came from a SendEvent request */
17789 	Display *display;	/* Display the event was read from */
17790 	Drawable drawable;
17791 	int x, y;
17792 	int width, height;
17793 	int count;		/* if non-zero, at least this many more */
17794 	int major_code;		/* core is CopyArea or CopyPlane */
17795 	int minor_code;		/* not defined in the core */
17796 }
17797 
17798 struct XNoExposeEvent{
17799 	int type;
17800 	arch_ulong serial;	/* # of last request processed by server */
17801 	Bool send_event;	/* true if this came from a SendEvent request */
17802 	Display *display;	/* Display the event was read from */
17803 	Drawable drawable;
17804 	int major_code;		/* core is CopyArea or CopyPlane */
17805 	int minor_code;		/* not defined in the core */
17806 }
17807 
17808 struct XVisibilityEvent{
17809 	int type;
17810 	arch_ulong serial;	/* # of last request processed by server */
17811 	Bool send_event;	/* true if this came from a SendEvent request */
17812 	Display *display;	/* Display the event was read from */
17813 	Window window;
17814 	VisibilityNotify state;		/* Visibility state */
17815 }
17816 
17817 struct XCreateWindowEvent{
17818 	int type;
17819 	arch_ulong serial;	/* # of last request processed by server */
17820 	Bool send_event;	/* true if this came from a SendEvent request */
17821 	Display *display;	/* Display the event was read from */
17822 	Window parent;		/* parent of the window */
17823 	Window window;		/* window id of window created */
17824 	int x, y;		/* window location */
17825 	int width, height;	/* size of window */
17826 	int border_width;	/* border width */
17827 	Bool override_redirect;	/* creation should be overridden */
17828 }
17829 
17830 struct XDestroyWindowEvent
17831 {
17832 	int type;
17833 	arch_ulong serial;		/* # of last request processed by server */
17834 	Bool send_event;	/* true if this came from a SendEvent request */
17835 	Display *display;	/* Display the event was read from */
17836 	Window event;
17837 	Window window;
17838 }
17839 
17840 struct XUnmapEvent
17841 {
17842 	int type;
17843 	arch_ulong serial;		/* # of last request processed by server */
17844 	Bool send_event;	/* true if this came from a SendEvent request */
17845 	Display *display;	/* Display the event was read from */
17846 	Window event;
17847 	Window window;
17848 	Bool from_configure;
17849 }
17850 
17851 struct XMapEvent
17852 {
17853 	int type;
17854 	arch_ulong serial;		/* # of last request processed by server */
17855 	Bool send_event;	/* true if this came from a SendEvent request */
17856 	Display *display;	/* Display the event was read from */
17857 	Window event;
17858 	Window window;
17859 	Bool override_redirect;	/* Boolean, is override set... */
17860 }
17861 
17862 struct XMapRequestEvent
17863 {
17864 	int type;
17865 	arch_ulong serial;	/* # of last request processed by server */
17866 	Bool send_event;	/* true if this came from a SendEvent request */
17867 	Display *display;	/* Display the event was read from */
17868 	Window parent;
17869 	Window window;
17870 }
17871 
17872 struct XReparentEvent
17873 {
17874 	int type;
17875 	arch_ulong serial;	/* # of last request processed by server */
17876 	Bool send_event;	/* true if this came from a SendEvent request */
17877 	Display *display;	/* Display the event was read from */
17878 	Window event;
17879 	Window window;
17880 	Window parent;
17881 	int x, y;
17882 	Bool override_redirect;
17883 }
17884 
17885 struct XConfigureEvent
17886 {
17887 	int type;
17888 	arch_ulong serial;	/* # of last request processed by server */
17889 	Bool send_event;	/* true if this came from a SendEvent request */
17890 	Display *display;	/* Display the event was read from */
17891 	Window event;
17892 	Window window;
17893 	int x, y;
17894 	int width, height;
17895 	int border_width;
17896 	Window above;
17897 	Bool override_redirect;
17898 }
17899 
17900 struct XGravityEvent
17901 {
17902 	int type;
17903 	arch_ulong serial;	/* # of last request processed by server */
17904 	Bool send_event;	/* true if this came from a SendEvent request */
17905 	Display *display;	/* Display the event was read from */
17906 	Window event;
17907 	Window window;
17908 	int x, y;
17909 }
17910 
17911 struct XResizeRequestEvent
17912 {
17913 	int type;
17914 	arch_ulong serial;	/* # of last request processed by server */
17915 	Bool send_event;	/* true if this came from a SendEvent request */
17916 	Display *display;	/* Display the event was read from */
17917 	Window window;
17918 	int width, height;
17919 }
17920 
17921 struct  XConfigureRequestEvent
17922 {
17923 	int type;
17924 	arch_ulong serial;	/* # of last request processed by server */
17925 	Bool send_event;	/* true if this came from a SendEvent request */
17926 	Display *display;	/* Display the event was read from */
17927 	Window parent;
17928 	Window window;
17929 	int x, y;
17930 	int width, height;
17931 	int border_width;
17932 	Window above;
17933 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17934 	arch_ulong value_mask;
17935 }
17936 
17937 struct XCirculateEvent
17938 {
17939 	int type;
17940 	arch_ulong serial;	/* # of last request processed by server */
17941 	Bool send_event;	/* true if this came from a SendEvent request */
17942 	Display *display;	/* Display the event was read from */
17943 	Window event;
17944 	Window window;
17945 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17946 }
17947 
17948 struct XCirculateRequestEvent
17949 {
17950 	int type;
17951 	arch_ulong serial;	/* # of last request processed by server */
17952 	Bool send_event;	/* true if this came from a SendEvent request */
17953 	Display *display;	/* Display the event was read from */
17954 	Window parent;
17955 	Window window;
17956 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17957 }
17958 
17959 struct XPropertyEvent
17960 {
17961 	int type;
17962 	arch_ulong serial;	/* # of last request processed by server */
17963 	Bool send_event;	/* true if this came from a SendEvent request */
17964 	Display *display;	/* Display the event was read from */
17965 	Window window;
17966 	Atom atom;
17967 	Time time;
17968 	PropertyNotification state;		/* NewValue, Deleted */
17969 }
17970 
17971 struct XSelectionClearEvent
17972 {
17973 	int type;
17974 	arch_ulong serial;	/* # of last request processed by server */
17975 	Bool send_event;	/* true if this came from a SendEvent request */
17976 	Display *display;	/* Display the event was read from */
17977 	Window window;
17978 	Atom selection;
17979 	Time time;
17980 }
17981 
17982 struct XSelectionRequestEvent
17983 {
17984 	int type;
17985 	arch_ulong serial;	/* # of last request processed by server */
17986 	Bool send_event;	/* true if this came from a SendEvent request */
17987 	Display *display;	/* Display the event was read from */
17988 	Window owner;
17989 	Window requestor;
17990 	Atom selection;
17991 	Atom target;
17992 	Atom property;
17993 	Time time;
17994 }
17995 
17996 struct XSelectionEvent
17997 {
17998 	int type;
17999 	arch_ulong serial;	/* # of last request processed by server */
18000 	Bool send_event;	/* true if this came from a SendEvent request */
18001 	Display *display;	/* Display the event was read from */
18002 	Window requestor;
18003 	Atom selection;
18004 	Atom target;
18005 	Atom property;		/* ATOM or None */
18006 	Time time;
18007 }
18008 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
18009 
18010 struct XColormapEvent
18011 {
18012 	int type;
18013 	arch_ulong serial;	/* # of last request processed by server */
18014 	Bool send_event;	/* true if this came from a SendEvent request */
18015 	Display *display;	/* Display the event was read from */
18016 	Window window;
18017 	Colormap colormap;	/* COLORMAP or None */
18018 	Bool new_;		/* C++ */
18019 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
18020 }
18021 version(X86_64) static assert(XColormapEvent.sizeof == 56);
18022 
18023 struct XClientMessageEvent
18024 {
18025 	int type;
18026 	arch_ulong serial;	/* # of last request processed by server */
18027 	Bool send_event;	/* true if this came from a SendEvent request */
18028 	Display *display;	/* Display the event was read from */
18029 	Window window;
18030 	Atom message_type;
18031 	int format;
18032 	union Data{
18033 		byte[20] b;
18034 		short[10] s;
18035 		arch_ulong[5] l;
18036 	}
18037 	Data data;
18038 
18039 }
18040 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
18041 
18042 struct XMappingEvent
18043 {
18044 	int type;
18045 	arch_ulong serial;	/* # of last request processed by server */
18046 	Bool send_event;	/* true if this came from a SendEvent request */
18047 	Display *display;	/* Display the event was read from */
18048 	Window window;		/* unused */
18049 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
18050 				   MappingPointer */
18051 	int first_keycode;	/* first keycode */
18052 	int count;		/* defines range of change w. first_keycode*/
18053 }
18054 
18055 struct XErrorEvent
18056 {
18057 	int type;
18058 	Display *display;	/* Display the event was read from */
18059 	XID resourceid;		/* resource id */
18060 	arch_ulong serial;	/* serial number of failed request */
18061 	ubyte error_code;	/* error code of failed request */
18062 	ubyte request_code;	/* Major op-code of failed request */
18063 	ubyte minor_code;	/* Minor op-code of failed request */
18064 }
18065 
18066 struct XAnyEvent
18067 {
18068 	int type;
18069 	arch_ulong serial;	/* # of last request processed by server */
18070 	Bool send_event;	/* true if this came from a SendEvent request */
18071 	Display *display;/* Display the event was read from */
18072 	Window window;	/* window on which event was requested in event mask */
18073 }
18074 
18075 union XEvent{
18076 	int type;		/* must not be changed; first element */
18077 	XAnyEvent xany;
18078 	XKeyEvent xkey;
18079 	XButtonEvent xbutton;
18080 	XMotionEvent xmotion;
18081 	XCrossingEvent xcrossing;
18082 	XFocusChangeEvent xfocus;
18083 	XExposeEvent xexpose;
18084 	XGraphicsExposeEvent xgraphicsexpose;
18085 	XNoExposeEvent xnoexpose;
18086 	XVisibilityEvent xvisibility;
18087 	XCreateWindowEvent xcreatewindow;
18088 	XDestroyWindowEvent xdestroywindow;
18089 	XUnmapEvent xunmap;
18090 	XMapEvent xmap;
18091 	XMapRequestEvent xmaprequest;
18092 	XReparentEvent xreparent;
18093 	XConfigureEvent xconfigure;
18094 	XGravityEvent xgravity;
18095 	XResizeRequestEvent xresizerequest;
18096 	XConfigureRequestEvent xconfigurerequest;
18097 	XCirculateEvent xcirculate;
18098 	XCirculateRequestEvent xcirculaterequest;
18099 	XPropertyEvent xproperty;
18100 	XSelectionClearEvent xselectionclear;
18101 	XSelectionRequestEvent xselectionrequest;
18102 	XSelectionEvent xselection;
18103 	XColormapEvent xcolormap;
18104 	XClientMessageEvent xclient;
18105 	XMappingEvent xmapping;
18106 	XErrorEvent xerror;
18107 	XKeymapEvent xkeymap;
18108 	arch_ulong[24] pad;
18109 }
18110 
18111 
18112 	struct Display {
18113 		XExtData *ext_data;	/* hook for extension to hang data */
18114 		_XPrivate *private1;
18115 		int fd;			/* Network socket. */
18116 		int private2;
18117 		int proto_major_version;/* major version of server's X protocol */
18118 		int proto_minor_version;/* minor version of servers X protocol */
18119 		char *vendor;		/* vendor of the server hardware */
18120 	    	XID private3;
18121 		XID private4;
18122 		XID private5;
18123 		int private6;
18124 		XID function(Display*)resource_alloc;/* allocator function */
18125 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
18126 		int bitmap_unit;	/* padding and data requirements */
18127 		int bitmap_pad;		/* padding requirements on bitmaps */
18128 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
18129 		int nformats;		/* number of pixmap formats in list */
18130 		ScreenFormat *pixmap_format;	/* pixmap format list */
18131 		int private8;
18132 		int release;		/* release of the server */
18133 		_XPrivate *private9;
18134 		_XPrivate *private10;
18135 		int qlen;		/* Length of input event queue */
18136 		arch_ulong last_request_read; /* seq number of last event read */
18137 		arch_ulong request;	/* sequence number of last request. */
18138 		XPointer private11;
18139 		XPointer private12;
18140 		XPointer private13;
18141 		XPointer private14;
18142 		uint max_request_size; /* maximum number 32 bit words in request*/
18143 		_XrmHashBucketRec *db;
18144 		int function  (Display*)private15;
18145 		char *display_name;	/* "host:display" string used on this connect*/
18146 		int default_screen;	/* default screen for operations */
18147 		int nscreens;		/* number of screens on this server*/
18148 		Screen *screens;	/* pointer to list of screens */
18149 		arch_ulong motion_buffer;	/* size of motion buffer */
18150 		arch_ulong private16;
18151 		int min_keycode;	/* minimum defined keycode */
18152 		int max_keycode;	/* maximum defined keycode */
18153 		XPointer private17;
18154 		XPointer private18;
18155 		int private19;
18156 		byte *xdefaults;	/* contents of defaults from server */
18157 		/* there is more to this structure, but it is private to Xlib */
18158 	}
18159 
18160 	// I got these numbers from a C program as a sanity test
18161 	version(X86_64) {
18162 		static assert(Display.sizeof == 296);
18163 		static assert(XPointer.sizeof == 8);
18164 		static assert(XErrorEvent.sizeof == 40);
18165 		static assert(XAnyEvent.sizeof == 40);
18166 		static assert(XMappingEvent.sizeof == 56);
18167 		static assert(XEvent.sizeof == 192);
18168     	} else version (AArch64) {
18169 		// omit check for aarch64
18170 	} else {
18171 		static assert(Display.sizeof == 176);
18172 		static assert(XPointer.sizeof == 4);
18173 		static assert(XEvent.sizeof == 96);
18174 	}
18175 
18176 struct Depth
18177 {
18178 	int depth;		/* this depth (Z) of the depth */
18179 	int nvisuals;		/* number of Visual types at this depth */
18180 	Visual *visuals;	/* list of visuals possible at this depth */
18181 }
18182 
18183 alias void* GC;
18184 alias c_ulong VisualID;
18185 alias XID Colormap;
18186 alias XID Cursor;
18187 alias XID KeySym;
18188 alias uint KeyCode;
18189 enum None = 0;
18190 }
18191 
18192 version(without_opengl) {}
18193 else {
18194 extern(C) nothrow @nogc {
18195 
18196 
18197 static if(!SdpyIsUsingIVGLBinds) {
18198 enum GLX_USE_GL=            1;       /* support GLX rendering */
18199 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
18200 enum GLX_LEVEL=             3;       /* level in plane stacking */
18201 enum GLX_RGBA=              4;       /* true if RGBA mode */
18202 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
18203 enum GLX_STEREO=            6;       /* stereo buffering supported */
18204 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
18205 enum GLX_RED_SIZE=          8;       /* number of red component bits */
18206 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
18207 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
18208 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
18209 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
18210 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
18211 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
18212 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
18213 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
18214 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
18215 
18216 
18217 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
18218 
18219 
18220 
18221 enum GL_TRUE = 1;
18222 enum GL_FALSE = 0;
18223 }
18224 
18225 alias XID GLXContextID;
18226 alias XID GLXPixmap;
18227 alias XID GLXDrawable;
18228 alias XID GLXPbuffer;
18229 alias XID GLXWindow;
18230 alias XID GLXFBConfigID;
18231 alias void* GLXContext;
18232 
18233 }
18234 }
18235 
18236 enum AllocNone = 0;
18237 
18238 extern(C) {
18239 	/* WARNING, this type not in Xlib spec */
18240 	extern(C) alias XIOErrorHandler = int function (Display* display);
18241 }
18242 
18243 extern(C) nothrow
18244 alias XErrorHandler = int function(Display*, XErrorEvent*);
18245 
18246 extern(C) nothrow @nogc {
18247 struct Screen{
18248 	XExtData *ext_data;		/* hook for extension to hang data */
18249 	Display *display;		/* back pointer to display structure */
18250 	Window root;			/* Root window id. */
18251 	int width, height;		/* width and height of screen */
18252 	int mwidth, mheight;	/* width and height of  in millimeters */
18253 	int ndepths;			/* number of depths possible */
18254 	Depth *depths;			/* list of allowable depths on the screen */
18255 	int root_depth;			/* bits per pixel */
18256 	Visual *root_visual;	/* root visual */
18257 	GC default_gc;			/* GC for the root root visual */
18258 	Colormap cmap;			/* default color map */
18259 	uint white_pixel;
18260 	uint black_pixel;		/* White and Black pixel values */
18261 	int max_maps, min_maps;	/* max and min color maps */
18262 	int backing_store;		/* Never, WhenMapped, Always */
18263 	bool save_unders;
18264 	int root_input_mask;	/* initial root input mask */
18265 }
18266 
18267 struct Visual
18268 {
18269 	XExtData *ext_data;	/* hook for extension to hang data */
18270 	VisualID visualid;	/* visual id of this visual */
18271 	int class_;			/* class of screen (monochrome, etc.) */
18272 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
18273 	int bits_per_rgb;	/* log base 2 of distinct color values */
18274 	int map_entries;	/* color map entries */
18275 }
18276 
18277 	alias Display* _XPrivDisplay;
18278 
18279 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system {
18280 		assert(dpy !is null);
18281 		return &dpy.screens[scr];
18282 	}
18283 
18284 	extern(D) Window RootWindow(Display *dpy,int scr) {
18285 		return ScreenOfDisplay(dpy,scr).root;
18286 	}
18287 
18288 	struct XWMHints {
18289 		arch_long flags;
18290 		Bool input;
18291 		int initial_state;
18292 		Pixmap icon_pixmap;
18293 		Window icon_window;
18294 		int icon_x, icon_y;
18295 		Pixmap icon_mask;
18296 		XID window_group;
18297 	}
18298 
18299 	struct XClassHint {
18300 		char* res_name;
18301 		char* res_class;
18302 	}
18303 
18304 	extern(D) int DefaultScreen(Display *dpy) {
18305 		return dpy.default_screen;
18306 	}
18307 
18308 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
18309 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
18310 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
18311 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
18312 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
18313 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
18314 
18315 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
18316 
18317 	enum int AnyPropertyType = 0;
18318 	enum int Success = 0;
18319 
18320 	enum int RevertToNone = None;
18321 	enum int PointerRoot = 1;
18322 	enum Time CurrentTime = 0;
18323 	enum int RevertToPointerRoot = PointerRoot;
18324 	enum int RevertToParent = 2;
18325 
18326 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
18327 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
18328 	}
18329 
18330 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
18331 		return ScreenOfDisplay(dpy,scr).root_visual;
18332 	}
18333 
18334 	extern(D) GC DefaultGC(Display *dpy,int scr) {
18335 		return ScreenOfDisplay(dpy,scr).default_gc;
18336 	}
18337 
18338 	extern(D) uint BlackPixel(Display *dpy,int scr) {
18339 		return ScreenOfDisplay(dpy,scr).black_pixel;
18340 	}
18341 
18342 	extern(D) uint WhitePixel(Display *dpy,int scr) {
18343 		return ScreenOfDisplay(dpy,scr).white_pixel;
18344 	}
18345 
18346 	alias void* XFontSet; // i think
18347 	struct XmbTextItem {
18348 		char* chars;
18349 		int nchars;
18350 		int delta;
18351 		XFontSet font_set;
18352 	}
18353 
18354 	struct XTextItem {
18355 		char* chars;
18356 		int nchars;
18357 		int delta;
18358 		Font font;
18359 	}
18360 
18361 	enum {
18362 		GXclear        = 0x0, /* 0 */
18363 		GXand          = 0x1, /* src AND dst */
18364 		GXandReverse   = 0x2, /* src AND NOT dst */
18365 		GXcopy         = 0x3, /* src */
18366 		GXandInverted  = 0x4, /* NOT src AND dst */
18367 		GXnoop         = 0x5, /* dst */
18368 		GXxor          = 0x6, /* src XOR dst */
18369 		GXor           = 0x7, /* src OR dst */
18370 		GXnor          = 0x8, /* NOT src AND NOT dst */
18371 		GXequiv        = 0x9, /* NOT src XOR dst */
18372 		GXinvert       = 0xa, /* NOT dst */
18373 		GXorReverse    = 0xb, /* src OR NOT dst */
18374 		GXcopyInverted = 0xc, /* NOT src */
18375 		GXorInverted   = 0xd, /* NOT src OR dst */
18376 		GXnand         = 0xe, /* NOT src OR NOT dst */
18377 		GXset          = 0xf, /* 1 */
18378 	}
18379 	enum QueueMode : int {
18380 		QueuedAlready,
18381 		QueuedAfterReading,
18382 		QueuedAfterFlush
18383 	}
18384 
18385 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
18386 
18387 	struct XPoint {
18388 		short x;
18389 		short y;
18390 	}
18391 
18392 	enum CoordMode:int {
18393 		CoordModeOrigin = 0,
18394 		CoordModePrevious = 1
18395 	}
18396 
18397 	enum PolygonShape:int {
18398 		Complex = 0,
18399 		Nonconvex = 1,
18400 		Convex = 2
18401 	}
18402 
18403 	struct XTextProperty {
18404 		const(char)* value;		/* same as Property routines */
18405 		Atom encoding;			/* prop type */
18406 		int format;				/* prop data format: 8, 16, or 32 */
18407 		arch_ulong nitems;		/* number of data items in value */
18408 	}
18409 
18410 	version( X86_64 ) {
18411 		static assert(XTextProperty.sizeof == 32);
18412 	}
18413 
18414 
18415 	struct XGCValues {
18416 		int function_;           /* logical operation */
18417 		arch_ulong plane_mask;/* plane mask */
18418 		arch_ulong foreground;/* foreground pixel */
18419 		arch_ulong background;/* background pixel */
18420 		int line_width;         /* line width */
18421 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
18422 		int cap_style;          /* CapNotLast, CapButt,
18423 					   CapRound, CapProjecting */
18424 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
18425 		int fill_style;         /* FillSolid, FillTiled,
18426 					   FillStippled, FillOpaeueStippled */
18427 		int fill_rule;          /* EvenOddRule, WindingRule */
18428 		int arc_mode;           /* ArcChord, ArcPieSlice */
18429 		Pixmap tile;            /* tile pixmap for tiling operations */
18430 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
18431 		int ts_x_origin;        /* offset for tile or stipple operations */
18432 		int ts_y_origin;
18433 		Font font;              /* default text font for text operations */
18434 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
18435 		Bool graphics_exposures;/* boolean, should exposures be generated */
18436 		int clip_x_origin;      /* origin for clipping */
18437 		int clip_y_origin;
18438 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
18439 		int dash_offset;        /* patterned/dashed line information */
18440 		char dashes;
18441 	}
18442 
18443 	struct XColor {
18444 		arch_ulong pixel;
18445 		ushort red, green, blue;
18446 		byte flags;
18447 		byte pad;
18448 	}
18449 
18450 	struct XRectangle {
18451 		short x;
18452 		short y;
18453 		ushort width;
18454 		ushort height;
18455 	}
18456 
18457 	enum ClipByChildren = 0;
18458 	enum IncludeInferiors = 1;
18459 
18460 	enum Atom XA_PRIMARY = 1;
18461 	enum Atom XA_SECONDARY = 2;
18462 	enum Atom XA_STRING = 31;
18463 	enum Atom XA_CARDINAL = 6;
18464 	enum Atom XA_WM_NAME = 39;
18465 	enum Atom XA_ATOM = 4;
18466 	enum Atom XA_WINDOW = 33;
18467 	enum Atom XA_WM_HINTS = 35;
18468 	enum int PropModeAppend = 2;
18469 	enum int PropModeReplace = 0;
18470 	enum int PropModePrepend = 1;
18471 
18472 	enum int CopyFromParent = 0;
18473 	enum int InputOutput = 1;
18474 
18475 	// XWMHints
18476 	enum InputHint = 1 << 0;
18477 	enum StateHint = 1 << 1;
18478 	enum IconPixmapHint = (1L << 2);
18479 	enum IconWindowHint = (1L << 3);
18480 	enum IconPositionHint = (1L << 4);
18481 	enum IconMaskHint = (1L << 5);
18482 	enum WindowGroupHint = (1L << 6);
18483 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
18484 	enum XUrgencyHint = (1L << 8);
18485 
18486 	// GC Components
18487 	enum GCFunction           =   (1L<<0);
18488 	enum GCPlaneMask         =    (1L<<1);
18489 	enum GCForeground       =     (1L<<2);
18490 	enum GCBackground      =      (1L<<3);
18491 	enum GCLineWidth      =       (1L<<4);
18492 	enum GCLineStyle     =        (1L<<5);
18493 	enum GCCapStyle     =         (1L<<6);
18494 	enum GCJoinStyle   =          (1L<<7);
18495 	enum GCFillStyle  =           (1L<<8);
18496 	enum GCFillRule  =            (1L<<9);
18497 	enum GCTile     =             (1L<<10);
18498 	enum GCStipple           =    (1L<<11);
18499 	enum GCTileStipXOrigin  =     (1L<<12);
18500 	enum GCTileStipYOrigin =      (1L<<13);
18501 	enum GCFont               =   (1L<<14);
18502 	enum GCSubwindowMode     =    (1L<<15);
18503 	enum GCGraphicsExposures=     (1L<<16);
18504 	enum GCClipXOrigin     =      (1L<<17);
18505 	enum GCClipYOrigin    =       (1L<<18);
18506 	enum GCClipMask      =        (1L<<19);
18507 	enum GCDashOffset   =         (1L<<20);
18508 	enum GCDashList    =          (1L<<21);
18509 	enum GCArcMode    =           (1L<<22);
18510 	enum GCLastBit   =            22;
18511 
18512 
18513 	enum int WithdrawnState = 0;
18514 	enum int NormalState = 1;
18515 	enum int IconicState = 3;
18516 
18517 }
18518 } else version (OSXCocoa) {
18519 
18520 /+
18521 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
18522 +/
18523 
18524 	private __gshared AppDelegate globalAppDelegate;
18525 
18526 	extern(Objective-C)
18527 	class AppDelegate : NSObject, NSApplicationDelegate {
18528 		override static AppDelegate alloc() @selector("alloc");
18529 
18530 
18531 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
18532 			SimpleWindow.processAllCustomEvents();
18533 		}
18534 
18535 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
18536 			immutable style = NSWindowStyleMask.resizable |
18537 				NSWindowStyleMask.closable |
18538 				NSWindowStyleMask.miniaturizable |
18539 				NSWindowStyleMask.titled;
18540 
18541 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
18542 
18543 			{
18544 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
18545 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
18546 				mainMenu.setSubmenu(menu, item);
18547 
18548 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
18549 				newItem.target = NSApp;
18550 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
18551 				newItem2.target = NSApp;
18552 			}
18553 
18554 			{
18555 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
18556 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
18557 				mainMenu.setSubmenu(menu, item);
18558 
18559 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
18560 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
18561 			}
18562 
18563 
18564 			NSApp.menu = mainMenu;
18565 
18566 
18567 			// auto controller = ViewController.alloc.init;
18568 
18569 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
18570 
18571 			/+
18572 			this.window = window;
18573 			this.controller = controller;
18574 			+/
18575 		}
18576 
18577 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
18578 			NSApplication.shared_.activateIgnoringOtherApps(true);
18579 		}
18580 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
18581 			return true;
18582 		}
18583 	}
18584 
18585 	extern(Objective-C)
18586 	class SDWindowDelegate : NSObject, NSWindowDelegate {
18587 		override static SDWindowDelegate alloc() @selector("alloc");
18588 		override SDWindowDelegate init() @selector("init");
18589 
18590 		SimpleWindow simpleWindow;
18591 
18592 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
18593 			auto window = cast(void*) notification.object;
18594 
18595 			// FIXME: do i need to release it?
18596 			SimpleWindow.nativeMapping.remove(window);
18597 		}
18598 
18599 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
18600 			if(simpleWindow.windowResized) {
18601 				// FIXME: automaticallyScaleIfPossible behaviors
18602 
18603 				simpleWindow._width = cast(int) frameSize.width;
18604 				simpleWindow._height = cast(int) frameSize.height;
18605 
18606 				simpleWindow.view.setFrameSize(frameSize);
18607 
18608 				/+
18609 				auto size = simpleWindow.view.frame.size;
18610 				writeln(cast(int) size.width, "x", cast(int) size.height);
18611 				+/
18612 
18613 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
18614 
18615 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
18616 
18617 				// simpleWindow.view.setNeedsDisplay(true);
18618 			}
18619 
18620 			return frameSize;
18621 		}
18622 
18623 		/+
18624 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
18625 			if(simpleWindow.windowResized) {
18626 				auto window = simpleWindow.window;
18627 				auto rect = window.contentRectForFrameRect(window.frame);
18628 				import std.stdio; writeln(window.frame.size);
18629 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
18630 			}
18631 		}
18632 		+/
18633 	}
18634 
18635 	extern(Objective-C)
18636 	class SDGraphicsView : NSView {
18637 		SimpleWindow simpleWindow;
18638 
18639 		override static SDGraphicsView alloc() @selector("alloc");
18640 		override SDGraphicsView init() @selector("init") {
18641 			super.init();
18642 			return this;
18643 		}
18644 
18645 		override void drawRect(NSRect rect) @selector("drawRect:") {
18646 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
18647 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18648 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
18649 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18650 			CGImageRelease(cgImage);
18651 		}
18652 
18653 		extern(D)
18654 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
18655 			MouseEvent me;
18656 			me.type = type;
18657 
18658 			auto pos = event.locationInWindow;
18659 
18660 			me.x = cast(int) pos.x;
18661 			me.y = cast(int) (simpleWindow.height - pos.y);
18662 
18663 			me.dx = 0; // FIXME
18664 			me.dy = 0; // FIXME
18665 
18666 			me.button = button;
18667 			me.modifierState = cast(uint) event.modifierFlags;
18668 			me.window = simpleWindow;
18669 
18670 			me.doubleClick = false;
18671 
18672 			if(simpleWindow && simpleWindow.handleMouseEvent)
18673 				simpleWindow.handleMouseEvent(me);
18674 		}
18675 
18676 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
18677 			// writeln(event.pressedMouseButtons);
18678 
18679 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18680 		}
18681 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
18682 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
18683 		}
18684 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
18685 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
18686 		}
18687 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
18688 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
18689 		}
18690 		/+
18691 			// FIXME
18692 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
18693 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18694 		}
18695 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
18696 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18697 		}
18698 		+/
18699 
18700 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
18701 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
18702 		}
18703 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
18704 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
18705 		}
18706 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
18707 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
18708 		}
18709 
18710 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
18711 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
18712 		}
18713 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
18714 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
18715 		}
18716 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
18717 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
18718 		}
18719 
18720 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
18721 			// import std.stdio; writeln(event.deltaY);
18722 		}
18723 
18724 		override void keyDown(NSEvent event) @selector("keyDown:") {
18725 			// the event may have multiple characters, and we send them all at once.
18726 			if (simpleWindow.handleCharEvent) {
18727 				auto chars = DeifiedNSString(event.characters);
18728 				foreach (dchar dc; chars.str)
18729 					simpleWindow.handleCharEvent(dc);
18730 			}
18731 
18732 			keyHelper(event, true);
18733 		}
18734 
18735 		override void keyUp(NSEvent event) @selector("keyUp:") {
18736 			keyHelper(event, false);
18737 		}
18738 
18739 		extern(D)
18740 		private void keyHelper(NSEvent event, bool pressed) {
18741 			if(simpleWindow.handleKeyEvent) {
18742 				KeyEvent ev;
18743 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
18744 				ev.pressed = pressed;
18745 				ev.hardwareCode = cast(ubyte) event.keyCode;
18746 				ev.modifierState = cast(uint) event.modifierFlags;
18747 				ev.window = simpleWindow;
18748 
18749 				simpleWindow.handleKeyEvent(ev);
18750 			}
18751 		}
18752 
18753 		override bool isFlipped() @selector("isFlipped") {
18754 			return true;
18755 		}
18756 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
18757 			return true;
18758 		}
18759 
18760 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
18761 			if(simpleWindow && simpleWindow.handlePulse)
18762 				simpleWindow.handlePulse();
18763 			/+
18764 			setNeedsDisplay = true;
18765 			+/
18766 		}
18767 	}
18768 
18769 private:
18770 	alias const(void)* CFStringRef;
18771 	alias const(void)* CFAllocatorRef;
18772 	alias const(void)* CFTypeRef;
18773 	alias const(void)* CGColorSpaceRef;
18774 	alias const(void)* CGImageRef;
18775 	alias ulong CGBitmapInfo;
18776 	alias NSGraphicsContext CGContextRef;
18777 
18778 	alias NSPoint CGPoint;
18779 	alias NSSize CGSize;
18780 	alias NSRect CGRect;
18781 
18782 	struct CGAffineTransform {
18783 		double a, b, c, d, tx, ty;
18784 	}
18785 
18786 	enum NSApplicationActivationPolicyRegular = 0;
18787 	enum NSBackingStoreBuffered = 2;
18788 	enum kCFStringEncodingUTF8 = 0x08000100;
18789 
18790 	enum : size_t {
18791 		NSBorderlessWindowMask = 0,
18792 		NSTitledWindowMask = 1 << 0,
18793 		NSClosableWindowMask = 1 << 1,
18794 		NSMiniaturizableWindowMask = 1 << 2,
18795 		NSResizableWindowMask = 1 << 3,
18796 		NSTexturedBackgroundWindowMask = 1 << 8
18797 	}
18798 
18799 	enum : ulong {
18800 		kCGImageAlphaNone,
18801 		kCGImageAlphaPremultipliedLast,
18802 		kCGImageAlphaPremultipliedFirst,
18803 		kCGImageAlphaLast,
18804 		kCGImageAlphaFirst,
18805 		kCGImageAlphaNoneSkipLast,
18806 		kCGImageAlphaNoneSkipFirst
18807 	}
18808 	enum : ulong {
18809 		kCGBitmapAlphaInfoMask = 0x1F,
18810 		kCGBitmapFloatComponents = (1 << 8),
18811 		kCGBitmapByteOrderMask = 0x7000,
18812 		kCGBitmapByteOrderDefault = (0 << 12),
18813 		kCGBitmapByteOrder16Little = (1 << 12),
18814 		kCGBitmapByteOrder32Little = (2 << 12),
18815 		kCGBitmapByteOrder16Big = (3 << 12),
18816 		kCGBitmapByteOrder32Big = (4 << 12)
18817 	}
18818 	enum CGPathDrawingMode {
18819 		kCGPathFill,
18820 		kCGPathEOFill,
18821 		kCGPathStroke,
18822 		kCGPathFillStroke,
18823 		kCGPathEOFillStroke
18824 	}
18825 	enum objc_AssociationPolicy : size_t {
18826 		OBJC_ASSOCIATION_ASSIGN = 0,
18827 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18828 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18829 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18830 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18831 	}
18832 
18833 	extern(C) {
18834 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18835 		void CGContextRelease(CGContextRef c);
18836 		ubyte* CGBitmapContextGetData(CGContextRef c);
18837 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18838 		size_t CGBitmapContextGetWidth(CGContextRef c);
18839 		size_t CGBitmapContextGetHeight(CGContextRef c);
18840 
18841 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18842 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18843 
18844 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18845 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18846 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18847 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18848 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18849 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
18850 
18851 		void CGContextBeginPath(CGContextRef c);
18852 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18853 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18854 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18855 		void CGContextAddRect(CGContextRef c, CGRect rect);
18856 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18857 		void CGContextSaveGState(CGContextRef c);
18858 		void CGContextRestoreGState(CGContextRef c);
18859 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18860 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18861 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18862 
18863 		void CGImageRelease(CGImageRef image);
18864 	}
18865 } else static assert(0, "Unsupported operating system");
18866 
18867 
18868 version(OSXCocoa) {
18869 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18870 	//
18871 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18872 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18873 	//
18874 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18875 	// Probably won't even fully compile right now
18876 
18877 	private enum double PI = 3.14159265358979323;
18878 
18879 	alias NSWindow NativeWindowHandle;
18880 	alias void delegate(NSid) NativeEventHandler;
18881 
18882 	enum KEY_ESCAPE = 27;
18883 
18884 	mixin template NativeImageImplementation() {
18885 		CGContextRef context;
18886 		ubyte* rawData;
18887 
18888 		final:
18889 
18890 		void convertToRgbaBytes(ubyte[] where) @system {
18891 			assert(where.length == this.width * this.height * 4);
18892 
18893 			// if rawData had a length....
18894 			//assert(rawData.length == where.length);
18895 			for(long idx = 0; idx < where.length; idx += 4) {
18896 				auto alpha = rawData[idx + 3];
18897 				if(alpha == 255) {
18898 					where[idx + 0] = rawData[idx + 0]; // r
18899 					where[idx + 1] = rawData[idx + 1]; // g
18900 					where[idx + 2] = rawData[idx + 2]; // b
18901 					where[idx + 3] = rawData[idx + 3]; // a
18902 				} else {
18903 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18904 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18905 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18906 					where[idx + 3] = rawData[idx + 3]; // a
18907 
18908 				}
18909 			}
18910 		}
18911 
18912 		void setFromRgbaBytes(in ubyte[] where) @system {
18913 			// FIXME: this is probably wrong
18914 			assert(where.length == this.width * this.height * 4);
18915 
18916 			// if rawData had a length....
18917 			//assert(rawData.length == where.length);
18918 			for(long idx = 0; idx < where.length; idx += 4) {
18919 				auto alpha = where[idx + 3];
18920 				if(alpha == 255) {
18921 					rawData[idx + 0] = where[idx + 0]; // r
18922 					rawData[idx + 1] = where[idx + 1]; // g
18923 					rawData[idx + 2] = where[idx + 2]; // b
18924 					rawData[idx + 3] = where[idx + 3]; // a
18925 				} else if(alpha == 0) {
18926 					rawData[idx + 0] = 0;
18927 					rawData[idx + 1] = 0;
18928 					rawData[idx + 2] = 0;
18929 					rawData[idx + 3] = 0;
18930 				} else {
18931 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18932 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18933 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18934 					rawData[idx + 3] = where[idx + 3]; // a
18935 				}
18936 			}
18937 		}
18938 
18939 
18940 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18941 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18942 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18943 			CGColorSpaceRelease(colorSpace);
18944 			rawData = CGBitmapContextGetData(context);
18945 		}
18946 		void dispose() {
18947 			CGContextRelease(context);
18948 		}
18949 
18950 		void setPixel(int x, int y, Color c) @system {
18951 			auto offset = (y * width + x) * 4;
18952 			if (c.a == 255) {
18953 				rawData[offset + 0] = c.r;
18954 				rawData[offset + 1] = c.g;
18955 				rawData[offset + 2] = c.b;
18956 				rawData[offset + 3] = c.a;
18957 			} else {
18958 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18959 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18960 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18961 				rawData[offset + 3] = c.a;
18962 			}
18963 		}
18964 	}
18965 
18966 	mixin template NativeScreenPainterImplementation() {
18967 		CGContextRef context;
18968 		ubyte[4] _outlineComponents;
18969 		NSView view;
18970 
18971 		Pen _activePen;
18972 		Color _fillColor;
18973 		Rectangle _clipRectangle;
18974 		OperatingSystemFont _font;
18975 
18976 		OperatingSystemFont getFont() {
18977 			if(_font is null) {
18978 				static OperatingSystemFont _defaultFont;
18979 				if(_defaultFont is null) {
18980 					_defaultFont = new OperatingSystemFont();
18981 					_defaultFont.loadDefault();
18982 				}
18983 				_font = _defaultFont;
18984 			}
18985 
18986 			return _font;
18987 		}
18988 
18989 		void create(PaintingHandle window) {
18990 			// this.destiny = window;
18991 			if(auto sw = cast(SimpleWindow) this.window) {
18992 				context = sw.drawingContext;
18993 				view = sw.view;
18994 			} else {
18995 				throw new NotYetImplementedException();
18996 			}
18997 		}
18998 
18999 		void dispose() {
19000 			view.setNeedsDisplay(true);
19001 		}
19002 
19003 		bool manualInvalidations;
19004 		void invalidateRect(Rectangle invalidRect) { }
19005 
19006 		// NotYetImplementedException
19007 		void rasterOp(RasterOp op) {
19008 		}
19009 		void setClipRectangle(int, int, int, int) {
19010 		}
19011 		Size textSize(in char[] txt) {
19012 			auto font = getFont();
19013 			return Size(font.stringWidth(txt), font.height());
19014 		}
19015 
19016 		void setFont(OperatingSystemFont font) {
19017 			_font = font;
19018 			//font.font.setInContext(context);
19019 		}
19020 		int fontHeight() {
19021 			auto font = getFont();
19022 			return font.height;
19023 		}
19024 
19025 		// end
19026 
19027 		void pen(Pen pen) {
19028 			_activePen = pen;
19029 			auto color = pen.color; // FIXME
19030 			double alphaComponent = color.a/255.0f;
19031 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
19032 
19033 			double[2] patternBuffer;
19034 			double[] pattern;
19035 			final switch(pen.style) {
19036 				case Pen.Style.Solid:
19037 					pattern = null;
19038 				break;
19039 				case Pen.Style.Dashed:
19040 					patternBuffer[0] = 4;
19041 					patternBuffer[1] = 1;
19042 					pattern = patternBuffer[];
19043 				break;
19044 				case Pen.Style.Dotted:
19045 					patternBuffer[0] = 1;
19046 					patternBuffer[1] = 1;
19047 					pattern = patternBuffer[];
19048 				break;
19049 			}
19050 
19051 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
19052 
19053 			if (color.a != 255) {
19054 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
19055 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
19056 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
19057 				_outlineComponents[3] = color.a;
19058 			} else {
19059 				_outlineComponents[0] = color.r;
19060 				_outlineComponents[1] = color.g;
19061 				_outlineComponents[2] = color.b;
19062 				_outlineComponents[3] = color.a;
19063 			}
19064 		}
19065 
19066 		@property void fillColor(Color color) {
19067 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
19068 		}
19069 
19070 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
19071 		// NotYetImplementedException for upper left/width/height
19072 			auto cgImage = CGBitmapContextCreateImage(image.context);
19073 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
19074 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
19075 			CGImageRelease(cgImage);
19076 		}
19077 
19078 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
19079 		// FIXME: is this efficient?
19080 			auto cgImage = CGBitmapContextCreateImage(s.handle);
19081 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
19082 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
19083 			CGImageRelease(cgImage);
19084 		}
19085 
19086 
19087 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
19088 		// FIXME: alignment
19089 			if (_outlineComponents[3] != 0) {
19090 				CGContextSaveGState(context);
19091 				auto invAlpha = 1.0f/_outlineComponents[3];
19092 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
19093 												  _outlineComponents[1]*invAlpha,
19094 												  _outlineComponents[2]*invAlpha,
19095 												  _outlineComponents[3]/255.0f);
19096 
19097 
19098 
19099 				// FIXME: should we clip it to the bounding box?
19100 				int textHeight = fontHeight;
19101 
19102 				auto lines = text.split('\n');
19103 
19104 				const lineHeight = textHeight;
19105 				textHeight *= lines.length;
19106 
19107 				int cy = y;
19108 
19109 				if(alignment & TextAlignment.VerticalBottom) {
19110 					if(y2 <= 0)
19111 						return;
19112 					auto h = y2 - y;
19113 					if(h > textHeight) {
19114 						cy += h - textHeight;
19115 						cy -= lineHeight / 2;
19116 					}
19117 				} else if(alignment & TextAlignment.VerticalCenter) {
19118 					if(y2 <= 0)
19119 						return;
19120 					auto h = y2 - y;
19121 					if(textHeight < h) {
19122 						cy += (h - textHeight) / 2;
19123 						//cy -= lineHeight / 4;
19124 					}
19125 				}
19126 
19127 				foreach(line; text.split('\n')) {
19128 					int textWidth = this.textSize(line).width;
19129 
19130 					int px = x, py = cy;
19131 
19132 					if(alignment & TextAlignment.Center) {
19133 						if(x2 <= 0)
19134 							return;
19135 						auto w = x2 - x;
19136 						if(w > textWidth)
19137 							px += (w - textWidth) / 2;
19138 					} else if(alignment & TextAlignment.Right) {
19139 						if(x2 <= 0)
19140 							return;
19141 						auto pos = x2 - textWidth;
19142 						if(pos > x)
19143 							px = pos;
19144 					}
19145 
19146 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
19147 
19148 					carry_on:
19149 					cy += lineHeight + 4;
19150 				}
19151 
19152 // auto cfstr = cast(NSid)createCFString(text);
19153 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
19154 // NSPoint(x, y), null);
19155 // CFRelease(cfstr);
19156 				CGContextRestoreGState(context);
19157 			}
19158 		}
19159 
19160 		void drawPixel(int x, int y) {
19161 			auto rawData = CGBitmapContextGetData(context);
19162 			auto width = CGBitmapContextGetWidth(context);
19163 			auto height = CGBitmapContextGetHeight(context);
19164 			auto offset = ((height - y - 1) * width + x) * 4;
19165 			rawData[offset .. offset+4] = _outlineComponents;
19166 		}
19167 
19168 		void drawLine(int x1, int y1, int x2, int y2) {
19169 			CGPoint[2] linePoints;
19170 			linePoints[0] = CGPoint(x1, y1);
19171 			linePoints[1] = CGPoint(x2, y2);
19172 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
19173 		}
19174 
19175 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
19176 			drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded
19177 		}
19178 
19179 		void drawRectangle(int x, int y, int width, int height) {
19180 			CGContextBeginPath(context);
19181 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
19182 			CGContextAddRect(context, rect);
19183 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19184 		}
19185 
19186 		void drawEllipse(int x1, int y1, int x2, int y2) {
19187 			CGContextBeginPath(context);
19188 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
19189 			CGContextAddEllipseInRect(context, rect);
19190 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19191 		}
19192 
19193 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
19194 			// @@@BUG@@@ Does not support elliptic arc (width != height).
19195 			CGContextBeginPath(context);
19196 			int clockwise = 0;
19197 			if(length < 0) {
19198 				clockwise = 1;
19199 				length = -length;
19200 			}
19201 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
19202 							start*PI/(180*64), (start+length)*PI/(180*64), clockwise);
19203 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19204 		}
19205 
19206 		void drawPolygon(Point[] intPoints) {
19207 			CGContextBeginPath(context);
19208 			CGPoint[16] pointsBuffer;
19209 			CGPoint[] points;
19210 			if(intPoints.length <= pointsBuffer.length)
19211 				points = pointsBuffer[0 .. intPoints.length];
19212 			else
19213 				points = new CGPoint[](intPoints.length);
19214 
19215 			foreach(idx, pt; intPoints)
19216 				points[idx] = CGPoint(pt.x, pt.y);
19217 
19218 			CGContextAddLines(context, points.ptr, points.length);
19219 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19220 		}
19221 	}
19222 
19223 	private bool appInitialized = false;
19224 	void initializeApp() {
19225 		if(appInitialized)
19226 			return;
19227 		synchronized {
19228 			if(appInitialized)
19229 				return;
19230 
19231 			auto app = NSApp(); // ensure the is initialized
19232 
19233 			auto dg = AppDelegate.alloc;
19234 			globalAppDelegate = dg;
19235 			NSApp.delegate_ = dg;
19236 
19237 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
19238 
19239 			appInitialized = true;
19240 		}
19241 	}
19242 
19243 	mixin template NativeSimpleWindowImplementation() {
19244 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
19245 			initializeApp();
19246 
19247 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
19248 
19249 			auto window = NSWindow.alloc.initWithContentRect(
19250 				contentRect,
19251 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
19252 				NSBackingStoreType.buffered,
19253 				true
19254 			);
19255 
19256 			SimpleWindow.nativeMapping[cast(void*) window] = this;
19257 
19258 			window.title = MacString(title).borrow;
19259 
19260 			auto dg = SDWindowDelegate.alloc.init;
19261 			dg.simpleWindow = this;
19262 			window.delegate_ = dg;
19263 
19264 			auto view = SDGraphicsView.alloc.init;
19265 			assert(view !is null);
19266 			window.contentView = view;
19267 			this.view = view;
19268 			view.simpleWindow = this;
19269 
19270 			window.center();
19271 
19272 			window.makeKeyAndOrderFront(null);
19273 
19274 			// no need to make a bitmap on mac since everything is double buffered already
19275 
19276 			// create area to draw on.
19277 			createNewDrawingContext(width, height);
19278 
19279 			window.setBackgroundColor(NSColor.whiteColor);
19280 		}
19281 
19282 		void createNewDrawingContext(int width, int height) {
19283 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
19284 			if(this.drawingContext)
19285 				CGContextRelease(this.drawingContext);
19286 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
19287 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
19288 			CGColorSpaceRelease(colorSpace);
19289 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
19290 			auto matrix = CGContextGetTextMatrix(drawingContext);
19291 			matrix.c = -matrix.c;
19292 			matrix.d = -matrix.d;
19293 			CGContextSetTextMatrix(drawingContext, matrix);
19294 
19295 		}
19296 
19297 		void dispose() {
19298 			closeWindow();
19299 			// window.release(); // closing the window does this automatically i think
19300 		}
19301 		void closeWindow() {
19302 			if(timer)
19303 				timer.invalidate();
19304 			window.close();
19305 		}
19306 
19307 		ScreenPainter getPainter(bool manualInvalidations) {
19308 			return ScreenPainter(this, this.window, manualInvalidations);
19309 		}
19310 
19311 		NSWindow window;
19312 		NSTimer timer;
19313 		NSView view;
19314 		CGContextRef drawingContext;
19315 	}
19316 }
19317 
19318 version(without_opengl) {} else
19319 extern(System) nothrow @nogc {
19320 	//enum uint GL_VERSION = 0x1F02;
19321 	//const(char)* glGetString (/*GLenum*/uint);
19322 	version(X11) {
19323 	static if (!SdpyIsUsingIVGLBinds) {
19324 
19325 		enum GLX_X_RENDERABLE = 0x8012;
19326 		enum GLX_DRAWABLE_TYPE = 0x8010;
19327 		enum GLX_RENDER_TYPE = 0x8011;
19328 		enum GLX_X_VISUAL_TYPE = 0x22;
19329 		enum GLX_TRUE_COLOR = 0x8002;
19330 		enum GLX_WINDOW_BIT = 0x00000001;
19331 		enum GLX_RGBA_BIT = 0x00000001;
19332 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
19333 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
19334 		enum GLX_SAMPLES = 0x186a1;
19335 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19336 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19337 	}
19338 
19339 		// GLX_EXT_swap_control
19340 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
19341 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
19342 
19343 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
19344 		extern(System) {
19345 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
19346 		}
19347 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
19348 
19349 		// this made public so we don't have to get it again and again
19350 		public bool glXCreateContextAttribsARB_present () @system {
19351 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
19352 				// get it
19353 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
19354 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
19355 			}
19356 			return (glXCreateContextAttribsARBFn !is null);
19357 		}
19358 
19359 		// this made public so we don't have to get it again and again
19360 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system {
19361 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
19362 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
19363 		}
19364 
19365 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
19366 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
19367 
19368 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
19369 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
19370 			if (_glx_swapInterval_fn is null) {
19371 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
19372 				if (_glx_swapInterval_fn is null) {
19373 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
19374 					return;
19375 				}
19376 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
19377 			}
19378 
19379 			if(glXSwapIntervalMESA is null) {
19380 				// it seems to require both to actually take effect on many computers
19381 				// idk why
19382 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
19383 				if(glXSwapIntervalMESA is null)
19384 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
19385 			}
19386 
19387 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
19388 				glXSwapIntervalMESA(wait ? 1 : 0);
19389 
19390 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
19391 		}
19392 	} else version(Windows) {
19393 	static if (!SdpyIsUsingIVGLBinds) {
19394 	enum GL_TRUE = 1;
19395 	enum GL_FALSE = 0;
19396 
19397 	public void* glbindGetProcAddress (const(char)* name) {
19398 		void* res = wglGetProcAddress(name);
19399 		if (res is null) {
19400 			/+
19401 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
19402 			import core.sys.windows.windef, core.sys.windows.winbase;
19403 			__gshared HINSTANCE dll = null;
19404 			if (dll is null) {
19405 				dll = LoadLibraryA("opengl32.dll");
19406 				if (dll is null) return null; // <32, but idc
19407 			}
19408 			res = GetProcAddress(dll, name);
19409 			+/
19410 			res = GetProcAddress(gl.libHandle, name);
19411 		}
19412 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
19413 		return res;
19414 	}
19415 	}
19416 
19417 
19418  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
19419 	void wglSetVSync(bool wait) {
19420 		if(wglSwapIntervalEXT is null) {
19421 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
19422 			if(wglSwapIntervalEXT is null)
19423 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
19424 		}
19425 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
19426 			return;
19427 
19428 		wglSwapIntervalEXT(wait ? 1 : 0);
19429 	}
19430 
19431 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19432 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19433 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
19434 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
19435 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
19436 
19437 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
19438 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
19439 
19440 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
19441 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
19442 
19443 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
19444 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
19445 
19446 		void wglInitOtherFunctions () {
19447 			if (wglCreateContextAttribsARB is null) {
19448 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
19449 			}
19450 		}
19451 	}
19452 
19453 	static if (!SdpyIsUsingIVGLBinds) {
19454 
19455 	interface GL {
19456 		extern(System) @nogc nothrow:
19457 
19458 		void glGetIntegerv(int, void*);
19459 		void glMatrixMode(int);
19460 		void glPushMatrix();
19461 		void glLoadIdentity();
19462 		void glOrtho(double, double, double, double, double, double);
19463 		void glFrustum(double, double, double, double, double, double);
19464 
19465 		void glPopMatrix();
19466 		void glEnable(int);
19467 		void glDisable(int);
19468 		void glClear(int);
19469 		void glBegin(int);
19470 		void glVertex2f(float, float);
19471 		void glVertex3f(float, float, float);
19472 		void glEnd();
19473 		void glColor3b(byte, byte, byte);
19474 		void glColor3ub(ubyte, ubyte, ubyte);
19475 		void glColor4b(byte, byte, byte, byte);
19476 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
19477 		void glColor3i(int, int, int);
19478 		void glColor3ui(uint, uint, uint);
19479 		void glColor4i(int, int, int, int);
19480 		void glColor4ui(uint, uint, uint, uint);
19481 		void glColor3f(float, float, float);
19482 		void glColor4f(float, float, float, float);
19483 		void glTranslatef(float, float, float);
19484 		void glScalef(float, float, float);
19485 		version(X11) {
19486 			void glSecondaryColor3b(byte, byte, byte);
19487 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
19488 			void glSecondaryColor3i(int, int, int);
19489 			void glSecondaryColor3ui(uint, uint, uint);
19490 			void glSecondaryColor3f(float, float, float);
19491 		}
19492 
19493 		void glDrawElements(int, int, int, void*);
19494 
19495 		void glRotatef(float, float, float, float);
19496 
19497 		uint glGetError();
19498 
19499 		void glDeleteTextures(int, uint*);
19500 
19501 
19502 		void glRasterPos2i(int, int);
19503 		void glDrawPixels(int, int, uint, uint, void*);
19504 		void glClearColor(float, float, float, float);
19505 
19506 
19507 		void glPixelStorei(uint, int);
19508 
19509 		void glGenTextures(uint, uint*);
19510 		void glBindTexture(int, int);
19511 		void glTexParameteri(uint, uint, int);
19512 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19513 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
19514 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
19515 			/*GLsizei*/int width, /*GLsizei*/int height,
19516 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19517 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19518 
19519 		void glLineWidth(int);
19520 
19521 
19522 		void glTexCoord2f(float, float);
19523 		void glVertex2i(int, int);
19524 		void glBlendFunc (int, int);
19525 		void glDepthFunc (int);
19526 		void glViewport(int, int, int, int);
19527 
19528 		void glClearDepth(double);
19529 
19530 		void glReadBuffer(uint);
19531 		void glReadPixels(int, int, int, int, int, int, void*);
19532 
19533 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
19534 
19535 		void glFlush();
19536 		void glFinish();
19537 
19538 		version(Windows) {
19539 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
19540 			HGLRC wglCreateContext(HDC);
19541 			HGLRC wglCreateLayerContext(HDC, int);
19542 			BOOL wglDeleteContext(HGLRC);
19543 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
19544 			HGLRC wglGetCurrentContext();
19545 			HDC wglGetCurrentDC();
19546 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
19547 			PROC wglGetProcAddress(LPCSTR);
19548 			BOOL wglMakeCurrent(HDC, HGLRC);
19549 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
19550 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
19551 			BOOL wglShareLists(HGLRC, HGLRC);
19552 			BOOL wglSwapLayerBuffers(HDC, UINT);
19553 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
19554 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
19555 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19556 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19557 		}
19558 
19559 	}
19560 
19561 	interface GL3 {
19562 		extern(System) @nogc nothrow:
19563 
19564 		void glGenVertexArrays(GLsizei, GLuint*);
19565 		void glBindVertexArray(GLuint);
19566 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
19567 		void glGenerateMipmap(GLenum);
19568 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
19569 		void glStencilMask(GLuint);
19570 		void glStencilFunc(GLenum, GLint, GLuint);
19571 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19572 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19573 		GLuint glCreateProgram();
19574 		GLuint glCreateShader(GLenum);
19575 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
19576 		void glCompileShader(GLuint);
19577 		void glGetShaderiv(GLuint, GLenum, GLint*);
19578 		void glAttachShader(GLuint, GLuint);
19579 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
19580 		void glLinkProgram(GLuint);
19581 		void glGetProgramiv(GLuint, GLenum, GLint*);
19582 		void glDeleteProgram(GLuint);
19583 		void glDeleteShader(GLuint);
19584 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
19585 		void glGenBuffers(GLsizei, GLuint*);
19586 
19587 		void glUniform1f(GLint location, GLfloat v0);
19588 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
19589 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
19590 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
19591 		void glUniform1i(GLint location, GLint v0);
19592 		void glUniform2i(GLint location, GLint v0, GLint v1);
19593 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
19594 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
19595 		void glUniform1ui(GLint location, GLuint v0);
19596 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
19597 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
19598 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
19599 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
19600 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
19601 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
19602 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
19603 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
19604 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
19605 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
19606 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
19607 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
19608 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
19609 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
19610 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
19611 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19612 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19613 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19614 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19615 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19616 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19617 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19618 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19619 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19620 
19621 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
19622 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
19623 		void glDrawArrays(GLenum, GLint, GLsizei);
19624 		void glStencilOp(GLenum, GLenum, GLenum);
19625 		void glUseProgram(GLuint);
19626 		void glCullFace(GLenum);
19627 		void glFrontFace(GLenum);
19628 		void glActiveTexture(GLenum);
19629 		void glBindBuffer(GLenum, GLuint);
19630 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
19631 		void glEnableVertexAttribArray(GLuint);
19632 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
19633 		void glUniform1i(GLint, GLint);
19634 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
19635 		void glDisableVertexAttribArray(GLuint);
19636 		void glDeleteBuffers(GLsizei, const(GLuint)*);
19637 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
19638 		void glLogicOp (GLenum opcode);
19639 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
19640 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
19641 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
19642 		GLenum glCheckFramebufferStatus (GLenum target);
19643 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
19644 	}
19645 
19646 	interface GL4 {
19647 		extern(System) @nogc nothrow:
19648 
19649 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
19650 			/*GLsizei*/int width, /*GLsizei*/int height,
19651 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19652 	}
19653 
19654 	interface GLU {
19655 		extern(System) @nogc nothrow:
19656 
19657 		void gluLookAt(double, double, double, double, double, double, double, double, double);
19658 		void gluPerspective(double, double, double, double);
19659 
19660 		char* gluErrorString(uint);
19661 	}
19662 
19663 
19664 	enum GL_RED = 0x1903;
19665 	enum GL_ALPHA = 0x1906;
19666 
19667 	enum uint GL_FRONT = 0x0404;
19668 
19669 	enum uint GL_BLEND = 0x0be2;
19670 	enum uint GL_LEQUAL = 0x0203;
19671 
19672 
19673 	enum uint GL_RGB = 0x1907;
19674 	enum uint GL_BGRA = 0x80e1;
19675 	enum uint GL_RGBA = 0x1908;
19676 	enum uint GL_RGBA8 = 0x8058;
19677 	enum uint GL_TEXTURE_2D =   0x0DE1;
19678 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
19679 	enum uint GL_NEAREST = 0x2600;
19680 	enum uint GL_LINEAR = 0x2601;
19681 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
19682 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
19683 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
19684 	enum uint GL_REPEAT = 0x2901;
19685 	enum uint GL_CLAMP = 0x2900;
19686 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
19687 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
19688 	enum uint GL_DECAL = 0x2101;
19689 	enum uint GL_MODULATE = 0x2100;
19690 	enum uint GL_TEXTURE_ENV = 0x2300;
19691 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
19692 	enum uint GL_REPLACE = 0x1E01;
19693 	enum uint GL_LIGHTING = 0x0B50;
19694 	enum uint GL_DITHER = 0x0BD0;
19695 
19696 	enum uint GL_NO_ERROR = 0;
19697 
19698 
19699 
19700 	enum int GL_VIEWPORT = 0x0BA2;
19701 	enum int GL_MODELVIEW = 0x1700;
19702 	enum int GL_TEXTURE = 0x1702;
19703 	enum int GL_PROJECTION = 0x1701;
19704 	enum int GL_DEPTH_TEST = 0x0B71;
19705 
19706 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
19707 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
19708 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
19709 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
19710 
19711 	enum int GL_POINTS = 0x0000;
19712 	enum int GL_LINES =  0x0001;
19713 	enum int GL_LINE_LOOP = 0x0002;
19714 	enum int GL_LINE_STRIP = 0x0003;
19715 	enum int GL_TRIANGLES = 0x0004;
19716 	enum int GL_TRIANGLE_STRIP = 5;
19717 	enum int GL_TRIANGLE_FAN = 6;
19718 	enum int GL_QUADS = 7;
19719 	enum int GL_QUAD_STRIP = 8;
19720 	enum int GL_POLYGON = 9;
19721 
19722 	alias GLvoid = void;
19723 	alias GLboolean = ubyte;
19724 	alias GLint = int;
19725 	alias GLuint = uint;
19726 	alias GLenum = uint;
19727 	alias GLchar = char;
19728 	alias GLsizei = int;
19729 	alias GLfloat = float;
19730 	alias GLintptr = size_t;
19731 	alias GLsizeiptr = ptrdiff_t;
19732 
19733 
19734 	enum uint GL_INVALID_ENUM = 0x0500;
19735 
19736 	enum uint GL_ZERO = 0;
19737 	enum uint GL_ONE = 1;
19738 
19739 	enum uint GL_BYTE = 0x1400;
19740 	enum uint GL_UNSIGNED_BYTE = 0x1401;
19741 	enum uint GL_SHORT = 0x1402;
19742 	enum uint GL_UNSIGNED_SHORT = 0x1403;
19743 	enum uint GL_INT = 0x1404;
19744 	enum uint GL_UNSIGNED_INT = 0x1405;
19745 	enum uint GL_FLOAT = 0x1406;
19746 	enum uint GL_2_BYTES = 0x1407;
19747 	enum uint GL_3_BYTES = 0x1408;
19748 	enum uint GL_4_BYTES = 0x1409;
19749 	enum uint GL_DOUBLE = 0x140A;
19750 
19751 	enum uint GL_STREAM_DRAW = 0x88E0;
19752 
19753 	enum uint GL_CCW = 0x0901;
19754 
19755 	enum uint GL_STENCIL_TEST = 0x0B90;
19756 	enum uint GL_SCISSOR_TEST = 0x0C11;
19757 
19758 	enum uint GL_EQUAL = 0x0202;
19759 	enum uint GL_NOTEQUAL = 0x0205;
19760 
19761 	enum uint GL_ALWAYS = 0x0207;
19762 	enum uint GL_KEEP = 0x1E00;
19763 
19764 	enum uint GL_INCR = 0x1E02;
19765 
19766 	enum uint GL_INCR_WRAP = 0x8507;
19767 	enum uint GL_DECR_WRAP = 0x8508;
19768 
19769 	enum uint GL_CULL_FACE = 0x0B44;
19770 	enum uint GL_BACK = 0x0405;
19771 
19772 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
19773 	enum uint GL_VERTEX_SHADER = 0x8B31;
19774 
19775 	enum uint GL_COMPILE_STATUS = 0x8B81;
19776 	enum uint GL_LINK_STATUS = 0x8B82;
19777 
19778 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
19779 
19780 	enum uint GL_STATIC_DRAW = 0x88E4;
19781 
19782 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
19783 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
19784 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
19785 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
19786 
19787 	enum uint GL_GENERATE_MIPMAP = 0x8191;
19788 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
19789 
19790 	enum uint GL_TEXTURE0 = 0x84C0U;
19791 	enum uint GL_TEXTURE1 = 0x84C1U;
19792 
19793 	enum uint GL_ARRAY_BUFFER = 0x8892;
19794 
19795 	enum uint GL_SRC_COLOR = 0x0300;
19796 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
19797 	enum uint GL_SRC_ALPHA = 0x0302;
19798 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
19799 	enum uint GL_DST_ALPHA = 0x0304;
19800 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
19801 	enum uint GL_DST_COLOR = 0x0306;
19802 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
19803 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
19804 
19805 	enum uint GL_INVERT = 0x150AU;
19806 
19807 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
19808 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
19809 
19810 	enum uint GL_FRAMEBUFFER = 0x8D40U;
19811 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
19812 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
19813 
19814 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
19815 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
19816 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
19817 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
19818 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
19819 
19820 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
19821 	enum uint GL_CLEAR = 0x1500U;
19822 	enum uint GL_COPY = 0x1503U;
19823 	enum uint GL_XOR = 0x1506U;
19824 
19825 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
19826 
19827 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
19828 
19829 	}
19830 }
19831 
19832 /++
19833 	History:
19834 		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.
19835 +/
19836 __gshared bool gluSuccessfullyLoaded = true;
19837 
19838 version(without_opengl) {} else {
19839 static if(!SdpyIsUsingIVGLBinds) {
19840 	version(Windows) {
19841 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
19842 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
19843 	} else {
19844 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
19845 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
19846 	}
19847 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
19848 
19849 
19850 	shared static this() {
19851 		gl.loadDynamicLibrary();
19852 
19853 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
19854 		// unless those functions are actually used
19855 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
19856 		glu.loadDynamicLibrary();
19857 	}
19858 }
19859 }
19860 
19861 /++
19862 	Convenience method for converting D arrays to opengl buffer data
19863 
19864 	I would LOVE to overload it with the original glBufferData, but D won't
19865 	let me since glBufferData is a function pointer :(
19866 
19867 	Added: August 25, 2020 (version 8.5)
19868 +/
19869 version(without_opengl) {} else
19870 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
19871 	glBufferData(target, data.length, data.ptr, usage);
19872 }
19873 
19874 /++
19875 	History:
19876 		Added September 1, 2024
19877 +/
19878 version(without_opengl) {} else
19879 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) {
19880 	glBufferSubData(target, offset, data.length, data.ptr);
19881 }
19882 
19883 /++
19884 	Convenience class for using opengl shaders.
19885 
19886 	Ensure that you've loaded opengl 3+ and set your active
19887 	context before trying to use this.
19888 
19889 	Added: August 25, 2020 (version 8.5)
19890 +/
19891 version(without_opengl) {} else
19892 final class OpenGlShader {
19893 	private int shaderProgram_;
19894 	private @property void shaderProgram(int a) {
19895 		shaderProgram_ = a;
19896 	}
19897 	/// Get the program ID for use in OpenGL functions.
19898 	public @property int shaderProgram() {
19899 		return shaderProgram_;
19900 	}
19901 
19902 	/++
19903 
19904 	+/
19905 	static struct Source {
19906 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19907 		string code; ///
19908 	}
19909 
19910 	/++
19911 		Helper method to just compile some shader code and check for errors
19912 		while you do glCreateShader, etc. on the outside yourself.
19913 
19914 		This just does `glShaderSource` and `glCompileShader` for the given code.
19915 
19916 		If you the OpenGlShader class constructor, you never need to call this yourself.
19917 	+/
19918 	static void compile(int sid, Source code) {
19919 		const(char)*[1] buffer;
19920 		int[1] lengthBuffer;
19921 
19922 		buffer[0] = code.code.ptr;
19923 		lengthBuffer[0] = cast(int) code.code.length;
19924 
19925 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19926 		glCompileShader(sid);
19927 
19928 		int success;
19929 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19930 		if(!success) {
19931 			char[512] info;
19932 			int len;
19933 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19934 
19935 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19936 		}
19937 	}
19938 
19939 	/++
19940 		Calls `glLinkProgram` and throws if error a occurs.
19941 
19942 		If you the OpenGlShader class constructor, you never need to call this yourself.
19943 	+/
19944 	static void link(int shaderProgram) {
19945 		glLinkProgram(shaderProgram);
19946 		int success;
19947 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19948 		if(!success) {
19949 			char[512] info;
19950 			int len;
19951 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19952 
19953 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19954 		}
19955 	}
19956 
19957 	/++
19958 		Constructs the shader object by calling `glCreateProgram`, then
19959 		compiling each given [Source], and finally, linking them together.
19960 
19961 		Throws: on compile or link failure.
19962 	+/
19963 	this(Source[] codes...) {
19964 		shaderProgram = glCreateProgram();
19965 
19966 		int[16] shadersBufferStack;
19967 
19968 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19969 			shadersBufferStack[0 .. codes.length] :
19970 			new int[](codes.length);
19971 
19972 		foreach(idx, code; codes) {
19973 			shadersBuffer[idx] = glCreateShader(code.type);
19974 
19975 			compile(shadersBuffer[idx], code);
19976 
19977 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19978 		}
19979 
19980 		link(shaderProgram);
19981 
19982 		foreach(s; shadersBuffer)
19983 			glDeleteShader(s);
19984 	}
19985 
19986 	/// Calls `glUseProgram(this.shaderProgram)`
19987 	void use() {
19988 		glUseProgram(this.shaderProgram);
19989 	}
19990 
19991 	/// Deletes the program.
19992 	void delete_() {
19993 		glDeleteProgram(shaderProgram);
19994 		shaderProgram = 0;
19995 	}
19996 
19997 	/++
19998 		[OpenGlShader.uniforms].name gives you one of these.
19999 
20000 		You can get the id out of it or just assign
20001 	+/
20002 	static struct Uniform {
20003 		/// the id passed to glUniform*
20004 		int id;
20005 
20006 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
20007 		void opAssign(float x, float y, float z, float w) {
20008 			if(id != -1)
20009 			glUniform4f(id, x, y, z, w);
20010 		}
20011 
20012 		void opAssign(float x) {
20013 			if(id != -1)
20014 			glUniform1f(id, x);
20015 		}
20016 
20017 		void opAssign(float x, float y) {
20018 			if(id != -1)
20019 			glUniform2f(id, x, y);
20020 		}
20021 
20022 		void opAssign(T)(T t) {
20023 			t.glUniform(id);
20024 		}
20025 	}
20026 
20027 	static struct UniformsHelper {
20028 		OpenGlShader _shader;
20029 
20030 		@property Uniform opDispatch(string name)() {
20031 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
20032 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
20033 			//if(i == -1)
20034 				//throw new Exception("Could not find uniform " ~ name);
20035 			return Uniform(i);
20036 		}
20037 
20038 		@property void opDispatch(string name, T)(T t) {
20039 			Uniform f = this.opDispatch!name;
20040 			t.glUniform(f);
20041 		}
20042 	}
20043 
20044 	/++
20045 		Gives access to the uniforms through dot access.
20046 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
20047 	+/
20048 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
20049 }
20050 
20051 version(without_opengl) {} else {
20052 /++
20053 	A static container of experimental types and value constructors for opengl 3+ shaders.
20054 
20055 
20056 	You can declare variables like:
20057 
20058 	```
20059 	OGL.vec3f something;
20060 	```
20061 
20062 	But generally it would be used with [OpenGlShader]'s uniform helpers like
20063 
20064 	```
20065 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
20066 	```
20067 
20068 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
20069 
20070 
20071 	History:
20072 		Added December 7, 2021. Not yet stable.
20073 +/
20074 final class OGL {
20075 	static:
20076 
20077 	private template typeFromSpecifier(string specifier) {
20078 		static if(specifier == "f")
20079 			alias typeFromSpecifier = GLfloat;
20080 		else static if(specifier == "i")
20081 			alias typeFromSpecifier = GLint;
20082 		else static if(specifier == "ui")
20083 			alias typeFromSpecifier = GLuint;
20084 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
20085 	}
20086 
20087 	private template CommonType(T...) {
20088 		static if(T.length == 1)
20089 			alias CommonType = T[0];
20090 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
20091 			alias CommonType = CommonType!(C, T[2 .. $]);
20092 	}
20093 
20094 	private template typesToSpecifier(T...) {
20095 		static if(is(CommonType!T == float))
20096 			enum typesToSpecifier = "f";
20097 		else static if(is(CommonType!T == int))
20098 			enum typesToSpecifier = "i";
20099 		else static if(is(CommonType!T == uint))
20100 			enum typesToSpecifier = "ui";
20101 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
20102 	}
20103 
20104 	private template genNames(size_t dim, size_t dim2 = 0) {
20105 		string helper() {
20106 			string s;
20107 			if(dim2) {
20108 				static if(__VERSION__ < 2102)
20109 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug
20110 				else
20111 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;";
20112 			} else {
20113 				if(dim > 0) s ~= "type x = 0;";
20114 				if(dim > 1) s ~= "type y = 0;";
20115 				if(dim > 2) s ~= "type z = 0;";
20116 				if(dim > 3) s ~= "type w = 0;";
20117 			}
20118 
20119 			s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }";
20120 			if(dim2)
20121 				s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }";
20122 
20123 			return s;
20124 		}
20125 
20126 		enum genNames = helper();
20127 	}
20128 
20129 	// there's vec, arrays of vec, mat, and arrays of mat
20130 	template opDispatch(string name)
20131 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
20132 	{
20133 		static if(name[4] == 'x') {
20134 			enum dimX = cast(int) (name[3] - '0');
20135 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
20136 
20137 			enum dimY = cast(int) (name[5] - '0');
20138 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
20139 
20140 			enum isArray = name[$ - 1] == 'v';
20141 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
20142 			alias type = typeFromSpecifier!typeSpecifier;
20143 		} else {
20144 			enum dim = cast(int) (name[3] - '0');
20145 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
20146 			enum isArray = name[$ - 1] == 'v';
20147 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
20148 			alias type = typeFromSpecifier!typeSpecifier;
20149 		}
20150 
20151 		align(1)
20152 		struct opDispatch {
20153 			align(1):
20154 			static if(name[4] == 'x')
20155 				mixin(genNames!(dimX, dimY));
20156 			else
20157 				mixin(genNames!dim);
20158 
20159 			private void glUniform(OpenGlShader.Uniform assignTo) {
20160 				glUniform(assignTo.id);
20161 			}
20162 			private void glUniform(int assignTo) {
20163 				static if(name[4] == 'x') {
20164 					static if(name[3] == name[5]) {
20165 						// import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY);
20166 						mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]);
20167 					} else {
20168 						mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr);
20169 					}
20170 				} else
20171 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
20172 			}
20173 		}
20174 	}
20175 
20176 	auto vec(T...)(T members) {
20177 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
20178 	}
20179 }
20180 
20181 void checkGlError() {
20182 	auto error = glGetError();
20183 	int[] errors;
20184 	string[] errorStrings;
20185 	while(error != GL_NO_ERROR) {
20186 		errors ~= error;
20187 		switch(error) {
20188 			case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break;
20189 			case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break;
20190 			case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break;
20191 			case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break;
20192 			case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break;
20193 			case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break;
20194 			default: errorStrings ~= "idk";
20195 		}
20196 		error = glGetError();
20197 	}
20198 	if(errors.length)
20199 		throw ArsdException!"glGetError"(errors, errorStrings);
20200 }
20201 
20202 /++
20203 	A matrix for simple uses that easily integrates with [OpenGlShader].
20204 
20205 	Might not be useful to you since it only as some simple functions and
20206 	probably isn't that fast.
20207 
20208 	Note it uses an inline static array for its storage, so copying it
20209 	may be expensive.
20210 +/
20211 struct BasicMatrix(int columns, int rows, T = float) {
20212 	static import core.stdc.math;
20213 	static if(is(T == float)) {
20214 		alias cos = core.stdc.math.cosf;
20215 		alias sin = core.stdc.math.sinf;
20216 	} else {
20217 		alias cos = core.stdc.math.cos;
20218 		alias sin = core.stdc.math.sin;
20219 	}
20220 
20221 	T[columns * rows] data = 0.0;
20222 
20223 	/++
20224 
20225 	+/
20226 	this(T[columns * rows] data) {
20227 		this.data = data;
20228 	}
20229 
20230 	/++
20231 		Basic operations that operate *in place*.
20232 	+/
20233 	static if(columns == 4 && rows == 4)
20234 	void translate(T x, T y, T z) {
20235 		BasicMatrix m = [
20236 			1, 0, 0, x,
20237 			0, 1, 0, y,
20238 			0, 0, 1, z,
20239 			0, 0, 0, 1
20240 		];
20241 
20242 		this *= m;
20243 	}
20244 
20245 	/// ditto
20246 	static if(columns == 4 && rows == 4)
20247 	void scale(T x, T y, T z) {
20248 		BasicMatrix m = [
20249 			x, 0, 0, 0,
20250 			0, y, 0, 0,
20251 			0, 0, z, 0,
20252 			0, 0, 0, 1
20253 		];
20254 
20255 		this *= m;
20256 	}
20257 
20258 	/// ditto
20259 	static if(columns == 4 && rows == 4)
20260 	void rotateX(T theta) {
20261 		BasicMatrix m = [
20262 			1,          0,           0, 0,
20263 			0, cos(theta), -sin(theta), 0,
20264 			0, sin(theta),  cos(theta), 0,
20265 			0,          0,           0, 1
20266 		];
20267 
20268 		this *= m;
20269 	}
20270 
20271 	/// ditto
20272 	static if(columns == 4 && rows == 4)
20273 	void rotateY(T theta) {
20274 		BasicMatrix m = [
20275 			 cos(theta), 0,  sin(theta), 0,
20276 			          0, 1,           0, 0,
20277 			-sin(theta), 0,  cos(theta), 0,
20278 			          0, 0,           0, 1
20279 		];
20280 
20281 		this *= m;
20282 	}
20283 
20284 	/// ditto
20285 	static if(columns == 4 && rows == 4)
20286 	void rotateZ(T theta) {
20287 		BasicMatrix m = [
20288 			cos(theta), -sin(theta), 0, 0,
20289 			sin(theta),  cos(theta), 0, 0,
20290 			         0,           0, 1, 0,
20291 				 0,           0, 0, 1
20292 		];
20293 
20294 		this *= m;
20295 	}
20296 
20297 	/++
20298 
20299 	+/
20300 	static if(columns == rows)
20301 	static BasicMatrix identity() {
20302 		BasicMatrix m;
20303 		foreach(i; 0 .. columns)
20304 			m.data[0 + i + i * columns] = 1.0;
20305 		return m;
20306 	}
20307 
20308 	static if(columns == rows)
20309 	void loadIdentity() {
20310 		this = identity();
20311 	}
20312 
20313 	static if(columns == 4 && rows == 4)
20314 	static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) {
20315 		return BasicMatrix([
20316 			2/(r-l),       0,        0, -(r+l)/(r-l),
20317 			      0, 2/(t-b),        0, -(t+b)/(t-b),
20318 			      0,       0, -2/(f-n), -(f+n)/(f-n),
20319 			      0,       0,        0,            1
20320 		]);
20321 	}
20322 
20323 	static if(columns == 4 && rows == 4)
20324 	void loadOrtho(T l, T r, T b, T t, T n, T f) {
20325 		this = ortho(l, r, b, t, n, f);
20326 	}
20327 
20328 	void opOpAssign(string op : "+")(const BasicMatrix rhs) {
20329 		this.data[] += rhs.data;
20330 	}
20331 	void opOpAssign(string op : "-")(const BasicMatrix rhs) {
20332 		this.data[] -= rhs.data;
20333 	}
20334 	void opOpAssign(string op : "*")(const T rhs) {
20335 		this.data[] *= rhs;
20336 	}
20337 	void opOpAssign(string op : "/")(const T rhs) {
20338 		this.data[] /= rhs;
20339 	}
20340 	void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) {
20341 		static assert(columns == rhsRows);
20342 		auto multiplySize = columns;
20343 
20344 		auto tmp = this.data; // copy cuz it is a value type
20345 
20346 		int idx = 0;
20347 		foreach(r; 0 .. rows)
20348 		foreach(c; 0 .. columns) {
20349 			T sum = 0.0;
20350 
20351 			foreach(i; 0 .. multiplySize)
20352 				sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c];
20353 
20354 			tmp[idx++] = sum;
20355 		}
20356 
20357 		this.data = tmp;
20358 	}
20359 }
20360 
20361 unittest {
20362 	auto m = BasicMatrix!(2, 2)([
20363 		1, 2,
20364 		3, 4
20365 	]);
20366 
20367 	auto m2 = BasicMatrix!(2, 2)([
20368 		5, 6,
20369 		7, 8
20370 	]);
20371 
20372 	//import std.conv;
20373 	m *= m2;
20374 	assert(m.data == [
20375 		19, 22,
20376 		43, 50
20377 	]);//, to!string(m.data));
20378 }
20379 
20380 
20381 
20382 class GlObjectBase {
20383 	protected uint _vao;
20384 	protected uint _elementsCount;
20385 
20386 	protected uint element_buffer;
20387 
20388 	void gen() {
20389 		glGenVertexArrays(1, &_vao);
20390 	}
20391 
20392 	void bind() {
20393 		glBindVertexArray(_vao);
20394 	}
20395 
20396 	void dispose() {
20397 		glDeleteVertexArrays(1, &_vao);
20398 	}
20399 
20400 	void draw() {
20401 		bind();
20402 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20403 		glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null);
20404 	}
20405 }
20406 
20407 /++
20408 
20409 +/
20410 class GlObject(T) : GlObjectBase {
20411 	protected uint VBO;
20412 
20413 	this(T[] arr, uint[] indices) {
20414 		gen();
20415 		bind();
20416 
20417 		glGenBuffers(1, &VBO);
20418 		glGenBuffers(1, &element_buffer);
20419 
20420 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
20421 		glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW);
20422 
20423 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20424 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
20425 		_elementsCount = cast(int) indices.length;
20426 
20427 		foreach(int idx, memberName; __traits(allMembers, T)) {
20428 			static if(memberName != "__ctor") {
20429 			static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) {
20430 				glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof);
20431 				glEnableVertexAttribArray(idx);
20432 			} else static assert(0); }
20433 		}
20434 	}
20435 
20436 	static string generateShaderDefinitions() {
20437 		string code;
20438 
20439 		foreach(idx, memberName; __traits(allMembers, T)) {
20440 			// never use stringof ladies and gents it has a LU thing at the end of it
20441 			static if(memberName != "__ctor")
20442 			code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n";
20443 		}
20444 
20445 		return code;
20446 	}
20447 }
20448 
20449 private string typeToGl(T)() {
20450 	static if(is(T == float[4]))
20451 		return "vec4";
20452 	else static if(is(T == float[3]))
20453 		return "vec3";
20454 	else static if(is(T == float[2]))
20455 		return "vec2";
20456 	else static assert(0, T.stringof);
20457 }
20458 
20459 
20460 }
20461 
20462 version(Emscripten) {
20463 
20464 } else version(linux) {
20465 	version(with_eventloop) {} else {
20466 		private int epollFd = -1;
20467 		void prepareEventLoop() {
20468 			if(epollFd != -1)
20469 				return; // already initialized, no need to do it again
20470 			import ep = core.sys.linux.epoll;
20471 
20472 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
20473 			if(epollFd == -1)
20474 				throw new Exception("epoll create failure");
20475 		}
20476 	}
20477 } else version(Posix) {
20478 	void prepareEventLoop() {}
20479 }
20480 
20481 version(X11) {
20482 	import core.stdc.locale : LC_ALL; // rdmd fix
20483 	__gshared bool sdx_isUTF8Locale;
20484 
20485 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
20486 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
20487 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
20488 	// anal magic is here. I (Ketmar) hope you like it.
20489 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
20490 	// always return correct unicode symbols. The detection is here 'cause user can change locale
20491 	// later.
20492 
20493 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
20494 	shared static this () @system {
20495 		if(!librariesSuccessfullyLoaded)
20496 			return;
20497 
20498 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
20499 
20500 		// this doesn't hurt; it may add some locking, but the speed is still
20501 		// allows doing 60 FPS videogames; also, ignore the result, as most
20502 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
20503 		// never seen this failing).
20504 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
20505 
20506 		setlocale(LC_ALL, "");
20507 		// check if out locale is UTF-8
20508 		auto lct = setlocale(LC_CTYPE, null);
20509 		if (lct is null) {
20510 			sdx_isUTF8Locale = false;
20511 		} else {
20512 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
20513 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
20514 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
20515 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
20516 				{
20517 					sdx_isUTF8Locale = true;
20518 					break;
20519 				}
20520 			}
20521 		}
20522 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
20523 	}
20524 }
20525 
20526 class ExperimentalTextComponent2 {
20527 	/+
20528 		Stage 1: get it working monospace
20529 		Stage 2: use proportional font
20530 		Stage 3: allow changes in inline style
20531 		Stage 4: allow new fonts and sizes in the middle
20532 		Stage 5: optimize gap buffer
20533 		Stage 6: optimize layout
20534 		Stage 7: word wrap
20535 		Stage 8: justification
20536 		Stage 9: editing, selection, etc.
20537 
20538 			Operations:
20539 				insert text
20540 				overstrike text
20541 				select
20542 				cut
20543 				modify
20544 	+/
20545 
20546 	/++
20547 		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.
20548 	+/
20549 	this(SimpleWindow window) {
20550 		this.window = window;
20551 	}
20552 
20553 	private SimpleWindow window;
20554 
20555 
20556 	/++
20557 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
20558 		representing the internal parts. The first pass is focused on the x parameter, then the
20559 		renderer is responsible for going back to the parts in the current line and calling
20560 		adjustDownForAscent to change the y params.
20561 	+/
20562 	static interface ComponentRenderHelper {
20563 
20564 		/+
20565 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
20566 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
20567 			to move (adjust y to make room for new line) until you get back to the same position,
20568 			then you can stop - if one thing is unchanged, nothing after it is changed too.
20569 
20570 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
20571 			once you reach something that is unchanged, you can stop.
20572 		+/
20573 
20574 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
20575 
20576 		int ascent() const;
20577 		int descent() const;
20578 
20579 		int advance() const;
20580 
20581 		bool endsWithExplititLineBreak() const;
20582 	}
20583 
20584 	static interface RenderResult {
20585 		/++
20586 			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.
20587 		+/
20588 		void popFront();
20589 		@property bool empty() const;
20590 		@property ComponentRenderHelper front() const;
20591 
20592 		void repositionForNextLine(Point baseline, int availableWidth);
20593 	}
20594 
20595 	static interface ComponentInFlow {
20596 		void draw(ScreenPainter painter);
20597 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
20598 
20599 		bool startsWithExplicitLineBreak() const;
20600 	}
20601 
20602 	static class TextFlowComponent : ComponentInFlow {
20603 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
20604 
20605 		Color foreground;
20606 		Color background;
20607 
20608 		OperatingSystemFont font; // should NEVER be null
20609 
20610 		ubyte attributes; // underline, strike through, display on new block
20611 
20612 		version(Windows)
20613 			const(wchar)[] content;
20614 		else
20615 			const(char)[] content; // this should NEVER have a newline, except at the end
20616 
20617 		RenderedComponent[] rendered; // entirely controlled by [rerender]
20618 
20619 		// could prolly put some spacing around it too like margin / padding
20620 
20621 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
20622 			in { assert(font !is null);
20623 			     assert(!font.isNull); }
20624 			do
20625 		{
20626 			this.foreground = f;
20627 			this.background = b;
20628 			this.font = font;
20629 
20630 			this.attributes = attr;
20631 			version(Windows) {
20632 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
20633 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
20634 				auto buffer = new wchar[](sz);
20635 				this.content = makeWindowsString(c, buffer, conversionFlags);
20636 			} else {
20637 				this.content = c.dup;
20638 			}
20639 		}
20640 
20641 		void draw(ScreenPainter painter) {
20642 			painter.setFont(this.font);
20643 			painter.outlineColor = this.foreground;
20644 			painter.fillColor = Color.transparent;
20645 			foreach(rendered; this.rendered) {
20646 				// the component works in term of baseline,
20647 				// but the painter works in term of upper left bounding box
20648 				// so need to translate that
20649 
20650 				if(this.background.a) {
20651 					painter.fillColor = this.background;
20652 					painter.outlineColor = this.background;
20653 
20654 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
20655 
20656 					painter.outlineColor = this.foreground;
20657 					painter.fillColor = Color.transparent;
20658 				}
20659 
20660 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
20661 
20662 				// FIXME: strike through, underline, highlight selection, etc.
20663 			}
20664 		}
20665 	}
20666 
20667 	// I could split the parts into words on render
20668 	// for easier word-wrap, each one being an unbreakable "inline-block"
20669 	private TextFlowComponent[] parts;
20670 	private int needsRerenderFrom;
20671 
20672 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
20673 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
20674 		parts ~= new TextFlowComponent(f, b, font, attr, c);
20675 	}
20676 
20677 	static struct RenderedComponent {
20678 		int startX;
20679 		int startY;
20680 		short width;
20681 		// 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!
20682 		// for individual chars in here you've gotta process on demand
20683 		version(Windows)
20684 			const(wchar)[] slice;
20685 		else
20686 			const(char)[] slice;
20687 	}
20688 
20689 
20690 	void rerender(Rectangle boundingBox) {
20691 		Point baseline = boundingBox.upperLeft;
20692 
20693 		this.boundingBox.left = boundingBox.left;
20694 		this.boundingBox.top = boundingBox.top;
20695 
20696 		auto remainingParts = parts;
20697 
20698 		int largestX;
20699 
20700 
20701 		foreach(part; parts)
20702 			part.font.prepareContext(window);
20703 		scope(exit)
20704 		foreach(part; parts)
20705 			part.font.releaseContext();
20706 
20707 		calculateNextLine:
20708 
20709 		int nextLineHeight = 0;
20710 		int nextBiggestDescent = 0;
20711 
20712 		foreach(part; remainingParts) {
20713 			auto height = part.font.ascent;
20714 			if(height > nextLineHeight)
20715 				nextLineHeight = height;
20716 			if(part.font.descent > nextBiggestDescent)
20717 				nextBiggestDescent = part.font.descent;
20718 			if(part.content.length && part.content[$-1] == '\n')
20719 				break;
20720 		}
20721 
20722 		baseline.y += nextLineHeight;
20723 		auto lineStart = baseline;
20724 
20725 		while(remainingParts.length) {
20726 			remainingParts[0].rendered = null;
20727 
20728 			bool eol;
20729 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
20730 				eol = true;
20731 
20732 			// FIXME: word wrap
20733 			auto font = remainingParts[0].font;
20734 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
20735 			auto width = font.stringWidth(slice, window);
20736 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
20737 
20738 			remainingParts = remainingParts[1 .. $];
20739 			baseline.x += width;
20740 
20741 			if(eol) {
20742 				baseline.y += nextBiggestDescent;
20743 				if(baseline.x > largestX)
20744 					largestX = baseline.x;
20745 				baseline.x = lineStart.x;
20746 				goto calculateNextLine;
20747 			}
20748 		}
20749 
20750 		if(baseline.x > largestX)
20751 			largestX = baseline.x;
20752 
20753 		this.boundingBox.right = largestX;
20754 		this.boundingBox.bottom = baseline.y;
20755 	}
20756 
20757 	// you must call rerender first!
20758 	void draw(ScreenPainter painter) {
20759 		foreach(part; parts) {
20760 			part.draw(painter);
20761 		}
20762 	}
20763 
20764 	struct IdentifyResult {
20765 		TextFlowComponent part;
20766 		int charIndexInPart;
20767 		int totalCharIndex = -1; // if this is -1, it just means the end
20768 
20769 		Rectangle boundingBox;
20770 	}
20771 
20772 	IdentifyResult identify(Point pt, bool exact = false) {
20773 		if(parts.length == 0)
20774 			return IdentifyResult(null, 0);
20775 
20776 		if(pt.y < boundingBox.top) {
20777 			if(exact)
20778 				return IdentifyResult(null, 1);
20779 			return IdentifyResult(parts[0], 0);
20780 		}
20781 		if(pt.y > boundingBox.bottom) {
20782 			if(exact)
20783 				return IdentifyResult(null, 2);
20784 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
20785 		}
20786 
20787 		int tci = 0;
20788 
20789 		// I should probably like binary search this or something...
20790 		foreach(ref part; parts) {
20791 			foreach(rendered; part.rendered) {
20792 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
20793 				if(rect.contains(pt)) {
20794 					auto x = pt.x - rendered.startX;
20795 					auto estimatedIdx = x / part.font.averageWidth;
20796 
20797 					if(estimatedIdx < 0)
20798 						estimatedIdx = 0;
20799 
20800 					if(estimatedIdx > rendered.slice.length)
20801 						estimatedIdx = cast(int) rendered.slice.length;
20802 
20803 					int idx;
20804 					int x1, x2;
20805 					if(part.font.isMonospace) {
20806 						auto w = part.font.averageWidth;
20807 						if(!exact && x > (estimatedIdx + 1) * w)
20808 							return IdentifyResult(null, 4);
20809 						idx = estimatedIdx;
20810 						x1 = idx * w;
20811 						x2 = (idx + 1) * w;
20812 					} else {
20813 						idx = estimatedIdx;
20814 
20815 						part.font.prepareContext(window);
20816 						scope(exit) part.font.releaseContext();
20817 
20818 						// int iterations;
20819 
20820 						while(true) {
20821 							// iterations++;
20822 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
20823 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
20824 
20825 							x1 += rendered.startX;
20826 							x2 += rendered.startX;
20827 
20828 							if(pt.x < x1) {
20829 								if(idx == 0) {
20830 									if(exact)
20831 										return IdentifyResult(null, 6);
20832 									else
20833 										break;
20834 								}
20835 								idx--;
20836 							} else if(pt.x > x2) {
20837 								idx++;
20838 								if(idx > rendered.slice.length) {
20839 									if(exact)
20840 										return IdentifyResult(null, 5);
20841 									else
20842 										break;
20843 								}
20844 							} else if(pt.x >= x1 && pt.x <= x2) {
20845 								if(idx)
20846 									idx--; // point it at the original index
20847 								break; // we fit
20848 							}
20849 						}
20850 
20851 						// writeln(iterations)
20852 					}
20853 
20854 
20855 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
20856 				}
20857 			}
20858 			tci += cast(int) part.content.length; // FIXME: utf-8?
20859 		}
20860 		return IdentifyResult(null, 3);
20861 	}
20862 
20863 	Rectangle boundingBox; // only set after [rerender]
20864 
20865 	// text will be positioned around the exclusion zone
20866 	static struct ExclusionZone {
20867 
20868 	}
20869 
20870 	ExclusionZone[] exclusionZones;
20871 }
20872 
20873 
20874 // Don't use this yet. When I'm happy with it, I will move it to the
20875 // regular module namespace.
20876 mixin template ExperimentalTextComponent() {
20877 
20878 static:
20879 
20880 	alias Rectangle = arsd.color.Rectangle;
20881 
20882 	struct ForegroundColor {
20883 		Color color;
20884 		alias color this;
20885 
20886 		this(Color c) {
20887 			color = c;
20888 		}
20889 
20890 		this(int r, int g, int b, int a = 255) {
20891 			color = Color(r, g, b, a);
20892 		}
20893 
20894 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
20895 			return ForegroundColor(mixin("Color." ~ s));
20896 		}
20897 	}
20898 
20899 	struct BackgroundColor {
20900 		Color color;
20901 		alias color this;
20902 
20903 		this(Color c) {
20904 			color = c;
20905 		}
20906 
20907 		this(int r, int g, int b, int a = 255) {
20908 			color = Color(r, g, b, a);
20909 		}
20910 
20911 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
20912 			return BackgroundColor(mixin("Color." ~ s));
20913 		}
20914 	}
20915 
20916 	static class InlineElement {
20917 		string text;
20918 
20919 		BlockElement containingBlock;
20920 
20921 		Color color = Color.black;
20922 		Color backgroundColor = Color.transparent;
20923 		ushort styles;
20924 
20925 		string font;
20926 		int fontSize;
20927 
20928 		int lineHeight;
20929 
20930 		void* identifier;
20931 
20932 		Rectangle boundingBox;
20933 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
20934 
20935 		bool isMergeCompatible(InlineElement other) {
20936 			return
20937 				containingBlock is other.containingBlock &&
20938 				color == other.color &&
20939 				backgroundColor == other.backgroundColor &&
20940 				styles == other.styles &&
20941 				font == other.font &&
20942 				fontSize == other.fontSize &&
20943 				lineHeight == other.lineHeight &&
20944 				true;
20945 		}
20946 
20947 		int xOfIndex(size_t index) {
20948 			if(index < letterXs.length)
20949 				return letterXs[index];
20950 			else
20951 				return boundingBox.right;
20952 		}
20953 
20954 		InlineElement clone() {
20955 			auto ie = new InlineElement();
20956 			ie.tupleof = this.tupleof;
20957 			return ie;
20958 		}
20959 
20960 		InlineElement getPreviousInlineElement() {
20961 			InlineElement prev = null;
20962 			foreach(ie; this.containingBlock.parts) {
20963 				if(ie is this)
20964 					break;
20965 				prev = ie;
20966 			}
20967 			if(prev is null) {
20968 				BlockElement pb;
20969 				BlockElement cb = this.containingBlock;
20970 				moar:
20971 				foreach(ie; this.containingBlock.containingLayout.blocks) {
20972 					if(ie is cb)
20973 						break;
20974 					pb = ie;
20975 				}
20976 				if(pb is null)
20977 					return null;
20978 				if(pb.parts.length == 0) {
20979 					cb = pb;
20980 					goto moar;
20981 				}
20982 
20983 				prev = pb.parts[$-1];
20984 
20985 			}
20986 			return prev;
20987 		}
20988 
20989 		InlineElement getNextInlineElement() {
20990 			InlineElement next = null;
20991 			foreach(idx, ie; this.containingBlock.parts) {
20992 				if(ie is this) {
20993 					if(idx + 1 < this.containingBlock.parts.length)
20994 						next = this.containingBlock.parts[idx + 1];
20995 					break;
20996 				}
20997 			}
20998 			if(next is null) {
20999 				BlockElement n;
21000 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
21001 					if(ie is this.containingBlock) {
21002 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
21003 							n = this.containingBlock.containingLayout.blocks[idx + 1];
21004 						break;
21005 					}
21006 				}
21007 				if(n is null)
21008 					return null;
21009 
21010 				if(n.parts.length)
21011 					next = n.parts[0];
21012 				else {} // FIXME
21013 
21014 			}
21015 			return next;
21016 		}
21017 
21018 	}
21019 
21020 	// Block elements are used entirely for positioning inline elements,
21021 	// which are the things that are actually drawn.
21022 	class BlockElement {
21023 		InlineElement[] parts;
21024 		uint alignment;
21025 
21026 		int whiteSpace; // pre, pre-wrap, wrap
21027 
21028 		TextLayout containingLayout;
21029 
21030 		// inputs
21031 		Point where;
21032 		Size minimumSize;
21033 		Size maximumSize;
21034 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
21035 		void* identifier;
21036 
21037 		Rectangle margin;
21038 		Rectangle padding;
21039 
21040 		// outputs
21041 		Rectangle[] boundingBoxes;
21042 	}
21043 
21044 	struct TextIdentifyResult {
21045 		InlineElement element;
21046 		int offset;
21047 
21048 		private TextIdentifyResult fixupNewline() {
21049 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
21050 				offset--;
21051 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
21052 				offset--;
21053 			}
21054 			return this;
21055 		}
21056 	}
21057 
21058 	class TextLayout {
21059 		BlockElement[] blocks;
21060 		Rectangle boundingBox_;
21061 		Rectangle boundingBox() { return boundingBox_; }
21062 		void boundingBox(Rectangle r) {
21063 			if(r != boundingBox_) {
21064 				boundingBox_ = r;
21065 				layoutInvalidated = true;
21066 			}
21067 		}
21068 
21069 		Rectangle contentBoundingBox() {
21070 			Rectangle r;
21071 			foreach(block; blocks)
21072 			foreach(ie; block.parts) {
21073 				if(ie.boundingBox.right > r.right)
21074 					r.right = ie.boundingBox.right;
21075 				if(ie.boundingBox.bottom > r.bottom)
21076 					r.bottom = ie.boundingBox.bottom;
21077 			}
21078 			return r;
21079 		}
21080 
21081 		BlockElement[] getBlocks() {
21082 			return blocks;
21083 		}
21084 
21085 		InlineElement[] getTexts() {
21086 			InlineElement[] elements;
21087 			foreach(block; blocks)
21088 				elements ~= block.parts;
21089 			return elements;
21090 		}
21091 
21092 		string getPlainText() {
21093 			string text;
21094 			foreach(block; blocks)
21095 				foreach(part; block.parts)
21096 					text ~= part.text;
21097 			return text;
21098 		}
21099 
21100 		string getHtml() {
21101 			return null; // FIXME
21102 		}
21103 
21104 		this(Rectangle boundingBox) {
21105 			this.boundingBox = boundingBox;
21106 		}
21107 
21108 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
21109 			auto be = new BlockElement();
21110 			be.containingLayout = this;
21111 			if(after is null)
21112 				blocks ~= be;
21113 			else {
21114 				foreach(idx, b; blocks) {
21115 					if(b is after.containingBlock) {
21116 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
21117 						break;
21118 					}
21119 				}
21120 			}
21121 			return be;
21122 		}
21123 
21124 		void clear() {
21125 			blocks = null;
21126 			selectionStart = selectionEnd = caret = Caret.init;
21127 		}
21128 
21129 		void addText(Args...)(Args args) {
21130 			if(blocks.length == 0)
21131 				addBlock();
21132 
21133 			InlineElement ie = new InlineElement();
21134 			foreach(idx, arg; args) {
21135 				static if(is(typeof(arg) == ForegroundColor))
21136 					ie.color = arg;
21137 				else static if(is(typeof(arg) == TextFormat)) {
21138 					if(arg & 0x8000) // ~TextFormat.something turns it off
21139 						ie.styles &= arg;
21140 					else
21141 						ie.styles |= arg;
21142 				} else static if(is(typeof(arg) == string)) {
21143 					static if(idx == 0 && args.length > 1)
21144 						static assert(0, "Put styles before the string.");
21145 					size_t lastLineIndex;
21146 					foreach(cidx, char a; arg) {
21147 						if(a == '\n') {
21148 							ie.text = arg[lastLineIndex .. cidx + 1];
21149 							lastLineIndex = cidx + 1;
21150 							ie.containingBlock = blocks[$-1];
21151 							blocks[$-1].parts ~= ie.clone;
21152 							ie.text = null;
21153 						} else {
21154 
21155 						}
21156 					}
21157 
21158 					ie.text = arg[lastLineIndex .. $];
21159 					ie.containingBlock = blocks[$-1];
21160 					blocks[$-1].parts ~= ie.clone;
21161 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
21162 				}
21163 			}
21164 
21165 			invalidateLayout();
21166 		}
21167 
21168 		void tryMerge(InlineElement into, InlineElement what) {
21169 			if(!into.isMergeCompatible(what)) {
21170 				return; // cannot merge, different configs
21171 			}
21172 
21173 			// cool, can merge, bring text together...
21174 			into.text ~= what.text;
21175 
21176 			// and remove what
21177 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
21178 				if(what.containingBlock.parts[a] is what) {
21179 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
21180 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
21181 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
21182 
21183 				}
21184 			}
21185 
21186 			// FIXME: ensure no other carets have a reference to it
21187 		}
21188 
21189 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
21190 		TextIdentifyResult identify(int x, int y, bool exact = false) {
21191 			TextIdentifyResult inexactMatch;
21192 			foreach(block; blocks) {
21193 				foreach(part; block.parts) {
21194 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
21195 
21196 						// FIXME binary search
21197 						int tidx;
21198 						int lastX;
21199 						foreach_reverse(idxo, lx; part.letterXs) {
21200 							int idx = cast(int) idxo;
21201 							if(lx <= x) {
21202 								if(lastX && lastX - x < x - lx)
21203 									tidx = idx + 1;
21204 								else
21205 									tidx = idx;
21206 								break;
21207 							}
21208 							lastX = lx;
21209 						}
21210 
21211 						return TextIdentifyResult(part, tidx).fixupNewline;
21212 					} else if(!exact) {
21213 						// we're not in the box, but are we on the same line?
21214 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
21215 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
21216 					}
21217 				}
21218 			}
21219 
21220 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
21221 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
21222 
21223 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
21224 		}
21225 
21226 		void moveCaretToPixelCoordinates(int x, int y) {
21227 			auto result = identify(x, y);
21228 			caret.inlineElement = result.element;
21229 			caret.offset = result.offset;
21230 		}
21231 
21232 		void selectToPixelCoordinates(int x, int y) {
21233 			auto result = identify(x, y);
21234 
21235 			if(y < caretLastDrawnY1) {
21236 				// on a previous line, carat is selectionEnd
21237 				selectionEnd = caret;
21238 
21239 				selectionStart = Caret(this, result.element, result.offset);
21240 			} else if(y > caretLastDrawnY2) {
21241 				// on a later line
21242 				selectionStart = caret;
21243 
21244 				selectionEnd = Caret(this, result.element, result.offset);
21245 			} else {
21246 				// on the same line...
21247 				if(x <= caretLastDrawnX) {
21248 					selectionEnd = caret;
21249 					selectionStart = Caret(this, result.element, result.offset);
21250 				} else {
21251 					selectionStart = caret;
21252 					selectionEnd = Caret(this, result.element, result.offset);
21253 				}
21254 
21255 			}
21256 		}
21257 
21258 
21259 		/// Call this if the inputs change. It will reflow everything
21260 		void redoLayout(ScreenPainter painter) {
21261 			//painter.setClipRectangle(boundingBox);
21262 			auto pos = Point(boundingBox.left, boundingBox.top);
21263 
21264 			int lastHeight;
21265 			void nl() {
21266 				pos.x = boundingBox.left;
21267 				pos.y += lastHeight;
21268 			}
21269 			foreach(block; blocks) {
21270 				nl();
21271 				foreach(part; block.parts) {
21272 					part.letterXs = null;
21273 
21274 					auto size = painter.textSize(part.text);
21275 					version(Windows)
21276 						if(part.text.length && part.text[$-1] == '\n')
21277 							size.height /= 2; // windows counts the new line at the end, but we don't want that
21278 
21279 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
21280 
21281 					foreach(idx, char c; part.text) {
21282 							// FIXME: unicode
21283 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
21284 					}
21285 
21286 					pos.x += size.width;
21287 					if(pos.x >= boundingBox.right) {
21288 						pos.y += size.height;
21289 						pos.x = boundingBox.left;
21290 						lastHeight = 0;
21291 					} else {
21292 						lastHeight = size.height;
21293 					}
21294 
21295 					if(part.text.length && part.text[$-1] == '\n')
21296 						nl();
21297 				}
21298 			}
21299 
21300 			layoutInvalidated = false;
21301 		}
21302 
21303 		bool layoutInvalidated = true;
21304 		void invalidateLayout() {
21305 			layoutInvalidated = true;
21306 		}
21307 
21308 // FIXME: caret can remain sometimes when inserting
21309 // FIXME: inserting at the beginning once you already have something can eff it up.
21310 		void drawInto(ScreenPainter painter, bool focused = false) {
21311 			if(layoutInvalidated)
21312 				redoLayout(painter);
21313 			foreach(block; blocks) {
21314 				foreach(part; block.parts) {
21315 					painter.outlineColor = part.color;
21316 					painter.fillColor = part.backgroundColor;
21317 
21318 					auto pos = part.boundingBox.upperLeft;
21319 					auto size = part.boundingBox.size;
21320 
21321 					painter.drawText(pos, part.text);
21322 					if(part.styles & TextFormat.underline)
21323 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
21324 					if(part.styles & TextFormat.strikethrough)
21325 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
21326 				}
21327 			}
21328 
21329 			// on every redraw, I will force the caret to be
21330 			// redrawn too, in order to eliminate perceived lag
21331 			// when moving around with the mouse.
21332 			eraseCaret(painter);
21333 
21334 			if(focused) {
21335 				highlightSelection(painter);
21336 				drawCaret(painter);
21337 			}
21338 		}
21339 
21340 		Color selectionXorColor = Color(255, 255, 127);
21341 
21342 		void highlightSelection(ScreenPainter painter) {
21343 			if(selectionStart is selectionEnd)
21344 				return; // no selection
21345 
21346 			if(selectionStart.inlineElement is null) return;
21347 			if(selectionEnd.inlineElement is null) return;
21348 
21349 			assert(selectionStart.inlineElement !is null);
21350 			assert(selectionEnd.inlineElement !is null);
21351 
21352 			painter.rasterOp = RasterOp.xor;
21353 			painter.outlineColor = Color.transparent;
21354 			painter.fillColor = selectionXorColor;
21355 
21356 			auto at = selectionStart.inlineElement;
21357 			auto atOffset = selectionStart.offset;
21358 			bool done;
21359 			while(at) {
21360 				auto box = at.boundingBox;
21361 				if(atOffset < at.letterXs.length)
21362 					box.left = at.letterXs[atOffset];
21363 
21364 				if(at is selectionEnd.inlineElement) {
21365 					if(selectionEnd.offset < at.letterXs.length)
21366 						box.right = at.letterXs[selectionEnd.offset];
21367 					done = true;
21368 				}
21369 
21370 				painter.drawRectangle(box.upperLeft, box.width, box.height);
21371 
21372 				if(done)
21373 					break;
21374 
21375 				at = at.getNextInlineElement();
21376 				atOffset = 0;
21377 			}
21378 		}
21379 
21380 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
21381 		bool caretShowingOnScreen = false;
21382 		void drawCaret(ScreenPainter painter) {
21383 			//painter.setClipRectangle(boundingBox);
21384 			int x, y1, y2;
21385 			if(caret.inlineElement is null) {
21386 				x = boundingBox.left;
21387 				y1 = boundingBox.top + 2;
21388 				y2 = boundingBox.top + painter.fontHeight;
21389 			} else {
21390 				x = caret.inlineElement.xOfIndex(caret.offset);
21391 				y1 = caret.inlineElement.boundingBox.top + 2;
21392 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21393 			}
21394 
21395 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
21396 				eraseCaret(painter);
21397 
21398 			painter.pen = Pen(Color.white, 1);
21399 			painter.rasterOp = RasterOp.xor;
21400 			painter.drawLine(
21401 				Point(x, y1),
21402 				Point(x, y2)
21403 			);
21404 			painter.rasterOp = RasterOp.normal;
21405 			caretShowingOnScreen = !caretShowingOnScreen;
21406 
21407 			if(caretShowingOnScreen) {
21408 				caretLastDrawnX = x;
21409 				caretLastDrawnY1 = y1;
21410 				caretLastDrawnY2 = y2;
21411 			}
21412 		}
21413 
21414 		Rectangle caretBoundingBox() {
21415 			int x, y1, y2;
21416 			if(caret.inlineElement is null) {
21417 				x = boundingBox.left;
21418 				y1 = boundingBox.top + 2;
21419 				y2 = boundingBox.top + 16;
21420 			} else {
21421 				x = caret.inlineElement.xOfIndex(caret.offset);
21422 				y1 = caret.inlineElement.boundingBox.top + 2;
21423 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21424 			}
21425 
21426 			return Rectangle(x, y1, x + 1, y2);
21427 		}
21428 
21429 		void eraseCaret(ScreenPainter painter) {
21430 			//painter.setClipRectangle(boundingBox);
21431 			if(!caretShowingOnScreen) return;
21432 			painter.pen = Pen(Color.white, 1);
21433 			painter.rasterOp = RasterOp.xor;
21434 			painter.drawLine(
21435 				Point(caretLastDrawnX, caretLastDrawnY1),
21436 				Point(caretLastDrawnX, caretLastDrawnY2)
21437 			);
21438 
21439 			caretShowingOnScreen = false;
21440 			painter.rasterOp = RasterOp.normal;
21441 		}
21442 
21443 		/// Caret movement api
21444 		/// These should give the user a logical result based on what they see on screen...
21445 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
21446 		void moveUp() {
21447 			if(caret.inlineElement is null) return;
21448 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21449 			auto y = caret.inlineElement.boundingBox.top + 2;
21450 
21451 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21452 			if(y < 0)
21453 				return;
21454 
21455 			auto i = identify(x, y);
21456 
21457 			if(i.element) {
21458 				caret.inlineElement = i.element;
21459 				caret.offset = i.offset;
21460 			}
21461 		}
21462 		void moveDown() {
21463 			if(caret.inlineElement is null) return;
21464 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21465 			auto y = caret.inlineElement.boundingBox.bottom - 2;
21466 
21467 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21468 
21469 			auto i = identify(x, y);
21470 			if(i.element) {
21471 				caret.inlineElement = i.element;
21472 				caret.offset = i.offset;
21473 			}
21474 		}
21475 		void moveLeft() {
21476 			if(caret.inlineElement is null) return;
21477 			if(caret.offset)
21478 				caret.offset--;
21479 			else {
21480 				auto p = caret.inlineElement.getPreviousInlineElement();
21481 				if(p) {
21482 					caret.inlineElement = p;
21483 					if(p.text.length && p.text[$-1] == '\n')
21484 						caret.offset = cast(int) p.text.length - 1;
21485 					else
21486 						caret.offset = cast(int) p.text.length;
21487 				}
21488 			}
21489 		}
21490 		void moveRight() {
21491 			if(caret.inlineElement is null) return;
21492 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
21493 				caret.offset++;
21494 			} else {
21495 				auto p = caret.inlineElement.getNextInlineElement();
21496 				if(p) {
21497 					caret.inlineElement = p;
21498 					caret.offset = 0;
21499 				}
21500 			}
21501 		}
21502 		void moveHome() {
21503 			if(caret.inlineElement is null) return;
21504 			auto x = 0;
21505 			auto y = caret.inlineElement.boundingBox.top + 2;
21506 
21507 			auto i = identify(x, y);
21508 
21509 			if(i.element) {
21510 				caret.inlineElement = i.element;
21511 				caret.offset = i.offset;
21512 			}
21513 		}
21514 		void moveEnd() {
21515 			if(caret.inlineElement is null) return;
21516 			auto x = int.max;
21517 			auto y = caret.inlineElement.boundingBox.top + 2;
21518 
21519 			auto i = identify(x, y);
21520 
21521 			if(i.element) {
21522 				caret.inlineElement = i.element;
21523 				caret.offset = i.offset;
21524 			}
21525 
21526 		}
21527 		void movePageUp(ref Caret caret) {}
21528 		void movePageDown(ref Caret caret) {}
21529 
21530 		void moveDocumentStart(ref Caret caret) {
21531 			if(blocks.length && blocks[0].parts.length)
21532 				caret = Caret(this, blocks[0].parts[0], 0);
21533 			else
21534 				caret = Caret.init;
21535 		}
21536 
21537 		void moveDocumentEnd(ref Caret caret) {
21538 			if(blocks.length) {
21539 				auto parts = blocks[$-1].parts;
21540 				if(parts.length) {
21541 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
21542 				} else {
21543 					caret = Caret.init;
21544 				}
21545 			} else
21546 				caret = Caret.init;
21547 		}
21548 
21549 		void deleteSelection() {
21550 			if(selectionStart is selectionEnd)
21551 				return;
21552 
21553 			if(selectionStart.inlineElement is null) return;
21554 			if(selectionEnd.inlineElement is null) return;
21555 
21556 			assert(selectionStart.inlineElement !is null);
21557 			assert(selectionEnd.inlineElement !is null);
21558 
21559 			auto at = selectionStart.inlineElement;
21560 
21561 			if(selectionEnd.inlineElement is at) {
21562 				// same element, need to chop out
21563 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
21564 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
21565 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
21566 			} else {
21567 				// different elements, we can do it with slicing
21568 				at.text = at.text[0 .. selectionStart.offset];
21569 				if(selectionStart.offset < at.letterXs.length)
21570 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
21571 
21572 				at = at.getNextInlineElement();
21573 
21574 				while(at) {
21575 					if(at is selectionEnd.inlineElement) {
21576 						at.text = at.text[selectionEnd.offset .. $];
21577 						if(selectionEnd.offset < at.letterXs.length)
21578 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
21579 						selectionEnd.offset = 0;
21580 						break;
21581 					} else {
21582 						auto cfd = at;
21583 						cfd.text = null; // delete the whole thing
21584 
21585 						at = at.getNextInlineElement();
21586 
21587 						if(cfd.text.length == 0) {
21588 							// and remove cfd
21589 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
21590 								if(cfd.containingBlock.parts[a] is cfd) {
21591 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
21592 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
21593 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
21594 
21595 								}
21596 							}
21597 						}
21598 					}
21599 				}
21600 			}
21601 
21602 			caret = selectionEnd;
21603 			selectNone();
21604 
21605 			invalidateLayout();
21606 
21607 		}
21608 
21609 		/// Plain text editing api. These work at the current caret inside the selected inline element.
21610 		void insert(in char[] text) {
21611 			foreach(dchar ch; text)
21612 				insert(ch);
21613 		}
21614 		/// ditto
21615 		void insert(dchar ch) {
21616 
21617 			bool selectionDeleted = false;
21618 			if(selectionStart !is selectionEnd) {
21619 				deleteSelection();
21620 				selectionDeleted = true;
21621 			}
21622 
21623 			if(ch == 127) {
21624 				delete_();
21625 				return;
21626 			}
21627 			if(ch == 8) {
21628 				if(!selectionDeleted)
21629 					backspace();
21630 				return;
21631 			}
21632 
21633 			invalidateLayout();
21634 
21635 			if(ch == 13) ch = 10;
21636 			auto e = caret.inlineElement;
21637 			if(e is null) {
21638 				addText("" ~ cast(char) ch) ; // FIXME
21639 				return;
21640 			}
21641 
21642 			if(caret.offset == e.text.length) {
21643 				e.text ~= cast(char) ch; // FIXME
21644 				caret.offset++;
21645 				if(ch == 10) {
21646 					auto c = caret.inlineElement.clone;
21647 					c.text = null;
21648 					c.letterXs = null;
21649 					insertPartAfter(c,e);
21650 					caret = Caret(this, c, 0);
21651 				}
21652 			} else {
21653 				// FIXME cast char sucks
21654 				if(ch == 10) {
21655 					auto c = caret.inlineElement.clone;
21656 					c.text = e.text[caret.offset .. $];
21657 					if(caret.offset < c.letterXs.length)
21658 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
21659 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
21660 					if(caret.offset <= e.letterXs.length) {
21661 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
21662 					}
21663 					insertPartAfter(c,e);
21664 					caret = Caret(this, c, 0);
21665 				} else {
21666 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
21667 					caret.offset++;
21668 				}
21669 			}
21670 		}
21671 
21672 		void insertPartAfter(InlineElement what, InlineElement where) {
21673 			foreach(idx, p; where.containingBlock.parts) {
21674 				if(p is where) {
21675 					if(idx + 1 == where.containingBlock.parts.length)
21676 						where.containingBlock.parts ~= what;
21677 					else
21678 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
21679 					return;
21680 				}
21681 			}
21682 		}
21683 
21684 		void cleanupStructures() {
21685 			for(size_t i = 0; i < blocks.length; i++) {
21686 				auto block = blocks[i];
21687 				for(size_t a = 0; a < block.parts.length; a++) {
21688 					auto part = block.parts[a];
21689 					if(part.text.length == 0) {
21690 						for(size_t b = a; b < block.parts.length - 1; b++)
21691 							block.parts[b] = block.parts[b+1];
21692 						block.parts = block.parts[0 .. $-1];
21693 					}
21694 				}
21695 				if(block.parts.length == 0) {
21696 					for(size_t a = i; a < blocks.length - 1; a++)
21697 						blocks[a] = blocks[a+1];
21698 					blocks = blocks[0 .. $-1];
21699 				}
21700 			}
21701 		}
21702 
21703 		void backspace() {
21704 			try_again:
21705 			auto e = caret.inlineElement;
21706 			if(e is null)
21707 				return;
21708 			if(caret.offset == 0) {
21709 				auto prev = e.getPreviousInlineElement();
21710 				if(prev is null)
21711 					return;
21712 				auto newOffset = cast(int) prev.text.length;
21713 				tryMerge(prev, e);
21714 				caret.inlineElement = prev;
21715 				caret.offset = prev is null ? 0 : newOffset;
21716 
21717 				goto try_again;
21718 			} else if(caret.offset == e.text.length) {
21719 				e.text = e.text[0 .. $-1];
21720 				caret.offset--;
21721 			} else {
21722 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
21723 				caret.offset--;
21724 			}
21725 			//cleanupStructures();
21726 
21727 			invalidateLayout();
21728 		}
21729 		void delete_() {
21730 			if(selectionStart !is selectionEnd)
21731 				deleteSelection();
21732 			else {
21733 				auto before = caret;
21734 				moveRight();
21735 				if(caret != before) {
21736 					backspace();
21737 				}
21738 			}
21739 
21740 			invalidateLayout();
21741 		}
21742 		void overstrike() {}
21743 
21744 		/// Selection API. See also: caret movement.
21745 		void selectAll() {
21746 			moveDocumentStart(selectionStart);
21747 			moveDocumentEnd(selectionEnd);
21748 		}
21749 		bool selectNone() {
21750 			if(selectionStart != selectionEnd) {
21751 				selectionStart = selectionEnd = Caret.init;
21752 				return true;
21753 			}
21754 			return false;
21755 		}
21756 
21757 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
21758 		/// They will modify the current selection if there is one and will splice one in if needed.
21759 		void changeAttributes() {}
21760 
21761 
21762 		/// Text search api. They manipulate the selection and/or caret.
21763 		void findText(string text) {}
21764 		void findIndex(size_t textIndex) {}
21765 
21766 		// sample event handlers
21767 
21768 		void handleEvent(KeyEvent event) {
21769 			//if(event.type == KeyEvent.Type.KeyPressed) {
21770 
21771 			//}
21772 		}
21773 
21774 		void handleEvent(dchar ch) {
21775 
21776 		}
21777 
21778 		void handleEvent(MouseEvent event) {
21779 
21780 		}
21781 
21782 		bool contentEditable; // can it be edited?
21783 		bool contentCaretable; // is there a caret/cursor that moves around in there?
21784 		bool contentSelectable; // selectable?
21785 
21786 		Caret caret;
21787 		Caret selectionStart;
21788 		Caret selectionEnd;
21789 
21790 		bool insertMode;
21791 	}
21792 
21793 	struct Caret {
21794 		TextLayout layout;
21795 		InlineElement inlineElement;
21796 		int offset;
21797 	}
21798 
21799 	enum TextFormat : ushort {
21800 		// decorations
21801 		underline = 1,
21802 		strikethrough = 2,
21803 
21804 		// font selectors
21805 
21806 		bold = 0x4000 | 1, // weight 700
21807 		light = 0x4000 | 2, // weight 300
21808 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
21809 		// bold | light is really invalid but should give weight 500
21810 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
21811 
21812 		italic = 0x4000 | 8,
21813 		smallcaps = 0x4000 | 16,
21814 	}
21815 
21816 	void* findFont(string family, int weight, TextFormat formats) {
21817 		return null;
21818 	}
21819 
21820 }
21821 
21822 /++
21823 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21824 
21825 	History:
21826 		Added February 19, 2021
21827 +/
21828 /// Group: drag_and_drop
21829 interface DropHandler {
21830 	/++
21831 		Called when the drag enters the handler's area.
21832 	+/
21833 	DragAndDropAction dragEnter(DropPackage*);
21834 	/++
21835 		Called when the drag leaves the handler's area or is
21836 		cancelled. You should free your resources when this is called.
21837 	+/
21838 	void dragLeave();
21839 	/++
21840 		Called continually as the drag moves over the handler's area.
21841 
21842 		Returns: feedback to the dragger
21843 	+/
21844 	DropParameters dragOver(Point pt);
21845 	/++
21846 		The user dropped the data and you should process it now. You can
21847 		access the data through the given [DropPackage].
21848 	+/
21849 	void drop(scope DropPackage*);
21850 	/++
21851 		Called when the drop is complete. You should free whatever temporary
21852 		resources you were using. It is often reasonable to simply forward
21853 		this call to [dragLeave].
21854 	+/
21855 	void finish();
21856 
21857 	/++
21858 		Parameters returned by [DropHandler.drop].
21859 	+/
21860 	static struct DropParameters {
21861 		/++
21862 			Acceptable action over this area.
21863 		+/
21864 		DragAndDropAction action;
21865 		/++
21866 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
21867 
21868 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
21869 		+/
21870 		Rectangle consistentWithin;
21871 	}
21872 }
21873 
21874 /++
21875 	History:
21876 		Added February 19, 2021
21877 +/
21878 /// Group: drag_and_drop
21879 enum DragAndDropAction {
21880 	none = 0,
21881 	copy,
21882 	move,
21883 	link,
21884 	ask,
21885 	custom
21886 }
21887 
21888 /++
21889 	An opaque structure representing dropped data. It contains
21890 	private, platform-specific data that your `drop` function
21891 	should simply forward.
21892 
21893 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21894 
21895 	History:
21896 		Added February 19, 2021
21897 +/
21898 /// Group: drag_and_drop
21899 struct DropPackage {
21900 	/++
21901 		Lists the available formats as magic numbers. You should compare these
21902 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
21903 		understand the passed data.
21904 	+/
21905 	DraggableData.FormatId[] availableFormats() {
21906 		version(X11) {
21907 			return xFormats;
21908 		} else version(Windows) {
21909 			if(pDataObj is null)
21910 				return null;
21911 
21912 			typeof(return) ret;
21913 
21914 			IEnumFORMATETC ef;
21915 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
21916 				FORMATETC fmt;
21917 				ULONG fetched;
21918 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
21919 					if(fetched == 0)
21920 						break;
21921 
21922 					if(fmt.lindex != -1)
21923 						continue;
21924 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
21925 						continue;
21926 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
21927 						continue;
21928 
21929 					ret ~= fmt.cfFormat;
21930 				}
21931 			}
21932 
21933 			return ret;
21934 		} else throw new NotYetImplementedException();
21935 	}
21936 
21937 	/++
21938 		Gets data from the drop and optionally accepts it.
21939 
21940 		Returns:
21941 			void because the data is fed asynchronously through the `dg` parameter.
21942 
21943 		Params:
21944 			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.
21945 
21946 			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.
21947 
21948 			Calling `getData` again after accepting a drop is not permitted.
21949 
21950 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
21951 
21952 			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.
21953 
21954 		Throws:
21955 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
21956 
21957 		History:
21958 			Included in first release of [DropPackage].
21959 	+/
21960 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
21961 		version(X11) {
21962 
21963 			auto display = XDisplayConnection.get();
21964 			auto selectionAtom = GetAtom!"XdndSelection"(display);
21965 			auto best = format;
21966 
21967 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
21968 
21969 				XDisplay* display;
21970 				Atom selectionAtom;
21971 				DraggableData.FormatId best;
21972 				DraggableData.FormatId format;
21973 				void delegate(scope ubyte[] data) dg;
21974 				DragAndDropAction acceptedAction;
21975 				Window sourceWindow;
21976 				SimpleWindow win;
21977 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
21978 					this.display = display;
21979 					this.win = win;
21980 					this.sourceWindow = sourceWindow;
21981 					this.format = format;
21982 					this.selectionAtom = selectionAtom;
21983 					this.best = best;
21984 					this.dg = dg;
21985 					this.acceptedAction = acceptedAction;
21986 				}
21987 
21988 
21989 				mixin X11GetSelectionHandler_Basics;
21990 
21991 				void handleData(Atom target, in ubyte[] data) {
21992 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
21993 
21994 					dg(cast(ubyte[]) data);
21995 
21996 					if(acceptedAction != DragAndDropAction.none) {
21997 						auto display = XDisplayConnection.get;
21998 
21999 						XClientMessageEvent xclient;
22000 
22001 						xclient.type = EventType.ClientMessage;
22002 						xclient.window = sourceWindow;
22003 						xclient.message_type = GetAtom!"XdndFinished"(display);
22004 						xclient.format = 32;
22005 						xclient.data.l[0] = win.impl.window;
22006 						xclient.data.l[1] = 1; // drop successful
22007 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
22008 
22009 						XSendEvent(
22010 							display,
22011 							sourceWindow,
22012 							false,
22013 							EventMask.NoEventMask,
22014 							cast(XEvent*) &xclient
22015 						);
22016 
22017 						XFlush(display);
22018 					}
22019 				}
22020 
22021 				Atom findBestFormat(Atom[] answer) {
22022 					Atom best = None;
22023 					foreach(option; answer) {
22024 						if(option == format) {
22025 							best = option;
22026 							break;
22027 						}
22028 						/*
22029 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
22030 							best = option;
22031 							break;
22032 						} else if(option == XA_STRING) {
22033 							best = option;
22034 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
22035 							best = option;
22036 						}
22037 						*/
22038 					}
22039 					return best;
22040 				}
22041 			}
22042 
22043 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
22044 
22045 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
22046 
22047 		} else version(Windows) {
22048 
22049 			// clean up like DragLeave
22050 			// pass effect back up
22051 
22052 			FORMATETC t;
22053 			assert(format >= 0 && format <= ushort.max);
22054 			t.cfFormat = cast(ushort) format;
22055 			t.lindex = -1;
22056 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22057 			t.tymed = TYMED.TYMED_HGLOBAL;
22058 
22059 			STGMEDIUM m;
22060 
22061 			if(pDataObj.GetData(&t, &m) != S_OK) {
22062 				// fail
22063 			} else {
22064 				// succeed, take the data and clean up
22065 
22066 				// FIXME: ensure it is legit HGLOBAL
22067 				auto handle = m.hGlobal;
22068 
22069 				if(handle) {
22070 					auto sz = GlobalSize(handle);
22071 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
22072 						scope(exit) GlobalUnlock(handle);
22073 						scope(exit) GlobalFree(handle);
22074 
22075 						auto data = ptr[0 .. sz];
22076 
22077 						dg(data);
22078 					}
22079 				}
22080 			}
22081 		}
22082 	}
22083 
22084 	private:
22085 
22086 	version(X11) {
22087 		SimpleWindow win;
22088 		Window sourceWindow;
22089 		Time dataTimestamp;
22090 
22091 		Atom[] xFormats;
22092 	}
22093 	version(Windows) {
22094 		IDataObject pDataObj;
22095 	}
22096 }
22097 
22098 /++
22099 	A generic helper base class for making a drop handler with a preference list of custom types.
22100 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
22101 	droppers too.
22102 
22103 	It assumes the whole window it used, but you can subclass to change that.
22104 
22105 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22106 
22107 	History:
22108 		Added February 19, 2021
22109 +/
22110 /// Group: drag_and_drop
22111 class GenericDropHandlerBase : DropHandler {
22112 	// no fancy state here so no need to do anything here
22113 	void finish() { }
22114 	void dragLeave() { }
22115 
22116 	private DragAndDropAction acceptedAction;
22117 	private DraggableData.FormatId acceptedFormat;
22118 	private void delegate(scope ubyte[]) acceptedHandler;
22119 
22120 	struct FormatHandler {
22121 		DraggableData.FormatId format;
22122 		void delegate(scope ubyte[]) handler;
22123 	}
22124 
22125 	protected abstract FormatHandler[] formatHandlers();
22126 
22127 	DragAndDropAction dragEnter(DropPackage* pkg) {
22128 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
22129 		foreach(fmt; formatHandlers())
22130 		foreach(f; pkg.availableFormats())
22131 			if(f == fmt.format) {
22132 				acceptedFormat = f;
22133 				acceptedHandler = fmt.handler;
22134 				return acceptedAction = DragAndDropAction.copy;
22135 			}
22136 		return acceptedAction = DragAndDropAction.none;
22137 	}
22138 	DropParameters dragOver(Point pt) {
22139 		return DropParameters(acceptedAction);
22140 	}
22141 
22142 	void drop(scope DropPackage* dropPackage) {
22143 		if(!acceptedFormat || acceptedHandler is null) {
22144 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
22145 			return; // prolly shouldn't happen anyway...
22146 		}
22147 
22148 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
22149 	}
22150 }
22151 
22152 /++
22153 	A simple handler for making your window accept drops of plain text.
22154 
22155 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22156 
22157 	History:
22158 		Added February 22, 2021
22159 +/
22160 /// Group: drag_and_drop
22161 class TextDropHandler : GenericDropHandlerBase {
22162 	private void delegate(in char[] text) dg;
22163 
22164 	/++
22165 
22166 	+/
22167 	this(void delegate(in char[] text) dg) {
22168 		this.dg = dg;
22169 	}
22170 
22171 	protected override FormatHandler[] formatHandlers() {
22172 		version(X11)
22173 			return [
22174 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
22175 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
22176 			];
22177 		else version(Windows)
22178 			return [
22179 				FormatHandler(CF_UNICODETEXT, &translator),
22180 			];
22181 		else throw new NotYetImplementedException();
22182 	}
22183 
22184 	private void translator(scope ubyte[] data) {
22185 		version(X11)
22186 			dg(cast(char[]) data);
22187 		else version(Windows)
22188 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
22189 	}
22190 }
22191 
22192 /++
22193 	A simple handler for making your window accept drops of files, issued to you as file names.
22194 
22195 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22196 
22197 	History:
22198 		Added February 22, 2021
22199 +/
22200 /// Group: drag_and_drop
22201 
22202 class FilesDropHandler : GenericDropHandlerBase {
22203 	private void delegate(in char[][]) dg;
22204 
22205 	/++
22206 
22207 	+/
22208 	this(void delegate(in char[][] fileNames) dg) {
22209 		this.dg = dg;
22210 	}
22211 
22212 	protected override FormatHandler[] formatHandlers() {
22213 		version(X11)
22214 			return [
22215 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
22216 			];
22217 		else version(Windows)
22218 			return [
22219 				FormatHandler(CF_HDROP, &translator),
22220 			];
22221 		else throw new NotYetImplementedException();
22222 	}
22223 
22224 	private void translator(scope ubyte[] data) @system {
22225 		version(X11) {
22226 			char[] listString = cast(char[]) data;
22227 			char[][16] buffer;
22228 			int count;
22229 			char[][] result = buffer[];
22230 
22231 			void commit(char[] s) {
22232 				if(count == result.length)
22233 					result.length += 16;
22234 				if(s.length > 7 && s[0 ..7] == "file://")
22235 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
22236 				result[count++] = s;
22237 			}
22238 
22239 			size_t last;
22240 			foreach(idx, char c; listString) {
22241 				if(c == '\n') {
22242 					commit(listString[last .. idx - 1]); // a \r
22243 					last = idx + 1; // a \n
22244 				}
22245 			}
22246 
22247 			if(last < listString.length) {
22248 				commit(listString[last .. $]);
22249 			}
22250 
22251 			// FIXME: they are uris now, should I translate it to local file names?
22252 			// of course the host name is supposed to be there cuz of X rokking...
22253 
22254 			dg(result[0 .. count]);
22255 		} else version(Windows) {
22256 
22257 			static struct DROPFILES {
22258 				DWORD pFiles;
22259 				POINT pt;
22260 				BOOL  fNC;
22261 				BOOL  fWide;
22262 			}
22263 
22264 
22265 			const(char)[][16] buffer;
22266 			int count;
22267 			const(char)[][] result = buffer[];
22268 			size_t last;
22269 
22270 			void commitA(in char[] stuff) {
22271 				if(count == result.length)
22272 					result.length += 16;
22273 				result[count++] = stuff;
22274 			}
22275 
22276 			void commitW(in wchar[] stuff) {
22277 				commitA(makeUtf8StringFromWindowsString(stuff));
22278 			}
22279 
22280 			void magic(T)(T chars) {
22281 				size_t idx;
22282 				while(chars[idx]) {
22283 					last = idx;
22284 					while(chars[idx]) {
22285 						idx++;
22286 					}
22287 					static if(is(T == char*))
22288 						commitA(chars[last .. idx]);
22289 					else
22290 						commitW(chars[last .. idx]);
22291 					idx++;
22292 				}
22293 			}
22294 
22295 			auto df = cast(DROPFILES*) data.ptr;
22296 			if(df.fWide) {
22297 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
22298 				magic(chars);
22299 			} else {
22300 				char* chars = cast(char*) (data.ptr + df.pFiles);
22301 				magic(chars);
22302 			}
22303 			dg(result[0 .. count]);
22304 		}
22305 		else throw new NotYetImplementedException();
22306 	}
22307 }
22308 
22309 /++
22310 	Interface to describe data being dragged. See also [draggable] helper function.
22311 
22312 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22313 
22314 	History:
22315 		Added February 19, 2021
22316 +/
22317 interface DraggableData {
22318 	version(X11)
22319 		alias FormatId = Atom;
22320 	else
22321 		alias FormatId = uint;
22322 	/++
22323 		Gets the platform-specific FormatId associated with the given named format.
22324 
22325 		This may be a MIME type, but may also be other various strings defined by the
22326 		programs you want to interoperate with.
22327 
22328 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
22329 		and convert it to some particular type for you.
22330 	+/
22331 	static FormatId getFormatId(string name)() {
22332 		version(X11)
22333 			return GetAtom!name(XDisplayConnection.get);
22334 		else version(Windows) {
22335 			static UINT cache;
22336 			if(!cache)
22337 				cache = RegisterClipboardFormatA(name);
22338 			return cache;
22339 		} else
22340 			throw new NotYetImplementedException();
22341 	}
22342 
22343 	/++
22344 		Looks up a string to represent the name for the given format, if there is one.
22345 
22346 		You should avoid using this function because it is slow. It is provided more for
22347 		debugging than for primary use.
22348 	+/
22349 	static string getFormatName(FormatId format) {
22350 		version(X11) {
22351 			if(format == 0)
22352 				return "None";
22353 			else
22354 				return getAtomName(format, XDisplayConnection.get);
22355 		} else version(Windows) {
22356 			switch(format) {
22357 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
22358 				case CF_DIBV5: return "CF_DIBV5";
22359 				case CF_RIFF: return "CF_RIFF";
22360 				case CF_WAVE: return "CF_WAVE";
22361 				case CF_HDROP: return "CF_HDROP";
22362 				default:
22363 					char[1024] name;
22364 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
22365 					return name[0 .. count].idup;
22366 			}
22367 		} else throw new NotYetImplementedException();
22368 	}
22369 
22370 	FormatId[] availableFormats();
22371 	// Return the slice of data you filled, empty slice if done.
22372 	// this is to support the incremental thing
22373 	ubyte[] getData(FormatId format, return scope ubyte[] data);
22374 
22375 	size_t dataLength(FormatId format);
22376 }
22377 
22378 /++
22379 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22380 
22381 	History:
22382 		Added February 19, 2021
22383 +/
22384 DraggableData draggable(string s) {
22385 	version(X11)
22386 	return new class X11SetSelectionHandler_Text, DraggableData {
22387 		this() {
22388 			super(s);
22389 		}
22390 
22391 		override FormatId[] availableFormats() {
22392 			return X11SetSelectionHandler_Text.availableFormats();
22393 		}
22394 
22395 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
22396 			return X11SetSelectionHandler_Text.getData(format, data);
22397 		}
22398 
22399 		size_t dataLength(FormatId format) {
22400 			return s.length;
22401 		}
22402 	};
22403 	else version(Windows)
22404 	return new class DraggableData {
22405 		FormatId[] availableFormats() {
22406 			return [CF_UNICODETEXT];
22407 		}
22408 
22409 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
22410 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
22411 		}
22412 
22413 		size_t dataLength(FormatId format) {
22414 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
22415 		}
22416 	};
22417 	else
22418 	throw new NotYetImplementedException();
22419 }
22420 
22421 /++
22422 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22423 
22424 	History:
22425 		Added February 19, 2021
22426 +/
22427 /// Group: drag_and_drop
22428 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
22429 in {
22430 	assert(window !is null);
22431 	assert(handler !is null);
22432 }
22433 do
22434 {
22435 	version(X11) {
22436 		auto sh = cast(X11SetSelectionHandler) handler;
22437 		if(sh is null) {
22438 			// gotta make my own adapter.
22439 			sh = new class X11SetSelectionHandler {
22440 				mixin X11SetSelectionHandler_Basics;
22441 
22442 				Atom[] availableFormats() { return handler.availableFormats(); }
22443 				ubyte[] getData(Atom format, return scope ubyte[] data) {
22444 					return handler.getData(format, data);
22445 				}
22446 
22447 				// since the drop selection is only ever used once it isn't important
22448 				// to reset it.
22449 				void done() {}
22450 			};
22451 		}
22452 		return doDragDropX11(window, sh, action);
22453 	} else version(Windows) {
22454 		return doDragDropWindows(window, handler, action);
22455 	} else throw new NotYetImplementedException();
22456 }
22457 
22458 version(Windows)
22459 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
22460 	IDataObject obj = new class IDataObject {
22461 		ULONG refCount;
22462 		ULONG AddRef() {
22463 			return ++refCount;
22464 		}
22465 		ULONG Release() {
22466 			return --refCount;
22467 		}
22468 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22469 			if (IID_IUnknown == *riid) {
22470 				*ppv = cast(void*) cast(IUnknown) this;
22471 			}
22472 			else if (IID_IDataObject == *riid) {
22473 				*ppv = cast(void*) cast(IDataObject) this;
22474 			}
22475 			else {
22476 				*ppv = null;
22477 				return E_NOINTERFACE;
22478 			}
22479 
22480 			AddRef();
22481 			return NOERROR;
22482 		}
22483 
22484 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
22485 			//  writeln("Advise");
22486 			return E_NOTIMPL;
22487 		}
22488 		HRESULT DUnadvise(DWORD dwConnection) {
22489 			return E_NOTIMPL;
22490 		}
22491 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
22492 			//  writeln("EnumDAdvise");
22493 			return OLE_E_ADVISENOTSUPPORTED;
22494 		}
22495 		// tell what formats it supports
22496 
22497 		FORMATETC[] types;
22498 		this() {
22499 			FORMATETC t;
22500 			foreach(ty; handler.availableFormats()) {
22501 				assert(ty <= ushort.max && ty >= 0);
22502 				t.cfFormat = cast(ushort) ty;
22503 				t.lindex = -1;
22504 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22505 				t.tymed = TYMED.TYMED_HGLOBAL;
22506 			}
22507 			types ~= t;
22508 		}
22509 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
22510 			if(dwDirection == DATADIR.DATADIR_GET) {
22511 				*ppenumFormatEtc = new class IEnumFORMATETC {
22512 					ULONG refCount;
22513 					ULONG AddRef() {
22514 						return ++refCount;
22515 					}
22516 					ULONG Release() {
22517 						return --refCount;
22518 					}
22519 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22520 						if (IID_IUnknown == *riid) {
22521 							*ppv = cast(void*) cast(IUnknown) this;
22522 						}
22523 						else if (IID_IEnumFORMATETC == *riid) {
22524 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
22525 						}
22526 						else {
22527 							*ppv = null;
22528 							return E_NOINTERFACE;
22529 						}
22530 
22531 						AddRef();
22532 						return NOERROR;
22533 					}
22534 
22535 
22536 					int pos;
22537 					this() {
22538 						pos = 0;
22539 					}
22540 
22541 					HRESULT Clone(IEnumFORMATETC* ppenum) {
22542 						// writeln("clone");
22543 						return E_NOTIMPL; // FIXME
22544 					}
22545 
22546 					// Caller is responsible for freeing memory
22547 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
22548 						// fetched may be null if celt is one
22549 						if(celt != 1)
22550 							return E_NOTIMPL; // FIXME
22551 
22552 						if(celt + pos > types.length)
22553 							return S_FALSE;
22554 
22555 						*rgelt = types[pos++];
22556 
22557 						if(pceltFetched !is null)
22558 							*pceltFetched = 1;
22559 
22560 						// writeln("ok celt ", celt);
22561 						return S_OK;
22562 					}
22563 
22564 					HRESULT Reset() {
22565 						pos = 0;
22566 						return S_OK;
22567 					}
22568 
22569 					HRESULT Skip(ULONG celt) {
22570 						if(celt + pos <= types.length) {
22571 							pos += celt;
22572 							return S_OK;
22573 						}
22574 						return S_FALSE;
22575 					}
22576 				};
22577 
22578 				return S_OK;
22579 			} else
22580 				return E_NOTIMPL;
22581 		}
22582 		// given a format, return the format you'd prefer to use cuz it is identical
22583 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
22584 			// FIXME: prolly could be better but meh
22585 			// writeln("gcf: ", *pformatectIn);
22586 			*pformatetcOut = *pformatectIn;
22587 			return S_OK;
22588 		}
22589 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22590 			foreach(ty; types) {
22591 				if(ty == *pformatetcIn) {
22592 					auto format = ty.cfFormat;
22593 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
22594 					STGMEDIUM medium;
22595 					medium.tymed = TYMED.TYMED_HGLOBAL;
22596 
22597 					auto sz = handler.dataLength(format);
22598 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
22599 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
22600 					if(auto data = cast(wchar*) GlobalLock(handle)) {
22601 						auto slice = data[0 .. sz];
22602 						scope(exit)
22603 							GlobalUnlock(handle);
22604 
22605 						handler.getData(format, cast(ubyte[]) slice[]);
22606 					}
22607 
22608 
22609 					medium.hGlobal = handle; // FIXME
22610 					*pmedium = medium;
22611 					return S_OK;
22612 				}
22613 			}
22614 			return DV_E_FORMATETC;
22615 		}
22616 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22617 			// writeln("GDH: ", *pformatetcIn);
22618 			return E_NOTIMPL; // FIXME
22619 		}
22620 		HRESULT QueryGetData(FORMATETC* pformatetc) {
22621 			auto search = *pformatetc;
22622 			search.tymed &= TYMED.TYMED_HGLOBAL;
22623 			foreach(ty; types)
22624 				if(ty == search) {
22625 					// writeln("QueryGetData ", search, " ", types[0]);
22626 					return S_OK;
22627 				}
22628 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
22629 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
22630 			}
22631 			return S_FALSE;
22632 		}
22633 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
22634 			//  writeln("SetData: ");
22635 			return E_NOTIMPL;
22636 		}
22637 	};
22638 
22639 
22640 	IDropSource src = new class IDropSource {
22641 		ULONG refCount;
22642 		ULONG AddRef() {
22643 			return ++refCount;
22644 		}
22645 		ULONG Release() {
22646 			return --refCount;
22647 		}
22648 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22649 			if (IID_IUnknown == *riid) {
22650 				*ppv = cast(void*) cast(IUnknown) this;
22651 			}
22652 			else if (IID_IDropSource == *riid) {
22653 				*ppv = cast(void*) cast(IDropSource) this;
22654 			}
22655 			else {
22656 				*ppv = null;
22657 				return E_NOINTERFACE;
22658 			}
22659 
22660 			AddRef();
22661 			return NOERROR;
22662 		}
22663 
22664 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
22665 			if(fEscapePressed)
22666 				return DRAGDROP_S_CANCEL;
22667 			if(!(grfKeyState & MK_LBUTTON))
22668 				return DRAGDROP_S_DROP;
22669 			return S_OK;
22670 		}
22671 
22672 		int GiveFeedback(uint dwEffect) {
22673 			return DRAGDROP_S_USEDEFAULTCURSORS;
22674 		}
22675 	};
22676 
22677 	DWORD effect;
22678 
22679 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
22680 
22681 	DROPEFFECT de = win32DragAndDropAction(action);
22682 
22683 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
22684 	// but still prolly a FIXME
22685 
22686 	auto ret = DoDragDrop(obj, src, de, &effect);
22687 	/+
22688 	if(ret == DRAGDROP_S_DROP)
22689 		writeln("drop ", effect);
22690 	else if(ret == DRAGDROP_S_CANCEL)
22691 		writeln("cancel");
22692 	else if(ret == S_OK)
22693 		writeln("ok");
22694 	else writeln(ret);
22695 	+/
22696 
22697 	return ret;
22698 }
22699 
22700 version(Windows)
22701 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
22702 	DROPEFFECT de;
22703 
22704 	with(DragAndDropAction)
22705 	with(DROPEFFECT)
22706 	final switch(action) {
22707 		case none: de = DROPEFFECT_NONE; break;
22708 		case copy: de = DROPEFFECT_COPY; break;
22709 		case move: de = DROPEFFECT_MOVE; break;
22710 		case link: de = DROPEFFECT_LINK; break;
22711 		case ask: throw new Exception("ask not implemented yet");
22712 		case custom: throw new Exception("custom not implemented yet");
22713 	}
22714 
22715 	return de;
22716 }
22717 
22718 
22719 /++
22720 	History:
22721 		Added February 19, 2021
22722 +/
22723 /// Group: drag_and_drop
22724 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
22725 	version(X11) {
22726 		auto display = XDisplayConnection.get;
22727 
22728 		Atom atom = 5; // right???
22729 
22730 		XChangeProperty(
22731 			display,
22732 			window.impl.window,
22733 			GetAtom!"XdndAware"(display),
22734 			XA_ATOM,
22735 			32 /* bits */,
22736 			PropModeReplace,
22737 			&atom,
22738 			1);
22739 
22740 		window.dropHandler = handler;
22741 	} else version(Windows) {
22742 
22743 		initDnd();
22744 
22745 		auto dropTarget = new class (handler) IDropTarget {
22746 			DropHandler handler;
22747 			this(DropHandler handler) {
22748 				this.handler = handler;
22749 			}
22750 			ULONG refCount;
22751 			ULONG AddRef() {
22752 				return ++refCount;
22753 			}
22754 			ULONG Release() {
22755 				return --refCount;
22756 			}
22757 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22758 				if (IID_IUnknown == *riid) {
22759 					*ppv = cast(void*) cast(IUnknown) this;
22760 				}
22761 				else if (IID_IDropTarget == *riid) {
22762 					*ppv = cast(void*) cast(IDropTarget) this;
22763 				}
22764 				else {
22765 					*ppv = null;
22766 					return E_NOINTERFACE;
22767 				}
22768 
22769 				AddRef();
22770 				return NOERROR;
22771 			}
22772 
22773 
22774 			// ///////////////////
22775 
22776 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22777 				DropPackage dropPackage = DropPackage(pDataObj);
22778 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
22779 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
22780 			}
22781 
22782 			HRESULT DragLeave() {
22783 				handler.dragLeave();
22784 				// release the IDataObject if needed
22785 				return S_OK;
22786 			}
22787 
22788 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22789 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
22790 
22791 				*pdwEffect = win32DragAndDropAction(res.action);
22792 				// same as DragEnter basically
22793 				return S_OK;
22794 			}
22795 
22796 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22797 				DropPackage pkg = DropPackage(pDataObj);
22798 				handler.drop(&pkg);
22799 
22800 				return S_OK;
22801 			}
22802 		};
22803 		// Windows can hold on to the handler and try to call it
22804 		// during which time the GC can't see it. so important to
22805 		// manually manage this. At some point i'll FIXME and make
22806 		// all my com instances manually managed since they supposed
22807 		// to respect the refcount.
22808 		import core.memory;
22809 		GC.addRoot(cast(void*) dropTarget);
22810 
22811 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
22812 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
22813 
22814 		window.dropHandler = handler;
22815 	} else throw new NotYetImplementedException();
22816 }
22817 
22818 
22819 
22820 static if(UsingSimpledisplayX11) {
22821 
22822 enum _NET_WM_STATE_ADD = 1;
22823 enum _NET_WM_STATE_REMOVE = 0;
22824 enum _NET_WM_STATE_TOGGLE = 2;
22825 
22826 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
22827 void demandAttention(SimpleWindow window, bool needs = true) {
22828 	demandAttention(window.impl.window, needs);
22829 }
22830 
22831 /// ditto
22832 void demandAttention(Window window, bool needs = true) {
22833 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
22834 }
22835 
22836 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
22837 	auto display = XDisplayConnection.get();
22838 	if(atom == None)
22839 		return; // non-failure error
22840 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
22841 
22842 	XClientMessageEvent xclient;
22843 
22844 	xclient.type = EventType.ClientMessage;
22845 	xclient.window = window;
22846 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
22847 	xclient.format = 32;
22848 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
22849 	xclient.data.l[1] = atom;
22850 	xclient.data.l[2] = atom2;
22851 	xclient.data.l[3] = 1;
22852 	// [3] == source. 0 == unknown, 1 == app, 2 == else
22853 
22854 	XSendEvent(
22855 		display,
22856 		RootWindow(display, DefaultScreen(display)),
22857 		false,
22858 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
22859 		cast(XEvent*) &xclient
22860 	);
22861 
22862 	/+
22863 	XChangeProperty(
22864 		display,
22865 		window.impl.window,
22866 		GetAtom!"_NET_WM_STATE"(display),
22867 		XA_ATOM,
22868 		32 /* bits */,
22869 		PropModeAppend,
22870 		&atom,
22871 		1);
22872 	+/
22873 }
22874 
22875 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
22876 	Atom actionAtom;
22877 	with(DragAndDropAction)
22878 	final switch(action) {
22879 		case none: actionAtom = None; break;
22880 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
22881 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
22882 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
22883 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
22884 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
22885 	}
22886 
22887 	return actionAtom;
22888 }
22889 
22890 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
22891 	// FIXME: I need to show user feedback somehow.
22892 	auto display = XDisplayConnection.get;
22893 
22894 	auto actionAtom = dndActionAtom(display, action);
22895 	assert(actionAtom, "Don't use action none to accept a drop");
22896 
22897 	setX11Selection!"XdndSelection"(window, handler, null);
22898 
22899 	auto oldKeyHandler = window.handleKeyEvent;
22900 	scope(exit) window.handleKeyEvent = oldKeyHandler;
22901 
22902 	auto oldCharHandler = window.handleCharEvent;
22903 	scope(exit) window.handleCharEvent = oldCharHandler;
22904 
22905 	auto oldMouseHandler = window.handleMouseEvent;
22906 	scope(exit) window.handleMouseEvent = oldMouseHandler;
22907 
22908 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
22909 
22910 	import core.sys.posix.sys.time;
22911 	timeval tv;
22912 	gettimeofday(&tv, null);
22913 
22914 	Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 );
22915 
22916 	Time lastMouseTimestamp;
22917 
22918 	bool dnding = true;
22919 	Window lastIn = None;
22920 
22921 	void leave() {
22922 		if(lastIn == None)
22923 			return;
22924 
22925 		XEvent ev;
22926 		ev.xclient.type = EventType.ClientMessage;
22927 		ev.xclient.window = lastIn;
22928 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
22929 		ev.xclient.format = 32;
22930 		ev.xclient.data.l[0] = window.impl.window;
22931 
22932 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22933 		XFlush(display);
22934 
22935 		lastIn = None;
22936 	}
22937 
22938 	void enter(Window w) {
22939 		assert(lastIn == None);
22940 
22941 		lastIn = w;
22942 
22943 		XEvent ev;
22944 		ev.xclient.type = EventType.ClientMessage;
22945 		ev.xclient.window = lastIn;
22946 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
22947 		ev.xclient.format = 32;
22948 		ev.xclient.data.l[0] = window.impl.window;
22949 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
22950 
22951 		auto types = handler.availableFormats();
22952 		assert(types.length > 0);
22953 
22954 		ev.xclient.data.l[2] = types[0];
22955 		if(types.length > 1)
22956 			ev.xclient.data.l[3] = types[1];
22957 		if(types.length > 2)
22958 			ev.xclient.data.l[4] = types[2];
22959 
22960 		// FIXME: other types?!?!? and make sure we skip TARGETS
22961 
22962 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22963 		XFlush(display);
22964 	}
22965 
22966 	void position(int rootX, int rootY) {
22967 		assert(lastIn != None);
22968 
22969 		XEvent ev;
22970 		ev.xclient.type = EventType.ClientMessage;
22971 		ev.xclient.window = lastIn;
22972 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
22973 		ev.xclient.format = 32;
22974 		ev.xclient.data.l[0] = window.impl.window;
22975 		ev.xclient.data.l[1] = 0; // reserved
22976 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
22977 		ev.xclient.data.l[3] = dataTimestamp;
22978 		ev.xclient.data.l[4] = actionAtom;
22979 
22980 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22981 		XFlush(display);
22982 
22983 	}
22984 
22985 	void drop() {
22986 		XEvent ev;
22987 		ev.xclient.type = EventType.ClientMessage;
22988 		ev.xclient.window = lastIn;
22989 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
22990 		ev.xclient.format = 32;
22991 		ev.xclient.data.l[0] = window.impl.window;
22992 		ev.xclient.data.l[1] = 0; // reserved
22993 		ev.xclient.data.l[2] = dataTimestamp;
22994 
22995 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22996 		XFlush(display);
22997 
22998 		lastIn = None;
22999 		dnding = false;
23000 	}
23001 
23002 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
23003 	// but idk if i should...
23004 
23005 	window.setEventHandlers(
23006 		delegate(KeyEvent ev) {
23007 			if(ev.pressed == true && ev.key == Key.Escape) {
23008 				// cancel
23009 				dnding = false;
23010 			}
23011 		},
23012 		delegate(MouseEvent ev) {
23013 			if(ev.timestamp < lastMouseTimestamp)
23014 				return;
23015 
23016 			lastMouseTimestamp = ev.timestamp;
23017 
23018 			if(ev.type == MouseEventType.motion) {
23019 				auto display = XDisplayConnection.get;
23020 				auto root = RootWindow(display, DefaultScreen(display));
23021 
23022 				Window topWindow;
23023 				int rootX, rootY;
23024 
23025 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
23026 
23027 				if(topWindow == None)
23028 					return;
23029 
23030 				top:
23031 				if(auto result = topWindow in eligibility) {
23032 					auto dropWindow = *result;
23033 					if(dropWindow == None) {
23034 						leave();
23035 						return;
23036 					}
23037 
23038 					if(dropWindow != lastIn) {
23039 						leave();
23040 						enter(dropWindow);
23041 						position(rootX, rootY);
23042 					} else {
23043 						position(rootX, rootY);
23044 					}
23045 				} else {
23046 					// determine eligibility
23047 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
23048 					if(data.length == 1) {
23049 						// in case there is no WM or it isn't reparenting
23050 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
23051 					} else {
23052 
23053 						Window tryScanChildren(Window search, int maxRecurse) {
23054 							// could be reparenting window manager, so gotta check the next few children too
23055 							Window child;
23056 							int x;
23057 							int y;
23058 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
23059 
23060 							if(child == None)
23061 								return None;
23062 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
23063 							if(data.length == 1) {
23064 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
23065 							} else {
23066 								if(maxRecurse)
23067 									return tryScanChildren(child, maxRecurse - 1);
23068 								else
23069 									return None;
23070 							}
23071 
23072 						}
23073 
23074 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
23075 						auto topResult = tryScanChildren(topWindow, 3);
23076 						// it is easy to have a false negative due to the mouse going over a WM
23077 						// child window like the close button if separate from the frame... so I
23078 						// can't really cache negatives, :(
23079 						if(topResult != None) {
23080 							eligibility[topWindow] = topResult;
23081 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
23082 						}
23083 					}
23084 
23085 				}
23086 
23087 			} else if(ev.type == MouseEventType.buttonReleased) {
23088 				drop();
23089 				dnding = false;
23090 			}
23091 		}
23092 	);
23093 
23094 	window.grabInput();
23095 	scope(exit)
23096 		window.releaseInputGrab();
23097 
23098 
23099 	EventLoop.get.run(() => dnding);
23100 
23101 	return 0;
23102 }
23103 
23104 /// X-specific
23105 TrueColorImage getWindowNetWmIcon(Window window) {
23106 	try {
23107 		auto display = XDisplayConnection.get;
23108 
23109 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
23110 
23111 		if (data.length > arch_ulong.sizeof * 2) {
23112 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
23113 			// these are an array of rgba images that we have to convert into pixmaps ourself
23114 
23115 			int width = cast(int) meta[0];
23116 			int height = cast(int) meta[1];
23117 
23118 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
23119 
23120 			static if(arch_ulong.sizeof == 4) {
23121 				bytes = bytes[0 .. width * height * 4];
23122 				alias imageData = bytes;
23123 			} else static if(arch_ulong.sizeof == 8) {
23124 				bytes = bytes[0 .. width * height * 8];
23125 				auto imageData = new ubyte[](4 * width * height);
23126 			} else static assert(0);
23127 
23128 
23129 
23130 			// this returns ARGB. Remember it is little-endian so
23131 			//                                         we have BGRA
23132 			// our thing uses RGBA, which in little endian, is ABGR
23133 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
23134 				auto r = bytes[idx + 2];
23135 				auto g = bytes[idx + 1];
23136 				auto b = bytes[idx + 0];
23137 				auto a = bytes[idx + 3];
23138 
23139 				imageData[idx2 + 0] = r;
23140 				imageData[idx2 + 1] = g;
23141 				imageData[idx2 + 2] = b;
23142 				imageData[idx2 + 3] = a;
23143 			}
23144 
23145 			return new TrueColorImage(width, height, imageData);
23146 		}
23147 
23148 		return null;
23149 	} catch(Exception e) {
23150 		return null;
23151 	}
23152 }
23153 
23154 } /* UsingSimpledisplayX11 */
23155 
23156 
23157 void loadBinNameToWindowClassName () {
23158 	import core.stdc.stdlib : realloc;
23159 	version(linux) {
23160 		// args[0] MAY be empty, so we'll just use this
23161 		import core.sys.posix.unistd : readlink;
23162 		char[1024] ebuf = void; // 1KB should be enough for everyone!
23163 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
23164 		if (len < 1) return;
23165 	} else /*version(Windows)*/ {
23166 		import core.runtime : Runtime;
23167 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
23168 		auto ebuf = Runtime.args[0];
23169 		auto len = ebuf.length;
23170 	}
23171 	auto pos = len;
23172 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
23173 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
23174 	if (sdpyWindowClassStr is null) return; // oops
23175 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
23176 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
23177 }
23178 
23179 /++
23180 	An interface representing a font that is drawn with custom facilities.
23181 
23182 	You might want [OperatingSystemFont] instead, which represents
23183 	a font loaded and drawn by functions native to the operating system.
23184 
23185 	WARNING: I might still change this.
23186 +/
23187 interface DrawableFont : MeasurableFont {
23188 	/++
23189 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
23190 
23191 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
23192 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
23193 		fill color, but that's up to the implementation.
23194 	+/
23195 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
23196 
23197 	/++
23198 		Requests that the given string is added to the image cache. You should only do this rarely, but
23199 		if you have a string that you know will be used over and over again, adding it to a cache can
23200 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
23201 		to implement this as a do-nothing method).
23202 	+/
23203 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
23204 }
23205 
23206 /++
23207 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
23208 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
23209 
23210 	You should also consider [OperatingSystemFont], which loads and draws a font with
23211 	facilities native to the user's operating system. You might also consider
23212 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
23213 	of game, as they have their own ways to draw text too.
23214 
23215 	Be warned: this can be slow, especially on remote connections to the X server, since
23216 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
23217 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
23218 	experiment in your specific case.
23219 
23220 	Please note that the return type of [DrawableFont] also includes an implementation of
23221 	[MeasurableFont].
23222 +/
23223 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
23224 	import arsd.ttf;
23225 	static class ArsdTtfFont : DrawableFont {
23226 		TtfFont font;
23227 		int size;
23228 		this(in ubyte[] data, int size) {
23229 			font = TtfFont(data);
23230 			this.size = size;
23231 
23232 
23233 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
23234 			int ascent_, descent_, line_gap;
23235 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
23236 
23237 			int advance, lsb;
23238 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
23239 			xWidth = cast(int) (advance * scale);
23240 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
23241 			MWidth = cast(int) (advance * scale);
23242 		}
23243 
23244 		private int ascent_;
23245 		private int descent_;
23246 		private int xWidth;
23247 		private int MWidth;
23248 
23249 		bool isMonospace() {
23250 			return xWidth == MWidth;
23251 		}
23252 		int averageWidth() {
23253 			return xWidth;
23254 		}
23255 		int height() {
23256 			return size;
23257 		}
23258 		int ascent() {
23259 			return ascent_;
23260 		}
23261 		int descent() {
23262 			return descent_;
23263 		}
23264 
23265 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
23266 			int width, height;
23267 			font.getStringSize(s, size, width, height);
23268 			return width;
23269 		}
23270 
23271 
23272 
23273 		Sprite[string] cache;
23274 
23275 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
23276 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
23277 			cache[text] = sprite;
23278 		}
23279 
23280 		Image stringToImage(Color fg, Color bg, in char[] text) {
23281 			int width, height;
23282 			auto data = font.renderString(text, size, width, height);
23283 			auto image = new TrueColorImage(width, height);
23284 			int pos = 0;
23285 			foreach(y; 0 .. height)
23286 			foreach(x; 0 .. width) {
23287 				fg.a = data[0];
23288 				bg.a = 255;
23289 				auto color = alphaBlend(fg, bg);
23290 				image.imageData.bytes[pos++] = color.r;
23291 				image.imageData.bytes[pos++] = color.g;
23292 				image.imageData.bytes[pos++] = color.b;
23293 				image.imageData.bytes[pos++] = data[0];
23294 				data = data[1 .. $];
23295 			}
23296 			assert(data.length == 0);
23297 
23298 			return Image.fromMemoryImage(image);
23299 		}
23300 
23301 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
23302 			Sprite sprite = (text in cache) ? *(text in cache) : null;
23303 
23304 			auto fg = painter.impl._outlineColor;
23305 			auto bg = painter.impl._fillColor;
23306 
23307 			if(sprite !is null) {
23308 				auto w = cast(SimpleWindow) painter.window;
23309 				assert(w !is null);
23310 
23311 				sprite.drawAt(painter, upperLeft);
23312 			} else {
23313 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
23314 			}
23315 		}
23316 	}
23317 
23318 	return new ArsdTtfFont(data, size);
23319 }
23320 
23321 class NotYetImplementedException : Exception {
23322 	this(string file = __FILE__, size_t line = __LINE__) {
23323 		super("Not yet implemented", file, line);
23324 	}
23325 }
23326 
23327 ///
23328 __gshared bool librariesSuccessfullyLoaded = true;
23329 ///
23330 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
23331 
23332 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
23333 	// mixin(staticForeachReplacement!Iface);
23334 	static foreach(name; __traits(derivedMembers, Iface))
23335 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23336 
23337 	void loadDynamicLibrary() @nogc {
23338 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23339 	}
23340 
23341 	void loadDynamicLibraryForReal() {
23342 		foreach(name; __traits(derivedMembers, Iface)) {
23343 			mixin("alias tmp = " ~ name ~ ";");
23344 			tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
23345 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
23346 		}
23347 	}
23348 }
23349 
23350 /+
23351 private const(char)[] staticForeachReplacement(Iface)() pure {
23352 /*
23353 	// just this for gdc 9....
23354 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
23355 
23356 	static foreach(name; __traits(derivedMembers, Iface))
23357 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23358 */
23359 
23360 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
23361 	size_t pos;
23362 
23363 	void append(in char[] what) {
23364 		if(pos + what.length > code.length)
23365 			code.length = (code.length * 3) / 2;
23366 		code[pos .. pos + what.length] = what[];
23367 		pos += what.length;
23368 	}
23369 
23370 	foreach(name; __traits(derivedMembers, Iface)) {
23371 		append(`__gshared typeof(&__traits(getMember, Iface, "`);
23372 		append(name);
23373 		append(`")) `);
23374 		append(name);
23375 		append(";");
23376 	}
23377 
23378 	return code[0 .. pos];
23379 }
23380 +/
23381 
23382 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
23383 	//mixin(staticForeachReplacement!Iface);
23384 	static foreach(name; __traits(derivedMembers, Iface))
23385 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23386 
23387 	private __gshared void* libHandle;
23388 	private __gshared bool attempted;
23389 
23390 	void loadDynamicLibrary() @nogc {
23391 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23392 	}
23393 
23394 	bool loadAttempted() {
23395 		return attempted;
23396 	}
23397 	bool loadSuccessful() {
23398 		return libHandle !is null;
23399 	}
23400 
23401 	void loadDynamicLibraryForReal() {
23402 		attempted = true;
23403 		version(Posix) {
23404 			import core.sys.posix.dlfcn;
23405 			version(OSX) {
23406 				version(X11)
23407 					libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
23408 				else
23409 					libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
23410 			} else {
23411 				version(apitrace) {
23412 					if(library == "GL" || library == "GLX") {
23413 						libHandle = dlopen("glxtrace.so", RTLD_NOW);
23414 						if(libHandle is null) {
23415 							assert(false, "Failed to load `glxtrace.so`.");
23416 						}
23417 					}
23418 					else {
23419 						libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23420 					}
23421 				}
23422 				else {
23423 					libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23424 				}
23425 				if(libHandle is null) {
23426 					libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
23427 				}
23428 			}
23429 
23430 			static void* loadsym(void* l, const char* name) {
23431 				import core.stdc.stdlib;
23432 				if(l is null)
23433 					return &abort;
23434 				return dlsym(l, name);
23435 			}
23436 		} else version(Windows) {
23437 			import core.sys.windows.winbase;
23438 			libHandle = LoadLibrary(library ~ ".dll");
23439 			static void* loadsym(void* l, const char* name) {
23440 				import core.stdc.stdlib;
23441 				if(l is null)
23442 					return &abort;
23443 				return GetProcAddress(l, name);
23444 			}
23445 		}
23446 		if(libHandle is null) {
23447 			success = false;
23448 			//throw new Exception("load failure of library " ~ library);
23449 		}
23450 		foreach(name; __traits(derivedMembers, Iface)) {
23451 			mixin("alias tmp = " ~ name ~ ";");
23452 			tmp = cast(typeof(tmp)) loadsym(libHandle, name);
23453 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
23454 		}
23455 	}
23456 
23457 	void unloadDynamicLibrary() {
23458 		version(Posix) {
23459 			import core.sys.posix.dlfcn;
23460 			dlclose(libHandle);
23461 		} else version(Windows) {
23462 			import core.sys.windows.winbase;
23463 			FreeLibrary(libHandle);
23464 		}
23465 		foreach(name; __traits(derivedMembers, Iface))
23466 			mixin(name ~ " = null;");
23467 	}
23468 }
23469 
23470 // version(X11)
23471 /++
23472 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
23473 
23474 	$(WARNING
23475 		This function is exempted from stability guarantees.
23476 	)
23477 +/
23478 float customScalingFactorForMonitor(int monitorNumber) @system {
23479 	import core.stdc.stdlib;
23480 	auto val = getenv("ARSD_SCALING_FACTOR");
23481 
23482 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
23483 	if(val is null)
23484 		return 1.0;
23485 
23486 	char[16] buffer = 0;
23487 	int pos;
23488 
23489 	const(char)* at = val;
23490 
23491 	foreach(item; 0 .. monitorNumber + 1) {
23492 		if(*at == 0)
23493 			break; // reuse the last number when we at the end of the string
23494 		pos = 0;
23495 		while(pos + 1 < buffer.length && *at && *at != ';') {
23496 			buffer[pos++] = *at;
23497 			at++;
23498 		}
23499 		if(*at)
23500 			at++; // skip the semicolon
23501 		buffer[pos] = 0;
23502 	}
23503 
23504 	//sdpyPrintDebugString(buffer[0 .. pos]);
23505 
23506 	import core.stdc.math;
23507 	auto f = atof(buffer.ptr);
23508 
23509 	if(f <= 0.0 || isnan(f) || isinf(f))
23510 		return 1.0;
23511 
23512 	return f;
23513 }
23514 
23515 void guiAbortProcess(string msg) {
23516 	import core.stdc.stdlib;
23517 	version(Windows) {
23518 		WCharzBuffer t = WCharzBuffer(msg);
23519 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
23520 	} else {
23521 		import core.stdc.stdio;
23522 		fwrite(msg.ptr, 1, msg.length, stderr);
23523 		msg = "\n";
23524 		fwrite(msg.ptr, 1, msg.length, stderr);
23525 		fflush(stderr);
23526 	}
23527 
23528 	abort();
23529 }
23530 
23531 private int minInternal(int a, int b) {
23532 	return (a < b) ? a : b;
23533 }
23534 
23535 private alias scriptable = arsd_jsvar_compatible;