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 // FIXME: tetris demo
817 // FIXME: space invaders demo
818 // FIXME: asteroids demo
819 
820 /++ $(ID Pong-example)
821 	$(H3 Pong)
822 
823 	This program creates a little Pong-like game. Player one is controlled
824 	with the keyboard.  Player two is controlled with the mouse. It demos
825 	the pulse timer, event handling, and some basic drawing.
826 +/
827 version(demos)
828 unittest {
829 	// dmd example.d simpledisplay.d color.d
830 	import arsd.simpledisplay;
831 
832 	enum paddleMovementSpeed = 8;
833 	enum paddleHeight = 48;
834 
835 	void main() {
836 		auto window = new SimpleWindow(600, 400, "Pong game!");
837 
838 		int playerOnePosition, playerTwoPosition;
839 		int playerOneMovement, playerTwoMovement;
840 		int playerOneScore, playerTwoScore;
841 
842 		int ballX, ballY;
843 		int ballDx, ballDy;
844 
845 		void serve() {
846 			import std.random;
847 
848 			ballX = window.width / 2;
849 			ballY = window.height / 2;
850 			ballDx = uniform(-4, 4) * 3;
851 			ballDy = uniform(-4, 4) * 3;
852 			if(ballDx == 0)
853 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
854 		}
855 
856 		serve();
857 
858 		window.eventLoop(50, // set a 50 ms timer pulls
859 			// This runs once per timer pulse
860 			delegate () {
861 				auto painter = window.draw();
862 
863 				painter.clear();
864 
865 				// Update everyone's motion
866 				playerOnePosition += playerOneMovement;
867 				playerTwoPosition += playerTwoMovement;
868 
869 				ballX += ballDx;
870 				ballY += ballDy;
871 
872 				// Bounce off the top and bottom edges of the window
873 				if(ballY + 7 >= window.height)
874 					ballDy = -ballDy;
875 				if(ballY - 8 <= 0)
876 					ballDy = -ballDy;
877 
878 				// Bounce off the paddle, if it is in position
879 				if(ballX - 8 <= 16) {
880 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
881 						ballDx = -ballDx + 1; // add some speed to keep it interesting
882 						ballDy += playerOneMovement; // and y movement based on your controls too
883 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
884 					} else {
885 						// Missed it
886 						playerTwoScore ++;
887 						serve();
888 					}
889 				}
890 
891 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
892 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
893 						ballDx = -ballDx - 1;
894 						ballDy += playerTwoMovement;
895 						ballX = window.width - 24;
896 					} else {
897 						// Missed it
898 						playerOneScore ++;
899 						serve();
900 					}
901 				}
902 
903 				// Draw the paddles
904 				painter.outlineColor = Color.black;
905 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
906 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
907 
908 				// Draw the ball
909 				painter.fillColor = Color.red;
910 				painter.outlineColor = Color.yellow;
911 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
912 
913 				// Draw the score
914 				painter.outlineColor = Color.blue;
915 				import std.conv;
916 				painter.drawText(Point(64, 4), to!string(playerOneScore));
917 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
918 
919 			},
920 			delegate (KeyEvent event) {
921 				// Player 1's controls are the arrow keys on the keyboard
922 				if(event.key == Key.Down)
923 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
924 				if(event.key == Key.Up)
925 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
926 
927 			},
928 			delegate (MouseEvent event) {
929 				// Player 2's controls are mouse movement while the left button is held down
930 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
931 					if(event.dy > 0)
932 						playerTwoMovement = paddleMovementSpeed;
933 					else if(event.dy < 0)
934 						playerTwoMovement = -paddleMovementSpeed;
935 				} else {
936 					playerTwoMovement = 0;
937 				}
938 			}
939 		);
940 	}
941 }
942 
943 /++ $(H3 $(ID example-minesweeper) Minesweeper)
944 
945 	This minesweeper demo shows how we can implement another classic
946 	game with simpledisplay and shows some mouse input and basic output
947 	code.
948 +/
949 version(demos)
950 unittest {
951 	import arsd.simpledisplay;
952 
953 	enum GameSquare {
954 		mine = 0,
955 		clear,
956 		m1, m2, m3, m4, m5, m6, m7, m8
957 	}
958 
959 	enum UserSquare {
960 		unknown,
961 		revealed,
962 		flagged,
963 		questioned
964 	}
965 
966 	enum GameState {
967 		inProgress,
968 		lose,
969 		win
970 	}
971 
972 	GameSquare[] board;
973 	UserSquare[] userState;
974 	GameState gameState;
975 	int boardWidth;
976 	int boardHeight;
977 
978 	bool isMine(int x, int y) {
979 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
980 			return false;
981 		return board[y * boardWidth + x] == GameSquare.mine;
982 	}
983 
984 	GameState reveal(int x, int y) {
985 		if(board[y * boardWidth + x] == GameSquare.clear) {
986 			floodFill(userState, boardWidth, boardHeight,
987 				UserSquare.unknown, UserSquare.revealed,
988 				x, y,
989 				(x, y) {
990 					if(board[y * boardWidth + x] == GameSquare.clear)
991 						return true;
992 					else {
993 						userState[y * boardWidth + x] = UserSquare.revealed;
994 						return false;
995 					}
996 				});
997 		} else {
998 			userState[y * boardWidth + x] = UserSquare.revealed;
999 			if(isMine(x, y))
1000 				return GameState.lose;
1001 		}
1002 
1003 		foreach(state; userState) {
1004 			if(state == UserSquare.unknown || state == UserSquare.questioned)
1005 				return GameState.inProgress;
1006 		}
1007 
1008 		return GameState.win;
1009 	}
1010 
1011 	void initializeBoard(int width, int height, int numberOfMines) {
1012 		boardWidth = width;
1013 		boardHeight = height;
1014 		board.length = width * height;
1015 
1016 		userState.length = width * height;
1017 		userState[] = UserSquare.unknown;
1018 
1019 		import std.algorithm, std.random, std.range;
1020 
1021 		board[] = GameSquare.clear;
1022 
1023 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
1024 			board[minePosition] = GameSquare.mine;
1025 
1026 		int x;
1027 		int y;
1028 		foreach(idx, ref square; board) {
1029 			if(square == GameSquare.clear) {
1030 				int danger = 0;
1031 				danger += isMine(x-1, y-1)?1:0;
1032 				danger += isMine(x-1, y)?1:0;
1033 				danger += isMine(x-1, y+1)?1:0;
1034 				danger += isMine(x, y-1)?1:0;
1035 				danger += isMine(x, y+1)?1:0;
1036 				danger += isMine(x+1, y-1)?1:0;
1037 				danger += isMine(x+1, y)?1:0;
1038 				danger += isMine(x+1, y+1)?1:0;
1039 
1040 				square = cast(GameSquare) (danger + 1);
1041 			}
1042 
1043 			x++;
1044 			if(x == width) {
1045 				x = 0;
1046 				y++;
1047 			}
1048 		}
1049 	}
1050 
1051 	void redraw(SimpleWindow window) {
1052 		import std.conv;
1053 
1054 		auto painter = window.draw();
1055 
1056 		painter.clear();
1057 
1058 		final switch(gameState) with(GameState) {
1059 			case inProgress:
1060 				break;
1061 			case win:
1062 				painter.fillColor = Color.green;
1063 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1064 				return;
1065 			case lose:
1066 				painter.fillColor = Color.red;
1067 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1068 				return;
1069 		}
1070 
1071 		int x = 0;
1072 		int y = 0;
1073 
1074 		foreach(idx, square; board) {
1075 			auto state = userState[idx];
1076 
1077 			final switch(state) with(UserSquare) {
1078 				case unknown:
1079 					painter.outlineColor = Color.black;
1080 					painter.fillColor = Color(128,128,128);
1081 
1082 					painter.drawRectangle(
1083 						Point(x * 20, y * 20),
1084 						20, 20
1085 					);
1086 				break;
1087 				case revealed:
1088 					if(square == GameSquare.clear) {
1089 						painter.outlineColor = Color.white;
1090 						painter.fillColor = Color.white;
1091 
1092 						painter.drawRectangle(
1093 							Point(x * 20, y * 20),
1094 							20, 20
1095 						);
1096 					} else {
1097 						painter.outlineColor = Color.black;
1098 						painter.fillColor = Color.white;
1099 
1100 						painter.drawText(
1101 							Point(x * 20, y * 20),
1102 							to!string(square)[1..2],
1103 							Point(x * 20 + 20, y * 20 + 20),
1104 							TextAlignment.Center | TextAlignment.VerticalCenter);
1105 					}
1106 				break;
1107 				case flagged:
1108 					painter.outlineColor = Color.black;
1109 					painter.fillColor = Color.red;
1110 					painter.drawRectangle(
1111 						Point(x * 20, y * 20),
1112 						20, 20
1113 					);
1114 				break;
1115 				case questioned:
1116 					painter.outlineColor = Color.black;
1117 					painter.fillColor = Color.yellow;
1118 					painter.drawRectangle(
1119 						Point(x * 20, y * 20),
1120 						20, 20
1121 					);
1122 				break;
1123 			}
1124 
1125 			x++;
1126 			if(x == boardWidth) {
1127 				x = 0;
1128 				y++;
1129 			}
1130 		}
1131 
1132 	}
1133 
1134 	void main() {
1135 		auto window = new SimpleWindow(200, 200);
1136 
1137 		initializeBoard(10, 10, 10);
1138 
1139 		redraw(window);
1140 		window.eventLoop(0,
1141 			delegate (MouseEvent me) {
1142 				if(me.type != MouseEventType.buttonPressed)
1143 					return;
1144 				auto x = me.x / 20;
1145 				auto y = me.y / 20;
1146 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1147 					if(me.button == MouseButton.left) {
1148 						gameState = reveal(x, y);
1149 					} else {
1150 						userState[y*boardWidth+x] = UserSquare.flagged;
1151 					}
1152 					redraw(window);
1153 				}
1154 			}
1155 		);
1156 	}
1157 }
1158 
1159 // FIXME: tetris demo
1160 // FIXME: space invaders demo
1161 // FIXME: asteroids demo
1162 
1163 import arsd.core;
1164 
1165 version(D_OpenD) {
1166 	version(OSX)
1167 		version=OSXCocoa;
1168 	version(iOS)
1169 		version=OSXCocoa;
1170 } else {
1171 	version(OSX) version(DigitalMars) version=OSXCocoa;
1172 }
1173 
1174 
1175 
1176 version(Emscripten) {
1177 	version=allow_unimplemented_features;
1178 	version=without_opengl;
1179 }
1180 
1181 
1182 version(OSXCocoa) {
1183 	version=without_opengl;
1184 	version=allow_unimplemented_features;
1185 	// version=OSXCocoa;
1186 	// pragma(linkerDirective, "-framework Cocoa");
1187 }
1188 
1189 version(without_opengl) {
1190 	enum SdpyIsUsingIVGLBinds = false;
1191 } else /*version(Posix)*/ {
1192 	static if (__traits(compiles, (){import iv.glbinds;})) {
1193 		enum SdpyIsUsingIVGLBinds = true;
1194 		public import iv.glbinds;
1195 		//pragma(msg, "SDPY: using iv.glbinds");
1196 	} else {
1197 		enum SdpyIsUsingIVGLBinds = false;
1198 	}
1199 //} else {
1200 //	enum SdpyIsUsingIVGLBinds = false;
1201 }
1202 
1203 version(Windows) {
1204 	//import core.sys.windows.windows;
1205 	import core.sys.windows.winnls;
1206 	import core.sys.windows.windef;
1207 	import core.sys.windows.basetyps;
1208 	import core.sys.windows.winbase;
1209 	import core.sys.windows.winuser;
1210 	import core.sys.windows.shellapi;
1211 	import core.sys.windows.wingdi;
1212 	static import gdi = core.sys.windows.wingdi; // so i
1213 
1214 	pragma(lib, "gdi32");
1215 	pragma(lib, "user32");
1216 
1217 	// for AlphaBlend... a breaking change....
1218 	version(CRuntime_DigitalMars) { } else
1219 		pragma(lib, "msimg32");
1220 
1221 	// core.sys.windows.dwmapi
1222 	private {
1223 		/++
1224 			See_also:
1225 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute
1226 		 +/
1227 		extern extern(Windows) HRESULT DwmGetWindowAttribute(
1228 			HWND hwnd,
1229 			DWORD dwAttribute,
1230 			PVOID pvAttribute,
1231 			DWORD cbAttribute
1232 		) nothrow @nogc;
1233 
1234 		/++
1235 			See_also:
1236 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
1237 		 +/
1238 		extern extern(Windows) HRESULT DwmSetWindowAttribute(
1239 			HWND hwnd,
1240 			DWORD dwAttribute,
1241 			LPCVOID pvAttribute,
1242 			DWORD cbAttribute
1243 		) nothrow @nogc;
1244 
1245 		/++
1246 			See_also:
1247 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
1248 		 +/
1249 		enum DWMWINDOWATTRIBUTE {
1250 			// incomplete, only declare what we need
1251 
1252 			/++
1253 				Usage:
1254 					pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*`
1255 
1256 				$(NOTE
1257 					Requires Windows 11 or later.
1258 				)
1259 			 +/
1260 			DWMWA_WINDOW_CORNER_PREFERENCE = 33,
1261 		}
1262 
1263 		/++
1264 			See_also:
1265 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
1266 		 +/
1267 		enum DWM_WINDOW_CORNER_PREFERENCE {
1268 			/// System decision
1269 			DWMWCP_DEFAULT = 0,
1270 
1271 			/// Never
1272 			DWMWCP_DONOTROUND = 1,
1273 
1274 			// If "appropriate"
1275 			DWMWCP_ROUND = 2,
1276 
1277 			// If "appropriate", but smaller radius
1278 			DWMWCP_ROUNDSMALL = 3
1279 		}
1280 
1281 		bool fromDWM(
1282 			DWM_WINDOW_CORNER_PREFERENCE value,
1283 			out CornerStyle result,
1284 		) @safe pure nothrow @nogc {
1285 			switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1286 				case DWMWCP_DEFAULT:
1287 					result = CornerStyle.automatic;
1288 					return true;
1289 				case DWMWCP_DONOTROUND:
1290 					result = CornerStyle.rectangular;
1291 					return true;
1292 				case DWMWCP_ROUND:
1293 					result = CornerStyle.rounded;
1294 					return true;
1295 				case DWMWCP_ROUNDSMALL:
1296 					result = CornerStyle.roundedSlightly;
1297 					return true;
1298 				default:
1299 					return false;
1300 			}
1301 		}
1302 
1303 		bool toDWM(
1304 			CornerStyle value,
1305 			out DWM_WINDOW_CORNER_PREFERENCE result,
1306 		) @safe pure nothrow @nogc {
1307 			final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1308 				case CornerStyle.automatic:
1309 					result = DWMWCP_DEFAULT;
1310 					return true;
1311 				case CornerStyle.rectangular:
1312 					result = DWMWCP_DONOTROUND;
1313 					return true;
1314 				case CornerStyle.rounded:
1315 					result = DWMWCP_ROUND;
1316 					return true;
1317 				case CornerStyle.roundedSlightly:
1318 					result = DWMWCP_ROUNDSMALL;
1319 					return true;
1320 			}
1321 		}
1322 
1323 		pragma(lib, "dwmapi");
1324 	}
1325 } else version(Emscripten) {
1326 } else version (linux) {
1327 	//k8: this is hack for rdmd. sorry.
1328 	static import core.sys.linux.epoll;
1329 	static import core.sys.linux.timerfd;
1330 }
1331 
1332 
1333 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1334 
1335 // http://wiki.dlang.org/Simpledisplay.d
1336 
1337 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1338 
1339 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1340 // but can i control the scroll lock led
1341 
1342 
1343 // Note: if you are using Image on X, you might want to do:
1344 /*
1345 	static if(UsingSimpledisplayX11) {
1346 		if(!Image.impl.xshmAvailable) {
1347 			// the images will use the slower XPutImage, you might
1348 			// want to consider an alternative method to get better speed
1349 		}
1350 	}
1351 
1352 	If the shared memory extension is available though, simpledisplay uses it
1353 	for a significant speed boost whenever you draw large Images.
1354 */
1355 
1356 // 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.
1357 
1358 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1359 
1360 /*
1361 	Biggest FIXME:
1362 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1363 
1364 		clean up opengl contexts when their windows close
1365 
1366 		fix resizing the bitmaps/pixmaps
1367 */
1368 
1369 // BTW on Windows:
1370 // -L/SUBSYSTEM:WINDOWS:5.0
1371 // to dmd will make a nice windows binary w/o a console if you want that.
1372 
1373 /*
1374 	Stuff to add:
1375 
1376 	use multibyte functions everywhere we can
1377 
1378 	OpenGL windows
1379 	more event stuff
1380 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1381 
1382 
1383 	resizeEvent
1384 		and make the windows non-resizable by default,
1385 		or perhaps stretched (if I can find something in X like StretchBlt)
1386 
1387 	take a screenshot function!
1388 
1389 	Pens and brushes?
1390 	Maybe a global event loop?
1391 
1392 	Mouse deltas
1393 	Key items
1394 */
1395 
1396 /*
1397 From MSDN:
1398 
1399 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1400 
1401 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.
1402 
1403 */
1404 
1405 version(Emscripten) {
1406 
1407 } else version(linux) {
1408 	version = X11;
1409 	version(without_libnotify) {
1410 		// we cool
1411 	}
1412 	else
1413 		version = libnotify;
1414 }
1415 
1416 version(libnotify) {
1417 	pragma(lib, "dl");
1418 	import core.sys.posix.dlfcn;
1419 
1420 	void delegate()[int] libnotify_action_delegates;
1421 	int libnotify_action_delegates_count;
1422 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1423 		auto idx = cast(int) user_data;
1424 		if(auto dgptr = idx in libnotify_action_delegates) {
1425 			(*dgptr)();
1426 			libnotify_action_delegates.remove(idx);
1427 		}
1428 	}
1429 
1430 	struct C_DynamicLibrary {
1431 		void* handle;
1432 		this(string name) {
1433 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1434 			if(handle is null)
1435 				throw new Exception("dlopen");
1436 		}
1437 
1438 		void close() {
1439 			dlclose(handle);
1440 		}
1441 
1442 		~this() {
1443 			// close
1444 		}
1445 
1446 		// FIXME: this looks up by name every time....
1447 		template call(string func, Ret, Args...) {
1448 			extern(C) Ret function(Args) fptr;
1449 			typeof(fptr) call() {
1450 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1451 				return fptr;
1452 			}
1453 		}
1454 	}
1455 
1456 	C_DynamicLibrary* libnotify;
1457 }
1458 
1459 version(OSX) {
1460 	version(OSXCocoa) {}
1461 	else { version = X11; }
1462 }
1463 	//version = OSXCocoa; // this was written by KennyTM
1464 version(FreeBSD)
1465 	version = X11;
1466 version(Solaris)
1467 	version = X11;
1468 
1469 version(X11) {
1470 	version(without_xft) {}
1471 	else version=with_xft;
1472 }
1473 
1474 void featureNotImplemented()() {
1475 	version(allow_unimplemented_features)
1476 		throw new NotYetImplementedException();
1477 	else
1478 		static assert(0);
1479 }
1480 
1481 // these are so the static asserts don't trigger unless you want to
1482 // add support to it for an OS
1483 version(Windows)
1484 	version = with_timer;
1485 version(linux)
1486 	version = with_timer;
1487 version(OSXCocoa)
1488 	version = with_timer;
1489 
1490 version(with_timer)
1491 	enum bool SimpledisplayTimerAvailable = true;
1492 else
1493 	enum bool SimpledisplayTimerAvailable = false;
1494 
1495 /// 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.
1496 version(Windows)
1497 	enum bool UsingSimpledisplayWindows = true;
1498 else
1499 	enum bool UsingSimpledisplayWindows = false;
1500 
1501 /// 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.
1502 version(X11)
1503 	enum bool UsingSimpledisplayX11 = true;
1504 else
1505 	enum bool UsingSimpledisplayX11 = false;
1506 
1507 /// 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.
1508 version(OSXCocoa)
1509 	enum bool UsingSimpledisplayCocoa = true;
1510 else
1511 	enum bool UsingSimpledisplayCocoa = false;
1512 
1513 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1514 version(Windows)
1515 	enum multipleWindowsSupported = true;
1516 else version(Emscripten)
1517 	enum multipleWindowsSupported = false;
1518 else version(X11)
1519 	enum multipleWindowsSupported = true;
1520 else version(OSXCocoa)
1521 	enum multipleWindowsSupported = true;
1522 else
1523 	static assert(0);
1524 
1525 version(without_opengl)
1526 	enum bool OpenGlEnabled = false;
1527 else
1528 	enum bool OpenGlEnabled = true;
1529 
1530 /++
1531 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1532 	If you mix this in above your `main` function, you no longer need to use the linker
1533 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1534 
1535 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1536 
1537 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1538 	stderr writeln. It will fail and throw an exception.
1539 
1540 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1541 
1542 	History:
1543 		Added November 24, 2021 (dub v10.4)
1544 +/
1545 mixin template EnableWindowsSubsystem() {
1546 	version(Windows)
1547 	version(CRuntime_Microsoft) {
1548 		pragma(linkerDirective, "/subsystem:windows");
1549 		version(LDC)
1550 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1551 		else
1552 			pragma(linkerDirective, "/entry:mainCRTStartup");
1553 	}
1554 }
1555 
1556 
1557 /++
1558 	After selecting a type from [WindowTypes], you may further customize
1559 	its behavior by setting one or more of these flags.
1560 
1561 
1562 	The different window types have different meanings of `normal`. If the
1563 	window type already is a good match for what you want to do, you should
1564 	just use [WindowFlags.normal], the default, which will do the right thing
1565 	for your users.
1566 
1567 	The window flags will not always be honored by the operating system
1568 	and window managers; they are hints, not commands.
1569 +/
1570 enum WindowFlags : int {
1571 	normal = 0, ///
1572 	skipTaskbar = 1, ///
1573 	alwaysOnTop = 2, ///
1574 	alwaysOnBottom = 4, ///
1575 	cannotBeActivated = 8, ///
1576 	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.
1577 	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.
1578 	/++
1579 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1580 		it is still a top-level window. This should NOT be set separately for most window types.
1581 
1582 		A transient window will not keep the application open if its main window closes.
1583 
1584 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1585 
1586 
1587 		From the ICCM:
1588 
1589 		$(BLOCKQUOTE
1590 			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.
1591 
1592 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1593 		)
1594 
1595 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1596 
1597 		History:
1598 			Added February 23, 2021 but not yet stabilized.
1599 	+/
1600 	transient = 64,
1601 	/++
1602 		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.
1603 
1604 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1605 
1606 		History:
1607 			Added April 1, 2022
1608 	+/
1609 	managesChildWindowFocus = 128,
1610 
1611 	/++
1612 	+/
1613 	overrideRedirect = 256,
1614 
1615 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1616 }
1617 
1618 /++
1619 	When creating a window, you can pass a type to SimpleWindow's constructor,
1620 	then further customize the window by changing `WindowFlags`.
1621 
1622 
1623 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1624 	use. The others are there to build a foundation for a higher level GUI toolkit,
1625 	but are themselves not as high level as you might think from their names.
1626 
1627 	This list is based on the EMWH spec for X11.
1628 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1629 +/
1630 enum WindowTypes : int {
1631 	/// An ordinary application window.
1632 	normal,
1633 	/// 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.
1634 	undecorated,
1635 	/// 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.
1636 	eventOnly,
1637 	/// A drop down menu, such as from a menu bar
1638 	dropdownMenu,
1639 	/// A popup menu, such as from a right click
1640 	popupMenu,
1641 	/// A popup bubble notification
1642 	notification,
1643 	/*
1644 	menu, /// a tearable menu bar (not override-redirect - contrast to popups)
1645 	toolbar, /// a tearable menu bar (not override-redirect)
1646 	splashScreen, /// a loading splash screen for your application
1647 	desktop, ///
1648 	dockOrPanel, /// think taskbar
1649 	utility, /// a palette or something
1650 	*/
1651 	/// A tiny window showing temporary help text or something.
1652 	tooltip,
1653 	/// only supported on X; will assert fail elsewhere
1654 	dnd,
1655 	/// can also be used for other auto-complete presentations
1656 	comboBoxDropdown,
1657 	/// a dialog box of some sort
1658 	dialog,
1659 	/// a child nested inside the parent. You must pass a parent window to the ctor
1660 	nestedChild,
1661 
1662 	/++
1663 		The type you get when you pass in an existing browser handle, which means most
1664 		of simpledisplay's fancy things will not be done since they were never set up.
1665 
1666 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1667 		failure; you should use the existing handle constructor.
1668 
1669 		History:
1670 			Added November 17, 2022 (previously it would have type `normal`)
1671 	+/
1672 	minimallyWrapped
1673 }
1674 
1675 
1676 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1677 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1678 private __gshared char* sdpyWindowClassStr = null;
1679 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1680 
1681 /**
1682 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1683 	You may want to change context version if you want to use advanced shaders or
1684 	other modern OpenGL techinques. This setting doesn't affect already created
1685 	windows. You may use version 2.1 as your default, which should be supported
1686 	by any box since 2006, so seems to be a reasonable choice.
1687 
1688 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1689 	old context creation code without any version specified. This is the safest
1690 	way to init OpenGL, but it may not give you access to advanced features.
1691 
1692 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1693 */
1694 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1695 
1696 /**
1697 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1698 	pipeline functions, and without "compatible" mode you won't be able to use
1699 	your old non-shader-based code with such contexts. By default SimpleDisplay
1700 	creates compatible context, so you can gradually upgrade your OpenGL code if
1701 	you want to (or leave it as is, as it should "just work").
1702 */
1703 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1704 
1705 /**
1706 	Set to `true` to allow creating OpenGL context with lower version than requested
1707 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1708 	`openGLContextFallbackActivated()` will return `true`.
1709 	*/
1710 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1711 
1712 /**
1713 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1714 	*/
1715 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1716 
1717 /++
1718 	History:
1719 		Added April 24, 2023  (dub v11.0)
1720 +/
1721 version(without_opengl) {} else
1722 auto openGLCurrentContext() {
1723 	version(Windows)
1724 		return wglGetCurrentContext();
1725 	else
1726 		return glXGetCurrentContext();
1727 }
1728 
1729 
1730 /**
1731 	Set window class name for all following `new SimpleWindow()` calls.
1732 
1733 	WARNING! For Windows, you should set your class name before creating any
1734 	window, and NEVER change it after that!
1735 */
1736 void sdpyWindowClass (const(char)[] v) {
1737 	import core.stdc.stdlib : realloc;
1738 	if (v.length == 0) v = "SimpleDisplayWindow";
1739 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1740 	if (sdpyWindowClassStr is null) return; // oops
1741 	sdpyWindowClassStr[0..v.length+1] = 0;
1742 	sdpyWindowClassStr[0..v.length] = v[];
1743 }
1744 
1745 /**
1746 	Get current window class name.
1747 */
1748 string sdpyWindowClass () @trusted {
1749 	if (sdpyWindowClassStr is null) return null;
1750 	foreach (immutable idx; 0..size_t.max-1) {
1751 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1752 	}
1753 	return null;
1754 }
1755 
1756 /++
1757 	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.
1758 
1759 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1760 +/
1761 float[2] getDpi() {
1762 	float[2] dpi;
1763 	version(Windows) {
1764 		HDC screen = GetDC(null);
1765 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1766 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1767 	} else version(X11) {
1768 		auto display = XDisplayConnection.get;
1769 		auto screen = DefaultScreen(display);
1770 
1771 		void fallback() {
1772 			/+
1773 			// 25.4 millimeters in an inch...
1774 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1775 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1776 			+/
1777 
1778 			// the physical size isn't actually as important as the logical size since this is
1779 			// all about scaling really
1780 			dpi[0] = 96;
1781 			dpi[1] = 96;
1782 		}
1783 
1784 		auto xft = getXftDpi();
1785 		if(xft is float.init)
1786 			fallback();
1787 		else {
1788 			dpi[0] = xft;
1789 			dpi[1] = xft;
1790 		}
1791 	}
1792 
1793 	return dpi;
1794 }
1795 
1796 version(X11)
1797 float getXftDpi() {
1798 	auto display = XDisplayConnection.get;
1799 
1800 	char* resourceString = XResourceManagerString(display);
1801 	XrmInitialize();
1802 
1803 	if (resourceString) {
1804 		auto db = XrmGetStringDatabase(resourceString);
1805 		XrmValue value;
1806 		char* type;
1807 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1808 			if (value.addr) {
1809 				import core.stdc.stdlib;
1810 				return atof(cast(char*) value.addr);
1811 			}
1812 		}
1813 	}
1814 
1815 	return float.init;
1816 }
1817 
1818 /++
1819 	Implementation used by [SimpleWindow.takeScreenshot].
1820 
1821 	Params:
1822 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1823 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1824 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1825 		x = the x-offset of the image to capture, from the left.
1826 		y = the y-offset of the image to capture, from the top.
1827 
1828 	History:
1829 		Added on March 14, 2021
1830 
1831 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1832 
1833 +/
1834 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1835 	TrueColorImage got;
1836 	version(X11) {
1837 		auto display = XDisplayConnection.get;
1838 		if(handle == 0)
1839 			handle = RootWindow(display, DefaultScreen(display));
1840 
1841 		if(width == 0 || height == 0) {
1842 			Window root;
1843 			int xpos, ypos;
1844 			uint widthret, heightret, borderret, depthret;
1845 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1846 
1847 			if(width == 0)
1848 				width = widthret;
1849 			if(height == 0)
1850 				height = heightret;
1851 		}
1852 
1853 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1854 
1855 		// https://github.com/adamdruppe/arsd/issues/98
1856 
1857 		auto i = new Image(image);
1858 		got = i.toTrueColorImage();
1859 
1860 		XDestroyImage(image);
1861 	} else version(Windows) {
1862 		auto hdc = GetDC(handle);
1863 		scope(exit) ReleaseDC(handle, hdc);
1864 
1865 		if(width == 0 || height == 0) {
1866 			BITMAP bmHeader;
1867 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1868 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1869 			if(width == 0)
1870 				width = bmHeader.bmWidth;
1871 			if(height == 0)
1872 				height = bmHeader.bmHeight;
1873 		}
1874 
1875 		auto i = new Image(width, height);
1876 		HDC hdcMem = CreateCompatibleDC(hdc);
1877 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1878 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1879 		SelectObject(hdcMem, hbmOld);
1880 		DeleteDC(hdcMem);
1881 
1882 		got = i.toTrueColorImage();
1883 	} else featureNotImplemented();
1884 
1885 	return got;
1886 }
1887 
1888 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1889 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1890 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1891 
1892 version(Windows)
1893 shared static this() {
1894 	auto lib = LoadLibrary("User32.dll");
1895 	if(lib is null)
1896 		return;
1897 	//scope(exit)
1898 		//FreeLibrary(lib);
1899 
1900 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1901 
1902 	if(SetProcessDpiAwarenessContext is null)
1903 		return;
1904 
1905 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1906 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1907 		//writeln(GetLastError());
1908 	}
1909 
1910 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1911 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1912 }
1913 
1914 /++
1915 	Blocking mode for event loop calls associated with a window instance.
1916 
1917 	History:
1918 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1919 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1920 		is, all would block until the application quit.
1921 
1922 		That behavior can still be achieved here with `untilApplicationQuits`,
1923 		or explicitly calling the top-level `EventLoop.get.run` function.
1924 +/
1925 enum BlockingMode {
1926 	/++
1927 		The event loop call will block until the whole application is ready
1928 		to quit if it is the only one running, but if it is nested inside
1929 		another one, it will only block until the window you're calling it on
1930 		closes.
1931 	+/
1932 	automatic             = 0x00,
1933 	/++
1934 		The event loop call will only return when the whole application
1935 		is ready to quit. This usually means all windows have been closed.
1936 
1937 		This is appropriate for your main application event loop.
1938 	+/
1939 	untilApplicationQuits = 0x01,
1940 	/++
1941 		The event loop will return when the window you're calling it on
1942 		closes. If there are other windows still open, they may be destroyed
1943 		unless you have another event loop running later.
1944 
1945 		This might be appropriate for a modal dialog box loop. Remember that
1946 		other windows are still processing input though, so you can end up
1947 		with a lengthy call stack if this happens in a loop, similar to a
1948 		recursive function (well, it literally is a recursive function, just
1949 		not an obvious looking one).
1950 	+/
1951 	untilWindowCloses     = 0x02,
1952 	/++
1953 		If an event loop is already running, this call will immediately
1954 		return, allowing the existing loop to handle it. If not, this call
1955 		will block until the condition you bitwise-or into the flag.
1956 
1957 		The default is to block until the application quits, same as with
1958 		the `automatic` setting (since if it were nested, which triggers until
1959 		window closes in automatic, this flag would instead not block at all),
1960 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1961 		it will only nest until the window closes. You might want that if you are
1962 		going to open two windows simultaneously and want closing just one of them
1963 		to trigger the event loop return.
1964 	+/
1965 	onlyIfNotNested       = 0x10,
1966 }
1967 
1968 /++
1969 	Window corner visuals preference
1970  +/
1971 enum CornerStyle {
1972 	/++
1973 		Use the default style automatically applied by the system or its window manager/compositor.
1974 	 +/
1975 	automatic,
1976 
1977 	/++
1978 		Prefer rectangular window corners
1979 	 +/
1980 	rectangular,
1981 
1982 	/++
1983 		Prefer rounded window corners
1984 	 +/
1985 	rounded,
1986 
1987 	/++
1988 		Prefer slightly-rounded window corners
1989 	 +/
1990 	roundedSlightly,
1991 }
1992 
1993 /++
1994 	The flagship window class.
1995 
1996 
1997 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1998 	out of more advanced or complex features of the underlying windowing system.
1999 
2000 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
2001 	and get a suitable window to work with.
2002 
2003 	From there, you can opt into additional features, like custom resizability and OpenGL support
2004 	with the next two constructor arguments. Or, if you need even more, you can set a window type
2005 	and customization flags with the final two constructor arguments.
2006 
2007 	If none of that works for you, you can also create a window using native function calls, then
2008 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
2009 	though, if you do this, managing the window is still your own responsibility! Notably, you
2010 	will need to destroy it yourself.
2011 +/
2012 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
2013 
2014 	/++
2015 		Copies the window's current state into a [TrueColorImage].
2016 
2017 		Be warned: this can be a very slow operation
2018 
2019 		History:
2020 			Actually implemented on March 14, 2021
2021 	+/
2022 	TrueColorImage takeScreenshot() {
2023 		version(Windows)
2024 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
2025 		else version(OSXCocoa)
2026 			throw new NotYetImplementedException();
2027 		else
2028 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
2029 	}
2030 
2031 	/++
2032 		Returns the actual logical DPI for the window on its current display monitor. If the window
2033 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
2034 
2035 		Please note this function may return zero if it doesn't know the answer!
2036 
2037 
2038 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
2039 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
2040 
2041 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
2042 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
2043 		window primarily resides on by checking the center point of the window against the monitor map.
2044 
2045 		Returns:
2046 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
2047 			assumes the X and Y dpi are the same.
2048 
2049 		History:
2050 			Added November 26, 2021 (dub v10.4)
2051 
2052 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
2053 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
2054 			that.
2055 
2056 		Bugs:
2057 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
2058 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
2059 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
2060 			and 1.5 on the secondary monitor.
2061 
2062 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
2063 			is a historical misnomer - the real thing of interest is the scale factor and due to
2064 			compatibility concerns the scale would modify dpi values to trick applications. But since
2065 			that's the terminology common out there, I used it too.
2066 
2067 		See_Also:
2068 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
2069 			as this since the window many be on a different monitor, but it is a reasonable fallback
2070 			to use if `actualDpi` returns 0.
2071 
2072 			[onDpiChanged] is changed when `actualDpi` has changed.
2073 	+/
2074 	int actualDpi() {
2075 		version(X11) bool useFallbackDpi = false;
2076 		if(!actualDpiLoadAttempted) {
2077 			// FIXME: do the actual monitor we are on
2078 			// and on X this is a good chance to load the monitor map.
2079 			version(Windows) {
2080 				if(GetDpiForWindow)
2081 					actualDpi_ = GetDpiForWindow(impl.hwnd);
2082 			} else version(X11) {
2083 				if(!xRandrInfoLoadAttemped) {
2084 					xRandrInfoLoadAttemped = true;
2085 					if(!XRandrLibrary.attempted) {
2086 						XRandrLibrary.loadDynamicLibrary();
2087 					}
2088 
2089 					if(XRandrLibrary.loadSuccessful) {
2090 						auto display = XDisplayConnection.get;
2091 						int scratch;
2092 						int major, minor;
2093 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
2094 							goto fallback;
2095 
2096 						XRRQueryVersion(display, &major, &minor);
2097 						if(major <= 1 && minor < 5)
2098 							goto fallback;
2099 
2100 						int count;
2101 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
2102 						if(monitors is null)
2103 							goto fallback;
2104 						scope(exit) XRRFreeMonitors(monitors);
2105 
2106 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
2107 						MonitorInfo.info.assumeSafeAppend();
2108 						foreach(idx, monitor; monitors[0 .. count]) {
2109 							MonitorInfo.info ~= MonitorInfo(
2110 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2111 								Size(monitor.mwidth, monitor.mheight),
2112 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
2113 							);
2114 
2115 							/+
2116 							if(monitor.mwidth == 0 || monitor.mheight == 0)
2117 							// unknown physical size, just guess 96 to avoid divide by zero
2118 							MonitorInfo.info ~= MonitorInfo(
2119 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2120 								Size(monitor.mwidth, monitor.mheight),
2121 								96
2122 							);
2123 							else
2124 							// and actual thing
2125 							MonitorInfo.info ~= MonitorInfo(
2126 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2127 								Size(monitor.mwidth, monitor.mheight),
2128 								minInternal(
2129 									// millimeter to int then rounding up.
2130 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
2131 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
2132 								)
2133 							);
2134 							+/
2135 						}
2136 					// writeln("Here", MonitorInfo.info);
2137 					}
2138 				}
2139 
2140 				if(XRandrLibrary.loadSuccessful) {
2141 					updateActualDpi(true);
2142 					// writeln("updated");
2143 
2144 					if(!requestedInput) {
2145 						// this is what requests live updates should the configuration change
2146 						// each time you select input, it sends an initial event, so very important
2147 						// to not get into a loop of selecting input, getting event, updating data,
2148 						// and reselecting input...
2149 						requestedInput = true;
2150 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
2151 						// writeln("requested input");
2152 					}
2153 				} else {
2154 					fallback:
2155 					// make sure we disable events that aren't coming
2156 					xrrEventBase = -1;
2157 					// best guess... respect the custom scaling user command to some extent at least though
2158 					useFallbackDpi = true;
2159 				}
2160 			} else version(OSXCocoa) {
2161 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
2162 			}
2163 			actualDpiLoadAttempted = true;
2164 		} else version(X11) if(MonitorInfo.info.length == 0) {
2165 			useFallbackDpi = true;
2166 		}
2167 
2168 		version(X11)
2169 		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...
2170 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
2171 		return actualDpi_;
2172 	}
2173 
2174 	private int actualDpi_;
2175 	private bool actualDpiLoadAttempted;
2176 
2177 	version(X11) private {
2178 		bool requestedInput;
2179 		static bool xRandrInfoLoadAttemped;
2180 		struct MonitorInfo {
2181 			Rectangle position;
2182 			Size size;
2183 			int dpi;
2184 
2185 			static MonitorInfo[] info;
2186 		}
2187 		bool screenPositionKnown;
2188 		int screenPositionX;
2189 		int screenPositionY;
2190 		void updateActualDpi(bool loadingNow = false) {
2191 			if(!loadingNow && !actualDpiLoadAttempted)
2192 				actualDpi(); // just to make it do the load
2193 			foreach(idx, m; MonitorInfo.info) {
2194 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
2195 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
2196 					actualDpi_ = m.dpi;
2197 					// writeln("monitor ", idx);
2198 					if(changed && onDpiChanged)
2199 						onDpiChanged();
2200 					break;
2201 				}
2202 			}
2203 		}
2204 	}
2205 
2206 	/++
2207 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2208 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2209 
2210 		History:
2211 			Added November 26, 2021 (dub v10.4)
2212 
2213 		See_Also:
2214 			[actualDpi]
2215 	+/
2216 	void delegate() onDpiChanged;
2217 
2218 	version(X11) {
2219 		void recreateAfterDisconnect() {
2220 			if(!stateDiscarded) return;
2221 
2222 			if(_parent !is null && _parent.stateDiscarded)
2223 				_parent.recreateAfterDisconnect();
2224 
2225 			bool wasHidden = hidden;
2226 
2227 			activeScreenPainter = null; // should already be done but just to confirm
2228 
2229 			actualDpi_ = 0;
2230 			actualDpiLoadAttempted = false;
2231 			xRandrInfoLoadAttemped = false;
2232 
2233 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2234 
2235 			if(auto dh = dropHandler) {
2236 				dropHandler = null;
2237 				enableDragAndDrop(this, dh);
2238 			}
2239 
2240 			if(recreateAdditionalConnectionState)
2241 				recreateAdditionalConnectionState();
2242 
2243 			hidden = wasHidden;
2244 			stateDiscarded = false;
2245 		}
2246 
2247 		bool stateDiscarded;
2248 		void discardConnectionState() {
2249 			if(XDisplayConnection.display)
2250 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2251 			if(discardAdditionalConnectionState)
2252 				discardAdditionalConnectionState();
2253 			stateDiscarded = true;
2254 		}
2255 
2256 		void delegate() discardAdditionalConnectionState;
2257 		void delegate() recreateAdditionalConnectionState;
2258 
2259 	}
2260 
2261 	private DropHandler dropHandler;
2262 
2263 	SimpleWindow _parent;
2264 	bool beingOpenKeepsAppOpen = true;
2265 	/++
2266 		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.
2267 
2268 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2269 
2270 		Params:
2271 
2272 		width = the width of the window's client area, in pixels
2273 		height = the height of the window's client area, in pixels
2274 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2275 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2276 		resizable = [Resizability] has three options:
2277 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2278 			$(P `fixedSize` will not allow the user to resize the window.)
2279 			$(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.)
2280 		windowType = The type of window you want to make.
2281 		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.
2282 		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".
2283 	+/
2284 	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) {
2285 		claimGuiThread();
2286 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2287 		this._width = this._virtualWidth = width;
2288 		this._height = this._virtualHeight = height;
2289 		this.openglMode = opengl;
2290 		version(X11) {
2291 			// auto scale not implemented except with opengl and even there it is kinda weird
2292 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2293 				resizable = Resizability.fixedSize;
2294 		}
2295 		this.resizability = resizable;
2296 		this.windowType = windowType;
2297 		this.customizationFlags = customizationFlags;
2298 		this._title = (title is null ? "D Application" : title);
2299 		this._parent = parent;
2300 		impl.createWindow(width, height, this._title, opengl, parent);
2301 
2302 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2303 			beingOpenKeepsAppOpen = false;
2304 	}
2305 
2306 	/// ditto
2307 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2308 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2309 	}
2310 
2311 	/// Same as above, except using the `Size` struct instead of separate width and height.
2312 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2313 		this(size.width, size.height, title, opengl, resizable);
2314 	}
2315 
2316 	/// ditto
2317 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2318 		this(size, title, opengl, resizable);
2319 	}
2320 
2321 
2322 	/++
2323 		Creates a window based on the given [Image]. It's client area
2324 		width and height is equal to the image. (A window's client area
2325 		is the drawable space inside; it excludes the title bar, etc.)
2326 
2327 		Windows based on images will not be resizable and do not use OpenGL.
2328 
2329 		It will draw the image in upon creation, but this will be overwritten
2330 		upon any draws, including the initial window visible event.
2331 
2332 		You probably do not want to use this and it may be removed from
2333 		the library eventually, or I might change it to be a "permanent"
2334 		background image; one that is automatically drawn on it before any
2335 		other drawing event. idk.
2336 	+/
2337 	this(Image image, string title = null) {
2338 		this(image.width, image.height, title);
2339 		this.image = image;
2340 	}
2341 
2342 	/++
2343 		Wraps a native window handle with very little additional processing - notably no destruction
2344 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2345 		windows created through the low level API (so you can use platform-specific options and
2346 		other details SimpleWindow does not expose) available to the event loop wrappers.
2347 	+/
2348 	this(NativeWindowHandle nativeWindow) {
2349 		windowType = WindowTypes.minimallyWrapped;
2350 		version(Windows)
2351 			impl.hwnd = nativeWindow;
2352 		else version(X11) {
2353 			impl.window = nativeWindow;
2354 			if(nativeWindow)
2355 				display = XDisplayConnection.get(); // get initial display to not segfault
2356 		} else version(Emscripten) {
2357 			// FIXME
2358 		} else version(OSXCocoa) {
2359 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2360 		} else featureNotImplemented();
2361 		// FIXME: set the size correctly
2362 		_width = 1;
2363 		_height = 1;
2364 		if(nativeWindow)
2365 			nativeMapping[cast(void*) nativeWindow] = this;
2366 
2367 		beingOpenKeepsAppOpen = false;
2368 
2369 		if(nativeWindow)
2370 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2371 		_suppressDestruction = true; // so it doesn't try to close
2372 	}
2373 
2374 	/++
2375 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2376 		The delegate will be called when the window manager asks you to take focus.
2377 
2378 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2379 
2380 		History:
2381 			Added April 1, 2022 (dub v10.8)
2382 	+/
2383 	SimpleWindow delegate() setRequestedInputFocus;
2384 
2385 	/// Experimental, do not use yet
2386 	/++
2387 		Grabs exclusive input from the user until you release it with
2388 		[releaseInputGrab].
2389 
2390 
2391 		Note: it is extremely rude to do this without good reason.
2392 		Reasons may include doing some kind of mouse drag operation
2393 		or popping up a temporary menu that should get events and will
2394 		be dismissed at ease by the user clicking away.
2395 
2396 		Params:
2397 			keyboard = do you want to grab keyboard input?
2398 			mouse = grab mouse input?
2399 			confine = confine the mouse cursor to inside this window?
2400 
2401 		History:
2402 			Prior to March 11, 2021, grabbing the keyboard would always also
2403 			set the X input focus. Now, it only focuses if it is a non-transient
2404 			window and otherwise manages the input direction internally.
2405 
2406 			This means spurious focus/blur events will no longer be sent and the
2407 			application will not steal focus from other applications (which the
2408 			window manager may have rejected anyway).
2409 	+/
2410 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2411 		static if(UsingSimpledisplayX11) {
2412 			XSync(XDisplayConnection.get, 0);
2413 			if(keyboard) {
2414 				if(isTransient && _parent) {
2415 					/*
2416 					FIXME:
2417 						setting the keyboard focus is not actually that helpful, what I more likely want
2418 						is the events from the parent window to be sent over here if we're transient.
2419 					*/
2420 
2421 					_parent.inputProxy = this;
2422 				} else {
2423 
2424 					SimpleWindow setTo;
2425 					if(setRequestedInputFocus !is null)
2426 						setTo = setRequestedInputFocus();
2427 					if(setTo is null)
2428 						setTo = this;
2429 
2430 					// sdpyPrintDebugString("grabInput() ", setTo.impl.window;
2431 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2432 				}
2433 			}
2434 			if(mouse) {
2435 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2436 				EventMask.PointerMotionMask // FIXME: not efficient
2437 				| EventMask.ButtonPressMask
2438 				| EventMask.ButtonReleaseMask
2439 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2440 				)
2441 			{
2442 				XSync(XDisplayConnection.get, 0);
2443 				import core.stdc.stdio;
2444 				printf("Grab input failed %d\n", res);
2445 				//throw new Exception("Grab input failed");
2446 			} else {
2447 				// cool
2448 			}
2449 			}
2450 
2451 		} else version(Windows) {
2452 			// FIXME: keyboard?
2453 			SetCapture(impl.hwnd);
2454 			if(confine) {
2455 				RECT rcClip;
2456 				//RECT rcOldClip;
2457 				//GetClipCursor(&rcOldClip);
2458 				GetWindowRect(hwnd, &rcClip);
2459 				ClipCursor(&rcClip);
2460 			}
2461 		} else version(Emscripten) {
2462 			// nothing necessary
2463 		} else version(OSXCocoa) {
2464 			// throw new NotYetImplementedException();
2465 		} else static assert(0);
2466 	}
2467 
2468 	private Point imePopupLocation = Point(0, 0);
2469 
2470 	/++
2471 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2472 
2473 		Bugs:
2474 			Not implemented outside X11.
2475 	+/
2476 	void setIMEPopupLocation(Point location) {
2477 		static if(UsingSimpledisplayX11) {
2478 			imePopupLocation = location;
2479 			updateIMEPopupLocation();
2480 		} else {
2481 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2482 			// throw new NotYetImplementedException();
2483 		}
2484 	}
2485 
2486 	/// ditto
2487 	void setIMEPopupLocation(int x, int y) {
2488 		return setIMEPopupLocation(Point(x, y));
2489 	}
2490 
2491 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2492 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2493 	// receives a ConfigureNotify event
2494 	private void updateIMEPopupLocation() {
2495 		static if(UsingSimpledisplayX11) {
2496 			if (xic is null) {
2497 				return;
2498 			}
2499 
2500 			XPoint nspot;
2501 			nspot.x = cast(short) imePopupLocation.x;
2502 			nspot.y = cast(short) imePopupLocation.y;
2503 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2504 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2505 			XFree(preeditAttr);
2506 		}
2507 	}
2508 
2509 	private bool imeFocused = true;
2510 
2511 	/++
2512 		Tells the IME whether or not an input field is currently focused in the window.
2513 
2514 		Bugs:
2515 			Not implemented outside X11.
2516 	+/
2517 	void setIMEFocused(bool value) {
2518 		imeFocused = value;
2519 		updateIMEFocused();
2520 	}
2521 
2522 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2523 	private void updateIMEFocused() {
2524 		static if(UsingSimpledisplayX11) {
2525 			if (xic is null) {
2526 				return;
2527 			}
2528 
2529 			if (focused && imeFocused) {
2530 				XSetICFocus(xic);
2531 			} else {
2532 				XUnsetICFocus(xic);
2533 			}
2534 		}
2535 	}
2536 
2537 	/++
2538 		Returns the native window.
2539 
2540 		History:
2541 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2542 			to access it through the `impl` member (which is semi-supported
2543 			but platform specific and here it is simple enough to offer an accessor).
2544 
2545 		Bugs:
2546 			Not implemented outside Windows or X11.
2547 	+/
2548 	NativeWindowHandle nativeWindowHandle() {
2549 		version(X11)
2550 			return impl.window;
2551 		else version(Windows)
2552 			return impl.hwnd;
2553 		else
2554 			throw new NotYetImplementedException();
2555 	}
2556 
2557 	private bool isTransient() {
2558 		with(WindowTypes)
2559 		final switch(windowType) {
2560 			case normal, undecorated, eventOnly:
2561 			case nestedChild, minimallyWrapped:
2562 				return (customizationFlags & WindowFlags.transient) ? true : false;
2563 			case dropdownMenu, popupMenu, notification, dialog, tooltip, dnd, comboBoxDropdown:
2564 				return true;
2565 		}
2566 	}
2567 
2568 	private SimpleWindow inputProxy;
2569 
2570 	/++
2571 		Releases the grab acquired by [grabInput].
2572 	+/
2573 	void releaseInputGrab() {
2574 		static if(UsingSimpledisplayX11) {
2575 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2576 			if(_parent)
2577 				_parent.inputProxy = null;
2578 		} else version(Windows) {
2579 			ReleaseCapture();
2580 			ClipCursor(null);
2581 		} else version(OSXCocoa) {
2582 			// throw new NotYetImplementedException();
2583 		} else version(Emscripten) {
2584 			// nothing needed
2585 		} else static assert(0);
2586 	}
2587 
2588 	/++
2589 		Sets the input focus to this window.
2590 
2591 		You shouldn't call this very often - please let the user control the input focus.
2592 	+/
2593 	void focus() {
2594 		static if(UsingSimpledisplayX11) {
2595 			SimpleWindow setTo;
2596 			if(setRequestedInputFocus !is null)
2597 				setTo = setRequestedInputFocus();
2598 			if(setTo is null)
2599 				setTo = this;
2600 			// sdpyPrintDebugString("sdpy.focus() ", setTo.impl.window);
2601 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2602 		} else version(Windows) {
2603 			SetFocus(this.impl.hwnd);
2604 		} else version(Emscripten) {
2605 			throw new NotYetImplementedException();
2606 		} else version(OSXCocoa) {
2607 			throw new NotYetImplementedException();
2608 		} else static assert(0);
2609 	}
2610 
2611 	/++
2612 		Requests attention from the user for this window.
2613 
2614 
2615 		The typical result of this function is to change the color
2616 		of the taskbar icon, though it may be tweaked on specific
2617 		platforms.
2618 
2619 		It is meant to unobtrusively tell the user that something
2620 		relevant to them happened in the background and they should
2621 		check the window when they get a chance. Upon receiving the
2622 		keyboard focus, the window will automatically return to its
2623 		natural state.
2624 
2625 		If the window already has the keyboard focus, this function
2626 		may do nothing, because the user is presumed to already be
2627 		giving the window attention.
2628 
2629 		Implementation_note:
2630 
2631 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2632 		atom on X11 and the FlashWindow function on Windows.
2633 	+/
2634 	void requestAttention() {
2635 		if(_focused)
2636 			return;
2637 
2638 		version(Windows) {
2639 			FLASHWINFO info;
2640 			info.cbSize = info.sizeof;
2641 			info.hwnd = impl.hwnd;
2642 			info.dwFlags = FLASHW_TRAY;
2643 			info.uCount = 1;
2644 
2645 			FlashWindowEx(&info);
2646 
2647 		} else version(X11) {
2648 			demandingAttention = true;
2649 			demandAttention(this, true);
2650 		} else version(Emscripten) {
2651 			throw new NotYetImplementedException();
2652 		} else version(OSXCocoa) {
2653 			throw new NotYetImplementedException();
2654 		} else static assert(0);
2655 	}
2656 
2657 	private bool _focused;
2658 
2659 	version(X11) private bool demandingAttention;
2660 
2661 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2662 	/// You'll have to call `close()` manually if you set this delegate.
2663 	void delegate () closeQuery;
2664 
2665 	/// This will be called when window visibility was changed.
2666 	void delegate (bool becomesVisible) visibilityChanged;
2667 
2668 	/// This will be called when window becomes visible for the first time.
2669 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2670 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2671 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2672 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2673 	private bool _visibleForTheFirstTimeCalled;
2674 	void delegate () visibleForTheFirstTime;
2675 
2676 	/// Returns true if the window has been closed.
2677 	final @property bool closed() { return _closed; }
2678 
2679 	private final @property bool notClosed() { return !_closed; }
2680 
2681 	/// Returns true if the window is focused.
2682 	final @property bool focused() { return _focused; }
2683 
2684 	private bool _visible;
2685 	/// Returns true if the window is visible (mapped).
2686 	final @property bool visible() { return _visible; }
2687 
2688 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2689 	void close() {
2690 		if (!_closed) {
2691 			runInGuiThread( {
2692 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2693 				if (onClosing !is null) onClosing();
2694 				impl.closeWindow();
2695 				_closed = true;
2696 			} );
2697 		}
2698 	}
2699 
2700 	/++
2701 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2702 
2703 		History:
2704 			Overload added on March 7, 2021.
2705 	+/
2706 	void close() shared {
2707 		(cast() this).close();
2708 	}
2709 
2710 	/++
2711 
2712 	+/
2713 	void maximize() {
2714 		version(Windows)
2715 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2716 		else version(X11) {
2717 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2718 
2719 			// also note _NET_WM_STATE_FULLSCREEN
2720 		}
2721 
2722 	}
2723 
2724 	private bool _fullscreen;
2725 	version(Windows)
2726 	private WINDOWPLACEMENT g_wpPrev;
2727 
2728 	/// not fully implemented but planned for a future release
2729 	void fullscreen(bool yes) {
2730 		version(Windows) {
2731 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2732 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2733 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2734 				MONITORINFO mi;
2735 				mi.cbSize = MONITORINFO.sizeof;
2736 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2737 					GetMonitorInfo(MonitorFromWindow(hwnd,
2738 					               MONITOR_DEFAULTTOPRIMARY), &mi)) {
2739 					SetWindowLong(hwnd, GWL_STYLE,
2740 					              dwStyle & ~WS_OVERLAPPEDWINDOW);
2741 					SetWindowPos(hwnd, HWND_TOP,
2742 					             mi.rcMonitor.left, mi.rcMonitor.top,
2743 					             mi.rcMonitor.right - mi.rcMonitor.left,
2744 					             mi.rcMonitor.bottom - mi.rcMonitor.top,
2745 					             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2746 				}
2747 			} else {
2748 				SetWindowLong(hwnd, GWL_STYLE,
2749 				              dwStyle | WS_OVERLAPPEDWINDOW);
2750 				SetWindowPlacement(hwnd, &g_wpPrev);
2751 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2752 				             SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2753 				             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2754 			}
2755 
2756 		} else version(X11) {
2757 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2758 		}
2759 
2760 		_fullscreen = yes;
2761 
2762 	}
2763 
2764 	bool fullscreen() {
2765 		return _fullscreen;
2766 	}
2767 
2768 	/++
2769 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2770 
2771 	+/
2772 	void minimize() {
2773 		version(Windows)
2774 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2775 		//else version(X11)
2776 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2777 	}
2778 
2779 	/// Alias for `hidden = false`
2780 	void show() {
2781 		hidden = false;
2782 	}
2783 
2784 	/// Alias for `hidden = true`
2785 	void hide() {
2786 		hidden = true;
2787 	}
2788 
2789 	/// Hide cursor when it enters the window.
2790 	void hideCursor() {
2791 		version(OSXCocoa) throw new NotYetImplementedException(); else
2792 		if (!_closed) impl.hideCursor();
2793 	}
2794 
2795 	/// Don't hide cursor when it enters the window.
2796 	void showCursor() {
2797 		version(OSXCocoa) throw new NotYetImplementedException(); else
2798 		if (!_closed) impl.showCursor();
2799 	}
2800 
2801 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2802 	 *
2803 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2804 	 * control. Try to think for other approaches before using this function.
2805 	 *
2806 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2807 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2808 	 *       receive "mouse moved here" event.
2809 	 */
2810 	bool warpMouse (int x, int y) {
2811 		version(X11) {
2812 			if (!_closed) { impl.warpMouse(x, y); return true; }
2813 		} else version(Windows) {
2814 			if (!_closed) {
2815 				POINT point;
2816 				point.x = x;
2817 				point.y = y;
2818 				if(ClientToScreen(impl.hwnd, &point)) {
2819 					SetCursorPos(point.x, point.y);
2820 					return true;
2821 				}
2822 			}
2823 		}
2824 		return false;
2825 	}
2826 
2827 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2828 	void sendDummyEvent () {
2829 		version(X11) {
2830 			if (!_closed) { impl.sendDummyEvent(); }
2831 		}
2832 	}
2833 
2834 	/// Set window minimal size.
2835 	void setMinSize (int minwidth, int minheight) {
2836 		version(OSXCocoa) throw new NotYetImplementedException(); else
2837 		if (!_closed) impl.setMinSize(minwidth, minheight);
2838 	}
2839 
2840 	/// Set window maximal size.
2841 	void setMaxSize (int maxwidth, int maxheight) {
2842 		version(OSXCocoa) throw new NotYetImplementedException(); else
2843 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2844 	}
2845 
2846 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2847 	/// Currently only supported on X11.
2848 	void setResizeGranularity (int granx, int grany) {
2849 		version(OSXCocoa) throw new NotYetImplementedException(); else
2850 		if (!_closed) impl.setResizeGranularity(granx, grany);
2851 	}
2852 
2853 	/// Move window.
2854 	void move(int x, int y) {
2855 		version(OSXCocoa) throw new NotYetImplementedException(); else
2856 		if (!_closed) impl.move(x, y);
2857 	}
2858 
2859 	/// ditto
2860 	void move(Point p) {
2861 		version(OSXCocoa) throw new NotYetImplementedException(); else
2862 		if (!_closed) impl.move(p.x, p.y);
2863 	}
2864 
2865 	/++
2866 		Resize window.
2867 
2868 		Note that the width and height of the window are NOT instantly
2869 		updated - it waits for the window manager to approve the resize
2870 		request, which means you must return to the event loop before the
2871 		width and height are actually changed.
2872 	+/
2873 	void resize(int w, int h) {
2874 		if(!_closed && _fullscreen) fullscreen = false;
2875 		version(OSXCocoa) throw new NotYetImplementedException(); else
2876 		if (!_closed) impl.resize(w, h);
2877 	}
2878 
2879 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2880 	void moveResize (int x, int y, int w, int h) {
2881 		if(!_closed && _fullscreen) fullscreen = false;
2882 		version(OSXCocoa) throw new NotYetImplementedException(); else
2883 		if (!_closed) impl.moveResize(x, y, w, h);
2884 	}
2885 
2886 	private bool _hidden;
2887 
2888 	/// Returns true if the window is hidden.
2889 	final @property bool hidden() {
2890 		return _hidden;
2891 	}
2892 
2893 	/// Shows or hides the window based on the bool argument.
2894 	final @property void hidden(bool b) {
2895 		_hidden = b;
2896 		version(Windows) {
2897 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2898 		} else version(X11) {
2899 			if(b)
2900 				//XUnmapWindow(impl.display, impl.window);
2901 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2902 			else
2903 				XMapWindow(impl.display, impl.window);
2904 		} else version(OSXCocoa) {
2905 			// throw new NotYetImplementedException();
2906 		} else version(Emscripten) {
2907 		} else static assert(0);
2908 	}
2909 
2910 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2911 	void opacity(double opacity) @property
2912 	in {
2913 		assert(opacity >= 0 && opacity <= 1);
2914 	} do {
2915 		version (Windows) {
2916 			impl.setOpacity(cast(ubyte)(255 * opacity));
2917 		} else version (X11) {
2918 			impl.setOpacity(cast(uint)(uint.max * opacity));
2919 		} else throw new NotYetImplementedException();
2920 	}
2921 
2922 	/++
2923 		Sets your event handlers, without entering the event loop. Useful if you
2924 		have multiple windows - set the handlers on each window, then only do
2925 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2926 
2927 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2928 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2929 		delegate signatures.
2930 	+/
2931 	void setEventHandlers(T...)(T eventHandlers) {
2932 		// FIXME: add more events
2933 		foreach(handler; eventHandlers) {
2934 			static if(__traits(compiles, handleKeyEvent = handler)) {
2935 				handleKeyEvent = handler;
2936 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2937 				handleCharEvent = handler;
2938 			} else static if(__traits(compiles, handlePulse = handler)) {
2939 				handlePulse = handler;
2940 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2941 				handleMouseEvent = handler;
2942 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2943 		}
2944 	}
2945 
2946 	/++
2947 		The event loop automatically returns when the window is closed
2948 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2949 		pulse timer is created. The event loop will block until an event
2950 		arrives or the pulse timer goes off.
2951 
2952 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2953 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2954 		[handleMouseEvent], based on the signature of delegates you provide.
2955 
2956 		Give one with no parameters to set a timer pulse handler. Give one that
2957 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2958 		and one that takes `dchar` for a char event handler. You can use as many
2959 		or as few handlers as you need for your application.
2960 
2961 		Bugs:
2962 
2963 		$(PITFALL
2964 			You should always have one event loop live for your application.
2965 			If you make two windows in sequence, the second call to eventLoop
2966 			might fail:
2967 
2968 			---
2969 			// don't do this!
2970 			auto window = new SimpleWindow();
2971 			window.eventLoop(0);
2972 
2973 			auto window2 = new SimpleWindow();
2974 			window2.eventLoop(0); // problematic! might crash
2975 			---
2976 
2977 			simpledisplay's current implementation assumes that final cleanup is
2978 			done when the event loop refcount reaches zero. So after the first
2979 			eventLoop returns, when there isn't already another one active, it assumes
2980 			the program will exit soon and cleans up.
2981 
2982 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2983 			it eventually, but in the mean time, there's an easy solution:
2984 
2985 			---
2986 			// do this
2987 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2988 
2989 			auto window = new SimpleWindow();
2990 			window.eventLoop(0);
2991 
2992 			auto window2 = new SimpleWindow();
2993 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2994 			---
2995 
2996 			By adding a top-level reference to the event loop, it ensures the final cleanup
2997 			is not performed until it goes out of scope too, letting the individual window loops
2998 			work without trouble despite the bug.
2999 		)
3000 
3001 		History:
3002 			The overload without `pulseTimeout` was added on December 8, 2021.
3003 
3004 			On December 9, 2021, the default blocking mode (which is now configurable
3005 			because [eventLoopWithBlockingMode] was added) switched from
3006 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
3007 			should almost never be noticeable to you since the typical simpledisplay
3008 			paradigm has been (and I still recommend) to have one `eventLoop` call.
3009 
3010 		See_Also:
3011 			[eventLoopWithBlockingMode]
3012 	+/
3013 	final int eventLoop(T...)(
3014 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3015 		T eventHandlers) /// delegate list like std.concurrency.receive
3016 	{
3017 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
3018 	}
3019 
3020 	/// ditto
3021 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
3022 	{
3023 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
3024 	}
3025 
3026 	/++
3027 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
3028 
3029 		History:
3030 			Added December 8, 2021 (dub v10.5)
3031 
3032 			Previously, this implementation was right inside [eventLoop], but when I wanted
3033 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
3034 			just renamed it instead of adding as an overload. Besides, the new name makes it
3035 			easier to remember the order and avoids ambiguity between two int-like params anyway.
3036 
3037 		See_Also:
3038 			[SimpleWindow.eventLoop], [EventLoop]
3039 
3040 		Bugs:
3041 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
3042 	+/
3043 	final int eventLoopWithBlockingMode(T...)(
3044 		BlockingMode blockingMode, /// when you want this function to block until
3045 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3046 		T eventHandlers) /// delegate list like std.concurrency.receive
3047 	{
3048 		setEventHandlers(eventHandlers);
3049 
3050 		version(with_eventloop) {
3051 			// delegates event loop to my other module
3052 			version(X11)
3053 				XFlush(display);
3054 
3055 			import arsd.eventloop;
3056 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
3057 			scope(exit) clearInterval(handle);
3058 
3059 			loop();
3060 			return 0;
3061 		} else version(OSXCocoa) {
3062 			// FIXME
3063 			if (handlePulse !is null && pulseTimeout != 0) {
3064 				timer = NSTimer.schedule(pulseTimeout*1e-3,
3065 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
3066 					null, true);
3067 			}
3068 
3069 			view.setNeedsDisplay(true);
3070 
3071 			NSApp.run();
3072 			return 0;
3073 		} else {
3074 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
3075 
3076 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
3077 				return 0;
3078 
3079 			return el.run(
3080 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
3081 					null :
3082 					&this.notClosed
3083 			);
3084 		}
3085 	}
3086 
3087 	/++
3088 		This lets you draw on the window (or its backing buffer) using basic
3089 		2D primitives.
3090 
3091 		Be sure to call this in a limited scope because your changes will not
3092 		actually appear on the window until ScreenPainter's destructor runs.
3093 
3094 		Returns: an instance of [ScreenPainter], which has the drawing methods
3095 		on it to draw on this window.
3096 
3097 		Params:
3098 			manualInvalidations = if you set this to true, you will need to
3099 			set the invalid rectangle on the painter yourself. If false, it
3100 			assumes the whole window has been redrawn each time you draw.
3101 
3102 			Only invalidated rectangles are blitted back to the window when
3103 			the destructor runs. Doing this yourself can reduce flickering
3104 			of child windows.
3105 
3106 		History:
3107 			The `manualInvalidations` parameter overload was added on
3108 			December 30, 2021 (dub v10.5)
3109 	+/
3110 	ScreenPainter draw() {
3111 		return draw(false);
3112 	}
3113 	/// ditto
3114 	ScreenPainter draw(bool manualInvalidations) {
3115 		return impl.getPainter(manualInvalidations);
3116 	}
3117 
3118 	// This is here to implement the interface we use for various native handlers.
3119 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
3120 
3121 	// maps native window handles to SimpleWindow instances, if there are any
3122 	// you shouldn't need this, but it is public in case you do in a native event handler or something
3123 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
3124 	version(OSXCocoa)
3125 	public __gshared SimpleWindow[void*] nativeMapping;
3126 	else
3127 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
3128 
3129 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
3130 	private int _virtualWidth;
3131 	private int _virtualHeight;
3132 
3133 	/// Width of the window's drawable client area, in pixels.
3134 	@scriptable
3135 	final @property int width() const pure nothrow @safe @nogc {
3136 		if(resizability == Resizability.automaticallyScaleIfPossible)
3137 			return _virtualWidth;
3138 		else
3139 			return _width;
3140 	}
3141 
3142 	/// Height of the window's drawable client area, in pixels.
3143 	@scriptable
3144 	final @property int height() const pure nothrow @safe @nogc {
3145 		if(resizability == Resizability.automaticallyScaleIfPossible)
3146 			return _virtualHeight;
3147 		else
3148 			return _height;
3149 	}
3150 
3151 	/++
3152 		Returns the actual size of the window, bypassing the logical
3153 		illusions of [Resizability.automaticallyScaleIfPossible].
3154 
3155 		History:
3156 			Added November 11, 2022 (dub v10.10)
3157 	+/
3158 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
3159 		return Size(_width, _height);
3160 	}
3161 
3162 
3163 	private int _width;
3164 	private int _height;
3165 
3166 	// HACK: making the best of some copy constructor woes with refcounting
3167 	private ScreenPainterImplementation* activeScreenPainter_;
3168 
3169 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
3170 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
3171 
3172 	private OpenGlOptions openglMode;
3173 	private Resizability resizability;
3174 	private WindowTypes windowType;
3175 	private int customizationFlags;
3176 
3177 	/// `true` if OpenGL was initialized for this window.
3178 	@property bool isOpenGL () const pure nothrow @safe @nogc {
3179 		version(without_opengl)
3180 			return false;
3181 		else
3182 			return (openglMode == OpenGlOptions.yes);
3183 	}
3184 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
3185 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
3186 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
3187 
3188 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3189 	/// to call this, as it's not recommended to share window between threads.
3190 	void mtLock () {
3191 		version(X11) {
3192 			XLockDisplay(this.display);
3193 		}
3194 	}
3195 
3196 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3197 	/// to call this, as it's not recommended to share window between threads.
3198 	void mtUnlock () {
3199 		version(X11) {
3200 			XUnlockDisplay(this.display);
3201 		}
3202 	}
3203 
3204 	/// Emit a beep to get user's attention.
3205 	void beep () {
3206 		version(X11) {
3207 			XBell(this.display, 100);
3208 		} else version(Windows) {
3209 			MessageBeep(0xFFFFFFFF);
3210 		}
3211 	}
3212 
3213 
3214 
3215 	version(without_opengl) {} else {
3216 
3217 		/// 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`.
3218 		void delegate() redrawOpenGlScene;
3219 
3220 		/// This will allow you to change OpenGL vsync state.
3221 		final @property void vsync (bool wait) {
3222 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3223 			version(X11) {
3224 				setAsCurrentOpenGlContext();
3225 				glxSetVSync(display, impl.window, wait);
3226 			} else version(Windows) {
3227 				setAsCurrentOpenGlContext();
3228 				wglSetVSync(wait);
3229 			}
3230 		}
3231 
3232 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3233 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3234 		/// enough without waiting 'em to finish their frame business.
3235 		bool useGLFinish = true;
3236 
3237 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3238 		/// 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.
3239 		void redrawOpenGlSceneNow() {
3240 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3241 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3242 			if(redrawOpenGlScene is null)
3243 				return;
3244 
3245 			this.mtLock();
3246 			scope(exit) this.mtUnlock();
3247 
3248 			this.setAsCurrentOpenGlContext();
3249 
3250 			redrawOpenGlScene();
3251 
3252 			this.swapOpenGlBuffers();
3253 			// 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.
3254 			if (useGLFinish) glFinish();
3255 		}
3256 
3257 		private bool redrawOpenGlSceneSoonSet = false;
3258 		private static class RedrawOpenGlSceneEvent {
3259 			SimpleWindow w;
3260 			this(SimpleWindow w) { this.w = w; }
3261 		}
3262 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3263 		/++
3264 			Queues an opengl redraw as soon as the other pending events are cleared.
3265 		+/
3266 		void redrawOpenGlSceneSoon() {
3267 			if(redrawOpenGlScene is null)
3268 				return;
3269 
3270 			if(!redrawOpenGlSceneSoonSet) {
3271 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3272 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3273 				redrawOpenGlSceneSoonSet = true;
3274 			}
3275 			this.postEvent(redrawOpenGlSceneEvent, true);
3276 		}
3277 
3278 
3279 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3280 		void setAsCurrentOpenGlContext() {
3281 			assert(openglMode == OpenGlOptions.yes);
3282 			version(X11) {
3283 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3284 					throw new Exception("glXMakeCurrent");
3285 			} else version(Windows) {
3286 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3287 				if (!wglMakeCurrent(ghDC, ghRC))
3288 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3289 			}
3290 		}
3291 
3292 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3293 		/// This doesn't throw, returning success flag instead.
3294 		bool setAsCurrentOpenGlContextNT() nothrow {
3295 			assert(openglMode == OpenGlOptions.yes);
3296 			version(X11) {
3297 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3298 			} else version(Windows) {
3299 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3300 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3301 			}
3302 		}
3303 
3304 		/// 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.
3305 		/// This doesn't throw, returning success flag instead.
3306 		bool releaseCurrentOpenGlContext() nothrow {
3307 			assert(openglMode == OpenGlOptions.yes);
3308 			version(X11) {
3309 				return (glXMakeCurrent(display, 0, null) != 0);
3310 			} else version(Windows) {
3311 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3312 				return wglMakeCurrent(ghDC, null) ? true : false;
3313 			}
3314 		}
3315 
3316 		/++
3317 			simpledisplay always uses double buffering, usually automatically. This
3318 			manually swaps the OpenGL buffers. You should only use this if you are NOT
3319 			using the [redrawOpenGlScene] delegate.
3320 
3321 
3322 			You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it
3323 			for you after calling your `redrawOpenGlScene`. Please note that once you swap
3324 			buffers, the contents become undefined - the implementation, in the OpenGL driver
3325 			or the desktop compositor, may not actually just swap two buffers. The back buffer's
3326 			contents are $(B undefined) after calling this function.
3327 
3328 			See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers
3329 			and https://linux.die.net/man/3/glxswapbuffers
3330 
3331 			Remember that this may throw an exception, which you can catch in a multithreaded
3332 			application to keep your thread from dying from an unhandled exception.
3333 		+/
3334 		void swapOpenGlBuffers() {
3335 			assert(openglMode == OpenGlOptions.yes);
3336 			version(X11) {
3337 				if (!this._visible) return; // no need to do this if window is invisible
3338 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3339 				glXSwapBuffers(display, impl.window);
3340 			} else version(Windows) {
3341 				SwapBuffers(ghDC);
3342 			}
3343 		}
3344 	}
3345 
3346 	/++
3347 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3348 
3349 
3350 		---
3351 			auto window = new SimpleWindow(100, 100, "First title");
3352 			window.title = "A new title";
3353 		---
3354 
3355 		You may call this function at any time.
3356 	+/
3357 	@property void title(string title) {
3358 		_title = title;
3359 		version(OSXCocoa) throw new NotYetImplementedException(); else
3360 		impl.setTitle(title);
3361 	}
3362 
3363 	private string _title;
3364 
3365 	/// Gets the title
3366 	@property string title() {
3367 		if(_title is null)
3368 			_title = getRealTitle();
3369 		return _title;
3370 	}
3371 
3372 	/++
3373 		Get the title as set by the window manager.
3374 		May not match what you attempted to set.
3375 	+/
3376 	string getRealTitle() {
3377 		static if(is(typeof(impl.getTitle())))
3378 			return impl.getTitle();
3379 		else
3380 			return null;
3381 	}
3382 
3383 	// don't use this generally it is not yet really released
3384 	version(X11)
3385 	@property Image secret_icon() {
3386 		return secret_icon_inner;
3387 	}
3388 	private Image secret_icon_inner;
3389 
3390 
3391 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3392 	@property void icon(MemoryImage icon) {
3393 		if(icon is null)
3394 			return;
3395 		auto tci = icon.getAsTrueColorImage();
3396 		version(Windows) {
3397 			winIcon = new WindowsIcon(icon);
3398 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3399 		} else version(X11) {
3400 			secret_icon_inner = Image.fromMemoryImage(icon);
3401 			// FIXME: ensure this is correct
3402 			auto display = XDisplayConnection.get;
3403 			arch_ulong[] buffer;
3404 			buffer ~= icon.width;
3405 			buffer ~= icon.height;
3406 			foreach(c; tci.imageData.colors) {
3407 				arch_ulong b;
3408 				b |= c.a << 24;
3409 				b |= c.r << 16;
3410 				b |= c.g << 8;
3411 				b |= c.b;
3412 				buffer ~= b;
3413 			}
3414 
3415 			XChangeProperty(
3416 				display,
3417 				impl.window,
3418 				GetAtom!("_NET_WM_ICON", true)(display),
3419 				GetAtom!"CARDINAL"(display),
3420 				32 /* bits */,
3421 				0 /*PropModeReplace*/,
3422 				buffer.ptr,
3423 				cast(int) buffer.length);
3424 		} else version(OSXCocoa) {
3425 			throw new NotYetImplementedException();
3426 		} else version(Emscripten) {
3427 			throw new NotYetImplementedException();
3428 		} else static assert(0);
3429 	}
3430 
3431 	version(Windows)
3432 		private WindowsIcon winIcon;
3433 
3434 	bool _suppressDestruction;
3435 
3436 	~this() {
3437 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3438 		if(_suppressDestruction)
3439 			return;
3440 		impl.dispose();
3441 	}
3442 
3443 	private bool _closed;
3444 
3445 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3446 	/*
3447 	ScreenPainter drawTransiently() {
3448 		return impl.getPainter();
3449 	}
3450 	*/
3451 
3452 	/// Draws an image on the window. This is meant to provide quick look
3453 	/// of a static image generated elsewhere.
3454 	@property void image(Image i) {
3455 	/+
3456 		version(Windows) {
3457 			BITMAP bm;
3458 			HDC hdc = GetDC(hwnd);
3459 			HDC hdcMem = CreateCompatibleDC(hdc);
3460 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3461 
3462 			GetObject(i.handle, bm.sizeof, &bm);
3463 
3464 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3465 
3466 			SelectObject(hdcMem, hbmOld);
3467 			DeleteDC(hdcMem);
3468 			ReleaseDC(hwnd, hdc);
3469 
3470 			/*
3471 			RECT r;
3472 			r.right = i.width;
3473 			r.bottom = i.height;
3474 			InvalidateRect(hwnd, &r, false);
3475 			*/
3476 		} else
3477 		version(X11) {
3478 			if(!destroyed) {
3479 				if(i.usingXshm)
3480 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3481 				else
3482 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3483 			}
3484 		} else
3485 		version(OSXCocoa) {
3486 			draw().drawImage(Point(0, 0), i);
3487 			setNeedsDisplay(view, true);
3488 		} else static assert(0);
3489 	+/
3490 		auto painter = this.draw;
3491 		painter.drawImage(Point(0, 0), i);
3492 	}
3493 
3494 	/++
3495 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3496 
3497 		---
3498 		window.cursor = GenericCursor.Help;
3499 		// now the window mouse cursor is set to a generic help
3500 		---
3501 
3502 	+/
3503 	@property void cursor(MouseCursor cursor) {
3504 		version(OSXCocoa)
3505 			{} // featureNotImplemented();
3506 		else
3507 		if(this.impl.curHidden <= 0) {
3508 			static if(UsingSimpledisplayX11) {
3509 				auto ch = cursor.cursorHandle;
3510 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3511 			} else version(Windows) {
3512 				auto ch = cursor.cursorHandle;
3513 				impl.currentCursor = ch;
3514 				SetCursor(ch); // redraw without waiting for mouse movement to update
3515 			} else featureNotImplemented();
3516 		}
3517 
3518 	}
3519 
3520 	/// What follows are the event handlers. These are set automatically
3521 	/// by the eventLoop function, but are still public so you can change
3522 	/// them later. wasPressed == true means key down. false == key up.
3523 
3524 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3525 	void delegate(KeyEvent ke) handleKeyEvent;
3526 
3527 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3528 	void delegate(dchar c) handleCharEvent;
3529 
3530 	/// Handles a timer pulse. Settable through setEventHandlers.
3531 	void delegate() handlePulse;
3532 
3533 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3534 	void delegate(bool) onFocusChange;
3535 
3536 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3537 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3538 	void delegate() onClosing;
3539 
3540 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3541 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3542 	 * last minute cleanup. */
3543 	void delegate() onDestroyed;
3544 
3545 	static if (UsingSimpledisplayX11)
3546 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3547 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3548 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3549 	 *
3550 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3551 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3552 
3553 	//version(Windows)
3554 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3555 
3556 	private {
3557 		int lastMouseX = int.min;
3558 		int lastMouseY = int.min;
3559 		void mdx(ref MouseEvent ev) {
3560 			if(lastMouseX == int.min || lastMouseY == int.min) {
3561 				ev.dx = 0;
3562 				ev.dy = 0;
3563 			} else {
3564 				ev.dx = ev.x - lastMouseX;
3565 				ev.dy = ev.y - lastMouseY;
3566 			}
3567 
3568 			lastMouseX = ev.x;
3569 			lastMouseY = ev.y;
3570 		}
3571 	}
3572 
3573 	/// Mouse event handler. Settable through setEventHandlers.
3574 	void delegate(MouseEvent) handleMouseEvent;
3575 
3576 	/// use to redraw child widgets if you use system apis to add stuff
3577 	void delegate() paintingFinished;
3578 
3579 	void delegate() paintingFinishedDg() {
3580 		return paintingFinished;
3581 	}
3582 
3583 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3584 	/// for this to ever happen.
3585 	void delegate(int width, int height) windowResized;
3586 
3587 	/++
3588 		Platform specific - handle any native message this window gets.
3589 
3590 		Note: this is called *in addition to* other event handlers, unless you either:
3591 
3592 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3593 
3594 		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.
3595 
3596 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3597 
3598 		On X, it takes the form of `int delegate(XEvent)`.
3599 
3600 		History:
3601 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3602 
3603 			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.
3604 	+/
3605 	NativeEventHandler handleNativeEvent_;
3606 
3607 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3608 		return handleNativeEvent_;
3609 	}
3610 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3611 		handleNativeEvent_ = neh;
3612 	}
3613 
3614 	version(Windows)
3615 	// compatibility shim with the old deprecated way
3616 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3617 	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) {
3618 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3619 			auto ret = dg(h, m, w, l);
3620 			if(ret == 0)
3621 				r = 1;
3622 			return ret;
3623 		};
3624 	}
3625 
3626 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3627 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3628 	/// this instead and it will work the same way.
3629 	__gshared NativeEventHandler handleNativeGlobalEvent;
3630 
3631 //  private:
3632 	/// The native implementation is available, but you shouldn't use it unless you are
3633 	/// familiar with the underlying operating system, don't mind depending on it, and
3634 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3635 	/// do what you need to do with handleNativeEvent instead.
3636 	///
3637 	/// This is likely to eventually change to be just a struct holding platform-specific
3638 	/// handles instead of a template mixin at some point because I'm not happy with the
3639 	/// code duplication here (ironically).
3640 	mixin NativeSimpleWindowImplementation!() impl;
3641 
3642 	/**
3643 		This is in-process one-way (from anything to window) event sending mechanics.
3644 		It is thread-safe, so it can be used in multi-threaded applications to send,
3645 		for example, "wake up and repaint" events when thread completed some operation.
3646 		This will allow to avoid using timer pulse to check events with synchronization,
3647 		'cause event handler will be called in UI thread. You can stop guessing which
3648 		pulse frequency will be enough for your app.
3649 		Note that events handlers may be called in arbitrary order, i.e. last registered
3650 		handler can be called first, and vice versa.
3651 	*/
3652 public:
3653 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3654 	 * "spamming" window with events it can't cope with.
3655 	 * It is safe to call this from non-UI threads.
3656 	 */
3657 	@property bool eventQueueEmpty() () {
3658 		synchronized(this) {
3659 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3660 		}
3661 		return true;
3662 	}
3663 
3664 	/** Does our custom event queue contains at least one with the given type?
3665 	 * Can be used in simple cases to prevent "spamming" window with events
3666 	 * it can't cope with.
3667 	 * It is safe to call this from non-UI threads.
3668 	 */
3669 	@property bool eventQueued(ET:Object) () {
3670 		synchronized(this) {
3671 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3672 				if (!o.doProcess) {
3673 					if (cast(ET)(o.evt)) return true;
3674 				}
3675 			}
3676 		}
3677 		return false;
3678 	}
3679 
3680 	/++
3681 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3682 
3683 		History:
3684 			Added May 12, 2021
3685 	+/
3686 	void delegate(Exception e) nothrow eventUncaughtException;
3687 
3688 	/** Add listener for custom event. Can be used like this:
3689 	 *
3690 	 * ---------------------
3691 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3692 	 *   ...
3693 	 *   win.removeEventListener(eid);
3694 	 * ---------------------
3695 	 *
3696 	 * Returns: 0 on failure (should never happen, so ignore it)
3697 	 *
3698 	 * $(WARNING Don't use this method in object destructors!)
3699 	 *
3700 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3701 	 *           'cause if event handler id counter will overflow, you won't be able
3702 	 *           to register any more events.)
3703 	 */
3704 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3705 		if (dg is null) return 0; // ignore empty handlers
3706 		synchronized(this) {
3707 			//FIXME: abort on overflow?
3708 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3709 			EventHandlerEntry e;
3710 			e.dg = delegate (Object o) {
3711 				if (auto co = cast(ET)o) {
3712 					try {
3713 						dg(co);
3714 					} catch (Exception e) {
3715 						// sorry!
3716 						if(eventUncaughtException)
3717 							eventUncaughtException(e);
3718 					}
3719 					return true;
3720 				}
3721 				return false;
3722 			};
3723 			e.id = lastUsedHandlerId;
3724 			auto optr = eventHandlers.ptr;
3725 			eventHandlers ~= e;
3726 			if (eventHandlers.ptr !is optr) {
3727 				import core.memory : GC;
3728 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3729 			}
3730 			return lastUsedHandlerId;
3731 		}
3732 	}
3733 
3734 	/// Remove event listener. It is safe to pass invalid event id here.
3735 	/// $(WARNING Don't use this method in object destructors!)
3736 	void removeEventListener() (uint id) {
3737 		if (id == 0 || id > lastUsedHandlerId) return;
3738 		synchronized(this) {
3739 			foreach (immutable idx; 0..eventHandlers.length) {
3740 				if (eventHandlers[idx].id == id) {
3741 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3742 					eventHandlers[$-1].dg = null;
3743 					eventHandlers.length -= 1;
3744 					eventHandlers.assumeSafeAppend;
3745 					return;
3746 				}
3747 			}
3748 		}
3749 	}
3750 
3751 	/// Post event to queue. It is safe to call this from non-UI threads.
3752 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3753 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3754 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3755 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3756 		if (this.closed) return false; // closed windows can't handle events
3757 
3758 		// remove all events of type `ET`
3759 		void removeAllET () {
3760 			uint eidx = 0, ec = eventQueueUsed;
3761 			auto eptr = eventQueue.ptr;
3762 			while (eidx < ec) {
3763 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3764 				if (cast(ET)eptr.evt !is null) {
3765 					// i found her!
3766 					if (inCustomEventProcessor) {
3767 						// if we're in custom event processing loop, processor will clear it for us
3768 						eptr.evt = null;
3769 						++eidx;
3770 						++eptr;
3771 					} else {
3772 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3773 						ec = --eventQueueUsed;
3774 						// clear last event (it is already copied)
3775 						eventQueue.ptr[ec].evt = null;
3776 					}
3777 				} else {
3778 					++eidx;
3779 					++eptr;
3780 				}
3781 			}
3782 		}
3783 
3784 		if (evt is null) {
3785 			if (replace) { synchronized(this) removeAllET(); }
3786 			// ignore empty events, they can't be handled anyway
3787 			return false;
3788 		}
3789 
3790 		// add events even if no event FD/event object created yet
3791 		synchronized(this) {
3792 			if (replace) removeAllET();
3793 			if (eventQueueUsed == uint.max) return false; // just in case
3794 			if (eventQueueUsed < eventQueue.length) {
3795 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3796 			} else {
3797 				if (eventQueue.capacity == eventQueue.length) {
3798 					// need to reallocate; do a trick to ensure that old array is cleared
3799 					auto oarr = eventQueue;
3800 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3801 					// just in case, do yet another check
3802 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3803 					import core.memory : GC;
3804 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3805 				} else {
3806 					auto optr = eventQueue.ptr;
3807 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3808 					assert(eventQueue.ptr is optr);
3809 				}
3810 				++eventQueueUsed;
3811 				assert(eventQueueUsed == eventQueue.length);
3812 			}
3813 			if (!eventWakeUp()) {
3814 				// can't wake up event processor, so there is no reason to keep the event
3815 				assert(eventQueueUsed > 0);
3816 				eventQueue[--eventQueueUsed].evt = null;
3817 				return false;
3818 			}
3819 			return true;
3820 		}
3821 	}
3822 
3823 	/// Post event to queue. It is safe to call this from non-UI threads.
3824 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3825 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3826 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3827 		return postTimeout!ET(evt, 0, replace);
3828 	}
3829 
3830 private:
3831 	private import core.time : MonoTime;
3832 
3833 	version(Posix) {
3834 		__gshared int customEventFDRead = -1;
3835 		__gshared int customEventFDWrite = -1;
3836 		__gshared int customSignalFD = -1;
3837 	} else version(Windows) {
3838 		__gshared HANDLE customEventH = null;
3839 	}
3840 
3841 	// wake up event processor
3842 	static bool eventWakeUp () {
3843 		version(X11) {
3844 			import core.sys.posix.unistd : write;
3845 			ulong n = 1;
3846 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3847 			return true;
3848 		} else version(Windows) {
3849 			if (customEventH !is null) SetEvent(customEventH);
3850 			return true;
3851 		} else version(OSXCocoa) {
3852 			if(globalAppDelegate)
3853 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3854 			return true;
3855 		} else {
3856 			// not implemented for other OSes
3857 			return false;
3858 		}
3859 	}
3860 
3861 	static struct QueuedEvent {
3862 		Object evt;
3863 		bool timed = false;
3864 		MonoTime hittime = MonoTime.zero;
3865 		bool doProcess = false; // process event at the current iteration (internal flag)
3866 
3867 		this (Object aevt, uint toutmsecs) {
3868 			evt = aevt;
3869 			if (toutmsecs > 0) {
3870 				import core.time : msecs;
3871 				timed = true;
3872 				hittime = MonoTime.currTime+toutmsecs.msecs;
3873 			}
3874 		}
3875 	}
3876 
3877 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3878 	static struct EventHandlerEntry {
3879 		CustomEventHandler dg;
3880 		uint id;
3881 	}
3882 
3883 	uint lastUsedHandlerId;
3884 	EventHandlerEntry[] eventHandlers;
3885 	QueuedEvent[] eventQueue = null;
3886 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3887 	bool inCustomEventProcessor = false; // required to properly remove events
3888 
3889 	// process queued events and call custom event handlers
3890 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3891 	void processCustomEvents () @system {
3892 		bool hasSomethingToDo = false;
3893 		uint ecount;
3894 		bool ocep;
3895 		synchronized(this) {
3896 			ocep = inCustomEventProcessor;
3897 			inCustomEventProcessor = true;
3898 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3899 			auto ctt = MonoTime.currTime;
3900 			bool hasEmpty = false;
3901 			// mark events to process (this is required for `eventQueued()`)
3902 			foreach (ref qe; eventQueue[0..ecount]) {
3903 				if (qe.evt is null) { hasEmpty = true; continue; }
3904 				if (qe.timed) {
3905 					qe.doProcess = (qe.hittime <= ctt);
3906 				} else {
3907 					qe.doProcess = true;
3908 				}
3909 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3910 			}
3911 			if (!hasSomethingToDo) {
3912 				// remove empty events
3913 				if (hasEmpty) {
3914 					uint eidx = 0, ec = eventQueueUsed;
3915 					auto eptr = eventQueue.ptr;
3916 					while (eidx < ec) {
3917 						if (eptr.evt is null) {
3918 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3919 							ec = --eventQueueUsed;
3920 							eventQueue.ptr[ec].evt = null; // make GC life easier
3921 						} else {
3922 							++eidx;
3923 							++eptr;
3924 						}
3925 					}
3926 				}
3927 				inCustomEventProcessor = ocep;
3928 				return;
3929 			}
3930 		}
3931 		// process marked events
3932 		uint efree = 0; // non-processed events will be put at this index
3933 		EventHandlerEntry[] eh;
3934 		Object evt;
3935 		foreach (immutable eidx; 0..ecount) {
3936 			synchronized(this) {
3937 				if (!eventQueue[eidx].doProcess) {
3938 					// skip this event
3939 					assert(efree <= eidx);
3940 					if (efree != eidx) {
3941 						// copy this event to queue start
3942 						eventQueue[efree] = eventQueue[eidx];
3943 						eventQueue[eidx].evt = null; // just in case
3944 					}
3945 					++efree;
3946 					continue;
3947 				}
3948 				evt = eventQueue[eidx].evt;
3949 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3950 				if (evt is null) continue; // just in case
3951 				// try all handlers; this can be slow, but meh...
3952 				eh = eventHandlers;
3953 			}
3954 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3955 			evt = null;
3956 			eh = null;
3957 		}
3958 		synchronized(this) {
3959 			// move all unprocessed events to queue top; efree holds first "free index"
3960 			foreach (immutable eidx; ecount..eventQueueUsed) {
3961 				assert(efree <= eidx);
3962 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3963 				++efree;
3964 			}
3965 			eventQueueUsed = efree;
3966 			// wake up event processor on next event loop iteration if we have more queued events
3967 			// also, remove empty events
3968 			bool awaken = false;
3969 			uint eidx = 0, ec = eventQueueUsed;
3970 			auto eptr = eventQueue.ptr;
3971 			while (eidx < ec) {
3972 				if (eptr.evt is null) {
3973 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3974 					ec = --eventQueueUsed;
3975 					eventQueue.ptr[ec].evt = null; // make GC life easier
3976 				} else {
3977 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3978 					++eidx;
3979 					++eptr;
3980 				}
3981 			}
3982 			inCustomEventProcessor = ocep;
3983 		}
3984 	}
3985 
3986 	// for all windows in nativeMapping
3987 	package static void processAllCustomEvents () @system {
3988 
3989 		cleanupQueue.process();
3990 
3991 		justCommunication.processCustomEvents();
3992 
3993 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3994 			if (sw is null || sw.closed) continue;
3995 			sw.processCustomEvents();
3996 		}
3997 
3998 		runPendingRunInGuiThreadDelegates();
3999 	}
4000 
4001 	// 0: infinite (i.e. no scheduled events in queue)
4002 	uint eventQueueTimeoutMSecs () {
4003 		synchronized(this) {
4004 			if (eventQueueUsed == 0) return 0;
4005 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
4006 			uint res = int.max;
4007 			auto ctt = MonoTime.currTime;
4008 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
4009 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
4010 				if (qe.doProcess) continue; // just in case
4011 				if (!qe.timed) return 1; // minimal
4012 				if (qe.hittime <= ctt) return 1; // minimal
4013 				auto tms = (qe.hittime-ctt).total!"msecs";
4014 				if (tms < 1) tms = 1; // safety net
4015 				if (tms >= int.max) tms = int.max-1; // and another safety net
4016 				if (res > tms) res = cast(uint)tms;
4017 			}
4018 			return (res >= int.max ? 0 : res);
4019 		}
4020 	}
4021 
4022 	// for all windows in nativeMapping
4023 	static uint eventAllQueueTimeoutMSecs () {
4024 		uint res = uint.max;
4025 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
4026 			if (sw is null || sw.closed) continue;
4027 			uint to = sw.eventQueueTimeoutMSecs();
4028 			if (to && to < res) {
4029 				res = to;
4030 				if (to == 1) break; // can't have less than this
4031 			}
4032 		}
4033 		return (res >= int.max ? 0 : res);
4034 	}
4035 
4036 	version(X11) {
4037 		ResizeEvent pendingResizeEvent;
4038 	}
4039 
4040 	/++
4041 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
4042 
4043 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
4044 		worth so you can disable it by setting this to `true`.
4045 
4046 		History:
4047 			Added November 13, 2022.
4048 	+/
4049 	public bool suppressAutoOpenglViewport = false;
4050 	private void updateOpenglViewportIfNeeded(int width, int height) {
4051 		if(suppressAutoOpenglViewport) return;
4052 
4053 		version(without_opengl) {} else
4054 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
4055 		// writeln(width, " ", height);
4056 			setAsCurrentOpenGlContextNT();
4057 			glViewport(0, 0, width, height);
4058 		}
4059 	}
4060 
4061 	// TODO: Implement on non-Windows platforms (where available).
4062 	private CornerStyle _fauxCornerStyle = CornerStyle.automatic;
4063 
4064 	/++
4065 		Style of the window's corners
4066 
4067 		$(WARNING
4068 			Currently only implemented on Windows targets.
4069 			Has no visual effect elsewhere.
4070 
4071 			Windows: Requires Windows 11 or later.
4072 		)
4073 
4074 		History:
4075 			Added September 09, 2024.
4076 	 +/
4077 	public CornerStyle cornerStyle() @trusted {
4078 		version(Windows) {
4079 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4080 			const apiResult = DwmGetWindowAttribute(
4081 				this.hwnd,
4082 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4083 				&dwmCorner,
4084 				typeof(dwmCorner).sizeof
4085 			);
4086 
4087 			if (apiResult != S_OK) {
4088 				// Unsupported?
4089 				if (apiResult == E_INVALIDARG) {
4090 					// Feature unsupported; Windows version probably too old.
4091 					// Requires Windows 11 (build 22000) or later.
4092 					return _fauxCornerStyle;
4093 				}
4094 
4095 				throw new WindowsApiException("DwmGetWindowAttribute", apiResult);
4096 			}
4097 
4098 			CornerStyle corner;
4099 			if (!dwmCorner.fromDWM(corner)) {
4100 				throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner);
4101 			}
4102 			return corner;
4103 		} else {
4104 			return _fauxCornerStyle;
4105 		}
4106 	}
4107 
4108 	/// ditto
4109 	public void cornerStyle(const CornerStyle corner) @trusted {
4110 		version(Windows) {
4111 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4112 			if (!corner.toDWM(dwmCorner)) {
4113 				assert(false, "This should have been impossible because of a final switch.");
4114 			}
4115 
4116 			const apiResult = DwmSetWindowAttribute(
4117 				this.hwnd,
4118 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4119 				&dwmCorner,
4120 				typeof(dwmCorner).sizeof
4121 			);
4122 
4123 			if (apiResult != S_OK) {
4124 				// Unsupported?
4125 				if (apiResult == E_INVALIDARG) {
4126 					// Feature unsupported; Windows version probably too old.
4127 					// Requires Windows 11 (build 22000) or later.
4128 					_fauxCornerStyle = corner;
4129 					return;
4130 				}
4131 
4132 				throw new WindowsApiException("DwmSetWindowAttribute", apiResult);
4133 			}
4134 		} else {
4135 			_fauxCornerStyle = corner;
4136 		}
4137 	}
4138 }
4139 
4140 version(OSXCocoa)
4141 	enum NSWindow NullWindow = null;
4142 else
4143 	enum NullWindow = NativeWindowHandle.init;
4144 
4145 /++
4146 	Magic pseudo-window for just posting events to a global queue.
4147 
4148 	Not entirely supported, I might delete it at any time.
4149 
4150 	Added Nov 5, 2021.
4151 +/
4152 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
4153 
4154 /* Drag and drop support { */
4155 version(X11) {
4156 
4157 } else version(Windows) {
4158 	import core.sys.windows.uuid;
4159 	import core.sys.windows.ole2;
4160 	import core.sys.windows.oleidl;
4161 	import core.sys.windows.objidl;
4162 	import core.sys.windows.wtypes;
4163 
4164 	pragma(lib, "ole32");
4165 	void initDnd() {
4166 		auto err = OleInitialize(null);
4167 		if(err != S_OK && err != S_FALSE)
4168 			throw new Exception("init");//err);
4169 	}
4170 }
4171 /* } End drag and drop support */
4172 
4173 
4174 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
4175 /// See [GenericCursor].
4176 class MouseCursor {
4177 	int osId;
4178 	bool isStockCursor;
4179 	private this(int osId) {
4180 		this.osId = osId;
4181 		this.isStockCursor = true;
4182 	}
4183 
4184 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
4185 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
4186 
4187 	version(Windows) {
4188 		HCURSOR cursor_;
4189 		HCURSOR cursorHandle() {
4190 			if(cursor_ is null)
4191 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
4192 			return cursor_;
4193 		}
4194 
4195 	} else static if(UsingSimpledisplayX11) {
4196 		Cursor cursor_ = None;
4197 		int xDisplaySequence;
4198 
4199 		Cursor cursorHandle() {
4200 			if(this.osId == None)
4201 				return None;
4202 
4203 			// we need to reload if we on a new X connection
4204 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
4205 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
4206 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
4207 			}
4208 			return cursor_;
4209 		}
4210 	}
4211 }
4212 
4213 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
4214 // https://tronche.com/gui/x/xlib/appendix/b/
4215 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
4216 /// 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.
4217 enum GenericCursorType {
4218 	Default, /// The default arrow pointer.
4219 	Wait, /// A cursor indicating something is loading and the user must wait.
4220 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
4221 	Help, /// A cursor indicating the user can get help about the pointer location.
4222 	Cross, /// A crosshair.
4223 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
4224 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
4225 	UpArrow, /// An arrow pointing straight up.
4226 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
4227 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
4228 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
4229 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
4230 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
4231 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
4232 
4233 }
4234 
4235 /*
4236 	X_plus == css cell == Windows ?
4237 */
4238 
4239 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
4240 static struct GenericCursor {
4241 	static:
4242 	///
4243 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
4244 		static MouseCursor mc;
4245 
4246 		auto type = __traits(getMember, GenericCursorType, str);
4247 
4248 		if(mc is null) {
4249 
4250 			version(Windows) {
4251 				int osId;
4252 				final switch(type) {
4253 					case GenericCursorType.Default: osId = IDC_ARROW; break;
4254 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
4255 					case GenericCursorType.Hand: osId = IDC_HAND; break;
4256 					case GenericCursorType.Help: osId = IDC_HELP; break;
4257 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
4258 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
4259 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
4260 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
4261 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
4262 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
4263 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
4264 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
4265 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
4266 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
4267 				}
4268 			} else static if(UsingSimpledisplayX11) {
4269 				int osId;
4270 				final switch(type) {
4271 					case GenericCursorType.Default: osId = None; break;
4272 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
4273 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
4274 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
4275 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
4276 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
4277 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
4278 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
4279 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
4280 
4281 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
4282 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
4283 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
4284 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
4285 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
4286 				}
4287 
4288 			} else {
4289 				int osId;
4290 				// featureNotImplemented();
4291 			}
4292 
4293 			mc = new MouseCursor(osId);
4294 		}
4295 		return mc;
4296 	}
4297 }
4298 
4299 
4300 /++
4301 	If you want to get more control over the event loop, you can use this.
4302 
4303 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4304 	to `EventLoop.get.run`.
4305 +/
4306 struct EventLoop {
4307 	@disable this();
4308 
4309 	/// Gets a reference to an existing event loop
4310 	static EventLoop get() {
4311 		return EventLoop(0, null);
4312 	}
4313 
4314 	static void quitApplication() {
4315 		version(use_arsd_core) {
4316 			import arsd.core;
4317 			ICoreEventLoop.exitApplication();
4318 		}
4319 		EventLoop.get().exit();
4320 	}
4321 
4322 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4323 
4324 	/// Construct an application-global event loop for yourself
4325 	/// See_Also: [SimpleWindow.setEventHandlers]
4326 	this(long pulseTimeout, void delegate() handlePulse) {
4327 		synchronized(monitor) {
4328 			if(impl is null) {
4329 				claimGuiThread();
4330 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4331 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4332 			} else {
4333 				if(pulseTimeout) {
4334 					impl.pulseTimeout = pulseTimeout;
4335 					impl.handlePulse = handlePulse;
4336 				}
4337 			}
4338 			impl.refcount++;
4339 		}
4340 	}
4341 
4342 	~this() {
4343 		if(impl is null)
4344 			return;
4345 		impl.refcount--;
4346 		if(impl.refcount == 0) {
4347 			impl.dispose();
4348 			if(thisIsGuiThread)
4349 				guiThreadFinalize();
4350 		}
4351 
4352 	}
4353 
4354 	this(this) {
4355 		if(impl is null)
4356 			return;
4357 		impl.refcount++;
4358 	}
4359 
4360 	/// Runs the event loop until the whileCondition, if present, returns false
4361 	int run(bool delegate() whileCondition = null) {
4362 		assert(impl !is null);
4363 		impl.notExited = true;
4364 		return impl.run(whileCondition);
4365 	}
4366 
4367 	/// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program)
4368 	void exit() {
4369 		assert(impl !is null);
4370 		impl.notExited = false;
4371 
4372 		version(use_arsd_core) {
4373 			import arsd.core;
4374 			ICoreEventLoop.exitApplication();
4375 		}
4376 	}
4377 
4378 	version(linux)
4379 	ref void delegate(int) signalHandler() {
4380 		assert(impl !is null);
4381 		return impl.signalHandler;
4382 	}
4383 
4384 	__gshared static EventLoopImpl* impl;
4385 }
4386 
4387 version(linux)
4388 	void delegate(int, int) globalHupHandler;
4389 
4390 version(Posix)
4391 	void makeNonBlocking(int fd) {
4392 		import fcntl = core.sys.posix.fcntl;
4393 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4394 		if(flags == -1)
4395 			throw new Exception("fcntl get");
4396 		flags |= fcntl.O_NONBLOCK;
4397 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4398 		if(s == -1)
4399 			throw new Exception("fcntl set");
4400 	}
4401 
4402 struct EventLoopImpl {
4403 	int refcount;
4404 
4405 	bool notExited = true;
4406 
4407 	version(Emscripten) {
4408 		void delegate(int) signalHandler;
4409 		static import unix = core.sys.posix.unistd;
4410 		static import err = core.stdc.errno;
4411 	} else
4412 	version(linux) {
4413 		static import ep = core.sys.linux.epoll;
4414 		static import unix = core.sys.posix.unistd;
4415 		static import err = core.stdc.errno;
4416 		import core.sys.linux.timerfd;
4417 
4418 		void delegate(int) signalHandler;
4419 	}
4420 
4421 	version(X11) {
4422 		int pulseFd = -1;
4423 		version(Emscripten) {} else
4424 		version(linux) ep.epoll_event[16] events = void;
4425 	} else version(Windows) {
4426 		Timer pulser;
4427 		HANDLE[] handles;
4428 	}
4429 
4430 
4431 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4432 	/// to call this, as it's not recommended to share window between threads.
4433 	void mtLock () {
4434 		version(X11) {
4435 			XLockDisplay(this.display);
4436 		}
4437 	}
4438 
4439 	version(X11)
4440 	auto display() { return XDisplayConnection.get; }
4441 
4442 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4443 	/// to call this, as it's not recommended to share window between threads.
4444 	void mtUnlock () {
4445 		version(X11) {
4446 			XUnlockDisplay(this.display);
4447 		}
4448 	}
4449 
4450 	version(with_eventloop)
4451 	void initialize(long pulseTimeout) {}
4452 	else
4453 	void initialize(long pulseTimeout) @system {
4454 		version(Windows) {
4455 			if(pulseTimeout && handlePulse !is null)
4456 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4457 
4458 			if (customEventH is null) {
4459 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4460 				if (customEventH !is null) {
4461 					handles ~= customEventH;
4462 				} else {
4463 					// this is something that should not be; better be safe than sorry
4464 					throw new Exception("can't create eventfd for custom event processing");
4465 				}
4466 			}
4467 
4468 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4469 		}
4470 
4471 		version(Emscripten) {
4472 
4473 		} else version(linux) {
4474 			prepareEventLoop();
4475 			{
4476 				auto display = XDisplayConnection.get;
4477 				// adding Xlib file
4478 				ep.epoll_event ev = void;
4479 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4480 				ev.events = ep.EPOLLIN;
4481 				ev.data.fd = display.fd;
4482 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4483 					throw new Exception("add x fd");// ~ to!string(epollFd));
4484 				displayFd = display.fd;
4485 			}
4486 
4487 			if(pulseTimeout && handlePulse !is null) {
4488 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4489 				if(pulseFd == -1)
4490 					throw new Exception("pulse timer create failed");
4491 
4492 				itimerspec value;
4493 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4494 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4495 
4496 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4497 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4498 
4499 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4500 					throw new Exception("couldn't make pulse timer");
4501 
4502 				ep.epoll_event ev = void;
4503 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4504 				ev.events = ep.EPOLLIN;
4505 				ev.data.fd = pulseFd;
4506 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4507 			}
4508 
4509 			// eventfd for custom events
4510 			if (customEventFDWrite == -1) {
4511 				customEventFDWrite = eventfd(0, 0);
4512 				customEventFDRead = customEventFDWrite;
4513 				if (customEventFDRead >= 0) {
4514 					ep.epoll_event ev = void;
4515 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4516 					ev.events = ep.EPOLLIN;
4517 					ev.data.fd = customEventFDRead;
4518 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4519 				} else {
4520 					// this is something that should not be; better be safe than sorry
4521 					throw new Exception("can't create eventfd for custom event processing");
4522 				}
4523 			}
4524 
4525 			if (customSignalFD == -1) {
4526 				import core.sys.linux.sys.signalfd;
4527 
4528 				sigset_t sigset;
4529 				auto err = sigemptyset(&sigset);
4530 				assert(!err);
4531 				err = sigaddset(&sigset, SIGINT);
4532 				assert(!err);
4533 				err = sigaddset(&sigset, SIGHUP);
4534 				assert(!err);
4535 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4536 				assert(!err);
4537 
4538 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4539 				assert(customSignalFD != -1);
4540 
4541 				ep.epoll_event ev = void;
4542 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4543 				ev.events = ep.EPOLLIN;
4544 				ev.data.fd = customSignalFD;
4545 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4546 			}
4547 		} else version(Posix) {
4548 			prepareEventLoop();
4549 			if (customEventFDRead == -1) {
4550 				int[2] bfr;
4551 				import core.sys.posix.unistd;
4552 				auto ret = pipe(bfr);
4553 				if(ret == -1) throw new Exception("pipe");
4554 				customEventFDRead = bfr[0];
4555 				customEventFDWrite = bfr[1];
4556 			}
4557 
4558 		}
4559 
4560 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4561 
4562 		version(linux) {
4563 			this.mtLock();
4564 			scope(exit) this.mtUnlock();
4565 			version(X11)
4566 				XPending(display); // no, really
4567 		}
4568 
4569 		disposed = false;
4570 	}
4571 
4572 	bool disposed = true;
4573 	version(X11)
4574 		int displayFd = -1;
4575 
4576 	version(with_eventloop)
4577 	void dispose() {}
4578 	else
4579 	void dispose() @system {
4580 		disposed = true;
4581 		version(X11) {
4582 			if(pulseFd != -1) {
4583 				import unix = core.sys.posix.unistd;
4584 				unix.close(pulseFd);
4585 				pulseFd = -1;
4586 			}
4587 
4588 				version(Emscripten) {} else
4589 				version(linux)
4590 				if(displayFd != -1) {
4591 					// 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
4592 					ep.epoll_event ev = void;
4593 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4594 					ev.events = ep.EPOLLIN;
4595 					ev.data.fd = displayFd;
4596 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4597 					displayFd = -1;
4598 				}
4599 
4600 		} else version(Windows) {
4601 			if(pulser !is null) {
4602 				pulser.destroy();
4603 				pulser = null;
4604 			}
4605 			if (customEventH !is null) {
4606 				CloseHandle(customEventH);
4607 				customEventH = null;
4608 			}
4609 		}
4610 	}
4611 
4612 	this(long pulseTimeout, void delegate() handlePulse) {
4613 		this.pulseTimeout = pulseTimeout;
4614 		this.handlePulse = handlePulse;
4615 		initialize(pulseTimeout);
4616 	}
4617 
4618 	private long pulseTimeout;
4619 	void delegate() handlePulse;
4620 
4621 	~this() {
4622 		dispose();
4623 	}
4624 
4625 	version(Posix)
4626 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4627 	version(Posix)
4628 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4629 	version(linux)
4630 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4631 	version(Windows)
4632 	ref auto customEventH() { return SimpleWindow.customEventH; }
4633 
4634 	version(X11) {
4635 		bool doXPending() {
4636 			bool done = false;
4637 
4638 			this.mtLock();
4639 			scope(exit) this.mtUnlock();
4640 			//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4641 			while(!done && XPending(display)) {
4642 				done = doXNextEvent(this.display);
4643 			}
4644 
4645 			return done;
4646 		}
4647 		void doXNextEventVoid() {
4648 			doXPending();
4649 		}
4650 	}
4651 
4652 	version(with_eventloop) {
4653 		int loopHelper(bool delegate() whileCondition) {
4654 			// FIXME: whileCondition
4655 			import arsd.eventloop;
4656 			loop();
4657 			return 0;
4658 		}
4659 	} else
4660 	int loopHelper(bool delegate() whileCondition) {
4661 		version(X11) {
4662 			bool done = false;
4663 
4664 			XFlush(display);
4665 			insideXEventLoop = true;
4666 			scope(exit) insideXEventLoop = false;
4667 
4668 			version(use_arsd_core) {
4669 				import arsd.core;
4670 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4671 
4672 				static bool loopInitialized = false;
4673 				if(!loopInitialized) {
4674 					el.addDelegateOnLoopIteration(&doXNextEventVoid, 0);
4675 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4676 
4677 					if(customSignalFD != -1)
4678 					cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() {
4679 						version(linux) {
4680 							import core.sys.linux.sys.signalfd;
4681 							import core.sys.posix.unistd : read;
4682 							signalfd_siginfo info;
4683 							read(customSignalFD, &info, info.sizeof);
4684 
4685 							auto sig = info.ssi_signo;
4686 
4687 							if(EventLoop.get.signalHandler !is null) {
4688 								EventLoop.get.signalHandler()(sig);
4689 							} else {
4690 								EventLoop.get.exit();
4691 							}
4692 						}
4693 					}));
4694 
4695 					if(display.fd != -1)
4696 					cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() {
4697 						this.mtLock();
4698 						scope(exit) this.mtUnlock();
4699 						while(!done && XPending(display)) {
4700 							done = doXNextEvent(this.display);
4701 						}
4702 					}));
4703 
4704 					if(pulseFd != -1)
4705 					cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() {
4706 						long expirationCount;
4707 						// 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...
4708 
4709 						handlePulse();
4710 
4711 						// read just to clear the buffer so poll doesn't trigger again
4712 						// BTW I read AFTER the pulse because if the pulse handler takes
4713 						// a lot of time to execute, we don't want the app to get stuck
4714 						// in a loop of timer hits without a chance to do anything else
4715 						//
4716 						// IOW handlePulse happens at most once per pulse interval.
4717 						unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4718 					}));
4719 
4720 					if(customEventFDRead != -1)
4721 					cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() {
4722 						// we have some custom events; process 'em
4723 						import core.sys.posix.unistd : read;
4724 						ulong n;
4725 						read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4726 						//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4727 						//SimpleWindow.processAllCustomEvents();
4728 					}));
4729 
4730 					// FIXME: posix fds
4731 					// FIXME up?
4732 
4733 
4734 					loopInitialized = true;
4735 				}
4736 
4737 				el.run(() => !whileCondition());
4738 			} else version(linux) {
4739 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4740 					bool forceXPending = false;
4741 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4742 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4743 					{
4744 						this.mtLock();
4745 						scope(exit) this.mtUnlock();
4746 						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
4747 					}
4748 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4749 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4750 					if(nfds == -1) {
4751 						if(err.errno == err.EINTR) {
4752 							//if(forceXPending) goto xpending;
4753 							continue; // interrupted by signal, just try again
4754 						}
4755 						throw new Exception("epoll wait failure");
4756 					}
4757 					// writeln(nfds, " ", events[0].data.fd);
4758 
4759 					SimpleWindow.processAllCustomEvents(); // anyway
4760 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4761 					foreach(idx; 0 .. nfds) {
4762 						if(done) break;
4763 						auto fd = events[idx].data.fd;
4764 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4765 						auto flags = events[idx].events;
4766 						if(flags & ep.EPOLLIN) {
4767 							if (fd == customSignalFD) {
4768 								version(linux) {
4769 									import core.sys.linux.sys.signalfd;
4770 									import core.sys.posix.unistd : read;
4771 									signalfd_siginfo info;
4772 									read(customSignalFD, &info, info.sizeof);
4773 
4774 									auto sig = info.ssi_signo;
4775 
4776 									if(EventLoop.get.signalHandler !is null) {
4777 										EventLoop.get.signalHandler()(sig);
4778 									} else {
4779 										EventLoop.get.exit();
4780 									}
4781 								}
4782 							} else if(fd == display.fd) {
4783 								version(sdddd) { writeln("X EVENT PENDING!"); }
4784 								this.mtLock();
4785 								scope(exit) this.mtUnlock();
4786 								while(!done && XPending(display)) {
4787 									done = doXNextEvent(this.display);
4788 								}
4789 								forceXPending = false;
4790 							} else if(fd == pulseFd) {
4791 								long expirationCount;
4792 								// 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...
4793 
4794 								handlePulse();
4795 
4796 								// read just to clear the buffer so poll doesn't trigger again
4797 								// BTW I read AFTER the pulse because if the pulse handler takes
4798 								// a lot of time to execute, we don't want the app to get stuck
4799 								// in a loop of timer hits without a chance to do anything else
4800 								//
4801 								// IOW handlePulse happens at most once per pulse interval.
4802 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4803 								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
4804 							} else if (fd == customEventFDRead) {
4805 								// we have some custom events; process 'em
4806 								import core.sys.posix.unistd : read;
4807 								ulong n;
4808 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4809 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4810 								//SimpleWindow.processAllCustomEvents();
4811 
4812 								forceXPending = true;
4813 							} else {
4814 								// some other timer
4815 								version(sdddd) { writeln("unknown fd: ", fd); }
4816 
4817 								if(Timer* t = fd in Timer.mapping)
4818 									(*t).trigger();
4819 
4820 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4821 									(*pfr).ready(flags);
4822 
4823 								// we don't know what the user did in this timer, so we need to assume that
4824 								// there's X data to be flushed and potentially processed
4825 								forceXPending = true;
4826 
4827 								// or i might add support for other FDs too
4828 								// but for now it is just timer
4829 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4830 							}
4831 						}
4832 						if(flags & ep.EPOLLHUP) {
4833 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4834 								(*pfr).hup(flags);
4835 							if(globalHupHandler)
4836 								globalHupHandler(fd, flags);
4837 						}
4838 						/+
4839 						} else {
4840 							// not interested in OUT, we are just reading here.
4841 							//
4842 							// error or hup might also be reported
4843 							// but it shouldn't here since we are only
4844 							// using a few types of FD and Xlib will report
4845 							// if it dies.
4846 							// so instead of thoughtfully handling it, I'll
4847 							// just throw. for now at least
4848 
4849 							throw new Exception("epoll did something else");
4850 						}
4851 						+/
4852 					}
4853 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4854 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4855 					xpending:
4856 					if (!done && forceXPending) {
4857 						done = doXPending();
4858 					}
4859 				}
4860 			} else {
4861 				// Generic fallback: yes to simple pulse support,
4862 				// but NO timer support!
4863 
4864 				// FIXME: we could probably support the POSIX timer_create
4865 				// signal-based option, but I'm in no rush to write it since
4866 				// I prefer the fd-based functions.
4867 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4868 
4869 					import core.sys.posix.poll;
4870 
4871 					pollfd[] pfds;
4872 					pollfd[32] pfdsBuffer;
4873 					auto len = PosixFdReader.mapping.length + 2;
4874 					// FIXME: i should just reuse the buffer
4875 					if(len < pfdsBuffer.length)
4876 						pfds = pfdsBuffer[0 .. len];
4877 					else
4878 						pfds = new pollfd[](len);
4879 
4880 					pfds[0].fd = display.fd;
4881 					pfds[0].events = POLLIN;
4882 					pfds[0].revents = 0;
4883 
4884 					int slot = 1;
4885 
4886 					if(customEventFDRead != -1) {
4887 						pfds[slot].fd = customEventFDRead;
4888 						pfds[slot].events = POLLIN;
4889 						pfds[slot].revents = 0;
4890 
4891 						slot++;
4892 					}
4893 
4894 					foreach(fd, obj; PosixFdReader.mapping) {
4895 						if(!obj.enabled) continue;
4896 						pfds[slot].fd = fd;
4897 						pfds[slot].events = POLLIN;
4898 						pfds[slot].revents = 0;
4899 
4900 						slot++;
4901 					}
4902 
4903 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4904 					if(ret == -1) throw new Exception("poll");
4905 
4906 					if(ret == 0) {
4907 						// FIXME it may not necessarily time out if events keep coming
4908 						if(handlePulse !is null)
4909 							handlePulse();
4910 					} else {
4911 						foreach(s; 0 .. slot) {
4912 							if(pfds[s].revents == 0) continue;
4913 
4914 							if(pfds[s].fd == display.fd) {
4915 								while(!done && XPending(display)) {
4916 									this.mtLock();
4917 									scope(exit) this.mtUnlock();
4918 									done = doXNextEvent(this.display);
4919 								}
4920 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4921 
4922 								import core.sys.posix.unistd : read;
4923 								ulong n;
4924 								read(customEventFDRead, &n, n.sizeof);
4925 								SimpleWindow.processAllCustomEvents();
4926 							} else {
4927 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4928 								if(pfds[s].revents & POLLNVAL) {
4929 									obj.dispose();
4930 								} else {
4931 									obj.ready(pfds[s].revents);
4932 								}
4933 							}
4934 
4935 							ret--;
4936 							if(ret == 0) break;
4937 						}
4938 					}
4939 				}
4940 			}
4941 		}
4942 
4943 		version(Windows) {
4944 
4945 			version(use_arsd_core) {
4946 				import arsd.core;
4947 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4948 				static bool loopInitialized = false;
4949 				if(!loopInitialized) {
4950 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4951 					el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0);
4952 					loopInitialized = true;
4953 				}
4954 				el.run(() => !whileCondition());
4955 			} else {
4956 				int ret = -1;
4957 				MSG message;
4958 				while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4959 					eventLoopRound++;
4960 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4961 					auto waitResult = MsgWaitForMultipleObjectsEx(
4962 						cast(int) handles.length, handles.ptr,
4963 						(wto == 0 ? INFINITE : wto), /* timeout */
4964 						0x04FF, /* QS_ALLINPUT */
4965 						0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4966 
4967 					SimpleWindow.processAllCustomEvents(); // anyway
4968 					enum WAIT_OBJECT_0 = 0;
4969 					if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4970 						auto h = handles[waitResult - WAIT_OBJECT_0];
4971 						if(auto e = h in WindowsHandleReader.mapping) {
4972 							(*e).ready();
4973 						}
4974 					} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4975 						// message ready
4976 						int count;
4977 						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
4978 							ret = GetMessage(&message, null, 0, 0);
4979 							if(ret == -1)
4980 								throw new WindowsApiException("GetMessage", GetLastError());
4981 							TranslateMessage(&message);
4982 							DispatchMessage(&message);
4983 
4984 							count++;
4985 							if(count > 10)
4986 								break; // take the opportunity to catch up on other events
4987 
4988 							if(ret == 0) { // WM_QUIT
4989 								EventLoop.quitApplication();
4990 								break;
4991 							}
4992 						}
4993 					} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4994 						SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4995 					} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4996 						// timeout, should never happen since we aren't using it
4997 					} else if(waitResult == 0xFFFFFFFF) {
4998 							// failed
4999 							throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
5000 					} else {
5001 						// idk....
5002 					}
5003 				}
5004 			}
5005 
5006 			// return message.wParam;
5007 			return 0;
5008 		} else {
5009 			return 0;
5010 		}
5011 	}
5012 
5013 	int run(bool delegate() whileCondition = null) {
5014 		if(disposed)
5015 			initialize(this.pulseTimeout);
5016 
5017 		version(X11) {
5018 			try {
5019 				return loopHelper(whileCondition);
5020 			} catch(XDisconnectException e) {
5021 				if(e.userRequested) {
5022 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
5023 						item.discardConnectionState();
5024 					XCloseDisplay(XDisplayConnection.display);
5025 				}
5026 
5027 				XDisplayConnection.display = null;
5028 
5029 				this.dispose();
5030 
5031 				throw e;
5032 			}
5033 		} else {
5034 			return loopHelper(whileCondition);
5035 		}
5036 	}
5037 }
5038 
5039 
5040 /++
5041 	Provides an icon on the system notification area (also known as the system tray).
5042 
5043 
5044 	If a notification area is not available with the NotificationIcon object is created,
5045 	it will silently succeed and simply attempt to create one when an area becomes available.
5046 
5047 
5048 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
5049 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
5050 	with true color was added at that time. I was just too lazy to write the fallback.
5051 
5052 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
5053 	you use arsd 10.x when targeting Windows XP.
5054 +/
5055 version(Emscripten) {} else
5056 version(OSXCocoa) {} else // NotYetImplementedException
5057 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
5058 
5059 	version(X11) {
5060 		void recreateAfterDisconnect() {
5061 			stateDiscarded = false;
5062 			clippixmap = None;
5063 			throw new Exception("NOT IMPLEMENTED");
5064 		}
5065 
5066 		bool stateDiscarded;
5067 		void discardConnectionState() {
5068 			stateDiscarded = true;
5069 		}
5070 	}
5071 
5072 
5073 	version(X11) {
5074 		Image img;
5075 
5076 		NativeEventHandler getNativeEventHandler() {
5077 			return delegate int(XEvent e) {
5078 				switch(e.type) {
5079 					case EventType.Expose:
5080 					//case EventType.VisibilityNotify:
5081 						redraw();
5082 					break;
5083 					case EventType.ClientMessage:
5084 						version(sddddd) {
5085 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
5086 						writeln("\t", e.xclient.format);
5087 						writeln("\t", e.xclient.data.l);
5088 						}
5089 					break;
5090 					case EventType.ButtonPress:
5091 						auto event = e.xbutton;
5092 						if (onClick !is null || onClickEx !is null) {
5093 							MouseButton mb = cast(MouseButton)0;
5094 							switch (event.button) {
5095 								case 1: mb = MouseButton.left; break; // left
5096 								case 2: mb = MouseButton.middle; break; // middle
5097 								case 3: mb = MouseButton.right; break; // right
5098 								case 4: mb = MouseButton.wheelUp; break; // scroll up
5099 								case 5: mb = MouseButton.wheelDown; break; // scroll down
5100 								case 6: break; // scroll left...
5101 								case 7: break; // scroll right...
5102 								case 8: mb = MouseButton.backButton; break;
5103 								case 9: mb = MouseButton.forwardButton; break;
5104 								default:
5105 							}
5106 							if (mb) {
5107 								try { onClick()(mb); } catch (Exception) {}
5108 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
5109 							}
5110 						}
5111 					break;
5112 					case EventType.EnterNotify:
5113 						if (onEnter !is null) {
5114 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
5115 						}
5116 						break;
5117 					case EventType.LeaveNotify:
5118 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
5119 						break;
5120 					case EventType.DestroyNotify:
5121 						active = false;
5122 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
5123 					break;
5124 					case EventType.ConfigureNotify:
5125 						auto event = e.xconfigure;
5126 						this.width = event.width;
5127 						this.height = event.height;
5128 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
5129 						redraw();
5130 					break;
5131 					default: return 1;
5132 				}
5133 				return 1;
5134 			};
5135 		}
5136 
5137 		/* private */ void hideBalloon() {
5138 			balloon.close();
5139 			version(with_timer)
5140 				timer.destroy();
5141 			balloon = null;
5142 			version(with_timer)
5143 				timer = null;
5144 		}
5145 
5146 		void redraw() {
5147 			if (!active) return;
5148 
5149 			auto display = XDisplayConnection.get;
5150 			GC gc;
5151 
5152 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
5153 
5154 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
5155 				Visual *visual;
5156 				XVisualInfo vis_info;
5157 				XSetWindowAttributes win_attr;
5158 				c_ulong win_mask;
5159 
5160 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
5161 					assert(0);
5162 					// return 1;
5163 				}
5164 
5165 				visual = vis_info.visual;
5166 
5167 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
5168 				win_attr.background_pixel = 0;
5169 				win_attr.border_pixel = 0;
5170 
5171 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
5172 
5173 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
5174 
5175 				return 0;
5176 			}
5177 
5178 			if(useAlpha)
5179 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
5180 			else
5181 				gc = DefaultGC(display, DefaultScreen(display));
5182 
5183 			XClearWindow(display, nativeHandle);
5184 
5185 			if(!useAlpha && img !is null)
5186 				XSetClipMask(display, gc, clippixmap);
5187 
5188 			/+
5189 			XSetForeground(display, gc,
5190 				cast(uint) 0 << 16 |
5191 				cast(uint) 0 << 8 |
5192 				cast(uint) 0);
5193 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
5194 			+/
5195 
5196 			if (img is null) {
5197 				XSetForeground(display, gc,
5198 					cast(uint) 0 << 16 |
5199 					cast(uint) 127 << 8 |
5200 					cast(uint) 0);
5201 				XFillArc(display, nativeHandle,
5202 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
5203 			} else {
5204 				int dx = 0;
5205 				int dy = 0;
5206 				if(width > img.width)
5207 					dx = (width - img.width) / 2;
5208 				if(height > img.height)
5209 					dy = (height - img.height) / 2;
5210 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
5211 				XSetClipOrigin(display, gc, dx, dy);
5212 
5213 				int max(int a, int b) {
5214 					if(a > b) return a; else return b;
5215 				}
5216 
5217 				if (img.usingXshm)
5218 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
5219 				else
5220 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
5221 			}
5222 			XSetClipMask(display, gc, None);
5223 			flushGui();
5224 		}
5225 
5226 		static Window getTrayOwner() {
5227 			auto display = XDisplayConnection.get;
5228 			auto i = cast(int) DefaultScreen(display);
5229 			if(i < 10 && i >= 0) {
5230 				static Atom atom;
5231 				if(atom == None)
5232 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
5233 				return XGetSelectionOwner(display, atom);
5234 			}
5235 			return None;
5236 		}
5237 
5238 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
5239 			auto to = getTrayOwner();
5240 			auto display = XDisplayConnection.get;
5241 			XEvent ev;
5242 			ev.xclient.type = EventType.ClientMessage;
5243 			ev.xclient.window = to;
5244 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
5245 			ev.xclient.format = 32;
5246 			ev.xclient.data.l[0] = CurrentTime;
5247 			ev.xclient.data.l[1] = message;
5248 			ev.xclient.data.l[2] = d1;
5249 			ev.xclient.data.l[3] = d2;
5250 			ev.xclient.data.l[4] = d3;
5251 
5252 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
5253 		}
5254 
5255 		private static NotificationAreaIcon[] activeIcons;
5256 
5257 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
5258 		private void newManager() {
5259 			close();
5260 			createXWin();
5261 
5262 			if(this.clippixmap)
5263 				XFreePixmap(XDisplayConnection.get, clippixmap);
5264 			if(this.originalMemoryImage)
5265 				this.icon = this.originalMemoryImage;
5266 			else if(this.img)
5267 				this.icon = this.img;
5268 		}
5269 
5270 		private bool useAlpha = false;
5271 
5272 		private void createXWin () {
5273 			// create window
5274 			auto display = XDisplayConnection.get;
5275 
5276 			// to check for MANAGER on root window to catch new/changed tray owners
5277 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
5278 			// so if a thing does appear, we can handle it
5279 			foreach(ai; activeIcons)
5280 				if(ai is this)
5281 					goto alreadythere;
5282 			activeIcons ~= this;
5283 			alreadythere:
5284 
5285 			// and check for an existing tray
5286 			auto trayOwner = getTrayOwner();
5287 			if(trayOwner == None)
5288 				return;
5289 				//throw new Exception("No notification area found");
5290 
5291 			Visual* v = cast(Visual*) CopyFromParent;
5292 
5293 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
5294 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
5295 			// a resize event later.
5296 			width = 22;
5297 			height = 22;
5298 
5299 			// if they system gave us a 32 bit visual we need to switch to it too
5300 			int depth = 24;
5301 
5302 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
5303 			if(visualProp !is null) {
5304 				c_ulong[] info = cast(c_ulong[]) visualProp;
5305 				if(info.length == 1) {
5306 					auto vid = info[0];
5307 					int returned;
5308 					XVisualInfo t;
5309 					t.visualid = vid;
5310 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
5311 					if(got !is null) {
5312 						if(returned == 1) {
5313 							v = got.visual;
5314 							depth = got.depth;
5315 							// writeln("using special visual ", got.depth);
5316 							// writeln(depth);
5317 						}
5318 						XFree(got);
5319 					}
5320 				}
5321 			}
5322 
5323 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
5324 			XSetWindowAttributes attr;
5325 			attr.background_pixel = 0;
5326 			attr.border_pixel = 0;
5327 			attr.override_redirect = 0;
5328 			if(v !is cast(Visual*) CopyFromParent) {
5329 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
5330 				CWFlags |= CWColormap;
5331 				if(depth == 32)
5332 					useAlpha = true;
5333 				else
5334 					goto plain;
5335 			} else {
5336 				plain:
5337 				attr.background_pixmap = 1 /* ParentRelative */;
5338 				CWFlags |= CWBackPixmap;
5339 			}
5340 
5341 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
5342 
5343 			assert(nativeWindow);
5344 
5345 			if(!useAlpha)
5346 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
5347 
5348 			nativeHandle = nativeWindow;
5349 
5350 			///+
5351 			arch_ulong[2] info;
5352 			info[0] = 0;
5353 			info[1] = 1;
5354 
5355 			string title = this.name is null ? "simpledisplay.d program" : this.name;
5356 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
5357 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
5358 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
5359 
5360 			XChangeProperty(
5361 				display,
5362 				nativeWindow,
5363 				GetAtom!("_XEMBED_INFO", true)(display),
5364 				GetAtom!("_XEMBED_INFO", true)(display),
5365 				32 /* bits */,
5366 				0 /*PropModeReplace*/,
5367 				info.ptr,
5368 				2);
5369 
5370 			import core.sys.posix.unistd;
5371 			arch_ulong pid = getpid();
5372 
5373 			XChangeProperty(
5374 				display,
5375 				nativeWindow,
5376 				GetAtom!("_NET_WM_PID", true)(display),
5377 				XA_CARDINAL,
5378 				32 /* bits */,
5379 				0 /*PropModeReplace*/,
5380 				&pid,
5381 				1);
5382 
5383 			updateNetWmIcon();
5384 
5385 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
5386 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
5387 				XClassHint klass;
5388 				XWMHints wh;
5389 				XSizeHints size;
5390 				klass.res_name = sdpyWindowClassStr;
5391 				klass.res_class = sdpyWindowClassStr;
5392 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
5393 			}
5394 
5395 				// believe it or not, THIS is what xfce needed for the 9999 issue
5396 				XSizeHints sh;
5397 				c_long spr;
5398 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
5399 				sh.flags |= PMaxSize | PMinSize;
5400 				// FIXME maybe nicer resizing
5401 				sh.min_width = 16;
5402 				sh.min_height = 16;
5403 				sh.max_width = 22;
5404 				sh.max_height = 22;
5405 				XSetWMNormalHints(display, nativeWindow, &sh);
5406 
5407 
5408 			//+/
5409 
5410 
5411 			XSelectInput(display, nativeWindow,
5412 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
5413 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5414 
5415 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5416 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5417 
5418 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5419 			active = true;
5420 		}
5421 
5422 		void updateNetWmIcon() {
5423 			if(img is null) return;
5424 			auto display = XDisplayConnection.get;
5425 			// FIXME: ensure this is correct
5426 			arch_ulong[] buffer;
5427 			auto imgMi = img.toTrueColorImage;
5428 			buffer ~= imgMi.width;
5429 			buffer ~= imgMi.height;
5430 			foreach(c; imgMi.imageData.colors) {
5431 				arch_ulong b;
5432 				b |= c.a << 24;
5433 				b |= c.r << 16;
5434 				b |= c.g << 8;
5435 				b |= c.b;
5436 				buffer ~= b;
5437 			}
5438 
5439 			XChangeProperty(
5440 				display,
5441 				nativeHandle,
5442 				GetAtom!"_NET_WM_ICON"(display),
5443 				GetAtom!"CARDINAL"(display),
5444 				32 /* bits */,
5445 				0 /*PropModeReplace*/,
5446 				buffer.ptr,
5447 				cast(int) buffer.length);
5448 		}
5449 
5450 
5451 
5452 		private SimpleWindow balloon;
5453 		version(with_timer)
5454 		private Timer timer;
5455 
5456 		private Window nativeHandle;
5457 		private Pixmap clippixmap = None;
5458 		private int width = 16;
5459 		private int height = 16;
5460 		private bool active = false;
5461 
5462 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5463 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5464 		void delegate () onLeave; /// X11 only.
5465 
5466 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5467 
5468 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5469 		void getWindowRect (out int x, out int y, out int width, out int height) {
5470 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5471 			Window dummyw;
5472 			auto dpy = XDisplayConnection.get;
5473 			//XWindowAttributes xwa;
5474 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5475 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5476 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5477 			width = this.width;
5478 			height = this.height;
5479 		}
5480 	}
5481 
5482 	/+
5483 		What I actually want from this:
5484 
5485 		* set / change: icon, tooltip
5486 		* handle: mouse click, right click
5487 		* show: notification bubble.
5488 	+/
5489 
5490 	version(Windows) {
5491 		WindowsIcon win32Icon;
5492 		HWND hwnd;
5493 
5494 		NOTIFYICONDATAW data;
5495 
5496 		NativeEventHandler getNativeEventHandler() {
5497 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5498 				if(msg == WM_USER) {
5499 					auto event = LOWORD(lParam);
5500 					auto iconId = HIWORD(lParam);
5501 					//auto x = GET_X_LPARAM(wParam);
5502 					//auto y = GET_Y_LPARAM(wParam);
5503 					switch(event) {
5504 						case WM_LBUTTONDOWN:
5505 							onClick()(MouseButton.left);
5506 						break;
5507 						case WM_RBUTTONDOWN:
5508 							onClick()(MouseButton.right);
5509 						break;
5510 						case WM_MBUTTONDOWN:
5511 							onClick()(MouseButton.middle);
5512 						break;
5513 						case WM_MOUSEMOVE:
5514 							// sent, we could use it.
5515 						break;
5516 						case WM_MOUSEWHEEL:
5517 							// NOT SENT
5518 						break;
5519 						//case NIN_KEYSELECT:
5520 						//case NIN_SELECT:
5521 						//break;
5522 						default: {}
5523 					}
5524 				}
5525 				return 0;
5526 			};
5527 		}
5528 
5529 		enum NIF_SHOWTIP = 0x00000080;
5530 
5531 		private static struct NOTIFYICONDATAW {
5532 			DWORD cbSize;
5533 			HWND  hWnd;
5534 			UINT  uID;
5535 			UINT  uFlags;
5536 			UINT  uCallbackMessage;
5537 			HICON hIcon;
5538 			WCHAR[128] szTip;
5539 			DWORD dwState;
5540 			DWORD dwStateMask;
5541 			WCHAR[256] szInfo;
5542 			union {
5543 				UINT uTimeout;
5544 				UINT uVersion;
5545 			}
5546 			WCHAR[64] szInfoTitle;
5547 			DWORD dwInfoFlags;
5548 			GUID  guidItem;
5549 			HICON hBalloonIcon;
5550 		}
5551 
5552 	}
5553 
5554 	/++
5555 		Note that on Windows, only left, right, and middle buttons are sent.
5556 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5557 		program is meant to be used on Windows too.
5558 	+/
5559 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5560 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5561 		// but on X, we need an Image, so its canonical ctor is there. They should
5562 		// forward to each other though.
5563 		version(X11) {
5564 			this.name = name;
5565 			this.onClick = onClick;
5566 			createXWin();
5567 			this.icon = icon;
5568 		} else version(Windows) {
5569 			this.onClick = onClick;
5570 			this.win32Icon = new WindowsIcon(icon);
5571 
5572 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5573 
5574 			static bool registered = false;
5575 			if(!registered) {
5576 				WNDCLASSEX wc;
5577 				wc.cbSize = wc.sizeof;
5578 				wc.hInstance = hInstance;
5579 				wc.lpfnWndProc = &WndProc;
5580 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5581 				if(!RegisterClassExW(&wc))
5582 					throw new WindowsApiException("RegisterClass", GetLastError());
5583 				registered = true;
5584 			}
5585 
5586 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5587 			if(hwnd is null)
5588 				throw new WindowsApiException("CreateWindow", GetLastError());
5589 
5590 			data.cbSize = data.sizeof;
5591 			data.hWnd = hwnd;
5592 			data.uID = cast(uint) cast(void*) this;
5593 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5594 				// NIF_INFO means show balloon
5595 			data.uCallbackMessage = WM_USER;
5596 			data.hIcon = this.win32Icon.hIcon;
5597 			data.szTip = ""; // FIXME
5598 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5599 			data.dwStateMask = NIS_HIDDEN; // windows vista
5600 
5601 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5602 
5603 
5604 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5605 
5606 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5607 		} else version(OSXCocoa) {
5608 			throw new NotYetImplementedException();
5609 		} else static assert(0);
5610 	}
5611 
5612 	/// ditto
5613 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5614 		version(X11) {
5615 			this.onClick = onClick;
5616 			this.name = name;
5617 			createXWin();
5618 			this.icon = icon;
5619 		} else version(Windows) {
5620 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5621 		} else version(OSXCocoa) {
5622 			throw new NotYetImplementedException();
5623 		} else static assert(0);
5624 	}
5625 
5626 	version(X11) {
5627 		/++
5628 			X-specific extension (for now at least)
5629 		+/
5630 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5631 			this.onClickEx = onClickEx;
5632 			createXWin();
5633 			if (icon !is null) this.icon = icon;
5634 		}
5635 
5636 		/// ditto
5637 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5638 			this.onClickEx = onClickEx;
5639 			createXWin();
5640 			this.icon = icon;
5641 		}
5642 	}
5643 
5644 	private void delegate (MouseButton button) onClick_;
5645 
5646 	///
5647 	@property final void delegate(MouseButton) onClick() {
5648 		if(onClick_ is null)
5649 			onClick_ = delegate void(MouseButton) {};
5650 		return onClick_;
5651 	}
5652 
5653 	/// ditto
5654 	@property final void onClick(void delegate(MouseButton) handler) {
5655 		// I made this a property setter so we can wrap smaller arg
5656 		// delegates and just forward all to onClickEx or something.
5657 		onClick_ = handler;
5658 	}
5659 
5660 
5661 	string name_;
5662 	@property void name(string n) {
5663 		name_ = n;
5664 	}
5665 
5666 	@property string name() {
5667 		return name_;
5668 	}
5669 
5670 	private MemoryImage originalMemoryImage;
5671 
5672 	///
5673 	@property void icon(MemoryImage i) {
5674 		version(X11) {
5675 			this.originalMemoryImage = i;
5676 			if (!active) return;
5677 			if (i !is null) {
5678 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5679 				if(!useAlpha)
5680 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5681 				// writeln("using pixmap ", clippixmap);
5682 				updateNetWmIcon();
5683 				redraw();
5684 			} else {
5685 				if (this.img !is null) {
5686 					this.img = null;
5687 					redraw();
5688 				}
5689 			}
5690 		} else version(Windows) {
5691 			this.win32Icon = new WindowsIcon(i);
5692 
5693 			data.uFlags = NIF_ICON;
5694 			data.hIcon = this.win32Icon.hIcon;
5695 
5696 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5697 		} else version(OSXCocoa) {
5698 			throw new NotYetImplementedException();
5699 		} else static assert(0);
5700 	}
5701 
5702 	/// ditto
5703 	@property void icon (Image i) {
5704 		version(X11) {
5705 			if (!active) return;
5706 			if (i !is img) {
5707 				originalMemoryImage = null;
5708 				img = i;
5709 				redraw();
5710 			}
5711 		} else version(Windows) {
5712 			this.icon(i is null ? null : i.toTrueColorImage());
5713 		} else version(OSXCocoa) {
5714 			throw new NotYetImplementedException();
5715 		} else static assert(0);
5716 	}
5717 
5718 	/++
5719 		Shows a balloon notification. You can only show one balloon at a time, if you call
5720 		it twice while one is already up, the first balloon will be replaced.
5721 
5722 
5723 		The user is free to block notifications and they will automatically disappear after
5724 		a timeout period.
5725 
5726 		Params:
5727 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5728 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5729 			icon = the icon to display with the notification. If null, it uses your existing icon.
5730 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5731 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5732 	+/
5733 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5734 		bool useCustom = true;
5735 		version(libnotify) {
5736 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5737 			try {
5738 				if(!active) return;
5739 
5740 				if(libnotify is null) {
5741 					libnotify = new C_DynamicLibrary("libnotify.so");
5742 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5743 				}
5744 
5745 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5746 
5747 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5748 
5749 				if(onclick) {
5750 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5751 					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);
5752 					libnotify_action_delegates_count++;
5753 				}
5754 
5755 				// FIXME icon
5756 
5757 				// set hint image-data
5758 				// set default action for onclick
5759 
5760 				void* error;
5761 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5762 
5763 				useCustom = false;
5764 			} catch(Exception e) {
5765 
5766 			}
5767 		}
5768 
5769 		version(X11) {
5770 		if(useCustom) {
5771 			if(!active) return;
5772 			if(balloon) {
5773 				hideBalloon();
5774 			}
5775 			// I know there are two specs for this, but one is never
5776 			// implemented by any window manager I have ever seen, and
5777 			// the other is a bloated mess and too complicated for simpledisplay...
5778 			// so doing my own little window instead.
5779 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5780 
5781 			int x, y, width, height;
5782 			getWindowRect(x, y, width, height);
5783 
5784 			int bx = x - balloon.width;
5785 			int by = y - balloon.height;
5786 			if(bx < 0)
5787 				bx = x + width + balloon.width;
5788 			if(by < 0)
5789 				by = y + height;
5790 
5791 			// just in case, make sure it is actually on scren
5792 			if(bx < 0)
5793 				bx = 0;
5794 			if(by < 0)
5795 				by = 0;
5796 
5797 			balloon.move(bx, by);
5798 			auto painter = balloon.draw();
5799 			painter.fillColor = Color(220, 220, 220);
5800 			painter.outlineColor = Color.black;
5801 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5802 			auto iconWidth = icon is null ? 0 : icon.width;
5803 			if(icon)
5804 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5805 			iconWidth += 6; // margin around the icon
5806 
5807 			// draw a close button
5808 			painter.outlineColor = Color(44, 44, 44);
5809 			painter.fillColor = Color(255, 255, 255);
5810 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5811 			painter.pen = Pen(Color.black, 3);
5812 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5813 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5814 			painter.pen = Pen(Color.black, 1);
5815 			painter.fillColor = Color(220, 220, 220);
5816 
5817 			// Draw the title and message
5818 			painter.drawText(Point(4 + iconWidth, 4), title);
5819 			painter.drawLine(
5820 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5821 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5822 			);
5823 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5824 
5825 			balloon.setEventHandlers(
5826 				(MouseEvent ev) {
5827 					if(ev.type == MouseEventType.buttonPressed) {
5828 						if(ev.x > balloon.width - 16 && ev.y < 16)
5829 							hideBalloon();
5830 						else if(onclick)
5831 							onclick();
5832 					}
5833 				}
5834 			);
5835 			balloon.show();
5836 
5837 			version(with_timer)
5838 			timer = new Timer(timeout, &hideBalloon);
5839 			else {} // FIXME
5840 		}
5841 		} else version(Windows) {
5842 			enum NIF_INFO = 0x00000010;
5843 
5844 			data.uFlags = NIF_INFO;
5845 
5846 			// FIXME: go back to the last valid unicode code point
5847 			if(title.length > 40)
5848 				title = title[0 .. 40];
5849 			if(message.length > 220)
5850 				message = message[0 .. 220];
5851 
5852 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5853 			enum NIIF_LARGE_ICON  = 0x00000020;
5854 			enum NIIF_NOSOUND = 0x00000010;
5855 			enum NIIF_USER = 0x00000004;
5856 			enum NIIF_ERROR = 0x00000003;
5857 			enum NIIF_WARNING = 0x00000002;
5858 			enum NIIF_INFO = 0x00000001;
5859 			enum NIIF_NONE = 0;
5860 
5861 			WCharzBuffer t = WCharzBuffer(title);
5862 			WCharzBuffer m = WCharzBuffer(message);
5863 
5864 			t.copyInto(data.szInfoTitle);
5865 			m.copyInto(data.szInfo);
5866 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5867 
5868 			if(icon !is null) {
5869 				auto i = new WindowsIcon(icon);
5870 				data.hBalloonIcon = i.hIcon;
5871 				data.dwInfoFlags |= NIIF_USER;
5872 			}
5873 
5874 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5875 		} else version(OSXCocoa) {
5876 			throw new NotYetImplementedException();
5877 		} else static assert(0);
5878 	}
5879 
5880 	///
5881 	//version(Windows)
5882 	void show() {
5883 		version(X11) {
5884 			if(!hidden)
5885 				return;
5886 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5887 			hidden = false;
5888 		} else version(Windows) {
5889 			data.uFlags = NIF_STATE;
5890 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5891 			data.dwStateMask = NIS_HIDDEN; // windows vista
5892 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5893 		} else version(OSXCocoa) {
5894 			throw new NotYetImplementedException();
5895 		} else static assert(0);
5896 	}
5897 
5898 	version(X11)
5899 		bool hidden = false;
5900 
5901 	///
5902 	//version(Windows)
5903 	void hide() {
5904 		version(X11) {
5905 			if(hidden)
5906 				return;
5907 			hidden = true;
5908 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5909 		} else version(Windows) {
5910 			data.uFlags = NIF_STATE;
5911 			data.dwState = NIS_HIDDEN; // windows vista
5912 			data.dwStateMask = NIS_HIDDEN; // windows vista
5913 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5914 		} else version(OSXCocoa) {
5915 			throw new NotYetImplementedException();
5916 		} else static assert(0);
5917 	}
5918 
5919 	///
5920 	void close () {
5921 		version(X11) {
5922 			if (active) {
5923 				active = false; // event handler will set this too, but meh
5924 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5925 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5926 				flushGui();
5927 			}
5928 		} else version(Windows) {
5929 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5930 		} else version(OSXCocoa) {
5931 			throw new NotYetImplementedException();
5932 		} else static assert(0);
5933 	}
5934 
5935 	~this() {
5936 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5937 		version(X11)
5938 			if(clippixmap != None)
5939 				XFreePixmap(XDisplayConnection.get, clippixmap);
5940 		close();
5941 	}
5942 }
5943 
5944 version(X11)
5945 /// Call `XFreePixmap` on the return value.
5946 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5947 	char[] data = new char[](i.width * i.height / 8 + 2);
5948 	data[] = 0;
5949 
5950 	int bitOffset = 0;
5951 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5952 		ubyte v = c.a > 128 ? 1 : 0;
5953 		data[bitOffset / 8] |= v << (bitOffset%8);
5954 		bitOffset++;
5955 	}
5956 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5957 	return handle;
5958 }
5959 
5960 
5961 // basic functions to make timers
5962 /**
5963 	A timer that will trigger your function on a given interval.
5964 
5965 
5966 	You create a timer with an interval and a callback. It will continue
5967 	to fire on the interval until it is destroyed.
5968 
5969 	There are currently no one-off timers (instead, just create one and
5970 	destroy it when it is triggered) nor are there pause/resume functions -
5971 	the timer must again be destroyed and recreated if you want to pause it.
5972 
5973 	---
5974 	auto timer = new Timer(50, { it happened!; });
5975 	timer.destroy();
5976 	---
5977 
5978 	Timers can only be expected to fire when the event loop is running and only
5979 	once per iteration through the event loop.
5980 
5981 	History:
5982 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5983 		slow could lock up the event loop. It now guarantees other things will
5984 		get a chance to run between timer calls, even if that means not keeping up
5985 		with the requested interval.
5986 */
5987 version(with_timer) {
5988 version(use_arsd_core)
5989 	alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api
5990 else
5991 class Timer {
5992 // FIXME: needs pause and unpause
5993 	// FIXME: I might add overloads for ones that take a count of
5994 	// how many elapsed since last time (on Windows, it will divide
5995 	// the ticks thing given, on Linux it is just available) and
5996 	// maybe one that takes an instance of the Timer itself too
5997 	/// Create a timer with a callback when it triggers.
5998 	this(int intervalInMilliseconds, void delegate() onPulse) @trusted {
5999 		assert(onPulse !is null);
6000 
6001 		this.intervalInMilliseconds = intervalInMilliseconds;
6002 		this.onPulse = onPulse;
6003 
6004 		version(Windows) {
6005 			/*
6006 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6007 			if(handle == 0)
6008 				throw new WindowsApiException("SetTimer", GetLastError());
6009 			*/
6010 
6011 			// thanks to Archival 998 for the WaitableTimer blocks
6012 			handle = CreateWaitableTimer(null, false, null);
6013 			long initialTime = -intervalInMilliseconds;
6014 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6015 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
6016 
6017 			mapping[handle] = this;
6018 
6019 		} else version(Emscripten) {
6020 		} else version(linux) {
6021 			static import ep = core.sys.linux.epoll;
6022 
6023 			import core.sys.linux.timerfd;
6024 
6025 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
6026 			if(fd == -1)
6027 				throw new Exception("timer create failed");
6028 
6029 			mapping[fd] = this;
6030 
6031 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6032 
6033 			if(timerfd_settime(fd, 0, &value, null) == -1)
6034 				throw new Exception("couldn't make pulse timer");
6035 
6036 			version(with_eventloop) {
6037 				import arsd.eventloop;
6038 				addFileEventListeners(fd, &trigger, null, null);
6039 			} else {
6040 				prepareEventLoop();
6041 
6042 				ep.epoll_event ev = void;
6043 				ev.events = ep.EPOLLIN;
6044 				ev.data.fd = fd;
6045 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6046 			}
6047 		} else featureNotImplemented();
6048 	}
6049 
6050 	private int intervalInMilliseconds;
6051 
6052 	// just cuz I sometimes call it this.
6053 	alias dispose = destroy;
6054 
6055 	/// Stop and destroy the timer object.
6056 	void destroy() {
6057 		version(Windows) {
6058 			staticDestroy(handle);
6059 			handle = null;
6060 		} else version(linux) {
6061 			staticDestroy(fd);
6062 			fd = -1;
6063 		} else featureNotImplemented();
6064 	}
6065 
6066 	version(Windows)
6067 	static void staticDestroy(HANDLE handle) {
6068 		if(handle) {
6069 			// KillTimer(null, handle);
6070 			CancelWaitableTimer(cast(void*)handle);
6071 			mapping.remove(handle);
6072 			CloseHandle(handle);
6073 		}
6074 	}
6075 	else version(Emscripten)
6076 	static void staticDestroy(int fd) @system {
6077 		assert(0);
6078 	}
6079 	else version(linux)
6080 	static void staticDestroy(int fd) @system {
6081 		if(fd != -1) {
6082 			import unix = core.sys.posix.unistd;
6083 			static import ep = core.sys.linux.epoll;
6084 
6085 			version(with_eventloop) {
6086 				import arsd.eventloop;
6087 				removeFileEventListeners(fd);
6088 			} else {
6089 				ep.epoll_event ev = void;
6090 				ev.events = ep.EPOLLIN;
6091 				ev.data.fd = fd;
6092 
6093 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6094 			}
6095 			unix.close(fd);
6096 			mapping.remove(fd);
6097 		}
6098 	}
6099 
6100 	~this() {
6101 		version(Windows) { if(handle)
6102 			cleanupQueue.queue!staticDestroy(handle);
6103 		} else version(linux) { if(fd != -1)
6104 			cleanupQueue.queue!staticDestroy(fd);
6105 		}
6106 	}
6107 
6108 	void changeTime(int intervalInMilliseconds)
6109 	{
6110 		this.intervalInMilliseconds = intervalInMilliseconds;
6111 		version(Windows)
6112 		{
6113 			if(handle)
6114 			{
6115 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6116 				long initialTime = -intervalInMilliseconds;
6117 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6118 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
6119 			}
6120 		} else version(linux) {
6121 			import core.sys.linux.timerfd;
6122 
6123 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6124 			if(timerfd_settime(fd, 0, &value, null) == -1) {
6125 				throw new Exception("couldn't change pulse timer");
6126 			}
6127 		} else {
6128 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
6129 		}
6130 	}
6131 
6132 
6133 	private:
6134 
6135 	void delegate() onPulse;
6136 
6137 	int lastEventLoopRoundTriggered;
6138 
6139 	version(linux) {
6140 		static auto makeItimerspec(int intervalInMilliseconds) {
6141 			import core.sys.linux.timerfd;
6142 
6143 			itimerspec value;
6144 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6145 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6146 
6147 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6148 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6149 
6150 			return value;
6151 		}
6152 	}
6153 
6154 	void trigger() {
6155 		version(linux) {
6156 			import unix = core.sys.posix.unistd;
6157 			long val;
6158 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
6159 		} else version(Windows) {
6160 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
6161 				return; // never try to actually run faster than the event loop
6162 			lastEventLoopRoundTriggered = eventLoopRound;
6163 		} else featureNotImplemented();
6164 
6165 		onPulse();
6166 	}
6167 
6168 	version(Windows)
6169 	void rearm() {
6170 
6171 	}
6172 
6173 	version(Windows)
6174 		extern(Windows)
6175 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
6176 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
6177 			if(Timer* t = timer in mapping) {
6178 				try
6179 				(*t).trigger();
6180 				catch(Exception e) { sdpy_abort(e); assert(0); }
6181 			}
6182 		}
6183 
6184 	version(Windows) {
6185 		//UINT_PTR handle;
6186 		//static Timer[UINT_PTR] mapping;
6187 		HANDLE handle;
6188 		__gshared Timer[HANDLE] mapping;
6189 	} else version(linux) {
6190 		int fd = -1;
6191 		__gshared Timer[int] mapping;
6192 	} else version(OSXCocoa) {
6193 	} else static assert(0, "timer not supported");
6194 }
6195 }
6196 
6197 version(Windows)
6198 private int eventLoopRound;
6199 
6200 version(Windows)
6201 /// 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
6202 class WindowsHandleReader {
6203 	///
6204 	this(void delegate() onReady, HANDLE handle) {
6205 		this.onReady = onReady;
6206 		this.handle = handle;
6207 
6208 		mapping[handle] = this;
6209 
6210 		enable();
6211 	}
6212 
6213 	version(use_arsd_core)
6214 		ICoreEventLoop.UnregisterToken unregisterToken;
6215 
6216 	///
6217 	void enable() {
6218 		version(use_arsd_core) {
6219 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready));
6220 		} else {
6221 			auto el = EventLoop.get().impl;
6222 			el.handles ~= handle;
6223 		}
6224 	}
6225 
6226 	///
6227 	void disable() {
6228 		version(use_arsd_core) {
6229 			unregisterToken.unregister();
6230 		} else {
6231 			auto el = EventLoop.get().impl;
6232 			for(int i = 0; i < el.handles.length; i++) {
6233 				if(el.handles[i] is handle) {
6234 					el.handles[i] = el.handles[$-1];
6235 					el.handles = el.handles[0 .. $-1];
6236 					return;
6237 				}
6238 			}
6239 		}
6240 	}
6241 
6242 	void dispose() {
6243 		disable();
6244 		if(handle)
6245 			mapping.remove(handle);
6246 		handle = null;
6247 	}
6248 
6249 	void ready() {
6250 		if(onReady)
6251 			onReady();
6252 	}
6253 
6254 	HANDLE handle;
6255 	void delegate() onReady;
6256 
6257 	__gshared WindowsHandleReader[HANDLE] mapping;
6258 }
6259 
6260 version(Posix)
6261 /// Lets you add files to the event loop for reading. Use at your own risk.
6262 class PosixFdReader {
6263 	///
6264 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6265 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
6266 	}
6267 
6268 	///
6269 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6270 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
6271 	}
6272 
6273 	///
6274 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6275 		this.onReady = onReady;
6276 		this.fd = fd;
6277 		this.captureWrites = captureWrites;
6278 		this.captureReads = captureReads;
6279 
6280 		mapping[fd] = this;
6281 
6282 		version(with_eventloop) {
6283 			import arsd.eventloop;
6284 			addFileEventListeners(fd, &readyel);
6285 		} else {
6286 			enable();
6287 		}
6288 	}
6289 
6290 	bool captureReads;
6291 	bool captureWrites;
6292 
6293 	version(use_arsd_core) {
6294 		import arsd.core;
6295 		ICoreEventLoop.UnregisterToken unregisterToken;
6296 	}
6297 
6298 	version(with_eventloop) {} else
6299 	///
6300 	void enable() @system {
6301 		enabled = true;
6302 
6303 		version(use_arsd_core) {
6304 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper(
6305 				() { onReady(fd, true, false); }
6306 			));
6307 			// FIXME: what if it is writeable?
6308 
6309 		} else version(linux) {
6310 			prepareEventLoop();
6311 			static import ep = core.sys.linux.epoll;
6312 			ep.epoll_event ev = void;
6313 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6314 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
6315 			ev.data.fd = fd;
6316 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6317 		} else {
6318 
6319 		}
6320 	}
6321 
6322 	version(with_eventloop) {} else
6323 	///
6324 	void disable() @system {
6325 		enabled = false;
6326 
6327 		version(use_arsd_core) {
6328 			unregisterToken.unregister();
6329 		} else
6330 		version(linux) {
6331 			prepareEventLoop();
6332 			static import ep = core.sys.linux.epoll;
6333 			ep.epoll_event ev = void;
6334 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6335 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
6336 			ev.data.fd = fd;
6337 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6338 		}
6339 	}
6340 
6341 	version(with_eventloop) {} else
6342 	///
6343 	void dispose() {
6344 		if(enabled)
6345 			disable();
6346 		if(fd != -1)
6347 			mapping.remove(fd);
6348 		fd = -1;
6349 	}
6350 
6351 	void delegate(int, bool, bool) onReady;
6352 
6353 	version(with_eventloop)
6354 	void readyel() {
6355 		onReady(fd, true, true);
6356 	}
6357 
6358 	void ready(uint flags) {
6359 		version(Emscripten) {
6360 			assert(0);
6361 		} else version(linux) {
6362 			static import ep = core.sys.linux.epoll;
6363 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
6364 		} else {
6365 			import core.sys.posix.poll;
6366 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
6367 		}
6368 	}
6369 
6370 	void hup(uint flags) {
6371 		if(onHup)
6372 			onHup();
6373 	}
6374 
6375 	void delegate() onHup;
6376 
6377 	int fd = -1;
6378 	private bool enabled;
6379 	__gshared PosixFdReader[int] mapping;
6380 }
6381 
6382 // basic functions to access the clipboard
6383 /+
6384 
6385 
6386 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
6387 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
6388 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6389 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
6390 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
6391 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6392 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
6393 
6394 +/
6395 
6396 /++
6397 	this does a delegate because it is actually an async call on X...
6398 	the receiver may never be called if the clipboard is empty or unavailable
6399 	gets plain text from the clipboard.
6400 +/
6401 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system {
6402 	version(Windows) {
6403 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6404 		if(OpenClipboard(hwndOwner) == 0)
6405 			throw new WindowsApiException("OpenClipboard", GetLastError());
6406 		scope(exit)
6407 			CloseClipboard();
6408 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
6409 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
6410 
6411 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
6412 				scope(exit)
6413 					GlobalUnlock(dataHandle);
6414 
6415 				// FIXME: CR/LF conversions
6416 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
6417 				int len = 0;
6418 				auto d = data;
6419 				while(*d) {
6420 					d++;
6421 					len++;
6422 				}
6423 				string s;
6424 				s.reserve(len);
6425 				foreach(dchar ch; data[0 .. len]) {
6426 					s ~= ch;
6427 				}
6428 				receiver(s);
6429 			}
6430 		}
6431 	} else version(X11) {
6432 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6433 	} else version(OSXCocoa) {
6434 		throw new NotYetImplementedException();
6435 	} else version(Emscripten) {
6436 		throw new NotYetImplementedException();
6437 	} else static assert(0);
6438 }
6439 
6440 // FIXME: a clipboard listener might be cool btw
6441 
6442 /++
6443 	this does a delegate because it is actually an async call on X...
6444 	the receiver may never be called if the clipboard is empty or unavailable
6445 	gets image from the clipboard.
6446 
6447 	templated because it introduces an optional dependency on arsd.bmp
6448 +/
6449 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6450 	version(Windows) {
6451 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6452 		if(OpenClipboard(hwndOwner) == 0)
6453 			throw new WindowsApiException("OpenClipboard", GetLastError());
6454 		scope(exit)
6455 			CloseClipboard();
6456 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6457 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6458 				scope(exit)
6459 					GlobalUnlock(dataHandle);
6460 
6461 				auto len = GlobalSize(dataHandle);
6462 
6463 				import arsd.bmp;
6464 				auto img = readBmp(data[0 .. len], false);
6465 				receiver(img);
6466 			}
6467 		}
6468 	} else version(X11) {
6469 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6470 	} else version(OSXCocoa) {
6471 		throw new NotYetImplementedException();
6472 	} else version(Emscripten) {
6473 		throw new NotYetImplementedException();
6474 	} else static assert(0);
6475 }
6476 
6477 /// Copies some text to the clipboard.
6478 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6479 	assert(clipboardOwner !is null);
6480 	version(Windows) {
6481 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6482 			throw new WindowsApiException("OpenClipboard", GetLastError());
6483 		scope(exit)
6484 			CloseClipboard();
6485 		EmptyClipboard();
6486 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6487 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6488 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6489 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6490 			auto slice = data[0 .. sz];
6491 			scope(failure)
6492 				GlobalUnlock(handle);
6493 
6494 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6495 
6496 			GlobalUnlock(handle);
6497 			SetClipboardData(CF_UNICODETEXT, handle);
6498 		}
6499 	} else version(X11) {
6500 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6501 	} else version(OSXCocoa) {
6502 		throw new NotYetImplementedException();
6503 	} else version(Emscripten) {
6504 		throw new NotYetImplementedException();
6505 	} else static assert(0);
6506 }
6507 
6508 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6509 	assert(clipboardOwner !is null);
6510 	version(Windows) {
6511 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6512 			throw new WindowsApiException("OpenClipboard", GetLastError());
6513 		scope(exit)
6514 			CloseClipboard();
6515 		EmptyClipboard();
6516 
6517 
6518 		import arsd.bmp;
6519 		ubyte[] mdata;
6520 		mdata.reserve(img.width * img.height);
6521 		void sink(ubyte b) {
6522 			mdata ~= b;
6523 		}
6524 		writeBmpIndirect(img, &sink, false);
6525 
6526 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6527 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6528 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6529 			auto slice = data[0 .. mdata.length];
6530 			scope(failure)
6531 				GlobalUnlock(handle);
6532 
6533 			slice[] = mdata[];
6534 
6535 			GlobalUnlock(handle);
6536 			SetClipboardData(CF_DIB, handle);
6537 		}
6538 	} else version(X11) {
6539 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6540 			mixin X11SetSelectionHandler_Basics;
6541 			private const(ubyte)[] mdata;
6542 			private const(ubyte)[] mdata_original;
6543 			this(MemoryImage img) {
6544 				import arsd.bmp;
6545 
6546 				mdata.reserve(img.width * img.height);
6547 				void sink(ubyte b) {
6548 					mdata ~= b;
6549 				}
6550 				writeBmpIndirect(img, &sink, true);
6551 
6552 				mdata_original = mdata;
6553 			}
6554 
6555 			Atom[] availableFormats() {
6556 				auto display = XDisplayConnection.get;
6557 				return [
6558 					GetAtom!"image/bmp"(display),
6559 					GetAtom!"TARGETS"(display)
6560 				];
6561 			}
6562 
6563 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6564 				if(mdata.length < data.length) {
6565 					data[0 .. mdata.length] = mdata[];
6566 					auto ret = data[0 .. mdata.length];
6567 					mdata = mdata[$..$];
6568 					return ret;
6569 				} else {
6570 					data[] = mdata[0 .. data.length];
6571 					mdata = mdata[data.length .. $];
6572 					return data[];
6573 				}
6574 			}
6575 
6576 			void done() {
6577 				mdata = mdata_original;
6578 			}
6579 		}
6580 
6581 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6582 	} else version(OSXCocoa) {
6583 		throw new NotYetImplementedException();
6584 	} else version(Emscripten) {
6585 		throw new NotYetImplementedException();
6586 	} else static assert(0);
6587 }
6588 
6589 
6590 version(X11) {
6591 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6592 
6593 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6594 
6595 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6596 	/// Platform-specific for X11.
6597 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6598 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6599 		__gshared static Atom a;
6600 		if(!a) {
6601 			a = XInternAtom(display, name, !create);
6602 			// FIXME: might need to synchronize this and attach it to the actual object
6603 			interredAtoms ~= &a;
6604 		}
6605 		if(a == None)
6606 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6607 		return a;
6608 	}
6609 
6610 	/// Platform-specific for X11 - gets atom names as a string.
6611 	string getAtomName(Atom atom, Display* display) {
6612 		auto got = XGetAtomName(display, atom);
6613 		scope(exit) XFree(got);
6614 		import core.stdc.string;
6615 		string s = got[0 .. strlen(got)].idup;
6616 		return s;
6617 	}
6618 
6619 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6620 	void setPrimarySelection(SimpleWindow window, string text) {
6621 		setX11Selection!"PRIMARY"(window, text);
6622 	}
6623 
6624 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6625 	void setSecondarySelection(SimpleWindow window, string text) {
6626 		setX11Selection!"SECONDARY"(window, text);
6627 	}
6628 
6629 	interface X11SetSelectionHandler {
6630 		// should include TARGETS right now
6631 		Atom[] availableFormats();
6632 		// Return the slice of data you filled, empty slice if done.
6633 		// this is to support the incremental thing
6634 		ubyte[] getData(Atom format, return scope ubyte[] data);
6635 
6636 		void done();
6637 
6638 		void handleRequest(XEvent);
6639 
6640 		bool matchesIncr(Window, Atom);
6641 		void sendMoreIncr(XPropertyEvent*);
6642 	}
6643 
6644 	mixin template X11SetSelectionHandler_Basics() {
6645 		Window incrWindow;
6646 		Atom incrAtom;
6647 		Atom selectionAtom;
6648 		Atom formatAtom;
6649 		ubyte[] toSend;
6650 		bool matchesIncr(Window w, Atom a) {
6651 			return incrAtom && incrAtom == a && w == incrWindow;
6652 		}
6653 		void sendMoreIncr(XPropertyEvent* event) {
6654 			auto display = XDisplayConnection.get;
6655 
6656 			XChangeProperty (display,
6657 				incrWindow,
6658 				incrAtom,
6659 				formatAtom,
6660 				8 /* bits */, PropModeReplace,
6661 				toSend.ptr, cast(int) toSend.length);
6662 
6663 			if(toSend.length != 0) {
6664 				toSend = this.getData(formatAtom, toSend[]);
6665 			} else {
6666 				this.done();
6667 				incrWindow = None;
6668 				incrAtom = None;
6669 				selectionAtom = None;
6670 				formatAtom = None;
6671 				toSend = null;
6672 			}
6673 		}
6674 		void handleRequest(XEvent ev) {
6675 
6676 			auto display = XDisplayConnection.get;
6677 
6678 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6679 			XSelectionEvent selectionEvent;
6680 			selectionEvent.type = EventType.SelectionNotify;
6681 			selectionEvent.display = event.display;
6682 			selectionEvent.requestor = event.requestor;
6683 			selectionEvent.selection = event.selection;
6684 			selectionEvent.time = event.time;
6685 			selectionEvent.target = event.target;
6686 
6687 			bool supportedType() {
6688 				foreach(t; this.availableFormats())
6689 					if(t == event.target)
6690 						return true;
6691 				return false;
6692 			}
6693 
6694 			if(event.property == None) {
6695 				selectionEvent.property = event.target;
6696 
6697 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6698 				XFlush(display);
6699 			} if(event.target == GetAtom!"TARGETS"(display)) {
6700 				/* respond with the supported types */
6701 				auto tlist = this.availableFormats();
6702 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6703 				selectionEvent.property = event.property;
6704 
6705 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6706 				XFlush(display);
6707 			} else if(supportedType()) {
6708 				auto buffer = new ubyte[](1024 * 64);
6709 				auto toSend = this.getData(event.target, buffer[]);
6710 
6711 				if(toSend.length < 32 * 1024) {
6712 					// small enough to send directly...
6713 					selectionEvent.property = event.property;
6714 					XChangeProperty (display,
6715 						selectionEvent.requestor,
6716 						selectionEvent.property,
6717 						event.target,
6718 						8 /* bits */, 0 /* PropModeReplace */,
6719 						toSend.ptr, cast(int) toSend.length);
6720 
6721 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6722 					XFlush(display);
6723 				} else {
6724 					// large, let's send incrementally
6725 					arch_ulong l = toSend.length;
6726 
6727 					// if I wanted other events from this window don't want to clear that out....
6728 					XWindowAttributes xwa;
6729 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6730 
6731 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6732 
6733 					incrWindow = event.requestor;
6734 					incrAtom = event.property;
6735 					formatAtom = event.target;
6736 					selectionAtom = event.selection;
6737 					this.toSend = toSend;
6738 
6739 					selectionEvent.property = event.property;
6740 					XChangeProperty (display,
6741 						selectionEvent.requestor,
6742 						selectionEvent.property,
6743 						GetAtom!"INCR"(display),
6744 						32 /* bits */, PropModeReplace,
6745 						&l, 1);
6746 
6747 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6748 					XFlush(display);
6749 				}
6750 				//if(after)
6751 					//after();
6752 			} else {
6753 				debug(sdpy_clip) {
6754 					writeln("Unsupported data ", getAtomName(event.target, display));
6755 				}
6756 				selectionEvent.property = None; // I don't know how to handle this type...
6757 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6758 				XFlush(display);
6759 			}
6760 		}
6761 	}
6762 
6763 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6764 		mixin X11SetSelectionHandler_Basics;
6765 		private const(ubyte)[] text;
6766 		private const(ubyte)[] text_original;
6767 		this(string text) {
6768 			this.text = cast(const ubyte[]) text;
6769 			this.text_original = this.text;
6770 		}
6771 		Atom[] availableFormats() {
6772 			auto display = XDisplayConnection.get;
6773 			return [
6774 				GetAtom!"UTF8_STRING"(display),
6775 				GetAtom!"text/plain"(display),
6776 				XA_STRING,
6777 				GetAtom!"TARGETS"(display)
6778 			];
6779 		}
6780 
6781 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6782 			if(text.length < data.length) {
6783 				data[0 .. text.length] = text[];
6784 				return data[0 .. text.length];
6785 			} else {
6786 				data[] = text[0 .. data.length];
6787 				text = text[data.length .. $];
6788 				return data[];
6789 			}
6790 		}
6791 
6792 		void done() {
6793 			text = text_original;
6794 		}
6795 	}
6796 
6797 	/// 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?!)
6798 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6799 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6800 	}
6801 
6802 	private __gshared bool mightShortCircuitClipboard;
6803 
6804 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6805 		assert(window !is null);
6806 
6807 		auto display = XDisplayConnection.get();
6808 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6809 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6810 		else Atom a = GetAtom!atomName(display);
6811 
6812 		if(mightShortCircuitClipboard)
6813 		if(auto ptr = a in window.impl.setSelectionHandlers) {
6814 			// we already have it, don't even need to inform the X server
6815 			// sdpyPrintDebugString("short circuit in set");
6816 			*ptr = data;
6817 			return;
6818 		}
6819 
6820 		// we don't have it, tell X we want it
6821 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6822 		window.impl.setSelectionHandlers[a] = data;
6823 		mightShortCircuitClipboard = true;
6824 	}
6825 
6826 	/+
6827 	/++
6828 		History:
6829 			Added September 28, 2024
6830 	+/
6831 	bool hasX11Selection(string atomName)(SimpleWindow window) {
6832 		auto display = XDisplayConnection.get();
6833 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6834 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6835 		else Atom a = GetAtom!atomName(display);
6836 
6837 		if(a in window.impl.setSelectionHandlers)
6838 			return true;
6839 		else
6840 			return false;
6841 	}
6842 	+/
6843 
6844 	///
6845 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6846 		getX11Selection!"PRIMARY"(window, handler);
6847 	}
6848 
6849 	// added July 28, 2020
6850 	// undocumented as experimental tho
6851 	interface X11GetSelectionHandler {
6852 		void handleData(Atom target, in ubyte[] data);
6853 		Atom findBestFormat(Atom[] answer);
6854 
6855 		void prepareIncremental(Window, Atom);
6856 		bool matchesIncr(Window, Atom);
6857 		void handleIncrData(Atom, in ubyte[] data);
6858 	}
6859 
6860 	mixin template X11GetSelectionHandler_Basics() {
6861 		Window incrWindow;
6862 		Atom incrAtom;
6863 
6864 		void prepareIncremental(Window w, Atom a) {
6865 			incrWindow = w;
6866 			incrAtom = a;
6867 		}
6868 		bool matchesIncr(Window w, Atom a) {
6869 			return incrWindow == w && incrAtom == a;
6870 		}
6871 
6872 		Atom incrFormatAtom;
6873 		ubyte[] incrData;
6874 		void handleIncrData(Atom format, in ubyte[] data) {
6875 			incrFormatAtom = format;
6876 
6877 			if(data.length)
6878 				incrData ~= data;
6879 			else
6880 				handleData(incrFormatAtom, incrData);
6881 
6882 		}
6883 	}
6884 
6885 	static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6886 		this(void delegate(in char[]) handler) {
6887 			this.handler = handler;
6888 		}
6889 
6890 		mixin X11GetSelectionHandler_Basics;
6891 
6892 		void delegate(in char[]) handler;
6893 
6894 		void handleData(Atom target, in ubyte[] data) {
6895 			// import std.stdio; writeln(target, " ", data);
6896 			if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6897 				handler(cast(const char[]) data);
6898 			else if(target == None && data is null)
6899 				handler(null); // no suitable selection exists
6900 		}
6901 
6902 		Atom findBestFormat(Atom[] answer) {
6903 			Atom best = None;
6904 			foreach(option; answer) {
6905 				if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6906 					best = option;
6907 					break;
6908 				} else if(option == XA_STRING) {
6909 					best = option;
6910 				} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6911 					best = option;
6912 				}
6913 			}
6914 			return best;
6915 		}
6916 	}
6917 
6918 	///
6919 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6920 		assert(window !is null);
6921 
6922 		auto display = XDisplayConnection.get();
6923 
6924 		static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY;
6925 		else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY;
6926 		else Atom atom = GetAtom!atomName(display);
6927 
6928 		if(mightShortCircuitClipboard)
6929 		if(auto ptr = atom in window.impl.setSelectionHandlers) {
6930 			if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) {
6931 				// we already have it! short circuit everything
6932 
6933 				// sdpyPrintDebugString("short circuit in get");
6934 				handler(cast(char[]) txt.text_original);
6935 				return;
6936 			}
6937 		}
6938 
6939 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6940 
6941 		auto target = GetAtom!"TARGETS"(display);
6942 
6943 		// SDD_DATA is "simpledisplay.d data"
6944 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6945 	}
6946 
6947 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6948 	/// only supports bmps. using this function will import arsd.bmp.
6949 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6950 		assert(window !is null);
6951 
6952 		auto display = XDisplayConnection.get();
6953 		auto atom = GetAtom!atomName(display);
6954 
6955 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6956 			this(void delegate(MemoryImage) handler) {
6957 				this.handler = handler;
6958 			}
6959 
6960 			mixin X11GetSelectionHandler_Basics;
6961 
6962 			void delegate(MemoryImage) handler;
6963 
6964 			void handleData(Atom target, in ubyte[] data) {
6965 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6966 					import arsd.bmp;
6967 					handler(readBmp(data));
6968 				}
6969 			}
6970 
6971 			Atom findBestFormat(Atom[] answer) {
6972 				Atom best = None;
6973 				foreach(option; answer) {
6974 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6975 						best = option;
6976 					}
6977 				}
6978 				return best;
6979 			}
6980 
6981 		}
6982 
6983 
6984 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6985 
6986 		auto target = GetAtom!"TARGETS"(display);
6987 
6988 		// SDD_DATA is "simpledisplay.d data"
6989 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6990 	}
6991 
6992 
6993 	///
6994 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6995 		Atom actualType;
6996 		int actualFormat;
6997 		arch_ulong actualItems;
6998 		arch_ulong bytesRemaining;
6999 		void* data;
7000 
7001 		auto display = XDisplayConnection.get();
7002 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
7003 			if(actualFormat == 0)
7004 				return null;
7005 			else {
7006 				int byteLength;
7007 				if(actualFormat == 32) {
7008 					// 32 means it is a C long... which is variable length
7009 					actualFormat = cast(int) arch_long.sizeof * 8;
7010 				}
7011 
7012 				// then it is just a bit count
7013 				byteLength = cast(int) (actualItems * actualFormat / 8);
7014 
7015 				auto d = new ubyte[](byteLength);
7016 				d[] = cast(ubyte[]) data[0 .. byteLength];
7017 				XFree(data);
7018 				return d;
7019 			}
7020 		}
7021 		return null;
7022 	}
7023 
7024 	/* defined in the systray spec */
7025 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
7026 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
7027 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
7028 
7029 
7030 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
7031 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
7032 	public class GlobalHotkey {
7033 		KeyEvent key;
7034 		void delegate () handler;
7035 
7036 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
7037 
7038 		/// Create from initialzed KeyEvent object
7039 		this (KeyEvent akey, void delegate () ahandler=null) {
7040 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
7041 			key = akey;
7042 			handler = ahandler;
7043 		}
7044 
7045 		/// Create from emacs-like key name ("C-M-Y", etc.)
7046 		this (const(char)[] akey, void delegate () ahandler=null) {
7047 			key = KeyEvent.parse(akey);
7048 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
7049 			handler = ahandler;
7050 		}
7051 
7052 	}
7053 
7054 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7055 		//conwriteln("failed to grab key");
7056 		GlobalHotkeyManager.ghfailed = true;
7057 		return 0;
7058 	}
7059 
7060 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7061 		Image.impl.xshmfailed = true;
7062 		return 0;
7063 	}
7064 
7065 	private __gshared int errorHappened;
7066 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7067 		import core.stdc.stdio;
7068 		char[265] buffer;
7069 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
7070 		debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, cast(long) evt.serial, evt.request_code, evt.minor_code, cast(long) evt.resourceid);
7071 		errorHappened = true;
7072 		return 0;
7073 	}
7074 
7075 	/++
7076 		Global hotkey manager. It contains static methods to manage global hotkeys.
7077 
7078 		---
7079 		 try {
7080 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
7081 		} catch (Exception e) {
7082 			conwriteln("ERROR registering hotkey!");
7083 		}
7084 		EventLoop.get.run();
7085 		---
7086 
7087 		The key strings are based on Emacs. In practical terms,
7088 		`M` means `alt` and `H` means the Windows logo key. `C`
7089 		is `ctrl`.
7090 
7091 		$(WARNING
7092 			This is X-specific right now. If you are on
7093 			Windows, try [registerHotKey] instead.
7094 
7095 			We will probably merge these into a single
7096 			interface later.
7097 		)
7098 	+/
7099 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
7100 		version(X11) {
7101 			void recreateAfterDisconnect() {
7102 				throw new Exception("NOT IMPLEMENTED");
7103 			}
7104 			void discardConnectionState() {
7105 				throw new Exception("NOT IMPLEMENTED");
7106 			}
7107 		}
7108 
7109 		private static immutable uint[8] masklist = [ 0,
7110 			KeyOrButtonMask.LockMask,
7111 			KeyOrButtonMask.Mod2Mask,
7112 			KeyOrButtonMask.Mod3Mask,
7113 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
7114 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
7115 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7116 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7117 		];
7118 		private __gshared GlobalHotkeyManager ghmanager;
7119 		private __gshared bool ghfailed = false;
7120 
7121 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
7122 			if (modmask == 0) return false;
7123 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
7124 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
7125 			return true;
7126 		}
7127 
7128 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
7129 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
7130 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
7131 			return modmask;
7132 		}
7133 
7134 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
7135 			uint keycode = cast(uint)ke.key;
7136 			auto dpy = XDisplayConnection.get;
7137 			return XKeysymToKeycode(dpy, keycode);
7138 		}
7139 
7140 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
7141 
7142 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
7143 
7144 		NativeEventHandler getNativeEventHandler () {
7145 			return delegate int (XEvent e) {
7146 				if (e.type != EventType.KeyPress) return 1;
7147 				auto kev = cast(const(XKeyEvent)*)&e;
7148 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
7149 				if (auto ghkp = hash in globalHotkeyList) {
7150 					try {
7151 						ghkp.doHandle();
7152 					} catch (Exception e) {
7153 						import core.stdc.stdio : stderr, fprintf;
7154 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
7155 					}
7156 				}
7157 				return 1;
7158 			};
7159 		}
7160 
7161 		private this () {
7162 			auto dpy = XDisplayConnection.get;
7163 			auto root = RootWindow(dpy, DefaultScreen(dpy));
7164 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
7165 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
7166 		}
7167 
7168 		/// Register new global hotkey with initialized `GlobalHotkey` object.
7169 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
7170 		static void register (GlobalHotkey gh) {
7171 			if (gh is null) return;
7172 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
7173 
7174 			auto dpy = XDisplayConnection.get;
7175 			immutable keycode = keyEvent2KeyCode(gh.key);
7176 
7177 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
7178 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
7179 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
7180 			XSync(dpy, 0/*False*/);
7181 
7182 			Window root = RootWindow(dpy, DefaultScreen(dpy));
7183 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7184 			ghfailed = false;
7185 			foreach (immutable uint ormask; masklist[]) {
7186 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
7187 			}
7188 			XSync(dpy, 0/*False*/);
7189 			XSetErrorHandler(savedErrorHandler);
7190 
7191 			if (ghfailed) {
7192 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7193 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
7194 				XSync(dpy, 0/*False*/);
7195 				XSetErrorHandler(savedErrorHandler);
7196 				throw new Exception("cannot register global hotkey");
7197 			}
7198 
7199 			globalHotkeyList[hash] = gh;
7200 		}
7201 
7202 		/// Ditto
7203 		static void register (const(char)[] akey, void delegate () ahandler) {
7204 			register(new GlobalHotkey(akey, ahandler));
7205 		}
7206 
7207 		private static void removeByHash (ulong hash) {
7208 			if (auto ghp = hash in globalHotkeyList) {
7209 				auto dpy = XDisplayConnection.get;
7210 				immutable keycode = keyEvent2KeyCode(ghp.key);
7211 				Window root = RootWindow(dpy, DefaultScreen(dpy));
7212 				XSync(dpy, 0/*False*/);
7213 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7214 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
7215 				XSync(dpy, 0/*False*/);
7216 				XSetErrorHandler(savedErrorHandler);
7217 				globalHotkeyList.remove(hash);
7218 			}
7219 		}
7220 
7221 		/// Register new global hotkey with previously used `GlobalHotkey` object.
7222 		/// It is safe to unregister unknown or invalid hotkey.
7223 		static void unregister (GlobalHotkey gh) {
7224 			//TODO: add second AA for faster search? prolly doesn't worth it.
7225 			if (gh is null) return;
7226 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
7227 				if (kv.value is gh) {
7228 					removeByHash(kv.key);
7229 					return;
7230 				}
7231 			}
7232 		}
7233 
7234 		/// Ditto.
7235 		static void unregister (const(char)[] key) {
7236 			auto kev = KeyEvent.parse(key);
7237 			immutable keycode = keyEvent2KeyCode(kev);
7238 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
7239 		}
7240 	}
7241 }
7242 
7243 version(Windows) {
7244 	/++
7245 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
7246 
7247 		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).
7248 	+/
7249 	void sendSyntheticInput(wstring s) {
7250 			INPUT[] inputs;
7251 			inputs.reserve(s.length * 2);
7252 
7253 			foreach(wchar c; s) {
7254 				INPUT input;
7255 				input.type = INPUT_KEYBOARD;
7256 				input.ki.wScan = c;
7257 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7258 				inputs ~= input;
7259 
7260 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7261 				inputs ~= input;
7262 			}
7263 
7264 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7265 				throw new WindowsApiException("SendInput", GetLastError());
7266 			}
7267 
7268 	}
7269 
7270 
7271 	// global hotkey helper function
7272 
7273 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
7274 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system {
7275 		__gshared int hotkeyId = 0;
7276 		int id = ++hotkeyId;
7277 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
7278 			throw new Exception("RegisterHotKey");
7279 
7280 		__gshared void delegate()[WPARAM][HWND] handlers;
7281 
7282 		handlers[window.impl.hwnd][id] = handler;
7283 
7284 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
7285 
7286 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7287 			switch(msg) {
7288 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
7289 				case WM_HOTKEY:
7290 					if(auto list = hwnd in handlers) {
7291 						if(auto h = wParam in *list) {
7292 							(*h)();
7293 							return 0;
7294 						}
7295 					}
7296 				goto default;
7297 				default:
7298 			}
7299 			if(oldHandler)
7300 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
7301 			return 1; // pass it on
7302 		};
7303 
7304 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
7305 			oldHandler = window.handleNativeEvent;
7306 			window.handleNativeEvent = nativeEventHandler;
7307 		}
7308 
7309 		return id;
7310 	}
7311 
7312 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
7313 	void unregisterHotKey(SimpleWindow window, int id) {
7314 		if(!UnregisterHotKey(window.impl.hwnd, id))
7315 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
7316 	}
7317 }
7318 
7319 version (X11) {
7320 	pragma(lib, "dl");
7321 	import core.sys.posix.dlfcn;
7322 }
7323 
7324 /++
7325 	Allows for sending synthetic input to the X server via the Xtst
7326 	extension or on Windows using SendInput.
7327 
7328 	Please remember user input is meant to be user - don't use this
7329 	if you have some other alternative!
7330 
7331 	History:
7332 		Added May 17, 2020 with the X implementation.
7333 
7334 		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.)
7335 	Bugs:
7336 		All methods on OSX Cocoa will throw not yet implemented exceptions.
7337 +/
7338 struct SyntheticInput {
7339 	@disable this();
7340 
7341 	private int* refcount;
7342 
7343 	version(X11) {
7344 		private void* lib;
7345 
7346 		private extern(C) {
7347 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
7348 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
7349 		}
7350 	}
7351 
7352 	/// The dummy param must be 0.
7353 	this(int dummy) {
7354 		version(X11) {
7355 			lib = dlopen("libXtst.so", RTLD_NOW);
7356 			if(lib is null)
7357 				throw new Exception("cannot load xtest lib extension");
7358 			scope(failure)
7359 				dlclose(lib);
7360 
7361 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
7362 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
7363 
7364 			if(XTestFakeKeyEvent is null)
7365 				throw new Exception("No XTestFakeKeyEvent");
7366 			if(XTestFakeButtonEvent is null)
7367 				throw new Exception("No XTestFakeButtonEvent");
7368 		}
7369 
7370 		refcount = new int;
7371 		*refcount = 1;
7372 	}
7373 
7374 	this(this) {
7375 		if(refcount)
7376 			*refcount += 1;
7377 	}
7378 
7379 	~this() {
7380 		if(refcount) {
7381 			*refcount -= 1;
7382 			if(*refcount == 0)
7383 				// I commented this because if I close the lib before
7384 				// XCloseDisplay, it is liable to segfault... so just
7385 				// gonna keep it loaded if it is loaded, no big deal
7386 				// anyway.
7387 				{} // dlclose(lib);
7388 		}
7389 	}
7390 
7391 	/++
7392 		Simulates typing a string into the keyboard.
7393 
7394 		Bugs:
7395 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
7396 
7397 			Not implemented except on Windows and X11.
7398 	+/
7399 	void sendSyntheticInput(string s) {
7400 		version(Windows) {
7401 			INPUT[] inputs;
7402 			inputs.reserve(s.length * 2);
7403 
7404 			auto ei = GetMessageExtraInfo();
7405 
7406 			foreach(wchar c; s) {
7407 				INPUT input;
7408 				input.type = INPUT_KEYBOARD;
7409 				input.ki.wScan = c;
7410 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7411 				input.ki.dwExtraInfo = ei;
7412 				inputs ~= input;
7413 
7414 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7415 				inputs ~= input;
7416 			}
7417 
7418 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7419 				throw new WindowsApiException("SendInput", GetLastError());
7420 			}
7421 		} else version(X11) {
7422 			int delay = 0;
7423 			foreach(ch; s) {
7424 				pressKey(cast(Key) ch, true, delay);
7425 				pressKey(cast(Key) ch, false, delay);
7426 				delay += 5;
7427 			}
7428 		} else throw new NotYetImplementedException();
7429 	}
7430 
7431 	/++
7432 		Sends a fake press or release key event.
7433 
7434 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7435 
7436 		Bugs:
7437 			The `delay` parameter is not implemented yet on Windows.
7438 
7439 			Not implemented except on Windows and X11.
7440 	+/
7441 	void pressKey(Key key, bool pressed, int delay = 0) {
7442 		version(Windows) {
7443 			INPUT input;
7444 			input.type = INPUT_KEYBOARD;
7445 			input.ki.wVk = cast(ushort) key;
7446 
7447 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
7448 			input.ki.dwExtraInfo = GetMessageExtraInfo();
7449 
7450 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7451 				throw new WindowsApiException("SendInput", GetLastError());
7452 			}
7453 		} else version(X11) {
7454 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
7455 		} else throw new NotYetImplementedException();
7456 	}
7457 
7458 	/++
7459 		Sends a fake mouse button press or release event.
7460 
7461 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7462 
7463 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
7464 
7465 		Bugs:
7466 			The `delay` parameter is not implemented yet on Windows.
7467 
7468 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
7469 
7470 			All arguments will throw NotYetImplementedException on OSX Cocoa.
7471 	+/
7472 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
7473 		version(Windows) {
7474 			INPUT input;
7475 			input.type = INPUT_MOUSE;
7476 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7477 
7478 			// input.mi.mouseData for a wheel event
7479 
7480 			switch(button) {
7481 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
7482 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
7483 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
7484 				case MouseButton.wheelUp:
7485 				case MouseButton.wheelDown:
7486 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
7487 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
7488 				break;
7489 				case MouseButton.backButton: throw new NotYetImplementedException();
7490 				case MouseButton.forwardButton: throw new NotYetImplementedException();
7491 				default:
7492 			}
7493 
7494 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7495 				throw new WindowsApiException("SendInput", GetLastError());
7496 			}
7497 		} else version(X11) {
7498 			int btn;
7499 
7500 			switch(button) {
7501 				case MouseButton.left: btn = 1; break;
7502 				case MouseButton.middle: btn = 2; break;
7503 				case MouseButton.right: btn = 3; break;
7504 				case MouseButton.wheelUp: btn = 4; break;
7505 				case MouseButton.wheelDown: btn = 5; break;
7506 				case MouseButton.backButton: btn = 8; break;
7507 				case MouseButton.forwardButton: btn = 9; break;
7508 				default:
7509 			}
7510 
7511 			assert(btn);
7512 
7513 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7514 		} else throw new NotYetImplementedException();
7515 	}
7516 
7517 	///
7518 	static void moveMouseArrowBy(int dx, int dy) {
7519 		version(Windows) {
7520 			INPUT input;
7521 			input.type = INPUT_MOUSE;
7522 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7523 			input.mi.dx = dx;
7524 			input.mi.dy = dy;
7525 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7526 
7527 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7528 				throw new WindowsApiException("SendInput", GetLastError());
7529 			}
7530 		} else version(X11) {
7531 			auto disp = XDisplayConnection.get();
7532 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7533 			XFlush(disp);
7534 		} else throw new NotYetImplementedException();
7535 	}
7536 
7537 	///
7538 	static void moveMouseArrowTo(int x, int y) {
7539 		version(Windows) {
7540 			INPUT input;
7541 			input.type = INPUT_MOUSE;
7542 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7543 			input.mi.dx = x;
7544 			input.mi.dy = y;
7545 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7546 
7547 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7548 				throw new WindowsApiException("SendInput", GetLastError());
7549 			}
7550 		} else version(X11) {
7551 			auto disp = XDisplayConnection.get();
7552 			auto root = RootWindow(disp, DefaultScreen(disp));
7553 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7554 			XFlush(disp);
7555 		} else throw new NotYetImplementedException();
7556 	}
7557 }
7558 
7559 
7560 
7561 /++
7562 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7563 
7564 	See_Also:
7565 	$(LIST
7566 		*[ScreenPainter]
7567 		*[ScreenPainter.rasterOp]
7568 	)
7569 +/
7570 enum RasterOp {
7571 	normal, /// Replaces the pixel.
7572 	xor, /// Uses bitwise xor to draw.
7573 }
7574 
7575 // being phobos-free keeps the size WAY down
7576 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7577 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7578 package(arsd) const(wchar)* toWStringz(string s) {
7579 	wstring r;
7580 	foreach(dchar c; s)
7581 		r ~= c;
7582 	r ~= '\0';
7583 	return r.ptr;
7584 }
7585 private string[] split(in void[] a, char c) {
7586 		string[] ret;
7587 		size_t previous = 0;
7588 		foreach(i, char ch; cast(ubyte[]) a) {
7589 			if(ch == c) {
7590 				ret ~= cast(string) a[previous .. i];
7591 				previous = i + 1;
7592 			}
7593 		}
7594 		if(previous != a.length)
7595 			ret ~= cast(string) a[previous .. $];
7596 		return ret;
7597 	}
7598 
7599 version(without_opengl) {
7600 	enum OpenGlOptions {
7601 		no,
7602 	}
7603 } else {
7604 	/++
7605 		Determines if you want an OpenGL context created on the new window.
7606 
7607 
7608 		See more: [#topics-3d|in the 3d topic].
7609 
7610 		---
7611 		import arsd.simpledisplay;
7612 		void main() {
7613 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7614 
7615 			// Set up the matrix
7616 			window.setAsCurrentOpenGlContext(); // make this window active
7617 
7618 			// This is called on each frame, we will draw our scene
7619 			window.redrawOpenGlScene = delegate() {
7620 
7621 			};
7622 
7623 			window.eventLoop(0);
7624 		}
7625 		---
7626 	+/
7627 	enum OpenGlOptions {
7628 		no, /// No OpenGL context is created
7629 		yes, /// Yes, create an OpenGL context
7630 	}
7631 
7632 	version(X11) {
7633 		static if (!SdpyIsUsingIVGLBinds) {
7634 
7635 
7636 			struct __GLXFBConfigRec {}
7637 			alias GLXFBConfig = __GLXFBConfigRec*;
7638 
7639 			//pragma(lib, "GL");
7640 			//pragma(lib, "GLU");
7641 			interface GLX {
7642 			extern(C) nothrow @nogc {
7643 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7644 						const int *attrib_list);
7645 
7646 				 void glXCopyContext(Display *dpy, GLXContext src,
7647 						GLXContext dst, arch_ulong mask);
7648 
7649 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7650 						GLXContext share_list, Bool direct);
7651 
7652 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7653 						Pixmap pixmap);
7654 
7655 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7656 
7657 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7658 
7659 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7660 						int attrib, int *value);
7661 
7662 				 GLXContext glXGetCurrentContext();
7663 
7664 				 GLXDrawable glXGetCurrentDrawable();
7665 
7666 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7667 
7668 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7669 						GLXContext ctx);
7670 
7671 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7672 
7673 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7674 
7675 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7676 
7677 				 void glXUseXFont(Font font, int first, int count, int list_base);
7678 
7679 				 void glXWaitGL();
7680 
7681 				 void glXWaitX();
7682 
7683 
7684 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7685 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7686 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7687 
7688 				char* glXQueryExtensionsString (Display*, int);
7689 				void* glXGetProcAddress (const(char)*);
7690 
7691 			}
7692 			}
7693 
7694 			version(OSX)
7695 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7696 			else
7697 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7698 			shared static this() {
7699 				glx.loadDynamicLibrary();
7700 			}
7701 
7702 			alias glbindGetProcAddress = glXGetProcAddress;
7703 		}
7704 	} else version(Windows) {
7705 		/* it is done below by interface GL */
7706 	} else
7707 		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.");
7708 }
7709 
7710 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7711 alias Resizablity = Resizability;
7712 
7713 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7714 enum Resizability {
7715 	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.
7716 	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.
7717 	/++
7718 		$(PITFALL
7719 			Planned for the future but not implemented.
7720 		)
7721 
7722 		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.
7723 
7724 		History:
7725 			Added November 11, 2022, but not yet implemented and may not be for some time.
7726 	+/
7727 	/*@__future*/ allowResizingMaintainingAspectRatio,
7728 	/++
7729 		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.
7730 
7731 		History:
7732 			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.
7733 
7734 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7735 	+/
7736 	automaticallyScaleIfPossible,
7737 }
7738 /// ditto
7739 alias Resizeability = Resizability;
7740 
7741 
7742 /++
7743 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7744 +/
7745 enum TextAlignment : uint {
7746 	Left = 0, ///
7747 	Center = 1, ///
7748 	Right = 2, ///
7749 
7750 	VerticalTop = 0, ///
7751 	VerticalCenter = 4, ///
7752 	VerticalBottom = 8, ///
7753 }
7754 
7755 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7756 alias Rectangle = arsd.color.Rectangle;
7757 
7758 
7759 /++
7760 	Keyboard press and release events.
7761 +/
7762 struct KeyEvent {
7763 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7764 	Key key;
7765 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7766 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7767 
7768 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7769 
7770 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7771 
7772 	SimpleWindow window; /// associated Window
7773 
7774 	/++
7775 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7776 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7777 		to predict if char events are actually coming..
7778 
7779 		Only available on X systems since this information is not given ahead of time elsewhere.
7780 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7781 
7782 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7783 		and potential quirks I'd recommend avoiding it.
7784 
7785 		History:
7786 			Added April 26, 2021 (dub v9.5)
7787 	+/
7788 	version(X11)
7789 		dchar[] charsPossible;
7790 
7791 	// convert key event to simplified string representation a-la emacs
7792 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7793 		uint dpos = 0;
7794 		void put (const(char)[] s...) nothrow @trusted {
7795 			static if (growdest) {
7796 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7797 			} else {
7798 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7799 			}
7800 		}
7801 
7802 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7803 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7804 		}
7805 
7806 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7807 
7808 		// put modifiers
7809 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7810 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7811 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7812 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7813 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7814 
7815 		if (this.key) {
7816 			foreach (string kn; __traits(allMembers, Key)) {
7817 				if (this.key == __traits(getMember, Key, kn)) {
7818 					// HACK!
7819 					static if (kn == "N0") put("0");
7820 					else static if (kn == "N1") put("1");
7821 					else static if (kn == "N2") put("2");
7822 					else static if (kn == "N3") put("3");
7823 					else static if (kn == "N4") put("4");
7824 					else static if (kn == "N5") put("5");
7825 					else static if (kn == "N6") put("6");
7826 					else static if (kn == "N7") put("7");
7827 					else static if (kn == "N8") put("8");
7828 					else static if (kn == "N9") put("9");
7829 					else put(kn);
7830 					return dest[0..dpos];
7831 				}
7832 			}
7833 			put("Unknown");
7834 		} else {
7835 			if (dpos && dest[dpos-1] == '+') --dpos;
7836 		}
7837 		return dest[0..dpos];
7838 	}
7839 
7840 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7841 
7842 	/** Parse string into key name with modifiers. It accepts things like:
7843 	 *
7844 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7845 	 *
7846 	 * Ctrl+Win+1 -- windows style
7847 	 *
7848 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7849 	 *
7850 	 * Ctrl Win 1 -- and space
7851 	 *
7852 	 * and even "Win + 1 + Ctrl".
7853 	 */
7854 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7855 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7856 
7857 		// remove trailing spaces
7858 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7859 
7860 		// tokens delimited by blank, '+', or '-'
7861 		// null on eol
7862 		const(char)[] getToken () nothrow @trusted @nogc {
7863 			// remove leading spaces and delimiters
7864 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7865 			if (name.length == 0) return null; // oops, no more tokens
7866 			// get token
7867 			size_t epos = 0;
7868 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7869 			assert(epos > 0 && epos <= name.length);
7870 			auto res = name[0..epos];
7871 			name = name[epos..$];
7872 			return res;
7873 		}
7874 
7875 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7876 			if (s0.length != s1.length) return false;
7877 			foreach (immutable ci, char c0; s0) {
7878 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7879 				char c1 = s1[ci];
7880 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7881 				if (c0 != c1) return false;
7882 			}
7883 			return true;
7884 		}
7885 
7886 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7887 		if (updown !is null) *updown = -1;
7888 		KeyEvent res;
7889 		res.key = cast(Key)0; // just in case
7890 		const(char)[] tk, tkn; // last token
7891 		bool allowEmascStyle = true;
7892 		bool ignoreModifiers = false;
7893 		tokenloop: for (;;) {
7894 			tk = tkn;
7895 			tkn = getToken();
7896 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7897 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7898 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7899 			if (allowEmascStyle && tkn.length != 0) {
7900 				if (tk.length == 1) {
7901 					char mdc = tk[0];
7902 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7903 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7904 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7905 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7906 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7907 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7908 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7909 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7910 				}
7911 			}
7912 			allowEmascStyle = false;
7913 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7914 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7915 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7916 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7917 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7918 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7919 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7920 			if (tk.length == 0) continue;
7921 			// try key name
7922 			if (res.key == 0) {
7923 				// little hack
7924 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7925 					final switch (tk[0]) {
7926 						case '0': tk = "N0"; break;
7927 						case '1': tk = "N1"; break;
7928 						case '2': tk = "N2"; break;
7929 						case '3': tk = "N3"; break;
7930 						case '4': tk = "N4"; break;
7931 						case '5': tk = "N5"; break;
7932 						case '6': tk = "N6"; break;
7933 						case '7': tk = "N7"; break;
7934 						case '8': tk = "N8"; break;
7935 						case '9': tk = "N9"; break;
7936 					}
7937 				}
7938 				foreach (string kn; __traits(allMembers, Key)) {
7939 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7940 				}
7941 			}
7942 			// unknown or duplicate key name, get out of here
7943 			break;
7944 		}
7945 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7946 		return res; // something
7947 	}
7948 
7949 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7950 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7951 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7952 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7953 		}
7954 		bool ignoreMods;
7955 		int updown;
7956 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7957 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7958 		if (this.key != ke.key) {
7959 			// things like "ctrl+alt" are complicated
7960 			uint tkm = this.modifierState&modmask;
7961 			uint kkm = ke.modifierState&modmask;
7962 			Key tk = this.key;
7963 			// ke
7964 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7965 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7966 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7967 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7968 			// this
7969 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7970 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7971 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7972 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7973 			return (tk == ke.key && tkm == kkm);
7974 		}
7975 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7976 	}
7977 }
7978 
7979 /// Sets the application name.
7980 @property string ApplicationName(string name) {
7981 	return _applicationName = name;
7982 }
7983 
7984 string _applicationName;
7985 
7986 /// ditto
7987 @property string ApplicationName() {
7988 	if(_applicationName is null) {
7989 		import core.runtime;
7990 		return Runtime.args[0];
7991 	}
7992 	return _applicationName;
7993 }
7994 
7995 
7996 /// Type of a [MouseEvent].
7997 enum MouseEventType : int {
7998 	motion = 0, /// The mouse moved inside the window
7999 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
8000 	buttonReleased = 2, /// A mouse button was released
8001 }
8002 
8003 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
8004 /++
8005 	Listen for this on your event listeners if you are interested in mouse action.
8006 
8007 	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.
8008 
8009 	Examples:
8010 
8011 	This will draw boxes on the window with the mouse as you hold the left button.
8012 	---
8013 	import arsd.simpledisplay;
8014 
8015 	void main() {
8016 		auto window = new SimpleWindow();
8017 
8018 		window.eventLoop(0,
8019 			(MouseEvent ev) {
8020 				if(ev.modifierState & ModifierState.leftButtonDown) {
8021 					auto painter = window.draw();
8022 					painter.fillColor = Color.red;
8023 					painter.outlineColor = Color.black;
8024 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
8025 				}
8026 			}
8027 		);
8028 	}
8029 	---
8030 +/
8031 struct MouseEvent {
8032 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
8033 
8034 	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.
8035 	int y; /// Current Y position of the cursor when the event fired.
8036 
8037 	int dx; /// Change in X position since last report
8038 	int dy; /// Change in Y position since last report
8039 
8040 	MouseButton button; /// See [MouseButton]
8041 	int modifierState; /// See [ModifierState]
8042 
8043 	version(X11)
8044 		private Time timestamp;
8045 
8046 	/// Returns a linear representation of mouse button,
8047 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
8048 	///
8049 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
8050 	@property ubyte buttonLinear() const {
8051 		import core.bitop;
8052 		if(button == 0)
8053 			return 0;
8054 		return (bsf(button) + 1) & 0b1111;
8055 	}
8056 
8057 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
8058 
8059 	SimpleWindow window; /// The window in which the event happened.
8060 
8061 	Point globalCoordinates() {
8062 		Point p;
8063 		if(window is null)
8064 			throw new Exception("wtf");
8065 		static if(UsingSimpledisplayX11) {
8066 			Window child;
8067 			XTranslateCoordinates(
8068 				XDisplayConnection.get,
8069 				window.impl.window,
8070 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
8071 				x, y, &p.x, &p.y, &child);
8072 			return p;
8073 		} else version(Windows) {
8074 			POINT[1] points;
8075 			points[0].x = x;
8076 			points[0].y = y;
8077 			MapWindowPoints(
8078 				window.impl.hwnd,
8079 				null,
8080 				points.ptr,
8081 				points.length
8082 			);
8083 			p.x = points[0].x;
8084 			p.y = points[0].y;
8085 
8086 			return p;
8087 		} else version(OSXCocoa) {
8088 			throw new NotYetImplementedException();
8089 		} else version(Emscripten) {
8090 			throw new NotYetImplementedException();
8091 		} else static assert(0);
8092 	}
8093 
8094 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
8095 
8096 	/**
8097 	can contain emacs-like modifier prefix
8098 	case-insensitive names:
8099 		lmbX/leftX
8100 		rmbX/rightX
8101 		mmbX/middleX
8102 		wheelX
8103 		motion (no prefix allowed)
8104 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
8105 	*/
8106 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
8107 		if (str.length == 0) return false; // just in case
8108 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
8109 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
8110 		auto anchor = str;
8111 		uint mods = 0; // uint.max == any
8112 		// interesting bits in kmod
8113 		uint kmodmask =
8114 			ModifierState.shift|
8115 			ModifierState.ctrl|
8116 			ModifierState.alt|
8117 			ModifierState.windows|
8118 			ModifierState.leftButtonDown|
8119 			ModifierState.middleButtonDown|
8120 			ModifierState.rightButtonDown|
8121 			0;
8122 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
8123 		bool wasButtons = false;
8124 		while (str.length) {
8125 			if (str.ptr[0] <= ' ') {
8126 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
8127 				continue;
8128 			}
8129 			// one-letter modifier?
8130 			if (str.length >= 2 && str.ptr[1] == '-') {
8131 				switch (str.ptr[0]) {
8132 					case '*': // "any" modifier (cannot be undone)
8133 						mods = mods.max;
8134 						break;
8135 					case 'C': case 'c': // emacs "ctrl"
8136 						if (mods != mods.max) mods |= ModifierState.ctrl;
8137 						break;
8138 					case 'M': case 'm': // emacs "meta"
8139 						if (mods != mods.max) mods |= ModifierState.alt;
8140 						break;
8141 					case 'S': case 's': // emacs "shift"
8142 						if (mods != mods.max) mods |= ModifierState.shift;
8143 						break;
8144 					case 'H': case 'h': // emacs "hyper" (aka winkey)
8145 						if (mods != mods.max) mods |= ModifierState.windows;
8146 						break;
8147 					default:
8148 						return false; // unknown modifier
8149 				}
8150 				str = str[2..$];
8151 				continue;
8152 			}
8153 			// word
8154 			char[16] buf = void; // locased
8155 			auto wep = 0;
8156 			while (str.length) {
8157 				immutable char ch = str.ptr[0];
8158 				if (ch <= ' ' || ch == '-') break;
8159 				str = str[1..$];
8160 				if (wep > buf.length) return false; // too long
8161 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8162 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8163 				else return false; // invalid char
8164 			}
8165 			if (wep == 0) return false; // just in case
8166 			uint bnum;
8167 			enum UpDown { None = -1, Up, Down, Any }
8168 			auto updown = UpDown.None; // 0: up; 1: down
8169 			switch (buf[0..wep]) {
8170 				// left button
8171 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
8172 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
8173 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
8174 				case "lmb": case "left": bnum = 0; break;
8175 				// middle button
8176 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
8177 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
8178 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
8179 				case "mmb": case "middle": bnum = 1; break;
8180 				// right button
8181 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
8182 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
8183 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
8184 				case "rmb": case "right": bnum = 2; break;
8185 				// wheel
8186 				case "wheelup": updown = UpDown.Up; goto case "wheel";
8187 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
8188 				case "wheelany": updown = UpDown.Any; goto case "wheel";
8189 				case "wheel": bnum = 3; break;
8190 				// motion
8191 				case "motion": bnum = 7; break;
8192 				// unknown
8193 				default: return false;
8194 			}
8195 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8196 			// parse possible "-up" or "-down"
8197 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
8198 				wep = 0;
8199 				foreach (immutable idx, immutable char ch; str[1..$]) {
8200 					if (ch <= ' ' || ch == '-') break;
8201 					assert(idx == wep); // for now; trick
8202 					if (wep > buf.length) { wep = 0; break; } // too long
8203 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8204 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8205 					else { wep = 0; break; } // invalid char
8206 				}
8207 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
8208 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
8209 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
8210 				// remove parsed part
8211 				if (updown != UpDown.None) str = str[wep+1..$];
8212 			}
8213 			if (updown == UpDown.None) {
8214 				updown = UpDown.Down;
8215 			}
8216 			wasButtons = wasButtons || (bnum <= 2);
8217 			//assert(updown != UpDown.None);
8218 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8219 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
8220 			if (lastButt != lastButt.max) {
8221 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
8222 				if (mods != mods.max) {
8223 					uint butbit = 0;
8224 					final switch (lastButt&0x03) {
8225 						case 0: butbit = ModifierState.leftButtonDown; break;
8226 						case 1: butbit = ModifierState.middleButtonDown; break;
8227 						case 2: butbit = ModifierState.rightButtonDown; break;
8228 					}
8229 					     if (lastButt&Flag.Down) mods |= butbit;
8230 					else if (lastButt&Flag.Up) mods &= ~butbit;
8231 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
8232 				}
8233 			}
8234 			// remember last button
8235 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
8236 		}
8237 		// no button -- nothing to do
8238 		if (lastButt == lastButt.max) return false;
8239 		// done parsing, check if something's left
8240 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
8241 		// remove action button from mask
8242 		if ((lastButt&0xff) < 3) {
8243 			final switch (lastButt&0x03) {
8244 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
8245 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
8246 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
8247 			}
8248 		}
8249 		// special case: "Motion" means "ignore buttons"
8250 		if ((lastButt&0xff) == 7 && !wasButtons) {
8251 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
8252 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
8253 		}
8254 		uint kmod = event.modifierState&kmodmask;
8255 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
8256 		// check modifier state
8257 		if (mods != mods.max) {
8258 			if (kmod != mods) return false;
8259 		}
8260 		// now check type
8261 		if ((lastButt&0xff) == 7) {
8262 			// motion
8263 			if (event.type != MouseEventType.motion) return false;
8264 		} else if ((lastButt&0xff) == 3) {
8265 			// wheel
8266 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
8267 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
8268 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
8269 			return false;
8270 		} else {
8271 			// buttons
8272 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
8273 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
8274 			{
8275 				return false;
8276 			}
8277 			// button number
8278 			switch (lastButt&0x03) {
8279 				case 0: if (event.button != MouseButton.left) return false; break;
8280 				case 1: if (event.button != MouseButton.middle) return false; break;
8281 				case 2: if (event.button != MouseButton.right) return false; break;
8282 				default: return false;
8283 			}
8284 		}
8285 		return true;
8286 	}
8287 }
8288 
8289 version(arsd_mevent_strcmp_test) unittest {
8290 	MouseEvent event;
8291 	event.type = MouseEventType.buttonPressed;
8292 	event.button = MouseButton.left;
8293 	event.modifierState = ModifierState.ctrl;
8294 	assert(event == "C-LMB");
8295 	assert(event != "C-LMBUP");
8296 	assert(event != "C-LMB-UP");
8297 	assert(event != "C-S-LMB");
8298 	assert(event == "*-LMB");
8299 	assert(event != "*-LMB-UP");
8300 
8301 	event.type = MouseEventType.buttonReleased;
8302 	assert(event != "C-LMB");
8303 	assert(event == "C-LMBUP");
8304 	assert(event == "C-LMB-UP");
8305 	assert(event != "C-S-LMB");
8306 	assert(event != "*-LMB");
8307 	assert(event == "*-LMB-UP");
8308 
8309 	event.button = MouseButton.right;
8310 	event.modifierState |= ModifierState.shift;
8311 	event.type = MouseEventType.buttonPressed;
8312 	assert(event != "C-LMB");
8313 	assert(event != "C-LMBUP");
8314 	assert(event != "C-LMB-UP");
8315 	assert(event != "C-S-LMB");
8316 	assert(event != "*-LMB");
8317 	assert(event != "*-LMB-UP");
8318 
8319 	assert(event != "C-RMB");
8320 	assert(event != "C-RMBUP");
8321 	assert(event != "C-RMB-UP");
8322 	assert(event == "C-S-RMB");
8323 	assert(event == "*-RMB");
8324 	assert(event != "*-RMB-UP");
8325 }
8326 
8327 /// This gives a few more options to drawing lines and such
8328 struct Pen {
8329 	Color color; /// the foreground color
8330 	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.
8331 	Style style; /// See [Style]
8332 /+
8333 // From X.h
8334 
8335 #define LineSolid		0
8336 #define LineOnOffDash		1
8337 #define LineDoubleDash		2
8338        LineDou-        The full path of the line is drawn, but the
8339        bleDash         even dashes are filled differently from the
8340                        odd dashes (see fill-style) with CapButt
8341                        style used where even and odd dashes meet.
8342 
8343 
8344 
8345 /* capStyle */
8346 
8347 #define CapNotLast		0
8348 #define CapButt			1
8349 #define CapRound		2
8350 #define CapProjecting		3
8351 
8352 /* joinStyle */
8353 
8354 #define JoinMiter		0
8355 #define JoinRound		1
8356 #define JoinBevel		2
8357 
8358 /* fillStyle */
8359 
8360 #define FillSolid		0
8361 #define FillTiled		1
8362 #define FillStippled		2
8363 #define FillOpaqueStippled	3
8364 
8365 
8366 +/
8367 	/// Style of lines drawn
8368 	enum Style {
8369 		Solid, /// a solid line
8370 		Dashed, /// a dashed line
8371 		Dotted, /// a dotted line
8372 	}
8373 }
8374 
8375 
8376 /++
8377 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
8378 
8379 
8380 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
8381 
8382 	$(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.)
8383 
8384 	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.
8385 
8386 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
8387 
8388 	$(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.
8389 
8390 	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!
8391 
8392 	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!)
8393 
8394 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
8395 
8396 	---
8397 		auto image = new Image(256, 256);
8398 		scope(exit) destroy(image);
8399 	---
8400 
8401 	As long as you don't hold on to it outside the scope.
8402 
8403 	I might change it to be an owned pointer at some point in the future.
8404 
8405 	)
8406 
8407 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
8408 	you can also often get a fair amount of speedup by getting the raw data format and
8409 	writing some custom code.
8410 
8411 	FIXME INSERT EXAMPLES HERE
8412 
8413 
8414 +/
8415 final class Image {
8416 	///
8417 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
8418 		this.width = width;
8419 		this.height = height;
8420 		this.enableAlpha = enableAlpha;
8421 
8422 		impl.createImage(width, height, forcexshm, enableAlpha);
8423 	}
8424 
8425 	///
8426 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
8427 		this(size.width, size.height, forcexshm, enableAlpha);
8428 	}
8429 
8430 	private bool suppressDestruction;
8431 
8432 	version(X11)
8433 	this(XImage* handle) {
8434 		this.handle = handle;
8435 		this.rawData = cast(ubyte*) handle.data;
8436 		this.width = handle.width;
8437 		this.height = handle.height;
8438 		this.enableAlpha = handle.depth == 32;
8439 		suppressDestruction = true;
8440 	}
8441 
8442 	~this() {
8443 		if(suppressDestruction) return;
8444 		impl.dispose();
8445 	}
8446 
8447 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
8448 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
8449 	pure const @system nothrow {
8450 		/*
8451 			To use these to draw a blue rectangle with size WxH at position X,Y...
8452 
8453 			// make certain that it will fit before we proceed
8454 			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!
8455 
8456 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
8457 			// (though calculating them isn't really that expensive).
8458 			auto nextLineAdjustment = img.adjustmentForNextLine();
8459 			auto offR = img.redByteOffset();
8460 			auto offB = img.blueByteOffset();
8461 			auto offG = img.greenByteOffset();
8462 			auto bpp = img.bytesPerPixel();
8463 
8464 			auto data = img.getDataPointer();
8465 
8466 			// figure out the starting byte offset
8467 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
8468 
8469 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
8470 
8471 			// and now our drawing loop for the rectangle
8472 			foreach(y; 0 .. H) {
8473 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
8474 				foreach(x; 0 .. W) {
8475 					// write our color
8476 					data[offR] = 0;
8477 					data[offG] = 0;
8478 					data[offB] = 255;
8479 
8480 					data += bpp; // moving to the next pixel is just an addition...
8481 				}
8482 				startOfLine += nextLineAdjustment;
8483 			}
8484 
8485 
8486 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
8487 
8488 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
8489 			can be made into a bitmask or something so we can write them as *uint...
8490 		*/
8491 
8492 		///
8493 		int offsetForTopLeftPixel() {
8494 			version(X11) {
8495 				return 0;
8496 			} else version(Windows) {
8497 				if(enableAlpha) {
8498 					return (width * 4) * (height - 1);
8499 				} else {
8500 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
8501 				}
8502 			} else version(OSXCocoa) {
8503 				return 0 ; //throw new NotYetImplementedException();
8504 			} else version(Emscripten) {
8505 				return 0;
8506 			} else static assert(0, "fill in this info for other OSes");
8507 		}
8508 
8509 		///
8510 		int offsetForPixel(int x, int y) {
8511 			version(X11) {
8512 				auto offset = (y * width + x) * 4;
8513 				return offset;
8514 			} else version(Windows) {
8515 				if(enableAlpha) {
8516 					auto itemsPerLine = width * 4;
8517 					// remember, bmps are upside down
8518 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8519 					return offset;
8520 				} else {
8521 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8522 					// remember, bmps are upside down
8523 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8524 					return offset;
8525 				}
8526 			} else version(OSXCocoa) {
8527 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8528 			} else version(Emscripten) {
8529 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8530 			} else static assert(0, "fill in this info for other OSes");
8531 		}
8532 
8533 		///
8534 		int adjustmentForNextLine() {
8535 			version(X11) {
8536 				return width * 4;
8537 			} else version(Windows) {
8538 				// windows bmps are upside down, so the adjustment is actually negative
8539 				if(enableAlpha)
8540 					return - (cast(int) width * 4);
8541 				else
8542 					return -((cast(int) width * 3 + 3) / 4) * 4;
8543 			} else version(OSXCocoa) {
8544 				return width * 4 ; //throw new NotYetImplementedException();
8545 			} else version(Emscripten) {
8546 				return width * 4 ; //throw new NotYetImplementedException();
8547 			} else static assert(0, "fill in this info for other OSes");
8548 		}
8549 
8550 		/// once you have the position of a pixel, use these to get to the proper color
8551 		int redByteOffset() {
8552 			version(X11) {
8553 				return 2;
8554 			} else version(Windows) {
8555 				return 2;
8556 			} else version(OSXCocoa) {
8557 				return 2 ; //throw new NotYetImplementedException();
8558 			} else version(Emscripten) {
8559 				return 2 ; //throw new NotYetImplementedException();
8560 			} else static assert(0, "fill in this info for other OSes");
8561 		}
8562 
8563 		///
8564 		int greenByteOffset() {
8565 			version(X11) {
8566 				return 1;
8567 			} else version(Windows) {
8568 				return 1;
8569 			} else version(OSXCocoa) {
8570 				return 1 ; //throw new NotYetImplementedException();
8571 			} else version(Emscripten) {
8572 				return 1 ; //throw new NotYetImplementedException();
8573 			} else static assert(0, "fill in this info for other OSes");
8574 		}
8575 
8576 		///
8577 		int blueByteOffset() {
8578 			version(X11) {
8579 				return 0;
8580 			} else version(Windows) {
8581 				return 0;
8582 			} else version(OSXCocoa) {
8583 				return 0 ; //throw new NotYetImplementedException();
8584 			} else version(Emscripten) {
8585 				return 0 ; //throw new NotYetImplementedException();
8586 			} else static assert(0, "fill in this info for other OSes");
8587 		}
8588 
8589 		/// Only valid if [enableAlpha] is true
8590 		int alphaByteOffset() {
8591 			version(X11) {
8592 				return 3;
8593 			} else version(Windows) {
8594 				return 3;
8595 			} else version(OSXCocoa) {
8596 				return 3; //throw new NotYetImplementedException();
8597 			} else version(Emscripten) {
8598 				return 3 ; //throw new NotYetImplementedException();
8599 			} else static assert(0, "fill in this info for other OSes");
8600 		}
8601 	}
8602 
8603 	///
8604 	final void putPixel(int x, int y, Color c) {
8605 		if(x < 0 || x >= width)
8606 			return;
8607 		if(y < 0 || y >= height)
8608 			return;
8609 
8610 		impl.setPixel(x, y, c);
8611 	}
8612 
8613 	///
8614 	final Color getPixel(int x, int y) {
8615 		if(x < 0 || x >= width)
8616 			return Color.transparent;
8617 		if(y < 0 || y >= height)
8618 			return Color.transparent;
8619 
8620 		version(OSXCocoa) throw new NotYetImplementedException(); else
8621 		return impl.getPixel(x, y);
8622 	}
8623 
8624 	///
8625 	final void opIndexAssign(Color c, int x, int y) {
8626 		putPixel(x, y, c);
8627 	}
8628 
8629 	///
8630 	TrueColorImage toTrueColorImage() {
8631 		auto tci = new TrueColorImage(width, height);
8632 		convertToRgbaBytes(tci.imageData.bytes);
8633 		return tci;
8634 	}
8635 
8636 	///
8637 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8638 		auto tci = i.getAsTrueColorImage();
8639 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8640 		static if(UsingSimpledisplayX11)
8641 			img.premultiply = premultiply;
8642 		img.setRgbaBytes(tci.imageData.bytes);
8643 		return img;
8644 	}
8645 
8646 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8647 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8648 	/// if you pass null, it will allocate a new one.
8649 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8650 		if(where is null)
8651 			where = new ubyte[this.width*this.height*4];
8652 		convertToRgbaBytes(where);
8653 		return where;
8654 	}
8655 
8656 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8657 	void setRgbaBytes(in ubyte[] from ) {
8658 		assert(from.length == this.width * this.height * 4);
8659 		setFromRgbaBytes(from);
8660 	}
8661 
8662 	// FIXME: make properly cross platform by getting rgba right
8663 
8664 	/// warning: this is not portable across platforms because the data format can change
8665 	ubyte* getDataPointer() {
8666 		return impl.rawData;
8667 	}
8668 
8669 	/// for use with getDataPointer
8670 	final int bytesPerLine() const pure @safe nothrow {
8671 		version(Windows)
8672 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8673 		else version(X11)
8674 			return 4 * width;
8675 		else version(OSXCocoa)
8676 			return 4 * width;
8677 		else static assert(0);
8678 	}
8679 
8680 	/// for use with getDataPointer
8681 	final int bytesPerPixel() const pure @safe nothrow {
8682 		version(Windows)
8683 			return enableAlpha ? 4 : 3;
8684 		else version(X11)
8685 			return 4;
8686 		else version(OSXCocoa)
8687 			return 4;
8688 		else static assert(0);
8689 	}
8690 
8691 	///
8692 	immutable int width;
8693 
8694 	///
8695 	immutable int height;
8696 
8697 	///
8698 	immutable bool enableAlpha;
8699     //private:
8700 	mixin NativeImageImplementation!() impl;
8701 }
8702 
8703 /++
8704 	A convenience function to pop up a window displaying the image.
8705 	If you pass a win, it will draw the image in it. Otherwise, it will
8706 	create a window with the size of the image and run its event loop, closing
8707 	when a key is pressed.
8708 
8709 	History:
8710 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8711 		always block until the application quit which could cause bizarre behavior
8712 		inside a more complex application. Now, the default is to block until
8713 		this window closes if it is the only event loop running, and otherwise,
8714 		not to block at all and just pop up the display window asynchronously.
8715 +/
8716 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8717 	if(win is null) {
8718 		win = new SimpleWindow(image);
8719 		{
8720 			auto p = win.draw;
8721 			p.drawImage(Point(0, 0), image);
8722 		}
8723 		win.eventLoopWithBlockingMode(
8724 			bm, 0,
8725 			(KeyEvent ev) {
8726 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8727 			} );
8728 	} else {
8729 		win.image = image;
8730 	}
8731 }
8732 
8733 enum FontWeight : int {
8734 	dontcare = 0,
8735 	thin = 100,
8736 	extralight = 200,
8737 	light = 300,
8738 	regular = 400,
8739 	medium = 500,
8740 	semibold = 600,
8741 	bold = 700,
8742 	extrabold = 800,
8743 	heavy = 900
8744 }
8745 
8746 /++
8747 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8748 
8749 	History:
8750 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8751 +/
8752 interface MeasurableFont {
8753 	/++
8754 		Returns true if it is a monospace font, meaning each of the
8755 		glyphs (at least the ascii characters) have matching width
8756 		and no kerning, so you can determine the display width of some
8757 		strings by simply multiplying the string width by [averageWidth].
8758 
8759 		(Please note that multiply doesn't $(I actually) work in general,
8760 		consider characters like tab and newline, but it does sometimes.)
8761 	+/
8762 	bool isMonospace();
8763 
8764 	/++
8765 		The average width of glyphs in the font, traditionally equal to the
8766 		width of the lowercase x. Can be used to estimate bounding boxes,
8767 		especially if the font [isMonospace].
8768 
8769 		Given in pixels.
8770 	+/
8771 	int averageWidth();
8772 	/++
8773 		The height of the bounding box of a line.
8774 	+/
8775 	int height();
8776 	/++
8777 		The maximum ascent of a glyph above the baseline.
8778 
8779 		Given in pixels.
8780 	+/
8781 	int ascent();
8782 	/++
8783 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8784 
8785 		Given in pixels.
8786 	+/
8787 	int descent();
8788 	/++
8789 		The display width of the given string, and if you provide a window, it will use it to
8790 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8791 
8792 		Given in pixels.
8793 	+/
8794 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8795 
8796 }
8797 
8798 // FIXME: i need a font cache and it needs to handle disconnects.
8799 
8800 /++
8801 	Represents a font loaded off the operating system or the X server.
8802 
8803 
8804 	While the api here is unified cross platform, the fonts are not necessarily
8805 	available, even across machines of the same platform, so be sure to always check
8806 	for null (using [isNull]) and have a fallback plan.
8807 
8808 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8809 
8810 	Worst case, a null font will automatically fall back to the default font loaded
8811 	for your system.
8812 +/
8813 class OperatingSystemFont : MeasurableFont {
8814 	// FIXME: when the X Connection is lost, these need to be invalidated!
8815 	// that means I need to store the original stuff again to reconstruct it too.
8816 
8817 	version(Emscripten) {
8818 		void* font;
8819 	} else version(X11) {
8820 		XFontStruct* font;
8821 		XFontSet fontset;
8822 
8823 		version(with_xft) {
8824 			XftFont* xftFont;
8825 			bool isXft;
8826 		}
8827 	} else version(Windows) {
8828 		HFONT font;
8829 		int width_;
8830 		int height_;
8831 	} else version(OSXCocoa) {
8832 		NSFont font;
8833 	} else static assert(0);
8834 
8835 	/++
8836 		Constructs the class and immediately calls [load].
8837 	+/
8838 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8839 		load(name, size, weight, italic);
8840 	}
8841 
8842 	/++
8843 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8844 
8845 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8846 
8847 		History:
8848 			Added January 24, 2021.
8849 	+/
8850 	this() {
8851 		// this space intentionally left blank
8852 	}
8853 
8854 	/++
8855 		Constructs a copy of the given font object.
8856 
8857 		History:
8858 			Added January 7, 2023.
8859 	+/
8860 	this(OperatingSystemFont font) {
8861 		if(font is null || font.loadedInfo is LoadedInfo.init)
8862 			loadDefault();
8863 		else
8864 			load(font.loadedInfo.tupleof);
8865 	}
8866 
8867 	/++
8868 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8869 
8870 		History:
8871 			Added November 13, 2020.
8872 	+/
8873 	version(with_xft)
8874 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8875 		unload();
8876 
8877 		if(!XftLibrary.attempted) {
8878 			XftLibrary.loadDynamicLibrary();
8879 		}
8880 
8881 		if(!XftLibrary.loadSuccessful)
8882 			return false;
8883 
8884 		auto display = XDisplayConnection.get;
8885 
8886 		char[256] nameBuffer = void;
8887 		int nbp = 0;
8888 
8889 		void add(in char[] a) {
8890 			nameBuffer[nbp .. nbp + a.length] = a[];
8891 			nbp += a.length;
8892 		}
8893 		add(name);
8894 
8895 		if(size) {
8896 			add(":size=");
8897 			add(toInternal!string(size));
8898 		}
8899 		if(weight != FontWeight.dontcare && weight != 400) {
8900 			if(weight < 400)
8901 				add(":style=Light");
8902 			else
8903 				add(":style=Bold");
8904 			add(":weight=");
8905 			add(weightToString(weight));
8906 		}
8907 		if(italic) {
8908 			if(weight == FontWeight.dontcare)
8909 				add(":style=Italic");
8910 			add(":slant=100");
8911 		}
8912 
8913 		nameBuffer[nbp] = 0;
8914 
8915 		this.xftFont = XftFontOpenName(
8916 			display,
8917 			DefaultScreen(display),
8918 			nameBuffer.ptr
8919 		);
8920 
8921 		this.isXft = true;
8922 
8923 		if(xftFont !is null) {
8924 			isMonospace_ = stringWidth("x") == stringWidth("M");
8925 			ascent_ = xftFont.ascent;
8926 			descent_ = xftFont.descent;
8927 		}
8928 
8929 		return !isNull();
8930 	}
8931 
8932 	/++
8933 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8934 
8935 
8936 		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.
8937 
8938 		If `pattern` is null, it returns all available font families.
8939 
8940 		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.
8941 
8942 		The format of the pattern is platform-specific.
8943 
8944 		History:
8945 			Added May 1, 2021 (dub v9.5)
8946 	+/
8947 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8948 		version(Windows) {
8949 			auto hdc = GetDC(null);
8950 			scope(exit) ReleaseDC(null, hdc);
8951 			LOGFONT logfont;
8952 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8953 				auto localHandler = *(cast(typeof(handler)*) p);
8954 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8955 			}
8956 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8957 		} else version(X11) {
8958 			//import core.stdc.stdio;
8959 			bool done = false;
8960 			version(with_xft) {
8961 				if(!XftLibrary.attempted) {
8962 					XftLibrary.loadDynamicLibrary();
8963 				}
8964 
8965 				if(!XftLibrary.loadSuccessful)
8966 					goto skipXft;
8967 
8968 				if(!FontConfigLibrary.attempted)
8969 					FontConfigLibrary.loadDynamicLibrary();
8970 				if(!FontConfigLibrary.loadSuccessful)
8971 					goto skipXft;
8972 
8973 				{
8974 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8975 					if(got is null)
8976 						goto skipXft;
8977 					scope(exit) FcFontSetDestroy(got);
8978 
8979 					auto fontPatterns = got.fonts[0 .. got.nfont];
8980 					foreach(candidate; fontPatterns) {
8981 						char* where, whereStyle;
8982 
8983 						char* pmg = FcNameUnparse(candidate);
8984 
8985 						//FcPatternGetString(candidate, "family", 0, &where);
8986 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8987 						//if(where && whereStyle) {
8988 						if(pmg) {
8989 							if(!handler(pmg.sliceCString))
8990 								return;
8991 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8992 						}
8993 					}
8994 				}
8995 			}
8996 
8997 			skipXft:
8998 
8999 			if(pattern is null)
9000 				pattern = "*";
9001 
9002 			int count;
9003 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
9004 			scope(exit) XFreeFontNames(coreFontsRaw);
9005 
9006 			auto coreFonts = coreFontsRaw[0 .. count];
9007 
9008 			foreach(font; coreFonts) {
9009 				char[128] tmp;
9010 				tmp[0 ..5] = "core:";
9011 				auto cf = font.sliceCString;
9012 				if(5 + cf.length > tmp.length)
9013 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
9014 				tmp[5 .. 5 + cf.length] = cf;
9015 				if(!handler(tmp[0 .. 5 + cf.length]))
9016 					return;
9017 			}
9018 		}
9019 	}
9020 
9021 	/++
9022 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
9023 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
9024 
9025 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
9026 		underlying system doesn't support returning the raw bytes.
9027 
9028 		History:
9029 			Added September 10, 2021 (dub v10.3)
9030 	+/
9031 	ubyte[] getTtfBytes() {
9032 		if(isNull)
9033 			return null;
9034 
9035 		version(Windows) {
9036 			auto dc = GetDC(null);
9037 			auto orig = SelectObject(dc, font);
9038 
9039 			scope(exit) {
9040 				SelectObject(dc, orig);
9041 				ReleaseDC(null, dc);
9042 			}
9043 
9044 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
9045 			if(res == GDI_ERROR)
9046 				return null;
9047 
9048 			ubyte[] buffer = new ubyte[](res);
9049 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
9050 			if(res == GDI_ERROR)
9051 				return null; // wtf really tbh
9052 
9053 			return buffer;
9054 		} else version(with_xft) {
9055 			if(isXft && xftFont) {
9056 				if(!FontConfigLibrary.attempted)
9057 					FontConfigLibrary.loadDynamicLibrary();
9058 				if(!FontConfigLibrary.loadSuccessful)
9059 					return null;
9060 
9061 				char* file;
9062 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
9063 					if (file !is null && file[0]) {
9064 						import core.stdc.stdio;
9065 						auto fp = fopen(file, "rb");
9066 						if(fp is null)
9067 							return null;
9068 						scope(exit)
9069 							fclose(fp);
9070 						fseek(fp, 0, SEEK_END);
9071 						ubyte[] buffer = new ubyte[](ftell(fp));
9072 						fseek(fp, 0, SEEK_SET);
9073 
9074 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
9075 						if(got != buffer.length)
9076 							return null;
9077 
9078 						return buffer;
9079 					}
9080 				}
9081 			}
9082 			return null;
9083 		} else throw new NotYetImplementedException();
9084 	}
9085 
9086 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
9087 
9088 	private string weightToString(FontWeight weight) {
9089 		with(FontWeight)
9090 		final switch(weight) {
9091 			case dontcare: return "*";
9092 			case thin: return "extralight";
9093 			case extralight: return "extralight";
9094 			case light: return "light";
9095 			case regular: return "regular";
9096 			case medium: return "medium";
9097 			case semibold: return "demibold";
9098 			case bold: return "bold";
9099 			case extrabold: return "demibold";
9100 			case heavy: return "black";
9101 		}
9102 	}
9103 
9104 	/++
9105 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
9106 
9107 		History:
9108 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9109 	+/
9110 	version(X11)
9111 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9112 		unload();
9113 
9114 		string xfontstr;
9115 
9116 		if(name.length > 3 && name[0 .. 3] == "-*-") {
9117 			// this is kinda a disgusting hack but if the user sends an exact
9118 			// string I'd like to honor it...
9119 			xfontstr = name;
9120 		} else {
9121 			string weightstr = weightToString(weight);
9122 			string sizestr;
9123 			if(size == 0)
9124 				sizestr = "*";
9125 			else
9126 				sizestr = toInternal!string(size);
9127 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
9128 		}
9129 
9130 		// writeln(xfontstr);
9131 
9132 		auto display = XDisplayConnection.get;
9133 
9134 		font = XLoadQueryFont(display, xfontstr.ptr);
9135 		if(font is null)
9136 			return false;
9137 
9138 		char** lol;
9139 		int lol2;
9140 		char* lol3;
9141 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
9142 
9143 		prepareFontInfo();
9144 
9145 		return !isNull();
9146 	}
9147 
9148 	version(X11)
9149 	private void prepareFontInfo() {
9150 		if(font !is null) {
9151 			isMonospace_ = stringWidth("l") == stringWidth("M");
9152 			ascent_ = font.max_bounds.ascent;
9153 			descent_ = font.max_bounds.descent;
9154 		}
9155 	}
9156 
9157 	version(OSXCocoa)
9158 	private void prepareFontInfo() {
9159 		if(font !is null) {
9160 			isMonospace_ = font.isFixedPitch;
9161 			ascent_ = cast(int) font.ascender;
9162 			descent_ = cast(int) - font.descender;
9163 		}
9164 	}
9165 
9166 
9167 	/++
9168 		Loads a Windows font. You probably want to use [load] instead to be more generic.
9169 
9170 		History:
9171 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9172 	+/
9173 	version(Windows)
9174 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
9175 		unload();
9176 
9177 		WCharzBuffer buffer = WCharzBuffer(name);
9178 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
9179 
9180 		prepareFontInfo(hdc);
9181 
9182 		return !isNull();
9183 	}
9184 
9185 	version(Windows)
9186 	void prepareFontInfo(HDC hdc = null) {
9187 		if(font is null)
9188 			return;
9189 
9190 		TEXTMETRIC tm;
9191 		auto dc = hdc ? hdc : GetDC(null);
9192 		auto orig = SelectObject(dc, font);
9193 		GetTextMetrics(dc, &tm);
9194 		SelectObject(dc, orig);
9195 		if(hdc is null)
9196 			ReleaseDC(null, dc);
9197 
9198 		width_ = tm.tmAveCharWidth;
9199 		height_ = tm.tmHeight;
9200 		ascent_ = tm.tmAscent;
9201 		descent_ = tm.tmDescent;
9202 		// 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.
9203 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
9204 	}
9205 
9206 
9207 	/++
9208 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
9209 
9210 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
9211 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
9212 
9213 		On Windows, it forwards directly to [loadWin32].
9214 
9215 		Params:
9216 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
9217 			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.
9218 			weight = approximate boldness, results may vary.
9219 			italic = try to get a slanted version of the given font.
9220 
9221 		History:
9222 			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.
9223 	+/
9224 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9225 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
9226 		version(X11) {
9227 			version(with_xft) {
9228 				if(name.length > 5 && name[0 .. 5] == "core:") {
9229 					goto core;
9230 				}
9231 
9232 				if(loadXft(name, size, weight, italic))
9233 					return true;
9234 				// if xft fails, fallback to core to avoid breaking
9235 				// code that already depended on this.
9236 			}
9237 
9238 			core:
9239 
9240 			if(name.length > 5 && name[0 .. 5] == "core:") {
9241 				name = name[5 .. $];
9242 			}
9243 
9244 			return loadCoreX(name, size, weight, italic);
9245 		} else version(Windows) {
9246 			return loadWin32(name, size, weight, italic);
9247 		} else version(OSXCocoa) {
9248 			return loadCocoa(name, size, weight, italic);
9249 		} else static assert(0);
9250 	}
9251 
9252 	version(OSXCocoa)
9253 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
9254 		unload();
9255 
9256 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
9257 		prepareFontInfo();
9258 
9259 		return !isNull();
9260 	}
9261 
9262 	private struct LoadedInfo {
9263 		string name;
9264 		int size;
9265 		FontWeight weight;
9266 		bool italic;
9267 	}
9268 	private LoadedInfo loadedInfo;
9269 
9270 	///
9271 	void unload() {
9272 		if(isNull())
9273 			return;
9274 
9275 		version(X11) {
9276 			auto display = XDisplayConnection.display;
9277 
9278 			if(display is null)
9279 				return;
9280 
9281 			version(with_xft) {
9282 				if(isXft) {
9283 					if(xftFont)
9284 						XftFontClose(display, xftFont);
9285 					isXft = false;
9286 					xftFont = null;
9287 					return;
9288 				}
9289 			}
9290 
9291 			if(font && font !is ScreenPainterImplementation.defaultfont)
9292 				XFreeFont(display, font);
9293 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
9294 				XFreeFontSet(display, fontset);
9295 
9296 			font = null;
9297 			fontset = null;
9298 		} else version(Windows) {
9299 			DeleteObject(font);
9300 			font = null;
9301 		} else version(OSXCocoa) {
9302 			font.release();
9303 			font = null;
9304 		} else static assert(0);
9305 	}
9306 
9307 	private bool isMonospace_;
9308 
9309 	/++
9310 		History:
9311 			Added January 16, 2021
9312 	+/
9313 	bool isMonospace() {
9314 		return isMonospace_;
9315 	}
9316 
9317 	/++
9318 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
9319 
9320 		History:
9321 			Added March 26, 2020
9322 			Documented January 16, 2021
9323 	+/
9324 	int averageWidth() {
9325 		version(X11) {
9326 			return stringWidth("x");
9327 		} version(OSXCocoa) {
9328 			return stringWidth("x");
9329 		} else version(Windows)
9330 			return width_;
9331 		else assert(0);
9332 	}
9333 
9334 	/++
9335 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
9336 
9337 		History:
9338 			Added January 16, 2021
9339 	+/
9340 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
9341 	// FIXME: what about tab?
9342 		if(isNull)
9343 			return 0;
9344 
9345 		version(X11) {
9346 			version(with_xft)
9347 				if(isXft && xftFont !is null) {
9348 					//return xftFont.max_advance_width;
9349 					XGlyphInfo extents;
9350 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
9351 					// writeln(extents);
9352 					return extents.xOff;
9353 				}
9354 			if(font is null)
9355 				return 0;
9356 			else if(fontset) {
9357 				XRectangle rect;
9358 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
9359 
9360 				return rect.width;
9361 			} else {
9362 				return XTextWidth(font, s.ptr, cast(int) s.length);
9363 			}
9364 		} else version(Windows) {
9365 			WCharzBuffer buffer = WCharzBuffer(s);
9366 
9367 			return stringWidth(buffer.slice, window);
9368 		} else version(OSXCocoa) {
9369 			/+
9370 			int charCount = [string length];
9371 			CGGlyph glyphs[charCount];
9372 			CGRect rects[charCount];
9373 
9374 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
9375 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
9376 
9377 			int totalwidth = 0, maxheight = 0;
9378 			for (int i=0; i < charCount; i++)
9379 			{
9380 				totalwidth += rects[i].size.width;
9381 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
9382 			}
9383 
9384 			dim = CGSizeMake(totalwidth, maxheight);
9385 			+/
9386 
9387 			return 16; // FIXME
9388 		}
9389 		else assert(0);
9390 	}
9391 
9392 	version(Windows)
9393 	/// ditto
9394 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
9395 		if(isNull)
9396 			return 0;
9397 		version(Windows) {
9398 			SIZE size;
9399 
9400 			prepareContext(window);
9401 			scope(exit) releaseContext();
9402 
9403 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
9404 
9405 			return size.cx;
9406 		} else {
9407 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
9408 			static assert(0, "not implemented yet");
9409 			//return stringWidth(s, window);
9410 		}
9411 	}
9412 
9413 	private {
9414 		int prepRefcount;
9415 
9416 		version(Windows) {
9417 			HDC dc;
9418 			HANDLE orig;
9419 			HWND hwnd;
9420 		}
9421 	}
9422 	/++
9423 		[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.
9424 
9425 		History:
9426 			Added January 23, 2021
9427 	+/
9428 	void prepareContext(SimpleWindow window = null) {
9429 		prepRefcount++;
9430 		if(prepRefcount == 1) {
9431 			version(Windows) {
9432 				hwnd = window is null ? null : window.impl.hwnd;
9433 				dc = GetDC(hwnd);
9434 				orig = SelectObject(dc, font);
9435 			}
9436 		}
9437 	}
9438 	/// ditto
9439 	void releaseContext() {
9440 		prepRefcount--;
9441 		if(prepRefcount == 0) {
9442 			version(Windows) {
9443 				SelectObject(dc, orig);
9444 				ReleaseDC(hwnd, dc);
9445 				hwnd = null;
9446 				dc = null;
9447 				orig = null;
9448 			}
9449 		}
9450 	}
9451 
9452 	/+
9453 		FIXME: I think I need advance and kerning pair
9454 
9455 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
9456 	+/
9457 
9458 	/++
9459 		Returns the height of the font.
9460 
9461 		History:
9462 			Added March 26, 2020
9463 			Documented January 16, 2021
9464 	+/
9465 	int height() {
9466 		version(X11) {
9467 			version(with_xft)
9468 				if(isXft && xftFont !is null) {
9469 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
9470 				}
9471 			if(font is null)
9472 				return 0;
9473 			return font.max_bounds.ascent + font.max_bounds.descent;
9474 		} else version(Windows) {
9475 			return height_;
9476 		} else version(OSXCocoa) {
9477 			if(font is null)
9478 				return 0;
9479 			return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight
9480 		}
9481 		else assert(0);
9482 	}
9483 
9484 	private int ascent_;
9485 	private int descent_;
9486 
9487 	/++
9488 		Max ascent above the baseline.
9489 
9490 		History:
9491 			Added January 22, 2021
9492 	+/
9493 	int ascent() {
9494 		return ascent_;
9495 	}
9496 
9497 	/++
9498 		Max descent below the baseline.
9499 
9500 		History:
9501 			Added January 22, 2021
9502 	+/
9503 	int descent() {
9504 		return descent_;
9505 	}
9506 
9507 	/++
9508 		Loads the default font used by [ScreenPainter] if none others are loaded.
9509 
9510 		Returns:
9511 			This method mutates the `this` object, but then returns `this` for
9512 			easy chaining like:
9513 
9514 			---
9515 			auto font = foo.isNull ? foo : foo.loadDefault
9516 			---
9517 
9518 		History:
9519 			Added previously, but left unimplemented until January 24, 2021.
9520 	+/
9521 	OperatingSystemFont loadDefault() {
9522 		unload();
9523 
9524 		loadedInfo = LoadedInfo.init;
9525 
9526 		version(X11) {
9527 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9528 			// but meh since sdpy does its own thing, this should be ok too
9529 
9530 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9531 			this.font = ScreenPainterImplementation.defaultfont;
9532 			this.fontset = ScreenPainterImplementation.defaultfontset;
9533 
9534 			prepareFontInfo();
9535 			return this;
9536 		} else version(Windows) {
9537 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9538 			this.font = ScreenPainterImplementation.defaultGuiFont;
9539 
9540 			prepareFontInfo();
9541 			return this;
9542 		} else version(OSXCocoa) {
9543 			this.font = NSFont.systemFontOfSize(15);
9544 
9545 			prepareFontInfo();
9546 
9547 			// import std.stdio; writeln("Load default: ", this.height());
9548 			return this;
9549 		} else throw new NotYetImplementedException();
9550 	}
9551 
9552 	///
9553 	bool isNull() {
9554 		version(with_xft)
9555 			if(isXft)
9556 				return xftFont is null;
9557 		return font is null;
9558 	}
9559 
9560 	/* Metrics */
9561 	/+
9562 		GetABCWidth
9563 		GetKerningPairs
9564 
9565 		if I do it right, I can size it all here, and match
9566 		what happens when I draw the full string with the OS functions.
9567 
9568 		subclasses might do the same thing while getting the glyphs on images
9569 	struct GlyphInfo {
9570 		int glyph;
9571 
9572 		size_t stringIdxStart;
9573 		size_t stringIdxEnd;
9574 
9575 		Rectangle boundingBox;
9576 	}
9577 	GlyphInfo[] getCharBoxes() {
9578 		// XftTextExtentsUtf8
9579 		return null;
9580 
9581 	}
9582 	+/
9583 
9584 	~this() {
9585 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9586 		unload();
9587 	}
9588 }
9589 
9590 version(Windows)
9591 private string sliceCString(const(wchar)[] w) {
9592 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9593 }
9594 
9595 private inout(char)[] sliceCString(inout(char)* s) {
9596 	import core.stdc.string;
9597 	auto len = strlen(s);
9598 	return s[0 .. len];
9599 }
9600 
9601 version(OSXCocoa)
9602 	alias PaintingHandle = NSObject;
9603 else
9604 	alias PaintingHandle = NativeWindowHandle;
9605 
9606 /**
9607 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9608 	than constructing it directly. Then, it is reference counted so you can pass it
9609 	at around and when the last ref goes out of scope, the buffered drawing activities
9610 	are all carried out.
9611 
9612 
9613 	Most functions use the outlineColor instead of taking a color themselves.
9614 	ScreenPainter is reference counted and draws its buffer to the screen when its
9615 	final reference goes out of scope.
9616 */
9617 struct ScreenPainter {
9618 	CapableOfBeingDrawnUpon window;
9619 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9620 		this.window = window;
9621 		if(window.closed)
9622 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9623 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9624 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9625 		if(window.activeScreenPainter !is null) {
9626 			impl = window.activeScreenPainter;
9627 			if(impl.referenceCount == 0) {
9628 				impl.window = window;
9629 				impl.create(handle);
9630 			}
9631 			impl.manualInvalidations = manualInvalidations;
9632 			impl.referenceCount++;
9633 		//	writeln("refcount ++ ", impl.referenceCount);
9634 		} else {
9635 			impl = new ScreenPainterImplementation;
9636 			impl.window = window;
9637 			impl.create(handle);
9638 			impl.referenceCount = 1;
9639 			impl.manualInvalidations = manualInvalidations;
9640 			window.activeScreenPainter = impl;
9641 			// writeln("constructed");
9642 		}
9643 
9644 		copyActiveOriginals();
9645 	}
9646 
9647 	/++
9648 		EXPERIMENTAL. subject to change.
9649 
9650 		When you draw a cursor, you can draw this to notify your window of where it is,
9651 		for IME systems to use.
9652 	+/
9653 	void notifyCursorPosition(int x, int y, int width, int height) {
9654 		if(auto w = cast(SimpleWindow) window) {
9655 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9656 		}
9657 	}
9658 
9659 	/++
9660 		If you are using manual invalidations, this informs the
9661 		window system that a section needs to be redrawn.
9662 
9663 		If you didn't opt into manual invalidation, you don't
9664 		have to call this.
9665 
9666 		History:
9667 			Added December 30, 2021 (dub v10.5)
9668 	+/
9669 	void invalidateRect(Rectangle rect) {
9670 		if(impl is null) return;
9671 
9672 		// transform(rect)
9673 		rect.left += _originX;
9674 		rect.right += _originX;
9675 		rect.top += _originY;
9676 		rect.bottom += _originY;
9677 
9678 		impl.invalidateRect(rect);
9679 	}
9680 
9681 	private Pen originalPen;
9682 	private Color originalFillColor;
9683 	private arsd.color.Rectangle originalClipRectangle;
9684 	private OperatingSystemFont originalFont;
9685 	void copyActiveOriginals() {
9686 		if(impl is null) return;
9687 		originalPen = impl._activePen;
9688 		originalFillColor = impl._fillColor;
9689 		originalClipRectangle = impl._clipRectangle;
9690 		version(OSXCocoa) {} else
9691 		originalFont = impl._activeFont;
9692 	}
9693 
9694 	~this() {
9695 		if(impl is null) return;
9696 		impl.referenceCount--;
9697 		//writeln("refcount -- ", impl.referenceCount);
9698 		if(impl.referenceCount == 0) {
9699 			// writeln("destructed");
9700 			impl.dispose();
9701 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9702 			// writeln("paint finished");
9703 		} else {
9704 			// there is still an active reference, reset stuff so the
9705 			// next user doesn't get weirdness via the reference
9706 			this.rasterOp = RasterOp.normal;
9707 			pen = originalPen;
9708 			fillColor = originalFillColor;
9709 			if(originalFont)
9710 				setFont(originalFont);
9711 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9712 		}
9713 	}
9714 
9715 	this(this) {
9716 		if(impl is null) return;
9717 		impl.referenceCount++;
9718 		//writeln("refcount ++ ", impl.referenceCount);
9719 
9720 		copyActiveOriginals();
9721 	}
9722 
9723 	private int _originX;
9724 	private int _originY;
9725 	@property int originX() { return _originX; }
9726 	@property int originY() { return _originY; }
9727 	@property int originX(int a) {
9728 		_originX = a;
9729 		return _originX;
9730 	}
9731 	@property int originY(int a) {
9732 		_originY = a;
9733 		return _originY;
9734 	}
9735 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9736 	private void transform(ref Point p) {
9737 		if(impl is null) return;
9738 		p.x += _originX;
9739 		p.y += _originY;
9740 	}
9741 
9742 	// this needs to be checked BEFORE the originX/Y transformation
9743 	private bool isClipped(Point p) {
9744 		return !currentClipRectangle.contains(p);
9745 	}
9746 	private bool isClipped(Point p, int width, int height) {
9747 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9748 	}
9749 	private bool isClipped(Point p, Size s) {
9750 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9751 	}
9752 	private bool isClipped(Point p, Point p2) {
9753 		// need to ensure the end points are actually included inside, so the +1 does that
9754 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9755 	}
9756 
9757 
9758 	/++
9759 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9760 
9761 		Returns:
9762 			The old clip rectangle.
9763 
9764 		History:
9765 			Return value was `void` prior to May 10, 2021.
9766 
9767 	+/
9768 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9769 		if(impl is null) return currentClipRectangle;
9770 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9771 			return currentClipRectangle; // no need to do anything
9772 		auto old = currentClipRectangle;
9773 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9774 		transform(pt);
9775 
9776 		impl.setClipRectangle(pt.x, pt.y, width, height);
9777 
9778 		return old;
9779 	}
9780 
9781 	/// ditto
9782 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9783 		if(impl is null) return currentClipRectangle;
9784 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9785 	}
9786 
9787 	///
9788 	void setFont(OperatingSystemFont font) {
9789 		if(impl is null) return;
9790 		impl.setFont(font);
9791 	}
9792 
9793 	///
9794 	int fontHeight() {
9795 		if(impl is null) return 0;
9796 		return impl.fontHeight();
9797 	}
9798 
9799 	private Pen activePen;
9800 
9801 	///
9802 	@property void pen(Pen p) {
9803 		if(impl is null) return;
9804 		activePen = p;
9805 		impl.pen(p);
9806 	}
9807 
9808 	///
9809 	@scriptable
9810 	@property void outlineColor(Color c) {
9811 		if(impl is null) return;
9812 		if(activePen.color == c)
9813 			return;
9814 		activePen.color = c;
9815 		impl.pen(activePen);
9816 	}
9817 
9818 	///
9819 	@scriptable
9820 	@property void fillColor(Color c) {
9821 		if(impl is null) return;
9822 		impl.fillColor(c);
9823 	}
9824 
9825 	///
9826 	@property void rasterOp(RasterOp op) {
9827 		if(impl is null) return;
9828 		impl.rasterOp(op);
9829 	}
9830 
9831 
9832 	void updateDisplay() {
9833 		// FIXME this should do what the dtor does
9834 	}
9835 
9836 	/// 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)
9837 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9838 		if(impl is null) return;
9839 		if(isClipped(upperLeft, width, height)) return;
9840 		transform(upperLeft);
9841 		version(Windows) {
9842 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9843 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9844 			RECT clip = scroll;
9845 			RECT uncovered;
9846 			HRGN hrgn;
9847 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9848 				throw new WindowsApiException("ScrollDC", GetLastError());
9849 
9850 		} else version(X11) {
9851 			// FIXME: clip stuff outside this rectangle
9852 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9853 		} else version(OSXCocoa) {
9854 			throw new NotYetImplementedException();
9855 		} else static assert(0);
9856 	}
9857 
9858 	///
9859 	void clear(Color color = Color.white()) {
9860 		if(impl is null) return;
9861 		fillColor = color;
9862 		outlineColor = color;
9863 		drawRectangle(Point(0, 0), window.width, window.height);
9864 	}
9865 
9866 	/++
9867 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9868 
9869 		Params:
9870 			upperLeft = point on the window where the upper left corner of the image will be drawn
9871 			imageUpperLeft = point on the image to start the slice to draw
9872 			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.
9873 		History:
9874 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9875 	+/
9876 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9877 		if(impl is null) return;
9878 		if(isClipped(upperLeft, s.width, s.height)) return;
9879 		transform(upperLeft);
9880 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9881 	}
9882 
9883 	///
9884 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9885 		if(impl is null) return;
9886 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9887 		transform(upperLeft);
9888 		if(w == 0 || w > i.width)
9889 			w = i.width;
9890 		if(h == 0 || h > i.height)
9891 			h = i.height;
9892 		if(upperLeftOfImage.x < 0)
9893 			upperLeftOfImage.x = 0;
9894 		if(upperLeftOfImage.y < 0)
9895 			upperLeftOfImage.y = 0;
9896 
9897 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9898 	}
9899 
9900 	///
9901 	Size textSize(in char[] text) {
9902 		if(impl is null) return Size(0, 0);
9903 		return impl.textSize(text);
9904 	}
9905 
9906 	/++
9907 		Draws a string in the window with the set font (see [setFont] to change it).
9908 
9909 		Params:
9910 			upperLeft = the upper left point of the bounding box of the text
9911 			text = the string to draw
9912 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9913 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9914 	+/
9915 	@scriptable
9916 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9917 		if(impl is null) return;
9918 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9919 			if(isClipped(upperLeft, lowerRight)) return;
9920 			transform(lowerRight);
9921 		} else {
9922 			if(isClipped(upperLeft, textSize(text))) return;
9923 		}
9924 		transform(upperLeft);
9925 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9926 	}
9927 
9928 	/++
9929 		Draws text using a custom font.
9930 
9931 		This is still MAJOR work in progress.
9932 
9933 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9934 	+/
9935 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9936 		if(impl is null) return;
9937 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9938 		transform(upperLeft);
9939 		font.drawString(this, upperLeft, text);
9940 	}
9941 
9942 	version(Windows)
9943 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9944 		if(impl is null) return;
9945 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9946 		transform(upperLeft);
9947 
9948 		if(text.length && text[$-1] == '\n')
9949 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9950 
9951 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9952 	}
9953 
9954 	static struct TextDrawingContext {
9955 		Point boundingBoxUpperLeft;
9956 		Point boundingBoxLowerRight;
9957 
9958 		Point currentLocation;
9959 
9960 		Point lastDrewUpperLeft;
9961 		Point lastDrewLowerRight;
9962 
9963 		// how do i do right aligned rich text?
9964 		// i kinda want to do a pre-made drawing then right align
9965 		// draw the whole block.
9966 		//
9967 		// That's exactly the diff: inline vs block stuff.
9968 
9969 		// I need to get coordinates of an inline section out too,
9970 		// not just a bounding box, but a series of bounding boxes
9971 		// should be ok. Consider what's needed to detect a click
9972 		// on a link in the middle of a paragraph breaking a line.
9973 		//
9974 		// Generally, we should be able to get the rectangles of
9975 		// any portion we draw.
9976 		//
9977 		// It also needs to tell what text is left if it overflows
9978 		// out of the box, so we can do stuff like float images around
9979 		// it. It should not attempt to draw a letter that would be
9980 		// clipped.
9981 		//
9982 		// I might also turn off word wrap stuff.
9983 	}
9984 
9985 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9986 		if(impl is null) return;
9987 		// FIXME
9988 	}
9989 
9990 	/// Drawing an individual pixel is slow. Avoid it if possible.
9991 	void drawPixel(Point where) {
9992 		if(impl is null) return;
9993 		if(isClipped(where)) return;
9994 		transform(where);
9995 		impl.drawPixel(where.x, where.y);
9996 	}
9997 
9998 
9999 	/// Draws a pen using the current pen / outlineColor
10000 	@scriptable
10001 	void drawLine(Point starting, Point ending) {
10002 		if(impl is null) return;
10003 		if(isClipped(starting, ending)) return;
10004 		transform(starting);
10005 		transform(ending);
10006 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
10007 	}
10008 
10009 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
10010 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
10011 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
10012 	@scriptable
10013 	void drawRectangle(Point upperLeft, int width, int height) {
10014 		if(impl is null) return;
10015 		if(isClipped(upperLeft, width, height)) return;
10016 		transform(upperLeft);
10017 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
10018 	}
10019 
10020 	/// ditto
10021 	void drawRectangle(Point upperLeft, Size size) {
10022 		if(impl is null) return;
10023 		if(isClipped(upperLeft, size.width, size.height)) return;
10024 		transform(upperLeft);
10025 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
10026 	}
10027 
10028 	/// ditto
10029 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
10030 		if(impl is null) return;
10031 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
10032 		transform(upperLeft);
10033 		transform(lowerRightInclusive);
10034 		impl.drawRectangle(upperLeft.x, upperLeft.y,
10035 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
10036 	}
10037 
10038 	// overload added on May 12, 2021
10039 	/// ditto
10040 	void drawRectangle(Rectangle rect) {
10041 		drawRectangle(rect.upperLeft, rect.size);
10042 	}
10043 
10044 	/// Arguments are the points of the bounding rectangle
10045 	void drawEllipse(Point upperLeft, Point lowerRight) {
10046 		if(impl is null) return;
10047 		if(isClipped(upperLeft, lowerRight)) return;
10048 		transform(upperLeft);
10049 		transform(lowerRight);
10050 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
10051 	}
10052 
10053 	/++
10054 		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.
10055 
10056 
10057 		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.
10058 
10059 		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.
10060 
10061 		Bugs:
10062 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
10063 
10064 			The arc outline on Linux sometimes goes over the target.
10065 
10066 			The fill on Windows sometimes stops short.
10067 
10068 		History:
10069 			This function was broken af, totally inconsistent on platforms until September 24, 2021.
10070 
10071 			The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024.
10072 	+/
10073 	void drawArc(Point upperLeft, int width, int height, int start, int length) {
10074 		if(impl is null) return;
10075 		// FIXME: not actually implemented
10076 		if(isClipped(upperLeft, width, height)) return;
10077 		transform(upperLeft);
10078 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length);
10079 	}
10080 
10081 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
10082 	void drawCircle(Point upperLeft, int diameter) {
10083 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
10084 	}
10085 
10086 	/++
10087 		Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush.
10088 
10089 
10090 		Bugs:
10091 			Not implemented on Mac; it will instead draw a non-rounded rectangle for now.
10092 
10093 		History:
10094 			Added August 3, 2024
10095 	+/
10096 	void drawRectangleRounded(Rectangle rect, int borderRadius) {
10097 		drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius);
10098 	}
10099 
10100 	/// ditto
10101 	void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) {
10102 		drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius);
10103 	}
10104 
10105 	/// ditto
10106 	void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
10107 		if(borderRadius <= 0) {
10108 			drawRectangle(upperLeft, lowerRight);
10109 			return;
10110 		}
10111 
10112 		transform(upperLeft);
10113 		transform(lowerRight);
10114 
10115 		impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius);
10116 	}
10117 
10118 	/// .
10119 	void drawPolygon(Point[] vertexes) {
10120 		if(impl is null) return;
10121 		assert(vertexes.length);
10122 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
10123 		foreach(ref vertex; vertexes) {
10124 			if(vertex.x < minX)
10125 				minX = vertex.x;
10126 			if(vertex.y < minY)
10127 				minY = vertex.y;
10128 			if(vertex.x > maxX)
10129 				maxX = vertex.x;
10130 			if(vertex.y > maxY)
10131 				maxY = vertex.y;
10132 			transform(vertex);
10133 		}
10134 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
10135 		impl.drawPolygon(vertexes);
10136 	}
10137 
10138 	/// ditto
10139 	void drawPolygon(Point[] vertexes...) {
10140 		if(impl is null) return;
10141 		drawPolygon(vertexes);
10142 	}
10143 
10144 
10145 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
10146 
10147 	//mixin NativeScreenPainterImplementation!() impl;
10148 
10149 
10150 	// HACK: if I mixin the impl directly, it won't let me override the copy
10151 	// constructor! The linker complains about there being multiple definitions.
10152 	// I'll make the best of it and reference count it though.
10153 	ScreenPainterImplementation* impl;
10154 }
10155 
10156 	// HACK: I need a pointer to the implementation so it's separate
10157 	struct ScreenPainterImplementation {
10158 		CapableOfBeingDrawnUpon window;
10159 		int referenceCount;
10160 		mixin NativeScreenPainterImplementation!();
10161 	}
10162 
10163 // FIXME: i haven't actually tested the sprite class on MS Windows
10164 
10165 /**
10166 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
10167 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
10168 
10169 
10170 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
10171 	though I'm not sure that's ideal and the implementation might change.
10172 
10173 	You create one by giving a window and an image. It optimizes for that window,
10174 	and copies the image into it to use as the initial picture. Creating a sprite
10175 	can be quite slow (especially over a network connection) so you should do it
10176 	as little as possible and just hold on to your sprite handles after making them.
10177 	simpledisplay does try to do its best though, using the XSHM extension if available,
10178 	but you should still write your code as if it will always be slow.
10179 
10180 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
10181 	a fast operation - much faster than drawing the Image itself every time.
10182 
10183 	`Sprite` represents a scarce resource which should be freed when you
10184 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
10185 	after it has been disposed. If you are unsure about this, don't take chances,
10186 	just let the garbage collector do it for you. But ideally, you can manage its
10187 	lifetime more efficiently.
10188 
10189 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
10190 	support alpha blending in its drawing at this time. That might change in the
10191 	future, but if you need alpha blending right now, use OpenGL instead. See
10192 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
10193 
10194 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
10195 	in by setting the enableAlpha = true in the constructor.
10196 */
10197 class Sprite : CapableOfBeingDrawnUpon {
10198 
10199 	///
10200 	ScreenPainter draw() {
10201 		return ScreenPainter(this, handle, false);
10202 	}
10203 
10204 	/++
10205 		Copies the sprite's current state into a [TrueColorImage].
10206 
10207 		Be warned: this can be a very slow operation
10208 
10209 		History:
10210 			Actually implemented on March 14, 2021
10211 	+/
10212 	TrueColorImage takeScreenshot() {
10213 		return trueColorImageFromNativeHandle(handle, width, height);
10214 	}
10215 
10216 	void delegate() paintingFinishedDg() { return null; }
10217 	bool closed() { return false; }
10218 	ScreenPainterImplementation* activeScreenPainter_;
10219 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
10220 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
10221 
10222 	version(Windows)
10223 		private ubyte* rawData;
10224 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
10225 	// ditto on the XPicture stuff
10226 
10227 	version(X11) {
10228 		private static XRenderPictFormat* RGB24;
10229 		private static XRenderPictFormat* ARGB32;
10230 
10231 		private Picture xrenderPicture;
10232 	}
10233 
10234 	version(X11)
10235 	private static void requireXRender() {
10236 		if(!XRenderLibrary.loadAttempted) {
10237 			XRenderLibrary.loadDynamicLibrary();
10238 		}
10239 
10240 		if(!XRenderLibrary.loadSuccessful)
10241 			throw new Exception("XRender library load failure");
10242 
10243 		auto display = XDisplayConnection.get;
10244 
10245 		// FIXME: if we migrate X displays, these need to be changed
10246 		if(RGB24 is null)
10247 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
10248 		if(ARGB32 is null)
10249 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
10250 	}
10251 
10252 	protected this() {}
10253 
10254 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
10255 		this._width = width;
10256 		this._height = height;
10257 		this.enableAlpha = enableAlpha;
10258 
10259 		version(X11) {
10260 			auto display = XDisplayConnection.get();
10261 
10262 			if(enableAlpha) {
10263 				requireXRender();
10264 			}
10265 
10266 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
10267 
10268 			if(enableAlpha) {
10269 				XRenderPictureAttributes attrs;
10270 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
10271 			}
10272 		} else version(Windows) {
10273 			version(CRuntime_DigitalMars) {
10274 				//if(enableAlpha)
10275 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
10276 			}
10277 
10278 			BITMAPINFO infoheader;
10279 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10280 			infoheader.bmiHeader.biWidth = width;
10281 			infoheader.bmiHeader.biHeight = height;
10282 			infoheader.bmiHeader.biPlanes = 1;
10283 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
10284 			infoheader.bmiHeader.biCompression = BI_RGB;
10285 
10286 			// FIXME: this should prolly be a device dependent bitmap...
10287 			handle = CreateDIBSection(
10288 				null,
10289 				&infoheader,
10290 				DIB_RGB_COLORS,
10291 				cast(void**) &rawData,
10292 				null,
10293 				0);
10294 
10295 			if(handle is null)
10296 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
10297 		}
10298 	}
10299 
10300 	/// Makes a sprite based on the image with the initial contents from the Image
10301 	this(SimpleWindow win, Image i) {
10302 		this(win, i.width, i.height, i.enableAlpha);
10303 
10304 		version(X11) {
10305 			auto display = XDisplayConnection.get();
10306 			auto gc = XCreateGC(display, this.handle, 0, null);
10307 			scope(exit) XFreeGC(display, gc);
10308 			if(i.usingXshm)
10309 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
10310 			else
10311 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
10312 		} else version(Windows) {
10313 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
10314 			auto arrLength = itemsPerLine * height;
10315 			rawData[0..arrLength] = i.rawData[0..arrLength];
10316 		} else version(OSXCocoa) {
10317 			// FIXME: I have no idea if this is even any good
10318 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
10319 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
10320 				colorSpace,
10321 				kCGImageAlphaPremultipliedLast
10322 				|kCGBitmapByteOrder32Big);
10323 			CGColorSpaceRelease(colorSpace);
10324 			auto rawData = CGBitmapContextGetData(handle);
10325 
10326 			auto rdl = (width * height * 4);
10327 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
10328 		} else static assert(0);
10329 	}
10330 
10331 	/++
10332 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
10333 
10334 		Params:
10335 			where = point on the window where the upper left corner of the image will be drawn
10336 			imageUpperLeft = point on the image to start the slice to draw
10337 			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.
10338 		History:
10339 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
10340 	+/
10341 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
10342 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
10343 	}
10344 
10345 	/// Call this when you're ready to get rid of it
10346 	void dispose() {
10347 		version(X11) {
10348 			staticDispose(xrenderPicture, handle);
10349 			xrenderPicture = None;
10350 			handle = None;
10351 		} else version(Windows) {
10352 			staticDispose(handle);
10353 			handle = null;
10354 		} else version(OSXCocoa) {
10355 			staticDispose(handle);
10356 			handle = null;
10357 		} else static assert(0);
10358 
10359 	}
10360 
10361 	version(X11)
10362 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
10363 		if(xrenderPicture)
10364 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
10365 		if(handle)
10366 			XFreePixmap(XDisplayConnection.get(), handle);
10367 	}
10368 	else version(Windows)
10369 	static void staticDispose(HBITMAP handle) {
10370 		if(handle)
10371 			DeleteObject(handle);
10372 	}
10373 	else version(OSXCocoa)
10374 	static void staticDispose(CGContextRef context) {
10375 		if(context)
10376 			CGContextRelease(context);
10377 	}
10378 
10379 	~this() {
10380 		version(X11) { if(xrenderPicture || handle)
10381 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
10382 		} else version(Windows) { if(handle)
10383 			cleanupQueue.queue!staticDispose(handle);
10384 		} else version(OSXCocoa) { if(handle)
10385 			cleanupQueue.queue!staticDispose(handle);
10386 		} else static assert(0);
10387 	}
10388 
10389 	///
10390 	final @property int width() { return _width; }
10391 
10392 	///
10393 	final @property int height() { return _height; }
10394 
10395 	///
10396 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
10397 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
10398 	}
10399 
10400 	auto nativeHandle() {
10401 		return handle;
10402 	}
10403 
10404 	private:
10405 
10406 	int _width;
10407 	int _height;
10408 	bool enableAlpha;
10409 	version(X11)
10410 		Pixmap handle;
10411 	else version(Windows)
10412 		HBITMAP handle;
10413 	else version(OSXCocoa)
10414 		CGContextRef handle;
10415 	else version(Emscripten)
10416 		void* handle;
10417 	else static assert(0);
10418 }
10419 
10420 /++
10421 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
10422 
10423 	History:
10424 		Added November 20, 2021 (dub v10.4)
10425 +/
10426 version(OSXCocoa) {} else // NotYetImplementedException
10427 abstract class Gradient : Sprite {
10428 	protected this(int w, int h) {
10429 		version(X11) {
10430 			Sprite.requireXRender();
10431 
10432 			super();
10433 			enableAlpha = true;
10434 			_width = w;
10435 			_height = h;
10436 		} else version(Windows) {
10437 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
10438 		}
10439 	}
10440 
10441 	version(Windows)
10442 	final void forEachPixel(scope Color delegate(int x, int y) dg) @system {
10443 		auto ptr = rawData;
10444 		foreach(j; 0 .. _height)
10445 		foreach(i; 0 .. _width) {
10446 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
10447 			*rawData = (color.a * color.b) / 255; rawData++;
10448 			*rawData = (color.a * color.g) / 255; rawData++;
10449 			*rawData = (color.a * color.r) / 255; rawData++;
10450 			*rawData = color.a; rawData++;
10451 		}
10452 	}
10453 
10454 	version(X11)
10455 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
10456 		assert(stops.length > 0);
10457 		assert(stops.length <= 16, "I got lazy with buffers");
10458 
10459 		XFixed[16] stopsPositions = void;
10460 		XRenderColor[16] colors = void;
10461 
10462 		foreach(idx, stop; stops) {
10463 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
10464 			auto c = stop.c;
10465 			colors[idx] = XRenderColor(
10466 				cast(ushort)(c.r * ushort.max / 255),
10467 				cast(ushort)(c.g * ushort.max / 255),
10468 				cast(ushort)(c.b * ushort.max / 255),
10469 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
10470 			);
10471 		}
10472 
10473 		xrenderPicture = dg(stopsPositions, colors);
10474 	}
10475 
10476 	///
10477 	static struct Stop {
10478 		float percentage; /// between 0 and 1.0
10479 		Color c;
10480 	}
10481 }
10482 
10483 /++
10484 	Creates a linear gradient between p1 and p2.
10485 
10486 	X ONLY RIGHT NOW
10487 
10488 	History:
10489 		Added November 20, 2021 (dub v10.4)
10490 
10491 	Bugs:
10492 		Not yet implemented on Windows.
10493 +/
10494 version(OSXCocoa) {} else // NotYetImplementedException
10495 class LinearGradient : Gradient {
10496 	/++
10497 
10498 	+/
10499 	this(Point p1, Point p2, Stop[] stops...) {
10500 		super(p2.x, p2.y);
10501 
10502 		version(X11) {
10503 			XLinearGradient gradient;
10504 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
10505 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
10506 
10507 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10508 				return XRenderCreateLinearGradient(
10509 					XDisplayConnection.get,
10510 					&gradient,
10511 					stopsPositions.ptr,
10512 					colors.ptr,
10513 					cast(int) stops.length);
10514 			});
10515 		} else version(Windows) {
10516 			// FIXME
10517 			forEachPixel((int x, int y) {
10518 				import core.stdc.math;
10519 
10520 				//sqrtf(
10521 
10522 				return Color.transparent;
10523 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10524 			});
10525 		}
10526 	}
10527 }
10528 
10529 /++
10530 	A conical gradient goes from color to color around a circumference from a center point.
10531 
10532 	X ONLY RIGHT NOW
10533 
10534 	History:
10535 		Added November 20, 2021 (dub v10.4)
10536 
10537 	Bugs:
10538 		Not yet implemented on Windows.
10539 +/
10540 version(OSXCocoa) {} else // NotYetImplementedException
10541 class ConicalGradient : Gradient {
10542 	/++
10543 
10544 	+/
10545 	this(Point center, float angleInDegrees, Stop[] stops...) {
10546 		super(center.x * 2, center.y * 2);
10547 
10548 		version(X11) {
10549 			XConicalGradient gradient;
10550 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
10551 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
10552 
10553 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10554 				return XRenderCreateConicalGradient(
10555 					XDisplayConnection.get,
10556 					&gradient,
10557 					stopsPositions.ptr,
10558 					colors.ptr,
10559 					cast(int) stops.length);
10560 			});
10561 		} else version(Windows) {
10562 			// FIXME
10563 			forEachPixel((int x, int y) {
10564 				import core.stdc.math;
10565 
10566 				//sqrtf(
10567 
10568 				return Color.transparent;
10569 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10570 			});
10571 
10572 		}
10573 	}
10574 }
10575 
10576 /++
10577 	A radial gradient goes from color to color based on distance from the center.
10578 	It is like rings of color.
10579 
10580 	X ONLY RIGHT NOW
10581 
10582 
10583 	More specifically, you create two circles: an inner circle and an outer circle.
10584 	The gradient is only drawn in the area outside the inner circle but inside the outer
10585 	circle. The closest line between those two circles forms the line for the gradient
10586 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10587 
10588 	History:
10589 		Added November 20, 2021 (dub v10.4)
10590 
10591 	Bugs:
10592 		Not yet implemented on Windows.
10593 +/
10594 version(OSXCocoa) {} else // NotYetImplementedException
10595 class RadialGradient : Gradient {
10596 	/++
10597 
10598 	+/
10599 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10600 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10601 
10602 		version(X11) {
10603 			XRadialGradient gradient;
10604 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10605 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10606 
10607 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10608 				return XRenderCreateRadialGradient(
10609 					XDisplayConnection.get,
10610 					&gradient,
10611 					stopsPositions.ptr,
10612 					colors.ptr,
10613 					cast(int) stops.length);
10614 			});
10615 		} else version(Windows) {
10616 			// FIXME
10617 			forEachPixel((int x, int y) {
10618 				import core.stdc.math;
10619 
10620 				//sqrtf(
10621 
10622 				return Color.transparent;
10623 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10624 			});
10625 		}
10626 	}
10627 }
10628 
10629 
10630 
10631 /+
10632 	NOT IMPLEMENTED
10633 
10634 	A display-stored image optimized for relatively quick drawing, like
10635 	[Sprite], but this one supports alpha channel blending and does NOT
10636 	support direct drawing upon it with a [ScreenPainter].
10637 
10638 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10639 	plain [ScreenPainter]... sort of.
10640 
10641 	On X11, it requires the Xrender extension and library. This is available
10642 	almost everywhere though.
10643 
10644 	History:
10645 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10646 +/
10647 version(none)
10648 class AlphaSprite {
10649 	/++
10650 		Copies the given image into it.
10651 	+/
10652 	this(MemoryImage img) {
10653 
10654 		if(!XRenderLibrary.loadAttempted) {
10655 			XRenderLibrary.loadDynamicLibrary();
10656 
10657 			// FIXME: this needs to be reconstructed when the X server changes
10658 			repopulateX();
10659 		}
10660 		if(!XRenderLibrary.loadSuccessful)
10661 			throw new Exception("XRender library load failure");
10662 
10663 		// I probably need to put the alpha mask in a separate Picture
10664 		// ugh
10665 		// maybe the Sprite itself can have an alpha bitmask anyway
10666 
10667 
10668 		auto display = XDisplayConnection.get();
10669 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10670 
10671 
10672 		XRenderPictureAttributes attrs;
10673 
10674 		handle = XRenderCreatePicture(
10675 			XDisplayConnection.get,
10676 			pixmap,
10677 			RGBA,
10678 			0,
10679 			&attrs
10680 		);
10681 
10682 	}
10683 
10684 	// maybe i'll use the create gradient functions too with static factories..
10685 
10686 	void drawAt(ScreenPainter painter, Point where) {
10687 		//painter.drawPixmap(this, where);
10688 
10689 		XRenderPictureAttributes attrs;
10690 
10691 		auto pic = XRenderCreatePicture(
10692 			XDisplayConnection.get,
10693 			painter.impl.d,
10694 			RGB,
10695 			0,
10696 			&attrs
10697 		);
10698 
10699 		XRenderComposite(
10700 			XDisplayConnection.get,
10701 			3, // PictOpOver
10702 			handle,
10703 			None,
10704 			pic,
10705 			0, // src
10706 			0,
10707 			0, // mask
10708 			0,
10709 			10, // dest
10710 			10,
10711 			100, // width
10712 			100
10713 		);
10714 
10715 		/+
10716 		XRenderFreePicture(
10717 			XDisplayConnection.get,
10718 			pic
10719 		);
10720 
10721 		XRenderFreePicture(
10722 			XDisplayConnection.get,
10723 			fill
10724 		);
10725 		+/
10726 		// on Windows you can stretch but Xrender still can't :(
10727 	}
10728 
10729 	static XRenderPictFormat* RGB;
10730 	static XRenderPictFormat* RGBA;
10731 	static void repopulateX() {
10732 		auto display = XDisplayConnection.get;
10733 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10734 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10735 	}
10736 
10737 	XPixmap pixmap;
10738 	Picture handle;
10739 }
10740 
10741 ///
10742 interface CapableOfBeingDrawnUpon {
10743 	///
10744 	ScreenPainter draw();
10745 	///
10746 	int width();
10747 	///
10748 	int height();
10749 	protected ScreenPainterImplementation* activeScreenPainter();
10750 	protected void activeScreenPainter(ScreenPainterImplementation*);
10751 	bool closed();
10752 
10753 	void delegate() paintingFinishedDg();
10754 
10755 	/// Be warned: this can be a very slow operation
10756 	TrueColorImage takeScreenshot();
10757 }
10758 
10759 /// 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].
10760 void flushGui() {
10761 	version(X11) {
10762 		auto dpy = XDisplayConnection.get();
10763 		XLockDisplay(dpy);
10764 		scope(exit) XUnlockDisplay(dpy);
10765 		XFlush(dpy);
10766 	}
10767 }
10768 
10769 /++
10770 	Runs the given code in the GUI thread when its event loop
10771 	is available, blocking until it completes. This allows you
10772 	to create and manipulate windows from another thread without
10773 	invoking undefined behavior.
10774 
10775 	If this is the gui thread, it runs the code immediately.
10776 
10777 	If no gui thread exists yet, the current thread is assumed
10778 	to be it. Attempting to create windows or run the event loop
10779 	in any other thread will cause an assertion failure.
10780 
10781 
10782 	$(TIP
10783 		Did you know you can use UFCS on delegate literals?
10784 
10785 		() {
10786 			// code here
10787 		}.runInGuiThread;
10788 	)
10789 
10790 	Returns:
10791 		`true` if the function was called, `false` if it was not.
10792 		The function may not be called because the gui thread had
10793 		already terminated by the time you called this.
10794 
10795 	History:
10796 		Added April 10, 2020 (v7.2.0)
10797 
10798 		Return value added and implementation tweaked to avoid locking
10799 		at program termination on February 24, 2021 (v9.2.1).
10800 +/
10801 bool runInGuiThread(scope void delegate() dg) @trusted {
10802 	claimGuiThread();
10803 
10804 	if(thisIsGuiThread) {
10805 		dg();
10806 		return true;
10807 	}
10808 
10809 	if(guiThreadTerminating)
10810 		return false;
10811 
10812 	import core.sync.semaphore;
10813 	static Semaphore sc;
10814 	if(sc is null)
10815 		sc = new Semaphore();
10816 
10817 	static RunQueueMember* rqm;
10818 	if(rqm is null)
10819 		rqm = new RunQueueMember;
10820 	rqm.dg = cast(typeof(rqm.dg)) dg;
10821 	rqm.signal = sc;
10822 	rqm.thrown = null;
10823 
10824 	synchronized(runInGuiThreadLock) {
10825 		runInGuiThreadQueue ~= rqm;
10826 	}
10827 
10828 	if(!SimpleWindow.eventWakeUp())
10829 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10830 
10831 	rqm.signal.wait();
10832 	auto t = rqm.thrown;
10833 
10834 	if(t)
10835 		throw t;
10836 
10837 	return true;
10838 }
10839 
10840 // note it runs sync if this is the gui thread....
10841 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10842 	claimGuiThread();
10843 
10844 	try {
10845 
10846 		if(thisIsGuiThread) {
10847 			dg();
10848 			return;
10849 		}
10850 
10851 		if(guiThreadTerminating)
10852 			return;
10853 
10854 		RunQueueMember* rqm = new RunQueueMember;
10855 		rqm.dg = cast(typeof(rqm.dg)) dg;
10856 		rqm.signal = null;
10857 		rqm.thrown = null;
10858 
10859 		synchronized(runInGuiThreadLock) {
10860 			runInGuiThreadQueue ~= rqm;
10861 		}
10862 
10863 		if(!SimpleWindow.eventWakeUp())
10864 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10865 	} catch(Exception e) {
10866 		if(handleError)
10867 			handleError(e);
10868 	}
10869 }
10870 
10871 private void runPendingRunInGuiThreadDelegates() {
10872 	more:
10873 	RunQueueMember* next;
10874 	synchronized(runInGuiThreadLock) {
10875 		if(runInGuiThreadQueue.length) {
10876 			next = runInGuiThreadQueue[0];
10877 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10878 		} else {
10879 			next = null;
10880 		}
10881 	}
10882 
10883 	if(next) {
10884 		try {
10885 			next.dg();
10886 			next.thrown = null;
10887 		} catch(Throwable t) {
10888 			next.thrown = t;
10889 		}
10890 
10891 		if(next.signal)
10892 			next.signal.notify();
10893 
10894 		goto more;
10895 	}
10896 }
10897 
10898 private void claimGuiThread() nothrow {
10899 	import core.atomic;
10900 	if(cas(&guiThreadExists_, false, true))
10901 		thisIsGuiThread = true;
10902 }
10903 
10904 private struct RunQueueMember {
10905 	void delegate() dg;
10906 	import core.sync.semaphore;
10907 	Semaphore signal;
10908 	Throwable thrown;
10909 }
10910 
10911 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10912 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10913 private bool thisIsGuiThread = false;
10914 private shared bool guiThreadExists_ = false;
10915 private shared bool guiThreadTerminating = false;
10916 
10917 /++
10918 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10919 	event loop. All windows must be exclusively created and managed by a single thread.
10920 
10921 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10922 	when you call one of its constructors.
10923 
10924 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10925 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10926 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10927 
10928 	The reason this function is available is in case you want to message pass between a gui
10929 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10930 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10931 
10932 	History:
10933 		Added December 3, 2021 (dub v10.5)
10934 +/
10935 public bool guiThreadExists() {
10936 	return guiThreadExists_;
10937 }
10938 
10939 /++
10940 	Returns `true` if this thread is either running or set to be running the
10941 	simpledisplay.d gui core event loop because it owns windows.
10942 
10943 	It is important to keep gui-related functionality in the right thread, so you will
10944 	want to `runInGuiThread` when you call them (with some specific exceptions called
10945 	out in those specific functions' documentation). Notably, all windows must be
10946 	created and managed only from the gui thread.
10947 
10948 	Will return false if simpledisplay's other functions haven't been called
10949 	yet; check [guiThreadExists] in addition to this.
10950 
10951 	History:
10952 		Added December 3, 2021 (dub v10.5)
10953 +/
10954 public bool thisThreadRunningGui() {
10955 	return thisIsGuiThread;
10956 }
10957 
10958 /++
10959 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10960 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10961 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10962 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10963 	file instead if you are in one of those situations).
10964 
10965 	It does not support outputting very many types; just strings and ints are likely to actually work.
10966 
10967 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10968 	is unspecified meaning I can change it at any time. The only point of this function is to help
10969 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10970 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10971 	in those contexts.
10972 
10973 	$(WARNING
10974 		I reserve the right to change this function at any time. You can use it if it helps you
10975 		but do not rely on it for anything permanent.
10976 	)
10977 
10978 	History:
10979 		Added December 3, 2021. Not formally supported under any stable tag.
10980 +/
10981 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10982 	try {
10983 		version(Windows) {
10984 			import core.sys.windows.wincon;
10985 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10986 				AllocConsole();
10987 			const(char)* fn = "CONOUT$";
10988 		} else version(Posix) {
10989 			const(char)* fn = "/dev/tty";
10990 		} else static assert(0, "Function not implemented for your system");
10991 
10992 		if(fileOverride.length)
10993 			fn = fileOverride.ptr;
10994 
10995 		import core.stdc.stdio;
10996 		auto fp = fopen(fn, "wt");
10997 		if(fp is null) return;
10998 		scope(exit) fclose(fp);
10999 
11000 		string str;
11001 		foreach(item; t) {
11002 			static if(is(typeof(item) : const(char)[]))
11003 				str ~= item;
11004 			else
11005 				str ~= toInternal!string(item);
11006 			str ~= " ";
11007 		}
11008 		str ~= "\n";
11009 
11010 		fwrite(str.ptr, 1, str.length, fp);
11011 		fflush(fp);
11012 	} catch(Exception e) {
11013 		// sorry no hope
11014 	}
11015 }
11016 
11017 private void guiThreadFinalize() {
11018 	assert(thisIsGuiThread);
11019 
11020 	guiThreadTerminating = true; // don't add any more from this point on
11021 	runPendingRunInGuiThreadDelegates();
11022 }
11023 
11024 /+
11025 interface IPromise {
11026 	void reportProgress(int current, int max, string message);
11027 
11028 	/+ // not formally in cuz of templates but still
11029 	IPromise Then();
11030 	IPromise Catch();
11031 	IPromise Finally();
11032 	+/
11033 }
11034 
11035 /+
11036 	auto promise = async({ ... });
11037 	promise.Then(whatever).
11038 		Then(whateverelse).
11039 		Catch((exception) { });
11040 
11041 
11042 	A promise is run inside a fiber and it looks something like:
11043 
11044 	try {
11045 		auto res = whatever();
11046 		auto res2 = whateverelse(res);
11047 	} catch(Exception e) {
11048 		{ }(e);
11049 	}
11050 
11051 	When a thing succeeds, it is passed as an arg to the next
11052 +/
11053 class Promise(T) : IPromise {
11054 	auto Then() { return null; }
11055 	auto Catch() { return null; }
11056 	auto Finally() { return null; }
11057 
11058 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
11059 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
11060 	T await();
11061 }
11062 
11063 interface Task {
11064 }
11065 
11066 interface Resolvable(T) : Task {
11067 	void run();
11068 
11069 	void resolve(T);
11070 
11071 	Resolvable!T then(void delegate(T)); // returns a new promise
11072 	Resolvable!T error(Throwable); // js catch
11073 	Resolvable!T completed(); // js finally
11074 
11075 }
11076 
11077 /++
11078 	Runs `work` in a helper thread and sends its return value back to the main gui
11079 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
11080 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
11081 	kill the program.
11082 
11083 	You can call reportProgress(position, max, message) to update your parent window
11084 	on your progress.
11085 
11086 	I should also use `shared` methods. FIXME
11087 
11088 	History:
11089 		Added March 6, 2021 (dub version 9.3).
11090 +/
11091 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
11092 	uponCompletion(work(null));
11093 }
11094 
11095 +/
11096 
11097 /// Used internal to dispatch events to various classes.
11098 interface CapableOfHandlingNativeEvent {
11099 	NativeEventHandler getNativeEventHandler();
11100 
11101 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
11102 
11103 	version(X11) {
11104 		// if this is impossible, you are allowed to just throw from it
11105 		// Note: if you call it from another object, set a flag cuz the manger will call you again
11106 		void recreateAfterDisconnect();
11107 		// discard any *connection specific* state, but keep enough that you
11108 		// can be recreated if possible. discardConnectionState() is always called immediately
11109 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
11110 		// you need initialization order
11111 		void discardConnectionState();
11112 	}
11113 }
11114 
11115 version(X11)
11116 /++
11117 	State of keys on mouse events, especially motion.
11118 
11119 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
11120 +/
11121 enum ModifierState : uint {
11122 	shift = 1, ///
11123 	capsLock = 2, ///
11124 	ctrl = 4, ///
11125 	alt = 8, /// Not always available on Windows
11126 	windows = 64, /// ditto
11127 	numLock = 16, ///
11128 
11129 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11130 	middleButtonDown = 512, /// ditto
11131 	rightButtonDown = 1024, /// ditto
11132 }
11133 else version(Emscripten)
11134 enum ModifierState : uint {
11135 	shift = 1, ///
11136 	capsLock = 2, ///
11137 	ctrl = 4, ///
11138 	alt = 8, /// Not always available on Windows
11139 	windows = 64, /// ditto
11140 	numLock = 16, ///
11141 
11142 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11143 	middleButtonDown = 512, /// ditto
11144 	rightButtonDown = 1024, /// ditto
11145 }
11146 else version(Windows)
11147 /// ditto
11148 enum ModifierState : uint {
11149 	shift = 4, ///
11150 	ctrl = 8, ///
11151 
11152 	// i'm not sure if the next two are available
11153 	alt = 256, /// not always available on Windows
11154 	windows = 512, /// ditto
11155 
11156 	capsLock = 1024, ///
11157 	numLock = 2048, ///
11158 
11159 	leftButtonDown = 1, /// not available on key events
11160 	middleButtonDown = 16, /// ditto
11161 	rightButtonDown = 2, /// ditto
11162 
11163 	backButtonDown = 0x20, /// not available on X
11164 	forwardButtonDown = 0x40, /// ditto
11165 }
11166 else version(OSXCocoa)
11167 // FIXME FIXME NotYetImplementedException
11168 enum ModifierState : uint {
11169 	shift = 1, ///
11170 	capsLock = 2, ///
11171 	ctrl = 4, ///
11172 	alt = 8, /// Not always available on Windows
11173 	windows = 64, /// ditto
11174 	numLock = 16, ///
11175 
11176 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11177 	middleButtonDown = 512, /// ditto
11178 	rightButtonDown = 1024, /// ditto
11179 }
11180 
11181 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
11182 enum MouseButton : int {
11183 	none = 0,
11184 	left = 1, ///
11185 	right = 2, ///
11186 	middle = 4, ///
11187 	wheelUp = 8, ///
11188 	wheelDown = 16, ///
11189 	backButton = 32, /// often found on the thumb and used for back in browsers
11190 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
11191 }
11192 
11193 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
11194 enum MouseButtonLinear : ubyte {
11195 	left = 1, ///
11196 	right, ///
11197 	middle, ///
11198 	wheelUp, ///
11199 	wheelDown, ///
11200 	backButton, /// often found on the thumb and used for back in browsers
11201 	forwardButton, /// often found on the thumb and used for forward in browsers
11202 }
11203 
11204 version(WebAssembly) {
11205 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11206 	enum Key {
11207 		Escape = 0xff1b, ///
11208 		F1 = 0xffbe, ///
11209 		F2 = 0xffbf, ///
11210 		F3 = 0xffc0, ///
11211 		F4 = 0xffc1, ///
11212 		F5 = 0xffc2, ///
11213 		F6 = 0xffc3, ///
11214 		F7 = 0xffc4, ///
11215 		F8 = 0xffc5, ///
11216 		F9 = 0xffc6, ///
11217 		F10 = 0xffc7, ///
11218 		F11 = 0xffc8, ///
11219 		F12 = 0xffc9, ///
11220 		PrintScreen = 0xff61, ///
11221 		ScrollLock = 0xff14, ///
11222 		Pause = 0xff13, ///
11223 		Grave = 0x60, /// The $(BACKTICK) ~ key
11224 		// number keys across the top of the keyboard
11225 		N1 = 0x31, /// Number key atop the keyboard
11226 		N2 = 0x32, ///
11227 		N3 = 0x33, ///
11228 		N4 = 0x34, ///
11229 		N5 = 0x35, ///
11230 		N6 = 0x36, ///
11231 		N7 = 0x37, ///
11232 		N8 = 0x38, ///
11233 		N9 = 0x39, ///
11234 		N0 = 0x30, ///
11235 		Dash = 0x2d, ///
11236 		Equals = 0x3d, ///
11237 		Backslash = 0x5c, /// The \ | key
11238 		Backspace = 0xff08, ///
11239 		Insert = 0xff63, ///
11240 		Home = 0xff50, ///
11241 		PageUp = 0xff55, ///
11242 		Delete = 0xffff, ///
11243 		End = 0xff57, ///
11244 		PageDown = 0xff56, ///
11245 		Up = 0xff52, ///
11246 		Down = 0xff54, ///
11247 		Left = 0xff51, ///
11248 		Right = 0xff53, ///
11249 
11250 		Tab = 0xff09, ///
11251 		Q = 0x71, ///
11252 		W = 0x77, ///
11253 		E = 0x65, ///
11254 		R = 0x72, ///
11255 		T = 0x74, ///
11256 		Y = 0x79, ///
11257 		U = 0x75, ///
11258 		I = 0x69, ///
11259 		O = 0x6f, ///
11260 		P = 0x70, ///
11261 		LeftBracket = 0x5b, /// the [ { key
11262 		RightBracket = 0x5d, /// the ] } key
11263 		CapsLock = 0xffe5, ///
11264 		A = 0x61, ///
11265 		S = 0x73, ///
11266 		D = 0x64, ///
11267 		F = 0x66, ///
11268 		G = 0x67, ///
11269 		H = 0x68, ///
11270 		J = 0x6a, ///
11271 		K = 0x6b, ///
11272 		L = 0x6c, ///
11273 		Semicolon = 0x3b, ///
11274 		Apostrophe = 0x27, ///
11275 		Enter = 0xff0d, ///
11276 		Shift = 0xffe1, ///
11277 		Z = 0x7a, ///
11278 		X = 0x78, ///
11279 		C = 0x63, ///
11280 		V = 0x76, ///
11281 		B = 0x62, ///
11282 		N = 0x6e, ///
11283 		M = 0x6d, ///
11284 		Comma = 0x2c, ///
11285 		Period = 0x2e, ///
11286 		Slash = 0x2f, /// the / ? key
11287 		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
11288 		Ctrl = 0xffe3, ///
11289 		Windows = 0xffeb, ///
11290 		Alt = 0xffe9, ///
11291 		Space = 0x20, ///
11292 		Alt_r = 0xffea, /// ditto of shift_r
11293 		Windows_r = 0xffec, ///
11294 		Menu = 0xff67, ///
11295 		Ctrl_r = 0xffe4, ///
11296 
11297 		NumLock = 0xff7f, ///
11298 		Divide = 0xffaf, /// The / key on the number pad
11299 		Multiply = 0xffaa, /// The * key on the number pad
11300 		Minus = 0xffad, /// The - key on the number pad
11301 		Plus = 0xffab, /// The + key on the number pad
11302 		PadEnter = 0xff8d, /// Numberpad enter key
11303 		Pad1 = 0xff9c, /// Numberpad keys
11304 		Pad2 = 0xff99, ///
11305 		Pad3 = 0xff9b, ///
11306 		Pad4 = 0xff96, ///
11307 		Pad5 = 0xff9d, ///
11308 		Pad6 = 0xff98, ///
11309 		Pad7 = 0xff95, ///
11310 		Pad8 = 0xff97, ///
11311 		Pad9 = 0xff9a, ///
11312 		Pad0 = 0xff9e, ///
11313 		PadDot = 0xff9f, ///
11314 	}
11315 } version(X11) {
11316 	// FIXME: match ASCII whenever we can. Most of it is already there,
11317 	// but there's a few exceptions and mismatches with Windows
11318 
11319 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11320 	enum Key {
11321 		Escape = 0xff1b, ///
11322 		F1 = 0xffbe, ///
11323 		F2 = 0xffbf, ///
11324 		F3 = 0xffc0, ///
11325 		F4 = 0xffc1, ///
11326 		F5 = 0xffc2, ///
11327 		F6 = 0xffc3, ///
11328 		F7 = 0xffc4, ///
11329 		F8 = 0xffc5, ///
11330 		F9 = 0xffc6, ///
11331 		F10 = 0xffc7, ///
11332 		F11 = 0xffc8, ///
11333 		F12 = 0xffc9, ///
11334 		PrintScreen = 0xff61, ///
11335 		ScrollLock = 0xff14, ///
11336 		Pause = 0xff13, ///
11337 		Grave = 0x60, /// The $(BACKTICK) ~ key
11338 		// number keys across the top of the keyboard
11339 		N1 = 0x31, /// Number key atop the keyboard
11340 		N2 = 0x32, ///
11341 		N3 = 0x33, ///
11342 		N4 = 0x34, ///
11343 		N5 = 0x35, ///
11344 		N6 = 0x36, ///
11345 		N7 = 0x37, ///
11346 		N8 = 0x38, ///
11347 		N9 = 0x39, ///
11348 		N0 = 0x30, ///
11349 		Dash = 0x2d, ///
11350 		Equals = 0x3d, ///
11351 		Backslash = 0x5c, /// The \ | key
11352 		Backspace = 0xff08, ///
11353 		Insert = 0xff63, ///
11354 		Home = 0xff50, ///
11355 		PageUp = 0xff55, ///
11356 		Delete = 0xffff, ///
11357 		End = 0xff57, ///
11358 		PageDown = 0xff56, ///
11359 		Up = 0xff52, ///
11360 		Down = 0xff54, ///
11361 		Left = 0xff51, ///
11362 		Right = 0xff53, ///
11363 
11364 		Tab = 0xff09, ///
11365 		Q = 0x71, ///
11366 		W = 0x77, ///
11367 		E = 0x65, ///
11368 		R = 0x72, ///
11369 		T = 0x74, ///
11370 		Y = 0x79, ///
11371 		U = 0x75, ///
11372 		I = 0x69, ///
11373 		O = 0x6f, ///
11374 		P = 0x70, ///
11375 		LeftBracket = 0x5b, /// the [ { key
11376 		RightBracket = 0x5d, /// the ] } key
11377 		CapsLock = 0xffe5, ///
11378 		A = 0x61, ///
11379 		S = 0x73, ///
11380 		D = 0x64, ///
11381 		F = 0x66, ///
11382 		G = 0x67, ///
11383 		H = 0x68, ///
11384 		J = 0x6a, ///
11385 		K = 0x6b, ///
11386 		L = 0x6c, ///
11387 		Semicolon = 0x3b, ///
11388 		Apostrophe = 0x27, ///
11389 		Enter = 0xff0d, ///
11390 		Shift = 0xffe1, ///
11391 		Z = 0x7a, ///
11392 		X = 0x78, ///
11393 		C = 0x63, ///
11394 		V = 0x76, ///
11395 		B = 0x62, ///
11396 		N = 0x6e, ///
11397 		M = 0x6d, ///
11398 		Comma = 0x2c, ///
11399 		Period = 0x2e, ///
11400 		Slash = 0x2f, /// the / ? key
11401 		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
11402 		Ctrl = 0xffe3, ///
11403 		Windows = 0xffeb, ///
11404 		Alt = 0xffe9, ///
11405 		Space = 0x20, ///
11406 		Alt_r = 0xffea, /// ditto of shift_r
11407 		Windows_r = 0xffec, ///
11408 		Menu = 0xff67, ///
11409 		Ctrl_r = 0xffe4, ///
11410 
11411 		NumLock = 0xff7f, ///
11412 		Divide = 0xffaf, /// The / key on the number pad
11413 		Multiply = 0xffaa, /// The * key on the number pad
11414 		Minus = 0xffad, /// The - key on the number pad
11415 		Plus = 0xffab, /// The + key on the number pad
11416 		PadEnter = 0xff8d, /// Numberpad enter key
11417 		Pad1 = 0xff9c, /// Numberpad keys
11418 		Pad2 = 0xff99, ///
11419 		Pad3 = 0xff9b, ///
11420 		Pad4 = 0xff96, ///
11421 		Pad5 = 0xff9d, ///
11422 		Pad6 = 0xff98, ///
11423 		Pad7 = 0xff95, ///
11424 		Pad8 = 0xff97, ///
11425 		Pad9 = 0xff9a, ///
11426 		Pad0 = 0xff9e, ///
11427 		PadDot = 0xff9f, ///
11428 	}
11429 } else version(Windows) {
11430 	// the character here is for en-us layouts and for illustration only
11431 	// if you actually want to get characters, wait for character events
11432 	// (the argument to your event handler is simply a dchar)
11433 	// those will be converted by the OS for the right locale.
11434 
11435 	enum Key {
11436 		Escape = 0x1b,
11437 		F1 = 0x70,
11438 		F2 = 0x71,
11439 		F3 = 0x72,
11440 		F4 = 0x73,
11441 		F5 = 0x74,
11442 		F6 = 0x75,
11443 		F7 = 0x76,
11444 		F8 = 0x77,
11445 		F9 = 0x78,
11446 		F10 = 0x79,
11447 		F11 = 0x7a,
11448 		F12 = 0x7b,
11449 		PrintScreen = 0x2c,
11450 		ScrollLock = 0x91,
11451 		Pause = 0x13,
11452 		Grave = 0xc0,
11453 		// number keys across the top of the keyboard
11454 		N1 = 0x31,
11455 		N2 = 0x32,
11456 		N3 = 0x33,
11457 		N4 = 0x34,
11458 		N5 = 0x35,
11459 		N6 = 0x36,
11460 		N7 = 0x37,
11461 		N8 = 0x38,
11462 		N9 = 0x39,
11463 		N0 = 0x30,
11464 		Dash = 0xbd,
11465 		Equals = 0xbb,
11466 		Backslash = 0xdc,
11467 		Backspace = 0x08,
11468 		Insert = 0x2d,
11469 		Home = 0x24,
11470 		PageUp = 0x21,
11471 		Delete = 0x2e,
11472 		End = 0x23,
11473 		PageDown = 0x22,
11474 		Up = 0x26,
11475 		Down = 0x28,
11476 		Left = 0x25,
11477 		Right = 0x27,
11478 
11479 		Tab = 0x09,
11480 		Q = 0x51,
11481 		W = 0x57,
11482 		E = 0x45,
11483 		R = 0x52,
11484 		T = 0x54,
11485 		Y = 0x59,
11486 		U = 0x55,
11487 		I = 0x49,
11488 		O = 0x4f,
11489 		P = 0x50,
11490 		LeftBracket = 0xdb,
11491 		RightBracket = 0xdd,
11492 		CapsLock = 0x14,
11493 		A = 0x41,
11494 		S = 0x53,
11495 		D = 0x44,
11496 		F = 0x46,
11497 		G = 0x47,
11498 		H = 0x48,
11499 		J = 0x4a,
11500 		K = 0x4b,
11501 		L = 0x4c,
11502 		Semicolon = 0xba,
11503 		Apostrophe = 0xde,
11504 		Enter = 0x0d,
11505 		Shift = 0x10,
11506 		Z = 0x5a,
11507 		X = 0x58,
11508 		C = 0x43,
11509 		V = 0x56,
11510 		B = 0x42,
11511 		N = 0x4e,
11512 		M = 0x4d,
11513 		Comma = 0xbc,
11514 		Period = 0xbe,
11515 		Slash = 0xbf,
11516 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11517 		Ctrl = 0x11,
11518 		Windows = 0x5b,
11519 		Alt = -5, // FIXME
11520 		Space = 0x20,
11521 		Alt_r = 0xffea, // ditto of shift_r
11522 		Windows_r = 0x5c, // ditto of shift_r
11523 		Menu = 0x5d,
11524 		Ctrl_r = 0xa3, // ditto of shift_r
11525 
11526 		NumLock = 0x90,
11527 		Divide = 0x6f,
11528 		Multiply = 0x6a,
11529 		Minus = 0x6d,
11530 		Plus = 0x6b,
11531 		PadEnter = -8, // FIXME
11532 		Pad1 = 0x61,
11533 		Pad2 = 0x62,
11534 		Pad3 = 0x63,
11535 		Pad4 = 0x64,
11536 		Pad5 = 0x65,
11537 		Pad6 = 0x66,
11538 		Pad7 = 0x67,
11539 		Pad8 = 0x68,
11540 		Pad9 = 0x69,
11541 		Pad0 = 0x60,
11542 		PadDot = 0x6e,
11543 	}
11544 
11545 	// I'm keeping this around for reference purposes
11546 	// ideally all these buttons will be listed for all platforms,
11547 	// but now now I'm just focusing on my US keyboard
11548 	version(none)
11549 	enum Key {
11550 		LBUTTON = 0x01,
11551 		RBUTTON = 0x02,
11552 		CANCEL = 0x03,
11553 		MBUTTON = 0x04,
11554 		//static if (_WIN32_WINNT > =  0x500) {
11555 		XBUTTON1 = 0x05,
11556 		XBUTTON2 = 0x06,
11557 		//}
11558 		BACK = 0x08,
11559 		TAB = 0x09,
11560 		CLEAR = 0x0C,
11561 		RETURN = 0x0D,
11562 		SHIFT = 0x10,
11563 		CONTROL = 0x11,
11564 		MENU = 0x12,
11565 		PAUSE = 0x13,
11566 		CAPITAL = 0x14,
11567 		KANA = 0x15,
11568 		HANGEUL = 0x15,
11569 		HANGUL = 0x15,
11570 		JUNJA = 0x17,
11571 		FINAL = 0x18,
11572 		HANJA = 0x19,
11573 		KANJI = 0x19,
11574 		ESCAPE = 0x1B,
11575 		CONVERT = 0x1C,
11576 		NONCONVERT = 0x1D,
11577 		ACCEPT = 0x1E,
11578 		MODECHANGE = 0x1F,
11579 		SPACE = 0x20,
11580 		PRIOR = 0x21,
11581 		NEXT = 0x22,
11582 		END = 0x23,
11583 		HOME = 0x24,
11584 		LEFT = 0x25,
11585 		UP = 0x26,
11586 		RIGHT = 0x27,
11587 		DOWN = 0x28,
11588 		SELECT = 0x29,
11589 		PRINT = 0x2A,
11590 		EXECUTE = 0x2B,
11591 		SNAPSHOT = 0x2C,
11592 		INSERT = 0x2D,
11593 		DELETE = 0x2E,
11594 		HELP = 0x2F,
11595 		LWIN = 0x5B,
11596 		RWIN = 0x5C,
11597 		APPS = 0x5D,
11598 		SLEEP = 0x5F,
11599 		NUMPAD0 = 0x60,
11600 		NUMPAD1 = 0x61,
11601 		NUMPAD2 = 0x62,
11602 		NUMPAD3 = 0x63,
11603 		NUMPAD4 = 0x64,
11604 		NUMPAD5 = 0x65,
11605 		NUMPAD6 = 0x66,
11606 		NUMPAD7 = 0x67,
11607 		NUMPAD8 = 0x68,
11608 		NUMPAD9 = 0x69,
11609 		MULTIPLY = 0x6A,
11610 		ADD = 0x6B,
11611 		SEPARATOR = 0x6C,
11612 		SUBTRACT = 0x6D,
11613 		DECIMAL = 0x6E,
11614 		DIVIDE = 0x6F,
11615 		F1 = 0x70,
11616 		F2 = 0x71,
11617 		F3 = 0x72,
11618 		F4 = 0x73,
11619 		F5 = 0x74,
11620 		F6 = 0x75,
11621 		F7 = 0x76,
11622 		F8 = 0x77,
11623 		F9 = 0x78,
11624 		F10 = 0x79,
11625 		F11 = 0x7A,
11626 		F12 = 0x7B,
11627 		F13 = 0x7C,
11628 		F14 = 0x7D,
11629 		F15 = 0x7E,
11630 		F16 = 0x7F,
11631 		F17 = 0x80,
11632 		F18 = 0x81,
11633 		F19 = 0x82,
11634 		F20 = 0x83,
11635 		F21 = 0x84,
11636 		F22 = 0x85,
11637 		F23 = 0x86,
11638 		F24 = 0x87,
11639 		NUMLOCK = 0x90,
11640 		SCROLL = 0x91,
11641 		LSHIFT = 0xA0,
11642 		RSHIFT = 0xA1,
11643 		LCONTROL = 0xA2,
11644 		RCONTROL = 0xA3,
11645 		LMENU = 0xA4,
11646 		RMENU = 0xA5,
11647 		//static if (_WIN32_WINNT > =  0x500) {
11648 		BROWSER_BACK = 0xA6,
11649 		BROWSER_FORWARD = 0xA7,
11650 		BROWSER_REFRESH = 0xA8,
11651 		BROWSER_STOP = 0xA9,
11652 		BROWSER_SEARCH = 0xAA,
11653 		BROWSER_FAVORITES = 0xAB,
11654 		BROWSER_HOME = 0xAC,
11655 		VOLUME_MUTE = 0xAD,
11656 		VOLUME_DOWN = 0xAE,
11657 		VOLUME_UP = 0xAF,
11658 		MEDIA_NEXT_TRACK = 0xB0,
11659 		MEDIA_PREV_TRACK = 0xB1,
11660 		MEDIA_STOP = 0xB2,
11661 		MEDIA_PLAY_PAUSE = 0xB3,
11662 		LAUNCH_MAIL = 0xB4,
11663 		LAUNCH_MEDIA_SELECT = 0xB5,
11664 		LAUNCH_APP1 = 0xB6,
11665 		LAUNCH_APP2 = 0xB7,
11666 		//}
11667 		OEM_1 = 0xBA,
11668 		//static if (_WIN32_WINNT > =  0x500) {
11669 		OEM_PLUS = 0xBB,
11670 		OEM_COMMA = 0xBC,
11671 		OEM_MINUS = 0xBD,
11672 		OEM_PERIOD = 0xBE,
11673 		//}
11674 		OEM_2 = 0xBF,
11675 		OEM_3 = 0xC0,
11676 		OEM_4 = 0xDB,
11677 		OEM_5 = 0xDC,
11678 		OEM_6 = 0xDD,
11679 		OEM_7 = 0xDE,
11680 		OEM_8 = 0xDF,
11681 		//static if (_WIN32_WINNT > =  0x500) {
11682 		OEM_102 = 0xE2,
11683 		//}
11684 		PROCESSKEY = 0xE5,
11685 		//static if (_WIN32_WINNT > =  0x500) {
11686 		PACKET = 0xE7,
11687 		//}
11688 		ATTN = 0xF6,
11689 		CRSEL = 0xF7,
11690 		EXSEL = 0xF8,
11691 		EREOF = 0xF9,
11692 		PLAY = 0xFA,
11693 		ZOOM = 0xFB,
11694 		NONAME = 0xFC,
11695 		PA1 = 0xFD,
11696 		OEM_CLEAR = 0xFE,
11697 	}
11698 
11699 } else version(OSXCocoa) {
11700 	enum Key {
11701 		Escape = 53,
11702 		F1 = 122,
11703 		F2 = 120,
11704 		F3 = 99,
11705 		F4 = 118,
11706 		F5 = 96,
11707 		F6 = 97,
11708 		F7 = 98,
11709 		F8 = 100,
11710 		F9 = 101,
11711 		F10 = 109,
11712 		F11 = 103,
11713 		F12 = 111,
11714 		PrintScreen = 105,
11715 		ScrollLock = 107,
11716 		Pause = 113,
11717 		Grave = 50,
11718 		// number keys across the top of the keyboard
11719 		N1 = 18,
11720 		N2 = 19,
11721 		N3 = 20,
11722 		N4 = 21,
11723 		N5 = 23,
11724 		N6 = 22,
11725 		N7 = 26,
11726 		N8 = 28,
11727 		N9 = 25,
11728 		N0 = 29,
11729 		Dash = 27,
11730 		Equals = 24,
11731 		Backslash = 42,
11732 		Backspace = 51,
11733 		Insert = 114,
11734 		Home = 115,
11735 		PageUp = 116,
11736 		Delete = 117,
11737 		End = 119,
11738 		PageDown = 121,
11739 		Up = 126,
11740 		Down = 125,
11741 		Left = 123,
11742 		Right = 124,
11743 
11744 		Tab = 48,
11745 		Q = 12,
11746 		W = 13,
11747 		E = 14,
11748 		R = 15,
11749 		T = 17,
11750 		Y = 16,
11751 		U = 32,
11752 		I = 34,
11753 		O = 31,
11754 		P = 35,
11755 		LeftBracket = 33,
11756 		RightBracket = 30,
11757 		CapsLock = 57,
11758 		A = 0,
11759 		S = 1,
11760 		D = 2,
11761 		F = 3,
11762 		G = 5,
11763 		H = 4,
11764 		J = 38,
11765 		K = 40,
11766 		L = 37,
11767 		Semicolon = 41,
11768 		Apostrophe = 39,
11769 		Enter = 36,
11770 		Shift = 56,
11771 		Z = 6,
11772 		X = 7,
11773 		C = 8,
11774 		V = 9,
11775 		B = 11,
11776 		N = 45,
11777 		M = 46,
11778 		Comma = 43,
11779 		Period = 47,
11780 		Slash = 44,
11781 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11782 		Ctrl = 59,
11783 		Windows = 55,
11784 		Alt = 58,
11785 		Space = 49,
11786 		Alt_r = -3, // ditto of shift_r
11787 		Windows_r = -2,
11788 		Menu = 110,
11789 		Ctrl_r = -1,
11790 
11791 		NumLock = 1,
11792 		Divide = 75,
11793 		Multiply = 67,
11794 		Minus = 78,
11795 		Plus = 69,
11796 		PadEnter = 76,
11797 		Pad1 = 83,
11798 		Pad2 = 84,
11799 		Pad3 = 85,
11800 		Pad4 = 86,
11801 		Pad5 = 87,
11802 		Pad6 = 88,
11803 		Pad7 = 89,
11804 		Pad8 = 91,
11805 		Pad9 = 92,
11806 		Pad0 = 82,
11807 		PadDot = 65,
11808 	}
11809 
11810 }
11811 
11812 char keyToLetterCharAssumingLotsOfThingsThatYouMightBetterNotAssume(Key key) {
11813 	version(OSXCocoa) {
11814 		return char.init; // FIXME
11815 	} else {
11816 		return cast(char)(key - Key.A + 'a');
11817 	}
11818 }
11819 
11820 /* Additional utilities */
11821 
11822 
11823 Color fromHsl(real h, real s, real l) {
11824 	return arsd.color.fromHsl([h,s,l]);
11825 }
11826 
11827 
11828 
11829 /* ********** What follows is the system-specific implementations *********/
11830 version(Windows) {
11831 
11832 
11833 	// helpers for making HICONs from MemoryImages
11834 	class WindowsIcon {
11835 		struct Win32Icon {
11836 			align(1):
11837 			uint biSize;
11838 			int biWidth;
11839 			int biHeight;
11840 			ushort biPlanes;
11841 			ushort biBitCount;
11842 			uint biCompression;
11843 			uint biSizeImage;
11844 			int biXPelsPerMeter;
11845 			int biYPelsPerMeter;
11846 			uint biClrUsed;
11847 			uint biClrImportant;
11848 			// RGBQUAD[colorCount] biColors;
11849 			/* Pixels:
11850 			Uint8 pixels[]
11851 			*/
11852 			/* Mask:
11853 			Uint8 mask[]
11854 			*/
11855 		}
11856 
11857 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11858 
11859 			assert(mi.width <= 256, "image too wide");
11860 			assert(mi.height <= 256, "image too tall");
11861 			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
11862 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11863 
11864 			int icon_plen = mi.width * mi.height * 4;
11865 			int icon_mlen = mi.width * mi.height / 8;
11866 
11867 			int colorCount = 0;
11868 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11869 
11870 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11871 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11872 
11873 			auto data = memory[Win32Icon.sizeof .. $];
11874 
11875 			width = mi.width;
11876 			height = mi.height;
11877 
11878 			auto trueColorImage = mi.getAsTrueColorImage();
11879 
11880 			icon_win32.biSize = 40;
11881 			icon_win32.biWidth = mi.width;
11882 			icon_win32.biHeight = mi.height*2;
11883 			icon_win32.biPlanes = 1;
11884 			icon_win32.biBitCount = 32;
11885 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11886 
11887 			int offset = 0;
11888 			int andOff = icon_plen * 8; // the and offset is in bits
11889 
11890 			// leaving the and mask as the default 0 so the rgba alpha blend
11891 			// does its thing instead
11892 			for(int y = height - 1; y >= 0; y--) {
11893 				int off2 = y * width * 4;
11894 				foreach(x; 0 .. width) {
11895 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11896 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11897 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11898 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11899 
11900 					offset += 4;
11901 					off2 += 4;
11902 				}
11903 			}
11904 
11905 			return memory;
11906 		}
11907 
11908 		this(MemoryImage mi) {
11909 			int icon_len, width, height;
11910 
11911 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11912 
11913 			/*
11914 			PNG* png = readPnpngData);
11915 			PNGHeader pngh = getHeader(png);
11916 			void* icon_win32;
11917 			if(pngh.depth == 4) {
11918 				auto i = new Win32Icon!(16);
11919 				i.fromPNG(png, pngh, icon_len, width, height);
11920 				icon_win32 = i;
11921 			}
11922 			else if(pngh.depth == 8) {
11923 				auto i = new Win32Icon!(256);
11924 				i.fromPNG(png, pngh, icon_len, width, height);
11925 				icon_win32 = i;
11926 			} else assert(0);
11927 			*/
11928 
11929 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11930 
11931 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11932 		}
11933 
11934 		~this() {
11935 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11936 			DestroyIcon(hIcon);
11937 		}
11938 
11939 		HICON hIcon;
11940 	}
11941 
11942 
11943 
11944 
11945 
11946 
11947 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11948 	alias HWND NativeWindowHandle;
11949 
11950 	extern(Windows)
11951 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11952 		try {
11953 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11954 				// it returns zero if the message is handled, so we won't do anything more there
11955 				// do I like that though?
11956 				int mustReturn;
11957 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11958 				if(mustReturn)
11959 					return ret;
11960 			}
11961 
11962 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11963 				if(window.getNativeEventHandler !is null) {
11964 					int mustReturn;
11965 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11966 					if(mustReturn)
11967 						return ret;
11968 				}
11969 				if(auto w = cast(SimpleWindow) (*window))
11970 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11971 				else
11972 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11973 			} else {
11974 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11975 			}
11976 		} catch (Exception e) {
11977 			try {
11978 				sdpy_abort(e);
11979 				return 0;
11980 			} catch(Exception e) { assert(0); }
11981 		}
11982 	}
11983 
11984 	void sdpy_abort(Throwable e) nothrow {
11985 		try
11986 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11987 		catch(Exception e)
11988 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11989 		ExitProcess(1);
11990 	}
11991 
11992 	mixin template NativeScreenPainterImplementation() {
11993 		HDC hdc;
11994 		HWND hwnd;
11995 		//HDC windowHdc;
11996 		HBITMAP oldBmp;
11997 
11998 		void create(PaintingHandle window) {
11999 			hwnd = window;
12000 
12001 			if(auto sw = cast(SimpleWindow) this.window) {
12002 				// drawing on a window, double buffer
12003 				auto windowHdc = GetDC(hwnd);
12004 
12005 				auto buffer = sw.impl.buffer;
12006 				if(buffer is null) {
12007 					hdc = windowHdc;
12008 					windowDc = true;
12009 				} else {
12010 					hdc = CreateCompatibleDC(windowHdc);
12011 
12012 					ReleaseDC(hwnd, windowHdc);
12013 
12014 					oldBmp = SelectObject(hdc, buffer);
12015 				}
12016 			} else {
12017 				// drawing on something else, draw directly
12018 				hdc = CreateCompatibleDC(null);
12019 				SelectObject(hdc, window);
12020 			}
12021 
12022 			// X doesn't draw a text background, so neither should we
12023 			SetBkMode(hdc, TRANSPARENT);
12024 
12025 			ensureDefaultFontLoaded();
12026 
12027 			if(defaultGuiFont) {
12028 				SelectObject(hdc, defaultGuiFont);
12029 				// DeleteObject(defaultGuiFont);
12030 			}
12031 		}
12032 
12033 		static HFONT defaultGuiFont;
12034 		static void ensureDefaultFontLoaded() {
12035 			static bool triedDefaultGuiFont = false;
12036 			if(!triedDefaultGuiFont) {
12037 				NONCLIENTMETRICS params;
12038 				params.cbSize = params.sizeof;
12039 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
12040 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
12041 				}
12042 				triedDefaultGuiFont = true;
12043 			}
12044 		}
12045 
12046 		private OperatingSystemFont _activeFont;
12047 
12048 		void setFont(OperatingSystemFont font) {
12049 			_activeFont = font;
12050 			if(font && font.font) {
12051 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
12052 					// error... how to handle tho?
12053 				} else {
12054 
12055 				}
12056 			}
12057 			else if(defaultGuiFont)
12058 				SelectObject(hdc, defaultGuiFont);
12059 		}
12060 
12061 		arsd.color.Rectangle _clipRectangle;
12062 
12063 		void setClipRectangle(int x, int y, int width, int height) {
12064 			auto old = _clipRectangle;
12065 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12066 			if(old == _clipRectangle)
12067 				return;
12068 
12069 			if(width == 0 || height == 0) {
12070 				SelectClipRgn(hdc, null);
12071 			} else {
12072 				auto region = CreateRectRgn(x, y, x + width, y + height);
12073 				SelectClipRgn(hdc, region);
12074 				DeleteObject(region);
12075 			}
12076 		}
12077 
12078 
12079 		// just because we can on Windows...
12080 		//void create(Image image);
12081 
12082 		void invalidateRect(Rectangle invalidRect) {
12083 			RECT rect;
12084 			rect.left = invalidRect.left;
12085 			rect.right = invalidRect.right;
12086 			rect.top = invalidRect.top;
12087 			rect.bottom = invalidRect.bottom;
12088 			InvalidateRect(hwnd, &rect, false);
12089 		}
12090 		bool manualInvalidations;
12091 
12092 		void dispose() {
12093 			// FIXME: this.window.width/height is probably wrong
12094 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
12095 			// ReleaseDC(hwnd, windowHdc);
12096 
12097 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
12098 			if(cast(SimpleWindow) this.window) {
12099 				if(!manualInvalidations)
12100 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
12101 			}
12102 
12103 			if(originalPen !is null)
12104 				SelectObject(hdc, originalPen);
12105 			if(currentPen !is null)
12106 				DeleteObject(currentPen);
12107 			if(originalBrush !is null)
12108 				SelectObject(hdc, originalBrush);
12109 			if(currentBrush !is null)
12110 				DeleteObject(currentBrush);
12111 
12112 			SelectObject(hdc, oldBmp);
12113 
12114 			if(windowDc)
12115 				ReleaseDC(hwnd, hdc);
12116 			else
12117 				DeleteDC(hdc);
12118 
12119 			if(window.paintingFinishedDg !is null)
12120 				window.paintingFinishedDg()();
12121 		}
12122 
12123 		bool windowDc;
12124 		HPEN originalPen;
12125 		HPEN currentPen;
12126 
12127 		Pen _activePen;
12128 
12129 		Color _outlineColor;
12130 
12131 		@property void pen(Pen p) {
12132 			_activePen = p;
12133 			_outlineColor = p.color;
12134 
12135 			HPEN pen;
12136 			if(p.color.a == 0) {
12137 				pen = GetStockObject(NULL_PEN);
12138 			} else {
12139 				int style = PS_SOLID;
12140 				final switch(p.style) {
12141 					case Pen.Style.Solid:
12142 						style = PS_SOLID;
12143 					break;
12144 					case Pen.Style.Dashed:
12145 						style = PS_DASH;
12146 					break;
12147 					case Pen.Style.Dotted:
12148 						style = PS_DOT;
12149 					break;
12150 				}
12151 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
12152 			}
12153 			auto orig = SelectObject(hdc, pen);
12154 			if(originalPen is null)
12155 				originalPen = orig;
12156 
12157 			if(currentPen !is null)
12158 				DeleteObject(currentPen);
12159 
12160 			currentPen = pen;
12161 
12162 			// the outline is like a foreground since it's done that way on X
12163 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
12164 
12165 		}
12166 
12167 		@property void rasterOp(RasterOp op) {
12168 			int mode;
12169 			final switch(op) {
12170 				case RasterOp.normal:
12171 					mode = R2_COPYPEN;
12172 				break;
12173 				case RasterOp.xor:
12174 					mode = R2_XORPEN;
12175 				break;
12176 			}
12177 			SetROP2(hdc, mode);
12178 		}
12179 
12180 		HBRUSH originalBrush;
12181 		HBRUSH currentBrush;
12182 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
12183 		@property void fillColor(Color c) {
12184 			if(c == _fillColor)
12185 				return;
12186 			_fillColor = c;
12187 			HBRUSH brush;
12188 			if(c.a == 0) {
12189 				brush = GetStockObject(HOLLOW_BRUSH);
12190 			} else {
12191 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12192 			}
12193 			auto orig = SelectObject(hdc, brush);
12194 			if(originalBrush is null)
12195 				originalBrush = orig;
12196 
12197 			if(currentBrush !is null)
12198 				DeleteObject(currentBrush);
12199 
12200 			currentBrush = brush;
12201 
12202 			// background color is NOT set because X doesn't draw text backgrounds
12203 			//   SetBkColor(hdc, RGB(255, 255, 255));
12204 		}
12205 
12206 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12207 			BITMAP bm;
12208 
12209 			HDC hdcMem = CreateCompatibleDC(hdc);
12210 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
12211 
12212 			GetObject(i.handle, bm.sizeof, &bm);
12213 
12214 			// or should I AlphaBlend!??!?!
12215 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
12216 
12217 			SelectObject(hdcMem, hbmOld);
12218 			DeleteDC(hdcMem);
12219 		}
12220 
12221 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12222 			BITMAP bm;
12223 
12224 			HDC hdcMem = CreateCompatibleDC(hdc);
12225 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
12226 
12227 			GetObject(s.handle, bm.sizeof, &bm);
12228 
12229 			version(CRuntime_DigitalMars) goto noalpha;
12230 
12231 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
12232 			if(s.enableAlpha) {
12233 				auto dw = w ? w : bm.bmWidth;
12234 				auto dh = h ? h : bm.bmHeight;
12235 				BLENDFUNCTION bf;
12236 				bf.BlendOp = AC_SRC_OVER;
12237 				bf.SourceConstantAlpha = 255;
12238 				bf.AlphaFormat = AC_SRC_ALPHA;
12239 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
12240 			} else {
12241 				noalpha:
12242 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
12243 			}
12244 
12245 			SelectObject(hdcMem, hbmOld);
12246 			DeleteDC(hdcMem);
12247 		}
12248 
12249 		Size textSize(scope const(char)[] text) {
12250 			bool dummyX;
12251 			if(text.length == 0) {
12252 				text = " ";
12253 				dummyX = true;
12254 			}
12255 			RECT rect;
12256 			WCharzBuffer buffer = WCharzBuffer(text);
12257 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
12258 			return Size(dummyX ? 0 : rect.right, rect.bottom);
12259 		}
12260 
12261 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
12262 			if(text.length && text[$-1] == '\n')
12263 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
12264 			if(text.length && text[$-1] == '\r')
12265 				text = text[0 .. $-1];
12266 
12267 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
12268 			if(x2 == 0 && y2 == 0) {
12269 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
12270 			} else {
12271 				RECT rect;
12272 				rect.left = x;
12273 				rect.top = y;
12274 				rect.right = x2;
12275 				rect.bottom = y2;
12276 
12277 				uint mode = DT_LEFT;
12278 				if(alignment & TextAlignment.Right)
12279 					mode = DT_RIGHT;
12280 				else if(alignment & TextAlignment.Center)
12281 					mode = DT_CENTER;
12282 
12283 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
12284 				if(alignment & TextAlignment.VerticalCenter)
12285 					mode |= DT_VCENTER | DT_SINGLELINE;
12286 
12287 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
12288 			}
12289 
12290 			/*
12291 			uint mode;
12292 
12293 			if(alignment & TextAlignment.Center)
12294 				mode = TA_CENTER;
12295 
12296 			SetTextAlign(hdc, mode);
12297 			*/
12298 		}
12299 
12300 		int fontHeight() {
12301 			TEXTMETRIC metric;
12302 			if(GetTextMetricsW(hdc, &metric)) {
12303 				return metric.tmHeight;
12304 			}
12305 
12306 			return 16; // idk just guessing here, maybe we should throw
12307 		}
12308 
12309 		void drawPixel(int x, int y) {
12310 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
12311 		}
12312 
12313 		// The basic shapes, outlined
12314 
12315 		void drawLine(int x1, int y1, int x2, int y2) {
12316 			MoveToEx(hdc, x1, y1, null);
12317 			LineTo(hdc, x2, y2);
12318 		}
12319 
12320 		void drawRectangle(int x, int y, int width, int height) {
12321 			// FIXME: with a wider pen this might not draw quite right. im not sure.
12322 			gdi.Rectangle(hdc, x, y, x + width, y + height);
12323 		}
12324 
12325 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
12326 			RoundRect(
12327 				hdc,
12328 				upperLeft.x, upperLeft.y,
12329 				lowerRight.x, lowerRight.y,
12330 				borderRadius, borderRadius
12331 			);
12332 		}
12333 
12334 		/// Arguments are the points of the bounding rectangle
12335 		void drawEllipse(int x1, int y1, int x2, int y2) {
12336 			Ellipse(hdc, x1, y1, x2, y2);
12337 		}
12338 
12339 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
12340 			//if(length > 360*64)
12341 				//length = 360*64;
12342 
12343 			if((start == 0 && length == 360*64)) {
12344 				drawEllipse(x1, y1, x1 + width, y1 + height);
12345 			} else {
12346 				import core.stdc.math;
12347 
12348 				bool clockwise = false;
12349 				if(length < 0) {
12350 					clockwise = true;
12351 					length = -length;
12352 				}
12353 
12354 				double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323;
12355 				double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323;
12356 
12357 				auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12358 				auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12359 				auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12360 				auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12361 
12362 				if(clockwise) {
12363 					auto t1 = c1;
12364 					auto t2 = c2;
12365 					c1 = c3;
12366 					c2 = c4;
12367 					c3 = t1;
12368 					c4 = t2;
12369 				}
12370 
12371 				//if(_activePen.color.a)
12372 					//Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12373 				//if(_fillColor.a)
12374 
12375 				Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12376 			}
12377 		}
12378 
12379 		void drawPolygon(Point[] vertexes) {
12380 			POINT[] points;
12381 			points.length = vertexes.length;
12382 
12383 			foreach(i, p; vertexes) {
12384 				points[i].x = p.x;
12385 				points[i].y = p.y;
12386 			}
12387 
12388 			Polygon(hdc, points.ptr, cast(int) points.length);
12389 		}
12390 	}
12391 
12392 
12393 	// Mix this into the SimpleWindow class
12394 	mixin template NativeSimpleWindowImplementation() {
12395 		int curHidden = 0; // counter
12396 		__gshared static bool[string] knownWinClasses;
12397 		static bool altPressed = false;
12398 
12399 		HANDLE oldCursor;
12400 
12401 		void hideCursor () {
12402 			if(curHidden == 0)
12403 				oldCursor = SetCursor(null);
12404 			++curHidden;
12405 		}
12406 
12407 		void showCursor () {
12408 			--curHidden;
12409 			if(curHidden == 0) {
12410 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
12411 			}
12412 		}
12413 
12414 
12415 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
12416 
12417 		void setMinSize (int minwidth, int minheight) {
12418 			minWidth = minwidth;
12419 			minHeight = minheight;
12420 		}
12421 		void setMaxSize (int maxwidth, int maxheight) {
12422 			maxWidth = maxwidth;
12423 			maxHeight = maxheight;
12424 		}
12425 
12426 		// FIXME i'm not sure that Windows has this functionality
12427 		// though it is nonessential anyway.
12428 		void setResizeGranularity (int granx, int grany) {}
12429 
12430 		ScreenPainter getPainter(bool manualInvalidations) {
12431 			return ScreenPainter(this, hwnd, manualInvalidations);
12432 		}
12433 
12434 		HBITMAP buffer;
12435 
12436 		void setTitle(string title) {
12437 			WCharzBuffer bfr = WCharzBuffer(title);
12438 			SetWindowTextW(hwnd, bfr.ptr);
12439 		}
12440 
12441 		string getTitle() {
12442 			auto len = GetWindowTextLengthW(hwnd);
12443 			if (!len)
12444 				return null;
12445 			wchar[256] tmpBuffer;
12446 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
12447 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12448 			auto str = buffer[0 .. len2];
12449 			return makeUtf8StringFromWindowsString(str);
12450 		}
12451 
12452 		void move(int x, int y) {
12453 			RECT rect;
12454 			GetWindowRect(hwnd, &rect);
12455 			// move it while maintaining the same size...
12456 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
12457 		}
12458 
12459 		void resize(int w, int h) {
12460 			RECT rect;
12461 			GetWindowRect(hwnd, &rect);
12462 
12463 			RECT client;
12464 			GetClientRect(hwnd, &client);
12465 
12466 			rect.right = rect.right - client.right + w;
12467 			rect.bottom = rect.bottom - client.bottom + h;
12468 
12469 			// same position, new size for the client rectangle
12470 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
12471 
12472 			updateOpenglViewportIfNeeded(w, h);
12473 		}
12474 
12475 		void moveResize (int x, int y, int w, int h) {
12476 			// what's given is the client rectangle, we need to adjust
12477 
12478 			RECT rect;
12479 			rect.left = x;
12480 			rect.top = y;
12481 			rect.right = w + x;
12482 			rect.bottom = h + y;
12483 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
12484 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
12485 
12486 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
12487 			updateOpenglViewportIfNeeded(w, h);
12488 			if (windowResized !is null) windowResized(w, h);
12489 		}
12490 
12491 		version(without_opengl) {} else {
12492 			HGLRC ghRC;
12493 			HDC ghDC;
12494 		}
12495 
12496 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
12497 			string cnamec;
12498 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
12499 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
12500 				cnamec = "DSimpleWindow";
12501 			} else {
12502 				cnamec = sdpyWindowClass;
12503 			}
12504 
12505 			WCharzBuffer cn = WCharzBuffer(cnamec);
12506 
12507 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
12508 
12509 			if(cnamec !in knownWinClasses) {
12510 				WNDCLASSEX wc;
12511 
12512 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
12513 				// to the object. Maybe.
12514 				wc.cbSize = wc.sizeof;
12515 				wc.cbClsExtra = 0;
12516 				wc.cbWndExtra = 0;
12517 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
12518 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
12519 				wc.hIcon = LoadIcon(hInstance, null);
12520 				wc.hInstance = hInstance;
12521 				wc.lpfnWndProc = &WndProc;
12522 				wc.lpszClassName = cn.ptr;
12523 				wc.hIconSm = null;
12524 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
12525 				if(!RegisterClassExW(&wc))
12526 					throw new WindowsApiException("RegisterClassExW", GetLastError());
12527 				knownWinClasses[cnamec] = true;
12528 			}
12529 
12530 			int style;
12531 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
12532 
12533 			// FIXME: windowType and customizationFlags
12534 			final switch(windowType) {
12535 				case WindowTypes.normal:
12536 					if(resizability == Resizability.fixedSize) {
12537 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
12538 					} else {
12539 						style = WS_OVERLAPPEDWINDOW;
12540 					}
12541 				break;
12542 				case WindowTypes.undecorated:
12543 					style = WS_POPUP | WS_SYSMENU;
12544 				break;
12545 				case WindowTypes.eventOnly:
12546 					_hidden = true;
12547 				break;
12548 				case WindowTypes.tooltip:
12549 				case WindowTypes.dnd:
12550 				case WindowTypes.comboBoxDropdown:
12551 				case WindowTypes.dropdownMenu:
12552 				case WindowTypes.popupMenu:
12553 				case WindowTypes.notification:
12554 					style = WS_POPUP;
12555 					flags |= WS_EX_NOACTIVATE;
12556 				break;
12557 				case WindowTypes.dialog:
12558 					style = WS_OVERLAPPEDWINDOW;
12559 				break;
12560 				case WindowTypes.nestedChild:
12561 					style = WS_CHILD;
12562 				break;
12563 				case WindowTypes.minimallyWrapped:
12564 					assert(0, "construct minimally wrapped through the other ctor overlad");
12565 			}
12566 
12567 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12568 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
12569 
12570 			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
12571 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
12572 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
12573 
12574 			if(!hwnd)
12575 				throw new WindowsApiException("CreateWindowEx", GetLastError());
12576 
12577 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12578 				setOpacity(255);
12579 
12580 			SimpleWindow.nativeMapping[hwnd] = this;
12581 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
12582 
12583 			if(windowType == WindowTypes.eventOnly)
12584 				return;
12585 
12586 			HDC hdc = GetDC(hwnd);
12587 
12588 			if(!hdc)
12589 				throw new WindowsApiException("GetDC", GetLastError());
12590 
12591 			version(without_opengl) {}
12592 			else {
12593 				if(opengl == OpenGlOptions.yes) {
12594 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12595 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12596 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
12597 					ghDC = hdc;
12598 					PIXELFORMATDESCRIPTOR pfd;
12599 
12600 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
12601 					pfd.nVersion = 1;
12602 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
12603 					pfd.dwLayerMask = PFD_MAIN_PLANE;
12604 					pfd.iPixelType = PFD_TYPE_RGBA;
12605 					pfd.cColorBits = 24;
12606 					pfd.cDepthBits = 24;
12607 					pfd.cAccumBits = 0;
12608 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
12609 
12610 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
12611 
12612 					if (pixelformat == 0)
12613 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
12614 
12615 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
12616 						throw new WindowsApiException("SetPixelFormat", GetLastError());
12617 
12618 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
12619 						// windoze is idiotic: we have to have OpenGL context to get function addresses
12620 						// so we will create fake context to get that stupid address
12621 						auto tmpcc = wglCreateContext(ghDC);
12622 						if (tmpcc !is null) {
12623 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
12624 							wglMakeCurrent(ghDC, tmpcc);
12625 							wglInitOtherFunctions();
12626 						}
12627 					}
12628 
12629 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
12630 						int[9] contextAttribs = [
12631 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
12632 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
12633 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
12634 							// for modern context, set "forward compatibility" flag too
12635 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
12636 							0/*None*/,
12637 						];
12638 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
12639 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
12640 							// activate fallback mode
12641 							// 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;
12642 							ghRC = wglCreateContext(ghDC);
12643 						}
12644 						if (ghRC is null)
12645 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
12646 					} else {
12647 						// try to do at least something
12648 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
12649 							sdpyOpenGLContextVersion = 0;
12650 							ghRC = wglCreateContext(ghDC);
12651 						}
12652 						if (ghRC is null)
12653 							throw new WindowsApiException("wglCreateContext", GetLastError());
12654 					}
12655 				}
12656 			}
12657 
12658 			if(opengl == OpenGlOptions.no) {
12659 				buffer = CreateCompatibleBitmap(hdc, width, height);
12660 
12661 				auto hdcBmp = CreateCompatibleDC(hdc);
12662 				// make sure it's filled with a blank slate
12663 				auto oldBmp = SelectObject(hdcBmp, buffer);
12664 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
12665 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
12666 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
12667 				SelectObject(hdcBmp, oldBmp);
12668 				SelectObject(hdcBmp, oldBrush);
12669 				SelectObject(hdcBmp, oldPen);
12670 				DeleteDC(hdcBmp);
12671 
12672 				bmpWidth = width;
12673 				bmpHeight = height;
12674 
12675 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
12676 			}
12677 
12678 			// We want the window's client area to match the image size
12679 			RECT rcClient, rcWindow;
12680 			POINT ptDiff;
12681 			GetClientRect(hwnd, &rcClient);
12682 			GetWindowRect(hwnd, &rcWindow);
12683 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
12684 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
12685 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
12686 
12687 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
12688 				ShowWindow(hwnd, SW_SHOWNORMAL);
12689 			} else {
12690 				_hidden = true;
12691 			}
12692 			this._visibleForTheFirstTimeCalled = false; // hack!
12693 		}
12694 
12695 
12696 		void dispose() {
12697 			if(buffer)
12698 				DeleteObject(buffer);
12699 		}
12700 
12701 		void closeWindow() {
12702 			if(ghRC) {
12703 				wglDeleteContext(ghRC);
12704 				ghRC = null;
12705 			}
12706 			DestroyWindow(hwnd);
12707 		}
12708 
12709 		bool setOpacity(ubyte alpha) {
12710 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
12711 		}
12712 
12713 		HANDLE currentCursor;
12714 
12715 		// returns zero if it recognized the event
12716 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
12717 			MouseEvent mouse;
12718 
12719 			void mouseEvent(bool isScreen, ulong mods) {
12720 				auto x = LOWORD(lParam);
12721 				auto y = HIWORD(lParam);
12722 				if(isScreen) {
12723 					POINT p;
12724 					p.x = x;
12725 					p.y = y;
12726 					ScreenToClient(hwnd, &p);
12727 					x = cast(ushort) p.x;
12728 					y = cast(ushort) p.y;
12729 				}
12730 
12731 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
12732 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
12733 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
12734 				}
12735 
12736 				mouse.x = x + offsetX;
12737 				mouse.y = y + offsetY;
12738 
12739 				wind.mdx(mouse);
12740 				mouse.modifierState = cast(int) mods;
12741 				mouse.window = wind;
12742 
12743 				if(wind.handleMouseEvent)
12744 					wind.handleMouseEvent(mouse);
12745 			}
12746 
12747 			switch(msg) {
12748 				case WM_GETMINMAXINFO:
12749 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12750 
12751 					if(wind.minWidth > 0) {
12752 						RECT rect;
12753 						rect.left = 100;
12754 						rect.top = 100;
12755 						rect.right = wind.minWidth + 100;
12756 						rect.bottom = wind.minHeight + 100;
12757 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12758 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12759 
12760 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12761 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12762 					}
12763 
12764 					if(wind.maxWidth < int.max) {
12765 						RECT rect;
12766 						rect.left = 100;
12767 						rect.top = 100;
12768 						rect.right = wind.maxWidth + 100;
12769 						rect.bottom = wind.maxHeight + 100;
12770 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12771 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12772 
12773 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12774 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12775 					}
12776 				break;
12777 				case WM_CHAR:
12778 					wchar c = cast(wchar) wParam;
12779 					if(wind.handleCharEvent)
12780 						wind.handleCharEvent(cast(dchar) c);
12781 				break;
12782 				  case WM_SETFOCUS:
12783 				  case WM_KILLFOCUS:
12784 					wind._focused = (msg == WM_SETFOCUS);
12785 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12786 					if(wind.onFocusChange)
12787 						wind.onFocusChange(msg == WM_SETFOCUS);
12788 				  break;
12789 
12790 				case WM_SYSKEYDOWN:
12791 					goto case;
12792 				case WM_SYSKEYUP:
12793 					if(lParam & (1 << 29)) {
12794 						goto case;
12795 					} else {
12796 						// no window has keyboard focus
12797 						goto default;
12798 					}
12799 				case WM_KEYDOWN:
12800 				case WM_KEYUP:
12801 					KeyEvent ev;
12802 					ev.key = cast(Key) wParam;
12803 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12804 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12805 
12806 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12807 
12808 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12809 						ev.modifierState |= ModifierState.shift;
12810 					//k8: this doesn't work; thanks for nothing, windows
12811 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12812 						ev.modifierState |= ModifierState.alt;*/
12813 					// this never seems to actually be set
12814 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12815 
12816 					if (wParam == 0x12) {
12817 						altPressed = (msg == WM_SYSKEYDOWN);
12818 					}
12819 
12820 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12821 						altPressed = false;
12822 					}
12823 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12824 
12825 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12826 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12827 						ev.modifierState |= ModifierState.ctrl;
12828 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12829 						ev.modifierState |= ModifierState.windows;
12830 					if(GetKeyState(Key.NumLock))
12831 						ev.modifierState |= ModifierState.numLock;
12832 					if(GetKeyState(Key.CapsLock))
12833 						ev.modifierState |= ModifierState.capsLock;
12834 
12835 					/+
12836 					// we always want to send the character too, so let's convert it
12837 					ubyte[256] state;
12838 					wchar[16] buffer;
12839 					GetKeyboardState(state.ptr);
12840 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12841 
12842 					foreach(dchar d; buffer) {
12843 						ev.character = d;
12844 						break;
12845 					}
12846 					+/
12847 
12848 					ev.window = wind;
12849 					if(wind.handleKeyEvent)
12850 						wind.handleKeyEvent(ev);
12851 				break;
12852 				case 0x020a /*WM_MOUSEWHEEL*/:
12853 					// send click
12854 					mouse.type = cast(MouseEventType) 1;
12855 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12856 					mouseEvent(true, LOWORD(wParam));
12857 
12858 					// also send release
12859 					mouse.type = cast(MouseEventType) 2;
12860 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12861 					mouseEvent(true, LOWORD(wParam));
12862 				break;
12863 				case WM_MOUSEMOVE:
12864 					mouse.type = cast(MouseEventType) 0;
12865 					mouseEvent(false, wParam);
12866 				break;
12867 				case WM_LBUTTONDOWN:
12868 				case WM_LBUTTONDBLCLK:
12869 					mouse.type = cast(MouseEventType) 1;
12870 					mouse.button = MouseButton.left;
12871 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12872 					mouseEvent(false, wParam);
12873 				break;
12874 				case WM_LBUTTONUP:
12875 					mouse.type = cast(MouseEventType) 2;
12876 					mouse.button = MouseButton.left;
12877 					mouseEvent(false, wParam);
12878 				break;
12879 				case WM_RBUTTONDOWN:
12880 				case WM_RBUTTONDBLCLK:
12881 					mouse.type = cast(MouseEventType) 1;
12882 					mouse.button = MouseButton.right;
12883 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12884 					mouseEvent(false, wParam);
12885 				break;
12886 				case WM_RBUTTONUP:
12887 					mouse.type = cast(MouseEventType) 2;
12888 					mouse.button = MouseButton.right;
12889 					mouseEvent(false, wParam);
12890 				break;
12891 				case WM_MBUTTONDOWN:
12892 				case WM_MBUTTONDBLCLK:
12893 					mouse.type = cast(MouseEventType) 1;
12894 					mouse.button = MouseButton.middle;
12895 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12896 					mouseEvent(false, wParam);
12897 				break;
12898 				case WM_MBUTTONUP:
12899 					mouse.type = cast(MouseEventType) 2;
12900 					mouse.button = MouseButton.middle;
12901 					mouseEvent(false, wParam);
12902 				break;
12903 				case WM_XBUTTONDOWN:
12904 				case WM_XBUTTONDBLCLK:
12905 					mouse.type = cast(MouseEventType) 1;
12906 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12907 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12908 					mouseEvent(false, wParam);
12909 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12910 				case WM_XBUTTONUP:
12911 					mouse.type = cast(MouseEventType) 2;
12912 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12913 					mouseEvent(false, wParam);
12914 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12915 
12916 				default: return 1;
12917 			}
12918 			return 0;
12919 		}
12920 
12921 		HWND hwnd;
12922 		private int oldWidth;
12923 		private int oldHeight;
12924 		private bool inSizeMove;
12925 
12926 		/++
12927 			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.
12928 
12929 			History:
12930 				Added November 23, 2021
12931 
12932 				Not fully stable, may be moved out of the impl struct.
12933 
12934 				Default value changed to `true` on February 15, 2021
12935 		+/
12936 		bool doLiveResizing = true;
12937 
12938 		package int bmpWidth;
12939 		package int bmpHeight;
12940 
12941 		// the extern(Windows) wndproc should just forward to this
12942 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12943 		try {
12944 			assert(hwnd is this.hwnd);
12945 
12946 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12947 			switch(msg) {
12948 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12949 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12950 					// The main things we can do are select, execute, close, or ignore
12951 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12952 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12953 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12954 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12955 
12956 					// returns the value in the *high order word* of the return value
12957 					// hence the << 16
12958 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12959 				case WM_SETCURSOR:
12960 					if(cast(HWND) wParam !is hwnd)
12961 						return 0; // further processing elsewhere
12962 
12963 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12964 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12965 						return 1;
12966 					} else {
12967 						return DefWindowProc(hwnd, msg, wParam, lParam);
12968 					}
12969 				//break;
12970 
12971 				case WM_CLOSE:
12972 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12973 				break;
12974 				case WM_DESTROY:
12975 					if (this.visibilityChanged !is null && this._visible) this.visibilityChanged(false);
12976 
12977 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12978 					SimpleWindow.nativeMapping.remove(hwnd);
12979 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12980 
12981 					bool anyImportant = false;
12982 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12983 						if(w.beingOpenKeepsAppOpen) {
12984 							anyImportant = true;
12985 							break;
12986 						}
12987 					if(!anyImportant) {
12988 						PostQuitMessage(0);
12989 					}
12990 				break;
12991 				case 0x02E0 /*WM_DPICHANGED*/:
12992 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12993 
12994 					RECT* prcNewWindow = cast(RECT*)lParam;
12995 					// docs say this is the recommended position and we should honor it
12996 					SetWindowPos(hwnd,
12997 							null,
12998 							prcNewWindow.left,
12999 							prcNewWindow.top,
13000 							prcNewWindow.right - prcNewWindow.left,
13001 							prcNewWindow.bottom - prcNewWindow.top,
13002 							SWP_NOZORDER | SWP_NOACTIVATE);
13003 
13004 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
13005 					// im not sure it is completely correct
13006 					// but without it the tabs and such do look weird as things change.
13007 					if(SystemParametersInfoForDpi) {
13008 						LOGFONT lfText;
13009 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
13010 						HFONT hFontNew = CreateFontIndirect(&lfText);
13011 						if (hFontNew)
13012 						{
13013 							//DeleteObject(hFontOld);
13014 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
13015 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
13016 								return TRUE;
13017 							}
13018 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
13019 						}
13020 					}
13021 
13022 					if(this.onDpiChanged)
13023 						this.onDpiChanged();
13024 				break;
13025 				case WM_ENTERIDLE:
13026 					// when a menu is up, it stops normal event processing (modal message loop)
13027 					// but this at least gives us a chance to SOMETIMES catch up
13028 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
13029 					SimpleWindow.processAllCustomEvents;
13030 					SimpleWindow.processAllCustomEvents;
13031 					SleepEx(0, true);
13032 					break;
13033 				case WM_SIZE:
13034 					if(wParam == 1 /* SIZE_MINIMIZED */)
13035 						break;
13036 					_width = LOWORD(lParam);
13037 					_height = HIWORD(lParam);
13038 
13039 					// I want to avoid tearing in the windows (my code is inefficient
13040 					// so this is a hack around that) so while sizing, we don't trigger,
13041 					// but we do want to trigger on events like mazimize.
13042 					if(!inSizeMove || doLiveResizing)
13043 						goto size_changed;
13044 				break;
13045 				/+
13046 				case WM_SIZING:
13047 					writeln("size");
13048 				break;
13049 				+/
13050 				// I don't like the tearing I get when redrawing on WM_SIZE
13051 				// (I know there's other ways to fix that but I don't like that behavior anyway)
13052 				// so instead it is going to redraw only at the end of a size.
13053 				case 0x0231: /* WM_ENTERSIZEMOVE */
13054 					inSizeMove = true;
13055 				break;
13056 				case 0x0232: /* WM_EXITSIZEMOVE */
13057 					inSizeMove = false;
13058 
13059 					size_changed:
13060 
13061 					// nothing relevant changed, don't bother redrawing
13062 					if(oldWidth == _width && oldHeight == _height) {
13063 						if(msg == 0x0232)
13064 							goto finalize_resize;
13065 						break;
13066 					}
13067 
13068 					// note: OpenGL windows don't use a backing bmp, so no need to change them
13069 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
13070 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
13071 						// gotta get the double buffer bmp to match the window
13072 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
13073 
13074 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
13075 						if(resizability != Resizability.automaticallyScaleIfPossible)
13076 						if(_width > bmpWidth || _height > bmpHeight) {
13077 							auto hdc = GetDC(hwnd);
13078 							auto oldBuffer = buffer;
13079 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
13080 
13081 							auto hdcBmp = CreateCompatibleDC(hdc);
13082 							auto oldBmp = SelectObject(hdcBmp, buffer);
13083 
13084 							auto hdcOldBmp = CreateCompatibleDC(hdc);
13085 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
13086 
13087 							/+
13088 							RECT r;
13089 							r.left = 0;
13090 							r.top = 0;
13091 							r.right = width;
13092 							r.bottom = height;
13093 							auto c = Color.green;
13094 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
13095 							FillRect(hdcBmp, &r, brush);
13096 							DeleteObject(brush);
13097 							+/
13098 
13099 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
13100 
13101 							bmpWidth = _width;
13102 							bmpHeight = _height;
13103 
13104 							SelectObject(hdcOldBmp, oldOldBmp);
13105 							DeleteDC(hdcOldBmp);
13106 
13107 							SelectObject(hdcBmp, oldBmp);
13108 							DeleteDC(hdcBmp);
13109 
13110 							ReleaseDC(hwnd, hdc);
13111 
13112 							DeleteObject(oldBuffer);
13113 						}
13114 					}
13115 
13116 					updateOpenglViewportIfNeeded(_width, _height);
13117 
13118 					if(resizability != Resizability.automaticallyScaleIfPossible)
13119 					if(windowResized !is null)
13120 						windowResized(_width, _height);
13121 
13122 					/+
13123 					if(inSizeMove) {
13124 						// SimpleWindow.processAllCustomEvents();
13125 						// SimpleWindow.processAllCustomEvents();
13126 
13127 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13128 						//sdpyPrintDebugString("redraw b");
13129 					} else {
13130 					+/ {
13131 						finalize_resize:
13132 						// when it is all done, make sure everything is freshly drawn or there might be
13133 						// weird bugs left.
13134 						SimpleWindow.processAllCustomEvents();
13135 						SimpleWindow.processAllCustomEvents();
13136 
13137 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13138 						// sdpyPrintDebugString("redraw");
13139 					}
13140 
13141 					oldWidth = this._width;
13142 					oldHeight = this._height;
13143 				break;
13144 				case WM_ERASEBKGND:
13145 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
13146 					if (!this._visibleForTheFirstTimeCalled) {
13147 						this._visibleForTheFirstTimeCalled = true;
13148 						if (this.visibleForTheFirstTime !is null) {
13149 							this.visibleForTheFirstTime();
13150 						}
13151 					}
13152 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
13153 					version(without_opengl) {} else {
13154 						if (openglMode == OpenGlOptions.yes) return 1;
13155 					}
13156 					// call windows default handler, so it can paint standard controls
13157 					goto default;
13158 				case WM_CTLCOLORBTN:
13159 				case WM_CTLCOLORSTATIC:
13160 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
13161 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
13162 					GetSysColorBrush(COLOR_3DFACE);
13163 				//break;
13164 				case WM_SHOWWINDOW:
13165 					auto before = this._visible;
13166 					this._visible = (wParam != 0);
13167 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
13168 						this._visibleForTheFirstTimeCalled = true;
13169 						if (this.visibleForTheFirstTime !is null) {
13170 							this.visibleForTheFirstTime();
13171 						}
13172 					}
13173 					if (this.visibilityChanged !is null && this._visible != before) this.visibilityChanged(this._visible);
13174 					break;
13175 				case WM_PAINT: {
13176 					if (!this._visibleForTheFirstTimeCalled) {
13177 						this._visibleForTheFirstTimeCalled = true;
13178 						if (this.visibleForTheFirstTime !is null) {
13179 							this.visibleForTheFirstTime();
13180 						}
13181 					}
13182 
13183 					BITMAP bm;
13184 					PAINTSTRUCT ps;
13185 
13186 					HDC hdc = BeginPaint(hwnd, &ps);
13187 
13188 					if(openglMode == OpenGlOptions.no) {
13189 
13190 						HDC hdcMem = CreateCompatibleDC(hdc);
13191 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
13192 
13193 						GetObject(buffer, bm.sizeof, &bm);
13194 
13195 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
13196 						if(resizability == Resizability.automaticallyScaleIfPossible)
13197 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
13198 						else
13199 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
13200 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
13201 
13202 						SelectObject(hdcMem, hbmOld);
13203 						DeleteDC(hdcMem);
13204 						EndPaint(hwnd, &ps);
13205 					} else {
13206 						EndPaint(hwnd, &ps);
13207 						version(without_opengl) {} else
13208 							redrawOpenGlSceneSoon();
13209 					}
13210 				} break;
13211 				  default:
13212 					return DefWindowProc(hwnd, msg, wParam, lParam);
13213 			}
13214 			 return 0;
13215 
13216 		}
13217 		catch(Throwable t) {
13218 			sdpyPrintDebugString(t.toString);
13219 			return 0;
13220 		}
13221 		}
13222 	}
13223 
13224 	mixin template NativeImageImplementation() {
13225 		HBITMAP handle;
13226 		ubyte* rawData;
13227 
13228 	final:
13229 
13230 		Color getPixel(int x, int y) @system {
13231 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13232 			// remember, bmps are upside down
13233 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13234 
13235 			Color c;
13236 			if(enableAlpha)
13237 				c.a = rawData[offset + 3];
13238 			else
13239 				c.a = 255;
13240 			c.b = rawData[offset + 0];
13241 			c.g = rawData[offset + 1];
13242 			c.r = rawData[offset + 2];
13243 			c.unPremultiply();
13244 			return c;
13245 		}
13246 
13247 		void setPixel(int x, int y, Color c) @system {
13248 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13249 			// remember, bmps are upside down
13250 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13251 
13252 			if(enableAlpha)
13253 				c.premultiply();
13254 
13255 			rawData[offset + 0] = c.b;
13256 			rawData[offset + 1] = c.g;
13257 			rawData[offset + 2] = c.r;
13258 			if(enableAlpha)
13259 				rawData[offset + 3] = c.a;
13260 		}
13261 
13262 		void convertToRgbaBytes(ubyte[] where) @system {
13263 			assert(where.length == this.width * this.height * 4);
13264 
13265 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13266 			int idx = 0;
13267 			int offset = itemsPerLine * (height - 1);
13268 			// remember, bmps are upside down
13269 			for(int y = height - 1; y >= 0; y--) {
13270 				auto offsetStart = offset;
13271 				for(int x = 0; x < width; x++) {
13272 					where[idx + 0] = rawData[offset + 2]; // r
13273 					where[idx + 1] = rawData[offset + 1]; // g
13274 					where[idx + 2] = rawData[offset + 0]; // b
13275 					if(enableAlpha) {
13276 						where[idx + 3] = rawData[offset + 3]; // a
13277 						unPremultiplyRgba(where[idx .. idx + 4]);
13278 						offset++;
13279 					} else
13280 						where[idx + 3] = 255; // a
13281 					idx += 4;
13282 					offset += 3;
13283 				}
13284 
13285 				offset = offsetStart - itemsPerLine;
13286 			}
13287 		}
13288 
13289 		void setFromRgbaBytes(in ubyte[] what) @system {
13290 			assert(what.length == this.width * this.height * 4);
13291 
13292 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
13293 			int idx = 0;
13294 			int offset = itemsPerLine * (height - 1);
13295 			// remember, bmps are upside down
13296 			for(int y = height - 1; y >= 0; y--) {
13297 				auto offsetStart = offset;
13298 				for(int x = 0; x < width; x++) {
13299 					if(enableAlpha) {
13300 						auto a = what[idx + 3];
13301 
13302 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
13303 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
13304 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
13305 						rawData[offset + 3] = a; // a
13306 						//premultiplyBgra(rawData[offset .. offset + 4]);
13307 						offset++;
13308 					} else {
13309 						rawData[offset + 2] = what[idx + 0]; // r
13310 						rawData[offset + 1] = what[idx + 1]; // g
13311 						rawData[offset + 0] = what[idx + 2]; // b
13312 					}
13313 					idx += 4;
13314 					offset += 3;
13315 				}
13316 
13317 				offset = offsetStart - itemsPerLine;
13318 			}
13319 		}
13320 
13321 
13322 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
13323 			BITMAPINFO infoheader;
13324 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
13325 			infoheader.bmiHeader.biWidth = width;
13326 			infoheader.bmiHeader.biHeight = height;
13327 			infoheader.bmiHeader.biPlanes = 1;
13328 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
13329 			infoheader.bmiHeader.biCompression = BI_RGB;
13330 
13331 			handle = CreateDIBSection(
13332 				null,
13333 				&infoheader,
13334 				DIB_RGB_COLORS,
13335 				cast(void**) &rawData,
13336 				null,
13337 				0);
13338 			if(handle is null)
13339 				throw new WindowsApiException("create image failed", GetLastError());
13340 
13341 		}
13342 
13343 		void dispose() {
13344 			DeleteObject(handle);
13345 		}
13346 	}
13347 
13348 	enum KEY_ESCAPE = 27;
13349 }
13350 
13351 version(Emscripten) {
13352 	alias int delegate(void*) NativeEventHandler;
13353 	alias void* NativeWindowHandle;
13354 
13355 	mixin template NativeSimpleWindowImplementation() { }
13356 	mixin template NativeScreenPainterImplementation() { }
13357 	mixin template NativeImageImplementation() { }
13358 }
13359 
13360 version(X11) {
13361 	/// This is the default font used. You might change this before doing anything else with
13362 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
13363 	/// for cross-platform compatibility.
13364 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13365 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13366 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
13367 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
13368 
13369 	alias int delegate(XEvent) NativeEventHandler;
13370 	alias Window NativeWindowHandle;
13371 
13372 	enum KEY_ESCAPE = 9;
13373 
13374 	mixin template NativeScreenPainterImplementation() {
13375 		Display* display;
13376 		Drawable d;
13377 		Drawable destiny;
13378 
13379 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
13380 		GC gc;
13381 
13382 		__gshared bool fontAttempted;
13383 
13384 		__gshared XFontStruct* defaultfont;
13385 		__gshared XFontSet defaultfontset;
13386 
13387 		XFontStruct* font;
13388 		XFontSet fontset;
13389 
13390 		void create(PaintingHandle window) {
13391 			this.display = XDisplayConnection.get();
13392 
13393 			Drawable buffer = None;
13394 			if(auto sw = cast(SimpleWindow) this.window) {
13395 				buffer = sw.impl.buffer;
13396 				this.destiny = cast(Drawable) window;
13397 			} else {
13398 				buffer = cast(Drawable) window;
13399 				this.destiny = None;
13400 			}
13401 
13402 			this.d = cast(Drawable) buffer;
13403 
13404 			auto dgc = DefaultGC(display, DefaultScreen(display));
13405 
13406 			this.gc = XCreateGC(display, d, 0, null);
13407 
13408 			XCopyGC(display, dgc, 0xffffffff, this.gc);
13409 
13410 			ensureDefaultFontLoaded();
13411 
13412 			font = defaultfont;
13413 			fontset = defaultfontset;
13414 
13415 			if(font) {
13416 				XSetFont(display, gc, font.fid);
13417 			}
13418 		}
13419 
13420 		static void ensureDefaultFontLoaded() {
13421 			if(!fontAttempted) {
13422 				auto display = XDisplayConnection.get;
13423 				auto font = XLoadQueryFont(display, xfontstr.ptr);
13424 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
13425 				if(font is null) {
13426 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
13427 					font = XLoadQueryFont(display, xfontstr.ptr);
13428 				}
13429 
13430 				char** lol;
13431 				int lol2;
13432 				char* lol3;
13433 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
13434 
13435 				fontAttempted = true;
13436 
13437 				defaultfont = font;
13438 				defaultfontset = fontset;
13439 			}
13440 		}
13441 
13442 		arsd.color.Rectangle _clipRectangle;
13443 		void setClipRectangle(int x, int y, int width, int height) {
13444 			auto old = _clipRectangle;
13445 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
13446 			if(old == _clipRectangle)
13447 				return;
13448 
13449 			if(width == 0 || height == 0) {
13450 				XSetClipMask(display, gc, None);
13451 
13452 				if(xrenderPicturePainter) {
13453 
13454 					XRectangle[1] rects;
13455 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
13456 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13457 				}
13458 
13459 				version(with_xft) {
13460 					if(xftDraw is null)
13461 						return;
13462 					XftDrawSetClip(xftDraw, null);
13463 				}
13464 			} else {
13465 				XRectangle[1] rects;
13466 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
13467 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
13468 
13469 				if(xrenderPicturePainter)
13470 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13471 
13472 				version(with_xft) {
13473 					if(xftDraw is null)
13474 						return;
13475 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
13476 				}
13477 			}
13478 		}
13479 
13480 		version(with_xft) {
13481 			XftFont* xftFont;
13482 			XftDraw* xftDraw;
13483 
13484 			XftColor xftColor;
13485 
13486 			void updateXftColor() {
13487 				if(xftFont is null)
13488 					return;
13489 
13490 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
13491 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
13492 
13493 				XftColorAllocValue(
13494 					display,
13495 					DefaultVisual(display, DefaultScreen(display)),
13496 					DefaultColormap(display, 0),
13497 					&colorIn,
13498 					&xftColor
13499 				);
13500 			}
13501 		}
13502 
13503 		void enableXftDraw() {
13504 			if(xftDraw is null) {
13505 				xftDraw = XftDrawCreate(
13506 					display,
13507 					d,
13508 					DefaultVisual(display, DefaultScreen(display)),
13509 					DefaultColormap(display, 0)
13510 				);
13511 
13512 				updateXftColor();
13513 			}
13514 		}
13515 
13516 		private OperatingSystemFont _activeFont;
13517 		void setFont(OperatingSystemFont font) {
13518 			_activeFont = font;
13519 			version(with_xft) {
13520 				if(font && font.isXft && font.xftFont)
13521 					this.xftFont = font.xftFont;
13522 				else
13523 					this.xftFont = null;
13524 
13525 				if(this.xftFont) {
13526 					enableXftDraw();
13527 					return;
13528 				}
13529 			}
13530 
13531 			if(font && font.font) {
13532 				this.font = font.font;
13533 				this.fontset = font.fontset;
13534 				XSetFont(display, gc, font.font.fid);
13535 			} else {
13536 				this.font = defaultfont;
13537 				this.fontset = defaultfontset;
13538 			}
13539 
13540 		}
13541 
13542 		private Picture xrenderPicturePainter;
13543 
13544 		bool manualInvalidations;
13545 		void invalidateRect(Rectangle invalidRect) {
13546 			// FIXME if manualInvalidations
13547 		}
13548 
13549 		void dispose() {
13550 			this.rasterOp = RasterOp.normal;
13551 
13552 			if(xrenderPicturePainter) {
13553 				XRenderFreePicture(display, xrenderPicturePainter);
13554 				xrenderPicturePainter = None;
13555 			}
13556 
13557 			// FIXME: this.window.width/height is probably wrong
13558 
13559 			// src x,y     then dest x, y
13560 			if(destiny != None) {
13561 				// FIXME: if manual invalidations we can actually only copy some of the area.
13562 				// if(manualInvalidations)
13563 				XSetClipMask(display, gc, None);
13564 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
13565 			}
13566 
13567 			XFreeGC(display, gc);
13568 
13569 			version(with_xft)
13570 			if(xftDraw) {
13571 				XftDrawDestroy(xftDraw);
13572 				xftDraw = null;
13573 			}
13574 
13575 			/+
13576 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
13577 			if(font && font !is defaultfont) {
13578 				XFreeFont(display, font);
13579 				font = null;
13580 			}
13581 			if(fontset && fontset !is defaultfontset) {
13582 				XFreeFontSet(display, fontset);
13583 				fontset = null;
13584 			}
13585 			+/
13586 			XFlush(display);
13587 
13588 			if(window.paintingFinishedDg !is null)
13589 				window.paintingFinishedDg()();
13590 		}
13591 
13592 		bool backgroundIsNotTransparent = true;
13593 		bool foregroundIsNotTransparent = true;
13594 
13595 		bool _penInitialized = false;
13596 		Pen _activePen;
13597 
13598 		Color _outlineColor;
13599 		Color _fillColor;
13600 
13601 		@property void pen(Pen p) {
13602 			if(_penInitialized && p == _activePen) {
13603 				return;
13604 			}
13605 			_penInitialized = true;
13606 			_activePen = p;
13607 			_outlineColor = p.color;
13608 
13609 			int style;
13610 
13611 			byte dashLength;
13612 
13613 			final switch(p.style) {
13614 				case Pen.Style.Solid:
13615 					style = 0 /*LineSolid*/;
13616 				break;
13617 				case Pen.Style.Dashed:
13618 					style = 1 /*LineOnOffDash*/;
13619 					dashLength = 4;
13620 				break;
13621 				case Pen.Style.Dotted:
13622 					style = 1 /*LineOnOffDash*/;
13623 					dashLength = 1;
13624 				break;
13625 			}
13626 
13627 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
13628 			if(dashLength)
13629 				XSetDashes(display, gc, 0, &dashLength, 1);
13630 
13631 			if(p.color.a == 0) {
13632 				foregroundIsNotTransparent = false;
13633 				return;
13634 			}
13635 
13636 			foregroundIsNotTransparent = true;
13637 
13638 			XSetForeground(display, gc, colorToX(p.color, display));
13639 
13640 			version(with_xft)
13641 				updateXftColor();
13642 		}
13643 
13644 		RasterOp _currentRasterOp;
13645 		bool _currentRasterOpInitialized = false;
13646 		@property void rasterOp(RasterOp op) {
13647 			if(_currentRasterOpInitialized && _currentRasterOp == op)
13648 				return;
13649 			_currentRasterOp = op;
13650 			_currentRasterOpInitialized = true;
13651 			int mode;
13652 			final switch(op) {
13653 				case RasterOp.normal:
13654 					mode = GXcopy;
13655 				break;
13656 				case RasterOp.xor:
13657 					mode = GXxor;
13658 				break;
13659 			}
13660 			XSetFunction(display, gc, mode);
13661 		}
13662 
13663 
13664 		bool _fillColorInitialized = false;
13665 
13666 		@property void fillColor(Color c) {
13667 			if(_fillColorInitialized && _fillColor == c)
13668 				return; // already good, no need to waste time calling it
13669 			_fillColor = c;
13670 			_fillColorInitialized = true;
13671 			if(c.a == 0) {
13672 				backgroundIsNotTransparent = false;
13673 				return;
13674 			}
13675 
13676 			backgroundIsNotTransparent = true;
13677 
13678 			XSetBackground(display, gc, colorToX(c, display));
13679 
13680 		}
13681 
13682 		void swapColors() {
13683 			auto tmp = _fillColor;
13684 			fillColor = _outlineColor;
13685 			auto newPen = _activePen;
13686 			newPen.color = tmp;
13687 			pen(newPen);
13688 		}
13689 
13690 		uint colorToX(Color c, Display* display) {
13691 			auto visual = DefaultVisual(display, DefaultScreen(display));
13692 			import core.bitop;
13693 			uint color = 0;
13694 			{
13695 			auto startBit = bsf(visual.red_mask);
13696 			auto lastBit = bsr(visual.red_mask);
13697 			auto r = cast(uint) c.r;
13698 			r >>= 7 - (lastBit - startBit);
13699 			r <<= startBit;
13700 			color |= r;
13701 			}
13702 			{
13703 			auto startBit = bsf(visual.green_mask);
13704 			auto lastBit = bsr(visual.green_mask);
13705 			auto g = cast(uint) c.g;
13706 			g >>= 7 - (lastBit - startBit);
13707 			g <<= startBit;
13708 			color |= g;
13709 			}
13710 			{
13711 			auto startBit = bsf(visual.blue_mask);
13712 			auto lastBit = bsr(visual.blue_mask);
13713 			auto b = cast(uint) c.b;
13714 			b >>= 7 - (lastBit - startBit);
13715 			b <<= startBit;
13716 			color |= b;
13717 			}
13718 
13719 
13720 
13721 			return color;
13722 		}
13723 
13724 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
13725 			// source x, source y
13726 			if(ix >= i.width) return;
13727 			if(iy >= i.height) return;
13728 			if(ix + w > i.width) w = i.width - ix;
13729 			if(iy + h > i.height) h = i.height - iy;
13730 			if(i.usingXshm)
13731 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
13732 			else
13733 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
13734 		}
13735 
13736 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
13737 			if(s.enableAlpha) {
13738 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
13739 				if(this.xrenderPicturePainter == None) {
13740 					XRenderPictureAttributes attrs;
13741 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
13742 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
13743 
13744 					// need to initialize the clip
13745 					XRectangle[1] rects;
13746 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
13747 
13748 					if(_clipRectangle != Rectangle.init)
13749 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13750 				}
13751 
13752 				XRenderComposite(
13753 					display,
13754 					3, // PicOpOver
13755 					s.xrenderPicture,
13756 					None,
13757 					this.xrenderPicturePainter,
13758 					ix,
13759 					iy,
13760 					0,
13761 					0,
13762 					x,
13763 					y,
13764 					w ? w : s.width,
13765 					h ? h : s.height
13766 				);
13767 			} else {
13768 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13769 			}
13770 		}
13771 
13772 		int fontHeight() {
13773 			version(with_xft)
13774 				if(xftFont !is null)
13775 					return xftFont.height;
13776 			if(font)
13777 				return font.max_bounds.ascent + font.max_bounds.descent;
13778 			return 12; // pretty common default...
13779 		}
13780 
13781 		int textWidth(in char[] line) {
13782 			version(with_xft)
13783 			if(xftFont) {
13784 				if(line.length == 0)
13785 					return 0;
13786 				XGlyphInfo extents;
13787 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13788 				return extents.width;
13789 			}
13790 
13791 			if(fontset) {
13792 				if(line.length == 0)
13793 					return 0;
13794 				XRectangle rect;
13795 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13796 
13797 				return rect.width;
13798 			}
13799 
13800 			if(font)
13801 				// FIXME: unicode
13802 				return XTextWidth( font, line.ptr, cast(int) line.length);
13803 			else
13804 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13805 		}
13806 
13807 		Size textSize(in char[] text) {
13808 			auto maxWidth = 0;
13809 			auto lineHeight = fontHeight;
13810 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13811 			foreach(line; text.split('\n')) {
13812 				int textWidth = this.textWidth(line);
13813 				if(textWidth > maxWidth)
13814 					maxWidth = textWidth;
13815 				h += lineHeight + 4;
13816 			}
13817 			return Size(maxWidth, h);
13818 		}
13819 
13820 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13821 			const(char)[] text;
13822 			version(with_xft)
13823 			if(xftFont) {
13824 				text = originalText;
13825 				goto loaded;
13826 			}
13827 
13828 			if(fontset)
13829 				text = originalText;
13830 			else {
13831 				text.reserve(originalText.length);
13832 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13833 				// then strip the rest so there isn't garbage
13834 				foreach(dchar ch; originalText)
13835 					if(ch < 256)
13836 						text ~= cast(ubyte) ch;
13837 					else
13838 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13839 			}
13840 			loaded:
13841 			if(text.length == 0)
13842 				return;
13843 
13844 			// FIXME: should we clip it to the bounding box?
13845 			int textHeight = fontHeight;
13846 
13847 			auto lines = text.split('\n');
13848 
13849 			const lineHeight = textHeight;
13850 			textHeight *= lines.length;
13851 
13852 			int cy = y;
13853 
13854 			if(alignment & TextAlignment.VerticalBottom) {
13855 				if(y2 <= 0)
13856 					return;
13857 				auto h = y2 - y;
13858 				if(h > textHeight) {
13859 					cy += h - textHeight;
13860 					cy -= lineHeight / 2;
13861 				}
13862 			} else if(alignment & TextAlignment.VerticalCenter) {
13863 				if(y2 <= 0)
13864 					return;
13865 				auto h = y2 - y;
13866 				if(textHeight < h) {
13867 					cy += (h - textHeight) / 2;
13868 					//cy -= lineHeight / 4;
13869 				}
13870 			}
13871 
13872 			foreach(line; text.split('\n')) {
13873 				int textWidth = this.textWidth(line);
13874 
13875 				int px = x, py = cy;
13876 
13877 				if(alignment & TextAlignment.Center) {
13878 					if(x2 <= 0)
13879 						return;
13880 					auto w = x2 - x;
13881 					if(w > textWidth)
13882 						px += (w - textWidth) / 2;
13883 				} else if(alignment & TextAlignment.Right) {
13884 					if(x2 <= 0)
13885 						return;
13886 					auto pos = x2 - textWidth;
13887 					if(pos > x)
13888 						px = pos;
13889 				}
13890 
13891 				version(with_xft)
13892 				if(xftFont) {
13893 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13894 
13895 					goto carry_on;
13896 				}
13897 
13898 				if(fontset)
13899 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13900 				else
13901 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13902 				carry_on:
13903 				cy += lineHeight + 4;
13904 			}
13905 		}
13906 
13907 		void drawPixel(int x, int y) {
13908 			XDrawPoint(display, d, gc, x, y);
13909 		}
13910 
13911 		// The basic shapes, outlined
13912 
13913 		void drawLine(int x1, int y1, int x2, int y2) {
13914 			if(foregroundIsNotTransparent)
13915 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13916 		}
13917 
13918 		void drawRectangle(int x, int y, int width, int height) {
13919 			if(backgroundIsNotTransparent) {
13920 				swapColors();
13921 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13922 				swapColors();
13923 			}
13924 			// 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
13925 			if(foregroundIsNotTransparent)
13926 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13927 		}
13928 
13929 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
13930 			int[4] radii = borderRadius;
13931 			auto r = Rectangle(upperLeft, lowerRight);
13932 
13933 			if(backgroundIsNotTransparent) {
13934 				swapColors();
13935 				// FIXME these overlap and thus draw the pixels multiple times
13936 				XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius);
13937 				XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13938 				swapColors();
13939 			}
13940 
13941 			drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top);
13942 			drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1);
13943 			drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2);
13944 			drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2);
13945 
13946 			//drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13947 
13948 			drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64);
13949 			drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64);
13950 			drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64);
13951 			drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64);
13952 		}
13953 
13954 
13955 		/// Arguments are the points of the bounding rectangle
13956 		void drawEllipse(int x1, int y1, int x2, int y2) {
13957 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13958 		}
13959 
13960 		// NOTE: start and finish are in units of degrees * 64
13961 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
13962 			if(backgroundIsNotTransparent) {
13963 				swapColors();
13964 				XFillArc(display, d, gc, x1, y1, width, height, start, length);
13965 				swapColors();
13966 			}
13967 			if(foregroundIsNotTransparent) {
13968 				XDrawArc(display, d, gc, x1, y1, width, height, start, length);
13969 
13970 				// Windows draws the straight lines on the edges too so FIXME sort of
13971 			}
13972 		}
13973 
13974 		void drawPolygon(Point[] vertexes) {
13975 			XPoint[16] pointsBuffer;
13976 			XPoint[] points;
13977 			if(vertexes.length <= pointsBuffer.length)
13978 				points = pointsBuffer[0 .. vertexes.length];
13979 			else
13980 				points.length = vertexes.length;
13981 
13982 			foreach(i, p; vertexes) {
13983 				points[i].x = cast(short) p.x;
13984 				points[i].y = cast(short) p.y;
13985 			}
13986 
13987 			if(backgroundIsNotTransparent) {
13988 				swapColors();
13989 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13990 				swapColors();
13991 			}
13992 			if(foregroundIsNotTransparent) {
13993 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13994 			}
13995 		}
13996 	}
13997 
13998 	/* XRender { */
13999 
14000 	struct XRenderColor {
14001 		ushort red;
14002 		ushort green;
14003 		ushort blue;
14004 		ushort alpha;
14005 	}
14006 
14007 	alias Picture = XID;
14008 	alias PictFormat = XID;
14009 
14010 	struct XGlyphInfo {
14011 		ushort width;
14012 		ushort height;
14013 		short x;
14014 		short y;
14015 		short xOff;
14016 		short yOff;
14017 	}
14018 
14019 struct XRenderDirectFormat {
14020     short   red;
14021     short   redMask;
14022     short   green;
14023     short   greenMask;
14024     short   blue;
14025     short   blueMask;
14026     short   alpha;
14027     short   alphaMask;
14028 }
14029 
14030 struct XRenderPictFormat {
14031     PictFormat		id;
14032     int			type;
14033     int			depth;
14034     XRenderDirectFormat	direct;
14035     Colormap		colormap;
14036 }
14037 
14038 enum PictFormatID	=   (1 << 0);
14039 enum PictFormatType	=   (1 << 1);
14040 enum PictFormatDepth	=   (1 << 2);
14041 enum PictFormatRed	=   (1 << 3);
14042 enum PictFormatRedMask  =(1 << 4);
14043 enum PictFormatGreen	=   (1 << 5);
14044 enum PictFormatGreenMask=(1 << 6);
14045 enum PictFormatBlue	=   (1 << 7);
14046 enum PictFormatBlueMask =(1 << 8);
14047 enum PictFormatAlpha	=   (1 << 9);
14048 enum PictFormatAlphaMask=(1 << 10);
14049 enum PictFormatColormap =(1 << 11);
14050 
14051 struct XRenderPictureAttributes {
14052 	int 		repeat;
14053 	Picture		alpha_map;
14054 	int			alpha_x_origin;
14055 	int			alpha_y_origin;
14056 	int			clip_x_origin;
14057 	int			clip_y_origin;
14058 	Pixmap		clip_mask;
14059 	Bool		graphics_exposures;
14060 	int			subwindow_mode;
14061 	int			poly_edge;
14062 	int			poly_mode;
14063 	Atom		dither;
14064 	Bool		component_alpha;
14065 }
14066 
14067 alias int XFixed;
14068 
14069 struct XPointFixed {
14070     XFixed  x, y;
14071 }
14072 
14073 struct XCircle {
14074     XFixed x;
14075     XFixed y;
14076     XFixed radius;
14077 }
14078 
14079 struct XTransform {
14080     XFixed[3][3]  matrix;
14081 }
14082 
14083 struct XFilters {
14084     int	    nfilter;
14085     char    **filter;
14086     int	    nalias;
14087     short   *alias_;
14088 }
14089 
14090 struct XIndexValue {
14091     c_ulong    pixel;
14092     ushort   red, green, blue, alpha;
14093 }
14094 
14095 struct XAnimCursor {
14096     Cursor	    cursor;
14097     c_ulong   delay;
14098 }
14099 
14100 struct XLinearGradient {
14101     XPointFixed p1;
14102     XPointFixed p2;
14103 }
14104 
14105 struct XRadialGradient {
14106     XCircle inner;
14107     XCircle outer;
14108 }
14109 
14110 struct XConicalGradient {
14111     XPointFixed center;
14112     XFixed angle; /* in degrees */
14113 }
14114 
14115 enum PictStandardARGB32  = 0;
14116 enum PictStandardRGB24   = 1;
14117 enum PictStandardA8	 =  2;
14118 enum PictStandardA4	 =  3;
14119 enum PictStandardA1	 =  4;
14120 enum PictStandardNUM	 =  5;
14121 
14122 interface XRender {
14123 extern(C) @nogc:
14124 
14125 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
14126 
14127 	Status XRenderQueryVersion (Display *dpy,
14128 			int     *major_versionp,
14129 			int     *minor_versionp);
14130 
14131 	Status XRenderQueryFormats (Display *dpy);
14132 
14133 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
14134 
14135 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
14136 
14137 	XRenderPictFormat *
14138 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
14139 
14140 	XRenderPictFormat *
14141 		XRenderFindFormat (Display			*dpy,
14142 				c_ulong		mask,
14143 				const XRenderPictFormat	*templ,
14144 				int				count);
14145 	XRenderPictFormat *
14146 		XRenderFindStandardFormat (Display		*dpy,
14147 				int			format);
14148 
14149 	XIndexValue *
14150 		XRenderQueryPictIndexValues(Display			*dpy,
14151 				const XRenderPictFormat	*format,
14152 				int				*num);
14153 
14154 	Picture XRenderCreatePicture(
14155 		Display *dpy,
14156 		Drawable drawable,
14157 		const XRenderPictFormat *format,
14158 		c_ulong valuemask,
14159 		const XRenderPictureAttributes *attributes);
14160 
14161 	void XRenderChangePicture (Display				*dpy,
14162 				Picture				picture,
14163 				c_ulong			valuemask,
14164 				const XRenderPictureAttributes  *attributes);
14165 
14166 	void
14167 		XRenderSetPictureClipRectangles (Display	    *dpy,
14168 				Picture	    picture,
14169 				int		    xOrigin,
14170 				int		    yOrigin,
14171 				const XRectangle *rects,
14172 				int		    n);
14173 
14174 	void
14175 		XRenderSetPictureClipRegion (Display	    *dpy,
14176 				Picture	    picture,
14177 				Region	    r);
14178 
14179 	void
14180 		XRenderSetPictureTransform (Display	    *dpy,
14181 				Picture	    picture,
14182 				XTransform	    *transform);
14183 
14184 	void
14185 		XRenderFreePicture (Display                   *dpy,
14186 				Picture                   picture);
14187 
14188 	void
14189 		XRenderComposite (Display   *dpy,
14190 				int	    op,
14191 				Picture   src,
14192 				Picture   mask,
14193 				Picture   dst,
14194 				int	    src_x,
14195 				int	    src_y,
14196 				int	    mask_x,
14197 				int	    mask_y,
14198 				int	    dst_x,
14199 				int	    dst_y,
14200 				uint	width,
14201 				uint	height);
14202 
14203 
14204 	Picture XRenderCreateSolidFill (Display *dpy,
14205 			const XRenderColor *color);
14206 
14207 	Picture XRenderCreateLinearGradient (Display *dpy,
14208 			const XLinearGradient *gradient,
14209 			const XFixed *stops,
14210 			const XRenderColor *colors,
14211 			int nstops);
14212 
14213 	Picture XRenderCreateRadialGradient (Display *dpy,
14214 			const XRadialGradient *gradient,
14215 			const XFixed *stops,
14216 			const XRenderColor *colors,
14217 			int nstops);
14218 
14219 	Picture XRenderCreateConicalGradient (Display *dpy,
14220 			const XConicalGradient *gradient,
14221 			const XFixed *stops,
14222 			const XRenderColor *colors,
14223 			int nstops);
14224 
14225 
14226 
14227 	Cursor
14228 		XRenderCreateCursor (Display	    *dpy,
14229 				Picture	    source,
14230 				uint   x,
14231 				uint   y);
14232 
14233 	XFilters *
14234 		XRenderQueryFilters (Display *dpy, Drawable drawable);
14235 
14236 	void
14237 		XRenderSetPictureFilter (Display    *dpy,
14238 				Picture    picture,
14239 				const char *filter,
14240 				XFixed	    *params,
14241 				int	    nparams);
14242 
14243 	Cursor
14244 		XRenderCreateAnimCursor (Display	*dpy,
14245 				int		ncursor,
14246 				XAnimCursor	*cursors);
14247 }
14248 
14249 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
14250 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
14251 
14252 	/* XRender } */
14253 
14254 	/* Xrandr { */
14255 
14256 struct XRRMonitorInfo {
14257     Atom name;
14258     Bool primary;
14259     Bool automatic;
14260     int noutput;
14261     int x;
14262     int y;
14263     int width;
14264     int height;
14265     int mwidth;
14266     int mheight;
14267     /*RROutput*/ void *outputs;
14268 }
14269 
14270 struct XRRScreenChangeNotifyEvent {
14271     int type;                   /* event base */
14272     c_ulong serial;       /* # of last request processed by server */
14273     Bool send_event;            /* true if this came from a SendEvent request */
14274     Display *display;           /* Display the event was read from */
14275     Window window;              /* window which selected for this event */
14276     Window root;                /* Root window for changed screen */
14277     Time timestamp;             /* when the screen change occurred */
14278     Time config_timestamp;      /* when the last configuration change */
14279     ushort/*SizeID*/ size_index;
14280     ushort/*SubpixelOrder*/ subpixel_order;
14281     ushort/*Rotation*/ rotation;
14282     int width;
14283     int height;
14284     int mwidth;
14285     int mheight;
14286 }
14287 
14288 enum RRScreenChangeNotify = 0;
14289 
14290 enum RRScreenChangeNotifyMask = 1;
14291 
14292 __gshared int xrrEventBase = -1;
14293 
14294 
14295 interface XRandr {
14296 extern(C) @nogc:
14297 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
14298 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
14299 
14300 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
14301 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
14302 
14303 	void XRRSelectInput(Display *dpy, Window window, int mask);
14304 }
14305 
14306 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
14307 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
14308 	/* Xrandr } */
14309 
14310 	/* Xft { */
14311 
14312 	// actually freetype
14313 	alias void FT_Face;
14314 
14315 	// actually fontconfig
14316 	private alias FcBool = int;
14317 	alias void FcCharSet;
14318 	alias void FcPattern;
14319 	alias void FcResult;
14320 	enum FcEndian { FcEndianBig, FcEndianLittle }
14321 	struct FcFontSet {
14322 		int nfont;
14323 		int sfont;
14324 		FcPattern** fonts;
14325 	}
14326 
14327 	// actually XRegion
14328 	struct BOX {
14329 		short x1, x2, y1, y2;
14330 	}
14331 	struct _XRegion {
14332 		c_long size;
14333 		c_long numRects;
14334 		BOX* rects;
14335 		BOX extents;
14336 	}
14337 
14338 	alias Region = _XRegion*;
14339 
14340 	// ok actually Xft
14341 
14342 	struct XftFontInfo;
14343 
14344 	struct XftFont {
14345 		int         ascent;
14346 		int         descent;
14347 		int         height;
14348 		int         max_advance_width;
14349 		FcCharSet*  charset;
14350 		FcPattern*  pattern;
14351 	}
14352 
14353 	struct XftDraw;
14354 
14355 	struct XftColor {
14356 		c_ulong pixel;
14357 		XRenderColor color;
14358 	}
14359 
14360 	struct XftCharSpec {
14361 		dchar           ucs4;
14362 		short           x;
14363 		short           y;
14364 	}
14365 
14366 	struct XftCharFontSpec {
14367 		XftFont         *font;
14368 		dchar           ucs4;
14369 		short           x;
14370 		short           y;
14371 	}
14372 
14373 	struct XftGlyphSpec {
14374 		uint            glyph;
14375 		short           x;
14376 		short           y;
14377 	}
14378 
14379 	struct XftGlyphFontSpec {
14380 		XftFont         *font;
14381 		uint            glyph;
14382 		short           x;
14383 		short           y;
14384 	}
14385 
14386 	interface Xft {
14387 	extern(C) @nogc pure:
14388 
14389 	Bool XftColorAllocName (Display  *dpy,
14390 				const Visual   *visual,
14391 				Colormap cmap,
14392 				const char     *name,
14393 				XftColor *result);
14394 
14395 	Bool XftColorAllocValue (Display         *dpy,
14396 				Visual          *visual,
14397 				Colormap        cmap,
14398 				const XRenderColor    *color,
14399 				XftColor        *result);
14400 
14401 	void XftColorFree (Display   *dpy,
14402 				Visual    *visual,
14403 				Colormap  cmap,
14404 				XftColor  *color);
14405 
14406 	Bool XftDefaultHasRender (Display *dpy);
14407 
14408 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
14409 
14410 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
14411 
14412 	XftDraw * XftDrawCreate (Display   *dpy,
14413 		       Drawable  drawable,
14414 		       Visual    *visual,
14415 		       Colormap  colormap);
14416 
14417 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
14418 			     Pixmap   bitmap);
14419 
14420 	XftDraw * XftDrawCreateAlpha (Display *dpy,
14421 			    Pixmap  pixmap,
14422 			    int     depth);
14423 
14424 	void XftDrawChange (XftDraw  *draw,
14425 		       Drawable drawable);
14426 
14427 	Display * XftDrawDisplay (XftDraw *draw);
14428 
14429 	Drawable XftDrawDrawable (XftDraw *draw);
14430 
14431 	Colormap XftDrawColormap (XftDraw *draw);
14432 
14433 	Visual * XftDrawVisual (XftDraw *draw);
14434 
14435 	void XftDrawDestroy (XftDraw *draw);
14436 
14437 	Picture XftDrawPicture (XftDraw *draw);
14438 
14439 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
14440 
14441 	void XftDrawGlyphs (XftDraw          *draw,
14442 				const XftColor *color,
14443 				XftFont          *pub,
14444 				int              x,
14445 				int              y,
14446 				const uint  *glyphs,
14447 				int              nglyphs);
14448 
14449 	void XftDrawString8 (XftDraw             *draw,
14450 				const XftColor    *color,
14451 				XftFont             *pub,
14452 				int                 x,
14453 				int                 y,
14454 				const char     *string,
14455 				int                 len);
14456 
14457 	void XftDrawString16 (XftDraw            *draw,
14458 				const XftColor   *color,
14459 				XftFont            *pub,
14460 				int                x,
14461 				int                y,
14462 				const wchar   *string,
14463 				int                len);
14464 
14465 	void XftDrawString32 (XftDraw            *draw,
14466 				const XftColor   *color,
14467 				XftFont            *pub,
14468 				int                x,
14469 				int                y,
14470 				const dchar   *string,
14471 				int                len);
14472 
14473 	void XftDrawStringUtf8 (XftDraw          *draw,
14474 				const XftColor *color,
14475 				XftFont          *pub,
14476 				int              x,
14477 				int              y,
14478 				const char  *string,
14479 				int              len);
14480 	void XftDrawStringUtf16 (XftDraw             *draw,
14481 				const XftColor    *color,
14482 				XftFont             *pub,
14483 				int                 x,
14484 				int                 y,
14485 				const char     *string,
14486 				FcEndian            endian,
14487 				int                 len);
14488 
14489 	void XftDrawCharSpec (XftDraw                *draw,
14490 				const XftColor       *color,
14491 				XftFont                *pub,
14492 				const XftCharSpec    *chars,
14493 				int                    len);
14494 
14495 	void XftDrawCharFontSpec (XftDraw                    *draw,
14496 				const XftColor           *color,
14497 				const XftCharFontSpec    *chars,
14498 				int                        len);
14499 
14500 	void XftDrawGlyphSpec (XftDraw               *draw,
14501 				const XftColor      *color,
14502 				XftFont               *pub,
14503 				const XftGlyphSpec  *glyphs,
14504 				int                   len);
14505 
14506 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
14507 				const XftColor          *color,
14508 				const XftGlyphFontSpec  *glyphs,
14509 				int                       len);
14510 
14511 	void XftDrawRect (XftDraw            *draw,
14512 				const XftColor   *color,
14513 				int                x,
14514 				int                y,
14515 				uint       width,
14516 				uint       height);
14517 
14518 	Bool XftDrawSetClip (XftDraw     *draw,
14519 				Region      r);
14520 
14521 
14522 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
14523 				int                   xOrigin,
14524 				int                   yOrigin,
14525 				const XRectangle    *rects,
14526 				int                   n);
14527 
14528 	void XftDrawSetSubwindowMode (XftDraw    *draw,
14529 				int        mode);
14530 
14531 	void XftGlyphExtents (Display            *dpy,
14532 				XftFont            *pub,
14533 				const uint    *glyphs,
14534 				int                nglyphs,
14535 				XGlyphInfo         *extents);
14536 
14537 	void XftTextExtents8 (Display            *dpy,
14538 				XftFont            *pub,
14539 				const char    *string,
14540 				int                len,
14541 				XGlyphInfo         *extents);
14542 
14543 	void XftTextExtents16 (Display           *dpy,
14544 				XftFont           *pub,
14545 				const wchar  *string,
14546 				int               len,
14547 				XGlyphInfo        *extents);
14548 
14549 	void XftTextExtents32 (Display           *dpy,
14550 				XftFont           *pub,
14551 				const dchar  *string,
14552 				int               len,
14553 				XGlyphInfo        *extents);
14554 
14555 	void XftTextExtentsUtf8 (Display         *dpy,
14556 				XftFont         *pub,
14557 				const char *string,
14558 				int             len,
14559 				XGlyphInfo      *extents);
14560 
14561 	void XftTextExtentsUtf16 (Display            *dpy,
14562 				XftFont            *pub,
14563 				const char    *string,
14564 				FcEndian           endian,
14565 				int                len,
14566 				XGlyphInfo         *extents);
14567 
14568 	FcPattern * XftFontMatch (Display           *dpy,
14569 				int               screen,
14570 				const FcPattern *pattern,
14571 				FcResult          *result);
14572 
14573 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
14574 
14575 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
14576 
14577 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
14578 
14579 	FT_Face XftLockFace (XftFont *pub);
14580 
14581 	void XftUnlockFace (XftFont *pub);
14582 
14583 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
14584 
14585 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
14586 
14587 	dchar XftFontInfoHash (const XftFontInfo *fi);
14588 
14589 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
14590 
14591 	XftFont * XftFontOpenInfo (Display        *dpy,
14592 				FcPattern      *pattern,
14593 				XftFontInfo    *fi);
14594 
14595 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
14596 
14597 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
14598 
14599 	void XftFontClose (Display *dpy, XftFont *pub);
14600 
14601 	FcBool XftInitFtLibrary();
14602 	void XftFontLoadGlyphs (Display          *dpy,
14603 				XftFont          *pub,
14604 				FcBool           need_bitmaps,
14605 				const uint  *glyphs,
14606 				int              nglyph);
14607 
14608 	void XftFontUnloadGlyphs (Display            *dpy,
14609 				XftFont            *pub,
14610 				const uint    *glyphs,
14611 				int                nglyph);
14612 
14613 	FcBool XftFontCheckGlyph (Display  *dpy,
14614 				XftFont  *pub,
14615 				FcBool   need_bitmaps,
14616 				uint  glyph,
14617 				uint  *missing,
14618 				int      *nmissing);
14619 
14620 	FcBool XftCharExists (Display      *dpy,
14621 				XftFont      *pub,
14622 				dchar    ucs4);
14623 
14624 	uint XftCharIndex (Display       *dpy,
14625 				XftFont       *pub,
14626 				dchar      ucs4);
14627 	FcBool XftInit (const char *config);
14628 
14629 	int XftGetVersion ();
14630 
14631 	FcFontSet * XftListFonts (Display   *dpy,
14632 				int       screen,
14633 				...);
14634 
14635 	FcPattern *XftNameParse (const char *name);
14636 
14637 	void XftGlyphRender (Display         *dpy,
14638 				int             op,
14639 				Picture         src,
14640 				XftFont         *pub,
14641 				Picture         dst,
14642 				int             srcx,
14643 				int             srcy,
14644 				int             x,
14645 				int             y,
14646 				const uint *glyphs,
14647 				int             nglyphs);
14648 
14649 	void XftGlyphSpecRender (Display                 *dpy,
14650 				int                     op,
14651 				Picture                 src,
14652 				XftFont                 *pub,
14653 				Picture                 dst,
14654 				int                     srcx,
14655 				int                     srcy,
14656 				const XftGlyphSpec    *glyphs,
14657 				int                     nglyphs);
14658 
14659 	void XftCharSpecRender (Display              *dpy,
14660 				int                  op,
14661 				Picture              src,
14662 				XftFont              *pub,
14663 				Picture              dst,
14664 				int                  srcx,
14665 				int                  srcy,
14666 				const XftCharSpec  *chars,
14667 				int                  len);
14668 	void XftGlyphFontSpecRender (Display                     *dpy,
14669 				int                         op,
14670 				Picture                     src,
14671 				Picture                     dst,
14672 				int                         srcx,
14673 				int                         srcy,
14674 				const XftGlyphFontSpec    *glyphs,
14675 				int                         nglyphs);
14676 
14677 	void XftCharFontSpecRender (Display                  *dpy,
14678 				int                      op,
14679 				Picture                  src,
14680 				Picture                  dst,
14681 				int                      srcx,
14682 				int                      srcy,
14683 				const XftCharFontSpec  *chars,
14684 				int                      len);
14685 
14686 	void XftTextRender8 (Display         *dpy,
14687 				int             op,
14688 				Picture         src,
14689 				XftFont         *pub,
14690 				Picture         dst,
14691 				int             srcx,
14692 				int             srcy,
14693 				int             x,
14694 				int             y,
14695 				const char *string,
14696 				int             len);
14697 	void XftTextRender16 (Display            *dpy,
14698 				int                op,
14699 				Picture            src,
14700 				XftFont            *pub,
14701 				Picture            dst,
14702 				int                srcx,
14703 				int                srcy,
14704 				int                x,
14705 				int                y,
14706 				const wchar   *string,
14707 				int                len);
14708 
14709 	void XftTextRender16BE (Display          *dpy,
14710 				int              op,
14711 				Picture          src,
14712 				XftFont          *pub,
14713 				Picture          dst,
14714 				int              srcx,
14715 				int              srcy,
14716 				int              x,
14717 				int              y,
14718 				const char  *string,
14719 				int              len);
14720 
14721 	void XftTextRender16LE (Display          *dpy,
14722 				int              op,
14723 				Picture          src,
14724 				XftFont          *pub,
14725 				Picture          dst,
14726 				int              srcx,
14727 				int              srcy,
14728 				int              x,
14729 				int              y,
14730 				const char  *string,
14731 				int              len);
14732 
14733 	void XftTextRender32 (Display            *dpy,
14734 				int                op,
14735 				Picture            src,
14736 				XftFont            *pub,
14737 				Picture            dst,
14738 				int                srcx,
14739 				int                srcy,
14740 				int                x,
14741 				int                y,
14742 				const dchar   *string,
14743 				int                len);
14744 
14745 	void XftTextRender32BE (Display          *dpy,
14746 				int              op,
14747 				Picture          src,
14748 				XftFont          *pub,
14749 				Picture          dst,
14750 				int              srcx,
14751 				int              srcy,
14752 				int              x,
14753 				int              y,
14754 				const char  *string,
14755 				int              len);
14756 
14757 	void XftTextRender32LE (Display          *dpy,
14758 				int              op,
14759 				Picture          src,
14760 				XftFont          *pub,
14761 				Picture          dst,
14762 				int              srcx,
14763 				int              srcy,
14764 				int              x,
14765 				int              y,
14766 				const char  *string,
14767 				int              len);
14768 
14769 	void XftTextRenderUtf8 (Display          *dpy,
14770 				int              op,
14771 				Picture          src,
14772 				XftFont          *pub,
14773 				Picture          dst,
14774 				int              srcx,
14775 				int              srcy,
14776 				int              x,
14777 				int              y,
14778 				const char  *string,
14779 				int              len);
14780 
14781 	void XftTextRenderUtf16 (Display         *dpy,
14782 				int             op,
14783 				Picture         src,
14784 				XftFont         *pub,
14785 				Picture         dst,
14786 				int             srcx,
14787 				int             srcy,
14788 				int             x,
14789 				int             y,
14790 				const char *string,
14791 				FcEndian        endian,
14792 				int             len);
14793 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14794 
14795 	}
14796 
14797 	interface FontConfig {
14798 	extern(C) @nogc pure:
14799 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14800 		void FcFontSetDestroy(FcFontSet*);
14801 		char* FcNameUnparse(const FcPattern *);
14802 	}
14803 
14804 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14805 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14806 
14807 
14808 	/* Xft } */
14809 
14810 	class XDisconnectException : Exception {
14811 		bool userRequested;
14812 		this(bool userRequested = true) {
14813 			this.userRequested = userRequested;
14814 			super("X disconnected");
14815 		}
14816 	}
14817 
14818 	/++
14819 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14820 
14821 		Please note that it returns
14822 	+/
14823 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14824 
14825 		static XErrorEvent[] errorBuffer;
14826 
14827 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14828 			errorBuffer ~= *evt;
14829 			return 0;
14830 		}
14831 
14832 		auto savedErrorHandler = XSetErrorHandler(&handler);
14833 
14834 		try {
14835 			dg();
14836 		} finally {
14837 			XSync(XDisplayConnection.get, 0/*False*/);
14838 			XSetErrorHandler(savedErrorHandler);
14839 		}
14840 
14841 		auto bfr = errorBuffer;
14842 		errorBuffer = null;
14843 
14844 		return bfr;
14845 	}
14846 
14847 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14848 	class XDisplayConnection {
14849 		private __gshared Display* display;
14850 		private __gshared XIM xim;
14851 		private __gshared char* displayName;
14852 
14853 		private __gshared int connectionSequence_;
14854 		private __gshared bool isLocal_;
14855 
14856 		/// use this for lazy caching when reconnection
14857 		static int connectionSequenceNumber() { return connectionSequence_; }
14858 
14859 		/++
14860 			Guesses if the connection appears to be local.
14861 
14862 			History:
14863 				Added June 3, 2021
14864 		+/
14865 		static @property bool isLocal() nothrow @trusted @nogc {
14866 			return isLocal_;
14867 		}
14868 
14869 		/// Attempts recreation of state, may require application assistance
14870 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14871 		/// then call this, and if successful, reenter the loop.
14872 		static void discardAndRecreate(string newDisplayString = null) {
14873 			if(insideXEventLoop)
14874 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14875 
14876 			// 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
14877 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14878 
14879 			foreach(handle; chnenhm) {
14880 				handle.discardConnectionState();
14881 			}
14882 
14883 			discardState();
14884 
14885 			if(newDisplayString !is null)
14886 				setDisplayName(newDisplayString);
14887 
14888 			auto display = get();
14889 
14890 			foreach(handle; chnenhm) {
14891 				handle.recreateAfterDisconnect();
14892 			}
14893 		}
14894 
14895 		private __gshared EventMask rootEventMask;
14896 
14897 		/++
14898 			Requests the specified input from the root window on the connection, in addition to any other request.
14899 
14900 
14901 			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.
14902 
14903 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14904 		+/
14905 		static void addRootInput(EventMask mask) {
14906 			auto old = rootEventMask;
14907 			rootEventMask |= mask;
14908 			get(); // to ensure display connected
14909 			if(display !is null && rootEventMask != old)
14910 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14911 		}
14912 
14913 		static void discardState() {
14914 			freeImages();
14915 
14916 			foreach(atomPtr; interredAtoms)
14917 				*atomPtr = 0;
14918 			interredAtoms = null;
14919 			interredAtoms.assumeSafeAppend();
14920 
14921 			ScreenPainterImplementation.fontAttempted = false;
14922 			ScreenPainterImplementation.defaultfont = null;
14923 			ScreenPainterImplementation.defaultfontset = null;
14924 
14925 			Image.impl.xshmQueryCompleted = false;
14926 			Image.impl._xshmAvailable = false;
14927 
14928 			SimpleWindow.nativeMapping = null;
14929 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14930 			// GlobalHotkeyManager
14931 
14932 			display = null;
14933 			xim = null;
14934 		}
14935 
14936 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14937 		private static void createXIM () {
14938 			import core.stdc.locale : setlocale, LC_ALL;
14939 			import core.stdc.stdio : stderr, fprintf;
14940 			import core.stdc.stdlib : free;
14941 			import core.stdc.string : strdup;
14942 
14943 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14944 
14945 			auto olocale = strdup(setlocale(LC_ALL, null));
14946 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14947 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14948 
14949 			//fprintf(stderr, "opening IM...\n");
14950 			foreach (string s; mtry) {
14951 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14952 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14953 			}
14954 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14955 		}
14956 
14957 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14958 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14959 		static struct ImgList {
14960 			size_t img; // class; hide it from GC
14961 			ImgList* next;
14962 		}
14963 
14964 		static __gshared ImgList* imglist = null;
14965 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14966 
14967 		static void registerImage (Image img) {
14968 			if (!imglistLocked && img !is null) {
14969 				import core.stdc.stdlib : malloc;
14970 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14971 				assert(it !is null); // do proper checks
14972 				it.img = cast(size_t)cast(void*)img;
14973 				it.next = imglist;
14974 				imglist = it;
14975 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14976 			}
14977 		}
14978 
14979 		static void unregisterImage (Image img) {
14980 			if (!imglistLocked && img !is null) {
14981 				import core.stdc.stdlib : free;
14982 				ImgList* prev = null;
14983 				ImgList* cur = imglist;
14984 				while (cur !is null) {
14985 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14986 					prev = cur;
14987 					cur = cur.next;
14988 				}
14989 				if (cur !is null) {
14990 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14991 					free(cur);
14992 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14993 				} else {
14994 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14995 				}
14996 			}
14997 		}
14998 
14999 		static void freeImages () { // needed for discardAndRecreate
15000 			imglistLocked = true;
15001 			scope(exit) imglistLocked = false;
15002 			ImgList* cur = imglist;
15003 			ImgList* next = null;
15004 			while (cur !is null) {
15005 				import core.stdc.stdlib : free;
15006 				next = cur.next;
15007 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
15008 				(cast(Image)cast(void*)cur.img).dispose();
15009 				free(cur);
15010 				cur = next;
15011 			}
15012 			imglist = null;
15013 		}
15014 
15015 		/// can be used to override normal handling of display name
15016 		/// from environment and/or command line
15017 		static setDisplayName(string newDisplayName) {
15018 			displayName = cast(char*) (newDisplayName ~ '\0');
15019 		}
15020 
15021 		/// resets to the default display string
15022 		static resetDisplayName() {
15023 			displayName = null;
15024 		}
15025 
15026 		///
15027 		static Display* get() {
15028 			if(display is null) {
15029 				if(!librariesSuccessfullyLoaded)
15030 					throw new Exception("Unable to load X11 client libraries");
15031 				display = XOpenDisplay(displayName);
15032 
15033 				isLocal_ = false;
15034 
15035 				connectionSequence_++;
15036 				if(display is null)
15037 					throw new Exception("Unable to open X display");
15038 
15039 				auto str = display.display_name;
15040 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
15041 				// and otherwise it probably isn't
15042 				if(str is null || (str[0] != ':' && str[0] != '/'))
15043 					isLocal_ = false;
15044 				else
15045 					isLocal_ = true;
15046 
15047 				XSetErrorHandler(&adrlogger);
15048 
15049 				debug(sdpy_x_errors) {
15050 					XSynchronize(display, true);
15051 
15052 					extern(C) int wtf() {
15053 						if(errorHappened) {
15054 							asm { int 3; }
15055 							errorHappened = false;
15056 						}
15057 						return 0;
15058 					}
15059 					XSetAfterFunction(display, &wtf);
15060 				}
15061 
15062 
15063 				XSetIOErrorHandler(&x11ioerrCB);
15064 				Bool sup;
15065 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
15066 				createXIM();
15067 				version(with_eventloop) {
15068 					import arsd.eventloop;
15069 					addFileEventListeners(display.fd, &eventListener, null, null);
15070 				}
15071 			}
15072 
15073 			return display;
15074 		}
15075 
15076 		extern(C)
15077 		static int x11ioerrCB(Display* dpy) {
15078 			throw new XDisconnectException(false);
15079 		}
15080 
15081 		version(with_eventloop) {
15082 			import arsd.eventloop;
15083 			static void eventListener(OsFileHandle fd) {
15084 				//this.mtLock();
15085 				//scope(exit) this.mtUnlock();
15086 				while(XPending(display))
15087 					doXNextEvent(display);
15088 			}
15089 		}
15090 
15091 		// close connection on program exit -- we need this to properly free all images
15092 		static ~this () {
15093 			// the gui thread must clean up after itself or else Xlib might deadlock
15094 			// using this flag on any thread destruction is the easiest way i know of
15095 			// (shared static this is run by the LAST thread to exit, which may not be
15096 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
15097 			if(thisIsGuiThread)
15098 				close();
15099 		}
15100 
15101 		///
15102 		static void close() {
15103 			if(display is null)
15104 				return;
15105 
15106 			version(with_eventloop) {
15107 				import arsd.eventloop;
15108 				removeFileEventListeners(display.fd);
15109 			}
15110 
15111 			// now remove all registered images to prevent shared memory leaks
15112 			freeImages();
15113 
15114 			// tbh I don't know why it is doing this but like if this happens to run
15115 			// from the other thread there's frequent hanging inside here.
15116 			if(thisIsGuiThread)
15117 				XCloseDisplay(display);
15118 			display = null;
15119 		}
15120 	}
15121 
15122 	mixin template NativeImageImplementation() {
15123 		XImage* handle;
15124 		ubyte* rawData;
15125 
15126 		XShmSegmentInfo shminfo;
15127 		bool premultiply = true;
15128 
15129 		__gshared bool xshmQueryCompleted;
15130 		__gshared bool _xshmAvailable;
15131 		public static @property bool xshmAvailable() {
15132 			if(!xshmQueryCompleted) {
15133 				int i1, i2, i3;
15134 				xshmQueryCompleted = true;
15135 
15136 				if(!XDisplayConnection.isLocal)
15137 					_xshmAvailable = false;
15138 				else
15139 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
15140 			}
15141 			return _xshmAvailable;
15142 		}
15143 
15144 		bool usingXshm;
15145 	final:
15146 
15147 		private __gshared bool xshmfailed;
15148 
15149 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
15150 			auto display = XDisplayConnection.get();
15151 			assert(display !is null);
15152 			auto screen = DefaultScreen(display);
15153 
15154 			// it will only use shared memory for somewhat largish images,
15155 			// since otherwise we risk wasting shared memory handles on a lot of little ones
15156 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
15157 
15158 
15159 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
15160 				// the actual use still fails. For example, if the program is in a container and permission denied
15161 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
15162 				//
15163 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
15164 
15165 
15166 				// synchronize so preexisting buffers are clear
15167 				XSync(display, false);
15168 				xshmfailed = false;
15169 
15170 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
15171 
15172 
15173 				usingXshm = true;
15174 				handle = XShmCreateImage(
15175 					display,
15176 					DefaultVisual(display, screen),
15177 					enableAlpha ? 32: 24,
15178 					ImageFormat.ZPixmap,
15179 					null,
15180 					&shminfo,
15181 					width, height);
15182 				if(handle is null)
15183 					goto abortXshm1;
15184 
15185 				if(handle.bytes_per_line != 4 * width)
15186 					goto abortXshm2;
15187 
15188 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
15189 				if(shminfo.shmid < 0)
15190 					goto abortXshm3;
15191 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
15192 				if(rawData == cast(ubyte*) -1)
15193 					goto abortXshm4;
15194 				shminfo.readOnly = 0;
15195 				XShmAttach(display, &shminfo);
15196 
15197 				// and now to the final error check to ensure it actually worked.
15198 				XSync(display, false);
15199 				if(xshmfailed)
15200 					goto abortXshm5;
15201 
15202 				XSetErrorHandler(oldErrorHandler);
15203 
15204 				XDisplayConnection.registerImage(this);
15205 				// if I don't flush here there's a chance the dtor will run before the
15206 				// ctor and lead to a bad value X error. While this hurts the efficiency
15207 				// it is local anyway so prolly better to keep it simple
15208 				XFlush(display);
15209 
15210 				return;
15211 
15212 				abortXshm5:
15213 					shmdt(shminfo.shmaddr);
15214 					rawData = null;
15215 
15216 				abortXshm4:
15217 					shmctl(shminfo.shmid, IPC_RMID, null);
15218 
15219 				abortXshm3:
15220 					// nothing needed, the shmget failed so there's nothing to free
15221 
15222 				abortXshm2:
15223 					XDestroyImage(handle);
15224 					handle = null;
15225 
15226 				abortXshm1:
15227 					XSetErrorHandler(oldErrorHandler);
15228 					usingXshm = false;
15229 					handle = null;
15230 
15231 					shminfo = typeof(shminfo).init;
15232 
15233 					_xshmAvailable = false; // don't try again in the future
15234 
15235 					// writeln("fallingback");
15236 
15237 					goto fallback;
15238 
15239 			} else {
15240 				fallback:
15241 
15242 				if (forcexshm) throw new Exception("can't create XShm Image");
15243 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
15244 				import core.stdc.stdlib : malloc;
15245 				rawData = cast(ubyte*) malloc(width * height * 4);
15246 
15247 				handle = XCreateImage(
15248 					display,
15249 					DefaultVisual(display, screen),
15250 					enableAlpha ? 32 : 24, // bpp
15251 					ImageFormat.ZPixmap,
15252 					0, // offset
15253 					rawData,
15254 					width, height,
15255 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
15256 			}
15257 		}
15258 
15259 		void dispose() {
15260 			// note: this calls free(rawData) for us
15261 			if(handle) {
15262 				if (usingXshm) {
15263 					XDisplayConnection.unregisterImage(this);
15264 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
15265 				}
15266 				XDestroyImage(handle);
15267 				if(usingXshm) {
15268 					shmdt(shminfo.shmaddr);
15269 					shmctl(shminfo.shmid, IPC_RMID, null);
15270 				}
15271 				handle = null;
15272 			}
15273 		}
15274 
15275 		Color getPixel(int x, int y) @system {
15276 			auto offset = (y * width + x) * 4;
15277 			Color c;
15278 			c.a = enableAlpha ? rawData[offset + 3] : 255;
15279 			c.b = rawData[offset + 0];
15280 			c.g = rawData[offset + 1];
15281 			c.r = rawData[offset + 2];
15282 			if(enableAlpha && premultiply)
15283 				c.unPremultiply;
15284 			return c;
15285 		}
15286 
15287 		void setPixel(int x, int y, Color c) @system {
15288 			if(enableAlpha && premultiply)
15289 				c.premultiply();
15290 			auto offset = (y * width + x) * 4;
15291 			rawData[offset + 0] = c.b;
15292 			rawData[offset + 1] = c.g;
15293 			rawData[offset + 2] = c.r;
15294 			if(enableAlpha)
15295 				rawData[offset + 3] = c.a;
15296 		}
15297 
15298 		void convertToRgbaBytes(ubyte[] where) @system {
15299 			assert(where.length == this.width * this.height * 4);
15300 
15301 			// if rawData had a length....
15302 			//assert(rawData.length == where.length);
15303 			for(int idx = 0; idx < where.length; idx += 4) {
15304 				where[idx + 0] = rawData[idx + 2]; // r
15305 				where[idx + 1] = rawData[idx + 1]; // g
15306 				where[idx + 2] = rawData[idx + 0]; // b
15307 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
15308 
15309 				if(enableAlpha && premultiply)
15310 					unPremultiplyRgba(where[idx .. idx + 4]);
15311 			}
15312 		}
15313 
15314 		void setFromRgbaBytes(in ubyte[] where) @system {
15315 			assert(where.length == this.width * this.height * 4);
15316 
15317 			// if rawData had a length....
15318 			//assert(rawData.length == where.length);
15319 			for(int idx = 0; idx < where.length; idx += 4) {
15320 				rawData[idx + 2] = where[idx + 0]; // r
15321 				rawData[idx + 1] = where[idx + 1]; // g
15322 				rawData[idx + 0] = where[idx + 2]; // b
15323 				if(enableAlpha) {
15324 					rawData[idx + 3] = where[idx + 3]; // a
15325 					if(premultiply)
15326 						premultiplyBgra(rawData[idx .. idx + 4]);
15327 				}
15328 			}
15329 		}
15330 
15331 	}
15332 
15333 	mixin template NativeSimpleWindowImplementation() {
15334 		GC gc;
15335 		Window window;
15336 		Display* display;
15337 
15338 		Pixmap buffer;
15339 		int bufferw, bufferh; // size of the buffer; can be bigger than window
15340 		XIC xic; // input context
15341 		int curHidden = 0; // counter
15342 		Cursor blankCurPtr = 0;
15343 		int cursorSequenceNumber = 0;
15344 		int warpEventCount = 0; // number of mouse movement events to eat
15345 
15346 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS
15347 		X11GetSelectionHandler[Atom] getSelectionHandlers;
15348 
15349 		version(without_opengl) {} else
15350 		GLXContext glc;
15351 
15352 		private void fixFixedSize(bool forced=false) (int width, int height) {
15353 			if (forced || this.resizability == Resizability.fixedSize) {
15354 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
15355 				XSizeHints sh;
15356 				static if (!forced) {
15357 					c_long spr;
15358 					XGetWMNormalHints(display, window, &sh, &spr);
15359 					sh.flags |= PMaxSize | PMinSize;
15360 				} else {
15361 					sh.flags = PMaxSize | PMinSize;
15362 				}
15363 				sh.min_width = width;
15364 				sh.min_height = height;
15365 				sh.max_width = width;
15366 				sh.max_height = height;
15367 				XSetWMNormalHints(display, window, &sh);
15368 				//XFlush(display);
15369 			}
15370 		}
15371 
15372 		ScreenPainter getPainter(bool manualInvalidations) {
15373 			return ScreenPainter(this, window, manualInvalidations);
15374 		}
15375 
15376 		void move(int x, int y) {
15377 			XMoveWindow(display, window, x, y);
15378 		}
15379 
15380 		void resize(int w, int h) {
15381 			if (w < 1) w = 1;
15382 			if (h < 1) h = 1;
15383 			XResizeWindow(display, window, w, h);
15384 
15385 			// calling this now to avoid waiting for the server to
15386 			// acknowledge the resize; draws without returning to the
15387 			// event loop will thus actually work. the server's event
15388 			// btw might overrule this and resize it again
15389 			recordX11Resize(display, this, w, h);
15390 
15391 			updateOpenglViewportIfNeeded(w, h);
15392 		}
15393 
15394 		void moveResize (int x, int y, int w, int h) {
15395 			if (w < 1) w = 1;
15396 			if (h < 1) h = 1;
15397 			XMoveResizeWindow(display, window, x, y, w, h);
15398 			updateOpenglViewportIfNeeded(w, h);
15399 		}
15400 
15401 		void hideCursor () {
15402 			if (curHidden++ == 0) {
15403 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
15404 					static const(char)[1] cmbmp = 0;
15405 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
15406 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
15407 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
15408 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
15409 					XFreePixmap(display, pm);
15410 				}
15411 				XDefineCursor(display, window, blankCurPtr);
15412 			}
15413 		}
15414 
15415 		void showCursor () {
15416 			if (--curHidden == 0) XUndefineCursor(display, window);
15417 		}
15418 
15419 		void warpMouse (int x, int y) {
15420 			// here i will send dummy "ignore next mouse motion" event,
15421 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
15422 			// and we don't need to report it to the user (as warping is
15423 			// used when the user needs movement deltas).
15424 			//XClientMessageEvent xclient;
15425 			XEvent e;
15426 			e.xclient.type = EventType.ClientMessage;
15427 			e.xclient.window = window;
15428 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15429 			e.xclient.format = 32;
15430 			e.xclient.data.l[0] = 0;
15431 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
15432 			//{ 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]); }
15433 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15434 			// now warp pointer...
15435 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
15436 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
15437 			// ...and flush
15438 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
15439 			XFlush(display);
15440 		}
15441 
15442 		void sendDummyEvent () {
15443 			// here i will send dummy event to ping event queue
15444 			XEvent e;
15445 			e.xclient.type = EventType.ClientMessage;
15446 			e.xclient.window = window;
15447 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15448 			e.xclient.format = 32;
15449 			e.xclient.data.l[0] = 0;
15450 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15451 			XFlush(display);
15452 		}
15453 
15454 		void setTitle(string title) {
15455 			if (title.ptr is null) title = "";
15456 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15457 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15458 			XTextProperty windowName;
15459 			windowName.value = title.ptr;
15460 			windowName.encoding = XA_UTF8; //XA_STRING;
15461 			windowName.format = 8;
15462 			windowName.nitems = cast(uint)title.length;
15463 			XSetWMName(display, window, &windowName);
15464 			char[1024] namebuf = 0;
15465 			auto maxlen = namebuf.length-1;
15466 			if (maxlen > title.length) maxlen = title.length;
15467 			namebuf[0..maxlen] = title[0..maxlen];
15468 			XStoreName(display, window, namebuf.ptr);
15469 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
15470 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
15471 		}
15472 
15473 		string[] getTitles() {
15474 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15475 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15476 			XTextProperty textProp;
15477 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
15478 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
15479 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
15480 				} else
15481 					return [];
15482 			} else
15483 				return null;
15484 		}
15485 
15486 		string getTitle() {
15487 			auto titles = getTitles();
15488 			return titles.length ? titles[0] : null;
15489 		}
15490 
15491 		void setMinSize (int minwidth, int minheight) {
15492 			import core.stdc.config : c_long;
15493 			if (minwidth < 1) minwidth = 1;
15494 			if (minheight < 1) minheight = 1;
15495 			XSizeHints sh;
15496 			c_long spr;
15497 			XGetWMNormalHints(display, window, &sh, &spr);
15498 			sh.min_width = minwidth;
15499 			sh.min_height = minheight;
15500 			sh.flags |= PMinSize;
15501 			XSetWMNormalHints(display, window, &sh);
15502 			flushGui();
15503 		}
15504 
15505 		void setMaxSize (int maxwidth, int maxheight) {
15506 			import core.stdc.config : c_long;
15507 			if (maxwidth < 1) maxwidth = 1;
15508 			if (maxheight < 1) maxheight = 1;
15509 			XSizeHints sh;
15510 			c_long spr;
15511 			XGetWMNormalHints(display, window, &sh, &spr);
15512 			sh.max_width = maxwidth;
15513 			sh.max_height = maxheight;
15514 			sh.flags |= PMaxSize;
15515 			XSetWMNormalHints(display, window, &sh);
15516 			flushGui();
15517 		}
15518 
15519 		void setResizeGranularity (int granx, int grany) {
15520 			import core.stdc.config : c_long;
15521 			if (granx < 1) granx = 1;
15522 			if (grany < 1) grany = 1;
15523 			XSizeHints sh;
15524 			c_long spr;
15525 			XGetWMNormalHints(display, window, &sh, &spr);
15526 			sh.width_inc = granx;
15527 			sh.height_inc = grany;
15528 			sh.flags |= PResizeInc;
15529 			XSetWMNormalHints(display, window, &sh);
15530 			flushGui();
15531 		}
15532 
15533 		void setOpacity (uint opacity) {
15534 			arch_ulong o = opacity;
15535 			if (opacity == uint.max)
15536 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
15537 			else
15538 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
15539 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
15540 		}
15541 
15542 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted {
15543 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
15544 			display = XDisplayConnection.get();
15545 			auto screen = DefaultScreen(display);
15546 
15547 			bool overrideRedirect = false;
15548 			if(
15549 				windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu ||
15550 				windowType == WindowTypes.tooltip ||
15551 				windowType == WindowTypes.notification ||
15552 				windowType == WindowTypes.dnd ||
15553 				windowType == WindowTypes.comboBoxDropdown ||
15554 				(customizationFlags & WindowFlags.overrideRedirect)
15555 			)// || windowType == WindowTypes.nestedChild)
15556 				overrideRedirect = true;
15557 
15558 			version(without_opengl) {}
15559 			else {
15560 				if(opengl == OpenGlOptions.yes) {
15561 					GLXFBConfig fbconf = null;
15562 					XVisualInfo* vi = null;
15563 					bool useLegacy = false;
15564 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
15565 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
15566 						int[23] visualAttribs = [
15567 							GLX_X_RENDERABLE , 1/*True*/,
15568 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
15569 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
15570 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
15571 							GLX_RED_SIZE     , 8,
15572 							GLX_GREEN_SIZE   , 8,
15573 							GLX_BLUE_SIZE    , 8,
15574 							GLX_ALPHA_SIZE   , 8,
15575 							GLX_DEPTH_SIZE   , 24,
15576 							GLX_STENCIL_SIZE , 8,
15577 							GLX_DOUBLEBUFFER , 1/*True*/,
15578 							0/*None*/,
15579 						];
15580 						int fbcount;
15581 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
15582 						if (fbcount == 0) {
15583 							useLegacy = true; // try to do at least something
15584 						} else {
15585 							// pick the FB config/visual with the most samples per pixel
15586 							int bestidx = -1, bestns = -1;
15587 							foreach (int fbi; 0..fbcount) {
15588 								int sb, samples;
15589 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
15590 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
15591 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
15592 							}
15593 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
15594 							fbconf = fbc[bestidx];
15595 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
15596 							XFree(fbc);
15597 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
15598 						}
15599 					}
15600 					if (vi is null || useLegacy) {
15601 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
15602 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
15603 						useLegacy = true;
15604 					}
15605 					if (vi is null) throw new Exception("no open gl visual found");
15606 
15607 					XSetWindowAttributes swa;
15608 					auto root = RootWindow(display, screen);
15609 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
15610 
15611 					swa.override_redirect = overrideRedirect;
15612 
15613 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15614 						0, 0, width, height,
15615 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
15616 
15617 					// now try to use `glXCreateContextAttribsARB()` if it's here
15618 					if (!useLegacy) {
15619 						// request fairly advanced context, even with stencil buffer!
15620 						int[9] contextAttribs = [
15621 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
15622 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
15623 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
15624 							// for modern context, set "forward compatibility" flag too
15625 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
15626 							0/*None*/,
15627 						];
15628 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
15629 						if (glc is null && sdpyOpenGLContextAllowFallback) {
15630 							sdpyOpenGLContextVersion = 0;
15631 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15632 						}
15633 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
15634 					} else {
15635 						// fallback to old GLX call
15636 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
15637 							sdpyOpenGLContextVersion = 0;
15638 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15639 						}
15640 					}
15641 					// sync to ensure any errors generated are processed
15642 					XSync(display, 0/*False*/);
15643 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
15644 					if(glc is null)
15645 						throw new Exception("glc");
15646 				}
15647 			}
15648 
15649 			if(opengl == OpenGlOptions.no) {
15650 
15651 				XSetWindowAttributes swa;
15652 				swa.background_pixel = WhitePixel(display, screen);
15653 				swa.border_pixel = BlackPixel(display, screen);
15654 				swa.override_redirect = overrideRedirect;
15655 				auto root = RootWindow(display, screen);
15656 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
15657 
15658 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15659 					0, 0, width, height,
15660 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
15661 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
15662 
15663 
15664 
15665 				/*
15666 				window = XCreateSimpleWindow(
15667 					display,
15668 					parent is null ? RootWindow(display, screen) : parent.impl.window,
15669 					0, 0, // x, y
15670 					width, height,
15671 					1, // border width
15672 					BlackPixel(display, screen), // border
15673 					WhitePixel(display, screen)); // background
15674 				*/
15675 
15676 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
15677 				bufferw = width;
15678 				bufferh = height;
15679 
15680 				gc = DefaultGC(display, screen);
15681 
15682 				// clear out the buffer to get us started...
15683 				XSetForeground(display, gc, WhitePixel(display, screen));
15684 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
15685 				XSetForeground(display, gc, BlackPixel(display, screen));
15686 			}
15687 
15688 			// input context
15689 			//TODO: create this only for top-level windows, and reuse that?
15690 			populateXic();
15691 
15692 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
15693 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
15694 			// window class
15695 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
15696 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
15697 				XClassHint klass;
15698 				XWMHints wh;
15699 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15700 					wh.input = true;
15701 					wh.flags |= InputHint;
15702 				}
15703 				XSizeHints size;
15704 				klass.res_name = sdpyWindowClassStr;
15705 				klass.res_class = sdpyWindowClassStr;
15706 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
15707 			}
15708 
15709 			setTitle(title);
15710 			SimpleWindow.nativeMapping[window] = this;
15711 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
15712 
15713 			// This gives our window a close button
15714 			if (windowType != WindowTypes.eventOnly) {
15715 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
15716 				int useAtoms;
15717 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15718 					useAtoms = 2;
15719 				} else {
15720 					useAtoms = 1;
15721 				}
15722 				assert(useAtoms <= atoms.length);
15723 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
15724 			}
15725 
15726 			// FIXME: windowType and customizationFlags
15727 			Atom[8] wsatoms; // here, due to goto
15728 			int wmsacount = 0; // here, due to goto
15729 
15730 			try
15731 			final switch(windowType) {
15732 				case WindowTypes.normal:
15733 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15734 				break;
15735 				case WindowTypes.undecorated:
15736 					motifHideDecorations();
15737 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15738 				break;
15739 				case WindowTypes.eventOnly:
15740 					_hidden = true;
15741 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
15742 					goto hiddenWindow;
15743 				//break;
15744 				case WindowTypes.nestedChild:
15745 					// handled in XCreateWindow calls
15746 				break;
15747 
15748 				case WindowTypes.dropdownMenu:
15749 					motifHideDecorations();
15750 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
15751 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15752 				break;
15753 				case WindowTypes.popupMenu:
15754 					motifHideDecorations();
15755 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
15756 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15757 				break;
15758 				case WindowTypes.notification:
15759 					motifHideDecorations();
15760 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
15761 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15762 				break;
15763 				case WindowTypes.minimallyWrapped:
15764 					assert(0, "don't create a minimallyWrapped thing explicitly!");
15765 
15766 				case WindowTypes.dialog:
15767 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display));
15768 				break;
15769 				case WindowTypes.comboBoxDropdown:
15770 					motifHideDecorations();
15771 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display));
15772 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15773 				break;
15774 				case WindowTypes.tooltip:
15775 					motifHideDecorations();
15776 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display));
15777 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15778 				break;
15779 				case WindowTypes.dnd:
15780 					motifHideDecorations();
15781 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display));
15782 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15783 				break;
15784 				/+
15785 				case WindowTypes.menu:
15786 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15787 					motifHideDecorations();
15788 				break;
15789 				case WindowTypes.desktop:
15790 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
15791 				break;
15792 				case WindowTypes.dock:
15793 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
15794 				break;
15795 				case WindowTypes.toolbar:
15796 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
15797 				break;
15798 				case WindowTypes.menu:
15799 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15800 				break;
15801 				case WindowTypes.utility:
15802 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
15803 				break;
15804 				case WindowTypes.splash:
15805 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
15806 				break;
15807 				case WindowTypes.notification:
15808 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
15809 				break;
15810 				+/
15811 			}
15812 			catch(Exception e) {
15813 				// XInternAtom failed, prolly a WM
15814 				// that doesn't support these things
15815 			}
15816 
15817 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15818 			// the two following flags may be ignored by WM
15819 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15820 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15821 
15822 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15823 
15824 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15825 
15826 			// What would be ideal here is if they only were
15827 			// selected if there was actually an event handler
15828 			// for them...
15829 
15830 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15831 
15832 			hiddenWindow:
15833 
15834 			// set the pid property for lookup later by window managers
15835 			// a standard convenience
15836 			import core.sys.posix.unistd;
15837 			arch_ulong pid = getpid();
15838 
15839 			XChangeProperty(
15840 				display,
15841 				impl.window,
15842 				GetAtom!("_NET_WM_PID", true)(display),
15843 				XA_CARDINAL,
15844 				32 /* bits */,
15845 				0 /*PropModeReplace*/,
15846 				&pid,
15847 				1);
15848 
15849 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15850 				if(parent is null) assert(0);
15851 				// sdpyPrintDebugString("transient");
15852 				XChangeProperty(
15853 					display,
15854 					impl.window,
15855 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15856 					XA_WINDOW,
15857 					32 /* bits */,
15858 					0 /*PropModeReplace*/,
15859 					&parent.impl.window,
15860 					1);
15861 
15862 			}
15863 
15864 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15865 				XMapWindow(display, window);
15866 			} else {
15867 				_hidden = true;
15868 			}
15869 		}
15870 
15871 		void populateXic() {
15872 			if (XDisplayConnection.xim !is null) {
15873 				xic = XCreateIC(XDisplayConnection.xim,
15874 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15875 						/*XNClientWindow*/"clientWindow".ptr, window,
15876 						/*XNFocusWindow*/"focusWindow".ptr, window,
15877 						null);
15878 				if (xic is null) {
15879 					import core.stdc.stdio : stderr, fprintf;
15880 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15881 				}
15882 			}
15883 		}
15884 
15885 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15886 			auto mask = EventMask.ExposureMask |
15887 				EventMask.KeyPressMask |
15888 				EventMask.KeyReleaseMask |
15889 				EventMask.PropertyChangeMask |
15890 				EventMask.FocusChangeMask |
15891 				EventMask.StructureNotifyMask |
15892 				EventMask.SubstructureNotifyMask |
15893 				EventMask.VisibilityChangeMask
15894 				| EventMask.ButtonPressMask
15895 				| EventMask.ButtonReleaseMask
15896 			;
15897 
15898 			// xshm is our shortcut for local connections
15899 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15900 				mask |= EventMask.PointerMotionMask;
15901 			else
15902 				mask |= EventMask.ButtonMotionMask;
15903 
15904 			XSelectInput(display, window, mask);
15905 		}
15906 
15907 
15908 		void setNetWMWindowType(Atom type) {
15909 			Atom[2] atoms;
15910 
15911 			atoms[0] = type;
15912 			// generic fallback
15913 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15914 
15915 			XChangeProperty(
15916 				display,
15917 				impl.window,
15918 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15919 				XA_ATOM,
15920 				32 /* bits */,
15921 				0 /*PropModeReplace*/,
15922 				atoms.ptr,
15923 				cast(int) atoms.length);
15924 		}
15925 
15926 		void motifHideDecorations(bool hide = true) {
15927 			MwmHints hints;
15928 			hints.flags = MWM_HINTS_DECORATIONS;
15929 			hints.decorations = hide ? 0 : 1;
15930 
15931 			XChangeProperty(
15932 				display,
15933 				impl.window,
15934 				GetAtom!"_MOTIF_WM_HINTS"(display),
15935 				GetAtom!"_MOTIF_WM_HINTS"(display),
15936 				32 /* bits */,
15937 				0 /*PropModeReplace*/,
15938 				&hints,
15939 				hints.sizeof / 4);
15940 		}
15941 
15942 		/*k8: unused
15943 		void createOpenGlContext() {
15944 
15945 		}
15946 		*/
15947 
15948 		void closeWindow() {
15949 			// I can't close this or a child window closing will
15950 			// break events for everyone. So I'm just leaking it right
15951 			// now and that is probably perfectly fine...
15952 			version(none)
15953 			if (customEventFDRead != -1) {
15954 				import core.sys.posix.unistd : close;
15955 				auto same = customEventFDRead == customEventFDWrite;
15956 
15957 				close(customEventFDRead);
15958 				if(!same)
15959 					close(customEventFDWrite);
15960 				customEventFDRead = -1;
15961 				customEventFDWrite = -1;
15962 			}
15963 
15964 			version(without_opengl) {} else
15965 			if(glc !is null) {
15966 				glXDestroyContext(display, glc);
15967 				glc = null;
15968 			}
15969 
15970 			if(buffer)
15971 				XFreePixmap(display, buffer);
15972 			bufferw = bufferh = 0;
15973 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15974 			XDestroyWindow(display, window);
15975 			XFlush(display);
15976 		}
15977 
15978 		void dispose() {
15979 		}
15980 
15981 		bool destroyed = false;
15982 	}
15983 
15984 	bool insideXEventLoop;
15985 }
15986 
15987 version(X11) {
15988 
15989 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15990 
15991 	private class ResizeEvent {
15992 		int width, height;
15993 	}
15994 
15995 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15996 		if(win.windowType == WindowTypes.minimallyWrapped)
15997 			return;
15998 
15999 		if(win.pendingResizeEvent is null) {
16000 			win.pendingResizeEvent = new ResizeEvent();
16001 			win.addEventListener((ResizeEvent re) {
16002 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
16003 			});
16004 		}
16005 		win.pendingResizeEvent.width = width;
16006 		win.pendingResizeEvent.height = height;
16007 		if(!win.eventQueued!ResizeEvent) {
16008 			win.postEvent(win.pendingResizeEvent);
16009 		}
16010 	}
16011 
16012 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
16013 		if(win.windowType == WindowTypes.minimallyWrapped)
16014 			return;
16015 		if(win.closed)
16016 			return;
16017 
16018 		if(width != win.width || height != win.height) {
16019 
16020 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
16021 			win._width = width;
16022 			win._height = height;
16023 
16024 			if(win.openglMode == OpenGlOptions.no) {
16025 				// FIXME: could this be more efficient?
16026 
16027 				if (win.bufferw < width || win.bufferh < height) {
16028 					//{ 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); }
16029 					// grow the internal buffer to match the window...
16030 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
16031 					{
16032 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
16033 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
16034 						scope(exit) XFreeGC(win.display, xgc);
16035 						XSetClipMask(win.display, xgc, None);
16036 						XSetForeground(win.display, xgc, 0);
16037 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
16038 					}
16039 					XCopyArea(display,
16040 						cast(Drawable) win.buffer,
16041 						cast(Drawable) newPixmap,
16042 						win.gc, 0, 0,
16043 						win.bufferw < width ? win.bufferw : win.width,
16044 						win.bufferh < height ? win.bufferh : win.height,
16045 						0, 0);
16046 
16047 					XFreePixmap(display, win.buffer);
16048 					win.buffer = newPixmap;
16049 					win.bufferw = width;
16050 					win.bufferh = height;
16051 				}
16052 
16053 				// clear unused parts of the buffer
16054 				if (win.bufferw > width || win.bufferh > height) {
16055 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
16056 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
16057 					scope(exit) XFreeGC(win.display, xgc);
16058 					XSetClipMask(win.display, xgc, None);
16059 					XSetForeground(win.display, xgc, 0);
16060 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
16061 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
16062 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
16063 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
16064 				}
16065 
16066 			}
16067 
16068 			win.updateOpenglViewportIfNeeded(width, height);
16069 
16070 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
16071 
16072 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
16073 			if(win.windowResized !is null) {
16074 				XUnlockDisplay(display);
16075 				scope(exit) XLockDisplay(display);
16076 				win.windowResized(width, height);
16077 			}
16078 		}
16079 	}
16080 
16081 
16082 	/// Platform-specific, you might use it when doing a custom event loop.
16083 	bool doXNextEvent(Display* display) {
16084 		bool done;
16085 		XEvent e;
16086 		XNextEvent(display, &e);
16087 		version(sddddd) {
16088 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16089 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
16090 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
16091 			}
16092 		}
16093 
16094 		// filter out compose events
16095 		if (XFilterEvent(&e, None)) {
16096 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
16097 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
16098 			return false;
16099 		}
16100 		// process keyboard mapping changes
16101 		if (e.type == EventType.KeymapNotify) {
16102 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
16103 			XRefreshKeyboardMapping(&e.xmapping);
16104 			return false;
16105 		}
16106 
16107 		version(with_eventloop)
16108 			import arsd.eventloop;
16109 
16110 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
16111 			// see windows impl's comments
16112 			XUnlockDisplay(display);
16113 			scope(exit) XLockDisplay(display);
16114 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
16115 			if(ret == 0)
16116 				return done;
16117 		}
16118 
16119 
16120 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16121 			if(win.getNativeEventHandler !is null) {
16122 				XUnlockDisplay(display);
16123 				scope(exit) XLockDisplay(display);
16124 				auto ret = win.getNativeEventHandler()(e);
16125 				if(ret == 0)
16126 					return done;
16127 			}
16128 		}
16129 
16130 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
16131 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
16132 				// we get this because of the RRScreenChangeNotifyMask
16133 
16134 				// this isn't actually an ideal way to do it since it wastes time
16135 				// but meh it is simple and it works.
16136 				win.actualDpiLoadAttempted = false;
16137 				SimpleWindow.xRandrInfoLoadAttemped = false;
16138 				win.updateActualDpi(); // trigger a reload
16139 			}
16140 		}
16141 
16142 		switch(e.type) {
16143 		  case EventType.SelectionClear:
16144 		  	// writeln("SelectionClear");
16145 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
16146 				// FIXME so it is supposed to finish any in progress transfers... but idk...
16147 				// writeln("SelectionClear");
16148 			}
16149 			SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
16150 			mightShortCircuitClipboard = false;
16151 		  break;
16152 		  case EventType.SelectionRequest:
16153 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
16154 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
16155 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
16156 				XUnlockDisplay(display);
16157 				scope(exit) XLockDisplay(display);
16158 				(*ssh).handleRequest(e);
16159 			}
16160 		  break;
16161 		  case EventType.PropertyNotify:
16162 			// import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
16163 
16164 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
16165 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
16166 					ssh.sendMoreIncr(&e.xproperty);
16167 			}
16168 
16169 
16170 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
16171 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16172 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
16173 					Atom target;
16174 					int format;
16175 					arch_ulong bytesafter, length;
16176 					void* value;
16177 
16178 					ubyte[] s;
16179 					Atom targetToKeep;
16180 
16181 					XGetWindowProperty(
16182 						e.xproperty.display,
16183 						e.xproperty.window,
16184 						e.xproperty.atom,
16185 						0,
16186 						100000 /* length */,
16187 						true, /* erase it to signal we got it and want more */
16188 						0 /*AnyPropertyType*/,
16189 						&target, &format, &length, &bytesafter, &value);
16190 
16191 					if(!targetToKeep)
16192 						targetToKeep = target;
16193 
16194 					auto id = (cast(ubyte*) value)[0 .. length];
16195 
16196 					handler.handleIncrData(targetToKeep, id);
16197 					if(length == 0) {
16198 						win.getSelectionHandlers.remove(e.xproperty.atom);
16199 					}
16200 
16201 					XFree(value);
16202 				}
16203 			}
16204 		  break;
16205 		  case EventType.SelectionNotify:
16206 		  	// import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom);
16207 			if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
16208 			if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16209 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
16210 					XUnlockDisplay(display);
16211 					scope(exit) XLockDisplay(display);
16212 					handler.handleData(None, null);
16213 					win.getSelectionHandlers.remove(e.xproperty.atom);
16214 				} else {
16215 					Atom target;
16216 					int format;
16217 					arch_ulong bytesafter, length;
16218 					void* value;
16219 					XGetWindowProperty(
16220 						e.xselection.display,
16221 						e.xselection.requestor,
16222 						e.xselection.property,
16223 						0,
16224 						100000 /* length */,
16225 						//false, /* don't erase it */
16226 						true, /* do erase it lol */
16227 						0 /*AnyPropertyType*/,
16228 						&target, &format, &length, &bytesafter, &value);
16229 
16230 					// FIXME: I don't have to copy it now since it is in char[] instead of string
16231 
16232 					{
16233 						XUnlockDisplay(display);
16234 						scope(exit) XLockDisplay(display);
16235 
16236 						if(target == XA_ATOM) {
16237 							// initial request, see what they are able to work with and request the best one
16238 							// we can handle, if available
16239 
16240 							Atom[] answer = (cast(Atom*) value)[0 .. length];
16241 							Atom best = handler.findBestFormat(answer);
16242 
16243 							/+
16244 							writeln("got ", answer);
16245 							foreach(a; answer)
16246 								writeln(XGetAtomName(display, a).stringz);
16247 							writeln("best ", best);
16248 							+/
16249 
16250 							if(best != None) {
16251 								// actually request the best format
16252 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
16253 							}
16254 						} else if(target == GetAtom!"INCR"(display)) {
16255 							// incremental
16256 
16257 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
16258 
16259 							// signal the sending program that we see
16260 							// the incr and are ready to receive more.
16261 							XDeleteProperty(
16262 								e.xselection.display,
16263 								e.xselection.requestor,
16264 								e.xselection.property);
16265 						} else {
16266 							// unsupported type... maybe, forward, then we done with it
16267 							if(target != None) {
16268 								handler.handleData(target, cast(ubyte[]) value[0 .. length]);
16269 								win.getSelectionHandlers.remove(e.xproperty.atom);
16270 							}
16271 						}
16272 					}
16273 					XFree(value);
16274 					/*
16275 					XDeleteProperty(
16276 						e.xselection.display,
16277 						e.xselection.requestor,
16278 						e.xselection.property);
16279 					*/
16280 				}
16281 			}
16282 			break;
16283 		  case EventType.ConfigureNotify:
16284 			auto event = e.xconfigure;
16285 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
16286 				if(win.windowType == WindowTypes.minimallyWrapped)
16287 					break;
16288 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
16289 
16290 				/+
16291 					The ICCCM says window managers must send a synthetic event when the window
16292 					is moved but NOT when it is resized. In the resize case, an event is sent
16293 					with position (0, 0) which can be wrong and break the dpi calculations.
16294 
16295 					So we only consider the synthetic events from the WM and otherwise
16296 					need to wait for some other event to get the position which... sucks.
16297 
16298 					I'd rather not have windows changing their layout on mouse motion after
16299 					switching monitors... might be forced to but for now just ignoring it.
16300 
16301 					Easiest way to switch monitors without sending a size position is by
16302 					maximize or fullscreen in a setup like mine, but on most setups those
16303 					work on the monitor it is already living on, so it should be ok most the
16304 					time.
16305 				+/
16306 				if(event.send_event) {
16307 					win.screenPositionKnown = true;
16308 					win.screenPositionX = event.x;
16309 					win.screenPositionY = event.y;
16310 					win.updateActualDpi();
16311 				}
16312 
16313 				win.updateIMEPopupLocation();
16314 				recordX11ResizeAsync(display, *win, event.width, event.height);
16315 			}
16316 		  break;
16317 		  case EventType.Expose:
16318 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
16319 				if(win.windowType == WindowTypes.minimallyWrapped)
16320 					break;
16321 				// if it is closing from a popup menu, it can get
16322 				// an Expose event right by the end and trigger a
16323 				// BadDrawable error ... we'll just check
16324 				// closed to handle that.
16325 				if((*win).closed) break;
16326 				if((*win).openglMode == OpenGlOptions.no) {
16327 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
16328 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
16329 					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);
16330 				} else {
16331 					// need to redraw the scene somehow
16332 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
16333 						XUnlockDisplay(display);
16334 						scope(exit) XLockDisplay(display);
16335 						version(without_opengl) {} else
16336 						win.redrawOpenGlSceneSoon();
16337 					}
16338 				}
16339 			}
16340 		  break;
16341 		  case EventType.FocusIn:
16342 		  case EventType.FocusOut:
16343 			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
16344 
16345 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16346 				/+
16347 
16348 				void info(string detail) {
16349 					string s;
16350 					// import std.conv;
16351 					// import std.datetime;
16352 					s ~= to!string(Clock.currTime);
16353 					s ~= " ";
16354 					s ~= e.type == EventType.FocusIn ? "in " : "out";
16355 					s ~= " ";
16356 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
16357 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
16358 					s ~= detail;
16359 					s ~= " ";
16360 
16361 					sdpyPrintDebugString(s);
16362 
16363 				}
16364 
16365 				switch(e.xfocus.detail) {
16366 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
16367 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
16368 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
16369 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
16370 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
16371 					case NotifyDetail.NotifyPointer: info("pointer"); break;
16372 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
16373 					case NotifyDetail.NotifyDetailNone: info("none"); break;
16374 					default:
16375 
16376 				}
16377 				+/
16378 
16379 
16380 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
16381 					break; // just ignore these they seem irrelevant
16382 
16383 				auto old = win._focused;
16384 				win._focused = e.type == EventType.FocusIn;
16385 
16386 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
16387 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
16388 					win._focused = true;
16389 
16390 				if(win.demandingAttention)
16391 					demandAttention(*win, false);
16392 
16393 				win.updateIMEFocused();
16394 
16395 				if(old != win._focused && win.onFocusChange) {
16396 					XUnlockDisplay(display);
16397 					scope(exit) XLockDisplay(display);
16398 					win.onFocusChange(win._focused);
16399 				}
16400 			}
16401 		  break;
16402 		  case EventType.VisibilityNotify:
16403 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16404 					auto before = (*win)._visible;
16405 					(*win)._visible = (e.xvisibility.state != VisibilityNotify.VisibilityFullyObscured);
16406 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
16407 						if (win.visibilityChanged !is null && before == true) {
16408 								XUnlockDisplay(display);
16409 								scope(exit) XLockDisplay(display);
16410 								win.visibilityChanged(false);
16411 							}
16412 					} else {
16413 						if (win.visibilityChanged !is null && before == false) {
16414 							XUnlockDisplay(display);
16415 							scope(exit) XLockDisplay(display);
16416 							win.visibilityChanged(true);
16417 						}
16418 					}
16419 				}
16420 				break;
16421 		  case EventType.ClientMessage:
16422 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
16423 					// "ignore next mouse motion" event, increment ignore counter for teh window
16424 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16425 						++(*win).warpEventCount;
16426 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
16427 					} else {
16428 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
16429 					}
16430 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
16431 					// user clicked the close button on the window manager
16432 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16433 						XUnlockDisplay(display);
16434 						scope(exit) XLockDisplay(display);
16435 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
16436 					}
16437 
16438 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
16439 					// writeln("HAPPENED");
16440 					// user clicked the close button on the window manager
16441 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16442 						XUnlockDisplay(display);
16443 						scope(exit) XLockDisplay(display);
16444 
16445 						auto setTo = *win;
16446 
16447 						if(win.setRequestedInputFocus !is null) {
16448 							auto s = win.setRequestedInputFocus();
16449 							if(s !is null) {
16450 								setTo = s;
16451 							}
16452 						}
16453 
16454 						assert(setTo !is null);
16455 
16456 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
16457 
16458 						// sdpyPrintDebugString("WM_TAKE_FOCUS ", setTo.impl.window);
16459 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
16460 					}
16461 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
16462 					foreach(nai; NotificationAreaIcon.activeIcons)
16463 						nai.newManager();
16464 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16465 
16466 					bool xDragWindow = true;
16467 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
16468 						//XDefineCursor(display, xDragWindow.impl.window,
16469 							//writeln("XdndStatus ", e.xclient.data.l);
16470 					}
16471 					if(auto dh = win.dropHandler) {
16472 
16473 						static Atom[3] xFormatsBuffer;
16474 						static Atom[] xFormats;
16475 
16476 						void resetXFormats() {
16477 							xFormatsBuffer[] = 0;
16478 							xFormats = xFormatsBuffer[];
16479 						}
16480 
16481 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
16482 							// on Windows it is supposed to return the effect you actually do FIXME
16483 
16484 							auto sourceWindow =  e.xclient.data.l[0];
16485 
16486 							xFormatsBuffer[0] = e.xclient.data.l[2];
16487 							xFormatsBuffer[1] = e.xclient.data.l[3];
16488 							xFormatsBuffer[2] = e.xclient.data.l[4];
16489 
16490 							if(e.xclient.data.l[1] & 1) {
16491 								// can just grab it all but like we don't necessarily need them...
16492 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
16493 							} else {
16494 								int len;
16495 								foreach(fmt; xFormatsBuffer)
16496 									if(fmt) len++;
16497 								xFormats = xFormatsBuffer[0 .. len];
16498 							}
16499 
16500 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
16501 
16502 							dh.dragEnter(&pkg);
16503 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
16504 
16505 							auto pack = e.xclient.data.l[2];
16506 
16507 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
16508 
16509 
16510 							XClientMessageEvent xclient;
16511 
16512 							xclient.type = EventType.ClientMessage;
16513 							xclient.window = e.xclient.data.l[0];
16514 							xclient.message_type = GetAtom!"XdndStatus"(display);
16515 							xclient.format = 32;
16516 							xclient.data.l[0] = win.impl.window;
16517 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
16518 							auto r = result.consistentWithin;
16519 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
16520 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
16521 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
16522 
16523 							XSendEvent(
16524 								display,
16525 								e.xclient.data.l[0],
16526 								false,
16527 								EventMask.NoEventMask,
16528 								cast(XEvent*) &xclient
16529 							);
16530 
16531 
16532 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
16533 							//writeln("XdndLeave");
16534 							// drop cancelled.
16535 							// data.l[0] is the source window
16536 							dh.dragLeave();
16537 
16538 							resetXFormats();
16539 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
16540 							// drop happening, should fetch data, then send finished
16541 							// writeln("XdndDrop");
16542 
16543 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
16544 
16545 							dh.drop(&pkg);
16546 
16547 							resetXFormats();
16548 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
16549 							// writeln("XdndFinished");
16550 
16551 							dh.finish();
16552 						}
16553 
16554 					}
16555 				}
16556 		  break;
16557 		  case EventType.MapNotify:
16558 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
16559 					auto before = (*win)._visible;
16560 					(*win)._visible = true;
16561 					if (!(*win)._visibleForTheFirstTimeCalled) {
16562 						(*win)._visibleForTheFirstTimeCalled = true;
16563 						if ((*win).visibleForTheFirstTime !is null) {
16564 							XUnlockDisplay(display);
16565 							scope(exit) XLockDisplay(display);
16566 							(*win).visibleForTheFirstTime();
16567 						}
16568 					}
16569 					if ((*win).visibilityChanged !is null && before == false) {
16570 						XUnlockDisplay(display);
16571 						scope(exit) XLockDisplay(display);
16572 						(*win).visibilityChanged(true);
16573 					}
16574 				}
16575 		  break;
16576 		  case EventType.UnmapNotify:
16577 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
16578 					auto before = (*win)._visible;
16579 					win._visible = false;
16580 					if (win.visibilityChanged !is null && before == true) {
16581 						XUnlockDisplay(display);
16582 						scope(exit) XLockDisplay(display);
16583 						win.visibilityChanged(false);
16584 					}
16585 			}
16586 		  break;
16587 		  case EventType.DestroyNotify:
16588 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
16589 				if(win.destroyed)
16590 					break; // might get a notification both for itself and from its parent
16591 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
16592 				win._closed = true; // just in case
16593 				win.destroyed = true;
16594 				if (win.xic !is null) {
16595 					XDestroyIC(win.xic);
16596 					win.xic = null; // just in case
16597 				}
16598 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
16599 				bool anyImportant = false;
16600 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
16601 					if(w.beingOpenKeepsAppOpen) {
16602 						anyImportant = true;
16603 						break;
16604 					}
16605 				if(!anyImportant) {
16606 					EventLoop.quitApplication();
16607 					done = true;
16608 				}
16609 			}
16610 			auto window = e.xdestroywindow.window;
16611 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
16612 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
16613 
16614 			version(with_eventloop) {
16615 				if(done) exit();
16616 			}
16617 		  break;
16618 
16619 		  case EventType.MotionNotify:
16620 			MouseEvent mouse;
16621 			auto event = e.xmotion;
16622 
16623 			mouse.type = MouseEventType.motion;
16624 			mouse.x = event.x;
16625 			mouse.y = event.y;
16626 			mouse.modifierState = event.state;
16627 
16628 			mouse.timestamp = event.time;
16629 
16630 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
16631 				mouse.window = *win;
16632 				if (win.warpEventCount > 0) {
16633 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
16634 					--(*win).warpEventCount;
16635 					(*win).mdx(mouse); // so deltas will be correctly updated
16636 				} else {
16637 					win.warpEventCount = 0; // just in case
16638 					(*win).mdx(mouse);
16639 					if((*win).handleMouseEvent) {
16640 						XUnlockDisplay(display);
16641 						scope(exit) XLockDisplay(display);
16642 						(*win).handleMouseEvent(mouse);
16643 					}
16644 				}
16645 			}
16646 
16647 		  	version(with_eventloop)
16648 				send(mouse);
16649 		  break;
16650 		  case EventType.ButtonPress:
16651 		  case EventType.ButtonRelease:
16652 			MouseEvent mouse;
16653 			auto event = e.xbutton;
16654 
16655 			mouse.timestamp = event.time;
16656 
16657 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
16658 			mouse.x = event.x;
16659 			mouse.y = event.y;
16660 
16661 			static Time lastMouseDownTime = 0;
16662 			static int lastMouseDownButton = -1;
16663 
16664 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
16665 			if(e.type == EventType.ButtonPress) {
16666 				lastMouseDownTime = event.time;
16667 				lastMouseDownButton = event.button;
16668 			}
16669 
16670 			switch(event.button) {
16671 				case 1: mouse.button = MouseButton.left; break; // left
16672 				case 2: mouse.button = MouseButton.middle; break; // middle
16673 				case 3: mouse.button = MouseButton.right; break; // right
16674 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
16675 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
16676 				case 6: break; // idk
16677 				case 7: break; // idk
16678 				case 8: mouse.button = MouseButton.backButton; break;
16679 				case 9: mouse.button = MouseButton.forwardButton; break;
16680 				default:
16681 			}
16682 
16683 			// FIXME: double check this
16684 			mouse.modifierState = event.state;
16685 
16686 			//mouse.modifierState = event.detail;
16687 
16688 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
16689 				mouse.window = *win;
16690 				(*win).mdx(mouse);
16691 				if((*win).handleMouseEvent) {
16692 					XUnlockDisplay(display);
16693 					scope(exit) XLockDisplay(display);
16694 					(*win).handleMouseEvent(mouse);
16695 				}
16696 			}
16697 			version(with_eventloop)
16698 				send(mouse);
16699 		  break;
16700 
16701 		  case EventType.KeyPress:
16702 		  case EventType.KeyRelease:
16703 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
16704 			KeyEvent ke;
16705 			ke.pressed = e.type == EventType.KeyPress;
16706 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
16707 
16708 			auto sym = XKeycodeToKeysym(
16709 				XDisplayConnection.get(),
16710 				e.xkey.keycode,
16711 				0);
16712 
16713 			ke.key = cast(Key) sym;//e.xkey.keycode;
16714 
16715 			ke.modifierState = e.xkey.state;
16716 
16717 			// writefln("%x", sym);
16718 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
16719 			int charbuflen = 0; // return value of XwcLookupString
16720 			if (ke.pressed) {
16721 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
16722 				if (win !is null && win.xic !is null) {
16723 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
16724 					Status status;
16725 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
16726 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
16727 				} else {
16728 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
16729 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
16730 					char[16] buffer;
16731 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
16732 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
16733 				}
16734 			}
16735 
16736 			// if there's no char, subst one
16737 			if (charbuflen == 0) {
16738 				switch (sym) {
16739 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
16740 					case 0xff8d: // keypad enter
16741 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
16742 					default : // ignore
16743 				}
16744 			}
16745 
16746 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
16747 				ke.window = *win;
16748 
16749 
16750 				if(win.inputProxy)
16751 					win = &win.inputProxy;
16752 
16753 				// char events are separate since they are on Windows too
16754 				// also, xcompose can generate long char sequences
16755 				// don't send char events if Meta and/or Hyper is pressed
16756 				// TODO: ctrl+char should only send control chars; not yet
16757 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
16758 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
16759 				}
16760 
16761 				dchar[32] charsComingBuffer;
16762 				int charsComingPosition;
16763 				dchar[] charsComing = charsComingBuffer[];
16764 
16765 				if (ke.pressed && charbuflen > 0) {
16766 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
16767 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
16768 						if(charsComingPosition >= charsComing.length)
16769 							charsComing.length = charsComingPosition + 8;
16770 
16771 						charsComing[charsComingPosition++] = ch;
16772 					}
16773 
16774 					charsComing = charsComing[0 .. charsComingPosition];
16775 				} else {
16776 					charsComing = null;
16777 				}
16778 
16779 				ke.charsPossible = charsComing;
16780 
16781 				if (win.handleKeyEvent) {
16782 					XUnlockDisplay(display);
16783 					scope(exit) XLockDisplay(display);
16784 					win.handleKeyEvent(ke);
16785 				}
16786 
16787 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
16788 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
16789 					XUnlockDisplay(display);
16790 					scope(exit) XLockDisplay(display);
16791 					foreach(ch; charsComing)
16792 						win.handleCharEvent(ch);
16793 				}
16794 			}
16795 
16796 			version(with_eventloop)
16797 				send(ke);
16798 		  break;
16799 		  default:
16800 		}
16801 
16802 		return done;
16803 	}
16804 }
16805 
16806 /* *************************************** */
16807 /*      Done with simpledisplay stuff      */
16808 /* *************************************** */
16809 
16810 // Necessary C library bindings follow
16811 version(Windows) {} else
16812 version(Emscripten) {} else
16813 version(X11) {
16814 
16815 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
16816 
16817 // X11 bindings needed here
16818 /*
16819 	A little of this is from the bindings project on
16820 	D Source and some of it is copy/paste from the C
16821 	header.
16822 
16823 	The DSource listing consistently used D's long
16824 	where C used long. That's wrong - C long is 32 bit, so
16825 	it should be int in D. I changed that here.
16826 
16827 	Note:
16828 	This isn't complete, just took what I needed for myself.
16829 */
16830 
16831 import core.stdc.stddef : wchar_t;
16832 
16833 interface XLib {
16834 extern(C) nothrow @nogc {
16835 	char* XResourceManagerString(Display*);
16836 	void XrmInitialize();
16837 	XrmDatabase XrmGetStringDatabase(char* data);
16838 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16839 
16840 	Cursor XCreateFontCursor(Display*, uint shape);
16841 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16842 	int XUndefineCursor(Display* display, Window w);
16843 
16844 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16845 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16846 	int XFreeCursor(Display* display, Cursor cursor);
16847 
16848 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16849 
16850 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16851 
16852 	XVaNestedList XVaCreateNestedList(int unused, ...);
16853 
16854 	char *XKeysymToString(KeySym keysym);
16855 	KeySym XKeycodeToKeysym(
16856 		Display*		/* display */,
16857 		KeyCode		/* keycode */,
16858 		int			/* index */
16859 	);
16860 
16861 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16862 
16863 	int XFree(void*);
16864 	int XDeleteProperty(Display *display, Window w, Atom property);
16865 
16866 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16867 
16868 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16869 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16870 		*actual_type_return, int *actual_format_return, arch_ulong
16871 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16872 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16873 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16874 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16875 
16876 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16877 
16878 	Window XGetSelectionOwner(Display *display, Atom selection);
16879 
16880 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16881 
16882 	char** XListFonts(Display*, const char*, int, int*);
16883 	void XFreeFontNames(char**);
16884 
16885 	Display* XOpenDisplay(const char*);
16886 	int XCloseDisplay(Display*);
16887 
16888 	int function() XSynchronize(Display*, bool);
16889 	int function() XSetAfterFunction(Display*, int function() proc);
16890 
16891 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16892 
16893 	Bool XSupportsLocale();
16894 	char* XSetLocaleModifiers(const(char)* modifier_list);
16895 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16896 	Status XCloseOM(XOM om);
16897 
16898 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16899 	Status XCloseIM(XIM im);
16900 
16901 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16902 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16903 	Display* XDisplayOfIM(XIM im);
16904 	char* XLocaleOfIM(XIM im);
16905 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16906 	void XDestroyIC(XIC ic);
16907 	void XSetICFocus(XIC ic);
16908 	void XUnsetICFocus(XIC ic);
16909 	//wchar_t* XwcResetIC(XIC ic);
16910 	char* XmbResetIC(XIC ic);
16911 	char* Xutf8ResetIC(XIC ic);
16912 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16913 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16914 	XIM XIMOfIC(XIC ic);
16915 
16916 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16917 
16918 
16919 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16920 	int XFreeFont(Display *display, XFontStruct *font_struct);
16921 	int XSetFont(Display* display, GC gc, Font font);
16922 	int XTextWidth(XFontStruct*, scope const char*, int);
16923 
16924 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16925 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16926 
16927 	Window XCreateSimpleWindow(
16928 		Display*	/* display */,
16929 		Window		/* parent */,
16930 		int			/* x */,
16931 		int			/* y */,
16932 		uint		/* width */,
16933 		uint		/* height */,
16934 		uint		/* border_width */,
16935 		uint		/* border */,
16936 		uint		/* background */
16937 	);
16938 	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);
16939 
16940 	int XReparentWindow(Display*, Window, Window, int, int);
16941 	int XClearWindow(Display*, Window);
16942 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16943 	int XMoveWindow(Display*, Window, int, int);
16944 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16945 
16946 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16947 
16948 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16949 
16950 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16951 
16952 	XImage *XCreateImage(
16953 		Display*		/* display */,
16954 		Visual*		/* visual */,
16955 		uint	/* depth */,
16956 		int			/* format */,
16957 		int			/* offset */,
16958 		ubyte*		/* data */,
16959 		uint	/* width */,
16960 		uint	/* height */,
16961 		int			/* bitmap_pad */,
16962 		int			/* bytes_per_line */
16963 	);
16964 
16965 	Status XInitImage (XImage* image);
16966 
16967 	Atom XInternAtom(
16968 		Display*		/* display */,
16969 		const char*	/* atom_name */,
16970 		Bool		/* only_if_exists */
16971 	);
16972 
16973 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16974 	char* XGetAtomName(Display*, Atom);
16975 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16976 
16977 	int XPutImage(
16978 		Display*	/* display */,
16979 		Drawable	/* d */,
16980 		GC			/* gc */,
16981 		XImage*	/* image */,
16982 		int			/* src_x */,
16983 		int			/* src_y */,
16984 		int			/* dest_x */,
16985 		int			/* dest_y */,
16986 		uint		/* width */,
16987 		uint		/* height */
16988 	);
16989 
16990 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16991 
16992 
16993 	int XDestroyWindow(
16994 		Display*	/* display */,
16995 		Window		/* w */
16996 	);
16997 
16998 	int XDestroyImage(XImage*);
16999 
17000 	int XSelectInput(
17001 		Display*	/* display */,
17002 		Window		/* w */,
17003 		EventMask	/* event_mask */
17004 	);
17005 
17006 	int XMapWindow(
17007 		Display*	/* display */,
17008 		Window		/* w */
17009 	);
17010 
17011 	Status XIconifyWindow(Display*, Window, int);
17012 	int XMapRaised(Display*, Window);
17013 	int XMapSubwindows(Display*, Window);
17014 
17015 	int XNextEvent(
17016 		Display*	/* display */,
17017 		XEvent*		/* event_return */
17018 	);
17019 
17020 	int XMaskEvent(Display*, arch_long, XEvent*);
17021 
17022 	Bool XFilterEvent(XEvent *event, Window window);
17023 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
17024 
17025 	Status XSetWMProtocols(
17026 		Display*	/* display */,
17027 		Window		/* w */,
17028 		Atom*		/* protocols */,
17029 		int			/* count */
17030 	);
17031 
17032 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
17033 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
17034 
17035 
17036 	Status XInitThreads();
17037 	void XLockDisplay (Display* display);
17038 	void XUnlockDisplay (Display* display);
17039 
17040 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
17041 
17042 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
17043 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
17044 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
17045 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
17046 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
17047 
17048 
17049 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
17050 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
17051 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
17052 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
17053 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
17054 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
17055 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
17056 	int XDrawPoint(Display*, Drawable, GC, int, int);
17057 	int XSetForeground(Display*, GC, uint);
17058 	int XSetBackground(Display*, GC, uint);
17059 
17060 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
17061 	void XFreeFontSet(Display*, XFontSet);
17062 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
17063 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
17064 
17065 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
17066 
17067 
17068 //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);
17069 
17070 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
17071 	int XSetFunction(Display*, GC, int);
17072 
17073 	GC XCreateGC(Display*, Drawable, uint, void*);
17074 	int XCopyGC(Display*, GC, uint, GC);
17075 	int XFreeGC(Display*, GC);
17076 
17077 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
17078 	bool XCheckMaskEvent(Display*, int, XEvent*);
17079 
17080 	int XPending(Display*);
17081 	int XEventsQueued(Display* display, int mode);
17082 
17083 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
17084 	int XFreePixmap(Display*, Pixmap);
17085 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
17086 	int XFlush(Display*);
17087 	int XBell(Display*, int);
17088 	int XSync(Display*, bool);
17089 
17090 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
17091 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
17092 
17093 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
17094 	int XUngrabKeyboard(Display*, Time);
17095 
17096 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
17097 
17098 	KeySym XStringToKeysym(const char *string);
17099 
17100 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
17101 
17102 	Window XDefaultRootWindow(Display*);
17103 
17104 	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);
17105 
17106 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
17107 
17108 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
17109 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
17110 
17111 	Status XAllocColor(Display*, Colormap, XColor*);
17112 
17113 	int XWithdrawWindow(Display*, Window, int);
17114 	int XUnmapWindow(Display*, Window);
17115 	int XLowerWindow(Display*, Window);
17116 	int XRaiseWindow(Display*, Window);
17117 
17118 	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);
17119 	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);
17120 
17121 	int XGetInputFocus(Display*, Window*, int*);
17122 	int XSetInputFocus(Display*, Window, int, Time);
17123 
17124 	XErrorHandler XSetErrorHandler(XErrorHandler);
17125 
17126 	int XGetErrorText(Display*, int, char*, int);
17127 
17128 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
17129 
17130 
17131 	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);
17132 	int XUngrabPointer(Display *display, Time time);
17133 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
17134 
17135 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
17136 
17137 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
17138 	int XSetClipMask(Display*, GC, Pixmap);
17139 	int XSetClipOrigin(Display*, GC, int, int);
17140 
17141 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
17142 
17143 	void XSetWMName(Display*, Window, XTextProperty*);
17144 	Status XGetWMName(Display*, Window, XTextProperty*);
17145 	int XStoreName(Display* display, Window w, const(char)* window_name);
17146 
17147 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
17148 
17149 }
17150 }
17151 
17152 interface Xext {
17153 extern(C) nothrow @nogc {
17154 	Status XShmAttach(Display*, XShmSegmentInfo*);
17155 	Status XShmDetach(Display*, XShmSegmentInfo*);
17156 	Status XShmPutImage(
17157 		Display*            /* dpy */,
17158 		Drawable            /* d */,
17159 		GC                  /* gc */,
17160 		XImage*             /* image */,
17161 		int                 /* src_x */,
17162 		int                 /* src_y */,
17163 		int                 /* dst_x */,
17164 		int                 /* dst_y */,
17165 		uint        /* src_width */,
17166 		uint        /* src_height */,
17167 		Bool                /* send_event */
17168 	);
17169 
17170 	Status XShmQueryExtension(Display*);
17171 
17172 	XImage *XShmCreateImage(
17173 		Display*            /* dpy */,
17174 		Visual*             /* visual */,
17175 		uint        /* depth */,
17176 		int                 /* format */,
17177 		char*               /* data */,
17178 		XShmSegmentInfo*    /* shminfo */,
17179 		uint        /* width */,
17180 		uint        /* height */
17181 	);
17182 
17183 	Pixmap XShmCreatePixmap(
17184 		Display*            /* dpy */,
17185 		Drawable            /* d */,
17186 		char*               /* data */,
17187 		XShmSegmentInfo*    /* shminfo */,
17188 		uint        /* width */,
17189 		uint        /* height */,
17190 		uint        /* depth */
17191 	);
17192 
17193 }
17194 }
17195 
17196 	// this requires -lXpm
17197 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
17198 
17199 
17200 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
17201 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
17202 shared static this() {
17203 	xlib.loadDynamicLibrary();
17204 	xext.loadDynamicLibrary();
17205 }
17206 
17207 
17208 extern(C) nothrow @nogc {
17209 
17210 alias XrmDatabase = void*;
17211 struct XrmValue {
17212 	uint size;
17213 	void* addr;
17214 }
17215 
17216 struct XVisualInfo {
17217 	Visual* visual;
17218 	VisualID visualid;
17219 	int screen;
17220 	uint depth;
17221 	int c_class;
17222 	c_ulong red_mask;
17223 	c_ulong green_mask;
17224 	c_ulong blue_mask;
17225 	int colormap_size;
17226 	int bits_per_rgb;
17227 }
17228 
17229 enum VisualNoMask=	0x0;
17230 enum VisualIDMask=	0x1;
17231 enum VisualScreenMask=0x2;
17232 enum VisualDepthMask=	0x4;
17233 enum VisualClassMask=	0x8;
17234 enum VisualRedMaskMask=0x10;
17235 enum VisualGreenMaskMask=0x20;
17236 enum VisualBlueMaskMask=0x40;
17237 enum VisualColormapSizeMask=0x80;
17238 enum VisualBitsPerRGBMask=0x100;
17239 enum VisualAllMask=	0x1FF;
17240 
17241 enum AnyKey = 0;
17242 enum AnyModifier = 1 << 15;
17243 
17244 // XIM and other crap
17245 struct _XOM {}
17246 struct _XIM {}
17247 struct _XIC {}
17248 alias XOM = _XOM*;
17249 alias XIM = _XIM*;
17250 alias XIC = _XIC*;
17251 
17252 alias XVaNestedList = void*;
17253 
17254 alias XIMStyle = arch_ulong;
17255 enum : arch_ulong {
17256 	XIMPreeditArea      = 0x0001,
17257 	XIMPreeditCallbacks = 0x0002,
17258 	XIMPreeditPosition  = 0x0004,
17259 	XIMPreeditNothing   = 0x0008,
17260 	XIMPreeditNone      = 0x0010,
17261 	XIMStatusArea       = 0x0100,
17262 	XIMStatusCallbacks  = 0x0200,
17263 	XIMStatusNothing    = 0x0400,
17264 	XIMStatusNone       = 0x0800,
17265 }
17266 
17267 
17268 /* X Shared Memory Extension functions */
17269 	//pragma(lib, "Xshm");
17270 	alias arch_ulong ShmSeg;
17271 	struct XShmSegmentInfo {
17272 		ShmSeg shmseg;
17273 		int shmid;
17274 		ubyte* shmaddr;
17275 		Bool readOnly;
17276 	}
17277 
17278 	// and the necessary OS functions
17279 	int shmget(int, size_t, int);
17280 	void* shmat(int, scope const void*, int);
17281 	int shmdt(scope const void*);
17282 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
17283 
17284 	enum IPC_PRIVATE = 0;
17285 	enum IPC_CREAT = 512;
17286 	enum IPC_RMID = 0;
17287 
17288 /* MIT-SHM end */
17289 
17290 
17291 enum MappingType:int {
17292 	MappingModifier		=0,
17293 	MappingKeyboard		=1,
17294 	MappingPointer		=2
17295 }
17296 
17297 /* ImageFormat -- PutImage, GetImage */
17298 enum ImageFormat:int {
17299 	XYBitmap	=0,	/* depth 1, XYFormat */
17300 	XYPixmap	=1,	/* depth == drawable depth */
17301 	ZPixmap	=2	/* depth == drawable depth */
17302 }
17303 
17304 enum ModifierName:int {
17305 	ShiftMapIndex	=0,
17306 	LockMapIndex	=1,
17307 	ControlMapIndex	=2,
17308 	Mod1MapIndex	=3,
17309 	Mod2MapIndex	=4,
17310 	Mod3MapIndex	=5,
17311 	Mod4MapIndex	=6,
17312 	Mod5MapIndex	=7
17313 }
17314 
17315 enum ButtonMask:int {
17316 	Button1Mask	=1<<8,
17317 	Button2Mask	=1<<9,
17318 	Button3Mask	=1<<10,
17319 	Button4Mask	=1<<11,
17320 	Button5Mask	=1<<12,
17321 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17322 }
17323 
17324 enum KeyOrButtonMask:uint {
17325 	ShiftMask	=1<<0,
17326 	LockMask	=1<<1,
17327 	ControlMask	=1<<2,
17328 	Mod1Mask	=1<<3,
17329 	Mod2Mask	=1<<4,
17330 	Mod3Mask	=1<<5,
17331 	Mod4Mask	=1<<6,
17332 	Mod5Mask	=1<<7,
17333 	Button1Mask	=1<<8,
17334 	Button2Mask	=1<<9,
17335 	Button3Mask	=1<<10,
17336 	Button4Mask	=1<<11,
17337 	Button5Mask	=1<<12,
17338 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17339 }
17340 
17341 enum ButtonName:int {
17342 	Button1	=1,
17343 	Button2	=2,
17344 	Button3	=3,
17345 	Button4	=4,
17346 	Button5	=5
17347 }
17348 
17349 /* Notify modes */
17350 enum NotifyModes:int
17351 {
17352 	NotifyNormal		=0,
17353 	NotifyGrab			=1,
17354 	NotifyUngrab		=2,
17355 	NotifyWhileGrabbed	=3
17356 }
17357 enum NotifyHint = 1;	/* for MotionNotify events */
17358 
17359 /* Notify detail */
17360 enum NotifyDetail:int
17361 {
17362 	NotifyAncestor			=0,
17363 	NotifyVirtual			=1,
17364 	NotifyInferior			=2,
17365 	NotifyNonlinear			=3,
17366 	NotifyNonlinearVirtual	=4,
17367 	NotifyPointer			=5,
17368 	NotifyPointerRoot		=6,
17369 	NotifyDetailNone		=7
17370 }
17371 
17372 /* Visibility notify */
17373 
17374 enum VisibilityNotify:int
17375 {
17376 VisibilityUnobscured		=0,
17377 VisibilityPartiallyObscured	=1,
17378 VisibilityFullyObscured		=2
17379 }
17380 
17381 
17382 enum WindowStackingMethod:int
17383 {
17384 	Above		=0,
17385 	Below		=1,
17386 	TopIf		=2,
17387 	BottomIf	=3,
17388 	Opposite	=4
17389 }
17390 
17391 /* Circulation request */
17392 enum CirculationRequest:int
17393 {
17394 	PlaceOnTop		=0,
17395 	PlaceOnBottom	=1
17396 }
17397 
17398 enum PropertyNotification:int
17399 {
17400 	PropertyNewValue	=0,
17401 	PropertyDelete		=1
17402 }
17403 
17404 enum ColorMapNotification:int
17405 {
17406 	ColormapUninstalled	=0,
17407 	ColormapInstalled		=1
17408 }
17409 
17410 
17411 	struct _XPrivate {}
17412 	struct _XrmHashBucketRec {}
17413 
17414 	alias void* XPointer;
17415 	alias void* XExtData;
17416 
17417 	version( X86_64 ) {
17418 		alias ulong XID;
17419 		alias ulong arch_ulong;
17420 		alias long arch_long;
17421 	} else version (AArch64) {
17422 		alias ulong XID;
17423 		alias ulong arch_ulong;
17424 		alias long arch_long;
17425 	} else {
17426 		alias uint XID;
17427 		alias uint arch_ulong;
17428 		alias int arch_long;
17429 	}
17430 
17431 	alias XID Window;
17432 	alias XID Drawable;
17433 	alias XID Pixmap;
17434 
17435 	alias arch_ulong Atom;
17436 	alias int Bool;
17437 	alias Display XDisplay;
17438 
17439 	alias int ByteOrder;
17440 	alias arch_ulong Time;
17441 	alias void ScreenFormat;
17442 
17443 	struct XImage {
17444 		int width, height;			/* size of image */
17445 		int xoffset;				/* number of pixels offset in X direction */
17446 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
17447 		void *data;					/* pointer to image data */
17448 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
17449 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
17450 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
17451 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
17452 		int depth;					/* depth of image */
17453 		int bytes_per_line;			/* accelarator to next line */
17454 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
17455 		arch_ulong red_mask;	/* bits in z arrangment */
17456 		arch_ulong green_mask;
17457 		arch_ulong blue_mask;
17458 		XPointer obdata;			/* hook for the object routines to hang on */
17459 		static struct F {				/* image manipulation routines */
17460 			XImage* function(
17461 				XDisplay* 			/* display */,
17462 				Visual*				/* visual */,
17463 				uint				/* depth */,
17464 				int					/* format */,
17465 				int					/* offset */,
17466 				ubyte*				/* data */,
17467 				uint				/* width */,
17468 				uint				/* height */,
17469 				int					/* bitmap_pad */,
17470 				int					/* bytes_per_line */) create_image;
17471 			int function(XImage *) destroy_image;
17472 			arch_ulong function(XImage *, int, int) get_pixel;
17473 			int function(XImage *, int, int, arch_ulong) put_pixel;
17474 			XImage* function(XImage *, int, int, uint, uint) sub_image;
17475 			int function(XImage *, arch_long) add_pixel;
17476 		}
17477 		F f;
17478 	}
17479 	version(X86_64) static assert(XImage.sizeof == 136);
17480 	else version(X86) static assert(XImage.sizeof == 88);
17481 
17482 struct XCharStruct {
17483 	short       lbearing;       /* origin to left edge of raster */
17484 	short       rbearing;       /* origin to right edge of raster */
17485 	short       width;          /* advance to next char's origin */
17486 	short       ascent;         /* baseline to top edge of raster */
17487 	short       descent;        /* baseline to bottom edge of raster */
17488 	ushort attributes;  /* per char flags (not predefined) */
17489 }
17490 
17491 /*
17492  * To allow arbitrary information with fonts, there are additional properties
17493  * returned.
17494  */
17495 struct XFontProp {
17496 	Atom name;
17497 	arch_ulong card32;
17498 }
17499 
17500 alias Atom Font;
17501 
17502 struct XFontStruct {
17503 	XExtData *ext_data;           /* Hook for extension to hang data */
17504 	Font fid;                     /* Font ID for this font */
17505 	uint direction;           /* Direction the font is painted */
17506 	uint min_char_or_byte2;   /* First character */
17507 	uint max_char_or_byte2;   /* Last character */
17508 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
17509 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
17510 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
17511 	uint default_char;        /* Char to print for undefined character */
17512 	int n_properties;             /* How many properties there are */
17513 	XFontProp *properties;        /* Pointer to array of additional properties*/
17514 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
17515 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
17516 	XCharStruct *per_char;        /* first_char to last_char information */
17517 	int ascent;                   /* Max extent above baseline for spacing */
17518 	int descent;                  /* Max descent below baseline for spacing */
17519 }
17520 
17521 
17522 /*
17523  * Definitions of specific events.
17524  */
17525 struct XKeyEvent
17526 {
17527 	int type;			/* of event */
17528 	arch_ulong serial;		/* # of last request processed by server */
17529 	Bool send_event;	/* true if this came from a SendEvent request */
17530 	Display *display;	/* Display the event was read from */
17531 	Window window;	        /* "event" window it is reported relative to */
17532 	Window root;	        /* root window that the event occurred on */
17533 	Window subwindow;	/* child window */
17534 	Time time;		/* milliseconds */
17535 	int x, y;		/* pointer x, y coordinates in event window */
17536 	int x_root, y_root;	/* coordinates relative to root */
17537 	KeyOrButtonMask state;	/* key or button mask */
17538 	uint keycode;	/* detail */
17539 	Bool same_screen;	/* same screen flag */
17540 }
17541 version(X86_64) static assert(XKeyEvent.sizeof == 96);
17542 alias XKeyEvent XKeyPressedEvent;
17543 alias XKeyEvent XKeyReleasedEvent;
17544 
17545 struct XButtonEvent
17546 {
17547 	int type;		/* of event */
17548 	arch_ulong serial;	/* # of last request processed by server */
17549 	Bool send_event;	/* true if this came from a SendEvent request */
17550 	Display *display;	/* Display the event was read from */
17551 	Window window;	        /* "event" window it is reported relative to */
17552 	Window root;	        /* root window that the event occurred on */
17553 	Window subwindow;	/* child window */
17554 	Time time;		/* milliseconds */
17555 	int x, y;		/* pointer x, y coordinates in event window */
17556 	int x_root, y_root;	/* coordinates relative to root */
17557 	KeyOrButtonMask state;	/* key or button mask */
17558 	uint button;	/* detail */
17559 	Bool same_screen;	/* same screen flag */
17560 }
17561 alias XButtonEvent XButtonPressedEvent;
17562 alias XButtonEvent XButtonReleasedEvent;
17563 
17564 struct XMotionEvent{
17565 	int type;		/* of event */
17566 	arch_ulong serial;	/* # of last request processed by server */
17567 	Bool send_event;	/* true if this came from a SendEvent request */
17568 	Display *display;	/* Display the event was read from */
17569 	Window window;	        /* "event" window reported relative to */
17570 	Window root;	        /* root window that the event occurred on */
17571 	Window subwindow;	/* child window */
17572 	Time time;		/* milliseconds */
17573 	int x, y;		/* pointer x, y coordinates in event window */
17574 	int x_root, y_root;	/* coordinates relative to root */
17575 	KeyOrButtonMask state;	/* key or button mask */
17576 	byte is_hint;		/* detail */
17577 	Bool same_screen;	/* same screen flag */
17578 }
17579 alias XMotionEvent XPointerMovedEvent;
17580 
17581 struct XCrossingEvent{
17582 	int type;		/* of event */
17583 	arch_ulong serial;	/* # of last request processed by server */
17584 	Bool send_event;	/* true if this came from a SendEvent request */
17585 	Display *display;	/* Display the event was read from */
17586 	Window window;	        /* "event" window reported relative to */
17587 	Window root;	        /* root window that the event occurred on */
17588 	Window subwindow;	/* child window */
17589 	Time time;		/* milliseconds */
17590 	int x, y;		/* pointer x, y coordinates in event window */
17591 	int x_root, y_root;	/* coordinates relative to root */
17592 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
17593 	NotifyDetail detail;
17594 	/*
17595 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17596 	 * NotifyNonlinear,NotifyNonlinearVirtual
17597 	 */
17598 	Bool same_screen;	/* same screen flag */
17599 	Bool focus;		/* Boolean focus */
17600 	KeyOrButtonMask state;	/* key or button mask */
17601 }
17602 alias XCrossingEvent XEnterWindowEvent;
17603 alias XCrossingEvent XLeaveWindowEvent;
17604 
17605 struct XFocusChangeEvent{
17606 	int type;		/* FocusIn or FocusOut */
17607 	arch_ulong serial;	/* # of last request processed by server */
17608 	Bool send_event;	/* true if this came from a SendEvent request */
17609 	Display *display;	/* Display the event was read from */
17610 	Window window;		/* window of event */
17611 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
17612 				   NotifyGrab, NotifyUngrab */
17613 	NotifyDetail detail;
17614 	/*
17615 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17616 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
17617 	 * NotifyPointerRoot, NotifyDetailNone
17618 	 */
17619 }
17620 alias XFocusChangeEvent XFocusInEvent;
17621 alias XFocusChangeEvent XFocusOutEvent;
17622 
17623 enum CWBackPixmap              = (1L<<0);
17624 enum CWBackPixel               = (1L<<1);
17625 enum CWBorderPixmap            = (1L<<2);
17626 enum CWBorderPixel             = (1L<<3);
17627 enum CWBitGravity              = (1L<<4);
17628 enum CWWinGravity              = (1L<<5);
17629 enum CWBackingStore            = (1L<<6);
17630 enum CWBackingPlanes           = (1L<<7);
17631 enum CWBackingPixel            = (1L<<8);
17632 enum CWOverrideRedirect        = (1L<<9);
17633 enum CWSaveUnder               = (1L<<10);
17634 enum CWEventMask               = (1L<<11);
17635 enum CWDontPropagate           = (1L<<12);
17636 enum CWColormap                = (1L<<13);
17637 enum CWCursor                  = (1L<<14);
17638 
17639 struct XWindowAttributes {
17640 	int x, y;			/* location of window */
17641 	int width, height;		/* width and height of window */
17642 	int border_width;		/* border width of window */
17643 	int depth;			/* depth of window */
17644 	Visual *visual;			/* the associated visual structure */
17645 	Window root;			/* root of screen containing window */
17646 	int class_;			/* InputOutput, InputOnly*/
17647 	int bit_gravity;		/* one of the bit gravity values */
17648 	int win_gravity;		/* one of the window gravity values */
17649 	int backing_store;		/* NotUseful, WhenMapped, Always */
17650 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
17651 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
17652 	Bool save_under;		/* boolean, should bits under be saved? */
17653 	Colormap colormap;		/* color map to be associated with window */
17654 	Bool map_installed;		/* boolean, is color map currently installed*/
17655 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
17656 	arch_long all_event_masks;		/* set of events all people have interest in*/
17657 	arch_long your_event_mask;		/* my event mask */
17658 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
17659 	Bool override_redirect;		/* boolean value for override-redirect */
17660 	Screen *screen;			/* back pointer to correct screen */
17661 }
17662 
17663 enum IsUnmapped = 0;
17664 enum IsUnviewable = 1;
17665 enum IsViewable = 2;
17666 
17667 struct XSetWindowAttributes {
17668 	Pixmap background_pixmap;/* background, None, or ParentRelative */
17669 	arch_ulong background_pixel;/* background pixel */
17670 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
17671 	arch_ulong border_pixel;/* border pixel value */
17672 	int bit_gravity;         /* one of bit gravity values */
17673 	int win_gravity;         /* one of the window gravity values */
17674 	int backing_store;       /* NotUseful, WhenMapped, Always */
17675 	arch_ulong backing_planes;/* planes to be preserved if possible */
17676 	arch_ulong backing_pixel;/* value to use in restoring planes */
17677 	Bool save_under;         /* should bits under be saved? (popups) */
17678 	arch_long event_mask;         /* set of events that should be saved */
17679 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
17680 	Bool override_redirect;  /* boolean value for override_redirect */
17681 	Colormap colormap;       /* color map to be associated with window */
17682 	Cursor cursor;           /* cursor to be displayed (or None) */
17683 }
17684 
17685 
17686 alias int Status;
17687 
17688 
17689 enum EventMask:int
17690 {
17691 	NoEventMask				=0,
17692 	KeyPressMask			=1<<0,
17693 	KeyReleaseMask			=1<<1,
17694 	ButtonPressMask			=1<<2,
17695 	ButtonReleaseMask		=1<<3,
17696 	EnterWindowMask			=1<<4,
17697 	LeaveWindowMask			=1<<5,
17698 	PointerMotionMask		=1<<6,
17699 	PointerMotionHintMask	=1<<7,
17700 	Button1MotionMask		=1<<8,
17701 	Button2MotionMask		=1<<9,
17702 	Button3MotionMask		=1<<10,
17703 	Button4MotionMask		=1<<11,
17704 	Button5MotionMask		=1<<12,
17705 	ButtonMotionMask		=1<<13,
17706 	KeymapStateMask		=1<<14,
17707 	ExposureMask			=1<<15,
17708 	VisibilityChangeMask	=1<<16,
17709 	StructureNotifyMask		=1<<17,
17710 	ResizeRedirectMask		=1<<18,
17711 	SubstructureNotifyMask	=1<<19,
17712 	SubstructureRedirectMask=1<<20,
17713 	FocusChangeMask			=1<<21,
17714 	PropertyChangeMask		=1<<22,
17715 	ColormapChangeMask		=1<<23,
17716 	OwnerGrabButtonMask		=1<<24
17717 }
17718 
17719 struct MwmHints {
17720 	c_ulong flags;
17721 	c_ulong functions;
17722 	c_ulong decorations;
17723 	c_long input_mode;
17724 	c_ulong status;
17725 }
17726 
17727 enum {
17728 	MWM_HINTS_FUNCTIONS = (1L << 0),
17729 	MWM_HINTS_DECORATIONS =  (1L << 1),
17730 
17731 	MWM_FUNC_ALL = (1L << 0),
17732 	MWM_FUNC_RESIZE = (1L << 1),
17733 	MWM_FUNC_MOVE = (1L << 2),
17734 	MWM_FUNC_MINIMIZE = (1L << 3),
17735 	MWM_FUNC_MAXIMIZE = (1L << 4),
17736 	MWM_FUNC_CLOSE = (1L << 5),
17737 
17738 	MWM_DECOR_ALL = (1L << 0),
17739 	MWM_DECOR_BORDER = (1L << 1),
17740 	MWM_DECOR_RESIZEH = (1L << 2),
17741 	MWM_DECOR_TITLE = (1L << 3),
17742 	MWM_DECOR_MENU = (1L << 4),
17743 	MWM_DECOR_MINIMIZE = (1L << 5),
17744 	MWM_DECOR_MAXIMIZE = (1L << 6),
17745 }
17746 
17747 import core.stdc.config : c_long, c_ulong;
17748 
17749 	/* Size hints mask bits */
17750 
17751 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
17752 	enum   USSize      = (1L << 1)          /* user specified width, height */;
17753 	enum   PPosition   = (1L << 2)          /* program specified position */;
17754 	enum   PSize       = (1L << 3)          /* program specified size */;
17755 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
17756 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
17757 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
17758 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
17759 	enum   PBaseSize   = (1L << 8);
17760 	enum   PWinGravity = (1L << 9);
17761 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
17762 	struct XSizeHints {
17763 		arch_long flags;         /* marks which fields in this structure are defined */
17764 		int x, y;           /* Obsolete */
17765 		int width, height;  /* Obsolete */
17766 		int min_width, min_height;
17767 		int max_width, max_height;
17768 		int width_inc, height_inc;
17769 		struct Aspect {
17770 			int x;       /* numerator */
17771 			int y;       /* denominator */
17772 		}
17773 
17774 		Aspect min_aspect;
17775 		Aspect max_aspect;
17776 		int base_width, base_height;
17777 		int win_gravity;
17778 		/* this structure may be extended in the future */
17779 	}
17780 
17781 
17782 
17783 enum EventType:int
17784 {
17785 	KeyPress			=2,
17786 	KeyRelease			=3,
17787 	ButtonPress			=4,
17788 	ButtonRelease		=5,
17789 	MotionNotify		=6,
17790 	EnterNotify			=7,
17791 	LeaveNotify			=8,
17792 	FocusIn				=9,
17793 	FocusOut			=10,
17794 	KeymapNotify		=11,
17795 	Expose				=12,
17796 	GraphicsExpose		=13,
17797 	NoExpose			=14,
17798 	VisibilityNotify	=15,
17799 	CreateNotify		=16,
17800 	DestroyNotify		=17,
17801 	UnmapNotify		=18,
17802 	MapNotify			=19,
17803 	MapRequest			=20,
17804 	ReparentNotify		=21,
17805 	ConfigureNotify		=22,
17806 	ConfigureRequest	=23,
17807 	GravityNotify		=24,
17808 	ResizeRequest		=25,
17809 	CirculateNotify		=26,
17810 	CirculateRequest	=27,
17811 	PropertyNotify		=28,
17812 	SelectionClear		=29,
17813 	SelectionRequest	=30,
17814 	SelectionNotify		=31,
17815 	ColormapNotify		=32,
17816 	ClientMessage		=33,
17817 	MappingNotify		=34,
17818 	LASTEvent			=35	/* must be bigger than any event # */
17819 }
17820 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
17821 struct XKeymapEvent
17822 {
17823 	int type;
17824 	arch_ulong serial;	/* # of last request processed by server */
17825 	Bool send_event;	/* true if this came from a SendEvent request */
17826 	Display *display;	/* Display the event was read from */
17827 	Window window;
17828 	byte[32] key_vector;
17829 }
17830 
17831 struct XExposeEvent
17832 {
17833 	int type;
17834 	arch_ulong serial;	/* # of last request processed by server */
17835 	Bool send_event;	/* true if this came from a SendEvent request */
17836 	Display *display;	/* Display the event was read from */
17837 	Window window;
17838 	int x, y;
17839 	int width, height;
17840 	int count;		/* if non-zero, at least this many more */
17841 }
17842 
17843 struct XGraphicsExposeEvent{
17844 	int type;
17845 	arch_ulong serial;	/* # of last request processed by server */
17846 	Bool send_event;	/* true if this came from a SendEvent request */
17847 	Display *display;	/* Display the event was read from */
17848 	Drawable drawable;
17849 	int x, y;
17850 	int width, height;
17851 	int count;		/* if non-zero, at least this many more */
17852 	int major_code;		/* core is CopyArea or CopyPlane */
17853 	int minor_code;		/* not defined in the core */
17854 }
17855 
17856 struct XNoExposeEvent{
17857 	int type;
17858 	arch_ulong serial;	/* # of last request processed by server */
17859 	Bool send_event;	/* true if this came from a SendEvent request */
17860 	Display *display;	/* Display the event was read from */
17861 	Drawable drawable;
17862 	int major_code;		/* core is CopyArea or CopyPlane */
17863 	int minor_code;		/* not defined in the core */
17864 }
17865 
17866 struct XVisibilityEvent{
17867 	int type;
17868 	arch_ulong serial;	/* # of last request processed by server */
17869 	Bool send_event;	/* true if this came from a SendEvent request */
17870 	Display *display;	/* Display the event was read from */
17871 	Window window;
17872 	VisibilityNotify state;		/* Visibility state */
17873 }
17874 
17875 struct XCreateWindowEvent{
17876 	int type;
17877 	arch_ulong serial;	/* # of last request processed by server */
17878 	Bool send_event;	/* true if this came from a SendEvent request */
17879 	Display *display;	/* Display the event was read from */
17880 	Window parent;		/* parent of the window */
17881 	Window window;		/* window id of window created */
17882 	int x, y;		/* window location */
17883 	int width, height;	/* size of window */
17884 	int border_width;	/* border width */
17885 	Bool override_redirect;	/* creation should be overridden */
17886 }
17887 
17888 struct XDestroyWindowEvent
17889 {
17890 	int type;
17891 	arch_ulong serial;		/* # of last request processed by server */
17892 	Bool send_event;	/* true if this came from a SendEvent request */
17893 	Display *display;	/* Display the event was read from */
17894 	Window event;
17895 	Window window;
17896 }
17897 
17898 struct XUnmapEvent
17899 {
17900 	int type;
17901 	arch_ulong serial;		/* # of last request processed by server */
17902 	Bool send_event;	/* true if this came from a SendEvent request */
17903 	Display *display;	/* Display the event was read from */
17904 	Window event;
17905 	Window window;
17906 	Bool from_configure;
17907 }
17908 
17909 struct XMapEvent
17910 {
17911 	int type;
17912 	arch_ulong serial;		/* # of last request processed by server */
17913 	Bool send_event;	/* true if this came from a SendEvent request */
17914 	Display *display;	/* Display the event was read from */
17915 	Window event;
17916 	Window window;
17917 	Bool override_redirect;	/* Boolean, is override set... */
17918 }
17919 
17920 struct XMapRequestEvent
17921 {
17922 	int type;
17923 	arch_ulong serial;	/* # of last request processed by server */
17924 	Bool send_event;	/* true if this came from a SendEvent request */
17925 	Display *display;	/* Display the event was read from */
17926 	Window parent;
17927 	Window window;
17928 }
17929 
17930 struct XReparentEvent
17931 {
17932 	int type;
17933 	arch_ulong serial;	/* # of last request processed by server */
17934 	Bool send_event;	/* true if this came from a SendEvent request */
17935 	Display *display;	/* Display the event was read from */
17936 	Window event;
17937 	Window window;
17938 	Window parent;
17939 	int x, y;
17940 	Bool override_redirect;
17941 }
17942 
17943 struct XConfigureEvent
17944 {
17945 	int type;
17946 	arch_ulong serial;	/* # of last request processed by server */
17947 	Bool send_event;	/* true if this came from a SendEvent request */
17948 	Display *display;	/* Display the event was read from */
17949 	Window event;
17950 	Window window;
17951 	int x, y;
17952 	int width, height;
17953 	int border_width;
17954 	Window above;
17955 	Bool override_redirect;
17956 }
17957 
17958 struct XGravityEvent
17959 {
17960 	int type;
17961 	arch_ulong serial;	/* # of last request processed by server */
17962 	Bool send_event;	/* true if this came from a SendEvent request */
17963 	Display *display;	/* Display the event was read from */
17964 	Window event;
17965 	Window window;
17966 	int x, y;
17967 }
17968 
17969 struct XResizeRequestEvent
17970 {
17971 	int type;
17972 	arch_ulong serial;	/* # of last request processed by server */
17973 	Bool send_event;	/* true if this came from a SendEvent request */
17974 	Display *display;	/* Display the event was read from */
17975 	Window window;
17976 	int width, height;
17977 }
17978 
17979 struct  XConfigureRequestEvent
17980 {
17981 	int type;
17982 	arch_ulong serial;	/* # of last request processed by server */
17983 	Bool send_event;	/* true if this came from a SendEvent request */
17984 	Display *display;	/* Display the event was read from */
17985 	Window parent;
17986 	Window window;
17987 	int x, y;
17988 	int width, height;
17989 	int border_width;
17990 	Window above;
17991 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17992 	arch_ulong value_mask;
17993 }
17994 
17995 struct XCirculateEvent
17996 {
17997 	int type;
17998 	arch_ulong serial;	/* # of last request processed by server */
17999 	Bool send_event;	/* true if this came from a SendEvent request */
18000 	Display *display;	/* Display the event was read from */
18001 	Window event;
18002 	Window window;
18003 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
18004 }
18005 
18006 struct XCirculateRequestEvent
18007 {
18008 	int type;
18009 	arch_ulong serial;	/* # of last request processed by server */
18010 	Bool send_event;	/* true if this came from a SendEvent request */
18011 	Display *display;	/* Display the event was read from */
18012 	Window parent;
18013 	Window window;
18014 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
18015 }
18016 
18017 struct XPropertyEvent
18018 {
18019 	int type;
18020 	arch_ulong serial;	/* # of last request processed by server */
18021 	Bool send_event;	/* true if this came from a SendEvent request */
18022 	Display *display;	/* Display the event was read from */
18023 	Window window;
18024 	Atom atom;
18025 	Time time;
18026 	PropertyNotification state;		/* NewValue, Deleted */
18027 }
18028 
18029 struct XSelectionClearEvent
18030 {
18031 	int type;
18032 	arch_ulong serial;	/* # of last request processed by server */
18033 	Bool send_event;	/* true if this came from a SendEvent request */
18034 	Display *display;	/* Display the event was read from */
18035 	Window window;
18036 	Atom selection;
18037 	Time time;
18038 }
18039 
18040 struct XSelectionRequestEvent
18041 {
18042 	int type;
18043 	arch_ulong serial;	/* # of last request processed by server */
18044 	Bool send_event;	/* true if this came from a SendEvent request */
18045 	Display *display;	/* Display the event was read from */
18046 	Window owner;
18047 	Window requestor;
18048 	Atom selection;
18049 	Atom target;
18050 	Atom property;
18051 	Time time;
18052 }
18053 
18054 struct XSelectionEvent
18055 {
18056 	int type;
18057 	arch_ulong serial;	/* # of last request processed by server */
18058 	Bool send_event;	/* true if this came from a SendEvent request */
18059 	Display *display;	/* Display the event was read from */
18060 	Window requestor;
18061 	Atom selection;
18062 	Atom target;
18063 	Atom property;		/* ATOM or None */
18064 	Time time;
18065 }
18066 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
18067 
18068 struct XColormapEvent
18069 {
18070 	int type;
18071 	arch_ulong serial;	/* # of last request processed by server */
18072 	Bool send_event;	/* true if this came from a SendEvent request */
18073 	Display *display;	/* Display the event was read from */
18074 	Window window;
18075 	Colormap colormap;	/* COLORMAP or None */
18076 	Bool new_;		/* C++ */
18077 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
18078 }
18079 version(X86_64) static assert(XColormapEvent.sizeof == 56);
18080 
18081 struct XClientMessageEvent
18082 {
18083 	int type;
18084 	arch_ulong serial;	/* # of last request processed by server */
18085 	Bool send_event;	/* true if this came from a SendEvent request */
18086 	Display *display;	/* Display the event was read from */
18087 	Window window;
18088 	Atom message_type;
18089 	int format;
18090 	union Data{
18091 		byte[20] b;
18092 		short[10] s;
18093 		arch_ulong[5] l;
18094 	}
18095 	Data data;
18096 
18097 }
18098 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
18099 
18100 struct XMappingEvent
18101 {
18102 	int type;
18103 	arch_ulong serial;	/* # of last request processed by server */
18104 	Bool send_event;	/* true if this came from a SendEvent request */
18105 	Display *display;	/* Display the event was read from */
18106 	Window window;		/* unused */
18107 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
18108 				   MappingPointer */
18109 	int first_keycode;	/* first keycode */
18110 	int count;		/* defines range of change w. first_keycode*/
18111 }
18112 
18113 struct XErrorEvent
18114 {
18115 	int type;
18116 	Display *display;	/* Display the event was read from */
18117 	XID resourceid;		/* resource id */
18118 	arch_ulong serial;	/* serial number of failed request */
18119 	ubyte error_code;	/* error code of failed request */
18120 	ubyte request_code;	/* Major op-code of failed request */
18121 	ubyte minor_code;	/* Minor op-code of failed request */
18122 }
18123 
18124 struct XAnyEvent
18125 {
18126 	int type;
18127 	arch_ulong serial;	/* # of last request processed by server */
18128 	Bool send_event;	/* true if this came from a SendEvent request */
18129 	Display *display;/* Display the event was read from */
18130 	Window window;	/* window on which event was requested in event mask */
18131 }
18132 
18133 union XEvent{
18134 	int type;		/* must not be changed; first element */
18135 	XAnyEvent xany;
18136 	XKeyEvent xkey;
18137 	XButtonEvent xbutton;
18138 	XMotionEvent xmotion;
18139 	XCrossingEvent xcrossing;
18140 	XFocusChangeEvent xfocus;
18141 	XExposeEvent xexpose;
18142 	XGraphicsExposeEvent xgraphicsexpose;
18143 	XNoExposeEvent xnoexpose;
18144 	XVisibilityEvent xvisibility;
18145 	XCreateWindowEvent xcreatewindow;
18146 	XDestroyWindowEvent xdestroywindow;
18147 	XUnmapEvent xunmap;
18148 	XMapEvent xmap;
18149 	XMapRequestEvent xmaprequest;
18150 	XReparentEvent xreparent;
18151 	XConfigureEvent xconfigure;
18152 	XGravityEvent xgravity;
18153 	XResizeRequestEvent xresizerequest;
18154 	XConfigureRequestEvent xconfigurerequest;
18155 	XCirculateEvent xcirculate;
18156 	XCirculateRequestEvent xcirculaterequest;
18157 	XPropertyEvent xproperty;
18158 	XSelectionClearEvent xselectionclear;
18159 	XSelectionRequestEvent xselectionrequest;
18160 	XSelectionEvent xselection;
18161 	XColormapEvent xcolormap;
18162 	XClientMessageEvent xclient;
18163 	XMappingEvent xmapping;
18164 	XErrorEvent xerror;
18165 	XKeymapEvent xkeymap;
18166 	arch_ulong[24] pad;
18167 }
18168 
18169 
18170 	struct Display {
18171 		XExtData *ext_data;	/* hook for extension to hang data */
18172 		_XPrivate *private1;
18173 		int fd;			/* Network socket. */
18174 		int private2;
18175 		int proto_major_version;/* major version of server's X protocol */
18176 		int proto_minor_version;/* minor version of servers X protocol */
18177 		char *vendor;		/* vendor of the server hardware */
18178 	    	XID private3;
18179 		XID private4;
18180 		XID private5;
18181 		int private6;
18182 		XID function(Display*)resource_alloc;/* allocator function */
18183 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
18184 		int bitmap_unit;	/* padding and data requirements */
18185 		int bitmap_pad;		/* padding requirements on bitmaps */
18186 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
18187 		int nformats;		/* number of pixmap formats in list */
18188 		ScreenFormat *pixmap_format;	/* pixmap format list */
18189 		int private8;
18190 		int release;		/* release of the server */
18191 		_XPrivate *private9;
18192 		_XPrivate *private10;
18193 		int qlen;		/* Length of input event queue */
18194 		arch_ulong last_request_read; /* seq number of last event read */
18195 		arch_ulong request;	/* sequence number of last request. */
18196 		XPointer private11;
18197 		XPointer private12;
18198 		XPointer private13;
18199 		XPointer private14;
18200 		uint max_request_size; /* maximum number 32 bit words in request*/
18201 		_XrmHashBucketRec *db;
18202 		int function  (Display*)private15;
18203 		char *display_name;	/* "host:display" string used on this connect*/
18204 		int default_screen;	/* default screen for operations */
18205 		int nscreens;		/* number of screens on this server*/
18206 		Screen *screens;	/* pointer to list of screens */
18207 		arch_ulong motion_buffer;	/* size of motion buffer */
18208 		arch_ulong private16;
18209 		int min_keycode;	/* minimum defined keycode */
18210 		int max_keycode;	/* maximum defined keycode */
18211 		XPointer private17;
18212 		XPointer private18;
18213 		int private19;
18214 		byte *xdefaults;	/* contents of defaults from server */
18215 		/* there is more to this structure, but it is private to Xlib */
18216 	}
18217 
18218 	// I got these numbers from a C program as a sanity test
18219 	version(X86_64) {
18220 		static assert(Display.sizeof == 296);
18221 		static assert(XPointer.sizeof == 8);
18222 		static assert(XErrorEvent.sizeof == 40);
18223 		static assert(XAnyEvent.sizeof == 40);
18224 		static assert(XMappingEvent.sizeof == 56);
18225 		static assert(XEvent.sizeof == 192);
18226     	} else version (AArch64) {
18227 		// omit check for aarch64
18228 	} else {
18229 		static assert(Display.sizeof == 176);
18230 		static assert(XPointer.sizeof == 4);
18231 		static assert(XEvent.sizeof == 96);
18232 	}
18233 
18234 struct Depth
18235 {
18236 	int depth;		/* this depth (Z) of the depth */
18237 	int nvisuals;		/* number of Visual types at this depth */
18238 	Visual *visuals;	/* list of visuals possible at this depth */
18239 }
18240 
18241 alias void* GC;
18242 alias c_ulong VisualID;
18243 alias XID Colormap;
18244 alias XID Cursor;
18245 alias XID KeySym;
18246 alias uint KeyCode;
18247 enum None = 0;
18248 }
18249 
18250 version(without_opengl) {}
18251 else {
18252 extern(C) nothrow @nogc {
18253 
18254 
18255 static if(!SdpyIsUsingIVGLBinds) {
18256 enum GLX_USE_GL=            1;       /* support GLX rendering */
18257 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
18258 enum GLX_LEVEL=             3;       /* level in plane stacking */
18259 enum GLX_RGBA=              4;       /* true if RGBA mode */
18260 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
18261 enum GLX_STEREO=            6;       /* stereo buffering supported */
18262 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
18263 enum GLX_RED_SIZE=          8;       /* number of red component bits */
18264 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
18265 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
18266 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
18267 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
18268 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
18269 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
18270 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
18271 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
18272 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
18273 
18274 
18275 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
18276 
18277 
18278 
18279 enum GL_TRUE = 1;
18280 enum GL_FALSE = 0;
18281 }
18282 
18283 alias XID GLXContextID;
18284 alias XID GLXPixmap;
18285 alias XID GLXDrawable;
18286 alias XID GLXPbuffer;
18287 alias XID GLXWindow;
18288 alias XID GLXFBConfigID;
18289 alias void* GLXContext;
18290 
18291 }
18292 }
18293 
18294 enum AllocNone = 0;
18295 
18296 extern(C) {
18297 	/* WARNING, this type not in Xlib spec */
18298 	extern(C) alias XIOErrorHandler = int function (Display* display);
18299 }
18300 
18301 extern(C) nothrow
18302 alias XErrorHandler = int function(Display*, XErrorEvent*);
18303 
18304 extern(C) nothrow @nogc {
18305 struct Screen{
18306 	XExtData *ext_data;		/* hook for extension to hang data */
18307 	Display *display;		/* back pointer to display structure */
18308 	Window root;			/* Root window id. */
18309 	int width, height;		/* width and height of screen */
18310 	int mwidth, mheight;	/* width and height of  in millimeters */
18311 	int ndepths;			/* number of depths possible */
18312 	Depth *depths;			/* list of allowable depths on the screen */
18313 	int root_depth;			/* bits per pixel */
18314 	Visual *root_visual;	/* root visual */
18315 	GC default_gc;			/* GC for the root root visual */
18316 	Colormap cmap;			/* default color map */
18317 	uint white_pixel;
18318 	uint black_pixel;		/* White and Black pixel values */
18319 	int max_maps, min_maps;	/* max and min color maps */
18320 	int backing_store;		/* Never, WhenMapped, Always */
18321 	bool save_unders;
18322 	int root_input_mask;	/* initial root input mask */
18323 }
18324 
18325 struct Visual
18326 {
18327 	XExtData *ext_data;	/* hook for extension to hang data */
18328 	VisualID visualid;	/* visual id of this visual */
18329 	int class_;			/* class of screen (monochrome, etc.) */
18330 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
18331 	int bits_per_rgb;	/* log base 2 of distinct color values */
18332 	int map_entries;	/* color map entries */
18333 }
18334 
18335 	alias Display* _XPrivDisplay;
18336 
18337 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system {
18338 		assert(dpy !is null);
18339 		return &dpy.screens[scr];
18340 	}
18341 
18342 	extern(D) Window RootWindow(Display *dpy,int scr) {
18343 		return ScreenOfDisplay(dpy,scr).root;
18344 	}
18345 
18346 	struct XWMHints {
18347 		arch_long flags;
18348 		Bool input;
18349 		int initial_state;
18350 		Pixmap icon_pixmap;
18351 		Window icon_window;
18352 		int icon_x, icon_y;
18353 		Pixmap icon_mask;
18354 		XID window_group;
18355 	}
18356 
18357 	struct XClassHint {
18358 		char* res_name;
18359 		char* res_class;
18360 	}
18361 
18362 	extern(D) int DefaultScreen(Display *dpy) {
18363 		return dpy.default_screen;
18364 	}
18365 
18366 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
18367 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
18368 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
18369 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
18370 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
18371 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
18372 
18373 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
18374 
18375 	enum int AnyPropertyType = 0;
18376 	enum int Success = 0;
18377 
18378 	enum int RevertToNone = None;
18379 	enum int PointerRoot = 1;
18380 	enum Time CurrentTime = 0;
18381 	enum int RevertToPointerRoot = PointerRoot;
18382 	enum int RevertToParent = 2;
18383 
18384 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
18385 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
18386 	}
18387 
18388 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
18389 		return ScreenOfDisplay(dpy,scr).root_visual;
18390 	}
18391 
18392 	extern(D) GC DefaultGC(Display *dpy,int scr) {
18393 		return ScreenOfDisplay(dpy,scr).default_gc;
18394 	}
18395 
18396 	extern(D) uint BlackPixel(Display *dpy,int scr) {
18397 		return ScreenOfDisplay(dpy,scr).black_pixel;
18398 	}
18399 
18400 	extern(D) uint WhitePixel(Display *dpy,int scr) {
18401 		return ScreenOfDisplay(dpy,scr).white_pixel;
18402 	}
18403 
18404 	alias void* XFontSet; // i think
18405 	struct XmbTextItem {
18406 		char* chars;
18407 		int nchars;
18408 		int delta;
18409 		XFontSet font_set;
18410 	}
18411 
18412 	struct XTextItem {
18413 		char* chars;
18414 		int nchars;
18415 		int delta;
18416 		Font font;
18417 	}
18418 
18419 	enum {
18420 		GXclear        = 0x0, /* 0 */
18421 		GXand          = 0x1, /* src AND dst */
18422 		GXandReverse   = 0x2, /* src AND NOT dst */
18423 		GXcopy         = 0x3, /* src */
18424 		GXandInverted  = 0x4, /* NOT src AND dst */
18425 		GXnoop         = 0x5, /* dst */
18426 		GXxor          = 0x6, /* src XOR dst */
18427 		GXor           = 0x7, /* src OR dst */
18428 		GXnor          = 0x8, /* NOT src AND NOT dst */
18429 		GXequiv        = 0x9, /* NOT src XOR dst */
18430 		GXinvert       = 0xa, /* NOT dst */
18431 		GXorReverse    = 0xb, /* src OR NOT dst */
18432 		GXcopyInverted = 0xc, /* NOT src */
18433 		GXorInverted   = 0xd, /* NOT src OR dst */
18434 		GXnand         = 0xe, /* NOT src OR NOT dst */
18435 		GXset          = 0xf, /* 1 */
18436 	}
18437 	enum QueueMode : int {
18438 		QueuedAlready,
18439 		QueuedAfterReading,
18440 		QueuedAfterFlush
18441 	}
18442 
18443 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
18444 
18445 	struct XPoint {
18446 		short x;
18447 		short y;
18448 	}
18449 
18450 	enum CoordMode:int {
18451 		CoordModeOrigin = 0,
18452 		CoordModePrevious = 1
18453 	}
18454 
18455 	enum PolygonShape:int {
18456 		Complex = 0,
18457 		Nonconvex = 1,
18458 		Convex = 2
18459 	}
18460 
18461 	struct XTextProperty {
18462 		const(char)* value;		/* same as Property routines */
18463 		Atom encoding;			/* prop type */
18464 		int format;				/* prop data format: 8, 16, or 32 */
18465 		arch_ulong nitems;		/* number of data items in value */
18466 	}
18467 
18468 	version( X86_64 ) {
18469 		static assert(XTextProperty.sizeof == 32);
18470 	}
18471 
18472 
18473 	struct XGCValues {
18474 		int function_;           /* logical operation */
18475 		arch_ulong plane_mask;/* plane mask */
18476 		arch_ulong foreground;/* foreground pixel */
18477 		arch_ulong background;/* background pixel */
18478 		int line_width;         /* line width */
18479 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
18480 		int cap_style;          /* CapNotLast, CapButt,
18481 					   CapRound, CapProjecting */
18482 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
18483 		int fill_style;         /* FillSolid, FillTiled,
18484 					   FillStippled, FillOpaeueStippled */
18485 		int fill_rule;          /* EvenOddRule, WindingRule */
18486 		int arc_mode;           /* ArcChord, ArcPieSlice */
18487 		Pixmap tile;            /* tile pixmap for tiling operations */
18488 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
18489 		int ts_x_origin;        /* offset for tile or stipple operations */
18490 		int ts_y_origin;
18491 		Font font;              /* default text font for text operations */
18492 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
18493 		Bool graphics_exposures;/* boolean, should exposures be generated */
18494 		int clip_x_origin;      /* origin for clipping */
18495 		int clip_y_origin;
18496 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
18497 		int dash_offset;        /* patterned/dashed line information */
18498 		char dashes;
18499 	}
18500 
18501 	struct XColor {
18502 		arch_ulong pixel;
18503 		ushort red, green, blue;
18504 		byte flags;
18505 		byte pad;
18506 	}
18507 
18508 	struct XRectangle {
18509 		short x;
18510 		short y;
18511 		ushort width;
18512 		ushort height;
18513 	}
18514 
18515 	enum ClipByChildren = 0;
18516 	enum IncludeInferiors = 1;
18517 
18518 	enum Atom XA_PRIMARY = 1;
18519 	enum Atom XA_SECONDARY = 2;
18520 	enum Atom XA_STRING = 31;
18521 	enum Atom XA_CARDINAL = 6;
18522 	enum Atom XA_WM_NAME = 39;
18523 	enum Atom XA_ATOM = 4;
18524 	enum Atom XA_WINDOW = 33;
18525 	enum Atom XA_WM_HINTS = 35;
18526 	enum int PropModeAppend = 2;
18527 	enum int PropModeReplace = 0;
18528 	enum int PropModePrepend = 1;
18529 
18530 	enum int CopyFromParent = 0;
18531 	enum int InputOutput = 1;
18532 
18533 	// XWMHints
18534 	enum InputHint = 1 << 0;
18535 	enum StateHint = 1 << 1;
18536 	enum IconPixmapHint = (1L << 2);
18537 	enum IconWindowHint = (1L << 3);
18538 	enum IconPositionHint = (1L << 4);
18539 	enum IconMaskHint = (1L << 5);
18540 	enum WindowGroupHint = (1L << 6);
18541 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
18542 	enum XUrgencyHint = (1L << 8);
18543 
18544 	// GC Components
18545 	enum GCFunction           =   (1L<<0);
18546 	enum GCPlaneMask         =    (1L<<1);
18547 	enum GCForeground       =     (1L<<2);
18548 	enum GCBackground      =      (1L<<3);
18549 	enum GCLineWidth      =       (1L<<4);
18550 	enum GCLineStyle     =        (1L<<5);
18551 	enum GCCapStyle     =         (1L<<6);
18552 	enum GCJoinStyle   =          (1L<<7);
18553 	enum GCFillStyle  =           (1L<<8);
18554 	enum GCFillRule  =            (1L<<9);
18555 	enum GCTile     =             (1L<<10);
18556 	enum GCStipple           =    (1L<<11);
18557 	enum GCTileStipXOrigin  =     (1L<<12);
18558 	enum GCTileStipYOrigin =      (1L<<13);
18559 	enum GCFont               =   (1L<<14);
18560 	enum GCSubwindowMode     =    (1L<<15);
18561 	enum GCGraphicsExposures=     (1L<<16);
18562 	enum GCClipXOrigin     =      (1L<<17);
18563 	enum GCClipYOrigin    =       (1L<<18);
18564 	enum GCClipMask      =        (1L<<19);
18565 	enum GCDashOffset   =         (1L<<20);
18566 	enum GCDashList    =          (1L<<21);
18567 	enum GCArcMode    =           (1L<<22);
18568 	enum GCLastBit   =            22;
18569 
18570 
18571 	enum int WithdrawnState = 0;
18572 	enum int NormalState = 1;
18573 	enum int IconicState = 3;
18574 
18575 }
18576 } else version (OSXCocoa) {
18577 
18578 /+
18579 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
18580 +/
18581 
18582 	private __gshared AppDelegate globalAppDelegate;
18583 
18584 	extern(Objective-C)
18585 	class AppDelegate : NSObject, NSApplicationDelegate {
18586 		override static AppDelegate alloc() @selector("alloc");
18587 
18588 
18589 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
18590 			SimpleWindow.processAllCustomEvents();
18591 		}
18592 
18593 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
18594 			immutable style = NSWindowStyleMask.resizable |
18595 				NSWindowStyleMask.closable |
18596 				NSWindowStyleMask.miniaturizable |
18597 				NSWindowStyleMask.titled;
18598 
18599 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
18600 
18601 			{
18602 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
18603 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
18604 				mainMenu.setSubmenu(menu, item);
18605 
18606 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
18607 				newItem.target = NSApp;
18608 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
18609 				newItem2.target = NSApp;
18610 			}
18611 
18612 			{
18613 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
18614 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
18615 				mainMenu.setSubmenu(menu, item);
18616 
18617 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
18618 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
18619 			}
18620 
18621 
18622 			NSApp.menu = mainMenu;
18623 
18624 
18625 			// auto controller = ViewController.alloc.init;
18626 
18627 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
18628 
18629 			/+
18630 			this.window = window;
18631 			this.controller = controller;
18632 			+/
18633 		}
18634 
18635 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
18636 			NSApplication.shared_.activateIgnoringOtherApps(true);
18637 		}
18638 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
18639 			return true;
18640 		}
18641 	}
18642 
18643 	extern(Objective-C)
18644 	class SDWindowDelegate : NSObject, NSWindowDelegate {
18645 		override static SDWindowDelegate alloc() @selector("alloc");
18646 		override SDWindowDelegate init() @selector("init");
18647 
18648 		SimpleWindow simpleWindow;
18649 
18650 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
18651 			auto window = cast(void*) notification.object;
18652 
18653 			// FIXME: do i need to release it?
18654 			SimpleWindow.nativeMapping.remove(window);
18655 		}
18656 
18657 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
18658 			if(simpleWindow.windowResized) {
18659 				// FIXME: automaticallyScaleIfPossible behaviors
18660 
18661 				simpleWindow._width = cast(int) frameSize.width;
18662 				simpleWindow._height = cast(int) frameSize.height;
18663 
18664 				simpleWindow.view.setFrameSize(frameSize);
18665 
18666 				/+
18667 				auto size = simpleWindow.view.frame.size;
18668 				writeln(cast(int) size.width, "x", cast(int) size.height);
18669 				+/
18670 
18671 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
18672 
18673 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
18674 
18675 				// simpleWindow.view.setNeedsDisplay(true);
18676 			}
18677 
18678 			return frameSize;
18679 		}
18680 
18681 		/+
18682 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
18683 			if(simpleWindow.windowResized) {
18684 				auto window = simpleWindow.window;
18685 				auto rect = window.contentRectForFrameRect(window.frame);
18686 				import std.stdio; writeln(window.frame.size);
18687 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
18688 			}
18689 		}
18690 		+/
18691 	}
18692 
18693 	extern(Objective-C)
18694 	class SDGraphicsView : NSView {
18695 		SimpleWindow simpleWindow;
18696 
18697 		override static SDGraphicsView alloc() @selector("alloc");
18698 		override SDGraphicsView init() @selector("init");/* {
18699 			super.init();
18700 			return this;
18701 		}*/
18702 
18703 		override void drawRect(NSRect rect) @selector("drawRect:") {
18704 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
18705 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18706 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
18707 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18708 			CGImageRelease(cgImage);
18709 		}
18710 
18711 		extern(D)
18712 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
18713 			MouseEvent me;
18714 			me.type = type;
18715 
18716 			auto pos = event.locationInWindow;
18717 
18718 			me.x = cast(int) pos.x;
18719 			me.y = cast(int) (simpleWindow.height - pos.y);
18720 
18721 			me.dx = 0; // FIXME
18722 			me.dy = 0; // FIXME
18723 
18724 			me.button = button;
18725 			me.modifierState = cast(uint) event.modifierFlags;
18726 			me.window = simpleWindow;
18727 
18728 			me.doubleClick = false;
18729 
18730 			if(simpleWindow && simpleWindow.handleMouseEvent)
18731 				simpleWindow.handleMouseEvent(me);
18732 		}
18733 
18734 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
18735 			// writeln(event.pressedMouseButtons);
18736 
18737 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18738 		}
18739 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
18740 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
18741 		}
18742 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
18743 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
18744 		}
18745 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
18746 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
18747 		}
18748 		/+
18749 			// FIXME
18750 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
18751 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18752 		}
18753 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
18754 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18755 		}
18756 		+/
18757 
18758 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
18759 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
18760 		}
18761 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
18762 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
18763 		}
18764 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
18765 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
18766 		}
18767 
18768 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
18769 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
18770 		}
18771 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
18772 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
18773 		}
18774 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
18775 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
18776 		}
18777 
18778 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
18779 			// import std.stdio; writeln(event.deltaY);
18780 		}
18781 
18782 		override void keyDown(NSEvent event) @selector("keyDown:") {
18783 			// the event may have multiple characters, and we send them all at once.
18784 			if (simpleWindow.handleCharEvent) {
18785 				auto chars = DeifiedNSString(event.characters);
18786 				foreach (dchar dc; chars.str)
18787 					simpleWindow.handleCharEvent(dc);
18788 			}
18789 
18790 			keyHelper(event, true);
18791 		}
18792 
18793 		override void keyUp(NSEvent event) @selector("keyUp:") {
18794 			keyHelper(event, false);
18795 		}
18796 
18797 		extern(D)
18798 		private void keyHelper(NSEvent event, bool pressed) {
18799 			if(simpleWindow.handleKeyEvent) {
18800 				KeyEvent ev;
18801 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
18802 				ev.pressed = pressed;
18803 				ev.hardwareCode = cast(ubyte) event.keyCode;
18804 				ev.modifierState = cast(uint) event.modifierFlags;
18805 				ev.window = simpleWindow;
18806 
18807 				simpleWindow.handleKeyEvent(ev);
18808 			}
18809 		}
18810 
18811 		override bool isFlipped() @selector("isFlipped") {
18812 			return true;
18813 		}
18814 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
18815 			return true;
18816 		}
18817 
18818 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
18819 			if(simpleWindow && simpleWindow.handlePulse)
18820 				simpleWindow.handlePulse();
18821 			/+
18822 			setNeedsDisplay = true;
18823 			+/
18824 		}
18825 	}
18826 
18827 private:
18828 	alias const(void)* CFStringRef;
18829 	alias const(void)* CFAllocatorRef;
18830 	alias const(void)* CFTypeRef;
18831 	alias const(void)* CGColorSpaceRef;
18832 	alias const(void)* CGImageRef;
18833 	alias ulong CGBitmapInfo;
18834 	alias NSGraphicsContext CGContextRef;
18835 
18836 	alias NSPoint CGPoint;
18837 	alias NSSize CGSize;
18838 	alias NSRect CGRect;
18839 
18840 	struct CGAffineTransform {
18841 		double a, b, c, d, tx, ty;
18842 	}
18843 
18844 	enum NSApplicationActivationPolicyRegular = 0;
18845 	enum NSBackingStoreBuffered = 2;
18846 	enum kCFStringEncodingUTF8 = 0x08000100;
18847 
18848 	enum : size_t {
18849 		NSBorderlessWindowMask = 0,
18850 		NSTitledWindowMask = 1 << 0,
18851 		NSClosableWindowMask = 1 << 1,
18852 		NSMiniaturizableWindowMask = 1 << 2,
18853 		NSResizableWindowMask = 1 << 3,
18854 		NSTexturedBackgroundWindowMask = 1 << 8
18855 	}
18856 
18857 	enum : ulong {
18858 		kCGImageAlphaNone,
18859 		kCGImageAlphaPremultipliedLast,
18860 		kCGImageAlphaPremultipliedFirst,
18861 		kCGImageAlphaLast,
18862 		kCGImageAlphaFirst,
18863 		kCGImageAlphaNoneSkipLast,
18864 		kCGImageAlphaNoneSkipFirst
18865 	}
18866 	enum : ulong {
18867 		kCGBitmapAlphaInfoMask = 0x1F,
18868 		kCGBitmapFloatComponents = (1 << 8),
18869 		kCGBitmapByteOrderMask = 0x7000,
18870 		kCGBitmapByteOrderDefault = (0 << 12),
18871 		kCGBitmapByteOrder16Little = (1 << 12),
18872 		kCGBitmapByteOrder32Little = (2 << 12),
18873 		kCGBitmapByteOrder16Big = (3 << 12),
18874 		kCGBitmapByteOrder32Big = (4 << 12)
18875 	}
18876 	enum CGPathDrawingMode {
18877 		kCGPathFill,
18878 		kCGPathEOFill,
18879 		kCGPathStroke,
18880 		kCGPathFillStroke,
18881 		kCGPathEOFillStroke
18882 	}
18883 	enum objc_AssociationPolicy : size_t {
18884 		OBJC_ASSOCIATION_ASSIGN = 0,
18885 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18886 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18887 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18888 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18889 	}
18890 
18891 	extern(C) {
18892 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18893 		void CGContextRelease(CGContextRef c);
18894 		ubyte* CGBitmapContextGetData(CGContextRef c);
18895 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18896 		size_t CGBitmapContextGetWidth(CGContextRef c);
18897 		size_t CGBitmapContextGetHeight(CGContextRef c);
18898 
18899 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18900 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18901 
18902 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18903 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18904 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18905 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18906 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18907 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
18908 
18909 		void CGContextBeginPath(CGContextRef c);
18910 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18911 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18912 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18913 		void CGContextAddRect(CGContextRef c, CGRect rect);
18914 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18915 		void CGContextSaveGState(CGContextRef c);
18916 		void CGContextRestoreGState(CGContextRef c);
18917 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18918 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18919 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18920 
18921 		void CGImageRelease(CGImageRef image);
18922 	}
18923 } else static assert(0, "Unsupported operating system");
18924 
18925 
18926 version(OSXCocoa) {
18927 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18928 	//
18929 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18930 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18931 	//
18932 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18933 	// Probably won't even fully compile right now
18934 
18935 	private enum double PI = 3.14159265358979323;
18936 
18937 	alias NSWindow NativeWindowHandle;
18938 	alias void delegate(NSid) NativeEventHandler;
18939 
18940 	enum KEY_ESCAPE = 27;
18941 
18942 	mixin template NativeImageImplementation() {
18943 		CGContextRef context;
18944 		ubyte* rawData;
18945 
18946 		final:
18947 
18948 		void convertToRgbaBytes(ubyte[] where) @system {
18949 			assert(where.length == this.width * this.height * 4);
18950 
18951 			// if rawData had a length....
18952 			//assert(rawData.length == where.length);
18953 			for(long idx = 0; idx < where.length; idx += 4) {
18954 				auto alpha = rawData[idx + 3];
18955 				if(alpha == 255) {
18956 					where[idx + 0] = rawData[idx + 0]; // r
18957 					where[idx + 1] = rawData[idx + 1]; // g
18958 					where[idx + 2] = rawData[idx + 2]; // b
18959 					where[idx + 3] = rawData[idx + 3]; // a
18960 				} else {
18961 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18962 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18963 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18964 					where[idx + 3] = rawData[idx + 3]; // a
18965 
18966 				}
18967 			}
18968 		}
18969 
18970 		void setFromRgbaBytes(in ubyte[] where) @system {
18971 			// FIXME: this is probably wrong
18972 			assert(where.length == this.width * this.height * 4);
18973 
18974 			// if rawData had a length....
18975 			//assert(rawData.length == where.length);
18976 			for(long idx = 0; idx < where.length; idx += 4) {
18977 				auto alpha = where[idx + 3];
18978 				if(alpha == 255) {
18979 					rawData[idx + 0] = where[idx + 0]; // r
18980 					rawData[idx + 1] = where[idx + 1]; // g
18981 					rawData[idx + 2] = where[idx + 2]; // b
18982 					rawData[idx + 3] = where[idx + 3]; // a
18983 				} else if(alpha == 0) {
18984 					rawData[idx + 0] = 0;
18985 					rawData[idx + 1] = 0;
18986 					rawData[idx + 2] = 0;
18987 					rawData[idx + 3] = 0;
18988 				} else {
18989 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18990 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18991 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18992 					rawData[idx + 3] = where[idx + 3]; // a
18993 				}
18994 			}
18995 		}
18996 
18997 
18998 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18999 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
19000 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
19001 			CGColorSpaceRelease(colorSpace);
19002 			rawData = CGBitmapContextGetData(context);
19003 		}
19004 		void dispose() {
19005 			CGContextRelease(context);
19006 		}
19007 
19008 		void setPixel(int x, int y, Color c) @system {
19009 			auto offset = (y * width + x) * 4;
19010 			if (c.a == 255) {
19011 				rawData[offset + 0] = c.r;
19012 				rawData[offset + 1] = c.g;
19013 				rawData[offset + 2] = c.b;
19014 				rawData[offset + 3] = c.a;
19015 			} else {
19016 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
19017 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
19018 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
19019 				rawData[offset + 3] = c.a;
19020 			}
19021 		}
19022 	}
19023 
19024 	mixin template NativeScreenPainterImplementation() {
19025 		CGContextRef context;
19026 		ubyte[4] _outlineComponents;
19027 		NSView view;
19028 
19029 		Pen _activePen;
19030 		Color _fillColor;
19031 		Rectangle _clipRectangle;
19032 		OperatingSystemFont _font;
19033 
19034 		OperatingSystemFont getFont() {
19035 			if(_font is null) {
19036 				static OperatingSystemFont _defaultFont;
19037 				if(_defaultFont is null) {
19038 					_defaultFont = new OperatingSystemFont();
19039 					_defaultFont.loadDefault();
19040 				}
19041 				_font = _defaultFont;
19042 			}
19043 
19044 			return _font;
19045 		}
19046 
19047 		void create(PaintingHandle window) {
19048 			// this.destiny = window;
19049 			if(auto sw = cast(SimpleWindow) this.window) {
19050 				context = sw.drawingContext;
19051 				view = sw.view;
19052 			} else {
19053 				throw new NotYetImplementedException();
19054 			}
19055 		}
19056 
19057 		void dispose() {
19058 			view.setNeedsDisplay(true);
19059 		}
19060 
19061 		bool manualInvalidations;
19062 		void invalidateRect(Rectangle invalidRect) { }
19063 
19064 		// NotYetImplementedException
19065 		void rasterOp(RasterOp op) {
19066 		}
19067 		void setClipRectangle(int, int, int, int) {
19068 		}
19069 		Size textSize(in char[] txt) {
19070 			auto font = getFont();
19071 			return Size(font.stringWidth(txt), font.height());
19072 		}
19073 
19074 		void setFont(OperatingSystemFont font) {
19075 			_font = font;
19076 			//font.font.setInContext(context);
19077 		}
19078 		int fontHeight() {
19079 			auto font = getFont();
19080 			return font.height;
19081 		}
19082 
19083 		// end
19084 
19085 		void pen(Pen pen) {
19086 			_activePen = pen;
19087 			auto color = pen.color; // FIXME
19088 			double alphaComponent = color.a/255.0f;
19089 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
19090 
19091 			double[2] patternBuffer;
19092 			double[] pattern;
19093 			final switch(pen.style) {
19094 				case Pen.Style.Solid:
19095 					pattern = null;
19096 				break;
19097 				case Pen.Style.Dashed:
19098 					patternBuffer[0] = 4;
19099 					patternBuffer[1] = 1;
19100 					pattern = patternBuffer[];
19101 				break;
19102 				case Pen.Style.Dotted:
19103 					patternBuffer[0] = 1;
19104 					patternBuffer[1] = 1;
19105 					pattern = patternBuffer[];
19106 				break;
19107 			}
19108 
19109 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
19110 
19111 			if (color.a != 255) {
19112 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
19113 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
19114 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
19115 				_outlineComponents[3] = color.a;
19116 			} else {
19117 				_outlineComponents[0] = color.r;
19118 				_outlineComponents[1] = color.g;
19119 				_outlineComponents[2] = color.b;
19120 				_outlineComponents[3] = color.a;
19121 			}
19122 		}
19123 
19124 		@property void fillColor(Color color) {
19125 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
19126 		}
19127 
19128 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
19129 		// NotYetImplementedException for upper left/width/height
19130 			auto cgImage = CGBitmapContextCreateImage(image.context);
19131 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
19132 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
19133 			CGImageRelease(cgImage);
19134 		}
19135 
19136 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
19137 		// FIXME: is this efficient?
19138 			auto cgImage = CGBitmapContextCreateImage(s.handle);
19139 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
19140 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
19141 			CGImageRelease(cgImage);
19142 		}
19143 
19144 
19145 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
19146 		// FIXME: alignment
19147 			if (_outlineComponents[3] != 0) {
19148 				CGContextSaveGState(context);
19149 				auto invAlpha = 1.0f/_outlineComponents[3];
19150 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
19151 												  _outlineComponents[1]*invAlpha,
19152 												  _outlineComponents[2]*invAlpha,
19153 												  _outlineComponents[3]/255.0f);
19154 
19155 
19156 
19157 				// FIXME: should we clip it to the bounding box?
19158 				int textHeight = fontHeight;
19159 
19160 				auto lines = text.split('\n');
19161 
19162 				const lineHeight = textHeight;
19163 				textHeight *= lines.length;
19164 
19165 				int cy = y;
19166 
19167 				if(alignment & TextAlignment.VerticalBottom) {
19168 					if(y2 <= 0)
19169 						return;
19170 					auto h = y2 - y;
19171 					if(h > textHeight) {
19172 						cy += h - textHeight;
19173 						cy -= lineHeight / 2;
19174 					}
19175 				} else if(alignment & TextAlignment.VerticalCenter) {
19176 					if(y2 <= 0)
19177 						return;
19178 					auto h = y2 - y;
19179 					if(textHeight < h) {
19180 						cy += (h - textHeight) / 2;
19181 						//cy -= lineHeight / 4;
19182 					}
19183 				}
19184 
19185 				foreach(line; text.split('\n')) {
19186 					int textWidth = this.textSize(line).width;
19187 
19188 					int px = x, py = cy;
19189 
19190 					if(alignment & TextAlignment.Center) {
19191 						if(x2 <= 0)
19192 							return;
19193 						auto w = x2 - x;
19194 						if(w > textWidth)
19195 							px += (w - textWidth) / 2;
19196 					} else if(alignment & TextAlignment.Right) {
19197 						if(x2 <= 0)
19198 							return;
19199 						auto pos = x2 - textWidth;
19200 						if(pos > x)
19201 							px = pos;
19202 					}
19203 
19204 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
19205 
19206 					carry_on:
19207 					cy += lineHeight + 4;
19208 				}
19209 
19210 // auto cfstr = cast(NSid)createCFString(text);
19211 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
19212 // NSPoint(x, y), null);
19213 // CFRelease(cfstr);
19214 				CGContextRestoreGState(context);
19215 			}
19216 		}
19217 
19218 		void drawPixel(int x, int y) {
19219 			auto rawData = CGBitmapContextGetData(context);
19220 			auto width = CGBitmapContextGetWidth(context);
19221 			auto height = CGBitmapContextGetHeight(context);
19222 			auto offset = ((height - y - 1) * width + x) * 4;
19223 			rawData[offset .. offset+4] = _outlineComponents;
19224 		}
19225 
19226 		void drawLine(int x1, int y1, int x2, int y2) {
19227 			CGPoint[2] linePoints;
19228 			linePoints[0] = CGPoint(x1, y1);
19229 			linePoints[1] = CGPoint(x2, y2);
19230 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
19231 		}
19232 
19233 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
19234 			drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded
19235 		}
19236 
19237 		void drawRectangle(int x, int y, int width, int height) {
19238 			CGContextBeginPath(context);
19239 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
19240 			CGContextAddRect(context, rect);
19241 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19242 		}
19243 
19244 		void drawEllipse(int x1, int y1, int x2, int y2) {
19245 			CGContextBeginPath(context);
19246 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
19247 			CGContextAddEllipseInRect(context, rect);
19248 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19249 		}
19250 
19251 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
19252 			// @@@BUG@@@ Does not support elliptic arc (width != height).
19253 			CGContextBeginPath(context);
19254 			int clockwise = 0;
19255 			if(length < 0) {
19256 				clockwise = 1;
19257 				length = -length;
19258 			}
19259 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
19260 							start*PI/(180*64), (start+length)*PI/(180*64), clockwise);
19261 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19262 		}
19263 
19264 		void drawPolygon(Point[] intPoints) {
19265 			CGContextBeginPath(context);
19266 			CGPoint[16] pointsBuffer;
19267 			CGPoint[] points;
19268 			if(intPoints.length <= pointsBuffer.length)
19269 				points = pointsBuffer[0 .. intPoints.length];
19270 			else
19271 				points = new CGPoint[](intPoints.length);
19272 
19273 			foreach(idx, pt; intPoints)
19274 				points[idx] = CGPoint(pt.x, pt.y);
19275 
19276 			CGContextAddLines(context, points.ptr, points.length);
19277 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19278 		}
19279 	}
19280 
19281 	private bool appInitialized = false;
19282 	void initializeApp() {
19283 		if(appInitialized)
19284 			return;
19285 		synchronized {
19286 			if(appInitialized)
19287 				return;
19288 
19289 			auto app = NSApp(); // ensure the is initialized
19290 
19291 			auto dg = AppDelegate.alloc;
19292 			globalAppDelegate = dg;
19293 			NSApp.delegate_ = dg;
19294 
19295 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
19296 
19297 			appInitialized = true;
19298 		}
19299 	}
19300 
19301 	mixin template NativeSimpleWindowImplementation() {
19302 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
19303 			initializeApp();
19304 
19305 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
19306 
19307 			auto window = NSWindow.alloc.initWithContentRect(
19308 				contentRect,
19309 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
19310 				NSBackingStoreType.buffered,
19311 				true
19312 			);
19313 
19314 			SimpleWindow.nativeMapping[cast(void*) window] = this;
19315 
19316 			window.title = MacString(title).borrow;
19317 
19318 			auto dg = SDWindowDelegate.alloc.init;
19319 			dg.simpleWindow = this;
19320 			window.delegate_ = dg;
19321 
19322 			auto view = SDGraphicsView.alloc.init;
19323 			assert(view !is null);
19324 			window.contentView = view;
19325 			this.view = view;
19326 			view.simpleWindow = this;
19327 
19328 			window.center();
19329 
19330 			window.makeKeyAndOrderFront(null);
19331 
19332 			// no need to make a bitmap on mac since everything is double buffered already
19333 
19334 			// create area to draw on.
19335 			createNewDrawingContext(width, height);
19336 
19337 			window.setBackgroundColor(NSColor.whiteColor);
19338 		}
19339 
19340 		void createNewDrawingContext(int width, int height) {
19341 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
19342 			if(this.drawingContext)
19343 				CGContextRelease(this.drawingContext);
19344 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
19345 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
19346 			CGColorSpaceRelease(colorSpace);
19347 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
19348 			auto matrix = CGContextGetTextMatrix(drawingContext);
19349 			matrix.c = -matrix.c;
19350 			matrix.d = -matrix.d;
19351 			CGContextSetTextMatrix(drawingContext, matrix);
19352 
19353 		}
19354 
19355 		void dispose() {
19356 			closeWindow();
19357 			// window.release(); // closing the window does this automatically i think
19358 		}
19359 		void closeWindow() {
19360 			if(timer)
19361 				timer.invalidate();
19362 			window.close();
19363 		}
19364 
19365 		ScreenPainter getPainter(bool manualInvalidations) {
19366 			return ScreenPainter(this, this.window, manualInvalidations);
19367 		}
19368 
19369 		NSWindow window;
19370 		NSTimer timer;
19371 		NSView view;
19372 		CGContextRef drawingContext;
19373 	}
19374 }
19375 
19376 version(without_opengl) {} else
19377 extern(System) nothrow @nogc {
19378 	//enum uint GL_VERSION = 0x1F02;
19379 	//const(char)* glGetString (/*GLenum*/uint);
19380 	version(X11) {
19381 	static if (!SdpyIsUsingIVGLBinds) {
19382 
19383 		enum GLX_X_RENDERABLE = 0x8012;
19384 		enum GLX_DRAWABLE_TYPE = 0x8010;
19385 		enum GLX_RENDER_TYPE = 0x8011;
19386 		enum GLX_X_VISUAL_TYPE = 0x22;
19387 		enum GLX_TRUE_COLOR = 0x8002;
19388 		enum GLX_WINDOW_BIT = 0x00000001;
19389 		enum GLX_RGBA_BIT = 0x00000001;
19390 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
19391 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
19392 		enum GLX_SAMPLES = 0x186a1;
19393 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19394 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19395 	}
19396 
19397 		// GLX_EXT_swap_control
19398 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
19399 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
19400 
19401 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
19402 		extern(System) {
19403 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
19404 		}
19405 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
19406 
19407 		// this made public so we don't have to get it again and again
19408 		public bool glXCreateContextAttribsARB_present () @system {
19409 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
19410 				// get it
19411 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
19412 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
19413 			}
19414 			return (glXCreateContextAttribsARBFn !is null);
19415 		}
19416 
19417 		// this made public so we don't have to get it again and again
19418 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system {
19419 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
19420 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
19421 		}
19422 
19423 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
19424 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
19425 
19426 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
19427 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
19428 			if (_glx_swapInterval_fn is null) {
19429 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
19430 				if (_glx_swapInterval_fn is null) {
19431 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
19432 					return;
19433 				}
19434 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
19435 			}
19436 
19437 			if(glXSwapIntervalMESA is null) {
19438 				// it seems to require both to actually take effect on many computers
19439 				// idk why
19440 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
19441 				if(glXSwapIntervalMESA is null)
19442 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
19443 			}
19444 
19445 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
19446 				glXSwapIntervalMESA(wait ? 1 : 0);
19447 
19448 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
19449 		}
19450 	} else version(Windows) {
19451 	static if (!SdpyIsUsingIVGLBinds) {
19452 	enum GL_TRUE = 1;
19453 	enum GL_FALSE = 0;
19454 
19455 	public void* glbindGetProcAddress (const(char)* name) {
19456 		void* res = wglGetProcAddress(name);
19457 		if (res is null) {
19458 			/+
19459 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
19460 			import core.sys.windows.windef, core.sys.windows.winbase;
19461 			__gshared HINSTANCE dll = null;
19462 			if (dll is null) {
19463 				dll = LoadLibraryA("opengl32.dll");
19464 				if (dll is null) return null; // <32, but idc
19465 			}
19466 			res = GetProcAddress(dll, name);
19467 			+/
19468 			res = GetProcAddress(gl.libHandle, name);
19469 		}
19470 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
19471 		return res;
19472 	}
19473 	}
19474 
19475 
19476  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
19477 	void wglSetVSync(bool wait) {
19478 		if(wglSwapIntervalEXT is null) {
19479 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
19480 			if(wglSwapIntervalEXT is null)
19481 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
19482 		}
19483 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
19484 			return;
19485 
19486 		wglSwapIntervalEXT(wait ? 1 : 0);
19487 	}
19488 
19489 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19490 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19491 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
19492 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
19493 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
19494 
19495 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
19496 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
19497 
19498 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
19499 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
19500 
19501 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
19502 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
19503 
19504 		void wglInitOtherFunctions () {
19505 			if (wglCreateContextAttribsARB is null) {
19506 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
19507 			}
19508 		}
19509 	}
19510 
19511 	static if (!SdpyIsUsingIVGLBinds) {
19512 
19513 	interface GL {
19514 		extern(System) @nogc nothrow:
19515 
19516 		void glGetIntegerv(int, void*);
19517 		void glMatrixMode(int);
19518 		void glPushMatrix();
19519 		void glLoadIdentity();
19520 		void glOrtho(double, double, double, double, double, double);
19521 		void glFrustum(double, double, double, double, double, double);
19522 
19523 		void glPopMatrix();
19524 		void glEnable(int);
19525 		void glDisable(int);
19526 		void glClear(int);
19527 		void glBegin(int);
19528 		void glVertex2f(float, float);
19529 		void glVertex3f(float, float, float);
19530 		void glEnd();
19531 		void glColor3b(byte, byte, byte);
19532 		void glColor3ub(ubyte, ubyte, ubyte);
19533 		void glColor4b(byte, byte, byte, byte);
19534 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
19535 		void glColor3i(int, int, int);
19536 		void glColor3ui(uint, uint, uint);
19537 		void glColor4i(int, int, int, int);
19538 		void glColor4ui(uint, uint, uint, uint);
19539 		void glColor3f(float, float, float);
19540 		void glColor4f(float, float, float, float);
19541 		void glTranslatef(float, float, float);
19542 		void glScalef(float, float, float);
19543 		version(X11) {
19544 			void glSecondaryColor3b(byte, byte, byte);
19545 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
19546 			void glSecondaryColor3i(int, int, int);
19547 			void glSecondaryColor3ui(uint, uint, uint);
19548 			void glSecondaryColor3f(float, float, float);
19549 		}
19550 
19551 		void glDrawElements(int, int, int, void*);
19552 
19553 		void glRotatef(float, float, float, float);
19554 
19555 		uint glGetError();
19556 
19557 		void glDeleteTextures(int, uint*);
19558 
19559 
19560 		void glRasterPos2i(int, int);
19561 		void glDrawPixels(int, int, uint, uint, void*);
19562 		void glClearColor(float, float, float, float);
19563 
19564 
19565 		void glPixelStorei(uint, int);
19566 
19567 		void glGenTextures(uint, uint*);
19568 		void glBindTexture(int, int);
19569 		void glTexParameteri(uint, uint, int);
19570 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19571 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
19572 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
19573 			/*GLsizei*/int width, /*GLsizei*/int height,
19574 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19575 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19576 
19577 		void glLineWidth(int);
19578 
19579 
19580 		void glTexCoord2f(float, float);
19581 		void glVertex2i(int, int);
19582 		void glBlendFunc (int, int);
19583 		void glDepthFunc (int);
19584 		void glViewport(int, int, int, int);
19585 
19586 		void glClearDepth(double);
19587 
19588 		void glReadBuffer(uint);
19589 		void glReadPixels(int, int, int, int, int, int, void*);
19590 
19591 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
19592 
19593 		void glFlush();
19594 		void glFinish();
19595 
19596 		version(Windows) {
19597 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
19598 			HGLRC wglCreateContext(HDC);
19599 			HGLRC wglCreateLayerContext(HDC, int);
19600 			BOOL wglDeleteContext(HGLRC);
19601 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
19602 			HGLRC wglGetCurrentContext();
19603 			HDC wglGetCurrentDC();
19604 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
19605 			PROC wglGetProcAddress(LPCSTR);
19606 			BOOL wglMakeCurrent(HDC, HGLRC);
19607 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
19608 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
19609 			BOOL wglShareLists(HGLRC, HGLRC);
19610 			BOOL wglSwapLayerBuffers(HDC, UINT);
19611 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
19612 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
19613 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19614 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19615 		}
19616 
19617 	}
19618 
19619 	interface GL3 {
19620 		extern(System) @nogc nothrow:
19621 
19622 		void glGenVertexArrays(GLsizei, GLuint*);
19623 		void glBindVertexArray(GLuint);
19624 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
19625 		void glGenerateMipmap(GLenum);
19626 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
19627 		void glStencilMask(GLuint);
19628 		void glStencilFunc(GLenum, GLint, GLuint);
19629 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19630 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19631 		GLuint glCreateProgram();
19632 		GLuint glCreateShader(GLenum);
19633 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
19634 		void glCompileShader(GLuint);
19635 		void glGetShaderiv(GLuint, GLenum, GLint*);
19636 		void glAttachShader(GLuint, GLuint);
19637 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
19638 		void glLinkProgram(GLuint);
19639 		void glGetProgramiv(GLuint, GLenum, GLint*);
19640 		void glDeleteProgram(GLuint);
19641 		void glDeleteShader(GLuint);
19642 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
19643 		void glGenBuffers(GLsizei, GLuint*);
19644 
19645 		void glUniform1f(GLint location, GLfloat v0);
19646 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
19647 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
19648 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
19649 		void glUniform1i(GLint location, GLint v0);
19650 		void glUniform2i(GLint location, GLint v0, GLint v1);
19651 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
19652 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
19653 		void glUniform1ui(GLint location, GLuint v0);
19654 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
19655 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
19656 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
19657 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
19658 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
19659 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
19660 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
19661 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
19662 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
19663 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
19664 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
19665 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
19666 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
19667 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
19668 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
19669 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19670 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19671 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19672 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19673 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19674 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19675 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19676 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19677 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19678 
19679 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
19680 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
19681 		void glDrawArrays(GLenum, GLint, GLsizei);
19682 		void glStencilOp(GLenum, GLenum, GLenum);
19683 		void glUseProgram(GLuint);
19684 		void glCullFace(GLenum);
19685 		void glFrontFace(GLenum);
19686 		void glActiveTexture(GLenum);
19687 		void glBindBuffer(GLenum, GLuint);
19688 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
19689 		void glEnableVertexAttribArray(GLuint);
19690 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
19691 		void glUniform1i(GLint, GLint);
19692 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
19693 		void glDisableVertexAttribArray(GLuint);
19694 		void glDeleteBuffers(GLsizei, const(GLuint)*);
19695 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
19696 		void glLogicOp (GLenum opcode);
19697 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
19698 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
19699 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
19700 		GLenum glCheckFramebufferStatus (GLenum target);
19701 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
19702 	}
19703 
19704 	interface GL4 {
19705 		extern(System) @nogc nothrow:
19706 
19707 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
19708 			/*GLsizei*/int width, /*GLsizei*/int height,
19709 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19710 	}
19711 
19712 	interface GLU {
19713 		extern(System) @nogc nothrow:
19714 
19715 		void gluLookAt(double, double, double, double, double, double, double, double, double);
19716 		void gluPerspective(double, double, double, double);
19717 
19718 		char* gluErrorString(uint);
19719 	}
19720 
19721 
19722 	enum GL_RED = 0x1903;
19723 	enum GL_ALPHA = 0x1906;
19724 
19725 	enum uint GL_FRONT = 0x0404;
19726 
19727 	enum uint GL_BLEND = 0x0be2;
19728 	enum uint GL_LEQUAL = 0x0203;
19729 
19730 
19731 	enum uint GL_RGB = 0x1907;
19732 	enum uint GL_BGRA = 0x80e1;
19733 	enum uint GL_RGBA = 0x1908;
19734 	enum uint GL_RGBA8 = 0x8058;
19735 	enum uint GL_TEXTURE_2D =   0x0DE1;
19736 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
19737 	enum uint GL_NEAREST = 0x2600;
19738 	enum uint GL_LINEAR = 0x2601;
19739 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
19740 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
19741 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
19742 	enum uint GL_REPEAT = 0x2901;
19743 	enum uint GL_CLAMP = 0x2900;
19744 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
19745 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
19746 	enum uint GL_DECAL = 0x2101;
19747 	enum uint GL_MODULATE = 0x2100;
19748 	enum uint GL_TEXTURE_ENV = 0x2300;
19749 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
19750 	enum uint GL_REPLACE = 0x1E01;
19751 	enum uint GL_LIGHTING = 0x0B50;
19752 	enum uint GL_DITHER = 0x0BD0;
19753 
19754 	enum uint GL_NO_ERROR = 0;
19755 
19756 
19757 
19758 	enum int GL_VIEWPORT = 0x0BA2;
19759 	enum int GL_MODELVIEW = 0x1700;
19760 	enum int GL_TEXTURE = 0x1702;
19761 	enum int GL_PROJECTION = 0x1701;
19762 	enum int GL_DEPTH_TEST = 0x0B71;
19763 
19764 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
19765 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
19766 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
19767 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
19768 
19769 	enum int GL_POINTS = 0x0000;
19770 	enum int GL_LINES =  0x0001;
19771 	enum int GL_LINE_LOOP = 0x0002;
19772 	enum int GL_LINE_STRIP = 0x0003;
19773 	enum int GL_TRIANGLES = 0x0004;
19774 	enum int GL_TRIANGLE_STRIP = 5;
19775 	enum int GL_TRIANGLE_FAN = 6;
19776 	enum int GL_QUADS = 7;
19777 	enum int GL_QUAD_STRIP = 8;
19778 	enum int GL_POLYGON = 9;
19779 
19780 	alias GLvoid = void;
19781 	alias GLboolean = ubyte;
19782 	alias GLint = int;
19783 	alias GLuint = uint;
19784 	alias GLenum = uint;
19785 	alias GLchar = char;
19786 	alias GLsizei = int;
19787 	alias GLfloat = float;
19788 	alias GLintptr = size_t;
19789 	alias GLsizeiptr = ptrdiff_t;
19790 
19791 
19792 	enum uint GL_INVALID_ENUM = 0x0500;
19793 
19794 	enum uint GL_ZERO = 0;
19795 	enum uint GL_ONE = 1;
19796 
19797 	enum uint GL_BYTE = 0x1400;
19798 	enum uint GL_UNSIGNED_BYTE = 0x1401;
19799 	enum uint GL_SHORT = 0x1402;
19800 	enum uint GL_UNSIGNED_SHORT = 0x1403;
19801 	enum uint GL_INT = 0x1404;
19802 	enum uint GL_UNSIGNED_INT = 0x1405;
19803 	enum uint GL_FLOAT = 0x1406;
19804 	enum uint GL_2_BYTES = 0x1407;
19805 	enum uint GL_3_BYTES = 0x1408;
19806 	enum uint GL_4_BYTES = 0x1409;
19807 	enum uint GL_DOUBLE = 0x140A;
19808 
19809 	enum uint GL_STREAM_DRAW = 0x88E0;
19810 
19811 	enum uint GL_CCW = 0x0901;
19812 
19813 	enum uint GL_STENCIL_TEST = 0x0B90;
19814 	enum uint GL_SCISSOR_TEST = 0x0C11;
19815 
19816 	enum uint GL_EQUAL = 0x0202;
19817 	enum uint GL_NOTEQUAL = 0x0205;
19818 
19819 	enum uint GL_ALWAYS = 0x0207;
19820 	enum uint GL_KEEP = 0x1E00;
19821 
19822 	enum uint GL_INCR = 0x1E02;
19823 
19824 	enum uint GL_INCR_WRAP = 0x8507;
19825 	enum uint GL_DECR_WRAP = 0x8508;
19826 
19827 	enum uint GL_CULL_FACE = 0x0B44;
19828 	enum uint GL_BACK = 0x0405;
19829 
19830 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
19831 	enum uint GL_VERTEX_SHADER = 0x8B31;
19832 
19833 	enum uint GL_COMPILE_STATUS = 0x8B81;
19834 	enum uint GL_LINK_STATUS = 0x8B82;
19835 
19836 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
19837 
19838 	enum uint GL_STATIC_DRAW = 0x88E4;
19839 
19840 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
19841 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
19842 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
19843 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
19844 
19845 	enum uint GL_GENERATE_MIPMAP = 0x8191;
19846 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
19847 
19848 	enum uint GL_TEXTURE0 = 0x84C0U;
19849 	enum uint GL_TEXTURE1 = 0x84C1U;
19850 
19851 	enum uint GL_ARRAY_BUFFER = 0x8892;
19852 
19853 	enum uint GL_SRC_COLOR = 0x0300;
19854 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
19855 	enum uint GL_SRC_ALPHA = 0x0302;
19856 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
19857 	enum uint GL_DST_ALPHA = 0x0304;
19858 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
19859 	enum uint GL_DST_COLOR = 0x0306;
19860 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
19861 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
19862 
19863 	enum uint GL_INVERT = 0x150AU;
19864 
19865 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
19866 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
19867 
19868 	enum uint GL_FRAMEBUFFER = 0x8D40U;
19869 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
19870 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
19871 
19872 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
19873 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
19874 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
19875 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
19876 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
19877 
19878 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
19879 	enum uint GL_CLEAR = 0x1500U;
19880 	enum uint GL_COPY = 0x1503U;
19881 	enum uint GL_XOR = 0x1506U;
19882 
19883 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
19884 
19885 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
19886 
19887 	}
19888 }
19889 
19890 /++
19891 	History:
19892 		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.
19893 +/
19894 __gshared bool gluSuccessfullyLoaded = true;
19895 
19896 version(without_opengl) {} else {
19897 static if(!SdpyIsUsingIVGLBinds) {
19898 	version(Windows) {
19899 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
19900 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
19901 	} else {
19902 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
19903 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
19904 	}
19905 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
19906 
19907 
19908 	shared static this() {
19909 		gl.loadDynamicLibrary();
19910 
19911 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
19912 		// unless those functions are actually used
19913 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
19914 		glu.loadDynamicLibrary();
19915 	}
19916 }
19917 }
19918 
19919 /++
19920 	Convenience method for converting D arrays to opengl buffer data
19921 
19922 	I would LOVE to overload it with the original glBufferData, but D won't
19923 	let me since glBufferData is a function pointer :(
19924 
19925 	Added: August 25, 2020 (version 8.5)
19926 +/
19927 version(without_opengl) {} else
19928 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
19929 	glBufferData(target, data.length, data.ptr, usage);
19930 }
19931 
19932 /++
19933 	History:
19934 		Added September 1, 2024
19935 +/
19936 version(without_opengl) {} else
19937 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) {
19938 	glBufferSubData(target, offset, data.length, data.ptr);
19939 }
19940 
19941 /++
19942 	Convenience class for using opengl shaders.
19943 
19944 	Ensure that you've loaded opengl 3+ and set your active
19945 	context before trying to use this.
19946 
19947 	Added: August 25, 2020 (version 8.5)
19948 +/
19949 version(without_opengl) {} else
19950 final class OpenGlShader {
19951 	private int shaderProgram_;
19952 	private @property void shaderProgram(int a) {
19953 		shaderProgram_ = a;
19954 	}
19955 	/// Get the program ID for use in OpenGL functions.
19956 	public @property int shaderProgram() {
19957 		return shaderProgram_;
19958 	}
19959 
19960 	/++
19961 
19962 	+/
19963 	static struct Source {
19964 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19965 		string code; ///
19966 	}
19967 
19968 	/++
19969 		Helper method to just compile some shader code and check for errors
19970 		while you do glCreateShader, etc. on the outside yourself.
19971 
19972 		This just does `glShaderSource` and `glCompileShader` for the given code.
19973 
19974 		If you the OpenGlShader class constructor, you never need to call this yourself.
19975 	+/
19976 	static void compile(int sid, Source code) {
19977 		const(char)*[1] buffer;
19978 		int[1] lengthBuffer;
19979 
19980 		buffer[0] = code.code.ptr;
19981 		lengthBuffer[0] = cast(int) code.code.length;
19982 
19983 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19984 		glCompileShader(sid);
19985 
19986 		int success;
19987 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19988 		if(!success) {
19989 			char[512] info;
19990 			int len;
19991 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19992 
19993 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19994 		}
19995 	}
19996 
19997 	/++
19998 		Calls `glLinkProgram` and throws if error a occurs.
19999 
20000 		If you the OpenGlShader class constructor, you never need to call this yourself.
20001 	+/
20002 	static void link(int shaderProgram) {
20003 		glLinkProgram(shaderProgram);
20004 		int success;
20005 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
20006 		if(!success) {
20007 			char[512] info;
20008 			int len;
20009 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
20010 
20011 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
20012 		}
20013 	}
20014 
20015 	/++
20016 		Constructs the shader object by calling `glCreateProgram`, then
20017 		compiling each given [Source], and finally, linking them together.
20018 
20019 		Throws: on compile or link failure.
20020 	+/
20021 	this(Source[] codes...) {
20022 		shaderProgram = glCreateProgram();
20023 
20024 		int[16] shadersBufferStack;
20025 
20026 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
20027 			shadersBufferStack[0 .. codes.length] :
20028 			new int[](codes.length);
20029 
20030 		foreach(idx, code; codes) {
20031 			shadersBuffer[idx] = glCreateShader(code.type);
20032 
20033 			compile(shadersBuffer[idx], code);
20034 
20035 			glAttachShader(shaderProgram, shadersBuffer[idx]);
20036 		}
20037 
20038 		link(shaderProgram);
20039 
20040 		foreach(s; shadersBuffer)
20041 			glDeleteShader(s);
20042 	}
20043 
20044 	/// Calls `glUseProgram(this.shaderProgram)`
20045 	void use() {
20046 		glUseProgram(this.shaderProgram);
20047 	}
20048 
20049 	/// Deletes the program.
20050 	void delete_() {
20051 		glDeleteProgram(shaderProgram);
20052 		shaderProgram = 0;
20053 	}
20054 
20055 	/++
20056 		[OpenGlShader.uniforms].name gives you one of these.
20057 
20058 		You can get the id out of it or just assign
20059 	+/
20060 	static struct Uniform {
20061 		/// the id passed to glUniform*
20062 		int id;
20063 
20064 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
20065 		void opAssign(float x, float y, float z, float w) {
20066 			if(id != -1)
20067 			glUniform4f(id, x, y, z, w);
20068 		}
20069 
20070 		void opAssign(float x) {
20071 			if(id != -1)
20072 			glUniform1f(id, x);
20073 		}
20074 
20075 		void opAssign(float x, float y) {
20076 			if(id != -1)
20077 			glUniform2f(id, x, y);
20078 		}
20079 
20080 		void opAssign(T)(T t) {
20081 			t.glUniform(id);
20082 		}
20083 	}
20084 
20085 	static struct UniformsHelper {
20086 		OpenGlShader _shader;
20087 
20088 		@property Uniform opDispatch(string name)() {
20089 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
20090 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
20091 			//if(i == -1)
20092 				//throw new Exception("Could not find uniform " ~ name);
20093 			return Uniform(i);
20094 		}
20095 
20096 		@property void opDispatch(string name, T)(T t) {
20097 			Uniform f = this.opDispatch!name;
20098 			t.glUniform(f);
20099 		}
20100 	}
20101 
20102 	/++
20103 		Gives access to the uniforms through dot access.
20104 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
20105 	+/
20106 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
20107 }
20108 
20109 version(without_opengl) {} else {
20110 /++
20111 	A static container of experimental types and value constructors for opengl 3+ shaders.
20112 
20113 
20114 	You can declare variables like:
20115 
20116 	```
20117 	OGL.vec3f something;
20118 	```
20119 
20120 	But generally it would be used with [OpenGlShader]'s uniform helpers like
20121 
20122 	```
20123 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
20124 	```
20125 
20126 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
20127 
20128 
20129 	History:
20130 		Added December 7, 2021. Not yet stable.
20131 +/
20132 final class OGL {
20133 	static:
20134 
20135 	private template typeFromSpecifier(string specifier) {
20136 		static if(specifier == "f")
20137 			alias typeFromSpecifier = GLfloat;
20138 		else static if(specifier == "i")
20139 			alias typeFromSpecifier = GLint;
20140 		else static if(specifier == "ui")
20141 			alias typeFromSpecifier = GLuint;
20142 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
20143 	}
20144 
20145 	private template CommonType(T...) {
20146 		static if(T.length == 1)
20147 			alias CommonType = T[0];
20148 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
20149 			alias CommonType = CommonType!(C, T[2 .. $]);
20150 	}
20151 
20152 	private template typesToSpecifier(T...) {
20153 		static if(is(CommonType!T == float))
20154 			enum typesToSpecifier = "f";
20155 		else static if(is(CommonType!T == int))
20156 			enum typesToSpecifier = "i";
20157 		else static if(is(CommonType!T == uint))
20158 			enum typesToSpecifier = "ui";
20159 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
20160 	}
20161 
20162 	private template genNames(size_t dim, size_t dim2 = 0) {
20163 		string helper() {
20164 			string s;
20165 			if(dim2) {
20166 				static if(__VERSION__ < 2102)
20167 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug
20168 				else
20169 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;";
20170 			} else {
20171 				if(dim > 0) s ~= "type x = 0;";
20172 				if(dim > 1) s ~= "type y = 0;";
20173 				if(dim > 2) s ~= "type z = 0;";
20174 				if(dim > 3) s ~= "type w = 0;";
20175 			}
20176 
20177 			s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }";
20178 			if(dim2)
20179 				s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }";
20180 
20181 			return s;
20182 		}
20183 
20184 		enum genNames = helper();
20185 	}
20186 
20187 	// there's vec, arrays of vec, mat, and arrays of mat
20188 	template opDispatch(string name)
20189 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
20190 	{
20191 		static if(name[4] == 'x') {
20192 			enum dimX = cast(int) (name[3] - '0');
20193 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
20194 
20195 			enum dimY = cast(int) (name[5] - '0');
20196 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
20197 
20198 			enum isArray = name[$ - 1] == 'v';
20199 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
20200 			alias type = typeFromSpecifier!typeSpecifier;
20201 		} else {
20202 			enum dim = cast(int) (name[3] - '0');
20203 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
20204 			enum isArray = name[$ - 1] == 'v';
20205 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
20206 			alias type = typeFromSpecifier!typeSpecifier;
20207 		}
20208 
20209 		align(1)
20210 		struct opDispatch {
20211 			align(1):
20212 			static if(name[4] == 'x')
20213 				mixin(genNames!(dimX, dimY));
20214 			else
20215 				mixin(genNames!dim);
20216 
20217 			private void glUniform(OpenGlShader.Uniform assignTo) {
20218 				glUniform(assignTo.id);
20219 			}
20220 			private void glUniform(int assignTo) {
20221 				static if(name[4] == 'x') {
20222 					static if(name[3] == name[5]) {
20223 						// import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY);
20224 						mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]);
20225 					} else {
20226 						mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr);
20227 					}
20228 				} else
20229 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
20230 			}
20231 		}
20232 	}
20233 
20234 	auto vec(T...)(T members) {
20235 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
20236 	}
20237 }
20238 
20239 void checkGlError() {
20240 	auto error = glGetError();
20241 	int[] errors;
20242 	string[] errorStrings;
20243 	while(error != GL_NO_ERROR) {
20244 		errors ~= error;
20245 		switch(error) {
20246 			case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break;
20247 			case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break;
20248 			case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break;
20249 			case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break;
20250 			case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break;
20251 			case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break;
20252 			default: errorStrings ~= "idk";
20253 		}
20254 		error = glGetError();
20255 	}
20256 	if(errors.length)
20257 		throw ArsdException!"glGetError"(errors, errorStrings);
20258 }
20259 
20260 /++
20261 	A matrix for simple uses that easily integrates with [OpenGlShader].
20262 
20263 	Might not be useful to you since it only as some simple functions and
20264 	probably isn't that fast.
20265 
20266 	Note it uses an inline static array for its storage, so copying it
20267 	may be expensive.
20268 +/
20269 struct BasicMatrix(int columns, int rows, T = float) {
20270 	static import core.stdc.math;
20271 	static if(is(T == float)) {
20272 		alias cos = core.stdc.math.cosf;
20273 		alias sin = core.stdc.math.sinf;
20274 	} else {
20275 		alias cos = core.stdc.math.cos;
20276 		alias sin = core.stdc.math.sin;
20277 	}
20278 
20279 	T[columns * rows] data = 0.0;
20280 
20281 	/++
20282 
20283 	+/
20284 	this(T[columns * rows] data) {
20285 		this.data = data;
20286 	}
20287 
20288 	/++
20289 		Basic operations that operate *in place*.
20290 	+/
20291 	static if(columns == 4 && rows == 4)
20292 	void translate(T x, T y, T z) {
20293 		BasicMatrix m = [
20294 			1, 0, 0, x,
20295 			0, 1, 0, y,
20296 			0, 0, 1, z,
20297 			0, 0, 0, 1
20298 		];
20299 
20300 		this *= m;
20301 	}
20302 
20303 	/// ditto
20304 	static if(columns == 4 && rows == 4)
20305 	void scale(T x, T y, T z) {
20306 		BasicMatrix m = [
20307 			x, 0, 0, 0,
20308 			0, y, 0, 0,
20309 			0, 0, z, 0,
20310 			0, 0, 0, 1
20311 		];
20312 
20313 		this *= m;
20314 	}
20315 
20316 	/// ditto
20317 	static if(columns == 4 && rows == 4)
20318 	void rotateX(T theta) {
20319 		BasicMatrix m = [
20320 			1,          0,           0, 0,
20321 			0, cos(theta), -sin(theta), 0,
20322 			0, sin(theta),  cos(theta), 0,
20323 			0,          0,           0, 1
20324 		];
20325 
20326 		this *= m;
20327 	}
20328 
20329 	/// ditto
20330 	static if(columns == 4 && rows == 4)
20331 	void rotateY(T theta) {
20332 		BasicMatrix m = [
20333 			 cos(theta), 0,  sin(theta), 0,
20334 			          0, 1,           0, 0,
20335 			-sin(theta), 0,  cos(theta), 0,
20336 			          0, 0,           0, 1
20337 		];
20338 
20339 		this *= m;
20340 	}
20341 
20342 	/// ditto
20343 	static if(columns == 4 && rows == 4)
20344 	void rotateZ(T theta) {
20345 		BasicMatrix m = [
20346 			cos(theta), -sin(theta), 0, 0,
20347 			sin(theta),  cos(theta), 0, 0,
20348 			         0,           0, 1, 0,
20349 				 0,           0, 0, 1
20350 		];
20351 
20352 		this *= m;
20353 	}
20354 
20355 	/++
20356 
20357 	+/
20358 	static if(columns == rows)
20359 	static BasicMatrix identity() {
20360 		BasicMatrix m;
20361 		foreach(i; 0 .. columns)
20362 			m.data[0 + i + i * columns] = 1.0;
20363 		return m;
20364 	}
20365 
20366 	static if(columns == rows)
20367 	void loadIdentity() {
20368 		this = identity();
20369 	}
20370 
20371 	static if(columns == 4 && rows == 4)
20372 	static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) {
20373 		return BasicMatrix([
20374 			2/(r-l),       0,        0, -(r+l)/(r-l),
20375 			      0, 2/(t-b),        0, -(t+b)/(t-b),
20376 			      0,       0, -2/(f-n), -(f+n)/(f-n),
20377 			      0,       0,        0,            1
20378 		]);
20379 	}
20380 
20381 	static if(columns == 4 && rows == 4)
20382 	void loadOrtho(T l, T r, T b, T t, T n, T f) {
20383 		this = ortho(l, r, b, t, n, f);
20384 	}
20385 
20386 	void opOpAssign(string op : "+")(const BasicMatrix rhs) {
20387 		this.data[] += rhs.data;
20388 	}
20389 	void opOpAssign(string op : "-")(const BasicMatrix rhs) {
20390 		this.data[] -= rhs.data;
20391 	}
20392 	void opOpAssign(string op : "*")(const T rhs) {
20393 		this.data[] *= rhs;
20394 	}
20395 	void opOpAssign(string op : "/")(const T rhs) {
20396 		this.data[] /= rhs;
20397 	}
20398 	void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) {
20399 		static assert(columns == rhsRows);
20400 		auto multiplySize = columns;
20401 
20402 		auto tmp = this.data; // copy cuz it is a value type
20403 
20404 		int idx = 0;
20405 		foreach(r; 0 .. rows)
20406 		foreach(c; 0 .. columns) {
20407 			T sum = 0.0;
20408 
20409 			foreach(i; 0 .. multiplySize)
20410 				sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c];
20411 
20412 			tmp[idx++] = sum;
20413 		}
20414 
20415 		this.data = tmp;
20416 	}
20417 }
20418 
20419 unittest {
20420 	auto m = BasicMatrix!(2, 2)([
20421 		1, 2,
20422 		3, 4
20423 	]);
20424 
20425 	auto m2 = BasicMatrix!(2, 2)([
20426 		5, 6,
20427 		7, 8
20428 	]);
20429 
20430 	//import std.conv;
20431 	m *= m2;
20432 	assert(m.data == [
20433 		19, 22,
20434 		43, 50
20435 	]);//, to!string(m.data));
20436 }
20437 
20438 
20439 
20440 class GlObjectBase {
20441 	protected uint _vao;
20442 	protected uint _elementsCount;
20443 
20444 	protected uint element_buffer;
20445 
20446 	void gen() {
20447 		glGenVertexArrays(1, &_vao);
20448 	}
20449 
20450 	void bind() {
20451 		glBindVertexArray(_vao);
20452 	}
20453 
20454 	void dispose() {
20455 		glDeleteVertexArrays(1, &_vao);
20456 	}
20457 
20458 	void draw() {
20459 		bind();
20460 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20461 		glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null);
20462 	}
20463 }
20464 
20465 /++
20466 
20467 +/
20468 class GlObject(T) : GlObjectBase {
20469 	protected uint VBO;
20470 
20471 	this(T[] arr, uint[] indices) {
20472 		gen();
20473 		bind();
20474 
20475 		glGenBuffers(1, &VBO);
20476 		glGenBuffers(1, &element_buffer);
20477 
20478 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
20479 		glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW);
20480 
20481 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20482 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
20483 		_elementsCount = cast(int) indices.length;
20484 
20485 		foreach(int idx, memberName; __traits(allMembers, T)) {
20486 			static if(memberName != "__ctor") {
20487 			static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) {
20488 				glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof);
20489 				glEnableVertexAttribArray(idx);
20490 			} else static assert(0); }
20491 		}
20492 	}
20493 
20494 	static string generateShaderDefinitions() {
20495 		string code;
20496 
20497 		foreach(idx, memberName; __traits(allMembers, T)) {
20498 			// never use stringof ladies and gents it has a LU thing at the end of it
20499 			static if(memberName != "__ctor")
20500 			code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n";
20501 		}
20502 
20503 		return code;
20504 	}
20505 }
20506 
20507 private string typeToGl(T)() {
20508 	static if(is(T == float[4]))
20509 		return "vec4";
20510 	else static if(is(T == float[3]))
20511 		return "vec3";
20512 	else static if(is(T == float[2]))
20513 		return "vec2";
20514 	else static assert(0, T.stringof);
20515 }
20516 
20517 
20518 }
20519 
20520 version(Emscripten) {
20521 
20522 } else version(linux) {
20523 	version(with_eventloop) {} else {
20524 		private int epollFd = -1;
20525 		void prepareEventLoop() {
20526 			if(epollFd != -1)
20527 				return; // already initialized, no need to do it again
20528 			import ep = core.sys.linux.epoll;
20529 
20530 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
20531 			if(epollFd == -1)
20532 				throw new Exception("epoll create failure");
20533 		}
20534 	}
20535 } else version(Posix) {
20536 	void prepareEventLoop() {}
20537 }
20538 
20539 version(X11) {
20540 	import core.stdc.locale : LC_ALL; // rdmd fix
20541 	__gshared bool sdx_isUTF8Locale;
20542 
20543 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
20544 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
20545 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
20546 	// anal magic is here. I (Ketmar) hope you like it.
20547 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
20548 	// always return correct unicode symbols. The detection is here 'cause user can change locale
20549 	// later.
20550 
20551 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
20552 	shared static this () @system {
20553 		if(!librariesSuccessfullyLoaded)
20554 			return;
20555 
20556 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
20557 
20558 		// this doesn't hurt; it may add some locking, but the speed is still
20559 		// allows doing 60 FPS videogames; also, ignore the result, as most
20560 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
20561 		// never seen this failing).
20562 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
20563 
20564 		setlocale(LC_ALL, "");
20565 		// check if out locale is UTF-8
20566 		auto lct = setlocale(LC_CTYPE, null);
20567 		if (lct is null) {
20568 			sdx_isUTF8Locale = false;
20569 		} else {
20570 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
20571 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
20572 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
20573 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
20574 				{
20575 					sdx_isUTF8Locale = true;
20576 					break;
20577 				}
20578 			}
20579 		}
20580 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
20581 	}
20582 }
20583 
20584 class ExperimentalTextComponent2 {
20585 	/+
20586 		Stage 1: get it working monospace
20587 		Stage 2: use proportional font
20588 		Stage 3: allow changes in inline style
20589 		Stage 4: allow new fonts and sizes in the middle
20590 		Stage 5: optimize gap buffer
20591 		Stage 6: optimize layout
20592 		Stage 7: word wrap
20593 		Stage 8: justification
20594 		Stage 9: editing, selection, etc.
20595 
20596 			Operations:
20597 				insert text
20598 				overstrike text
20599 				select
20600 				cut
20601 				modify
20602 	+/
20603 
20604 	/++
20605 		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.
20606 	+/
20607 	this(SimpleWindow window) {
20608 		this.window = window;
20609 	}
20610 
20611 	private SimpleWindow window;
20612 
20613 
20614 	/++
20615 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
20616 		representing the internal parts. The first pass is focused on the x parameter, then the
20617 		renderer is responsible for going back to the parts in the current line and calling
20618 		adjustDownForAscent to change the y params.
20619 	+/
20620 	static interface ComponentRenderHelper {
20621 
20622 		/+
20623 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
20624 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
20625 			to move (adjust y to make room for new line) until you get back to the same position,
20626 			then you can stop - if one thing is unchanged, nothing after it is changed too.
20627 
20628 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
20629 			once you reach something that is unchanged, you can stop.
20630 		+/
20631 
20632 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
20633 
20634 		int ascent() const;
20635 		int descent() const;
20636 
20637 		int advance() const;
20638 
20639 		bool endsWithExplititLineBreak() const;
20640 	}
20641 
20642 	static interface RenderResult {
20643 		/++
20644 			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.
20645 		+/
20646 		void popFront();
20647 		@property bool empty() const;
20648 		@property ComponentRenderHelper front() const;
20649 
20650 		void repositionForNextLine(Point baseline, int availableWidth);
20651 	}
20652 
20653 	static interface ComponentInFlow {
20654 		void draw(ScreenPainter painter);
20655 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
20656 
20657 		bool startsWithExplicitLineBreak() const;
20658 	}
20659 
20660 	static class TextFlowComponent : ComponentInFlow {
20661 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
20662 
20663 		Color foreground;
20664 		Color background;
20665 
20666 		OperatingSystemFont font; // should NEVER be null
20667 
20668 		ubyte attributes; // underline, strike through, display on new block
20669 
20670 		version(Windows)
20671 			const(wchar)[] content;
20672 		else
20673 			const(char)[] content; // this should NEVER have a newline, except at the end
20674 
20675 		RenderedComponent[] rendered; // entirely controlled by [rerender]
20676 
20677 		// could prolly put some spacing around it too like margin / padding
20678 
20679 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
20680 			in { assert(font !is null);
20681 			     assert(!font.isNull); }
20682 			do
20683 		{
20684 			this.foreground = f;
20685 			this.background = b;
20686 			this.font = font;
20687 
20688 			this.attributes = attr;
20689 			version(Windows) {
20690 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
20691 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
20692 				auto buffer = new wchar[](sz);
20693 				this.content = makeWindowsString(c, buffer, conversionFlags);
20694 			} else {
20695 				this.content = c.dup;
20696 			}
20697 		}
20698 
20699 		void draw(ScreenPainter painter) {
20700 			painter.setFont(this.font);
20701 			painter.outlineColor = this.foreground;
20702 			painter.fillColor = Color.transparent;
20703 			foreach(rendered; this.rendered) {
20704 				// the component works in term of baseline,
20705 				// but the painter works in term of upper left bounding box
20706 				// so need to translate that
20707 
20708 				if(this.background.a) {
20709 					painter.fillColor = this.background;
20710 					painter.outlineColor = this.background;
20711 
20712 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
20713 
20714 					painter.outlineColor = this.foreground;
20715 					painter.fillColor = Color.transparent;
20716 				}
20717 
20718 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
20719 
20720 				// FIXME: strike through, underline, highlight selection, etc.
20721 			}
20722 		}
20723 	}
20724 
20725 	// I could split the parts into words on render
20726 	// for easier word-wrap, each one being an unbreakable "inline-block"
20727 	private TextFlowComponent[] parts;
20728 	private int needsRerenderFrom;
20729 
20730 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
20731 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
20732 		parts ~= new TextFlowComponent(f, b, font, attr, c);
20733 	}
20734 
20735 	static struct RenderedComponent {
20736 		int startX;
20737 		int startY;
20738 		short width;
20739 		// 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!
20740 		// for individual chars in here you've gotta process on demand
20741 		version(Windows)
20742 			const(wchar)[] slice;
20743 		else
20744 			const(char)[] slice;
20745 	}
20746 
20747 
20748 	void rerender(Rectangle boundingBox) {
20749 		Point baseline = boundingBox.upperLeft;
20750 
20751 		this.boundingBox.left = boundingBox.left;
20752 		this.boundingBox.top = boundingBox.top;
20753 
20754 		auto remainingParts = parts;
20755 
20756 		int largestX;
20757 
20758 
20759 		foreach(part; parts)
20760 			part.font.prepareContext(window);
20761 		scope(exit)
20762 		foreach(part; parts)
20763 			part.font.releaseContext();
20764 
20765 		calculateNextLine:
20766 
20767 		int nextLineHeight = 0;
20768 		int nextBiggestDescent = 0;
20769 
20770 		foreach(part; remainingParts) {
20771 			auto height = part.font.ascent;
20772 			if(height > nextLineHeight)
20773 				nextLineHeight = height;
20774 			if(part.font.descent > nextBiggestDescent)
20775 				nextBiggestDescent = part.font.descent;
20776 			if(part.content.length && part.content[$-1] == '\n')
20777 				break;
20778 		}
20779 
20780 		baseline.y += nextLineHeight;
20781 		auto lineStart = baseline;
20782 
20783 		while(remainingParts.length) {
20784 			remainingParts[0].rendered = null;
20785 
20786 			bool eol;
20787 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
20788 				eol = true;
20789 
20790 			// FIXME: word wrap
20791 			auto font = remainingParts[0].font;
20792 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
20793 			auto width = font.stringWidth(slice, window);
20794 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
20795 
20796 			remainingParts = remainingParts[1 .. $];
20797 			baseline.x += width;
20798 
20799 			if(eol) {
20800 				baseline.y += nextBiggestDescent;
20801 				if(baseline.x > largestX)
20802 					largestX = baseline.x;
20803 				baseline.x = lineStart.x;
20804 				goto calculateNextLine;
20805 			}
20806 		}
20807 
20808 		if(baseline.x > largestX)
20809 			largestX = baseline.x;
20810 
20811 		this.boundingBox.right = largestX;
20812 		this.boundingBox.bottom = baseline.y;
20813 	}
20814 
20815 	// you must call rerender first!
20816 	void draw(ScreenPainter painter) {
20817 		foreach(part; parts) {
20818 			part.draw(painter);
20819 		}
20820 	}
20821 
20822 	struct IdentifyResult {
20823 		TextFlowComponent part;
20824 		int charIndexInPart;
20825 		int totalCharIndex = -1; // if this is -1, it just means the end
20826 
20827 		Rectangle boundingBox;
20828 	}
20829 
20830 	IdentifyResult identify(Point pt, bool exact = false) {
20831 		if(parts.length == 0)
20832 			return IdentifyResult(null, 0);
20833 
20834 		if(pt.y < boundingBox.top) {
20835 			if(exact)
20836 				return IdentifyResult(null, 1);
20837 			return IdentifyResult(parts[0], 0);
20838 		}
20839 		if(pt.y > boundingBox.bottom) {
20840 			if(exact)
20841 				return IdentifyResult(null, 2);
20842 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
20843 		}
20844 
20845 		int tci = 0;
20846 
20847 		// I should probably like binary search this or something...
20848 		foreach(ref part; parts) {
20849 			foreach(rendered; part.rendered) {
20850 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
20851 				if(rect.contains(pt)) {
20852 					auto x = pt.x - rendered.startX;
20853 					auto estimatedIdx = x / part.font.averageWidth;
20854 
20855 					if(estimatedIdx < 0)
20856 						estimatedIdx = 0;
20857 
20858 					if(estimatedIdx > rendered.slice.length)
20859 						estimatedIdx = cast(int) rendered.slice.length;
20860 
20861 					int idx;
20862 					int x1, x2;
20863 					if(part.font.isMonospace) {
20864 						auto w = part.font.averageWidth;
20865 						if(!exact && x > (estimatedIdx + 1) * w)
20866 							return IdentifyResult(null, 4);
20867 						idx = estimatedIdx;
20868 						x1 = idx * w;
20869 						x2 = (idx + 1) * w;
20870 					} else {
20871 						idx = estimatedIdx;
20872 
20873 						part.font.prepareContext(window);
20874 						scope(exit) part.font.releaseContext();
20875 
20876 						// int iterations;
20877 
20878 						while(true) {
20879 							// iterations++;
20880 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
20881 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
20882 
20883 							x1 += rendered.startX;
20884 							x2 += rendered.startX;
20885 
20886 							if(pt.x < x1) {
20887 								if(idx == 0) {
20888 									if(exact)
20889 										return IdentifyResult(null, 6);
20890 									else
20891 										break;
20892 								}
20893 								idx--;
20894 							} else if(pt.x > x2) {
20895 								idx++;
20896 								if(idx > rendered.slice.length) {
20897 									if(exact)
20898 										return IdentifyResult(null, 5);
20899 									else
20900 										break;
20901 								}
20902 							} else if(pt.x >= x1 && pt.x <= x2) {
20903 								if(idx)
20904 									idx--; // point it at the original index
20905 								break; // we fit
20906 							}
20907 						}
20908 
20909 						// writeln(iterations)
20910 					}
20911 
20912 
20913 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
20914 				}
20915 			}
20916 			tci += cast(int) part.content.length; // FIXME: utf-8?
20917 		}
20918 		return IdentifyResult(null, 3);
20919 	}
20920 
20921 	Rectangle boundingBox; // only set after [rerender]
20922 
20923 	// text will be positioned around the exclusion zone
20924 	static struct ExclusionZone {
20925 
20926 	}
20927 
20928 	ExclusionZone[] exclusionZones;
20929 }
20930 
20931 
20932 // Don't use this yet. When I'm happy with it, I will move it to the
20933 // regular module namespace.
20934 mixin template ExperimentalTextComponent() {
20935 
20936 static:
20937 
20938 	alias Rectangle = arsd.color.Rectangle;
20939 
20940 	struct ForegroundColor {
20941 		Color color;
20942 		alias color this;
20943 
20944 		this(Color c) {
20945 			color = c;
20946 		}
20947 
20948 		this(int r, int g, int b, int a = 255) {
20949 			color = Color(r, g, b, a);
20950 		}
20951 
20952 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
20953 			return ForegroundColor(mixin("Color." ~ s));
20954 		}
20955 	}
20956 
20957 	struct BackgroundColor {
20958 		Color color;
20959 		alias color this;
20960 
20961 		this(Color c) {
20962 			color = c;
20963 		}
20964 
20965 		this(int r, int g, int b, int a = 255) {
20966 			color = Color(r, g, b, a);
20967 		}
20968 
20969 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
20970 			return BackgroundColor(mixin("Color." ~ s));
20971 		}
20972 	}
20973 
20974 	static class InlineElement {
20975 		string text;
20976 
20977 		BlockElement containingBlock;
20978 
20979 		Color color = Color.black;
20980 		Color backgroundColor = Color.transparent;
20981 		ushort styles;
20982 
20983 		string font;
20984 		int fontSize;
20985 
20986 		int lineHeight;
20987 
20988 		void* identifier;
20989 
20990 		Rectangle boundingBox;
20991 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
20992 
20993 		bool isMergeCompatible(InlineElement other) {
20994 			return
20995 				containingBlock is other.containingBlock &&
20996 				color == other.color &&
20997 				backgroundColor == other.backgroundColor &&
20998 				styles == other.styles &&
20999 				font == other.font &&
21000 				fontSize == other.fontSize &&
21001 				lineHeight == other.lineHeight &&
21002 				true;
21003 		}
21004 
21005 		int xOfIndex(size_t index) {
21006 			if(index < letterXs.length)
21007 				return letterXs[index];
21008 			else
21009 				return boundingBox.right;
21010 		}
21011 
21012 		InlineElement clone() {
21013 			auto ie = new InlineElement();
21014 			ie.tupleof = this.tupleof;
21015 			return ie;
21016 		}
21017 
21018 		InlineElement getPreviousInlineElement() {
21019 			InlineElement prev = null;
21020 			foreach(ie; this.containingBlock.parts) {
21021 				if(ie is this)
21022 					break;
21023 				prev = ie;
21024 			}
21025 			if(prev is null) {
21026 				BlockElement pb;
21027 				BlockElement cb = this.containingBlock;
21028 				moar:
21029 				foreach(ie; this.containingBlock.containingLayout.blocks) {
21030 					if(ie is cb)
21031 						break;
21032 					pb = ie;
21033 				}
21034 				if(pb is null)
21035 					return null;
21036 				if(pb.parts.length == 0) {
21037 					cb = pb;
21038 					goto moar;
21039 				}
21040 
21041 				prev = pb.parts[$-1];
21042 
21043 			}
21044 			return prev;
21045 		}
21046 
21047 		InlineElement getNextInlineElement() {
21048 			InlineElement next = null;
21049 			foreach(idx, ie; this.containingBlock.parts) {
21050 				if(ie is this) {
21051 					if(idx + 1 < this.containingBlock.parts.length)
21052 						next = this.containingBlock.parts[idx + 1];
21053 					break;
21054 				}
21055 			}
21056 			if(next is null) {
21057 				BlockElement n;
21058 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
21059 					if(ie is this.containingBlock) {
21060 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
21061 							n = this.containingBlock.containingLayout.blocks[idx + 1];
21062 						break;
21063 					}
21064 				}
21065 				if(n is null)
21066 					return null;
21067 
21068 				if(n.parts.length)
21069 					next = n.parts[0];
21070 				else {} // FIXME
21071 
21072 			}
21073 			return next;
21074 		}
21075 
21076 	}
21077 
21078 	// Block elements are used entirely for positioning inline elements,
21079 	// which are the things that are actually drawn.
21080 	class BlockElement {
21081 		InlineElement[] parts;
21082 		uint alignment;
21083 
21084 		int whiteSpace; // pre, pre-wrap, wrap
21085 
21086 		TextLayout containingLayout;
21087 
21088 		// inputs
21089 		Point where;
21090 		Size minimumSize;
21091 		Size maximumSize;
21092 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
21093 		void* identifier;
21094 
21095 		Rectangle margin;
21096 		Rectangle padding;
21097 
21098 		// outputs
21099 		Rectangle[] boundingBoxes;
21100 	}
21101 
21102 	struct TextIdentifyResult {
21103 		InlineElement element;
21104 		int offset;
21105 
21106 		private TextIdentifyResult fixupNewline() {
21107 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
21108 				offset--;
21109 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
21110 				offset--;
21111 			}
21112 			return this;
21113 		}
21114 	}
21115 
21116 	class TextLayout {
21117 		BlockElement[] blocks;
21118 		Rectangle boundingBox_;
21119 		Rectangle boundingBox() { return boundingBox_; }
21120 		void boundingBox(Rectangle r) {
21121 			if(r != boundingBox_) {
21122 				boundingBox_ = r;
21123 				layoutInvalidated = true;
21124 			}
21125 		}
21126 
21127 		Rectangle contentBoundingBox() {
21128 			Rectangle r;
21129 			foreach(block; blocks)
21130 			foreach(ie; block.parts) {
21131 				if(ie.boundingBox.right > r.right)
21132 					r.right = ie.boundingBox.right;
21133 				if(ie.boundingBox.bottom > r.bottom)
21134 					r.bottom = ie.boundingBox.bottom;
21135 			}
21136 			return r;
21137 		}
21138 
21139 		BlockElement[] getBlocks() {
21140 			return blocks;
21141 		}
21142 
21143 		InlineElement[] getTexts() {
21144 			InlineElement[] elements;
21145 			foreach(block; blocks)
21146 				elements ~= block.parts;
21147 			return elements;
21148 		}
21149 
21150 		string getPlainText() {
21151 			string text;
21152 			foreach(block; blocks)
21153 				foreach(part; block.parts)
21154 					text ~= part.text;
21155 			return text;
21156 		}
21157 
21158 		string getHtml() {
21159 			return null; // FIXME
21160 		}
21161 
21162 		this(Rectangle boundingBox) {
21163 			this.boundingBox = boundingBox;
21164 		}
21165 
21166 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
21167 			auto be = new BlockElement();
21168 			be.containingLayout = this;
21169 			if(after is null)
21170 				blocks ~= be;
21171 			else {
21172 				foreach(idx, b; blocks) {
21173 					if(b is after.containingBlock) {
21174 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
21175 						break;
21176 					}
21177 				}
21178 			}
21179 			return be;
21180 		}
21181 
21182 		void clear() {
21183 			blocks = null;
21184 			selectionStart = selectionEnd = caret = Caret.init;
21185 		}
21186 
21187 		void addText(Args...)(Args args) {
21188 			if(blocks.length == 0)
21189 				addBlock();
21190 
21191 			InlineElement ie = new InlineElement();
21192 			foreach(idx, arg; args) {
21193 				static if(is(typeof(arg) == ForegroundColor))
21194 					ie.color = arg;
21195 				else static if(is(typeof(arg) == TextFormat)) {
21196 					if(arg & 0x8000) // ~TextFormat.something turns it off
21197 						ie.styles &= arg;
21198 					else
21199 						ie.styles |= arg;
21200 				} else static if(is(typeof(arg) == string)) {
21201 					static if(idx == 0 && args.length > 1)
21202 						static assert(0, "Put styles before the string.");
21203 					size_t lastLineIndex;
21204 					foreach(cidx, char a; arg) {
21205 						if(a == '\n') {
21206 							ie.text = arg[lastLineIndex .. cidx + 1];
21207 							lastLineIndex = cidx + 1;
21208 							ie.containingBlock = blocks[$-1];
21209 							blocks[$-1].parts ~= ie.clone;
21210 							ie.text = null;
21211 						} else {
21212 
21213 						}
21214 					}
21215 
21216 					ie.text = arg[lastLineIndex .. $];
21217 					ie.containingBlock = blocks[$-1];
21218 					blocks[$-1].parts ~= ie.clone;
21219 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
21220 				}
21221 			}
21222 
21223 			invalidateLayout();
21224 		}
21225 
21226 		void tryMerge(InlineElement into, InlineElement what) {
21227 			if(!into.isMergeCompatible(what)) {
21228 				return; // cannot merge, different configs
21229 			}
21230 
21231 			// cool, can merge, bring text together...
21232 			into.text ~= what.text;
21233 
21234 			// and remove what
21235 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
21236 				if(what.containingBlock.parts[a] is what) {
21237 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
21238 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
21239 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
21240 
21241 				}
21242 			}
21243 
21244 			// FIXME: ensure no other carets have a reference to it
21245 		}
21246 
21247 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
21248 		TextIdentifyResult identify(int x, int y, bool exact = false) {
21249 			TextIdentifyResult inexactMatch;
21250 			foreach(block; blocks) {
21251 				foreach(part; block.parts) {
21252 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
21253 
21254 						// FIXME binary search
21255 						int tidx;
21256 						int lastX;
21257 						foreach_reverse(idxo, lx; part.letterXs) {
21258 							int idx = cast(int) idxo;
21259 							if(lx <= x) {
21260 								if(lastX && lastX - x < x - lx)
21261 									tidx = idx + 1;
21262 								else
21263 									tidx = idx;
21264 								break;
21265 							}
21266 							lastX = lx;
21267 						}
21268 
21269 						return TextIdentifyResult(part, tidx).fixupNewline;
21270 					} else if(!exact) {
21271 						// we're not in the box, but are we on the same line?
21272 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
21273 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
21274 					}
21275 				}
21276 			}
21277 
21278 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
21279 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
21280 
21281 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
21282 		}
21283 
21284 		void moveCaretToPixelCoordinates(int x, int y) {
21285 			auto result = identify(x, y);
21286 			caret.inlineElement = result.element;
21287 			caret.offset = result.offset;
21288 		}
21289 
21290 		void selectToPixelCoordinates(int x, int y) {
21291 			auto result = identify(x, y);
21292 
21293 			if(y < caretLastDrawnY1) {
21294 				// on a previous line, carat is selectionEnd
21295 				selectionEnd = caret;
21296 
21297 				selectionStart = Caret(this, result.element, result.offset);
21298 			} else if(y > caretLastDrawnY2) {
21299 				// on a later line
21300 				selectionStart = caret;
21301 
21302 				selectionEnd = Caret(this, result.element, result.offset);
21303 			} else {
21304 				// on the same line...
21305 				if(x <= caretLastDrawnX) {
21306 					selectionEnd = caret;
21307 					selectionStart = Caret(this, result.element, result.offset);
21308 				} else {
21309 					selectionStart = caret;
21310 					selectionEnd = Caret(this, result.element, result.offset);
21311 				}
21312 
21313 			}
21314 		}
21315 
21316 
21317 		/// Call this if the inputs change. It will reflow everything
21318 		void redoLayout(ScreenPainter painter) {
21319 			//painter.setClipRectangle(boundingBox);
21320 			auto pos = Point(boundingBox.left, boundingBox.top);
21321 
21322 			int lastHeight;
21323 			void nl() {
21324 				pos.x = boundingBox.left;
21325 				pos.y += lastHeight;
21326 			}
21327 			foreach(block; blocks) {
21328 				nl();
21329 				foreach(part; block.parts) {
21330 					part.letterXs = null;
21331 
21332 					auto size = painter.textSize(part.text);
21333 					version(Windows)
21334 						if(part.text.length && part.text[$-1] == '\n')
21335 							size.height /= 2; // windows counts the new line at the end, but we don't want that
21336 
21337 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
21338 
21339 					foreach(idx, char c; part.text) {
21340 							// FIXME: unicode
21341 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
21342 					}
21343 
21344 					pos.x += size.width;
21345 					if(pos.x >= boundingBox.right) {
21346 						pos.y += size.height;
21347 						pos.x = boundingBox.left;
21348 						lastHeight = 0;
21349 					} else {
21350 						lastHeight = size.height;
21351 					}
21352 
21353 					if(part.text.length && part.text[$-1] == '\n')
21354 						nl();
21355 				}
21356 			}
21357 
21358 			layoutInvalidated = false;
21359 		}
21360 
21361 		bool layoutInvalidated = true;
21362 		void invalidateLayout() {
21363 			layoutInvalidated = true;
21364 		}
21365 
21366 // FIXME: caret can remain sometimes when inserting
21367 // FIXME: inserting at the beginning once you already have something can eff it up.
21368 		void drawInto(ScreenPainter painter, bool focused = false) {
21369 			if(layoutInvalidated)
21370 				redoLayout(painter);
21371 			foreach(block; blocks) {
21372 				foreach(part; block.parts) {
21373 					painter.outlineColor = part.color;
21374 					painter.fillColor = part.backgroundColor;
21375 
21376 					auto pos = part.boundingBox.upperLeft;
21377 					auto size = part.boundingBox.size;
21378 
21379 					painter.drawText(pos, part.text);
21380 					if(part.styles & TextFormat.underline)
21381 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
21382 					if(part.styles & TextFormat.strikethrough)
21383 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
21384 				}
21385 			}
21386 
21387 			// on every redraw, I will force the caret to be
21388 			// redrawn too, in order to eliminate perceived lag
21389 			// when moving around with the mouse.
21390 			eraseCaret(painter);
21391 
21392 			if(focused) {
21393 				highlightSelection(painter);
21394 				drawCaret(painter);
21395 			}
21396 		}
21397 
21398 		Color selectionXorColor = Color(255, 255, 127);
21399 
21400 		void highlightSelection(ScreenPainter painter) {
21401 			if(selectionStart is selectionEnd)
21402 				return; // no selection
21403 
21404 			if(selectionStart.inlineElement is null) return;
21405 			if(selectionEnd.inlineElement is null) return;
21406 
21407 			assert(selectionStart.inlineElement !is null);
21408 			assert(selectionEnd.inlineElement !is null);
21409 
21410 			painter.rasterOp = RasterOp.xor;
21411 			painter.outlineColor = Color.transparent;
21412 			painter.fillColor = selectionXorColor;
21413 
21414 			auto at = selectionStart.inlineElement;
21415 			auto atOffset = selectionStart.offset;
21416 			bool done;
21417 			while(at) {
21418 				auto box = at.boundingBox;
21419 				if(atOffset < at.letterXs.length)
21420 					box.left = at.letterXs[atOffset];
21421 
21422 				if(at is selectionEnd.inlineElement) {
21423 					if(selectionEnd.offset < at.letterXs.length)
21424 						box.right = at.letterXs[selectionEnd.offset];
21425 					done = true;
21426 				}
21427 
21428 				painter.drawRectangle(box.upperLeft, box.width, box.height);
21429 
21430 				if(done)
21431 					break;
21432 
21433 				at = at.getNextInlineElement();
21434 				atOffset = 0;
21435 			}
21436 		}
21437 
21438 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
21439 		bool caretShowingOnScreen = false;
21440 		void drawCaret(ScreenPainter painter) {
21441 			//painter.setClipRectangle(boundingBox);
21442 			int x, y1, y2;
21443 			if(caret.inlineElement is null) {
21444 				x = boundingBox.left;
21445 				y1 = boundingBox.top + 2;
21446 				y2 = boundingBox.top + painter.fontHeight;
21447 			} else {
21448 				x = caret.inlineElement.xOfIndex(caret.offset);
21449 				y1 = caret.inlineElement.boundingBox.top + 2;
21450 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21451 			}
21452 
21453 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
21454 				eraseCaret(painter);
21455 
21456 			painter.pen = Pen(Color.white, 1);
21457 			painter.rasterOp = RasterOp.xor;
21458 			painter.drawLine(
21459 				Point(x, y1),
21460 				Point(x, y2)
21461 			);
21462 			painter.rasterOp = RasterOp.normal;
21463 			caretShowingOnScreen = !caretShowingOnScreen;
21464 
21465 			if(caretShowingOnScreen) {
21466 				caretLastDrawnX = x;
21467 				caretLastDrawnY1 = y1;
21468 				caretLastDrawnY2 = y2;
21469 			}
21470 		}
21471 
21472 		Rectangle caretBoundingBox() {
21473 			int x, y1, y2;
21474 			if(caret.inlineElement is null) {
21475 				x = boundingBox.left;
21476 				y1 = boundingBox.top + 2;
21477 				y2 = boundingBox.top + 16;
21478 			} else {
21479 				x = caret.inlineElement.xOfIndex(caret.offset);
21480 				y1 = caret.inlineElement.boundingBox.top + 2;
21481 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21482 			}
21483 
21484 			return Rectangle(x, y1, x + 1, y2);
21485 		}
21486 
21487 		void eraseCaret(ScreenPainter painter) {
21488 			//painter.setClipRectangle(boundingBox);
21489 			if(!caretShowingOnScreen) return;
21490 			painter.pen = Pen(Color.white, 1);
21491 			painter.rasterOp = RasterOp.xor;
21492 			painter.drawLine(
21493 				Point(caretLastDrawnX, caretLastDrawnY1),
21494 				Point(caretLastDrawnX, caretLastDrawnY2)
21495 			);
21496 
21497 			caretShowingOnScreen = false;
21498 			painter.rasterOp = RasterOp.normal;
21499 		}
21500 
21501 		/// Caret movement api
21502 		/// These should give the user a logical result based on what they see on screen...
21503 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
21504 		void moveUp() {
21505 			if(caret.inlineElement is null) return;
21506 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21507 			auto y = caret.inlineElement.boundingBox.top + 2;
21508 
21509 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21510 			if(y < 0)
21511 				return;
21512 
21513 			auto i = identify(x, y);
21514 
21515 			if(i.element) {
21516 				caret.inlineElement = i.element;
21517 				caret.offset = i.offset;
21518 			}
21519 		}
21520 		void moveDown() {
21521 			if(caret.inlineElement is null) return;
21522 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21523 			auto y = caret.inlineElement.boundingBox.bottom - 2;
21524 
21525 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21526 
21527 			auto i = identify(x, y);
21528 			if(i.element) {
21529 				caret.inlineElement = i.element;
21530 				caret.offset = i.offset;
21531 			}
21532 		}
21533 		void moveLeft() {
21534 			if(caret.inlineElement is null) return;
21535 			if(caret.offset)
21536 				caret.offset--;
21537 			else {
21538 				auto p = caret.inlineElement.getPreviousInlineElement();
21539 				if(p) {
21540 					caret.inlineElement = p;
21541 					if(p.text.length && p.text[$-1] == '\n')
21542 						caret.offset = cast(int) p.text.length - 1;
21543 					else
21544 						caret.offset = cast(int) p.text.length;
21545 				}
21546 			}
21547 		}
21548 		void moveRight() {
21549 			if(caret.inlineElement is null) return;
21550 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
21551 				caret.offset++;
21552 			} else {
21553 				auto p = caret.inlineElement.getNextInlineElement();
21554 				if(p) {
21555 					caret.inlineElement = p;
21556 					caret.offset = 0;
21557 				}
21558 			}
21559 		}
21560 		void moveHome() {
21561 			if(caret.inlineElement is null) return;
21562 			auto x = 0;
21563 			auto y = caret.inlineElement.boundingBox.top + 2;
21564 
21565 			auto i = identify(x, y);
21566 
21567 			if(i.element) {
21568 				caret.inlineElement = i.element;
21569 				caret.offset = i.offset;
21570 			}
21571 		}
21572 		void moveEnd() {
21573 			if(caret.inlineElement is null) return;
21574 			auto x = int.max;
21575 			auto y = caret.inlineElement.boundingBox.top + 2;
21576 
21577 			auto i = identify(x, y);
21578 
21579 			if(i.element) {
21580 				caret.inlineElement = i.element;
21581 				caret.offset = i.offset;
21582 			}
21583 
21584 		}
21585 		void movePageUp(ref Caret caret) {}
21586 		void movePageDown(ref Caret caret) {}
21587 
21588 		void moveDocumentStart(ref Caret caret) {
21589 			if(blocks.length && blocks[0].parts.length)
21590 				caret = Caret(this, blocks[0].parts[0], 0);
21591 			else
21592 				caret = Caret.init;
21593 		}
21594 
21595 		void moveDocumentEnd(ref Caret caret) {
21596 			if(blocks.length) {
21597 				auto parts = blocks[$-1].parts;
21598 				if(parts.length) {
21599 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
21600 				} else {
21601 					caret = Caret.init;
21602 				}
21603 			} else
21604 				caret = Caret.init;
21605 		}
21606 
21607 		void deleteSelection() {
21608 			if(selectionStart is selectionEnd)
21609 				return;
21610 
21611 			if(selectionStart.inlineElement is null) return;
21612 			if(selectionEnd.inlineElement is null) return;
21613 
21614 			assert(selectionStart.inlineElement !is null);
21615 			assert(selectionEnd.inlineElement !is null);
21616 
21617 			auto at = selectionStart.inlineElement;
21618 
21619 			if(selectionEnd.inlineElement is at) {
21620 				// same element, need to chop out
21621 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
21622 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
21623 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
21624 			} else {
21625 				// different elements, we can do it with slicing
21626 				at.text = at.text[0 .. selectionStart.offset];
21627 				if(selectionStart.offset < at.letterXs.length)
21628 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
21629 
21630 				at = at.getNextInlineElement();
21631 
21632 				while(at) {
21633 					if(at is selectionEnd.inlineElement) {
21634 						at.text = at.text[selectionEnd.offset .. $];
21635 						if(selectionEnd.offset < at.letterXs.length)
21636 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
21637 						selectionEnd.offset = 0;
21638 						break;
21639 					} else {
21640 						auto cfd = at;
21641 						cfd.text = null; // delete the whole thing
21642 
21643 						at = at.getNextInlineElement();
21644 
21645 						if(cfd.text.length == 0) {
21646 							// and remove cfd
21647 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
21648 								if(cfd.containingBlock.parts[a] is cfd) {
21649 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
21650 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
21651 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
21652 
21653 								}
21654 							}
21655 						}
21656 					}
21657 				}
21658 			}
21659 
21660 			caret = selectionEnd;
21661 			selectNone();
21662 
21663 			invalidateLayout();
21664 
21665 		}
21666 
21667 		/// Plain text editing api. These work at the current caret inside the selected inline element.
21668 		void insert(in char[] text) {
21669 			foreach(dchar ch; text)
21670 				insert(ch);
21671 		}
21672 		/// ditto
21673 		void insert(dchar ch) {
21674 
21675 			bool selectionDeleted = false;
21676 			if(selectionStart !is selectionEnd) {
21677 				deleteSelection();
21678 				selectionDeleted = true;
21679 			}
21680 
21681 			if(ch == 127) {
21682 				delete_();
21683 				return;
21684 			}
21685 			if(ch == 8) {
21686 				if(!selectionDeleted)
21687 					backspace();
21688 				return;
21689 			}
21690 
21691 			invalidateLayout();
21692 
21693 			if(ch == 13) ch = 10;
21694 			auto e = caret.inlineElement;
21695 			if(e is null) {
21696 				addText("" ~ cast(char) ch) ; // FIXME
21697 				return;
21698 			}
21699 
21700 			if(caret.offset == e.text.length) {
21701 				e.text ~= cast(char) ch; // FIXME
21702 				caret.offset++;
21703 				if(ch == 10) {
21704 					auto c = caret.inlineElement.clone;
21705 					c.text = null;
21706 					c.letterXs = null;
21707 					insertPartAfter(c,e);
21708 					caret = Caret(this, c, 0);
21709 				}
21710 			} else {
21711 				// FIXME cast char sucks
21712 				if(ch == 10) {
21713 					auto c = caret.inlineElement.clone;
21714 					c.text = e.text[caret.offset .. $];
21715 					if(caret.offset < c.letterXs.length)
21716 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
21717 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
21718 					if(caret.offset <= e.letterXs.length) {
21719 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
21720 					}
21721 					insertPartAfter(c,e);
21722 					caret = Caret(this, c, 0);
21723 				} else {
21724 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
21725 					caret.offset++;
21726 				}
21727 			}
21728 		}
21729 
21730 		void insertPartAfter(InlineElement what, InlineElement where) {
21731 			foreach(idx, p; where.containingBlock.parts) {
21732 				if(p is where) {
21733 					if(idx + 1 == where.containingBlock.parts.length)
21734 						where.containingBlock.parts ~= what;
21735 					else
21736 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
21737 					return;
21738 				}
21739 			}
21740 		}
21741 
21742 		void cleanupStructures() {
21743 			for(size_t i = 0; i < blocks.length; i++) {
21744 				auto block = blocks[i];
21745 				for(size_t a = 0; a < block.parts.length; a++) {
21746 					auto part = block.parts[a];
21747 					if(part.text.length == 0) {
21748 						for(size_t b = a; b < block.parts.length - 1; b++)
21749 							block.parts[b] = block.parts[b+1];
21750 						block.parts = block.parts[0 .. $-1];
21751 					}
21752 				}
21753 				if(block.parts.length == 0) {
21754 					for(size_t a = i; a < blocks.length - 1; a++)
21755 						blocks[a] = blocks[a+1];
21756 					blocks = blocks[0 .. $-1];
21757 				}
21758 			}
21759 		}
21760 
21761 		void backspace() {
21762 			try_again:
21763 			auto e = caret.inlineElement;
21764 			if(e is null)
21765 				return;
21766 			if(caret.offset == 0) {
21767 				auto prev = e.getPreviousInlineElement();
21768 				if(prev is null)
21769 					return;
21770 				auto newOffset = cast(int) prev.text.length;
21771 				tryMerge(prev, e);
21772 				caret.inlineElement = prev;
21773 				caret.offset = prev is null ? 0 : newOffset;
21774 
21775 				goto try_again;
21776 			} else if(caret.offset == e.text.length) {
21777 				e.text = e.text[0 .. $-1];
21778 				caret.offset--;
21779 			} else {
21780 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
21781 				caret.offset--;
21782 			}
21783 			//cleanupStructures();
21784 
21785 			invalidateLayout();
21786 		}
21787 		void delete_() {
21788 			if(selectionStart !is selectionEnd)
21789 				deleteSelection();
21790 			else {
21791 				auto before = caret;
21792 				moveRight();
21793 				if(caret != before) {
21794 					backspace();
21795 				}
21796 			}
21797 
21798 			invalidateLayout();
21799 		}
21800 		void overstrike() {}
21801 
21802 		/// Selection API. See also: caret movement.
21803 		void selectAll() {
21804 			moveDocumentStart(selectionStart);
21805 			moveDocumentEnd(selectionEnd);
21806 		}
21807 		bool selectNone() {
21808 			if(selectionStart != selectionEnd) {
21809 				selectionStart = selectionEnd = Caret.init;
21810 				return true;
21811 			}
21812 			return false;
21813 		}
21814 
21815 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
21816 		/// They will modify the current selection if there is one and will splice one in if needed.
21817 		void changeAttributes() {}
21818 
21819 
21820 		/// Text search api. They manipulate the selection and/or caret.
21821 		void findText(string text) {}
21822 		void findIndex(size_t textIndex) {}
21823 
21824 		// sample event handlers
21825 
21826 		void handleEvent(KeyEvent event) {
21827 			//if(event.type == KeyEvent.Type.KeyPressed) {
21828 
21829 			//}
21830 		}
21831 
21832 		void handleEvent(dchar ch) {
21833 
21834 		}
21835 
21836 		void handleEvent(MouseEvent event) {
21837 
21838 		}
21839 
21840 		bool contentEditable; // can it be edited?
21841 		bool contentCaretable; // is there a caret/cursor that moves around in there?
21842 		bool contentSelectable; // selectable?
21843 
21844 		Caret caret;
21845 		Caret selectionStart;
21846 		Caret selectionEnd;
21847 
21848 		bool insertMode;
21849 	}
21850 
21851 	struct Caret {
21852 		TextLayout layout;
21853 		InlineElement inlineElement;
21854 		int offset;
21855 	}
21856 
21857 	enum TextFormat : ushort {
21858 		// decorations
21859 		underline = 1,
21860 		strikethrough = 2,
21861 
21862 		// font selectors
21863 
21864 		bold = 0x4000 | 1, // weight 700
21865 		light = 0x4000 | 2, // weight 300
21866 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
21867 		// bold | light is really invalid but should give weight 500
21868 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
21869 
21870 		italic = 0x4000 | 8,
21871 		smallcaps = 0x4000 | 16,
21872 	}
21873 
21874 	void* findFont(string family, int weight, TextFormat formats) {
21875 		return null;
21876 	}
21877 
21878 }
21879 
21880 /++
21881 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21882 
21883 	History:
21884 		Added February 19, 2021
21885 +/
21886 /// Group: drag_and_drop
21887 interface DropHandler {
21888 	/++
21889 		Called when the drag enters the handler's area.
21890 	+/
21891 	DragAndDropAction dragEnter(DropPackage*);
21892 	/++
21893 		Called when the drag leaves the handler's area or is
21894 		cancelled. You should free your resources when this is called.
21895 	+/
21896 	void dragLeave();
21897 	/++
21898 		Called continually as the drag moves over the handler's area.
21899 
21900 		Returns: feedback to the dragger
21901 	+/
21902 	DropParameters dragOver(Point pt);
21903 	/++
21904 		The user dropped the data and you should process it now. You can
21905 		access the data through the given [DropPackage].
21906 	+/
21907 	void drop(scope DropPackage*);
21908 	/++
21909 		Called when the drop is complete. You should free whatever temporary
21910 		resources you were using. It is often reasonable to simply forward
21911 		this call to [dragLeave].
21912 	+/
21913 	void finish();
21914 
21915 	/++
21916 		Parameters returned by [DropHandler.drop].
21917 	+/
21918 	static struct DropParameters {
21919 		/++
21920 			Acceptable action over this area.
21921 		+/
21922 		DragAndDropAction action;
21923 		/++
21924 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
21925 
21926 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
21927 		+/
21928 		Rectangle consistentWithin;
21929 	}
21930 }
21931 
21932 /++
21933 	History:
21934 		Added February 19, 2021
21935 +/
21936 /// Group: drag_and_drop
21937 enum DragAndDropAction {
21938 	none = 0,
21939 	copy,
21940 	move,
21941 	link,
21942 	ask,
21943 	custom
21944 }
21945 
21946 /++
21947 	An opaque structure representing dropped data. It contains
21948 	private, platform-specific data that your `drop` function
21949 	should simply forward.
21950 
21951 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21952 
21953 	History:
21954 		Added February 19, 2021
21955 +/
21956 /// Group: drag_and_drop
21957 struct DropPackage {
21958 	/++
21959 		Lists the available formats as magic numbers. You should compare these
21960 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
21961 		understand the passed data.
21962 	+/
21963 	DraggableData.FormatId[] availableFormats() {
21964 		version(X11) {
21965 			return xFormats;
21966 		} else version(Windows) {
21967 			if(pDataObj is null)
21968 				return null;
21969 
21970 			typeof(return) ret;
21971 
21972 			IEnumFORMATETC ef;
21973 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
21974 				FORMATETC fmt;
21975 				ULONG fetched;
21976 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
21977 					if(fetched == 0)
21978 						break;
21979 
21980 					if(fmt.lindex != -1)
21981 						continue;
21982 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
21983 						continue;
21984 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
21985 						continue;
21986 
21987 					ret ~= fmt.cfFormat;
21988 				}
21989 			}
21990 
21991 			return ret;
21992 		} else throw new NotYetImplementedException();
21993 	}
21994 
21995 	/++
21996 		Gets data from the drop and optionally accepts it.
21997 
21998 		Returns:
21999 			void because the data is fed asynchronously through the `dg` parameter.
22000 
22001 		Params:
22002 			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.
22003 
22004 			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.
22005 
22006 			Calling `getData` again after accepting a drop is not permitted.
22007 
22008 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
22009 
22010 			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.
22011 
22012 		Throws:
22013 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
22014 
22015 		History:
22016 			Included in first release of [DropPackage].
22017 	+/
22018 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
22019 		version(X11) {
22020 
22021 			auto display = XDisplayConnection.get();
22022 			auto selectionAtom = GetAtom!"XdndSelection"(display);
22023 			auto best = format;
22024 
22025 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
22026 
22027 				XDisplay* display;
22028 				Atom selectionAtom;
22029 				DraggableData.FormatId best;
22030 				DraggableData.FormatId format;
22031 				void delegate(scope ubyte[] data) dg;
22032 				DragAndDropAction acceptedAction;
22033 				Window sourceWindow;
22034 				SimpleWindow win;
22035 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
22036 					this.display = display;
22037 					this.win = win;
22038 					this.sourceWindow = sourceWindow;
22039 					this.format = format;
22040 					this.selectionAtom = selectionAtom;
22041 					this.best = best;
22042 					this.dg = dg;
22043 					this.acceptedAction = acceptedAction;
22044 				}
22045 
22046 
22047 				mixin X11GetSelectionHandler_Basics;
22048 
22049 				void handleData(Atom target, in ubyte[] data) {
22050 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
22051 
22052 					dg(cast(ubyte[]) data);
22053 
22054 					if(acceptedAction != DragAndDropAction.none) {
22055 						auto display = XDisplayConnection.get;
22056 
22057 						XClientMessageEvent xclient;
22058 
22059 						xclient.type = EventType.ClientMessage;
22060 						xclient.window = sourceWindow;
22061 						xclient.message_type = GetAtom!"XdndFinished"(display);
22062 						xclient.format = 32;
22063 						xclient.data.l[0] = win.impl.window;
22064 						xclient.data.l[1] = 1; // drop successful
22065 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
22066 
22067 						XSendEvent(
22068 							display,
22069 							sourceWindow,
22070 							false,
22071 							EventMask.NoEventMask,
22072 							cast(XEvent*) &xclient
22073 						);
22074 
22075 						XFlush(display);
22076 					}
22077 				}
22078 
22079 				Atom findBestFormat(Atom[] answer) {
22080 					Atom best = None;
22081 					foreach(option; answer) {
22082 						if(option == format) {
22083 							best = option;
22084 							break;
22085 						}
22086 						/*
22087 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
22088 							best = option;
22089 							break;
22090 						} else if(option == XA_STRING) {
22091 							best = option;
22092 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
22093 							best = option;
22094 						}
22095 						*/
22096 					}
22097 					return best;
22098 				}
22099 			}
22100 
22101 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
22102 
22103 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
22104 
22105 		} else version(Windows) {
22106 
22107 			// clean up like DragLeave
22108 			// pass effect back up
22109 
22110 			FORMATETC t;
22111 			assert(format >= 0 && format <= ushort.max);
22112 			t.cfFormat = cast(ushort) format;
22113 			t.lindex = -1;
22114 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22115 			t.tymed = TYMED.TYMED_HGLOBAL;
22116 
22117 			STGMEDIUM m;
22118 
22119 			if(pDataObj.GetData(&t, &m) != S_OK) {
22120 				// fail
22121 			} else {
22122 				// succeed, take the data and clean up
22123 
22124 				// FIXME: ensure it is legit HGLOBAL
22125 				auto handle = m.hGlobal;
22126 
22127 				if(handle) {
22128 					auto sz = GlobalSize(handle);
22129 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
22130 						scope(exit) GlobalUnlock(handle);
22131 						scope(exit) GlobalFree(handle);
22132 
22133 						auto data = ptr[0 .. sz];
22134 
22135 						dg(data);
22136 					}
22137 				}
22138 			}
22139 		}
22140 	}
22141 
22142 	private:
22143 
22144 	version(X11) {
22145 		SimpleWindow win;
22146 		Window sourceWindow;
22147 		Time dataTimestamp;
22148 
22149 		Atom[] xFormats;
22150 	}
22151 	version(Windows) {
22152 		IDataObject pDataObj;
22153 	}
22154 }
22155 
22156 /++
22157 	A generic helper base class for making a drop handler with a preference list of custom types.
22158 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
22159 	droppers too.
22160 
22161 	It assumes the whole window it used, but you can subclass to change that.
22162 
22163 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22164 
22165 	History:
22166 		Added February 19, 2021
22167 +/
22168 /// Group: drag_and_drop
22169 class GenericDropHandlerBase : DropHandler {
22170 	// no fancy state here so no need to do anything here
22171 	void finish() { }
22172 	void dragLeave() { }
22173 
22174 	private DragAndDropAction acceptedAction;
22175 	private DraggableData.FormatId acceptedFormat;
22176 	private void delegate(scope ubyte[]) acceptedHandler;
22177 
22178 	struct FormatHandler {
22179 		DraggableData.FormatId format;
22180 		void delegate(scope ubyte[]) handler;
22181 	}
22182 
22183 	protected abstract FormatHandler[] formatHandlers();
22184 
22185 	DragAndDropAction dragEnter(DropPackage* pkg) {
22186 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
22187 		foreach(fmt; formatHandlers())
22188 		foreach(f; pkg.availableFormats())
22189 			if(f == fmt.format) {
22190 				acceptedFormat = f;
22191 				acceptedHandler = fmt.handler;
22192 				return acceptedAction = DragAndDropAction.copy;
22193 			}
22194 		return acceptedAction = DragAndDropAction.none;
22195 	}
22196 	DropParameters dragOver(Point pt) {
22197 		return DropParameters(acceptedAction);
22198 	}
22199 
22200 	void drop(scope DropPackage* dropPackage) {
22201 		if(!acceptedFormat || acceptedHandler is null) {
22202 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
22203 			return; // prolly shouldn't happen anyway...
22204 		}
22205 
22206 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
22207 	}
22208 }
22209 
22210 /++
22211 	A simple handler for making your window accept drops of plain text.
22212 
22213 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22214 
22215 	History:
22216 		Added February 22, 2021
22217 +/
22218 /// Group: drag_and_drop
22219 class TextDropHandler : GenericDropHandlerBase {
22220 	private void delegate(in char[] text) dg;
22221 
22222 	/++
22223 
22224 	+/
22225 	this(void delegate(in char[] text) dg) {
22226 		this.dg = dg;
22227 	}
22228 
22229 	protected override FormatHandler[] formatHandlers() {
22230 		version(X11)
22231 			return [
22232 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
22233 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
22234 			];
22235 		else version(Windows)
22236 			return [
22237 				FormatHandler(CF_UNICODETEXT, &translator),
22238 			];
22239 		else throw new NotYetImplementedException();
22240 	}
22241 
22242 	private void translator(scope ubyte[] data) {
22243 		version(X11)
22244 			dg(cast(char[]) data);
22245 		else version(Windows)
22246 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
22247 	}
22248 }
22249 
22250 /++
22251 	A simple handler for making your window accept drops of files, issued to you as file names.
22252 
22253 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22254 
22255 	History:
22256 		Added February 22, 2021
22257 +/
22258 /// Group: drag_and_drop
22259 
22260 class FilesDropHandler : GenericDropHandlerBase {
22261 	private void delegate(in char[][]) dg;
22262 
22263 	/++
22264 
22265 	+/
22266 	this(void delegate(in char[][] fileNames) dg) {
22267 		this.dg = dg;
22268 	}
22269 
22270 	protected override FormatHandler[] formatHandlers() {
22271 		version(X11)
22272 			return [
22273 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
22274 			];
22275 		else version(Windows)
22276 			return [
22277 				FormatHandler(CF_HDROP, &translator),
22278 			];
22279 		else throw new NotYetImplementedException();
22280 	}
22281 
22282 	private void translator(scope ubyte[] data) @system {
22283 		version(X11) {
22284 			char[] listString = cast(char[]) data;
22285 			char[][16] buffer;
22286 			int count;
22287 			char[][] result = buffer[];
22288 
22289 			void commit(char[] s) {
22290 				if(count == result.length)
22291 					result.length += 16;
22292 				if(s.length > 7 && s[0 ..7] == "file://")
22293 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
22294 				result[count++] = s;
22295 			}
22296 
22297 			size_t last;
22298 			foreach(idx, char c; listString) {
22299 				if(c == '\n') {
22300 					commit(listString[last .. idx - 1]); // a \r
22301 					last = idx + 1; // a \n
22302 				}
22303 			}
22304 
22305 			if(last < listString.length) {
22306 				commit(listString[last .. $]);
22307 			}
22308 
22309 			// FIXME: they are uris now, should I translate it to local file names?
22310 			// of course the host name is supposed to be there cuz of X rokking...
22311 
22312 			dg(result[0 .. count]);
22313 		} else version(Windows) {
22314 
22315 			static struct DROPFILES {
22316 				DWORD pFiles;
22317 				POINT pt;
22318 				BOOL  fNC;
22319 				BOOL  fWide;
22320 			}
22321 
22322 
22323 			const(char)[][16] buffer;
22324 			int count;
22325 			const(char)[][] result = buffer[];
22326 			size_t last;
22327 
22328 			void commitA(in char[] stuff) {
22329 				if(count == result.length)
22330 					result.length += 16;
22331 				result[count++] = stuff;
22332 			}
22333 
22334 			void commitW(in wchar[] stuff) {
22335 				commitA(makeUtf8StringFromWindowsString(stuff));
22336 			}
22337 
22338 			void magic(T)(T chars) {
22339 				size_t idx;
22340 				while(chars[idx]) {
22341 					last = idx;
22342 					while(chars[idx]) {
22343 						idx++;
22344 					}
22345 					static if(is(T == char*))
22346 						commitA(chars[last .. idx]);
22347 					else
22348 						commitW(chars[last .. idx]);
22349 					idx++;
22350 				}
22351 			}
22352 
22353 			auto df = cast(DROPFILES*) data.ptr;
22354 			if(df.fWide) {
22355 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
22356 				magic(chars);
22357 			} else {
22358 				char* chars = cast(char*) (data.ptr + df.pFiles);
22359 				magic(chars);
22360 			}
22361 			dg(result[0 .. count]);
22362 		}
22363 		else throw new NotYetImplementedException();
22364 	}
22365 }
22366 
22367 /++
22368 	Interface to describe data being dragged. See also [draggable] helper function.
22369 
22370 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22371 
22372 	History:
22373 		Added February 19, 2021
22374 +/
22375 interface DraggableData {
22376 	version(X11)
22377 		alias FormatId = Atom;
22378 	else
22379 		alias FormatId = uint;
22380 	/++
22381 		Gets the platform-specific FormatId associated with the given named format.
22382 
22383 		This may be a MIME type, but may also be other various strings defined by the
22384 		programs you want to interoperate with.
22385 
22386 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
22387 		and convert it to some particular type for you.
22388 	+/
22389 	static FormatId getFormatId(string name)() {
22390 		version(X11)
22391 			return GetAtom!name(XDisplayConnection.get);
22392 		else version(Windows) {
22393 			static UINT cache;
22394 			if(!cache)
22395 				cache = RegisterClipboardFormatA(name);
22396 			return cache;
22397 		} else
22398 			throw new NotYetImplementedException();
22399 	}
22400 
22401 	/++
22402 		Looks up a string to represent the name for the given format, if there is one.
22403 
22404 		You should avoid using this function because it is slow. It is provided more for
22405 		debugging than for primary use.
22406 	+/
22407 	static string getFormatName(FormatId format) {
22408 		version(X11) {
22409 			if(format == 0)
22410 				return "None";
22411 			else
22412 				return getAtomName(format, XDisplayConnection.get);
22413 		} else version(Windows) {
22414 			switch(format) {
22415 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
22416 				case CF_DIBV5: return "CF_DIBV5";
22417 				case CF_RIFF: return "CF_RIFF";
22418 				case CF_WAVE: return "CF_WAVE";
22419 				case CF_HDROP: return "CF_HDROP";
22420 				default:
22421 					char[1024] name;
22422 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
22423 					return name[0 .. count].idup;
22424 			}
22425 		} else throw new NotYetImplementedException();
22426 	}
22427 
22428 	FormatId[] availableFormats();
22429 	// Return the slice of data you filled, empty slice if done.
22430 	// this is to support the incremental thing
22431 	ubyte[] getData(FormatId format, return scope ubyte[] data);
22432 
22433 	size_t dataLength(FormatId format);
22434 }
22435 
22436 /++
22437 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22438 
22439 	History:
22440 		Added February 19, 2021
22441 +/
22442 DraggableData draggable(string s) {
22443 	version(X11)
22444 	return new class X11SetSelectionHandler_Text, DraggableData {
22445 		this() {
22446 			super(s);
22447 		}
22448 
22449 		override FormatId[] availableFormats() {
22450 			return X11SetSelectionHandler_Text.availableFormats();
22451 		}
22452 
22453 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
22454 			return X11SetSelectionHandler_Text.getData(format, data);
22455 		}
22456 
22457 		size_t dataLength(FormatId format) {
22458 			return s.length;
22459 		}
22460 	};
22461 	else version(Windows)
22462 	return new class DraggableData {
22463 		FormatId[] availableFormats() {
22464 			return [CF_UNICODETEXT];
22465 		}
22466 
22467 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
22468 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
22469 		}
22470 
22471 		size_t dataLength(FormatId format) {
22472 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
22473 		}
22474 	};
22475 	else
22476 	throw new NotYetImplementedException();
22477 }
22478 
22479 /++
22480 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22481 
22482 	History:
22483 		Added February 19, 2021
22484 +/
22485 /// Group: drag_and_drop
22486 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
22487 in {
22488 	assert(window !is null);
22489 	assert(handler !is null);
22490 }
22491 do
22492 {
22493 	version(X11) {
22494 		auto sh = cast(X11SetSelectionHandler) handler;
22495 		if(sh is null) {
22496 			// gotta make my own adapter.
22497 			sh = new class X11SetSelectionHandler {
22498 				mixin X11SetSelectionHandler_Basics;
22499 
22500 				Atom[] availableFormats() { return handler.availableFormats(); }
22501 				ubyte[] getData(Atom format, return scope ubyte[] data) {
22502 					return handler.getData(format, data);
22503 				}
22504 
22505 				// since the drop selection is only ever used once it isn't important
22506 				// to reset it.
22507 				void done() {}
22508 			};
22509 		}
22510 		return doDragDropX11(window, sh, action);
22511 	} else version(Windows) {
22512 		return doDragDropWindows(window, handler, action);
22513 	} else throw new NotYetImplementedException();
22514 }
22515 
22516 version(Windows)
22517 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
22518 	IDataObject obj = new class IDataObject {
22519 		ULONG refCount;
22520 		ULONG AddRef() {
22521 			return ++refCount;
22522 		}
22523 		ULONG Release() {
22524 			return --refCount;
22525 		}
22526 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22527 			if (IID_IUnknown == *riid) {
22528 				*ppv = cast(void*) cast(IUnknown) this;
22529 			}
22530 			else if (IID_IDataObject == *riid) {
22531 				*ppv = cast(void*) cast(IDataObject) this;
22532 			}
22533 			else {
22534 				*ppv = null;
22535 				return E_NOINTERFACE;
22536 			}
22537 
22538 			AddRef();
22539 			return NOERROR;
22540 		}
22541 
22542 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
22543 			//  writeln("Advise");
22544 			return E_NOTIMPL;
22545 		}
22546 		HRESULT DUnadvise(DWORD dwConnection) {
22547 			return E_NOTIMPL;
22548 		}
22549 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
22550 			//  writeln("EnumDAdvise");
22551 			return OLE_E_ADVISENOTSUPPORTED;
22552 		}
22553 		// tell what formats it supports
22554 
22555 		FORMATETC[] types;
22556 		this() {
22557 			FORMATETC t;
22558 			foreach(ty; handler.availableFormats()) {
22559 				assert(ty <= ushort.max && ty >= 0);
22560 				t.cfFormat = cast(ushort) ty;
22561 				t.lindex = -1;
22562 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22563 				t.tymed = TYMED.TYMED_HGLOBAL;
22564 			}
22565 			types ~= t;
22566 		}
22567 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
22568 			if(dwDirection == DATADIR.DATADIR_GET) {
22569 				*ppenumFormatEtc = new class IEnumFORMATETC {
22570 					ULONG refCount;
22571 					ULONG AddRef() {
22572 						return ++refCount;
22573 					}
22574 					ULONG Release() {
22575 						return --refCount;
22576 					}
22577 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22578 						if (IID_IUnknown == *riid) {
22579 							*ppv = cast(void*) cast(IUnknown) this;
22580 						}
22581 						else if (IID_IEnumFORMATETC == *riid) {
22582 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
22583 						}
22584 						else {
22585 							*ppv = null;
22586 							return E_NOINTERFACE;
22587 						}
22588 
22589 						AddRef();
22590 						return NOERROR;
22591 					}
22592 
22593 
22594 					int pos;
22595 					this() {
22596 						pos = 0;
22597 					}
22598 
22599 					HRESULT Clone(IEnumFORMATETC* ppenum) {
22600 						// writeln("clone");
22601 						return E_NOTIMPL; // FIXME
22602 					}
22603 
22604 					// Caller is responsible for freeing memory
22605 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
22606 						// fetched may be null if celt is one
22607 						if(celt != 1)
22608 							return E_NOTIMPL; // FIXME
22609 
22610 						if(celt + pos > types.length)
22611 							return S_FALSE;
22612 
22613 						*rgelt = types[pos++];
22614 
22615 						if(pceltFetched !is null)
22616 							*pceltFetched = 1;
22617 
22618 						// writeln("ok celt ", celt);
22619 						return S_OK;
22620 					}
22621 
22622 					HRESULT Reset() {
22623 						pos = 0;
22624 						return S_OK;
22625 					}
22626 
22627 					HRESULT Skip(ULONG celt) {
22628 						if(celt + pos <= types.length) {
22629 							pos += celt;
22630 							return S_OK;
22631 						}
22632 						return S_FALSE;
22633 					}
22634 				};
22635 
22636 				return S_OK;
22637 			} else
22638 				return E_NOTIMPL;
22639 		}
22640 		// given a format, return the format you'd prefer to use cuz it is identical
22641 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
22642 			// FIXME: prolly could be better but meh
22643 			// writeln("gcf: ", *pformatectIn);
22644 			*pformatetcOut = *pformatectIn;
22645 			return S_OK;
22646 		}
22647 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22648 			foreach(ty; types) {
22649 				if(ty == *pformatetcIn) {
22650 					auto format = ty.cfFormat;
22651 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
22652 					STGMEDIUM medium;
22653 					medium.tymed = TYMED.TYMED_HGLOBAL;
22654 
22655 					auto sz = handler.dataLength(format);
22656 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
22657 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
22658 					if(auto data = cast(wchar*) GlobalLock(handle)) {
22659 						auto slice = data[0 .. sz];
22660 						scope(exit)
22661 							GlobalUnlock(handle);
22662 
22663 						handler.getData(format, cast(ubyte[]) slice[]);
22664 					}
22665 
22666 
22667 					medium.hGlobal = handle; // FIXME
22668 					*pmedium = medium;
22669 					return S_OK;
22670 				}
22671 			}
22672 			return DV_E_FORMATETC;
22673 		}
22674 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22675 			// writeln("GDH: ", *pformatetcIn);
22676 			return E_NOTIMPL; // FIXME
22677 		}
22678 		HRESULT QueryGetData(FORMATETC* pformatetc) {
22679 			auto search = *pformatetc;
22680 			search.tymed &= TYMED.TYMED_HGLOBAL;
22681 			foreach(ty; types)
22682 				if(ty == search) {
22683 					// writeln("QueryGetData ", search, " ", types[0]);
22684 					return S_OK;
22685 				}
22686 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
22687 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
22688 			}
22689 			return S_FALSE;
22690 		}
22691 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
22692 			//  writeln("SetData: ");
22693 			return E_NOTIMPL;
22694 		}
22695 	};
22696 
22697 
22698 	IDropSource src = new class IDropSource {
22699 		ULONG refCount;
22700 		ULONG AddRef() {
22701 			return ++refCount;
22702 		}
22703 		ULONG Release() {
22704 			return --refCount;
22705 		}
22706 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22707 			if (IID_IUnknown == *riid) {
22708 				*ppv = cast(void*) cast(IUnknown) this;
22709 			}
22710 			else if (IID_IDropSource == *riid) {
22711 				*ppv = cast(void*) cast(IDropSource) this;
22712 			}
22713 			else {
22714 				*ppv = null;
22715 				return E_NOINTERFACE;
22716 			}
22717 
22718 			AddRef();
22719 			return NOERROR;
22720 		}
22721 
22722 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
22723 			if(fEscapePressed)
22724 				return DRAGDROP_S_CANCEL;
22725 			if(!(grfKeyState & MK_LBUTTON))
22726 				return DRAGDROP_S_DROP;
22727 			return S_OK;
22728 		}
22729 
22730 		int GiveFeedback(uint dwEffect) {
22731 			return DRAGDROP_S_USEDEFAULTCURSORS;
22732 		}
22733 	};
22734 
22735 	DWORD effect;
22736 
22737 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
22738 
22739 	DROPEFFECT de = win32DragAndDropAction(action);
22740 
22741 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
22742 	// but still prolly a FIXME
22743 
22744 	auto ret = DoDragDrop(obj, src, de, &effect);
22745 	/+
22746 	if(ret == DRAGDROP_S_DROP)
22747 		writeln("drop ", effect);
22748 	else if(ret == DRAGDROP_S_CANCEL)
22749 		writeln("cancel");
22750 	else if(ret == S_OK)
22751 		writeln("ok");
22752 	else writeln(ret);
22753 	+/
22754 
22755 	return ret;
22756 }
22757 
22758 version(Windows)
22759 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
22760 	DROPEFFECT de;
22761 
22762 	with(DragAndDropAction)
22763 	with(DROPEFFECT)
22764 	final switch(action) {
22765 		case none: de = DROPEFFECT_NONE; break;
22766 		case copy: de = DROPEFFECT_COPY; break;
22767 		case move: de = DROPEFFECT_MOVE; break;
22768 		case link: de = DROPEFFECT_LINK; break;
22769 		case ask: throw new Exception("ask not implemented yet");
22770 		case custom: throw new Exception("custom not implemented yet");
22771 	}
22772 
22773 	return de;
22774 }
22775 
22776 
22777 /++
22778 	History:
22779 		Added February 19, 2021
22780 +/
22781 /// Group: drag_and_drop
22782 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
22783 	version(X11) {
22784 		auto display = XDisplayConnection.get;
22785 
22786 		Atom atom = 5; // right???
22787 
22788 		XChangeProperty(
22789 			display,
22790 			window.impl.window,
22791 			GetAtom!"XdndAware"(display),
22792 			XA_ATOM,
22793 			32 /* bits */,
22794 			PropModeReplace,
22795 			&atom,
22796 			1);
22797 
22798 		window.dropHandler = handler;
22799 	} else version(Windows) {
22800 
22801 		initDnd();
22802 
22803 		auto dropTarget = new class (handler) IDropTarget {
22804 			DropHandler handler;
22805 			this(DropHandler handler) {
22806 				this.handler = handler;
22807 			}
22808 			ULONG refCount;
22809 			ULONG AddRef() {
22810 				return ++refCount;
22811 			}
22812 			ULONG Release() {
22813 				return --refCount;
22814 			}
22815 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22816 				if (IID_IUnknown == *riid) {
22817 					*ppv = cast(void*) cast(IUnknown) this;
22818 				}
22819 				else if (IID_IDropTarget == *riid) {
22820 					*ppv = cast(void*) cast(IDropTarget) this;
22821 				}
22822 				else {
22823 					*ppv = null;
22824 					return E_NOINTERFACE;
22825 				}
22826 
22827 				AddRef();
22828 				return NOERROR;
22829 			}
22830 
22831 
22832 			// ///////////////////
22833 
22834 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22835 				DropPackage dropPackage = DropPackage(pDataObj);
22836 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
22837 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
22838 			}
22839 
22840 			HRESULT DragLeave() {
22841 				handler.dragLeave();
22842 				// release the IDataObject if needed
22843 				return S_OK;
22844 			}
22845 
22846 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22847 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
22848 
22849 				*pdwEffect = win32DragAndDropAction(res.action);
22850 				// same as DragEnter basically
22851 				return S_OK;
22852 			}
22853 
22854 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22855 				DropPackage pkg = DropPackage(pDataObj);
22856 				handler.drop(&pkg);
22857 
22858 				return S_OK;
22859 			}
22860 		};
22861 		// Windows can hold on to the handler and try to call it
22862 		// during which time the GC can't see it. so important to
22863 		// manually manage this. At some point i'll FIXME and make
22864 		// all my com instances manually managed since they supposed
22865 		// to respect the refcount.
22866 		import core.memory;
22867 		GC.addRoot(cast(void*) dropTarget);
22868 
22869 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
22870 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
22871 
22872 		window.dropHandler = handler;
22873 	} else throw new NotYetImplementedException();
22874 }
22875 
22876 
22877 
22878 static if(UsingSimpledisplayX11) {
22879 
22880 enum _NET_WM_STATE_ADD = 1;
22881 enum _NET_WM_STATE_REMOVE = 0;
22882 enum _NET_WM_STATE_TOGGLE = 2;
22883 
22884 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
22885 void demandAttention(SimpleWindow window, bool needs = true) {
22886 	demandAttention(window.impl.window, needs);
22887 }
22888 
22889 /// ditto
22890 void demandAttention(Window window, bool needs = true) {
22891 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
22892 }
22893 
22894 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
22895 	auto display = XDisplayConnection.get();
22896 	if(atom == None)
22897 		return; // non-failure error
22898 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
22899 
22900 	XClientMessageEvent xclient;
22901 
22902 	xclient.type = EventType.ClientMessage;
22903 	xclient.window = window;
22904 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
22905 	xclient.format = 32;
22906 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
22907 	xclient.data.l[1] = atom;
22908 	xclient.data.l[2] = atom2;
22909 	xclient.data.l[3] = 1;
22910 	// [3] == source. 0 == unknown, 1 == app, 2 == else
22911 
22912 	XSendEvent(
22913 		display,
22914 		RootWindow(display, DefaultScreen(display)),
22915 		false,
22916 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
22917 		cast(XEvent*) &xclient
22918 	);
22919 
22920 	/+
22921 	XChangeProperty(
22922 		display,
22923 		window.impl.window,
22924 		GetAtom!"_NET_WM_STATE"(display),
22925 		XA_ATOM,
22926 		32 /* bits */,
22927 		PropModeAppend,
22928 		&atom,
22929 		1);
22930 	+/
22931 }
22932 
22933 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
22934 	Atom actionAtom;
22935 	with(DragAndDropAction)
22936 	final switch(action) {
22937 		case none: actionAtom = None; break;
22938 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
22939 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
22940 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
22941 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
22942 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
22943 	}
22944 
22945 	return actionAtom;
22946 }
22947 
22948 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
22949 	// FIXME: I need to show user feedback somehow.
22950 	auto display = XDisplayConnection.get;
22951 
22952 	auto actionAtom = dndActionAtom(display, action);
22953 	assert(actionAtom, "Don't use action none to accept a drop");
22954 
22955 	setX11Selection!"XdndSelection"(window, handler, null);
22956 
22957 	auto oldKeyHandler = window.handleKeyEvent;
22958 	scope(exit) window.handleKeyEvent = oldKeyHandler;
22959 
22960 	auto oldCharHandler = window.handleCharEvent;
22961 	scope(exit) window.handleCharEvent = oldCharHandler;
22962 
22963 	auto oldMouseHandler = window.handleMouseEvent;
22964 	scope(exit) window.handleMouseEvent = oldMouseHandler;
22965 
22966 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
22967 
22968 	import core.sys.posix.sys.time;
22969 	timeval tv;
22970 	gettimeofday(&tv, null);
22971 
22972 	Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 );
22973 
22974 	Time lastMouseTimestamp;
22975 
22976 	bool dnding = true;
22977 	Window lastIn = None;
22978 
22979 	void leave() {
22980 		if(lastIn == None)
22981 			return;
22982 
22983 		XEvent ev;
22984 		ev.xclient.type = EventType.ClientMessage;
22985 		ev.xclient.window = lastIn;
22986 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
22987 		ev.xclient.format = 32;
22988 		ev.xclient.data.l[0] = window.impl.window;
22989 
22990 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22991 		XFlush(display);
22992 
22993 		lastIn = None;
22994 	}
22995 
22996 	void enter(Window w) {
22997 		assert(lastIn == None);
22998 
22999 		lastIn = w;
23000 
23001 		XEvent ev;
23002 		ev.xclient.type = EventType.ClientMessage;
23003 		ev.xclient.window = lastIn;
23004 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
23005 		ev.xclient.format = 32;
23006 		ev.xclient.data.l[0] = window.impl.window;
23007 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
23008 
23009 		auto types = handler.availableFormats();
23010 		assert(types.length > 0);
23011 
23012 		ev.xclient.data.l[2] = types[0];
23013 		if(types.length > 1)
23014 			ev.xclient.data.l[3] = types[1];
23015 		if(types.length > 2)
23016 			ev.xclient.data.l[4] = types[2];
23017 
23018 		// FIXME: other types?!?!? and make sure we skip TARGETS
23019 
23020 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
23021 		XFlush(display);
23022 	}
23023 
23024 	void position(int rootX, int rootY) {
23025 		assert(lastIn != None);
23026 
23027 		XEvent ev;
23028 		ev.xclient.type = EventType.ClientMessage;
23029 		ev.xclient.window = lastIn;
23030 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
23031 		ev.xclient.format = 32;
23032 		ev.xclient.data.l[0] = window.impl.window;
23033 		ev.xclient.data.l[1] = 0; // reserved
23034 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
23035 		ev.xclient.data.l[3] = dataTimestamp;
23036 		ev.xclient.data.l[4] = actionAtom;
23037 
23038 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
23039 		XFlush(display);
23040 
23041 	}
23042 
23043 	void drop() {
23044 		XEvent ev;
23045 		ev.xclient.type = EventType.ClientMessage;
23046 		ev.xclient.window = lastIn;
23047 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
23048 		ev.xclient.format = 32;
23049 		ev.xclient.data.l[0] = window.impl.window;
23050 		ev.xclient.data.l[1] = 0; // reserved
23051 		ev.xclient.data.l[2] = dataTimestamp;
23052 
23053 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
23054 		XFlush(display);
23055 
23056 		lastIn = None;
23057 		dnding = false;
23058 	}
23059 
23060 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
23061 	// but idk if i should...
23062 
23063 	window.setEventHandlers(
23064 		delegate(KeyEvent ev) {
23065 			if(ev.pressed == true && ev.key == Key.Escape) {
23066 				// cancel
23067 				dnding = false;
23068 			}
23069 		},
23070 		delegate(MouseEvent ev) {
23071 			if(ev.timestamp < lastMouseTimestamp)
23072 				return;
23073 
23074 			lastMouseTimestamp = ev.timestamp;
23075 
23076 			if(ev.type == MouseEventType.motion) {
23077 				auto display = XDisplayConnection.get;
23078 				auto root = RootWindow(display, DefaultScreen(display));
23079 
23080 				Window topWindow;
23081 				int rootX, rootY;
23082 
23083 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
23084 
23085 				if(topWindow == None)
23086 					return;
23087 
23088 				top:
23089 				if(auto result = topWindow in eligibility) {
23090 					auto dropWindow = *result;
23091 					if(dropWindow == None) {
23092 						leave();
23093 						return;
23094 					}
23095 
23096 					if(dropWindow != lastIn) {
23097 						leave();
23098 						enter(dropWindow);
23099 						position(rootX, rootY);
23100 					} else {
23101 						position(rootX, rootY);
23102 					}
23103 				} else {
23104 					// determine eligibility
23105 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
23106 					if(data.length == 1) {
23107 						// in case there is no WM or it isn't reparenting
23108 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
23109 					} else {
23110 
23111 						Window tryScanChildren(Window search, int maxRecurse) {
23112 							// could be reparenting window manager, so gotta check the next few children too
23113 							Window child;
23114 							int x;
23115 							int y;
23116 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
23117 
23118 							if(child == None)
23119 								return None;
23120 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
23121 							if(data.length == 1) {
23122 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
23123 							} else {
23124 								if(maxRecurse)
23125 									return tryScanChildren(child, maxRecurse - 1);
23126 								else
23127 									return None;
23128 							}
23129 
23130 						}
23131 
23132 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
23133 						auto topResult = tryScanChildren(topWindow, 3);
23134 						// it is easy to have a false negative due to the mouse going over a WM
23135 						// child window like the close button if separate from the frame... so I
23136 						// can't really cache negatives, :(
23137 						if(topResult != None) {
23138 							eligibility[topWindow] = topResult;
23139 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
23140 						}
23141 					}
23142 
23143 				}
23144 
23145 			} else if(ev.type == MouseEventType.buttonReleased) {
23146 				drop();
23147 				dnding = false;
23148 			}
23149 		}
23150 	);
23151 
23152 	window.grabInput();
23153 	scope(exit)
23154 		window.releaseInputGrab();
23155 
23156 
23157 	EventLoop.get.run(() => dnding);
23158 
23159 	return 0;
23160 }
23161 
23162 /// X-specific
23163 TrueColorImage getWindowNetWmIcon(Window window) {
23164 	try {
23165 		auto display = XDisplayConnection.get;
23166 
23167 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
23168 
23169 		if (data.length > arch_ulong.sizeof * 2) {
23170 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
23171 			// these are an array of rgba images that we have to convert into pixmaps ourself
23172 
23173 			int width = cast(int) meta[0];
23174 			int height = cast(int) meta[1];
23175 
23176 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
23177 
23178 			static if(arch_ulong.sizeof == 4) {
23179 				bytes = bytes[0 .. width * height * 4];
23180 				alias imageData = bytes;
23181 			} else static if(arch_ulong.sizeof == 8) {
23182 				bytes = bytes[0 .. width * height * 8];
23183 				auto imageData = new ubyte[](4 * width * height);
23184 			} else static assert(0);
23185 
23186 
23187 
23188 			// this returns ARGB. Remember it is little-endian so
23189 			//                                         we have BGRA
23190 			// our thing uses RGBA, which in little endian, is ABGR
23191 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
23192 				auto r = bytes[idx + 2];
23193 				auto g = bytes[idx + 1];
23194 				auto b = bytes[idx + 0];
23195 				auto a = bytes[idx + 3];
23196 
23197 				imageData[idx2 + 0] = r;
23198 				imageData[idx2 + 1] = g;
23199 				imageData[idx2 + 2] = b;
23200 				imageData[idx2 + 3] = a;
23201 			}
23202 
23203 			return new TrueColorImage(width, height, imageData);
23204 		}
23205 
23206 		return null;
23207 	} catch(Exception e) {
23208 		return null;
23209 	}
23210 }
23211 
23212 } /* UsingSimpledisplayX11 */
23213 
23214 
23215 void loadBinNameToWindowClassName () {
23216 	import core.stdc.stdlib : realloc;
23217 	version(linux) {
23218 		// args[0] MAY be empty, so we'll just use this
23219 		import core.sys.posix.unistd : readlink;
23220 		char[1024] ebuf = void; // 1KB should be enough for everyone!
23221 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
23222 		if (len < 1) return;
23223 	} else /*version(Windows)*/ {
23224 		import core.runtime : Runtime;
23225 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
23226 		auto ebuf = Runtime.args[0];
23227 		auto len = ebuf.length;
23228 	}
23229 	auto pos = len;
23230 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
23231 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
23232 	if (sdpyWindowClassStr is null) return; // oops
23233 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
23234 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
23235 }
23236 
23237 /++
23238 	An interface representing a font that is drawn with custom facilities.
23239 
23240 	You might want [OperatingSystemFont] instead, which represents
23241 	a font loaded and drawn by functions native to the operating system.
23242 
23243 	WARNING: I might still change this.
23244 +/
23245 interface DrawableFont : MeasurableFont {
23246 	/++
23247 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
23248 
23249 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
23250 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
23251 		fill color, but that's up to the implementation.
23252 	+/
23253 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
23254 
23255 	/++
23256 		Requests that the given string is added to the image cache. You should only do this rarely, but
23257 		if you have a string that you know will be used over and over again, adding it to a cache can
23258 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
23259 		to implement this as a do-nothing method).
23260 	+/
23261 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
23262 }
23263 
23264 /++
23265 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
23266 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
23267 
23268 	You should also consider [OperatingSystemFont], which loads and draws a font with
23269 	facilities native to the user's operating system. You might also consider
23270 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
23271 	of game, as they have their own ways to draw text too.
23272 
23273 	Be warned: this can be slow, especially on remote connections to the X server, since
23274 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
23275 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
23276 	experiment in your specific case.
23277 
23278 	Please note that the return type of [DrawableFont] also includes an implementation of
23279 	[MeasurableFont].
23280 +/
23281 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
23282 	import arsd.ttf;
23283 	static class ArsdTtfFont : DrawableFont {
23284 		TtfFont font;
23285 		int size;
23286 		this(in ubyte[] data, int size) {
23287 			font = TtfFont(data);
23288 			this.size = size;
23289 
23290 
23291 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
23292 			int ascent_, descent_, line_gap;
23293 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
23294 
23295 			int advance, lsb;
23296 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
23297 			xWidth = cast(int) (advance * scale);
23298 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
23299 			MWidth = cast(int) (advance * scale);
23300 		}
23301 
23302 		private int ascent_;
23303 		private int descent_;
23304 		private int xWidth;
23305 		private int MWidth;
23306 
23307 		bool isMonospace() {
23308 			return xWidth == MWidth;
23309 		}
23310 		int averageWidth() {
23311 			return xWidth;
23312 		}
23313 		int height() {
23314 			return size;
23315 		}
23316 		int ascent() {
23317 			return ascent_;
23318 		}
23319 		int descent() {
23320 			return descent_;
23321 		}
23322 
23323 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
23324 			int width, height;
23325 			font.getStringSize(s, size, width, height);
23326 			return width;
23327 		}
23328 
23329 
23330 
23331 		Sprite[string] cache;
23332 
23333 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
23334 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
23335 			cache[text] = sprite;
23336 		}
23337 
23338 		Image stringToImage(Color fg, Color bg, in char[] text) {
23339 			int width, height;
23340 			auto data = font.renderString(text, size, width, height);
23341 			auto image = new TrueColorImage(width, height);
23342 			int pos = 0;
23343 			foreach(y; 0 .. height)
23344 			foreach(x; 0 .. width) {
23345 				fg.a = data[0];
23346 				bg.a = 255;
23347 				auto color = alphaBlend(fg, bg);
23348 				image.imageData.bytes[pos++] = color.r;
23349 				image.imageData.bytes[pos++] = color.g;
23350 				image.imageData.bytes[pos++] = color.b;
23351 				image.imageData.bytes[pos++] = data[0];
23352 				data = data[1 .. $];
23353 			}
23354 			assert(data.length == 0);
23355 
23356 			return Image.fromMemoryImage(image);
23357 		}
23358 
23359 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
23360 			Sprite sprite = (text in cache) ? *(text in cache) : null;
23361 
23362 			auto fg = painter.impl._outlineColor;
23363 			auto bg = painter.impl._fillColor;
23364 
23365 			if(sprite !is null) {
23366 				auto w = cast(SimpleWindow) painter.window;
23367 				assert(w !is null);
23368 
23369 				sprite.drawAt(painter, upperLeft);
23370 			} else {
23371 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
23372 			}
23373 		}
23374 	}
23375 
23376 	return new ArsdTtfFont(data, size);
23377 }
23378 
23379 class NotYetImplementedException : Exception {
23380 	this(string file = __FILE__, size_t line = __LINE__) {
23381 		super("Not yet implemented", file, line);
23382 	}
23383 }
23384 
23385 ///
23386 __gshared bool librariesSuccessfullyLoaded = true;
23387 ///
23388 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
23389 
23390 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
23391 	// mixin(staticForeachReplacement!Iface);
23392 	static foreach(name; __traits(derivedMembers, Iface))
23393 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23394 
23395 	void loadDynamicLibrary() @nogc {
23396 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23397 	}
23398 
23399 	void loadDynamicLibraryForReal() {
23400 		foreach(name; __traits(derivedMembers, Iface)) {
23401 			mixin("alias tmp = " ~ name ~ ";");
23402 			tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
23403 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
23404 		}
23405 	}
23406 }
23407 
23408 /+
23409 private const(char)[] staticForeachReplacement(Iface)() pure {
23410 /*
23411 	// just this for gdc 9....
23412 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
23413 
23414 	static foreach(name; __traits(derivedMembers, Iface))
23415 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23416 */
23417 
23418 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
23419 	size_t pos;
23420 
23421 	void append(in char[] what) {
23422 		if(pos + what.length > code.length)
23423 			code.length = (code.length * 3) / 2;
23424 		code[pos .. pos + what.length] = what[];
23425 		pos += what.length;
23426 	}
23427 
23428 	foreach(name; __traits(derivedMembers, Iface)) {
23429 		append(`__gshared typeof(&__traits(getMember, Iface, "`);
23430 		append(name);
23431 		append(`")) `);
23432 		append(name);
23433 		append(";");
23434 	}
23435 
23436 	return code[0 .. pos];
23437 }
23438 +/
23439 
23440 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
23441 	//mixin(staticForeachReplacement!Iface);
23442 	static foreach(name; __traits(derivedMembers, Iface))
23443 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23444 
23445 	private __gshared void* libHandle;
23446 	private __gshared bool attempted;
23447 
23448 	void loadDynamicLibrary() @nogc {
23449 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23450 	}
23451 
23452 	bool loadAttempted() {
23453 		return attempted;
23454 	}
23455 	bool loadSuccessful() {
23456 		return libHandle !is null;
23457 	}
23458 
23459 	void loadDynamicLibraryForReal() {
23460 		attempted = true;
23461 		version(Posix) {
23462 			import core.sys.posix.dlfcn;
23463 			version(OSX) {
23464 				version(X11)
23465 					libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
23466 				else
23467 					libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
23468 			} else {
23469 				version(apitrace) {
23470 					if(library == "GL" || library == "GLX") {
23471 						libHandle = dlopen("glxtrace.so", RTLD_NOW);
23472 						if(libHandle is null) {
23473 							assert(false, "Failed to load `glxtrace.so`.");
23474 						}
23475 					}
23476 					else {
23477 						libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23478 					}
23479 				}
23480 				else {
23481 					libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23482 				}
23483 				if(libHandle is null) {
23484 					libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
23485 				}
23486 			}
23487 
23488 			static void* loadsym(void* l, const char* name) {
23489 				import core.stdc.stdlib;
23490 				if(l is null)
23491 					return &abort;
23492 				return dlsym(l, name);
23493 			}
23494 		} else version(Windows) {
23495 			import core.sys.windows.winbase;
23496 			libHandle = LoadLibrary(library ~ ".dll");
23497 			static void* loadsym(void* l, const char* name) {
23498 				import core.stdc.stdlib;
23499 				if(l is null)
23500 					return &abort;
23501 				return GetProcAddress(l, name);
23502 			}
23503 		}
23504 		if(libHandle is null) {
23505 			success = false;
23506 			//throw new Exception("load failure of library " ~ library);
23507 		}
23508 		foreach(name; __traits(derivedMembers, Iface)) {
23509 			mixin("alias tmp = " ~ name ~ ";");
23510 			tmp = cast(typeof(tmp)) loadsym(libHandle, name);
23511 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
23512 		}
23513 	}
23514 
23515 	void unloadDynamicLibrary() {
23516 		version(Posix) {
23517 			import core.sys.posix.dlfcn;
23518 			dlclose(libHandle);
23519 		} else version(Windows) {
23520 			import core.sys.windows.winbase;
23521 			FreeLibrary(libHandle);
23522 		}
23523 		foreach(name; __traits(derivedMembers, Iface))
23524 			mixin(name ~ " = null;");
23525 	}
23526 }
23527 
23528 // version(X11)
23529 /++
23530 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
23531 
23532 	$(WARNING
23533 		This function is exempted from stability guarantees.
23534 	)
23535 +/
23536 float customScalingFactorForMonitor(int monitorNumber) @system {
23537 	import core.stdc.stdlib;
23538 	auto val = getenv("ARSD_SCALING_FACTOR");
23539 
23540 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
23541 	if(val is null)
23542 		return 1.0;
23543 
23544 	char[16] buffer = 0;
23545 	int pos;
23546 
23547 	const(char)* at = val;
23548 
23549 	foreach(item; 0 .. monitorNumber + 1) {
23550 		if(*at == 0)
23551 			break; // reuse the last number when we at the end of the string
23552 		pos = 0;
23553 		while(pos + 1 < buffer.length && *at && *at != ';') {
23554 			buffer[pos++] = *at;
23555 			at++;
23556 		}
23557 		if(*at)
23558 			at++; // skip the semicolon
23559 		buffer[pos] = 0;
23560 	}
23561 
23562 	//sdpyPrintDebugString(buffer[0 .. pos]);
23563 
23564 	import core.stdc.math;
23565 	auto f = atof(buffer.ptr);
23566 
23567 	if(f <= 0.0 || isnan(f) || isinf(f))
23568 		return 1.0;
23569 
23570 	return f;
23571 }
23572 
23573 void guiAbortProcess(string msg) {
23574 	import core.stdc.stdlib;
23575 	version(Windows) {
23576 		WCharzBuffer t = WCharzBuffer(msg);
23577 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
23578 	} else {
23579 		import core.stdc.stdio;
23580 		fwrite(msg.ptr, 1, msg.length, stderr);
23581 		msg = "\n";
23582 		fwrite(msg.ptr, 1, msg.length, stderr);
23583 		fflush(stderr);
23584 	}
23585 
23586 	abort();
23587 }
23588 
23589 private int minInternal(int a, int b) {
23590 	return (a < b) ? a : b;
23591 }
23592 
23593 private alias scriptable = arsd_jsvar_compatible;