1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 /+
4 	To share some stuff between two opengl threads:
5 	windows
6 	https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
7 	linux
8 	https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
9 +/
10 
11 
12 // Search for: FIXME: leaks if multithreaded gc
13 
14 // https://freedesktop.org/wiki/Specifications/XDND/
15 
16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
17 
18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
20 
21 
22 // on Mac with X11: -L-L/usr/X11/lib
23 
24 /+
25 
26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
27 
28 	Progress bar in taskbar
29 		- i can probably just set a property on the window...
30 		  it sets that prop to an integer 0 .. 100. Taskbar
31 		  deletes it or window deletes it when it is handled.
32 		- prolly display it as a nice little line at the bottom.
33 
34 
35 from gtk:
36 
37 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
38 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
39 
40 >+  if (cardinal > 0)
41 >+  {
42 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
45 >+                     XA_CARDINAL, 32,
46 >+                     PropModeReplace,
47 >+                     (guchar *) &cardinal, 1);
48 >+  }
49 >+  else
50 >+  {
51 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
52 >+                     xid,
53 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
54 >+  }
55 
56 from Windows:
57 
58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
59 
60 interface
61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
63 listen for msg, return TRUE
64 interface->SetProgressState(hwnd, TBPF_NORMAL);
65 interface->SetProgressValue(hwnd, 40, 100);
66 
67 
68 	My new notification system.
69 		- use a unix socket? or a x property? or a udp port?
70 		- could of course also get on the dbus train but ugh.
71 		- it could also reply with the info as a string for easy remote examination.
72 
73 +/
74 
75 /*
76 	Event Loop would be nices:
77 
78 	* add on idle - runs when nothing else happens
79 		* which can specify how long to yield for
80 	* send messages without a recipient window
81 	* setTimeout
82 	* setInterval
83 */
84 
85 /*
86 	Classic games I want to add:
87 		* my tetris clone
88 		* pac man
89 */
90 
91 /*
92 	Text layout needs a lot of work. Plain drawText is useful but too
93 	limited. It will need some kind of text context thing which it will
94 	update and you can pass it on and get more details out of it.
95 
96 	It will need a bounding box, a current cursor location that is updated
97 	as drawing continues, and various changable facts (which can also be
98 	changed on the painter i guess) like font, color, size, background,
99 	etc.
100 
101 	We can also fetch the caret location from it somehow.
102 
103 	Should prolly be an overload of drawText
104 
105 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
106 
107 		WS_EX_NOACTIVATE
108 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
109 		full screen windows. Can just set the atom on X. Windows will be harder.
110 
111 		moving windows. resizing windows.
112 
113 		hide cursor, capture cursor, change cursor.
114 
115 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
116 	sure the pieces are there to do its job easily and make other jobs possible.
117 */
118 
119 /++
120 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
121 	including creating windows, drawing on them, working with the clipboard,
122 	timers, OpenGL, and more. However, it does NOT provide high level GUI
123 	widgets. See my minigui.d, an extension to this module, for that
124 	functionality.
125 
126 	simpledisplay provides cross-platform wrapping for Windows and Linux
127 	(and perhaps other OSes that use X11), but also does not prevent you
128 	from using the underlying facilities if you need them. It has a goal
129 	of working efficiently over a remote X link (at least as far as Xlib
130 	reasonably allows.)
131 
132 	simpledisplay depends on [arsd.color|color.d], which should be available from the
133 	same place where you got this file. Other than that, however, it has
134 	very few dependencies and ones that don't come with the OS and/or the
135 	compiler are all opt-in.
136 
137 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
138 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
139 
140 	simpledisplay is basically stable. I plan to refactor the internals,
141 	and may add new features and fix bugs, but It do not expect to
142 	significantly change the API. It has been stable a few years already now.
143 
144 	Installation_instructions:
145 
146 	`simpledisplay.d` does not have any dependencies outside the
147 	operating system and `color.d`, so it should just work most the
148 	time, but there are a few caveats on some systems:
149 
150 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
151 	console to be automatically allocated.
152 
153 	Please note when compiling on Win64, you need to explicitly list
154 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
155 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
156 
157 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
158 	note the "w".
159 
160 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
161 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
162 	See [EnableWindowsSubsystem] for more information.
163 
164 	$(PITFALL
165 		With the Windows subsystem, there is no console, so standard writeln will throw!
166 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
167 		create a console as needed.
168 	)
169 
170 	On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
171 
172 	On Ubuntu, you might need to install X11 development libraries to
173 	successfully link.
174 
175 	$(CONSOLE
176 		$ sudo apt-get install libglc-dev
177 		$ sudo apt-get install libx11-dev
178 	)
179 
180 
181 	Jump_list:
182 
183 	Don't worry, you don't have to read this whole documentation file!
184 
185 	Check out the [#event-example] and [#Pong-example] to get started quickly.
186 
187 	The main classes you may want to create are [SimpleWindow], [Timer],
188 	[Image], and [Sprite].
189 
190 	The main functions you'll want are [setClipboardText] and [getClipboardText].
191 
192 	There are also platform-specific functions available such as [XDisplayConnection]
193 	and [GetAtom] for X11, among others.
194 
195 	See the examples and topics list below to learn more.
196 
197 	$(WARNING
198 		There should only be one GUI thread per application,
199 		and all windows should be created in it and your
200 		event loop should run there.
201 
202 		To do otherwise is undefined behavior and has no
203 		cross platform guarantees.
204 	)
205 
206 	$(H2 About this documentation)
207 
208 	The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
209 
210 	Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
211 
212 	All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
213 
214 	To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
215 
216 	If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
217 
218 	At points, I will talk about implementation details in the documentation. These are sometimes
219 	subject to change, but nevertheless useful to understand what is really going on. You can learn
220 	more about some of the referenced things by searching the web for info about using them from C.
221 	You can always look at the source of simpledisplay.d too for the most authoritative source on
222 	its specific implementation. If you disagree with how I did something, please contact me so we
223 	can discuss it!
224 
225 	$(H2 Using with fibers)
226 
227 	simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
228 
229 	$(H2 Topics)
230 
231 	$(H3 $(ID topic-windows) Windows)
232 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
233 		window on the user's screen.
234 
235 		You may create multiple windows, if the underlying platform supports it. You may check
236 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
237 		SimpleWindow's constructor at runtime to handle those cases.
238 
239 		A single running event loop will handle as many windows as needed.
240 
241 	$(H3 $(ID topic-event-loops) Event loops)
242 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
243 
244 		The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there:
245 
246 		---
247 		// dmd example.d simpledisplay.d color.d
248 		import arsd.simpledisplay;
249 		void main() {
250 			auto window = new SimpleWindow(200, 200);
251 			window.eventLoop(0,
252 			  delegate (dchar) { /* got a character key press */ }
253 			);
254 		}
255 		---
256 
257 		$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
258 
259 		On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
260 
261 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
262 
263 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
264 
265 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
266 
267 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
268 		Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
269 
270 		See the [NotificationAreaIcon] class.
271 
272 	$(H3 $(ID topic-input-handling) Input handling)
273 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
274 
275 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
276 
277 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
278 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
279 
280 		Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
281 
282 		---
283 		// dmd example.d simpledisplay.d color.d
284 		import arsd.simpledisplay;
285 		void main() {
286 			auto window = new SimpleWindow(200, 200);
287 			{ // introduce sub-scope
288 				auto painter = window.draw(); // begin drawing
289 				/* draw here */
290 				painter.outlineColor = Color.red;
291 				painter.fillColor = Color.black;
292 				painter.drawRectangle(Point(0, 0), 200, 200);
293 			} // end scope, calling `painter`'s destructor, drawing to the screen.
294 			window.eventLoop(0); // handle events
295 		}
296 		---
297 
298 		Painting is done based on two color properties, a pen and a brush.
299 
300 		At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
301 
302 		FIXME Add example of 2d opengl drawing here.
303 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
304 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
305 
306 		Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
307 
308 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
309 
310 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
311 
312 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
313 
314 		simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
315 
316 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
317 
318 		---
319 		import arsd.simpledisplay;
320 
321 		void main() {
322 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
323 
324 			float otherColor = 0.0;
325 			float colorDelta = 0.05;
326 
327 			window.redrawOpenGlScene = delegate() {
328 				glLoadIdentity();
329 				glBegin(GL_QUADS);
330 
331 				glColor3f(1.0, otherColor, 0);
332 				glVertex3f(-0.8, -0.8, 0);
333 
334 				glColor3f(1.0, otherColor, 1.0);
335 				glVertex3f(0.8, -0.8, 0);
336 
337 				glColor3f(0, 1.0, otherColor);
338 				glVertex3f(0.8, 0.8, 0);
339 
340 				glColor3f(otherColor, 0, 1.0);
341 				glVertex3f(-0.8, 0.8, 0);
342 
343 				glEnd();
344 			};
345 
346 			window.eventLoop(50, () {
347 				otherColor += colorDelta;
348 				if(otherColor > 1.0) {
349 					otherColor = 1.0;
350 					colorDelta = -0.05;
351 				}
352 				if(otherColor < 0) {
353 					otherColor = 0;
354 					colorDelta = 0.05;
355 				}
356 				// at the end of the timer, we have to request a redraw
357 				// or we won't see the changes.
358 				window.redrawOpenGlSceneSoon();
359 			});
360 		}
361 		---
362 
363 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
364 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
365 		simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too.
366 
367 		This example program shows how you can set up a shader to draw a rectangle:
368 
369 		---
370 		import arsd.simpledisplay;
371 
372 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
373 
374 		void main() {
375 			// First thing we do, before creating the window, is declare what version we want.
376 			setOpenGLContextVersion(3, 3);
377 			// turning off legacy compat is required to use version 3.3 and newer
378 			openGLContextCompatible = false;
379 
380 			uint VAO;
381 			OpenGlShader shader;
382 
383 			// then we can create the window.
384 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
385 
386 			// additional setup needs to be done when it is visible, simpledisplay offers a property
387 			// for exactly that:
388 			window.visibleForTheFirstTime = delegate() {
389 				// now with the window loaded, we can start loading the modern opengl functions.
390 
391 				// you MUST set the context first.
392 				window.setAsCurrentOpenGlContext;
393 				// then load the remainder of the library
394 				gl3.loadDynamicLibrary();
395 
396 				// now you can create the shaders, etc.
397 				shader = new OpenGlShader(
398 					OpenGlShader.Source(GL_VERTEX_SHADER, `
399 						#version 330 core
400 						layout (location = 0) in vec3 aPos;
401 						void main() {
402 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
403 						}
404 					`),
405 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
406 						#version 330 core
407 						out vec4 FragColor;
408 						uniform vec4 mycolor;
409 						void main() {
410 							FragColor = mycolor;
411 						}
412 					`),
413 				);
414 
415 				// and do whatever other setup you want.
416 
417 				float[] vertices = [
418 					0.5f,  0.5f, 0.0f,  // top right
419 					0.5f, -0.5f, 0.0f,  // bottom right
420 					-0.5f, -0.5f, 0.0f,  // bottom left
421 					-0.5f,  0.5f, 0.0f   // top left
422 				];
423 				uint[] indices = [  // note that we start from 0!
424 					0, 1, 3,  // first Triangle
425 					1, 2, 3   // second Triangle
426 				];
427 				uint VBO, EBO;
428 				glGenVertexArrays(1, &VAO);
429 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
430 				glBindVertexArray(VAO);
431 
432 				glGenBuffers(1, &VBO);
433 				glGenBuffers(1, &EBO);
434 
435 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
436 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
437 
438 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
439 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
440 
441 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
442 				glEnableVertexAttribArray(0);
443 
444 				// the library will set the initial viewport and trigger our first draw,
445 				// so these next two lines are NOT needed. they are just here as comments
446 				// to show what would happen next.
447 
448 				// glViewport(0, 0, window.width, window.height);
449 				// window.redrawOpenGlSceneNow();
450 			};
451 
452 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
453 			// it is our render method.
454 			window.redrawOpenGlScene = delegate() {
455 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
456 				glClear(GL_COLOR_BUFFER_BIT);
457 
458 				glUseProgram(shader.shaderProgram);
459 
460 				// the shader helper class has methods to set uniforms too
461 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
462 
463 				glBindVertexArray(VAO);
464 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
465 			};
466 
467 			window.eventLoop(0);
468 		}
469 		---
470 
471 	This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread.
472 
473 	$(H3 $(ID vulkan) Vulkan)
474 
475 	See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
476 
477 	https://github.com/adamdruppe/VulkanizeDSdpy
478 
479 	https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
480 
481 	$(H3 $(ID topic-images) Displaying images)
482 		You can also load PNG images using [arsd.png].
483 
484 		---
485 		// dmd example.d simpledisplay.d color.d png.d
486 		import arsd.simpledisplay;
487 		import arsd.png;
488 
489 		void main() {
490 			auto image = Image.fromMemoryImage(readPng("image.png"));
491 			displayImage(image);
492 		}
493 		---
494 
495 		Compile with `dmd example.d simpledisplay.d png.d`.
496 
497 		If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel.
498 
499 	$(H3 $(ID topic-sprites) Sprites)
500 		The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link.
501 
502 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
503 
504 	$(H3 $(ID topic-clipboard) Clipboard)
505 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
506 
507 		It also has helpers for handling X-specific events.
508 
509 	$(H3 $(ID topic-dnd) Drag and Drop)
510 		See [enableDragAndDrop] and [draggable].
511 
512 	$(H3 $(ID topic-timers) Timers)
513 		There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer].
514 
515 		The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse.
516 
517 		---
518 			import arsd.simpledisplay;
519 
520 			void main() {
521 				auto window = new SimpleWindow(400, 400);
522 				// every 100 ms, it will draw a random line
523 				// on the window.
524 				window.eventLoop(100, {
525 					auto painter = window.draw();
526 
527 					import std.random;
528 					// random color
529 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
530 					// random line
531 					painter.drawLine(
532 						Point(uniform(0, window.width), uniform(0, window.height)),
533 						Point(uniform(0, window.width), uniform(0, window.height)));
534 
535 				});
536 			}
537 		---
538 
539 		The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish.
540 
541 		The pulse timer and instances of the [Timer] class may be combined at will.
542 
543 		---
544 			import arsd.simpledisplay;
545 
546 			void main() {
547 				auto window = new SimpleWindow(400, 400);
548 				auto timer = new Timer(1000, delegate {
549 					auto painter = window.draw();
550 					painter.clear();
551 				});
552 
553 				window.eventLoop(0);
554 			}
555 		---
556 
557 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
558 
559 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
560 		simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself.
561 
562 		See also: `xwindows.d` from my github.
563 
564 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
565 		`handleNativeEvent` and `handleNativeGlobalEvent`.
566 
567 	$(H3 $(ID topic-integration) Integration with other libraries)
568 		Integration with a third-party event loop is possible.
569 
570 		On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d.
571 
572 	$(H3 $(ID topic-guis) GUI widgets)
573 		simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you!
574 
575 		Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes.
576 
577 		Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.)
578 
579 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
580 
581 	$(H2 Platform-specific tips and tricks)
582 
583 	X_tips:
584 
585 	On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release.
586 
587 	$(H4 apitrace)
588 
589 	Out of the box, simpledisplay might not work as expected in combination with
590 	[apitrace](https://apitrace.github.io).
591 	However it can be instructed to specifically load the GL/GLX wrapper libraries provided by apitrace instead of
592 	the system libraries. This should restore the lost functionality.
593 
594 	$(NUMBERED_LIST
595 		* Compile with `-version=apitrace`.
596 		* When launching such a simpledisplay app, it must be able to locate the apitrace wrapper libraries.
597 		* Running the app will generate an apitrace trace file.
598 		  It should print a log message similar to "apitrace: loaded into /directory" during startup.
599 	)
600 
601 	There are multiple ways to enable a simpledisplay app to locate the wrapper libraries.
602 
603 	One way to achieved this is by pointing the `LD_LIBRARY_PATH` environment variable to the directory containing
604 	those wrappers.
605 
606 	```sh
607 	LD_LIBRARY_PATH=/path/to/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
608 
609 	# e.g.
610 	LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
611 	```
612 
613 	Alternatively, the simpledisplay app can also be launched via $(I apitrace).
614 
615 	```sh
616 	apitrace trace -a gl ./myapp
617 	```
618 
619 	Another way that seems to work is to preload `glxtrace.so` through `LD_PRELOAD`.
620 
621 	```sh
622 	LD_PRELOAD=/path/to/apitrace/wrappers/glxtrace.so ./myapp
623 	```
624 
625 	Windows_tips:
626 
627 	You can add icons or manifest files to your exe using a resource file.
628 
629 	To create a Windows .ico file, use the gimp or something. I'll write a helper
630 	program later.
631 
632 	Create `yourapp.rc`:
633 
634 	```rc
635 		1 ICON filename.ico
636 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
637 	```
638 
639 	And `yourapp.exe.manifest`:
640 
641 	```xml
642 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
643 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
644 		<assemblyIdentity
645 		    version="1.0.0.0"
646 		    processorArchitecture="*"
647 		    name="CompanyName.ProductName.YourApplication"
648 		    type="win32"
649 		/>
650 		<description>Your application description here.</description>
651 		<dependency>
652 		    <dependentAssembly>
653 			<assemblyIdentity
654 			    type="win32"
655 			    name="Microsoft.Windows.Common-Controls"
656 			    version="6.0.0.0"
657 			    processorArchitecture="*"
658 			    publicKeyToken="6595b64144ccf1df"
659 			    language="*"
660 			/>
661 		    </dependentAssembly>
662 		</dependency>
663 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
664 			<windowsSettings>
665 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
666 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
667 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
668 				<!-- to render crisply in DPI-unaware contexts -->
669 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
670 			</windowsSettings>
671 		</application>
672 		</assembly>
673 	```
674 
675 	You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`.
676 
677 	Doing this lets you opt into various new things since Windows XP.
678 
679 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
680 
681 	$(H2 Tips)
682 
683 	$(H3 Name conflicts)
684 
685 	simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import:
686 
687 	---
688 	static import sdpy = arsd.simpledisplay;
689 	import arsd.simpledisplay : SimpleWindow;
690 
691 	void main() {
692 		auto window = new SimpleWindow();
693 		sdpy.EventLoop.get.run();
694 	}
695 	---
696 
697 	$(H2 $(ID developer-notes) Developer notes)
698 
699 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
700 	implementation though.
701 
702 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
703 	suck. If I was rewriting it, I wouldn't do it that way again.
704 
705 	This file must not have any more required dependencies. If you need bindings, add
706 	them right to this file. Once it gets into druntime and is there for a while, remove
707 	bindings from here to avoid conflicts (or put them in an appropriate version block
708 	so it continues to just work on old dmd), but wait a couple releases before making the
709 	transition so this module remains usable with older versions of dmd.
710 
711 	You may have optional dependencies if needed by putting them in version blocks or
712 	template functions. You may also extend the module with other modules with UFCS without
713 	actually editing this - that is nice to do if you can.
714 
715 	Try to make functions work the same way across operating systems. I typically make
716 	it thinly wrap Windows, then emulate that on Linux.
717 
718 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
719 	Phobos! So try to avoid it.
720 
721 	See more comments throughout the source.
722 
723 	I realize this file is fairly large, but over half that is just bindings at the bottom
724 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
725 	to understand. I suggest you jump around the source by looking for a particular
726 	declaration you're interested in, like `class SimpleWindow` using your editor's search
727 	function, then look at one piece at a time.
728 
729 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
730 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
731 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
732 
733 	I live in the eastern United States, so I will most likely not be around at night in
734 	that US east timezone.
735 
736 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
737 
738 	Building documentation: use my adrdox generator, `dub run adrdox`.
739 
740 	Examples:
741 
742 	$(DIV $(ID Event-example))
743 	$(H3 $(ID event-example) Event example)
744 	This program creates a window and draws events inside them as they
745 	happen, scrolling the text in the window as needed. Run this program
746 	and experiment to get a feel for where basic input events take place
747 	in the library.
748 
749 	---
750 	// dmd example.d simpledisplay.d color.d
751 	import arsd.simpledisplay;
752 	import std.conv;
753 
754 	void main() {
755 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
756 
757 		int y = 0;
758 
759 		void addLine(string text) {
760 			auto painter = window.draw();
761 
762 			if(y + painter.fontHeight >= window.height) {
763 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
764 				y -= painter.fontHeight;
765 			}
766 
767 			painter.outlineColor = Color.red;
768 			painter.fillColor = Color.black;
769 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
770 
771 			painter.outlineColor = Color.white;
772 
773 			painter.drawText(Point(10, y), text);
774 
775 			y += painter.fontHeight;
776 		}
777 
778 		window.eventLoop(1000,
779 		  () {
780 			addLine("Timer went off!");
781 		  },
782 		  (KeyEvent event) {
783 			addLine(to!string(event));
784 		  },
785 		  (MouseEvent event) {
786 			addLine(to!string(event));
787 		  },
788 		  (dchar ch) {
789 			addLine(to!string(ch));
790 		  }
791 		);
792 	}
793 	---
794 
795 	If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too.
796 
797 	$(COMMENT
798 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
799 
800 	---
801 
802 	---
803 	)
804 
805 	History:
806 		Initial release in April 2011.
807 
808 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
809 
810 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
811 
812 		On October 5, 2024, apitrace support was added for Linux targets.
813 +/
814 module arsd.simpledisplay;
815 
816 import arsd.core;
817 
818 // FIXME: tetris demo
819 // FIXME: space invaders demo
820 // FIXME: asteroids demo
821 
822 /++ $(ID Pong-example)
823 	$(H3 Pong)
824 
825 	This program creates a little Pong-like game. Player one is controlled
826 	with the keyboard.  Player two is controlled with the mouse. It demos
827 	the pulse timer, event handling, and some basic drawing.
828 +/
829 version(demos)
830 unittest {
831 	// dmd example.d simpledisplay.d color.d
832 	import arsd.simpledisplay;
833 
834 	enum paddleMovementSpeed = 8;
835 	enum paddleHeight = 48;
836 
837 	void main() {
838 		auto window = new SimpleWindow(600, 400, "Pong game!");
839 
840 		int playerOnePosition, playerTwoPosition;
841 		int playerOneMovement, playerTwoMovement;
842 		int playerOneScore, playerTwoScore;
843 
844 		int ballX, ballY;
845 		int ballDx, ballDy;
846 
847 		void serve() {
848 			import std.random;
849 
850 			ballX = window.width / 2;
851 			ballY = window.height / 2;
852 			ballDx = uniform(-4, 4) * 3;
853 			ballDy = uniform(-4, 4) * 3;
854 			if(ballDx == 0)
855 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
856 		}
857 
858 		serve();
859 
860 		window.eventLoop(50, // set a 50 ms timer pulls
861 			// This runs once per timer pulse
862 			delegate () {
863 				auto painter = window.draw();
864 
865 				painter.clear();
866 
867 				// Update everyone's motion
868 				playerOnePosition += playerOneMovement;
869 				playerTwoPosition += playerTwoMovement;
870 
871 				ballX += ballDx;
872 				ballY += ballDy;
873 
874 				// Bounce off the top and bottom edges of the window
875 				if(ballY + 7 >= window.height)
876 					ballDy = -ballDy;
877 				if(ballY - 8 <= 0)
878 					ballDy = -ballDy;
879 
880 				// Bounce off the paddle, if it is in position
881 				if(ballX - 8 <= 16) {
882 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
883 						ballDx = -ballDx + 1; // add some speed to keep it interesting
884 						ballDy += playerOneMovement; // and y movement based on your controls too
885 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
886 					} else {
887 						// Missed it
888 						playerTwoScore ++;
889 						serve();
890 					}
891 				}
892 
893 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
894 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
895 						ballDx = -ballDx - 1;
896 						ballDy += playerTwoMovement;
897 						ballX = window.width - 24;
898 					} else {
899 						// Missed it
900 						playerOneScore ++;
901 						serve();
902 					}
903 				}
904 
905 				// Draw the paddles
906 				painter.outlineColor = Color.black;
907 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
908 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
909 
910 				// Draw the ball
911 				painter.fillColor = Color.red;
912 				painter.outlineColor = Color.yellow;
913 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
914 
915 				// Draw the score
916 				painter.outlineColor = Color.blue;
917 				import std.conv;
918 				painter.drawText(Point(64, 4), to!string(playerOneScore));
919 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
920 
921 			},
922 			delegate (KeyEvent event) {
923 				// Player 1's controls are the arrow keys on the keyboard
924 				if(event.key == Key.Down)
925 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
926 				if(event.key == Key.Up)
927 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
928 
929 			},
930 			delegate (MouseEvent event) {
931 				// Player 2's controls are mouse movement while the left button is held down
932 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
933 					if(event.dy > 0)
934 						playerTwoMovement = paddleMovementSpeed;
935 					else if(event.dy < 0)
936 						playerTwoMovement = -paddleMovementSpeed;
937 				} else {
938 					playerTwoMovement = 0;
939 				}
940 			}
941 		);
942 	}
943 }
944 
945 /++ $(H3 $(ID example-minesweeper) Minesweeper)
946 
947 	This minesweeper demo shows how we can implement another classic
948 	game with simpledisplay and shows some mouse input and basic output
949 	code.
950 +/
951 version(demos)
952 unittest {
953 	import arsd.simpledisplay;
954 
955 	enum GameSquare {
956 		mine = 0,
957 		clear,
958 		m1, m2, m3, m4, m5, m6, m7, m8
959 	}
960 
961 	enum UserSquare {
962 		unknown,
963 		revealed,
964 		flagged,
965 		questioned
966 	}
967 
968 	enum GameState {
969 		inProgress,
970 		lose,
971 		win
972 	}
973 
974 	GameSquare[] board;
975 	UserSquare[] userState;
976 	GameState gameState;
977 	int boardWidth;
978 	int boardHeight;
979 
980 	bool isMine(int x, int y) {
981 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
982 			return false;
983 		return board[y * boardWidth + x] == GameSquare.mine;
984 	}
985 
986 	GameState reveal(int x, int y) {
987 		if(board[y * boardWidth + x] == GameSquare.clear) {
988 			floodFill(userState, boardWidth, boardHeight,
989 				UserSquare.unknown, UserSquare.revealed,
990 				x, y,
991 				(x, y) {
992 					if(board[y * boardWidth + x] == GameSquare.clear)
993 						return true;
994 					else {
995 						userState[y * boardWidth + x] = UserSquare.revealed;
996 						return false;
997 					}
998 				});
999 		} else {
1000 			userState[y * boardWidth + x] = UserSquare.revealed;
1001 			if(isMine(x, y))
1002 				return GameState.lose;
1003 		}
1004 
1005 		foreach(state; userState) {
1006 			if(state == UserSquare.unknown || state == UserSquare.questioned)
1007 				return GameState.inProgress;
1008 		}
1009 
1010 		return GameState.win;
1011 	}
1012 
1013 	void initializeBoard(int width, int height, int numberOfMines) {
1014 		boardWidth = width;
1015 		boardHeight = height;
1016 		board.length = width * height;
1017 
1018 		userState.length = width * height;
1019 		userState[] = UserSquare.unknown;
1020 
1021 		import std.algorithm, std.random, std.range;
1022 
1023 		board[] = GameSquare.clear;
1024 
1025 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
1026 			board[minePosition] = GameSquare.mine;
1027 
1028 		int x;
1029 		int y;
1030 		foreach(idx, ref square; board) {
1031 			if(square == GameSquare.clear) {
1032 				int danger = 0;
1033 				danger += isMine(x-1, y-1)?1:0;
1034 				danger += isMine(x-1, y)?1:0;
1035 				danger += isMine(x-1, y+1)?1:0;
1036 				danger += isMine(x, y-1)?1:0;
1037 				danger += isMine(x, y+1)?1:0;
1038 				danger += isMine(x+1, y-1)?1:0;
1039 				danger += isMine(x+1, y)?1:0;
1040 				danger += isMine(x+1, y+1)?1:0;
1041 
1042 				square = cast(GameSquare) (danger + 1);
1043 			}
1044 
1045 			x++;
1046 			if(x == width) {
1047 				x = 0;
1048 				y++;
1049 			}
1050 		}
1051 	}
1052 
1053 	void redraw(SimpleWindow window) {
1054 		import std.conv;
1055 
1056 		auto painter = window.draw();
1057 
1058 		painter.clear();
1059 
1060 		final switch(gameState) with(GameState) {
1061 			case inProgress:
1062 				break;
1063 			case win:
1064 				painter.fillColor = Color.green;
1065 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1066 				return;
1067 			case lose:
1068 				painter.fillColor = Color.red;
1069 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1070 				return;
1071 		}
1072 
1073 		int x = 0;
1074 		int y = 0;
1075 
1076 		foreach(idx, square; board) {
1077 			auto state = userState[idx];
1078 
1079 			final switch(state) with(UserSquare) {
1080 				case unknown:
1081 					painter.outlineColor = Color.black;
1082 					painter.fillColor = Color(128,128,128);
1083 
1084 					painter.drawRectangle(
1085 						Point(x * 20, y * 20),
1086 						20, 20
1087 					);
1088 				break;
1089 				case revealed:
1090 					if(square == GameSquare.clear) {
1091 						painter.outlineColor = Color.white;
1092 						painter.fillColor = Color.white;
1093 
1094 						painter.drawRectangle(
1095 							Point(x * 20, y * 20),
1096 							20, 20
1097 						);
1098 					} else {
1099 						painter.outlineColor = Color.black;
1100 						painter.fillColor = Color.white;
1101 
1102 						painter.drawText(
1103 							Point(x * 20, y * 20),
1104 							to!string(square)[1..2],
1105 							Point(x * 20 + 20, y * 20 + 20),
1106 							TextAlignment.Center | TextAlignment.VerticalCenter);
1107 					}
1108 				break;
1109 				case flagged:
1110 					painter.outlineColor = Color.black;
1111 					painter.fillColor = Color.red;
1112 					painter.drawRectangle(
1113 						Point(x * 20, y * 20),
1114 						20, 20
1115 					);
1116 				break;
1117 				case questioned:
1118 					painter.outlineColor = Color.black;
1119 					painter.fillColor = Color.yellow;
1120 					painter.drawRectangle(
1121 						Point(x * 20, y * 20),
1122 						20, 20
1123 					);
1124 				break;
1125 			}
1126 
1127 			x++;
1128 			if(x == boardWidth) {
1129 				x = 0;
1130 				y++;
1131 			}
1132 		}
1133 
1134 	}
1135 
1136 	void main() {
1137 		auto window = new SimpleWindow(200, 200);
1138 
1139 		initializeBoard(10, 10, 10);
1140 
1141 		redraw(window);
1142 		window.eventLoop(0,
1143 			delegate (MouseEvent me) {
1144 				if(me.type != MouseEventType.buttonPressed)
1145 					return;
1146 				auto x = me.x / 20;
1147 				auto y = me.y / 20;
1148 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1149 					if(me.button == MouseButton.left) {
1150 						gameState = reveal(x, y);
1151 					} else {
1152 						userState[y*boardWidth+x] = UserSquare.flagged;
1153 					}
1154 					redraw(window);
1155 				}
1156 			}
1157 		);
1158 	}
1159 }
1160 
1161 // FIXME: tetris demo
1162 // FIXME: space invaders demo
1163 // FIXME: asteroids demo
1164 
1165 version(OSX) version(DigitalMars) version=OSXCocoa;
1166 
1167 version(Emscripten) {
1168 	version=allow_unimplemented_features;
1169 	version=without_opengl;
1170 }
1171 
1172 
1173 version(OSXCocoa) {
1174 	version=without_opengl;
1175 	version=allow_unimplemented_features;
1176 	// version=OSXCocoa;
1177 	// pragma(linkerDirective, "-framework Cocoa");
1178 }
1179 
1180 version(without_opengl) {
1181 	enum SdpyIsUsingIVGLBinds = false;
1182 } else /*version(Posix)*/ {
1183 	static if (__traits(compiles, (){import iv.glbinds;})) {
1184 		enum SdpyIsUsingIVGLBinds = true;
1185 		public import iv.glbinds;
1186 		//pragma(msg, "SDPY: using iv.glbinds");
1187 	} else {
1188 		enum SdpyIsUsingIVGLBinds = false;
1189 	}
1190 //} else {
1191 //	enum SdpyIsUsingIVGLBinds = false;
1192 }
1193 
1194 version(Windows) {
1195 	//import core.sys.windows.windows;
1196 	import core.sys.windows.winnls;
1197 	import core.sys.windows.windef;
1198 	import core.sys.windows.basetyps;
1199 	import core.sys.windows.winbase;
1200 	import core.sys.windows.winuser;
1201 	import core.sys.windows.shellapi;
1202 	import core.sys.windows.wingdi;
1203 	static import gdi = core.sys.windows.wingdi; // so i
1204 
1205 	pragma(lib, "gdi32");
1206 	pragma(lib, "user32");
1207 
1208 	// for AlphaBlend... a breaking change....
1209 	version(CRuntime_DigitalMars) { } else
1210 		pragma(lib, "msimg32");
1211 
1212 	// core.sys.windows.dwmapi
1213 	private {
1214 		/++
1215 			See_also:
1216 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute
1217 		 +/
1218 		extern extern(Windows) HRESULT DwmGetWindowAttribute(
1219 			HWND hwnd,
1220 			DWORD dwAttribute,
1221 			PVOID pvAttribute,
1222 			DWORD cbAttribute
1223 		) nothrow @nogc;
1224 
1225 		/++
1226 			See_also:
1227 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
1228 		 +/
1229 		extern extern(Windows) HRESULT DwmSetWindowAttribute(
1230 			HWND hwnd,
1231 			DWORD dwAttribute,
1232 			LPCVOID pvAttribute,
1233 			DWORD cbAttribute
1234 		) nothrow @nogc;
1235 
1236 		/++
1237 			See_also:
1238 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
1239 		 +/
1240 		enum DWMWINDOWATTRIBUTE {
1241 			// incomplete, only declare what we need
1242 
1243 			/++
1244 				Usage:
1245 					pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*`
1246 
1247 				$(NOTE
1248 					Requires Windows 11 or later.
1249 				)
1250 			 +/
1251 			DWMWA_WINDOW_CORNER_PREFERENCE = 33,
1252 		}
1253 
1254 		/++
1255 			See_also:
1256 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
1257 		 +/
1258 		enum DWM_WINDOW_CORNER_PREFERENCE {
1259 			/// System decision
1260 			DWMWCP_DEFAULT = 0,
1261 
1262 			/// Never
1263 			DWMWCP_DONOTROUND = 1,
1264 
1265 			// If "appropriate"
1266 			DWMWCP_ROUND = 2,
1267 
1268 			// If "appropriate", but smaller radius
1269 			DWMWCP_ROUNDSMALL = 3
1270 		}
1271 
1272 		bool fromDWM(
1273 			DWM_WINDOW_CORNER_PREFERENCE value,
1274 			out CornerStyle result,
1275 		) @safe pure nothrow @nogc {
1276 			switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1277 				case DWMWCP_DEFAULT:
1278 					result = CornerStyle.automatic;
1279 					return true;
1280 				case DWMWCP_DONOTROUND:
1281 					result = CornerStyle.rectangular;
1282 					return true;
1283 				case DWMWCP_ROUND:
1284 					result = CornerStyle.rounded;
1285 					return true;
1286 				case DWMWCP_ROUNDSMALL:
1287 					result = CornerStyle.roundedSlightly;
1288 					return true;
1289 				default:
1290 					return false;
1291 			}
1292 		}
1293 
1294 		bool toDWM(
1295 			CornerStyle value,
1296 			out DWM_WINDOW_CORNER_PREFERENCE result,
1297 		) @safe pure nothrow @nogc {
1298 			final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1299 				case CornerStyle.automatic:
1300 					result = DWMWCP_DEFAULT;
1301 					return true;
1302 				case CornerStyle.rectangular:
1303 					result = DWMWCP_DONOTROUND;
1304 					return true;
1305 				case CornerStyle.rounded:
1306 					result = DWMWCP_ROUND;
1307 					return true;
1308 				case CornerStyle.roundedSlightly:
1309 					result = DWMWCP_ROUNDSMALL;
1310 					return true;
1311 			}
1312 		}
1313 
1314 		pragma(lib, "dwmapi");
1315 	}
1316 } else version(Emscripten) {
1317 } else version (linux) {
1318 	//k8: this is hack for rdmd. sorry.
1319 	static import core.sys.linux.epoll;
1320 	static import core.sys.linux.timerfd;
1321 }
1322 
1323 
1324 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1325 
1326 // http://wiki.dlang.org/Simpledisplay.d
1327 
1328 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1329 
1330 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1331 // but can i control the scroll lock led
1332 
1333 
1334 // Note: if you are using Image on X, you might want to do:
1335 /*
1336 	static if(UsingSimpledisplayX11) {
1337 		if(!Image.impl.xshmAvailable) {
1338 			// the images will use the slower XPutImage, you might
1339 			// want to consider an alternative method to get better speed
1340 		}
1341 	}
1342 
1343 	If the shared memory extension is available though, simpledisplay uses it
1344 	for a significant speed boost whenever you draw large Images.
1345 */
1346 
1347 // CHANGE FROM LAST VERSION: the window background is no longer fixed, so you might want to fill the screen with a particular color before drawing.
1348 
1349 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1350 
1351 /*
1352 	Biggest FIXME:
1353 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1354 
1355 		clean up opengl contexts when their windows close
1356 
1357 		fix resizing the bitmaps/pixmaps
1358 */
1359 
1360 // BTW on Windows:
1361 // -L/SUBSYSTEM:WINDOWS:5.0
1362 // to dmd will make a nice windows binary w/o a console if you want that.
1363 
1364 /*
1365 	Stuff to add:
1366 
1367 	use multibyte functions everywhere we can
1368 
1369 	OpenGL windows
1370 	more event stuff
1371 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1372 
1373 
1374 	resizeEvent
1375 		and make the windows non-resizable by default,
1376 		or perhaps stretched (if I can find something in X like StretchBlt)
1377 
1378 	take a screenshot function!
1379 
1380 	Pens and brushes?
1381 	Maybe a global event loop?
1382 
1383 	Mouse deltas
1384 	Key items
1385 */
1386 
1387 /*
1388 From MSDN:
1389 
1390 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1391 
1392 Important  Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates of the cursor position because these macros return incorrect results on systems with multiple monitors. Systems with multiple monitors can have negative x- and y- coordinates, and LOWORD and HIWORD treat the coordinates as unsigned quantities.
1393 
1394 */
1395 
1396 version(Emscripten) {
1397 
1398 } else version(linux) {
1399 	version = X11;
1400 	version(without_libnotify) {
1401 		// we cool
1402 	}
1403 	else
1404 		version = libnotify;
1405 }
1406 
1407 version(libnotify) {
1408 	pragma(lib, "dl");
1409 	import core.sys.posix.dlfcn;
1410 
1411 	void delegate()[int] libnotify_action_delegates;
1412 	int libnotify_action_delegates_count;
1413 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1414 		auto idx = cast(int) user_data;
1415 		if(auto dgptr = idx in libnotify_action_delegates) {
1416 			(*dgptr)();
1417 			libnotify_action_delegates.remove(idx);
1418 		}
1419 	}
1420 
1421 	struct C_DynamicLibrary {
1422 		void* handle;
1423 		this(string name) {
1424 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1425 			if(handle is null)
1426 				throw new Exception("dlopen");
1427 		}
1428 
1429 		void close() {
1430 			dlclose(handle);
1431 		}
1432 
1433 		~this() {
1434 			// close
1435 		}
1436 
1437 		// FIXME: this looks up by name every time....
1438 		template call(string func, Ret, Args...) {
1439 			extern(C) Ret function(Args) fptr;
1440 			typeof(fptr) call() {
1441 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1442 				return fptr;
1443 			}
1444 		}
1445 	}
1446 
1447 	C_DynamicLibrary* libnotify;
1448 }
1449 
1450 version(OSX) {
1451 	version(OSXCocoa) {}
1452 	else { version = X11; }
1453 }
1454 	//version = OSXCocoa; // this was written by KennyTM
1455 version(FreeBSD)
1456 	version = X11;
1457 version(Solaris)
1458 	version = X11;
1459 
1460 version(X11) {
1461 	version(without_xft) {}
1462 	else version=with_xft;
1463 }
1464 
1465 void featureNotImplemented()() {
1466 	version(allow_unimplemented_features)
1467 		throw new NotYetImplementedException();
1468 	else
1469 		static assert(0);
1470 }
1471 
1472 // these are so the static asserts don't trigger unless you want to
1473 // add support to it for an OS
1474 version(Windows)
1475 	version = with_timer;
1476 version(linux)
1477 	version = with_timer;
1478 version(OSXCocoa)
1479 	version = with_timer;
1480 
1481 version(with_timer)
1482 	enum bool SimpledisplayTimerAvailable = true;
1483 else
1484 	enum bool SimpledisplayTimerAvailable = false;
1485 
1486 /// If you have to get down and dirty with implementation details, this helps figure out if Windows is available you can `static if(UsingSimpledisplayWindows) ...` more reliably than `version()` because `version` is module-local.
1487 version(Windows)
1488 	enum bool UsingSimpledisplayWindows = true;
1489 else
1490 	enum bool UsingSimpledisplayWindows = false;
1491 
1492 /// If you have to get down and dirty with implementation details, this helps figure out if X is available you can `static if(UsingSimpledisplayX11) ...` more reliably than `version()` because `version` is module-local.
1493 version(X11)
1494 	enum bool UsingSimpledisplayX11 = true;
1495 else
1496 	enum bool UsingSimpledisplayX11 = false;
1497 
1498 /// If you have to get down and dirty with implementation details, this helps figure out if Cocoa is available you can `static if(UsingSimpledisplayCocoa) ...` more reliably than `version()` because `version` is module-local.
1499 version(OSXCocoa)
1500 	enum bool UsingSimpledisplayCocoa = true;
1501 else
1502 	enum bool UsingSimpledisplayCocoa = false;
1503 
1504 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1505 version(Windows)
1506 	enum multipleWindowsSupported = true;
1507 else version(Emscripten)
1508 	enum multipleWindowsSupported = false;
1509 else version(X11)
1510 	enum multipleWindowsSupported = true;
1511 else version(OSXCocoa)
1512 	enum multipleWindowsSupported = true;
1513 else
1514 	static assert(0);
1515 
1516 version(without_opengl)
1517 	enum bool OpenGlEnabled = false;
1518 else
1519 	enum bool OpenGlEnabled = true;
1520 
1521 /++
1522 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1523 	If you mix this in above your `main` function, you no longer need to use the linker
1524 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1525 
1526 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1527 
1528 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1529 	stderr writeln. It will fail and throw an exception.
1530 
1531 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1532 
1533 	History:
1534 		Added November 24, 2021 (dub v10.4)
1535 +/
1536 mixin template EnableWindowsSubsystem() {
1537 	version(Windows)
1538 	version(CRuntime_Microsoft) {
1539 		pragma(linkerDirective, "/subsystem:windows");
1540 		version(LDC)
1541 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1542 		else
1543 			pragma(linkerDirective, "/entry:mainCRTStartup");
1544 	}
1545 }
1546 
1547 
1548 /++
1549 	After selecting a type from [WindowTypes], you may further customize
1550 	its behavior by setting one or more of these flags.
1551 
1552 
1553 	The different window types have different meanings of `normal`. If the
1554 	window type already is a good match for what you want to do, you should
1555 	just use [WindowFlags.normal], the default, which will do the right thing
1556 	for your users.
1557 
1558 	The window flags will not always be honored by the operating system
1559 	and window managers; they are hints, not commands.
1560 +/
1561 enum WindowFlags : int {
1562 	normal = 0, ///
1563 	skipTaskbar = 1, ///
1564 	alwaysOnTop = 2, ///
1565 	alwaysOnBottom = 4, ///
1566 	cannotBeActivated = 8, ///
1567 	alwaysRequestMouseMotionEvents = 16, /// By default, simpledisplay will attempt to optimize mouse motion event reporting when it detects a remote connection, causing them to only be issued if input is grabbed (see: [SimpleWindow.grabInput]). This means doing hover effects and mouse game control on a remote X connection may not work right. Include this flag to override this optimization and always request the motion events. However btw, if you are doing mouse game control, you probably want to grab input anyway, and hover events are usually expendable! So think before you use this flag.
1568 	extraComposite = 32, /// On windows this will make this a layered windows (not supported for child windows before windows 8) to support transparency and improve animation performance.
1569 	/++
1570 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1571 		it is still a top-level window. This should NOT be set separately for most window types.
1572 
1573 		A transient window will not keep the application open if its main window closes.
1574 
1575 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1576 
1577 
1578 		From the ICCM:
1579 
1580 		$(BLOCKQUOTE
1581 			It is important not to confuse WM_TRANSIENT_FOR with override-redirect. WM_TRANSIENT_FOR should be used in those cases where the pointer is not grabbed while the window is mapped (in other words, if other windows are allowed to be active while the transient is up). If other windows must be prevented from processing input (for example, when implementing pop-up menus), use override-redirect and grab the pointer while the window is mapped.
1582 
1583 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1584 		)
1585 
1586 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1587 
1588 		History:
1589 			Added February 23, 2021 but not yet stabilized.
1590 	+/
1591 	transient = 64,
1592 	/++
1593 		This indicates that the window manages its own platform-specific child window input focus. You must use a delegate, [SimpleWindow.setRequestedInputFocus], to set the input when requested. This delegate returns the handle to the window that should receive the focus.
1594 
1595 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1596 
1597 		History:
1598 			Added April 1, 2022
1599 	+/
1600 	managesChildWindowFocus = 128,
1601 
1602 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1603 }
1604 
1605 /++
1606 	When creating a window, you can pass a type to SimpleWindow's constructor,
1607 	then further customize the window by changing `WindowFlags`.
1608 
1609 
1610 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1611 	use. The others are there to build a foundation for a higher level GUI toolkit,
1612 	but are themselves not as high level as you might think from their names.
1613 
1614 	This list is based on the EMWH spec for X11.
1615 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1616 +/
1617 enum WindowTypes : int {
1618 	/// An ordinary application window.
1619 	normal,
1620 	/// A generic window without a title bar or border. You can draw on the entire area of the screen it takes up and use it as you wish. Remember that users don't really expect these though, so don't use it where a window of any other type is appropriate.
1621 	undecorated,
1622 	/// A window that doesn't actually display on screen. You can use it for cases where you need a dummy window handle to communicate with or something.
1623 	eventOnly,
1624 	/// A drop down menu, such as from a menu bar
1625 	dropdownMenu,
1626 	/// A popup menu, such as from a right click
1627 	popupMenu,
1628 	/// A popup bubble notification
1629 	notification,
1630 	/*
1631 	menu, /// a tearable menu bar
1632 	splashScreen, /// a loading splash screen for your application
1633 	tooltip, /// A tiny window showing temporary help text or something.
1634 	comboBoxDropdown,
1635 	toolbar
1636 	*/
1637 	/// a dialog box of some sort
1638 	dialog,
1639 	/// a child nested inside the parent. You must pass a parent window to the ctor
1640 	nestedChild,
1641 
1642 	/++
1643 		The type you get when you pass in an existing browser handle, which means most
1644 		of simpledisplay's fancy things will not be done since they were never set up.
1645 
1646 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1647 		failure; you should use the existing handle constructor.
1648 
1649 		History:
1650 			Added November 17, 2022 (previously it would have type `normal`)
1651 	+/
1652 	minimallyWrapped
1653 }
1654 
1655 
1656 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1657 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1658 private __gshared char* sdpyWindowClassStr = null;
1659 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1660 
1661 /**
1662 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1663 	You may want to change context version if you want to use advanced shaders or
1664 	other modern OpenGL techinques. This setting doesn't affect already created
1665 	windows. You may use version 2.1 as your default, which should be supported
1666 	by any box since 2006, so seems to be a reasonable choice.
1667 
1668 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1669 	old context creation code without any version specified. This is the safest
1670 	way to init OpenGL, but it may not give you access to advanced features.
1671 
1672 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1673 */
1674 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1675 
1676 /**
1677 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1678 	pipeline functions, and without "compatible" mode you won't be able to use
1679 	your old non-shader-based code with such contexts. By default SimpleDisplay
1680 	creates compatible context, so you can gradually upgrade your OpenGL code if
1681 	you want to (or leave it as is, as it should "just work").
1682 */
1683 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1684 
1685 /**
1686 	Set to `true` to allow creating OpenGL context with lower version than requested
1687 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1688 	`openGLContextFallbackActivated()` will return `true`.
1689 	*/
1690 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1691 
1692 /**
1693 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1694 	*/
1695 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1696 
1697 /++
1698 	History:
1699 		Added April 24, 2023  (dub v11.0)
1700 +/
1701 version(without_opengl) {} else
1702 auto openGLCurrentContext() {
1703 	version(Windows)
1704 		return wglGetCurrentContext();
1705 	else
1706 		return glXGetCurrentContext();
1707 }
1708 
1709 
1710 /**
1711 	Set window class name for all following `new SimpleWindow()` calls.
1712 
1713 	WARNING! For Windows, you should set your class name before creating any
1714 	window, and NEVER change it after that!
1715 */
1716 void sdpyWindowClass (const(char)[] v) {
1717 	import core.stdc.stdlib : realloc;
1718 	if (v.length == 0) v = "SimpleDisplayWindow";
1719 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1720 	if (sdpyWindowClassStr is null) return; // oops
1721 	sdpyWindowClassStr[0..v.length+1] = 0;
1722 	sdpyWindowClassStr[0..v.length] = v[];
1723 }
1724 
1725 /**
1726 	Get current window class name.
1727 */
1728 string sdpyWindowClass () @trusted {
1729 	if (sdpyWindowClassStr is null) return null;
1730 	foreach (immutable idx; 0..size_t.max-1) {
1731 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1732 	}
1733 	return null;
1734 }
1735 
1736 /++
1737 	Returns the logical DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor.
1738 
1739 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1740 +/
1741 float[2] getDpi() {
1742 	float[2] dpi;
1743 	version(Windows) {
1744 		HDC screen = GetDC(null);
1745 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1746 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1747 	} else version(X11) {
1748 		auto display = XDisplayConnection.get;
1749 		auto screen = DefaultScreen(display);
1750 
1751 		void fallback() {
1752 			/+
1753 			// 25.4 millimeters in an inch...
1754 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1755 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1756 			+/
1757 
1758 			// the physical size isn't actually as important as the logical size since this is
1759 			// all about scaling really
1760 			dpi[0] = 96;
1761 			dpi[1] = 96;
1762 		}
1763 
1764 		auto xft = getXftDpi();
1765 		if(xft is float.init)
1766 			fallback();
1767 		else {
1768 			dpi[0] = xft;
1769 			dpi[1] = xft;
1770 		}
1771 	}
1772 
1773 	return dpi;
1774 }
1775 
1776 version(X11)
1777 float getXftDpi() {
1778 	auto display = XDisplayConnection.get;
1779 
1780 	char* resourceString = XResourceManagerString(display);
1781 	XrmInitialize();
1782 
1783 	if (resourceString) {
1784 		auto db = XrmGetStringDatabase(resourceString);
1785 		XrmValue value;
1786 		char* type;
1787 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1788 			if (value.addr) {
1789 				import core.stdc.stdlib;
1790 				return atof(cast(char*) value.addr);
1791 			}
1792 		}
1793 	}
1794 
1795 	return float.init;
1796 }
1797 
1798 /++
1799 	Implementation used by [SimpleWindow.takeScreenshot].
1800 
1801 	Params:
1802 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1803 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1804 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1805 		x = the x-offset of the image to capture, from the left.
1806 		y = the y-offset of the image to capture, from the top.
1807 
1808 	History:
1809 		Added on March 14, 2021
1810 
1811 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1812 
1813 +/
1814 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1815 	TrueColorImage got;
1816 	version(X11) {
1817 		auto display = XDisplayConnection.get;
1818 		if(handle == 0)
1819 			handle = RootWindow(display, DefaultScreen(display));
1820 
1821 		if(width == 0 || height == 0) {
1822 			Window root;
1823 			int xpos, ypos;
1824 			uint widthret, heightret, borderret, depthret;
1825 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1826 
1827 			if(width == 0)
1828 				width = widthret;
1829 			if(height == 0)
1830 				height = heightret;
1831 		}
1832 
1833 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1834 
1835 		// https://github.com/adamdruppe/arsd/issues/98
1836 
1837 		auto i = new Image(image);
1838 		got = i.toTrueColorImage();
1839 
1840 		XDestroyImage(image);
1841 	} else version(Windows) {
1842 		auto hdc = GetDC(handle);
1843 		scope(exit) ReleaseDC(handle, hdc);
1844 
1845 		if(width == 0 || height == 0) {
1846 			BITMAP bmHeader;
1847 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1848 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1849 			if(width == 0)
1850 				width = bmHeader.bmWidth;
1851 			if(height == 0)
1852 				height = bmHeader.bmHeight;
1853 		}
1854 
1855 		auto i = new Image(width, height);
1856 		HDC hdcMem = CreateCompatibleDC(hdc);
1857 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1858 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1859 		SelectObject(hdcMem, hbmOld);
1860 		DeleteDC(hdcMem);
1861 
1862 		got = i.toTrueColorImage();
1863 	} else featureNotImplemented();
1864 
1865 	return got;
1866 }
1867 
1868 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1869 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1870 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1871 
1872 version(Windows)
1873 shared static this() {
1874 	auto lib = LoadLibrary("User32.dll");
1875 	if(lib is null)
1876 		return;
1877 	//scope(exit)
1878 		//FreeLibrary(lib);
1879 
1880 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1881 
1882 	if(SetProcessDpiAwarenessContext is null)
1883 		return;
1884 
1885 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1886 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1887 		//writeln(GetLastError());
1888 	}
1889 
1890 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1891 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1892 }
1893 
1894 /++
1895 	Blocking mode for event loop calls associated with a window instance.
1896 
1897 	History:
1898 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1899 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1900 		is, all would block until the application quit.
1901 
1902 		That behavior can still be achieved here with `untilApplicationQuits`,
1903 		or explicitly calling the top-level `EventLoop.get.run` function.
1904 +/
1905 enum BlockingMode {
1906 	/++
1907 		The event loop call will block until the whole application is ready
1908 		to quit if it is the only one running, but if it is nested inside
1909 		another one, it will only block until the window you're calling it on
1910 		closes.
1911 	+/
1912 	automatic             = 0x00,
1913 	/++
1914 		The event loop call will only return when the whole application
1915 		is ready to quit. This usually means all windows have been closed.
1916 
1917 		This is appropriate for your main application event loop.
1918 	+/
1919 	untilApplicationQuits = 0x01,
1920 	/++
1921 		The event loop will return when the window you're calling it on
1922 		closes. If there are other windows still open, they may be destroyed
1923 		unless you have another event loop running later.
1924 
1925 		This might be appropriate for a modal dialog box loop. Remember that
1926 		other windows are still processing input though, so you can end up
1927 		with a lengthy call stack if this happens in a loop, similar to a
1928 		recursive function (well, it literally is a recursive function, just
1929 		not an obvious looking one).
1930 	+/
1931 	untilWindowCloses     = 0x02,
1932 	/++
1933 		If an event loop is already running, this call will immediately
1934 		return, allowing the existing loop to handle it. If not, this call
1935 		will block until the condition you bitwise-or into the flag.
1936 
1937 		The default is to block until the application quits, same as with
1938 		the `automatic` setting (since if it were nested, which triggers until
1939 		window closes in automatic, this flag would instead not block at all),
1940 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1941 		it will only nest until the window closes. You might want that if you are
1942 		going to open two windows simultaneously and want closing just one of them
1943 		to trigger the event loop return.
1944 	+/
1945 	onlyIfNotNested       = 0x10,
1946 }
1947 
1948 /++
1949 	Window corner visuals preference
1950  +/
1951 enum CornerStyle {
1952 	/++
1953 		Use the default style automatically applied by the system or its window manager/compositor.
1954 	 +/
1955 	automatic,
1956 
1957 	/++
1958 		Prefer rectangular window corners
1959 	 +/
1960 	rectangular,
1961 
1962 	/++
1963 		Prefer rounded window corners
1964 	 +/
1965 	rounded,
1966 
1967 	/++
1968 		Prefer slightly-rounded window corners
1969 	 +/
1970 	roundedSlightly,
1971 }
1972 
1973 /++
1974 	The flagship window class.
1975 
1976 
1977 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1978 	out of more advanced or complex features of the underlying windowing system.
1979 
1980 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1981 	and get a suitable window to work with.
1982 
1983 	From there, you can opt into additional features, like custom resizability and OpenGL support
1984 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1985 	and customization flags with the final two constructor arguments.
1986 
1987 	If none of that works for you, you can also create a window using native function calls, then
1988 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1989 	though, if you do this, managing the window is still your own responsibility! Notably, you
1990 	will need to destroy it yourself.
1991 +/
1992 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1993 
1994 	/++
1995 		Copies the window's current state into a [TrueColorImage].
1996 
1997 		Be warned: this can be a very slow operation
1998 
1999 		History:
2000 			Actually implemented on March 14, 2021
2001 	+/
2002 	TrueColorImage takeScreenshot() {
2003 		version(Windows)
2004 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
2005 		else version(OSXCocoa)
2006 			throw new NotYetImplementedException();
2007 		else
2008 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
2009 	}
2010 
2011 	/++
2012 		Returns the actual logical DPI for the window on its current display monitor. If the window
2013 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
2014 
2015 		Please note this function may return zero if it doesn't know the answer!
2016 
2017 
2018 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
2019 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
2020 
2021 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
2022 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
2023 		window primarily resides on by checking the center point of the window against the monitor map.
2024 
2025 		Returns:
2026 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
2027 			assumes the X and Y dpi are the same.
2028 
2029 		History:
2030 			Added November 26, 2021 (dub v10.4)
2031 
2032 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
2033 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
2034 			that.
2035 
2036 		Bugs:
2037 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
2038 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
2039 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
2040 			and 1.5 on the secondary monitor.
2041 
2042 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
2043 			is a historical misnomer - the real thing of interest is the scale factor and due to
2044 			compatibility concerns the scale would modify dpi values to trick applications. But since
2045 			that's the terminology common out there, I used it too.
2046 
2047 		See_Also:
2048 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
2049 			as this since the window many be on a different monitor, but it is a reasonable fallback
2050 			to use if `actualDpi` returns 0.
2051 
2052 			[onDpiChanged] is changed when `actualDpi` has changed.
2053 	+/
2054 	int actualDpi() {
2055 		version(X11) bool useFallbackDpi = false;
2056 		if(!actualDpiLoadAttempted) {
2057 			// FIXME: do the actual monitor we are on
2058 			// and on X this is a good chance to load the monitor map.
2059 			version(Windows) {
2060 				if(GetDpiForWindow)
2061 					actualDpi_ = GetDpiForWindow(impl.hwnd);
2062 			} else version(X11) {
2063 				if(!xRandrInfoLoadAttemped) {
2064 					xRandrInfoLoadAttemped = true;
2065 					if(!XRandrLibrary.attempted) {
2066 						XRandrLibrary.loadDynamicLibrary();
2067 					}
2068 
2069 					if(XRandrLibrary.loadSuccessful) {
2070 						auto display = XDisplayConnection.get;
2071 						int scratch;
2072 						int major, minor;
2073 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
2074 							goto fallback;
2075 
2076 						XRRQueryVersion(display, &major, &minor);
2077 						if(major <= 1 && minor < 5)
2078 							goto fallback;
2079 
2080 						int count;
2081 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
2082 						if(monitors is null)
2083 							goto fallback;
2084 						scope(exit) XRRFreeMonitors(monitors);
2085 
2086 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
2087 						MonitorInfo.info.assumeSafeAppend();
2088 						foreach(idx, monitor; monitors[0 .. count]) {
2089 							MonitorInfo.info ~= MonitorInfo(
2090 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2091 								Size(monitor.mwidth, monitor.mheight),
2092 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
2093 							);
2094 
2095 							/+
2096 							if(monitor.mwidth == 0 || monitor.mheight == 0)
2097 							// unknown physical size, just guess 96 to avoid divide by zero
2098 							MonitorInfo.info ~= MonitorInfo(
2099 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2100 								Size(monitor.mwidth, monitor.mheight),
2101 								96
2102 							);
2103 							else
2104 							// and actual thing
2105 							MonitorInfo.info ~= MonitorInfo(
2106 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2107 								Size(monitor.mwidth, monitor.mheight),
2108 								minInternal(
2109 									// millimeter to int then rounding up.
2110 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
2111 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
2112 								)
2113 							);
2114 							+/
2115 						}
2116 					// writeln("Here", MonitorInfo.info);
2117 					}
2118 				}
2119 
2120 				if(XRandrLibrary.loadSuccessful) {
2121 					updateActualDpi(true);
2122 					// writeln("updated");
2123 
2124 					if(!requestedInput) {
2125 						// this is what requests live updates should the configuration change
2126 						// each time you select input, it sends an initial event, so very important
2127 						// to not get into a loop of selecting input, getting event, updating data,
2128 						// and reselecting input...
2129 						requestedInput = true;
2130 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
2131 						// writeln("requested input");
2132 					}
2133 				} else {
2134 					fallback:
2135 					// make sure we disable events that aren't coming
2136 					xrrEventBase = -1;
2137 					// best guess... respect the custom scaling user command to some extent at least though
2138 					useFallbackDpi = true;
2139 				}
2140 			} else version(OSXCocoa) {
2141 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
2142 			}
2143 			actualDpiLoadAttempted = true;
2144 		} else version(X11) if(MonitorInfo.info.length == 0) {
2145 			useFallbackDpi = true;
2146 		}
2147 
2148 		version(X11)
2149 		if(useFallbackDpi || actualDpi_ == 0) // FIXME: the actualDpi_ will be populated eventually when we get the first synthetic configure event from the window manager, but that might be a little while so actualDpi_ can be 0 until then...
2150 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
2151 		return actualDpi_;
2152 	}
2153 
2154 	private int actualDpi_;
2155 	private bool actualDpiLoadAttempted;
2156 
2157 	version(X11) private {
2158 		bool requestedInput;
2159 		static bool xRandrInfoLoadAttemped;
2160 		struct MonitorInfo {
2161 			Rectangle position;
2162 			Size size;
2163 			int dpi;
2164 
2165 			static MonitorInfo[] info;
2166 		}
2167 		bool screenPositionKnown;
2168 		int screenPositionX;
2169 		int screenPositionY;
2170 		void updateActualDpi(bool loadingNow = false) {
2171 			if(!loadingNow && !actualDpiLoadAttempted)
2172 				actualDpi(); // just to make it do the load
2173 			foreach(idx, m; MonitorInfo.info) {
2174 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
2175 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
2176 					actualDpi_ = m.dpi;
2177 					// writeln("monitor ", idx);
2178 					if(changed && onDpiChanged)
2179 						onDpiChanged();
2180 					break;
2181 				}
2182 			}
2183 		}
2184 	}
2185 
2186 	/++
2187 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2188 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2189 
2190 		History:
2191 			Added November 26, 2021 (dub v10.4)
2192 
2193 		See_Also:
2194 			[actualDpi]
2195 	+/
2196 	void delegate() onDpiChanged;
2197 
2198 	version(X11) {
2199 		void recreateAfterDisconnect() {
2200 			if(!stateDiscarded) return;
2201 
2202 			if(_parent !is null && _parent.stateDiscarded)
2203 				_parent.recreateAfterDisconnect();
2204 
2205 			bool wasHidden = hidden;
2206 
2207 			activeScreenPainter = null; // should already be done but just to confirm
2208 
2209 			actualDpi_ = 0;
2210 			actualDpiLoadAttempted = false;
2211 			xRandrInfoLoadAttemped = false;
2212 
2213 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2214 
2215 			if(auto dh = dropHandler) {
2216 				dropHandler = null;
2217 				enableDragAndDrop(this, dh);
2218 			}
2219 
2220 			if(recreateAdditionalConnectionState)
2221 				recreateAdditionalConnectionState();
2222 
2223 			hidden = wasHidden;
2224 			stateDiscarded = false;
2225 		}
2226 
2227 		bool stateDiscarded;
2228 		void discardConnectionState() {
2229 			if(XDisplayConnection.display)
2230 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2231 			if(discardAdditionalConnectionState)
2232 				discardAdditionalConnectionState();
2233 			stateDiscarded = true;
2234 		}
2235 
2236 		void delegate() discardAdditionalConnectionState;
2237 		void delegate() recreateAdditionalConnectionState;
2238 
2239 	}
2240 
2241 	private DropHandler dropHandler;
2242 
2243 	SimpleWindow _parent;
2244 	bool beingOpenKeepsAppOpen = true;
2245 	/++
2246 		This creates a window with the given options. The window will be visible and able to receive input as soon as you start your event loop. You may draw on it immediately after creating the window, without needing to wait for the event loop to start if you want.
2247 
2248 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2249 
2250 		Params:
2251 
2252 		width = the width of the window's client area, in pixels
2253 		height = the height of the window's client area, in pixels
2254 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2255 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2256 		resizable = [Resizability] has three options:
2257 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2258 			$(P `fixedSize` will not allow the user to resize the window.)
2259 			$(P `automaticallyScaleIfPossible` will allow the user to resize, but will still present the original size to the API user. The contents you draw will be scaled to the size the user chose. If this scaling is not efficient, the window will be fixed size. The `windowResized` event handler will never be called. This is the default.)
2260 		windowType = The type of window you want to make.
2261 		customizationFlags = A way to make a window without a border, always on top, skip taskbar, and more. Do not use this if one of the pre-defined [WindowTypes], given in the `windowType` argument, is a good match for what you need.
2262 		parent = the parent window, if applicable. This makes the child window nested inside the parent unless you set [WindowFlags.transient], which makes it a top-level window merely owned by the "parent".
2263 	+/
2264 	this(int width = 640, int height = 480, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2265 		claimGuiThread();
2266 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2267 		this._width = this._virtualWidth = width;
2268 		this._height = this._virtualHeight = height;
2269 		this.openglMode = opengl;
2270 		version(X11) {
2271 			// auto scale not implemented except with opengl and even there it is kinda weird
2272 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2273 				resizable = Resizability.fixedSize;
2274 		}
2275 		this.resizability = resizable;
2276 		this.windowType = windowType;
2277 		this.customizationFlags = customizationFlags;
2278 		this._title = (title is null ? "D Application" : title);
2279 		this._parent = parent;
2280 		impl.createWindow(width, height, this._title, opengl, parent);
2281 
2282 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2283 			beingOpenKeepsAppOpen = false;
2284 	}
2285 
2286 	/// ditto
2287 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2288 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2289 	}
2290 
2291 	/// Same as above, except using the `Size` struct instead of separate width and height.
2292 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2293 		this(size.width, size.height, title, opengl, resizable);
2294 	}
2295 
2296 	/// ditto
2297 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2298 		this(size, title, opengl, resizable);
2299 	}
2300 
2301 
2302 	/++
2303 		Creates a window based on the given [Image]. It's client area
2304 		width and height is equal to the image. (A window's client area
2305 		is the drawable space inside; it excludes the title bar, etc.)
2306 
2307 		Windows based on images will not be resizable and do not use OpenGL.
2308 
2309 		It will draw the image in upon creation, but this will be overwritten
2310 		upon any draws, including the initial window visible event.
2311 
2312 		You probably do not want to use this and it may be removed from
2313 		the library eventually, or I might change it to be a "permanent"
2314 		background image; one that is automatically drawn on it before any
2315 		other drawing event. idk.
2316 	+/
2317 	this(Image image, string title = null) {
2318 		this(image.width, image.height, title);
2319 		this.image = image;
2320 	}
2321 
2322 	/++
2323 		Wraps a native window handle with very little additional processing - notably no destruction
2324 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2325 		windows created through the low level API (so you can use platform-specific options and
2326 		other details SimpleWindow does not expose) available to the event loop wrappers.
2327 	+/
2328 	this(NativeWindowHandle nativeWindow) {
2329 		windowType = WindowTypes.minimallyWrapped;
2330 		version(Windows)
2331 			impl.hwnd = nativeWindow;
2332 		else version(X11) {
2333 			impl.window = nativeWindow;
2334 			if(nativeWindow)
2335 				display = XDisplayConnection.get(); // get initial display to not segfault
2336 		} else version(Emscripten) {
2337 			// FIXME
2338 		} else version(OSXCocoa) {
2339 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2340 		} else featureNotImplemented();
2341 		// FIXME: set the size correctly
2342 		_width = 1;
2343 		_height = 1;
2344 		if(nativeWindow)
2345 			nativeMapping[cast(void*) nativeWindow] = this;
2346 
2347 		beingOpenKeepsAppOpen = false;
2348 
2349 		if(nativeWindow)
2350 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2351 		_suppressDestruction = true; // so it doesn't try to close
2352 	}
2353 
2354 	/++
2355 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2356 		The delegate will be called when the window manager asks you to take focus.
2357 
2358 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2359 
2360 		History:
2361 			Added April 1, 2022 (dub v10.8)
2362 	+/
2363 	SimpleWindow delegate() setRequestedInputFocus;
2364 
2365 	/// Experimental, do not use yet
2366 	/++
2367 		Grabs exclusive input from the user until you release it with
2368 		[releaseInputGrab].
2369 
2370 
2371 		Note: it is extremely rude to do this without good reason.
2372 		Reasons may include doing some kind of mouse drag operation
2373 		or popping up a temporary menu that should get events and will
2374 		be dismissed at ease by the user clicking away.
2375 
2376 		Params:
2377 			keyboard = do you want to grab keyboard input?
2378 			mouse = grab mouse input?
2379 			confine = confine the mouse cursor to inside this window?
2380 
2381 		History:
2382 			Prior to March 11, 2021, grabbing the keyboard would always also
2383 			set the X input focus. Now, it only focuses if it is a non-transient
2384 			window and otherwise manages the input direction internally.
2385 
2386 			This means spurious focus/blur events will no longer be sent and the
2387 			application will not steal focus from other applications (which the
2388 			window manager may have rejected anyway).
2389 	+/
2390 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2391 		static if(UsingSimpledisplayX11) {
2392 			XSync(XDisplayConnection.get, 0);
2393 			if(keyboard) {
2394 				if(isTransient && _parent) {
2395 					/*
2396 					FIXME:
2397 						setting the keyboard focus is not actually that helpful, what I more likely want
2398 						is the events from the parent window to be sent over here if we're transient.
2399 					*/
2400 
2401 					_parent.inputProxy = this;
2402 				} else {
2403 
2404 					SimpleWindow setTo;
2405 					if(setRequestedInputFocus !is null)
2406 						setTo = setRequestedInputFocus();
2407 					if(setTo is null)
2408 						setTo = this;
2409 
2410 					// sdpyPrintDebugString("grabInput() ", setTo.impl.window;
2411 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2412 				}
2413 			}
2414 			if(mouse) {
2415 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2416 				EventMask.PointerMotionMask // FIXME: not efficient
2417 				| EventMask.ButtonPressMask
2418 				| EventMask.ButtonReleaseMask
2419 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2420 				)
2421 			{
2422 				XSync(XDisplayConnection.get, 0);
2423 				import core.stdc.stdio;
2424 				printf("Grab input failed %d\n", res);
2425 				//throw new Exception("Grab input failed");
2426 			} else {
2427 				// cool
2428 			}
2429 			}
2430 
2431 		} else version(Windows) {
2432 			// FIXME: keyboard?
2433 			SetCapture(impl.hwnd);
2434 			if(confine) {
2435 				RECT rcClip;
2436 				//RECT rcOldClip;
2437 				//GetClipCursor(&rcOldClip);
2438 				GetWindowRect(hwnd, &rcClip);
2439 				ClipCursor(&rcClip);
2440 			}
2441 		} else version(Emscripten) {
2442 			// nothing necessary
2443 		} else version(OSXCocoa) {
2444 			// throw new NotYetImplementedException();
2445 		} else static assert(0);
2446 	}
2447 
2448 	private Point imePopupLocation = Point(0, 0);
2449 
2450 	/++
2451 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2452 
2453 		Bugs:
2454 			Not implemented outside X11.
2455 	+/
2456 	void setIMEPopupLocation(Point location) {
2457 		static if(UsingSimpledisplayX11) {
2458 			imePopupLocation = location;
2459 			updateIMEPopupLocation();
2460 		} else {
2461 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2462 			// throw new NotYetImplementedException();
2463 		}
2464 	}
2465 
2466 	/// ditto
2467 	void setIMEPopupLocation(int x, int y) {
2468 		return setIMEPopupLocation(Point(x, y));
2469 	}
2470 
2471 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2472 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2473 	// receives a ConfigureNotify event
2474 	private void updateIMEPopupLocation() {
2475 		static if(UsingSimpledisplayX11) {
2476 			if (xic is null) {
2477 				return;
2478 			}
2479 
2480 			XPoint nspot;
2481 			nspot.x = cast(short) imePopupLocation.x;
2482 			nspot.y = cast(short) imePopupLocation.y;
2483 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2484 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2485 			XFree(preeditAttr);
2486 		}
2487 	}
2488 
2489 	private bool imeFocused = true;
2490 
2491 	/++
2492 		Tells the IME whether or not an input field is currently focused in the window.
2493 
2494 		Bugs:
2495 			Not implemented outside X11.
2496 	+/
2497 	void setIMEFocused(bool value) {
2498 		imeFocused = value;
2499 		updateIMEFocused();
2500 	}
2501 
2502 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2503 	private void updateIMEFocused() {
2504 		static if(UsingSimpledisplayX11) {
2505 			if (xic is null) {
2506 				return;
2507 			}
2508 
2509 			if (focused && imeFocused) {
2510 				XSetICFocus(xic);
2511 			} else {
2512 				XUnsetICFocus(xic);
2513 			}
2514 		}
2515 	}
2516 
2517 	/++
2518 		Returns the native window.
2519 
2520 		History:
2521 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2522 			to access it through the `impl` member (which is semi-supported
2523 			but platform specific and here it is simple enough to offer an accessor).
2524 
2525 		Bugs:
2526 			Not implemented outside Windows or X11.
2527 	+/
2528 	NativeWindowHandle nativeWindowHandle() {
2529 		version(X11)
2530 			return impl.window;
2531 		else version(Windows)
2532 			return impl.hwnd;
2533 		else
2534 			throw new NotYetImplementedException();
2535 	}
2536 
2537 	private bool isTransient() {
2538 		with(WindowTypes)
2539 		final switch(windowType) {
2540 			case normal, undecorated, eventOnly:
2541 			case nestedChild, minimallyWrapped:
2542 				return (customizationFlags & WindowFlags.transient) ? true : false;
2543 			case dropdownMenu, popupMenu, notification, dialog:
2544 				return true;
2545 		}
2546 	}
2547 
2548 	private SimpleWindow inputProxy;
2549 
2550 	/++
2551 		Releases the grab acquired by [grabInput].
2552 	+/
2553 	void releaseInputGrab() {
2554 		static if(UsingSimpledisplayX11) {
2555 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2556 			if(_parent)
2557 				_parent.inputProxy = null;
2558 		} else version(Windows) {
2559 			ReleaseCapture();
2560 			ClipCursor(null);
2561 		} else version(OSXCocoa) {
2562 			// throw new NotYetImplementedException();
2563 		} else version(Emscripten) {
2564 			// nothing needed
2565 		} else static assert(0);
2566 	}
2567 
2568 	/++
2569 		Sets the input focus to this window.
2570 
2571 		You shouldn't call this very often - please let the user control the input focus.
2572 	+/
2573 	void focus() {
2574 		static if(UsingSimpledisplayX11) {
2575 			SimpleWindow setTo;
2576 			if(setRequestedInputFocus !is null)
2577 				setTo = setRequestedInputFocus();
2578 			if(setTo is null)
2579 				setTo = this;
2580 			// sdpyPrintDebugString("sdpy.focus() ", setTo.impl.window);
2581 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2582 		} else version(Windows) {
2583 			SetFocus(this.impl.hwnd);
2584 		} else version(Emscripten) {
2585 			throw new NotYetImplementedException();
2586 		} else version(OSXCocoa) {
2587 			throw new NotYetImplementedException();
2588 		} else static assert(0);
2589 	}
2590 
2591 	/++
2592 		Requests attention from the user for this window.
2593 
2594 
2595 		The typical result of this function is to change the color
2596 		of the taskbar icon, though it may be tweaked on specific
2597 		platforms.
2598 
2599 		It is meant to unobtrusively tell the user that something
2600 		relevant to them happened in the background and they should
2601 		check the window when they get a chance. Upon receiving the
2602 		keyboard focus, the window will automatically return to its
2603 		natural state.
2604 
2605 		If the window already has the keyboard focus, this function
2606 		may do nothing, because the user is presumed to already be
2607 		giving the window attention.
2608 
2609 		Implementation_note:
2610 
2611 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2612 		atom on X11 and the FlashWindow function on Windows.
2613 	+/
2614 	void requestAttention() {
2615 		if(_focused)
2616 			return;
2617 
2618 		version(Windows) {
2619 			FLASHWINFO info;
2620 			info.cbSize = info.sizeof;
2621 			info.hwnd = impl.hwnd;
2622 			info.dwFlags = FLASHW_TRAY;
2623 			info.uCount = 1;
2624 
2625 			FlashWindowEx(&info);
2626 
2627 		} else version(X11) {
2628 			demandingAttention = true;
2629 			demandAttention(this, true);
2630 		} else version(Emscripten) {
2631 			throw new NotYetImplementedException();
2632 		} else version(OSXCocoa) {
2633 			throw new NotYetImplementedException();
2634 		} else static assert(0);
2635 	}
2636 
2637 	private bool _focused;
2638 
2639 	version(X11) private bool demandingAttention;
2640 
2641 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2642 	/// You'll have to call `close()` manually if you set this delegate.
2643 	void delegate () closeQuery;
2644 
2645 	/// This will be called when window visibility was changed.
2646 	void delegate (bool becomesVisible) visibilityChanged;
2647 
2648 	/// This will be called when window becomes visible for the first time.
2649 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2650 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2651 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2652 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2653 	private bool _visibleForTheFirstTimeCalled;
2654 	void delegate () visibleForTheFirstTime;
2655 
2656 	/// Returns true if the window has been closed.
2657 	final @property bool closed() { return _closed; }
2658 
2659 	private final @property bool notClosed() { return !_closed; }
2660 
2661 	/// Returns true if the window is focused.
2662 	final @property bool focused() { return _focused; }
2663 
2664 	private bool _visible;
2665 	/// Returns true if the window is visible (mapped).
2666 	final @property bool visible() { return _visible; }
2667 
2668 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2669 	void close() {
2670 		if (!_closed) {
2671 			runInGuiThread( {
2672 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2673 				if (onClosing !is null) onClosing();
2674 				impl.closeWindow();
2675 				_closed = true;
2676 			} );
2677 		}
2678 	}
2679 
2680 	/++
2681 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2682 
2683 		History:
2684 			Overload added on March 7, 2021.
2685 	+/
2686 	void close() shared {
2687 		(cast() this).close();
2688 	}
2689 
2690 	/++
2691 
2692 	+/
2693 	void maximize() {
2694 		version(Windows)
2695 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2696 		else version(X11) {
2697 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2698 
2699 			// also note _NET_WM_STATE_FULLSCREEN
2700 		}
2701 
2702 	}
2703 
2704 	private bool _fullscreen;
2705 	version(Windows)
2706 	private WINDOWPLACEMENT g_wpPrev;
2707 
2708 	/// not fully implemented but planned for a future release
2709 	void fullscreen(bool yes) {
2710 		version(Windows) {
2711 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2712 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2713 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2714 				MONITORINFO mi;
2715 				mi.cbSize = MONITORINFO.sizeof;
2716 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2717 					GetMonitorInfo(MonitorFromWindow(hwnd,
2718 					               MONITOR_DEFAULTTOPRIMARY), &mi)) {
2719 					SetWindowLong(hwnd, GWL_STYLE,
2720 					              dwStyle & ~WS_OVERLAPPEDWINDOW);
2721 					SetWindowPos(hwnd, HWND_TOP,
2722 					             mi.rcMonitor.left, mi.rcMonitor.top,
2723 					             mi.rcMonitor.right - mi.rcMonitor.left,
2724 					             mi.rcMonitor.bottom - mi.rcMonitor.top,
2725 					             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2726 				}
2727 			} else {
2728 				SetWindowLong(hwnd, GWL_STYLE,
2729 				              dwStyle | WS_OVERLAPPEDWINDOW);
2730 				SetWindowPlacement(hwnd, &g_wpPrev);
2731 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2732 				             SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2733 				             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2734 			}
2735 
2736 		} else version(X11) {
2737 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2738 		}
2739 
2740 		_fullscreen = yes;
2741 
2742 	}
2743 
2744 	bool fullscreen() {
2745 		return _fullscreen;
2746 	}
2747 
2748 	/++
2749 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2750 
2751 	+/
2752 	void minimize() {
2753 		version(Windows)
2754 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2755 		//else version(X11)
2756 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2757 	}
2758 
2759 	/// Alias for `hidden = false`
2760 	void show() {
2761 		hidden = false;
2762 	}
2763 
2764 	/// Alias for `hidden = true`
2765 	void hide() {
2766 		hidden = true;
2767 	}
2768 
2769 	/// Hide cursor when it enters the window.
2770 	void hideCursor() {
2771 		version(OSXCocoa) throw new NotYetImplementedException(); else
2772 		if (!_closed) impl.hideCursor();
2773 	}
2774 
2775 	/// Don't hide cursor when it enters the window.
2776 	void showCursor() {
2777 		version(OSXCocoa) throw new NotYetImplementedException(); else
2778 		if (!_closed) impl.showCursor();
2779 	}
2780 
2781 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2782 	 *
2783 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2784 	 * control. Try to think for other approaches before using this function.
2785 	 *
2786 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2787 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2788 	 *       receive "mouse moved here" event.
2789 	 */
2790 	bool warpMouse (int x, int y) {
2791 		version(X11) {
2792 			if (!_closed) { impl.warpMouse(x, y); return true; }
2793 		} else version(Windows) {
2794 			if (!_closed) {
2795 				POINT point;
2796 				point.x = x;
2797 				point.y = y;
2798 				if(ClientToScreen(impl.hwnd, &point)) {
2799 					SetCursorPos(point.x, point.y);
2800 					return true;
2801 				}
2802 			}
2803 		}
2804 		return false;
2805 	}
2806 
2807 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2808 	void sendDummyEvent () {
2809 		version(X11) {
2810 			if (!_closed) { impl.sendDummyEvent(); }
2811 		}
2812 	}
2813 
2814 	/// Set window minimal size.
2815 	void setMinSize (int minwidth, int minheight) {
2816 		version(OSXCocoa) throw new NotYetImplementedException(); else
2817 		if (!_closed) impl.setMinSize(minwidth, minheight);
2818 	}
2819 
2820 	/// Set window maximal size.
2821 	void setMaxSize (int maxwidth, int maxheight) {
2822 		version(OSXCocoa) throw new NotYetImplementedException(); else
2823 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2824 	}
2825 
2826 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2827 	/// Currently only supported on X11.
2828 	void setResizeGranularity (int granx, int grany) {
2829 		version(OSXCocoa) throw new NotYetImplementedException(); else
2830 		if (!_closed) impl.setResizeGranularity(granx, grany);
2831 	}
2832 
2833 	/// Move window.
2834 	void move(int x, int y) {
2835 		version(OSXCocoa) throw new NotYetImplementedException(); else
2836 		if (!_closed) impl.move(x, y);
2837 	}
2838 
2839 	/// ditto
2840 	void move(Point p) {
2841 		version(OSXCocoa) throw new NotYetImplementedException(); else
2842 		if (!_closed) impl.move(p.x, p.y);
2843 	}
2844 
2845 	/++
2846 		Resize window.
2847 
2848 		Note that the width and height of the window are NOT instantly
2849 		updated - it waits for the window manager to approve the resize
2850 		request, which means you must return to the event loop before the
2851 		width and height are actually changed.
2852 	+/
2853 	void resize(int w, int h) {
2854 		if(!_closed && _fullscreen) fullscreen = false;
2855 		version(OSXCocoa) throw new NotYetImplementedException(); else
2856 		if (!_closed) impl.resize(w, h);
2857 	}
2858 
2859 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2860 	void moveResize (int x, int y, int w, int h) {
2861 		if(!_closed && _fullscreen) fullscreen = false;
2862 		version(OSXCocoa) throw new NotYetImplementedException(); else
2863 		if (!_closed) impl.moveResize(x, y, w, h);
2864 	}
2865 
2866 	private bool _hidden;
2867 
2868 	/// Returns true if the window is hidden.
2869 	final @property bool hidden() {
2870 		return _hidden;
2871 	}
2872 
2873 	/// Shows or hides the window based on the bool argument.
2874 	final @property void hidden(bool b) {
2875 		_hidden = b;
2876 		version(Windows) {
2877 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2878 		} else version(X11) {
2879 			if(b)
2880 				//XUnmapWindow(impl.display, impl.window);
2881 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2882 			else
2883 				XMapWindow(impl.display, impl.window);
2884 		} else version(OSXCocoa) {
2885 			// throw new NotYetImplementedException();
2886 		} else version(Emscripten) {
2887 		} else static assert(0);
2888 	}
2889 
2890 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2891 	void opacity(double opacity) @property
2892 	in {
2893 		assert(opacity >= 0 && opacity <= 1);
2894 	} do {
2895 		version (Windows) {
2896 			impl.setOpacity(cast(ubyte)(255 * opacity));
2897 		} else version (X11) {
2898 			impl.setOpacity(cast(uint)(uint.max * opacity));
2899 		} else throw new NotYetImplementedException();
2900 	}
2901 
2902 	/++
2903 		Sets your event handlers, without entering the event loop. Useful if you
2904 		have multiple windows - set the handlers on each window, then only do
2905 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2906 
2907 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2908 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2909 		delegate signatures.
2910 	+/
2911 	void setEventHandlers(T...)(T eventHandlers) {
2912 		// FIXME: add more events
2913 		foreach(handler; eventHandlers) {
2914 			static if(__traits(compiles, handleKeyEvent = handler)) {
2915 				handleKeyEvent = handler;
2916 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2917 				handleCharEvent = handler;
2918 			} else static if(__traits(compiles, handlePulse = handler)) {
2919 				handlePulse = handler;
2920 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2921 				handleMouseEvent = handler;
2922 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2923 		}
2924 	}
2925 
2926 	/++
2927 		The event loop automatically returns when the window is closed
2928 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2929 		pulse timer is created. The event loop will block until an event
2930 		arrives or the pulse timer goes off.
2931 
2932 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2933 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2934 		[handleMouseEvent], based on the signature of delegates you provide.
2935 
2936 		Give one with no parameters to set a timer pulse handler. Give one that
2937 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2938 		and one that takes `dchar` for a char event handler. You can use as many
2939 		or as few handlers as you need for your application.
2940 
2941 		Bugs:
2942 
2943 		$(PITFALL
2944 			You should always have one event loop live for your application.
2945 			If you make two windows in sequence, the second call to eventLoop
2946 			might fail:
2947 
2948 			---
2949 			// don't do this!
2950 			auto window = new SimpleWindow();
2951 			window.eventLoop(0);
2952 
2953 			auto window2 = new SimpleWindow();
2954 			window2.eventLoop(0); // problematic! might crash
2955 			---
2956 
2957 			simpledisplay's current implementation assumes that final cleanup is
2958 			done when the event loop refcount reaches zero. So after the first
2959 			eventLoop returns, when there isn't already another one active, it assumes
2960 			the program will exit soon and cleans up.
2961 
2962 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2963 			it eventually, but in the mean time, there's an easy solution:
2964 
2965 			---
2966 			// do this
2967 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2968 
2969 			auto window = new SimpleWindow();
2970 			window.eventLoop(0);
2971 
2972 			auto window2 = new SimpleWindow();
2973 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2974 			---
2975 
2976 			By adding a top-level reference to the event loop, it ensures the final cleanup
2977 			is not performed until it goes out of scope too, letting the individual window loops
2978 			work without trouble despite the bug.
2979 		)
2980 
2981 		History:
2982 			The overload without `pulseTimeout` was added on December 8, 2021.
2983 
2984 			On December 9, 2021, the default blocking mode (which is now configurable
2985 			because [eventLoopWithBlockingMode] was added) switched from
2986 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2987 			should almost never be noticeable to you since the typical simpledisplay
2988 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2989 
2990 		See_Also:
2991 			[eventLoopWithBlockingMode]
2992 	+/
2993 	final int eventLoop(T...)(
2994 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2995 		T eventHandlers) /// delegate list like std.concurrency.receive
2996 	{
2997 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2998 	}
2999 
3000 	/// ditto
3001 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
3002 	{
3003 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
3004 	}
3005 
3006 	/++
3007 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
3008 
3009 		History:
3010 			Added December 8, 2021 (dub v10.5)
3011 
3012 			Previously, this implementation was right inside [eventLoop], but when I wanted
3013 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
3014 			just renamed it instead of adding as an overload. Besides, the new name makes it
3015 			easier to remember the order and avoids ambiguity between two int-like params anyway.
3016 
3017 		See_Also:
3018 			[SimpleWindow.eventLoop], [EventLoop]
3019 
3020 		Bugs:
3021 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
3022 	+/
3023 	final int eventLoopWithBlockingMode(T...)(
3024 		BlockingMode blockingMode, /// when you want this function to block until
3025 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3026 		T eventHandlers) /// delegate list like std.concurrency.receive
3027 	{
3028 		setEventHandlers(eventHandlers);
3029 
3030 		version(with_eventloop) {
3031 			// delegates event loop to my other module
3032 			version(X11)
3033 				XFlush(display);
3034 
3035 			import arsd.eventloop;
3036 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
3037 			scope(exit) clearInterval(handle);
3038 
3039 			loop();
3040 			return 0;
3041 		} else version(OSXCocoa) {
3042 			// FIXME
3043 			if (handlePulse !is null && pulseTimeout != 0) {
3044 				timer = NSTimer.schedule(pulseTimeout*1e-3,
3045 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
3046 					null, true);
3047 			}
3048 
3049 			view.setNeedsDisplay(true);
3050 
3051 			NSApp.run();
3052 			return 0;
3053 		} else {
3054 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
3055 
3056 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
3057 				return 0;
3058 
3059 			return el.run(
3060 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
3061 					null :
3062 					&this.notClosed
3063 			);
3064 		}
3065 	}
3066 
3067 	/++
3068 		This lets you draw on the window (or its backing buffer) using basic
3069 		2D primitives.
3070 
3071 		Be sure to call this in a limited scope because your changes will not
3072 		actually appear on the window until ScreenPainter's destructor runs.
3073 
3074 		Returns: an instance of [ScreenPainter], which has the drawing methods
3075 		on it to draw on this window.
3076 
3077 		Params:
3078 			manualInvalidations = if you set this to true, you will need to
3079 			set the invalid rectangle on the painter yourself. If false, it
3080 			assumes the whole window has been redrawn each time you draw.
3081 
3082 			Only invalidated rectangles are blitted back to the window when
3083 			the destructor runs. Doing this yourself can reduce flickering
3084 			of child windows.
3085 
3086 		History:
3087 			The `manualInvalidations` parameter overload was added on
3088 			December 30, 2021 (dub v10.5)
3089 	+/
3090 	ScreenPainter draw() {
3091 		return draw(false);
3092 	}
3093 	/// ditto
3094 	ScreenPainter draw(bool manualInvalidations) {
3095 		return impl.getPainter(manualInvalidations);
3096 	}
3097 
3098 	// This is here to implement the interface we use for various native handlers.
3099 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
3100 
3101 	// maps native window handles to SimpleWindow instances, if there are any
3102 	// you shouldn't need this, but it is public in case you do in a native event handler or something
3103 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
3104 	version(OSXCocoa)
3105 	public __gshared SimpleWindow[void*] nativeMapping;
3106 	else
3107 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
3108 
3109 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
3110 	private int _virtualWidth;
3111 	private int _virtualHeight;
3112 
3113 	/// Width of the window's drawable client area, in pixels.
3114 	@scriptable
3115 	final @property int width() const pure nothrow @safe @nogc {
3116 		if(resizability == Resizability.automaticallyScaleIfPossible)
3117 			return _virtualWidth;
3118 		else
3119 			return _width;
3120 	}
3121 
3122 	/// Height of the window's drawable client area, in pixels.
3123 	@scriptable
3124 	final @property int height() const pure nothrow @safe @nogc {
3125 		if(resizability == Resizability.automaticallyScaleIfPossible)
3126 			return _virtualHeight;
3127 		else
3128 			return _height;
3129 	}
3130 
3131 	/++
3132 		Returns the actual size of the window, bypassing the logical
3133 		illusions of [Resizability.automaticallyScaleIfPossible].
3134 
3135 		History:
3136 			Added November 11, 2022 (dub v10.10)
3137 	+/
3138 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
3139 		return Size(_width, _height);
3140 	}
3141 
3142 
3143 	private int _width;
3144 	private int _height;
3145 
3146 	// HACK: making the best of some copy constructor woes with refcounting
3147 	private ScreenPainterImplementation* activeScreenPainter_;
3148 
3149 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
3150 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
3151 
3152 	private OpenGlOptions openglMode;
3153 	private Resizability resizability;
3154 	private WindowTypes windowType;
3155 	private int customizationFlags;
3156 
3157 	/// `true` if OpenGL was initialized for this window.
3158 	@property bool isOpenGL () const pure nothrow @safe @nogc {
3159 		version(without_opengl)
3160 			return false;
3161 		else
3162 			return (openglMode == OpenGlOptions.yes);
3163 	}
3164 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
3165 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
3166 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
3167 
3168 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3169 	/// to call this, as it's not recommended to share window between threads.
3170 	void mtLock () {
3171 		version(X11) {
3172 			XLockDisplay(this.display);
3173 		}
3174 	}
3175 
3176 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3177 	/// to call this, as it's not recommended to share window between threads.
3178 	void mtUnlock () {
3179 		version(X11) {
3180 			XUnlockDisplay(this.display);
3181 		}
3182 	}
3183 
3184 	/// Emit a beep to get user's attention.
3185 	void beep () {
3186 		version(X11) {
3187 			XBell(this.display, 100);
3188 		} else version(Windows) {
3189 			MessageBeep(0xFFFFFFFF);
3190 		}
3191 	}
3192 
3193 
3194 
3195 	version(without_opengl) {} else {
3196 
3197 		/// 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`.
3198 		void delegate() redrawOpenGlScene;
3199 
3200 		/// This will allow you to change OpenGL vsync state.
3201 		final @property void vsync (bool wait) {
3202 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3203 			version(X11) {
3204 				setAsCurrentOpenGlContext();
3205 				glxSetVSync(display, impl.window, wait);
3206 			} else version(Windows) {
3207 				setAsCurrentOpenGlContext();
3208 				wglSetVSync(wait);
3209 			}
3210 		}
3211 
3212 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3213 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3214 		/// enough without waiting 'em to finish their frame business.
3215 		bool useGLFinish = true;
3216 
3217 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3218 		/// 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.
3219 		void redrawOpenGlSceneNow() {
3220 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3221 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3222 			if(redrawOpenGlScene is null)
3223 				return;
3224 
3225 			this.mtLock();
3226 			scope(exit) this.mtUnlock();
3227 
3228 			this.setAsCurrentOpenGlContext();
3229 
3230 			redrawOpenGlScene();
3231 
3232 			this.swapOpenGlBuffers();
3233 			// 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.
3234 			if (useGLFinish) glFinish();
3235 		}
3236 
3237 		private bool redrawOpenGlSceneSoonSet = false;
3238 		private static class RedrawOpenGlSceneEvent {
3239 			SimpleWindow w;
3240 			this(SimpleWindow w) { this.w = w; }
3241 		}
3242 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3243 		/++
3244 			Queues an opengl redraw as soon as the other pending events are cleared.
3245 		+/
3246 		void redrawOpenGlSceneSoon() {
3247 			if(redrawOpenGlScene is null)
3248 				return;
3249 
3250 			if(!redrawOpenGlSceneSoonSet) {
3251 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3252 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3253 				redrawOpenGlSceneSoonSet = true;
3254 			}
3255 			this.postEvent(redrawOpenGlSceneEvent, true);
3256 		}
3257 
3258 
3259 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3260 		void setAsCurrentOpenGlContext() {
3261 			assert(openglMode == OpenGlOptions.yes);
3262 			version(X11) {
3263 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3264 					throw new Exception("glXMakeCurrent");
3265 			} else version(Windows) {
3266 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3267 				if (!wglMakeCurrent(ghDC, ghRC))
3268 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3269 			}
3270 		}
3271 
3272 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3273 		/// This doesn't throw, returning success flag instead.
3274 		bool setAsCurrentOpenGlContextNT() nothrow {
3275 			assert(openglMode == OpenGlOptions.yes);
3276 			version(X11) {
3277 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3278 			} else version(Windows) {
3279 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3280 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3281 			}
3282 		}
3283 
3284 		/// 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.
3285 		/// This doesn't throw, returning success flag instead.
3286 		bool releaseCurrentOpenGlContext() nothrow {
3287 			assert(openglMode == OpenGlOptions.yes);
3288 			version(X11) {
3289 				return (glXMakeCurrent(display, 0, null) != 0);
3290 			} else version(Windows) {
3291 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3292 				return wglMakeCurrent(ghDC, null) ? true : false;
3293 			}
3294 		}
3295 
3296 		/++
3297 			simpledisplay always uses double buffering, usually automatically. This
3298 			manually swaps the OpenGL buffers. You should only use this if you are NOT
3299 			using the [redrawOpenGlScene] delegate.
3300 
3301 
3302 			You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it
3303 			for you after calling your `redrawOpenGlScene`. Please note that once you swap
3304 			buffers, the contents become undefined - the implementation, in the OpenGL driver
3305 			or the desktop compositor, may not actually just swap two buffers. The back buffer's
3306 			contents are $(B undefined) after calling this function.
3307 
3308 			See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers
3309 			and https://linux.die.net/man/3/glxswapbuffers
3310 
3311 			Remember that this may throw an exception, which you can catch in a multithreaded
3312 			application to keep your thread from dying from an unhandled exception.
3313 		+/
3314 		void swapOpenGlBuffers() {
3315 			assert(openglMode == OpenGlOptions.yes);
3316 			version(X11) {
3317 				if (!this._visible) return; // no need to do this if window is invisible
3318 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3319 				glXSwapBuffers(display, impl.window);
3320 			} else version(Windows) {
3321 				SwapBuffers(ghDC);
3322 			}
3323 		}
3324 	}
3325 
3326 	/++
3327 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3328 
3329 
3330 		---
3331 			auto window = new SimpleWindow(100, 100, "First title");
3332 			window.title = "A new title";
3333 		---
3334 
3335 		You may call this function at any time.
3336 	+/
3337 	@property void title(string title) {
3338 		_title = title;
3339 		version(OSXCocoa) throw new NotYetImplementedException(); else
3340 		impl.setTitle(title);
3341 	}
3342 
3343 	private string _title;
3344 
3345 	/// Gets the title
3346 	@property string title() {
3347 		if(_title is null)
3348 			_title = getRealTitle();
3349 		return _title;
3350 	}
3351 
3352 	/++
3353 		Get the title as set by the window manager.
3354 		May not match what you attempted to set.
3355 	+/
3356 	string getRealTitle() {
3357 		static if(is(typeof(impl.getTitle())))
3358 			return impl.getTitle();
3359 		else
3360 			return null;
3361 	}
3362 
3363 	// don't use this generally it is not yet really released
3364 	version(X11)
3365 	@property Image secret_icon() {
3366 		return secret_icon_inner;
3367 	}
3368 	private Image secret_icon_inner;
3369 
3370 
3371 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3372 	@property void icon(MemoryImage icon) {
3373 		if(icon is null)
3374 			return;
3375 		auto tci = icon.getAsTrueColorImage();
3376 		version(Windows) {
3377 			winIcon = new WindowsIcon(icon);
3378 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3379 		} else version(X11) {
3380 			secret_icon_inner = Image.fromMemoryImage(icon);
3381 			// FIXME: ensure this is correct
3382 			auto display = XDisplayConnection.get;
3383 			arch_ulong[] buffer;
3384 			buffer ~= icon.width;
3385 			buffer ~= icon.height;
3386 			foreach(c; tci.imageData.colors) {
3387 				arch_ulong b;
3388 				b |= c.a << 24;
3389 				b |= c.r << 16;
3390 				b |= c.g << 8;
3391 				b |= c.b;
3392 				buffer ~= b;
3393 			}
3394 
3395 			XChangeProperty(
3396 				display,
3397 				impl.window,
3398 				GetAtom!("_NET_WM_ICON", true)(display),
3399 				GetAtom!"CARDINAL"(display),
3400 				32 /* bits */,
3401 				0 /*PropModeReplace*/,
3402 				buffer.ptr,
3403 				cast(int) buffer.length);
3404 		} else version(OSXCocoa) {
3405 			throw new NotYetImplementedException();
3406 		} else version(Emscripten) {
3407 			throw new NotYetImplementedException();
3408 		} else static assert(0);
3409 	}
3410 
3411 	version(Windows)
3412 		private WindowsIcon winIcon;
3413 
3414 	bool _suppressDestruction;
3415 
3416 	~this() {
3417 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3418 		if(_suppressDestruction)
3419 			return;
3420 		impl.dispose();
3421 	}
3422 
3423 	private bool _closed;
3424 
3425 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3426 	/*
3427 	ScreenPainter drawTransiently() {
3428 		return impl.getPainter();
3429 	}
3430 	*/
3431 
3432 	/// Draws an image on the window. This is meant to provide quick look
3433 	/// of a static image generated elsewhere.
3434 	@property void image(Image i) {
3435 	/+
3436 		version(Windows) {
3437 			BITMAP bm;
3438 			HDC hdc = GetDC(hwnd);
3439 			HDC hdcMem = CreateCompatibleDC(hdc);
3440 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3441 
3442 			GetObject(i.handle, bm.sizeof, &bm);
3443 
3444 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3445 
3446 			SelectObject(hdcMem, hbmOld);
3447 			DeleteDC(hdcMem);
3448 			ReleaseDC(hwnd, hdc);
3449 
3450 			/*
3451 			RECT r;
3452 			r.right = i.width;
3453 			r.bottom = i.height;
3454 			InvalidateRect(hwnd, &r, false);
3455 			*/
3456 		} else
3457 		version(X11) {
3458 			if(!destroyed) {
3459 				if(i.usingXshm)
3460 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3461 				else
3462 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3463 			}
3464 		} else
3465 		version(OSXCocoa) {
3466 			draw().drawImage(Point(0, 0), i);
3467 			setNeedsDisplay(view, true);
3468 		} else static assert(0);
3469 	+/
3470 		auto painter = this.draw;
3471 		painter.drawImage(Point(0, 0), i);
3472 	}
3473 
3474 	/++
3475 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3476 
3477 		---
3478 		window.cursor = GenericCursor.Help;
3479 		// now the window mouse cursor is set to a generic help
3480 		---
3481 
3482 	+/
3483 	@property void cursor(MouseCursor cursor) {
3484 		version(OSXCocoa)
3485 			{} // featureNotImplemented();
3486 		else
3487 		if(this.impl.curHidden <= 0) {
3488 			static if(UsingSimpledisplayX11) {
3489 				auto ch = cursor.cursorHandle;
3490 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3491 			} else version(Windows) {
3492 				auto ch = cursor.cursorHandle;
3493 				impl.currentCursor = ch;
3494 				SetCursor(ch); // redraw without waiting for mouse movement to update
3495 			} else featureNotImplemented();
3496 		}
3497 
3498 	}
3499 
3500 	/// What follows are the event handlers. These are set automatically
3501 	/// by the eventLoop function, but are still public so you can change
3502 	/// them later. wasPressed == true means key down. false == key up.
3503 
3504 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3505 	void delegate(KeyEvent ke) handleKeyEvent;
3506 
3507 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3508 	void delegate(dchar c) handleCharEvent;
3509 
3510 	/// Handles a timer pulse. Settable through setEventHandlers.
3511 	void delegate() handlePulse;
3512 
3513 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3514 	void delegate(bool) onFocusChange;
3515 
3516 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3517 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3518 	void delegate() onClosing;
3519 
3520 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3521 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3522 	 * last minute cleanup. */
3523 	void delegate() onDestroyed;
3524 
3525 	static if (UsingSimpledisplayX11)
3526 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3527 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3528 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3529 	 *
3530 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3531 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3532 
3533 	//version(Windows)
3534 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3535 
3536 	private {
3537 		int lastMouseX = int.min;
3538 		int lastMouseY = int.min;
3539 		void mdx(ref MouseEvent ev) {
3540 			if(lastMouseX == int.min || lastMouseY == int.min) {
3541 				ev.dx = 0;
3542 				ev.dy = 0;
3543 			} else {
3544 				ev.dx = ev.x - lastMouseX;
3545 				ev.dy = ev.y - lastMouseY;
3546 			}
3547 
3548 			lastMouseX = ev.x;
3549 			lastMouseY = ev.y;
3550 		}
3551 	}
3552 
3553 	/// Mouse event handler. Settable through setEventHandlers.
3554 	void delegate(MouseEvent) handleMouseEvent;
3555 
3556 	/// use to redraw child widgets if you use system apis to add stuff
3557 	void delegate() paintingFinished;
3558 
3559 	void delegate() paintingFinishedDg() {
3560 		return paintingFinished;
3561 	}
3562 
3563 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3564 	/// for this to ever happen.
3565 	void delegate(int width, int height) windowResized;
3566 
3567 	/++
3568 		Platform specific - handle any native message this window gets.
3569 
3570 		Note: this is called *in addition to* other event handlers, unless you either:
3571 
3572 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3573 
3574 		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.
3575 
3576 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3577 
3578 		On X, it takes the form of `int delegate(XEvent)`.
3579 
3580 		History:
3581 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3582 
3583 			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.
3584 	+/
3585 	NativeEventHandler handleNativeEvent_;
3586 
3587 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3588 		return handleNativeEvent_;
3589 	}
3590 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3591 		handleNativeEvent_ = neh;
3592 	}
3593 
3594 	version(Windows)
3595 	// compatibility shim with the old deprecated way
3596 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3597 	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) {
3598 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3599 			auto ret = dg(h, m, w, l);
3600 			if(ret == 0)
3601 				r = 1;
3602 			return ret;
3603 		};
3604 	}
3605 
3606 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3607 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3608 	/// this instead and it will work the same way.
3609 	__gshared NativeEventHandler handleNativeGlobalEvent;
3610 
3611 //  private:
3612 	/// The native implementation is available, but you shouldn't use it unless you are
3613 	/// familiar with the underlying operating system, don't mind depending on it, and
3614 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3615 	/// do what you need to do with handleNativeEvent instead.
3616 	///
3617 	/// This is likely to eventually change to be just a struct holding platform-specific
3618 	/// handles instead of a template mixin at some point because I'm not happy with the
3619 	/// code duplication here (ironically).
3620 	mixin NativeSimpleWindowImplementation!() impl;
3621 
3622 	/**
3623 		This is in-process one-way (from anything to window) event sending mechanics.
3624 		It is thread-safe, so it can be used in multi-threaded applications to send,
3625 		for example, "wake up and repaint" events when thread completed some operation.
3626 		This will allow to avoid using timer pulse to check events with synchronization,
3627 		'cause event handler will be called in UI thread. You can stop guessing which
3628 		pulse frequency will be enough for your app.
3629 		Note that events handlers may be called in arbitrary order, i.e. last registered
3630 		handler can be called first, and vice versa.
3631 	*/
3632 public:
3633 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3634 	 * "spamming" window with events it can't cope with.
3635 	 * It is safe to call this from non-UI threads.
3636 	 */
3637 	@property bool eventQueueEmpty() () {
3638 		synchronized(this) {
3639 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3640 		}
3641 		return true;
3642 	}
3643 
3644 	/** Does our custom event queue contains at least one with the given type?
3645 	 * Can be used in simple cases to prevent "spamming" window with events
3646 	 * it can't cope with.
3647 	 * It is safe to call this from non-UI threads.
3648 	 */
3649 	@property bool eventQueued(ET:Object) () {
3650 		synchronized(this) {
3651 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3652 				if (!o.doProcess) {
3653 					if (cast(ET)(o.evt)) return true;
3654 				}
3655 			}
3656 		}
3657 		return false;
3658 	}
3659 
3660 	/++
3661 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3662 
3663 		History:
3664 			Added May 12, 2021
3665 	+/
3666 	void delegate(Exception e) nothrow eventUncaughtException;
3667 
3668 	/** Add listener for custom event. Can be used like this:
3669 	 *
3670 	 * ---------------------
3671 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3672 	 *   ...
3673 	 *   win.removeEventListener(eid);
3674 	 * ---------------------
3675 	 *
3676 	 * Returns: 0 on failure (should never happen, so ignore it)
3677 	 *
3678 	 * $(WARNING Don't use this method in object destructors!)
3679 	 *
3680 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3681 	 *           'cause if event handler id counter will overflow, you won't be able
3682 	 *           to register any more events.)
3683 	 */
3684 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3685 		if (dg is null) return 0; // ignore empty handlers
3686 		synchronized(this) {
3687 			//FIXME: abort on overflow?
3688 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3689 			EventHandlerEntry e;
3690 			e.dg = delegate (Object o) {
3691 				if (auto co = cast(ET)o) {
3692 					try {
3693 						dg(co);
3694 					} catch (Exception e) {
3695 						// sorry!
3696 						if(eventUncaughtException)
3697 							eventUncaughtException(e);
3698 					}
3699 					return true;
3700 				}
3701 				return false;
3702 			};
3703 			e.id = lastUsedHandlerId;
3704 			auto optr = eventHandlers.ptr;
3705 			eventHandlers ~= e;
3706 			if (eventHandlers.ptr !is optr) {
3707 				import core.memory : GC;
3708 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3709 			}
3710 			return lastUsedHandlerId;
3711 		}
3712 	}
3713 
3714 	/// Remove event listener. It is safe to pass invalid event id here.
3715 	/// $(WARNING Don't use this method in object destructors!)
3716 	void removeEventListener() (uint id) {
3717 		if (id == 0 || id > lastUsedHandlerId) return;
3718 		synchronized(this) {
3719 			foreach (immutable idx; 0..eventHandlers.length) {
3720 				if (eventHandlers[idx].id == id) {
3721 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3722 					eventHandlers[$-1].dg = null;
3723 					eventHandlers.length -= 1;
3724 					eventHandlers.assumeSafeAppend;
3725 					return;
3726 				}
3727 			}
3728 		}
3729 	}
3730 
3731 	/// Post event to queue. It is safe to call this from non-UI threads.
3732 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3733 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3734 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3735 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3736 		if (this.closed) return false; // closed windows can't handle events
3737 
3738 		// remove all events of type `ET`
3739 		void removeAllET () {
3740 			uint eidx = 0, ec = eventQueueUsed;
3741 			auto eptr = eventQueue.ptr;
3742 			while (eidx < ec) {
3743 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3744 				if (cast(ET)eptr.evt !is null) {
3745 					// i found her!
3746 					if (inCustomEventProcessor) {
3747 						// if we're in custom event processing loop, processor will clear it for us
3748 						eptr.evt = null;
3749 						++eidx;
3750 						++eptr;
3751 					} else {
3752 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3753 						ec = --eventQueueUsed;
3754 						// clear last event (it is already copied)
3755 						eventQueue.ptr[ec].evt = null;
3756 					}
3757 				} else {
3758 					++eidx;
3759 					++eptr;
3760 				}
3761 			}
3762 		}
3763 
3764 		if (evt is null) {
3765 			if (replace) { synchronized(this) removeAllET(); }
3766 			// ignore empty events, they can't be handled anyway
3767 			return false;
3768 		}
3769 
3770 		// add events even if no event FD/event object created yet
3771 		synchronized(this) {
3772 			if (replace) removeAllET();
3773 			if (eventQueueUsed == uint.max) return false; // just in case
3774 			if (eventQueueUsed < eventQueue.length) {
3775 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3776 			} else {
3777 				if (eventQueue.capacity == eventQueue.length) {
3778 					// need to reallocate; do a trick to ensure that old array is cleared
3779 					auto oarr = eventQueue;
3780 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3781 					// just in case, do yet another check
3782 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3783 					import core.memory : GC;
3784 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3785 				} else {
3786 					auto optr = eventQueue.ptr;
3787 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3788 					assert(eventQueue.ptr is optr);
3789 				}
3790 				++eventQueueUsed;
3791 				assert(eventQueueUsed == eventQueue.length);
3792 			}
3793 			if (!eventWakeUp()) {
3794 				// can't wake up event processor, so there is no reason to keep the event
3795 				assert(eventQueueUsed > 0);
3796 				eventQueue[--eventQueueUsed].evt = null;
3797 				return false;
3798 			}
3799 			return true;
3800 		}
3801 	}
3802 
3803 	/// Post event to queue. It is safe to call this from non-UI threads.
3804 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3805 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3806 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3807 		return postTimeout!ET(evt, 0, replace);
3808 	}
3809 
3810 private:
3811 	private import core.time : MonoTime;
3812 
3813 	version(Posix) {
3814 		__gshared int customEventFDRead = -1;
3815 		__gshared int customEventFDWrite = -1;
3816 		__gshared int customSignalFD = -1;
3817 	} else version(Windows) {
3818 		__gshared HANDLE customEventH = null;
3819 	}
3820 
3821 	// wake up event processor
3822 	static bool eventWakeUp () {
3823 		version(X11) {
3824 			import core.sys.posix.unistd : write;
3825 			ulong n = 1;
3826 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3827 			return true;
3828 		} else version(Windows) {
3829 			if (customEventH !is null) SetEvent(customEventH);
3830 			return true;
3831 		} else version(OSXCocoa) {
3832 			if(globalAppDelegate)
3833 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3834 			return true;
3835 		} else {
3836 			// not implemented for other OSes
3837 			return false;
3838 		}
3839 	}
3840 
3841 	static struct QueuedEvent {
3842 		Object evt;
3843 		bool timed = false;
3844 		MonoTime hittime = MonoTime.zero;
3845 		bool doProcess = false; // process event at the current iteration (internal flag)
3846 
3847 		this (Object aevt, uint toutmsecs) {
3848 			evt = aevt;
3849 			if (toutmsecs > 0) {
3850 				import core.time : msecs;
3851 				timed = true;
3852 				hittime = MonoTime.currTime+toutmsecs.msecs;
3853 			}
3854 		}
3855 	}
3856 
3857 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3858 	static struct EventHandlerEntry {
3859 		CustomEventHandler dg;
3860 		uint id;
3861 	}
3862 
3863 	uint lastUsedHandlerId;
3864 	EventHandlerEntry[] eventHandlers;
3865 	QueuedEvent[] eventQueue = null;
3866 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3867 	bool inCustomEventProcessor = false; // required to properly remove events
3868 
3869 	// process queued events and call custom event handlers
3870 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3871 	void processCustomEvents () @system {
3872 		bool hasSomethingToDo = false;
3873 		uint ecount;
3874 		bool ocep;
3875 		synchronized(this) {
3876 			ocep = inCustomEventProcessor;
3877 			inCustomEventProcessor = true;
3878 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3879 			auto ctt = MonoTime.currTime;
3880 			bool hasEmpty = false;
3881 			// mark events to process (this is required for `eventQueued()`)
3882 			foreach (ref qe; eventQueue[0..ecount]) {
3883 				if (qe.evt is null) { hasEmpty = true; continue; }
3884 				if (qe.timed) {
3885 					qe.doProcess = (qe.hittime <= ctt);
3886 				} else {
3887 					qe.doProcess = true;
3888 				}
3889 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3890 			}
3891 			if (!hasSomethingToDo) {
3892 				// remove empty events
3893 				if (hasEmpty) {
3894 					uint eidx = 0, ec = eventQueueUsed;
3895 					auto eptr = eventQueue.ptr;
3896 					while (eidx < ec) {
3897 						if (eptr.evt is null) {
3898 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3899 							ec = --eventQueueUsed;
3900 							eventQueue.ptr[ec].evt = null; // make GC life easier
3901 						} else {
3902 							++eidx;
3903 							++eptr;
3904 						}
3905 					}
3906 				}
3907 				inCustomEventProcessor = ocep;
3908 				return;
3909 			}
3910 		}
3911 		// process marked events
3912 		uint efree = 0; // non-processed events will be put at this index
3913 		EventHandlerEntry[] eh;
3914 		Object evt;
3915 		foreach (immutable eidx; 0..ecount) {
3916 			synchronized(this) {
3917 				if (!eventQueue[eidx].doProcess) {
3918 					// skip this event
3919 					assert(efree <= eidx);
3920 					if (efree != eidx) {
3921 						// copy this event to queue start
3922 						eventQueue[efree] = eventQueue[eidx];
3923 						eventQueue[eidx].evt = null; // just in case
3924 					}
3925 					++efree;
3926 					continue;
3927 				}
3928 				evt = eventQueue[eidx].evt;
3929 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3930 				if (evt is null) continue; // just in case
3931 				// try all handlers; this can be slow, but meh...
3932 				eh = eventHandlers;
3933 			}
3934 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3935 			evt = null;
3936 			eh = null;
3937 		}
3938 		synchronized(this) {
3939 			// move all unprocessed events to queue top; efree holds first "free index"
3940 			foreach (immutable eidx; ecount..eventQueueUsed) {
3941 				assert(efree <= eidx);
3942 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3943 				++efree;
3944 			}
3945 			eventQueueUsed = efree;
3946 			// wake up event processor on next event loop iteration if we have more queued events
3947 			// also, remove empty events
3948 			bool awaken = false;
3949 			uint eidx = 0, ec = eventQueueUsed;
3950 			auto eptr = eventQueue.ptr;
3951 			while (eidx < ec) {
3952 				if (eptr.evt is null) {
3953 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3954 					ec = --eventQueueUsed;
3955 					eventQueue.ptr[ec].evt = null; // make GC life easier
3956 				} else {
3957 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3958 					++eidx;
3959 					++eptr;
3960 				}
3961 			}
3962 			inCustomEventProcessor = ocep;
3963 		}
3964 	}
3965 
3966 	// for all windows in nativeMapping
3967 	package static void processAllCustomEvents () @system {
3968 
3969 		cleanupQueue.process();
3970 
3971 		justCommunication.processCustomEvents();
3972 
3973 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3974 			if (sw is null || sw.closed) continue;
3975 			sw.processCustomEvents();
3976 		}
3977 
3978 		runPendingRunInGuiThreadDelegates();
3979 	}
3980 
3981 	// 0: infinite (i.e. no scheduled events in queue)
3982 	uint eventQueueTimeoutMSecs () {
3983 		synchronized(this) {
3984 			if (eventQueueUsed == 0) return 0;
3985 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3986 			uint res = int.max;
3987 			auto ctt = MonoTime.currTime;
3988 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3989 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3990 				if (qe.doProcess) continue; // just in case
3991 				if (!qe.timed) return 1; // minimal
3992 				if (qe.hittime <= ctt) return 1; // minimal
3993 				auto tms = (qe.hittime-ctt).total!"msecs";
3994 				if (tms < 1) tms = 1; // safety net
3995 				if (tms >= int.max) tms = int.max-1; // and another safety net
3996 				if (res > tms) res = cast(uint)tms;
3997 			}
3998 			return (res >= int.max ? 0 : res);
3999 		}
4000 	}
4001 
4002 	// for all windows in nativeMapping
4003 	static uint eventAllQueueTimeoutMSecs () {
4004 		uint res = uint.max;
4005 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
4006 			if (sw is null || sw.closed) continue;
4007 			uint to = sw.eventQueueTimeoutMSecs();
4008 			if (to && to < res) {
4009 				res = to;
4010 				if (to == 1) break; // can't have less than this
4011 			}
4012 		}
4013 		return (res >= int.max ? 0 : res);
4014 	}
4015 
4016 	version(X11) {
4017 		ResizeEvent pendingResizeEvent;
4018 	}
4019 
4020 	/++
4021 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
4022 
4023 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
4024 		worth so you can disable it by setting this to `true`.
4025 
4026 		History:
4027 			Added November 13, 2022.
4028 	+/
4029 	public bool suppressAutoOpenglViewport = false;
4030 	private void updateOpenglViewportIfNeeded(int width, int height) {
4031 		if(suppressAutoOpenglViewport) return;
4032 
4033 		version(without_opengl) {} else
4034 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
4035 		// writeln(width, " ", height);
4036 			setAsCurrentOpenGlContextNT();
4037 			glViewport(0, 0, width, height);
4038 		}
4039 	}
4040 
4041 	// TODO: Implement on non-Windows platforms (where available).
4042 	private CornerStyle _fauxCornerStyle = CornerStyle.automatic;
4043 
4044 	/++
4045 		Style of the window's corners
4046 
4047 		$(WARNING
4048 			Currently only implemented on Windows targets.
4049 			Has no visual effect elsewhere.
4050 
4051 			Windows: Requires Windows 11 or later.
4052 		)
4053 
4054 		History:
4055 			Added September 09, 2024.
4056 	 +/
4057 	public CornerStyle cornerStyle() @trusted {
4058 		version(Windows) {
4059 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4060 			const apiResult = DwmGetWindowAttribute(
4061 				this.hwnd,
4062 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4063 				&dwmCorner,
4064 				typeof(dwmCorner).sizeof
4065 			);
4066 
4067 			if (apiResult != S_OK) {
4068 				// Unsupported?
4069 				if (apiResult == E_INVALIDARG) {
4070 					// Feature unsupported; Windows version probably too old.
4071 					// Requires Windows 11 (build 22000) or later.
4072 					return _fauxCornerStyle;
4073 				}
4074 
4075 				throw new WindowsApiException("DwmGetWindowAttribute", apiResult);
4076 			}
4077 
4078 			CornerStyle corner;
4079 			if (!dwmCorner.fromDWM(corner)) {
4080 				throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner);
4081 			}
4082 			return corner;
4083 		} else {
4084 			return _fauxCornerStyle;
4085 		}
4086 	}
4087 
4088 	/// ditto
4089 	public void cornerStyle(const CornerStyle corner) @trusted {
4090 		version(Windows) {
4091 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4092 			if (!corner.toDWM(dwmCorner)) {
4093 				assert(false, "This should have been impossible because of a final switch.");
4094 			}
4095 
4096 			const apiResult = DwmSetWindowAttribute(
4097 				this.hwnd,
4098 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4099 				&dwmCorner,
4100 				typeof(dwmCorner).sizeof
4101 			);
4102 
4103 			if (apiResult != S_OK) {
4104 				// Unsupported?
4105 				if (apiResult == E_INVALIDARG) {
4106 					// Feature unsupported; Windows version probably too old.
4107 					// Requires Windows 11 (build 22000) or later.
4108 					_fauxCornerStyle = corner;
4109 					return;
4110 				}
4111 
4112 				throw new WindowsApiException("DwmSetWindowAttribute", apiResult);
4113 			}
4114 		} else {
4115 			_fauxCornerStyle = corner;
4116 		}
4117 	}
4118 }
4119 
4120 version(OSXCocoa)
4121 	enum NSWindow NullWindow = null;
4122 else
4123 	enum NullWindow = NativeWindowHandle.init;
4124 
4125 /++
4126 	Magic pseudo-window for just posting events to a global queue.
4127 
4128 	Not entirely supported, I might delete it at any time.
4129 
4130 	Added Nov 5, 2021.
4131 +/
4132 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
4133 
4134 /* Drag and drop support { */
4135 version(X11) {
4136 
4137 } else version(Windows) {
4138 	import core.sys.windows.uuid;
4139 	import core.sys.windows.ole2;
4140 	import core.sys.windows.oleidl;
4141 	import core.sys.windows.objidl;
4142 	import core.sys.windows.wtypes;
4143 
4144 	pragma(lib, "ole32");
4145 	void initDnd() {
4146 		auto err = OleInitialize(null);
4147 		if(err != S_OK && err != S_FALSE)
4148 			throw new Exception("init");//err);
4149 	}
4150 }
4151 /* } End drag and drop support */
4152 
4153 
4154 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
4155 /// See [GenericCursor].
4156 class MouseCursor {
4157 	int osId;
4158 	bool isStockCursor;
4159 	private this(int osId) {
4160 		this.osId = osId;
4161 		this.isStockCursor = true;
4162 	}
4163 
4164 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
4165 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
4166 
4167 	version(Windows) {
4168 		HCURSOR cursor_;
4169 		HCURSOR cursorHandle() {
4170 			if(cursor_ is null)
4171 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
4172 			return cursor_;
4173 		}
4174 
4175 	} else static if(UsingSimpledisplayX11) {
4176 		Cursor cursor_ = None;
4177 		int xDisplaySequence;
4178 
4179 		Cursor cursorHandle() {
4180 			if(this.osId == None)
4181 				return None;
4182 
4183 			// we need to reload if we on a new X connection
4184 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
4185 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
4186 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
4187 			}
4188 			return cursor_;
4189 		}
4190 	}
4191 }
4192 
4193 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
4194 // https://tronche.com/gui/x/xlib/appendix/b/
4195 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
4196 /// 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.
4197 enum GenericCursorType {
4198 	Default, /// The default arrow pointer.
4199 	Wait, /// A cursor indicating something is loading and the user must wait.
4200 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
4201 	Help, /// A cursor indicating the user can get help about the pointer location.
4202 	Cross, /// A crosshair.
4203 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
4204 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
4205 	UpArrow, /// An arrow pointing straight up.
4206 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
4207 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
4208 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
4209 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
4210 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
4211 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
4212 
4213 }
4214 
4215 /*
4216 	X_plus == css cell == Windows ?
4217 */
4218 
4219 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
4220 static struct GenericCursor {
4221 	static:
4222 	///
4223 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
4224 		static MouseCursor mc;
4225 
4226 		auto type = __traits(getMember, GenericCursorType, str);
4227 
4228 		if(mc is null) {
4229 
4230 			version(Windows) {
4231 				int osId;
4232 				final switch(type) {
4233 					case GenericCursorType.Default: osId = IDC_ARROW; break;
4234 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
4235 					case GenericCursorType.Hand: osId = IDC_HAND; break;
4236 					case GenericCursorType.Help: osId = IDC_HELP; break;
4237 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
4238 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
4239 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
4240 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
4241 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
4242 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
4243 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
4244 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
4245 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
4246 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
4247 				}
4248 			} else static if(UsingSimpledisplayX11) {
4249 				int osId;
4250 				final switch(type) {
4251 					case GenericCursorType.Default: osId = None; break;
4252 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
4253 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
4254 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
4255 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
4256 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
4257 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
4258 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
4259 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
4260 
4261 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
4262 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
4263 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
4264 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
4265 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
4266 				}
4267 
4268 			} else {
4269 				int osId;
4270 				// featureNotImplemented();
4271 			}
4272 
4273 			mc = new MouseCursor(osId);
4274 		}
4275 		return mc;
4276 	}
4277 }
4278 
4279 
4280 /++
4281 	If you want to get more control over the event loop, you can use this.
4282 
4283 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4284 	to `EventLoop.get.run`.
4285 +/
4286 struct EventLoop {
4287 	@disable this();
4288 
4289 	/// Gets a reference to an existing event loop
4290 	static EventLoop get() {
4291 		return EventLoop(0, null);
4292 	}
4293 
4294 	static void quitApplication() {
4295 		version(use_arsd_core) {
4296 			import arsd.core;
4297 			ICoreEventLoop.exitApplication();
4298 		}
4299 		EventLoop.get().exit();
4300 	}
4301 
4302 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4303 
4304 	/// Construct an application-global event loop for yourself
4305 	/// See_Also: [SimpleWindow.setEventHandlers]
4306 	this(long pulseTimeout, void delegate() handlePulse) {
4307 		synchronized(monitor) {
4308 			if(impl is null) {
4309 				claimGuiThread();
4310 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4311 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4312 			} else {
4313 				if(pulseTimeout) {
4314 					impl.pulseTimeout = pulseTimeout;
4315 					impl.handlePulse = handlePulse;
4316 				}
4317 			}
4318 			impl.refcount++;
4319 		}
4320 	}
4321 
4322 	~this() {
4323 		if(impl is null)
4324 			return;
4325 		impl.refcount--;
4326 		if(impl.refcount == 0) {
4327 			impl.dispose();
4328 			if(thisIsGuiThread)
4329 				guiThreadFinalize();
4330 		}
4331 
4332 	}
4333 
4334 	this(this) {
4335 		if(impl is null)
4336 			return;
4337 		impl.refcount++;
4338 	}
4339 
4340 	/// Runs the event loop until the whileCondition, if present, returns false
4341 	int run(bool delegate() whileCondition = null) {
4342 		assert(impl !is null);
4343 		impl.notExited = true;
4344 		return impl.run(whileCondition);
4345 	}
4346 
4347 	/// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program)
4348 	void exit() {
4349 		assert(impl !is null);
4350 		impl.notExited = false;
4351 
4352 		version(use_arsd_core) {
4353 			import arsd.core;
4354 			ICoreEventLoop.exitApplication();
4355 		}
4356 	}
4357 
4358 	version(linux)
4359 	ref void delegate(int) signalHandler() {
4360 		assert(impl !is null);
4361 		return impl.signalHandler;
4362 	}
4363 
4364 	__gshared static EventLoopImpl* impl;
4365 }
4366 
4367 version(linux)
4368 	void delegate(int, int) globalHupHandler;
4369 
4370 version(Posix)
4371 	void makeNonBlocking(int fd) {
4372 		import fcntl = core.sys.posix.fcntl;
4373 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4374 		if(flags == -1)
4375 			throw new Exception("fcntl get");
4376 		flags |= fcntl.O_NONBLOCK;
4377 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4378 		if(s == -1)
4379 			throw new Exception("fcntl set");
4380 	}
4381 
4382 struct EventLoopImpl {
4383 	int refcount;
4384 
4385 	bool notExited = true;
4386 
4387 	version(Emscripten) {
4388 		void delegate(int) signalHandler;
4389 		static import unix = core.sys.posix.unistd;
4390 		static import err = core.stdc.errno;
4391 	} else
4392 	version(linux) {
4393 		static import ep = core.sys.linux.epoll;
4394 		static import unix = core.sys.posix.unistd;
4395 		static import err = core.stdc.errno;
4396 		import core.sys.linux.timerfd;
4397 
4398 		void delegate(int) signalHandler;
4399 	}
4400 
4401 	version(X11) {
4402 		int pulseFd = -1;
4403 		version(Emscripten) {} else
4404 		version(linux) ep.epoll_event[16] events = void;
4405 	} else version(Windows) {
4406 		Timer pulser;
4407 		HANDLE[] handles;
4408 	}
4409 
4410 
4411 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4412 	/// to call this, as it's not recommended to share window between threads.
4413 	void mtLock () {
4414 		version(X11) {
4415 			XLockDisplay(this.display);
4416 		}
4417 	}
4418 
4419 	version(X11)
4420 	auto display() { return XDisplayConnection.get; }
4421 
4422 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4423 	/// to call this, as it's not recommended to share window between threads.
4424 	void mtUnlock () {
4425 		version(X11) {
4426 			XUnlockDisplay(this.display);
4427 		}
4428 	}
4429 
4430 	version(with_eventloop)
4431 	void initialize(long pulseTimeout) {}
4432 	else
4433 	void initialize(long pulseTimeout) @system {
4434 		version(Windows) {
4435 			if(pulseTimeout && handlePulse !is null)
4436 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4437 
4438 			if (customEventH is null) {
4439 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4440 				if (customEventH !is null) {
4441 					handles ~= customEventH;
4442 				} else {
4443 					// this is something that should not be; better be safe than sorry
4444 					throw new Exception("can't create eventfd for custom event processing");
4445 				}
4446 			}
4447 
4448 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4449 		}
4450 
4451 		version(Emscripten) {
4452 
4453 		} else version(linux) {
4454 			prepareEventLoop();
4455 			{
4456 				auto display = XDisplayConnection.get;
4457 				// adding Xlib file
4458 				ep.epoll_event ev = void;
4459 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4460 				ev.events = ep.EPOLLIN;
4461 				ev.data.fd = display.fd;
4462 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4463 					throw new Exception("add x fd");// ~ to!string(epollFd));
4464 				displayFd = display.fd;
4465 			}
4466 
4467 			if(pulseTimeout && handlePulse !is null) {
4468 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4469 				if(pulseFd == -1)
4470 					throw new Exception("pulse timer create failed");
4471 
4472 				itimerspec value;
4473 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4474 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4475 
4476 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4477 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4478 
4479 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4480 					throw new Exception("couldn't make pulse timer");
4481 
4482 				ep.epoll_event ev = void;
4483 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4484 				ev.events = ep.EPOLLIN;
4485 				ev.data.fd = pulseFd;
4486 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4487 			}
4488 
4489 			// eventfd for custom events
4490 			if (customEventFDWrite == -1) {
4491 				customEventFDWrite = eventfd(0, 0);
4492 				customEventFDRead = customEventFDWrite;
4493 				if (customEventFDRead >= 0) {
4494 					ep.epoll_event ev = void;
4495 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4496 					ev.events = ep.EPOLLIN;
4497 					ev.data.fd = customEventFDRead;
4498 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4499 				} else {
4500 					// this is something that should not be; better be safe than sorry
4501 					throw new Exception("can't create eventfd for custom event processing");
4502 				}
4503 			}
4504 
4505 			if (customSignalFD == -1) {
4506 				import core.sys.linux.sys.signalfd;
4507 
4508 				sigset_t sigset;
4509 				auto err = sigemptyset(&sigset);
4510 				assert(!err);
4511 				err = sigaddset(&sigset, SIGINT);
4512 				assert(!err);
4513 				err = sigaddset(&sigset, SIGHUP);
4514 				assert(!err);
4515 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4516 				assert(!err);
4517 
4518 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4519 				assert(customSignalFD != -1);
4520 
4521 				ep.epoll_event ev = void;
4522 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4523 				ev.events = ep.EPOLLIN;
4524 				ev.data.fd = customSignalFD;
4525 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4526 			}
4527 		} else version(Posix) {
4528 			prepareEventLoop();
4529 			if (customEventFDRead == -1) {
4530 				int[2] bfr;
4531 				import core.sys.posix.unistd;
4532 				auto ret = pipe(bfr);
4533 				if(ret == -1) throw new Exception("pipe");
4534 				customEventFDRead = bfr[0];
4535 				customEventFDWrite = bfr[1];
4536 			}
4537 
4538 		}
4539 
4540 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4541 
4542 		version(linux) {
4543 			this.mtLock();
4544 			scope(exit) this.mtUnlock();
4545 			version(X11)
4546 				XPending(display); // no, really
4547 		}
4548 
4549 		disposed = false;
4550 	}
4551 
4552 	bool disposed = true;
4553 	version(X11)
4554 		int displayFd = -1;
4555 
4556 	version(with_eventloop)
4557 	void dispose() {}
4558 	else
4559 	void dispose() @system {
4560 		disposed = true;
4561 		version(X11) {
4562 			if(pulseFd != -1) {
4563 				import unix = core.sys.posix.unistd;
4564 				unix.close(pulseFd);
4565 				pulseFd = -1;
4566 			}
4567 
4568 				version(Emscripten) {} else
4569 				version(linux)
4570 				if(displayFd != -1) {
4571 					// 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
4572 					ep.epoll_event ev = void;
4573 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4574 					ev.events = ep.EPOLLIN;
4575 					ev.data.fd = displayFd;
4576 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4577 					displayFd = -1;
4578 				}
4579 
4580 		} else version(Windows) {
4581 			if(pulser !is null) {
4582 				pulser.destroy();
4583 				pulser = null;
4584 			}
4585 			if (customEventH !is null) {
4586 				CloseHandle(customEventH);
4587 				customEventH = null;
4588 			}
4589 		}
4590 	}
4591 
4592 	this(long pulseTimeout, void delegate() handlePulse) {
4593 		this.pulseTimeout = pulseTimeout;
4594 		this.handlePulse = handlePulse;
4595 		initialize(pulseTimeout);
4596 	}
4597 
4598 	private long pulseTimeout;
4599 	void delegate() handlePulse;
4600 
4601 	~this() {
4602 		dispose();
4603 	}
4604 
4605 	version(Posix)
4606 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4607 	version(Posix)
4608 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4609 	version(linux)
4610 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4611 	version(Windows)
4612 	ref auto customEventH() { return SimpleWindow.customEventH; }
4613 
4614 	version(X11) {
4615 		bool doXPending() {
4616 			bool done = false;
4617 
4618 			this.mtLock();
4619 			scope(exit) this.mtUnlock();
4620 			//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4621 			while(!done && XPending(display)) {
4622 				done = doXNextEvent(this.display);
4623 			}
4624 
4625 			return done;
4626 		}
4627 		void doXNextEventVoid() {
4628 			doXPending();
4629 		}
4630 	}
4631 
4632 	version(with_eventloop) {
4633 		int loopHelper(bool delegate() whileCondition) {
4634 			// FIXME: whileCondition
4635 			import arsd.eventloop;
4636 			loop();
4637 			return 0;
4638 		}
4639 	} else
4640 	int loopHelper(bool delegate() whileCondition) {
4641 		version(X11) {
4642 			bool done = false;
4643 
4644 			XFlush(display);
4645 			insideXEventLoop = true;
4646 			scope(exit) insideXEventLoop = false;
4647 
4648 			version(use_arsd_core) {
4649 				import arsd.core;
4650 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4651 
4652 				static bool loopInitialized = false;
4653 				if(!loopInitialized) {
4654 					el.addDelegateOnLoopIteration(&doXNextEventVoid, 0);
4655 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4656 
4657 					if(customSignalFD != -1)
4658 					cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() {
4659 						version(linux) {
4660 							import core.sys.linux.sys.signalfd;
4661 							import core.sys.posix.unistd : read;
4662 							signalfd_siginfo info;
4663 							read(customSignalFD, &info, info.sizeof);
4664 
4665 							auto sig = info.ssi_signo;
4666 
4667 							if(EventLoop.get.signalHandler !is null) {
4668 								EventLoop.get.signalHandler()(sig);
4669 							} else {
4670 								EventLoop.get.exit();
4671 							}
4672 						}
4673 					}));
4674 
4675 					if(display.fd != -1)
4676 					cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() {
4677 						this.mtLock();
4678 						scope(exit) this.mtUnlock();
4679 						while(!done && XPending(display)) {
4680 							done = doXNextEvent(this.display);
4681 						}
4682 					}));
4683 
4684 					if(pulseFd != -1)
4685 					cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() {
4686 						long expirationCount;
4687 						// 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...
4688 
4689 						handlePulse();
4690 
4691 						// read just to clear the buffer so poll doesn't trigger again
4692 						// BTW I read AFTER the pulse because if the pulse handler takes
4693 						// a lot of time to execute, we don't want the app to get stuck
4694 						// in a loop of timer hits without a chance to do anything else
4695 						//
4696 						// IOW handlePulse happens at most once per pulse interval.
4697 						unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4698 					}));
4699 
4700 					if(customEventFDRead != -1)
4701 					cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() {
4702 						// we have some custom events; process 'em
4703 						import core.sys.posix.unistd : read;
4704 						ulong n;
4705 						read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4706 						//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4707 						//SimpleWindow.processAllCustomEvents();
4708 					}));
4709 
4710 					// FIXME: posix fds
4711 					// FIXME up?
4712 
4713 
4714 					loopInitialized = true;
4715 				}
4716 
4717 				el.run(() => !whileCondition());
4718 			} else version(linux) {
4719 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4720 					bool forceXPending = false;
4721 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4722 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4723 					{
4724 						this.mtLock();
4725 						scope(exit) this.mtUnlock();
4726 						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
4727 					}
4728 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4729 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4730 					if(nfds == -1) {
4731 						if(err.errno == err.EINTR) {
4732 							//if(forceXPending) goto xpending;
4733 							continue; // interrupted by signal, just try again
4734 						}
4735 						throw new Exception("epoll wait failure");
4736 					}
4737 					// writeln(nfds, " ", events[0].data.fd);
4738 
4739 					SimpleWindow.processAllCustomEvents(); // anyway
4740 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4741 					foreach(idx; 0 .. nfds) {
4742 						if(done) break;
4743 						auto fd = events[idx].data.fd;
4744 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4745 						auto flags = events[idx].events;
4746 						if(flags & ep.EPOLLIN) {
4747 							if (fd == customSignalFD) {
4748 								version(linux) {
4749 									import core.sys.linux.sys.signalfd;
4750 									import core.sys.posix.unistd : read;
4751 									signalfd_siginfo info;
4752 									read(customSignalFD, &info, info.sizeof);
4753 
4754 									auto sig = info.ssi_signo;
4755 
4756 									if(EventLoop.get.signalHandler !is null) {
4757 										EventLoop.get.signalHandler()(sig);
4758 									} else {
4759 										EventLoop.get.exit();
4760 									}
4761 								}
4762 							} else if(fd == display.fd) {
4763 								version(sdddd) { writeln("X EVENT PENDING!"); }
4764 								this.mtLock();
4765 								scope(exit) this.mtUnlock();
4766 								while(!done && XPending(display)) {
4767 									done = doXNextEvent(this.display);
4768 								}
4769 								forceXPending = false;
4770 							} else if(fd == pulseFd) {
4771 								long expirationCount;
4772 								// 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...
4773 
4774 								handlePulse();
4775 
4776 								// read just to clear the buffer so poll doesn't trigger again
4777 								// BTW I read AFTER the pulse because if the pulse handler takes
4778 								// a lot of time to execute, we don't want the app to get stuck
4779 								// in a loop of timer hits without a chance to do anything else
4780 								//
4781 								// IOW handlePulse happens at most once per pulse interval.
4782 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4783 								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
4784 							} else if (fd == customEventFDRead) {
4785 								// we have some custom events; process 'em
4786 								import core.sys.posix.unistd : read;
4787 								ulong n;
4788 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4789 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4790 								//SimpleWindow.processAllCustomEvents();
4791 
4792 								forceXPending = true;
4793 							} else {
4794 								// some other timer
4795 								version(sdddd) { writeln("unknown fd: ", fd); }
4796 
4797 								if(Timer* t = fd in Timer.mapping)
4798 									(*t).trigger();
4799 
4800 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4801 									(*pfr).ready(flags);
4802 
4803 								// we don't know what the user did in this timer, so we need to assume that
4804 								// there's X data to be flushed and potentially processed
4805 								forceXPending = true;
4806 
4807 								// or i might add support for other FDs too
4808 								// but for now it is just timer
4809 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4810 							}
4811 						}
4812 						if(flags & ep.EPOLLHUP) {
4813 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4814 								(*pfr).hup(flags);
4815 							if(globalHupHandler)
4816 								globalHupHandler(fd, flags);
4817 						}
4818 						/+
4819 						} else {
4820 							// not interested in OUT, we are just reading here.
4821 							//
4822 							// error or hup might also be reported
4823 							// but it shouldn't here since we are only
4824 							// using a few types of FD and Xlib will report
4825 							// if it dies.
4826 							// so instead of thoughtfully handling it, I'll
4827 							// just throw. for now at least
4828 
4829 							throw new Exception("epoll did something else");
4830 						}
4831 						+/
4832 					}
4833 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4834 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4835 					xpending:
4836 					if (!done && forceXPending) {
4837 						done = doXPending();
4838 					}
4839 				}
4840 			} else {
4841 				// Generic fallback: yes to simple pulse support,
4842 				// but NO timer support!
4843 
4844 				// FIXME: we could probably support the POSIX timer_create
4845 				// signal-based option, but I'm in no rush to write it since
4846 				// I prefer the fd-based functions.
4847 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4848 
4849 					import core.sys.posix.poll;
4850 
4851 					pollfd[] pfds;
4852 					pollfd[32] pfdsBuffer;
4853 					auto len = PosixFdReader.mapping.length + 2;
4854 					// FIXME: i should just reuse the buffer
4855 					if(len < pfdsBuffer.length)
4856 						pfds = pfdsBuffer[0 .. len];
4857 					else
4858 						pfds = new pollfd[](len);
4859 
4860 					pfds[0].fd = display.fd;
4861 					pfds[0].events = POLLIN;
4862 					pfds[0].revents = 0;
4863 
4864 					int slot = 1;
4865 
4866 					if(customEventFDRead != -1) {
4867 						pfds[slot].fd = customEventFDRead;
4868 						pfds[slot].events = POLLIN;
4869 						pfds[slot].revents = 0;
4870 
4871 						slot++;
4872 					}
4873 
4874 					foreach(fd, obj; PosixFdReader.mapping) {
4875 						if(!obj.enabled) continue;
4876 						pfds[slot].fd = fd;
4877 						pfds[slot].events = POLLIN;
4878 						pfds[slot].revents = 0;
4879 
4880 						slot++;
4881 					}
4882 
4883 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4884 					if(ret == -1) throw new Exception("poll");
4885 
4886 					if(ret == 0) {
4887 						// FIXME it may not necessarily time out if events keep coming
4888 						if(handlePulse !is null)
4889 							handlePulse();
4890 					} else {
4891 						foreach(s; 0 .. slot) {
4892 							if(pfds[s].revents == 0) continue;
4893 
4894 							if(pfds[s].fd == display.fd) {
4895 								while(!done && XPending(display)) {
4896 									this.mtLock();
4897 									scope(exit) this.mtUnlock();
4898 									done = doXNextEvent(this.display);
4899 								}
4900 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4901 
4902 								import core.sys.posix.unistd : read;
4903 								ulong n;
4904 								read(customEventFDRead, &n, n.sizeof);
4905 								SimpleWindow.processAllCustomEvents();
4906 							} else {
4907 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4908 								if(pfds[s].revents & POLLNVAL) {
4909 									obj.dispose();
4910 								} else {
4911 									obj.ready(pfds[s].revents);
4912 								}
4913 							}
4914 
4915 							ret--;
4916 							if(ret == 0) break;
4917 						}
4918 					}
4919 				}
4920 			}
4921 		}
4922 
4923 		version(Windows) {
4924 
4925 			version(use_arsd_core) {
4926 				import arsd.core;
4927 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4928 				static bool loopInitialized = false;
4929 				if(!loopInitialized) {
4930 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4931 					el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0);
4932 					loopInitialized = true;
4933 				}
4934 				el.run(() => !whileCondition());
4935 			} else {
4936 				int ret = -1;
4937 				MSG message;
4938 				while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4939 					eventLoopRound++;
4940 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4941 					auto waitResult = MsgWaitForMultipleObjectsEx(
4942 						cast(int) handles.length, handles.ptr,
4943 						(wto == 0 ? INFINITE : wto), /* timeout */
4944 						0x04FF, /* QS_ALLINPUT */
4945 						0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4946 
4947 					SimpleWindow.processAllCustomEvents(); // anyway
4948 					enum WAIT_OBJECT_0 = 0;
4949 					if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4950 						auto h = handles[waitResult - WAIT_OBJECT_0];
4951 						if(auto e = h in WindowsHandleReader.mapping) {
4952 							(*e).ready();
4953 						}
4954 					} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4955 						// message ready
4956 						int count;
4957 						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
4958 							ret = GetMessage(&message, null, 0, 0);
4959 							if(ret == -1)
4960 								throw new WindowsApiException("GetMessage", GetLastError());
4961 							TranslateMessage(&message);
4962 							DispatchMessage(&message);
4963 
4964 							count++;
4965 							if(count > 10)
4966 								break; // take the opportunity to catch up on other events
4967 
4968 							if(ret == 0) { // WM_QUIT
4969 								EventLoop.quitApplication();
4970 								break;
4971 							}
4972 						}
4973 					} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4974 						SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4975 					} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4976 						// timeout, should never happen since we aren't using it
4977 					} else if(waitResult == 0xFFFFFFFF) {
4978 							// failed
4979 							throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4980 					} else {
4981 						// idk....
4982 					}
4983 				}
4984 			}
4985 
4986 			// return message.wParam;
4987 			return 0;
4988 		} else {
4989 			return 0;
4990 		}
4991 	}
4992 
4993 	int run(bool delegate() whileCondition = null) {
4994 		if(disposed)
4995 			initialize(this.pulseTimeout);
4996 
4997 		version(X11) {
4998 			try {
4999 				return loopHelper(whileCondition);
5000 			} catch(XDisconnectException e) {
5001 				if(e.userRequested) {
5002 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
5003 						item.discardConnectionState();
5004 					XCloseDisplay(XDisplayConnection.display);
5005 				}
5006 
5007 				XDisplayConnection.display = null;
5008 
5009 				this.dispose();
5010 
5011 				throw e;
5012 			}
5013 		} else {
5014 			return loopHelper(whileCondition);
5015 		}
5016 	}
5017 }
5018 
5019 
5020 /++
5021 	Provides an icon on the system notification area (also known as the system tray).
5022 
5023 
5024 	If a notification area is not available with the NotificationIcon object is created,
5025 	it will silently succeed and simply attempt to create one when an area becomes available.
5026 
5027 
5028 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
5029 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
5030 	with true color was added at that time. I was just too lazy to write the fallback.
5031 
5032 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
5033 	you use arsd 10.x when targeting Windows XP.
5034 +/
5035 version(Emscripten) {} else
5036 version(OSXCocoa) {} else // NotYetImplementedException
5037 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
5038 
5039 	version(X11) {
5040 		void recreateAfterDisconnect() {
5041 			stateDiscarded = false;
5042 			clippixmap = None;
5043 			throw new Exception("NOT IMPLEMENTED");
5044 		}
5045 
5046 		bool stateDiscarded;
5047 		void discardConnectionState() {
5048 			stateDiscarded = true;
5049 		}
5050 	}
5051 
5052 
5053 	version(X11) {
5054 		Image img;
5055 
5056 		NativeEventHandler getNativeEventHandler() {
5057 			return delegate int(XEvent e) {
5058 				switch(e.type) {
5059 					case EventType.Expose:
5060 					//case EventType.VisibilityNotify:
5061 						redraw();
5062 					break;
5063 					case EventType.ClientMessage:
5064 						version(sddddd) {
5065 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
5066 						writeln("\t", e.xclient.format);
5067 						writeln("\t", e.xclient.data.l);
5068 						}
5069 					break;
5070 					case EventType.ButtonPress:
5071 						auto event = e.xbutton;
5072 						if (onClick !is null || onClickEx !is null) {
5073 							MouseButton mb = cast(MouseButton)0;
5074 							switch (event.button) {
5075 								case 1: mb = MouseButton.left; break; // left
5076 								case 2: mb = MouseButton.middle; break; // middle
5077 								case 3: mb = MouseButton.right; break; // right
5078 								case 4: mb = MouseButton.wheelUp; break; // scroll up
5079 								case 5: mb = MouseButton.wheelDown; break; // scroll down
5080 								case 6: break; // scroll left...
5081 								case 7: break; // scroll right...
5082 								case 8: mb = MouseButton.backButton; break;
5083 								case 9: mb = MouseButton.forwardButton; break;
5084 								default:
5085 							}
5086 							if (mb) {
5087 								try { onClick()(mb); } catch (Exception) {}
5088 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
5089 							}
5090 						}
5091 					break;
5092 					case EventType.EnterNotify:
5093 						if (onEnter !is null) {
5094 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
5095 						}
5096 						break;
5097 					case EventType.LeaveNotify:
5098 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
5099 						break;
5100 					case EventType.DestroyNotify:
5101 						active = false;
5102 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
5103 					break;
5104 					case EventType.ConfigureNotify:
5105 						auto event = e.xconfigure;
5106 						this.width = event.width;
5107 						this.height = event.height;
5108 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
5109 						redraw();
5110 					break;
5111 					default: return 1;
5112 				}
5113 				return 1;
5114 			};
5115 		}
5116 
5117 		/* private */ void hideBalloon() {
5118 			balloon.close();
5119 			version(with_timer)
5120 				timer.destroy();
5121 			balloon = null;
5122 			version(with_timer)
5123 				timer = null;
5124 		}
5125 
5126 		void redraw() {
5127 			if (!active) return;
5128 
5129 			auto display = XDisplayConnection.get;
5130 			GC gc;
5131 
5132 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
5133 
5134 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
5135 				Visual *visual;
5136 				XVisualInfo vis_info;
5137 				XSetWindowAttributes win_attr;
5138 				c_ulong win_mask;
5139 
5140 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
5141 					assert(0);
5142 					// return 1;
5143 				}
5144 
5145 				visual = vis_info.visual;
5146 
5147 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
5148 				win_attr.background_pixel = 0;
5149 				win_attr.border_pixel = 0;
5150 
5151 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
5152 
5153 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
5154 
5155 				return 0;
5156 			}
5157 
5158 			if(useAlpha)
5159 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
5160 			else
5161 				gc = DefaultGC(display, DefaultScreen(display));
5162 
5163 			XClearWindow(display, nativeHandle);
5164 
5165 			if(!useAlpha && img !is null)
5166 				XSetClipMask(display, gc, clippixmap);
5167 
5168 			/+
5169 			XSetForeground(display, gc,
5170 				cast(uint) 0 << 16 |
5171 				cast(uint) 0 << 8 |
5172 				cast(uint) 0);
5173 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
5174 			+/
5175 
5176 			if (img is null) {
5177 				XSetForeground(display, gc,
5178 					cast(uint) 0 << 16 |
5179 					cast(uint) 127 << 8 |
5180 					cast(uint) 0);
5181 				XFillArc(display, nativeHandle,
5182 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
5183 			} else {
5184 				int dx = 0;
5185 				int dy = 0;
5186 				if(width > img.width)
5187 					dx = (width - img.width) / 2;
5188 				if(height > img.height)
5189 					dy = (height - img.height) / 2;
5190 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
5191 				XSetClipOrigin(display, gc, dx, dy);
5192 
5193 				int max(int a, int b) {
5194 					if(a > b) return a; else return b;
5195 				}
5196 
5197 				if (img.usingXshm)
5198 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
5199 				else
5200 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
5201 			}
5202 			XSetClipMask(display, gc, None);
5203 			flushGui();
5204 		}
5205 
5206 		static Window getTrayOwner() {
5207 			auto display = XDisplayConnection.get;
5208 			auto i = cast(int) DefaultScreen(display);
5209 			if(i < 10 && i >= 0) {
5210 				static Atom atom;
5211 				if(atom == None)
5212 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
5213 				return XGetSelectionOwner(display, atom);
5214 			}
5215 			return None;
5216 		}
5217 
5218 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
5219 			auto to = getTrayOwner();
5220 			auto display = XDisplayConnection.get;
5221 			XEvent ev;
5222 			ev.xclient.type = EventType.ClientMessage;
5223 			ev.xclient.window = to;
5224 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
5225 			ev.xclient.format = 32;
5226 			ev.xclient.data.l[0] = CurrentTime;
5227 			ev.xclient.data.l[1] = message;
5228 			ev.xclient.data.l[2] = d1;
5229 			ev.xclient.data.l[3] = d2;
5230 			ev.xclient.data.l[4] = d3;
5231 
5232 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
5233 		}
5234 
5235 		private static NotificationAreaIcon[] activeIcons;
5236 
5237 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
5238 		private void newManager() {
5239 			close();
5240 			createXWin();
5241 
5242 			if(this.clippixmap)
5243 				XFreePixmap(XDisplayConnection.get, clippixmap);
5244 			if(this.originalMemoryImage)
5245 				this.icon = this.originalMemoryImage;
5246 			else if(this.img)
5247 				this.icon = this.img;
5248 		}
5249 
5250 		private bool useAlpha = false;
5251 
5252 		private void createXWin () {
5253 			// create window
5254 			auto display = XDisplayConnection.get;
5255 
5256 			// to check for MANAGER on root window to catch new/changed tray owners
5257 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
5258 			// so if a thing does appear, we can handle it
5259 			foreach(ai; activeIcons)
5260 				if(ai is this)
5261 					goto alreadythere;
5262 			activeIcons ~= this;
5263 			alreadythere:
5264 
5265 			// and check for an existing tray
5266 			auto trayOwner = getTrayOwner();
5267 			if(trayOwner == None)
5268 				return;
5269 				//throw new Exception("No notification area found");
5270 
5271 			Visual* v = cast(Visual*) CopyFromParent;
5272 
5273 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
5274 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
5275 			// a resize event later.
5276 			width = 22;
5277 			height = 22;
5278 
5279 			// if they system gave us a 32 bit visual we need to switch to it too
5280 			int depth = 24;
5281 
5282 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
5283 			if(visualProp !is null) {
5284 				c_ulong[] info = cast(c_ulong[]) visualProp;
5285 				if(info.length == 1) {
5286 					auto vid = info[0];
5287 					int returned;
5288 					XVisualInfo t;
5289 					t.visualid = vid;
5290 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
5291 					if(got !is null) {
5292 						if(returned == 1) {
5293 							v = got.visual;
5294 							depth = got.depth;
5295 							// writeln("using special visual ", got.depth);
5296 							// writeln(depth);
5297 						}
5298 						XFree(got);
5299 					}
5300 				}
5301 			}
5302 
5303 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
5304 			XSetWindowAttributes attr;
5305 			attr.background_pixel = 0;
5306 			attr.border_pixel = 0;
5307 			attr.override_redirect = 0;
5308 			if(v !is cast(Visual*) CopyFromParent) {
5309 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
5310 				CWFlags |= CWColormap;
5311 				if(depth == 32)
5312 					useAlpha = true;
5313 				else
5314 					goto plain;
5315 			} else {
5316 				plain:
5317 				attr.background_pixmap = 1 /* ParentRelative */;
5318 				CWFlags |= CWBackPixmap;
5319 			}
5320 
5321 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
5322 
5323 			assert(nativeWindow);
5324 
5325 			if(!useAlpha)
5326 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
5327 
5328 			nativeHandle = nativeWindow;
5329 
5330 			///+
5331 			arch_ulong[2] info;
5332 			info[0] = 0;
5333 			info[1] = 1;
5334 
5335 			string title = this.name is null ? "simpledisplay.d program" : this.name;
5336 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
5337 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
5338 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
5339 
5340 			XChangeProperty(
5341 				display,
5342 				nativeWindow,
5343 				GetAtom!("_XEMBED_INFO", true)(display),
5344 				GetAtom!("_XEMBED_INFO", true)(display),
5345 				32 /* bits */,
5346 				0 /*PropModeReplace*/,
5347 				info.ptr,
5348 				2);
5349 
5350 			import core.sys.posix.unistd;
5351 			arch_ulong pid = getpid();
5352 
5353 			XChangeProperty(
5354 				display,
5355 				nativeWindow,
5356 				GetAtom!("_NET_WM_PID", true)(display),
5357 				XA_CARDINAL,
5358 				32 /* bits */,
5359 				0 /*PropModeReplace*/,
5360 				&pid,
5361 				1);
5362 
5363 			updateNetWmIcon();
5364 
5365 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
5366 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
5367 				XClassHint klass;
5368 				XWMHints wh;
5369 				XSizeHints size;
5370 				klass.res_name = sdpyWindowClassStr;
5371 				klass.res_class = sdpyWindowClassStr;
5372 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
5373 			}
5374 
5375 				// believe it or not, THIS is what xfce needed for the 9999 issue
5376 				XSizeHints sh;
5377 				c_long spr;
5378 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
5379 				sh.flags |= PMaxSize | PMinSize;
5380 				// FIXME maybe nicer resizing
5381 				sh.min_width = 16;
5382 				sh.min_height = 16;
5383 				sh.max_width = 22;
5384 				sh.max_height = 22;
5385 				XSetWMNormalHints(display, nativeWindow, &sh);
5386 
5387 
5388 			//+/
5389 
5390 
5391 			XSelectInput(display, nativeWindow,
5392 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
5393 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5394 
5395 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5396 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5397 
5398 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5399 			active = true;
5400 		}
5401 
5402 		void updateNetWmIcon() {
5403 			if(img is null) return;
5404 			auto display = XDisplayConnection.get;
5405 			// FIXME: ensure this is correct
5406 			arch_ulong[] buffer;
5407 			auto imgMi = img.toTrueColorImage;
5408 			buffer ~= imgMi.width;
5409 			buffer ~= imgMi.height;
5410 			foreach(c; imgMi.imageData.colors) {
5411 				arch_ulong b;
5412 				b |= c.a << 24;
5413 				b |= c.r << 16;
5414 				b |= c.g << 8;
5415 				b |= c.b;
5416 				buffer ~= b;
5417 			}
5418 
5419 			XChangeProperty(
5420 				display,
5421 				nativeHandle,
5422 				GetAtom!"_NET_WM_ICON"(display),
5423 				GetAtom!"CARDINAL"(display),
5424 				32 /* bits */,
5425 				0 /*PropModeReplace*/,
5426 				buffer.ptr,
5427 				cast(int) buffer.length);
5428 		}
5429 
5430 
5431 
5432 		private SimpleWindow balloon;
5433 		version(with_timer)
5434 		private Timer timer;
5435 
5436 		private Window nativeHandle;
5437 		private Pixmap clippixmap = None;
5438 		private int width = 16;
5439 		private int height = 16;
5440 		private bool active = false;
5441 
5442 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5443 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5444 		void delegate () onLeave; /// X11 only.
5445 
5446 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5447 
5448 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5449 		void getWindowRect (out int x, out int y, out int width, out int height) {
5450 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5451 			Window dummyw;
5452 			auto dpy = XDisplayConnection.get;
5453 			//XWindowAttributes xwa;
5454 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5455 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5456 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5457 			width = this.width;
5458 			height = this.height;
5459 		}
5460 	}
5461 
5462 	/+
5463 		What I actually want from this:
5464 
5465 		* set / change: icon, tooltip
5466 		* handle: mouse click, right click
5467 		* show: notification bubble.
5468 	+/
5469 
5470 	version(Windows) {
5471 		WindowsIcon win32Icon;
5472 		HWND hwnd;
5473 
5474 		NOTIFYICONDATAW data;
5475 
5476 		NativeEventHandler getNativeEventHandler() {
5477 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5478 				if(msg == WM_USER) {
5479 					auto event = LOWORD(lParam);
5480 					auto iconId = HIWORD(lParam);
5481 					//auto x = GET_X_LPARAM(wParam);
5482 					//auto y = GET_Y_LPARAM(wParam);
5483 					switch(event) {
5484 						case WM_LBUTTONDOWN:
5485 							onClick()(MouseButton.left);
5486 						break;
5487 						case WM_RBUTTONDOWN:
5488 							onClick()(MouseButton.right);
5489 						break;
5490 						case WM_MBUTTONDOWN:
5491 							onClick()(MouseButton.middle);
5492 						break;
5493 						case WM_MOUSEMOVE:
5494 							// sent, we could use it.
5495 						break;
5496 						case WM_MOUSEWHEEL:
5497 							// NOT SENT
5498 						break;
5499 						//case NIN_KEYSELECT:
5500 						//case NIN_SELECT:
5501 						//break;
5502 						default: {}
5503 					}
5504 				}
5505 				return 0;
5506 			};
5507 		}
5508 
5509 		enum NIF_SHOWTIP = 0x00000080;
5510 
5511 		private static struct NOTIFYICONDATAW {
5512 			DWORD cbSize;
5513 			HWND  hWnd;
5514 			UINT  uID;
5515 			UINT  uFlags;
5516 			UINT  uCallbackMessage;
5517 			HICON hIcon;
5518 			WCHAR[128] szTip;
5519 			DWORD dwState;
5520 			DWORD dwStateMask;
5521 			WCHAR[256] szInfo;
5522 			union {
5523 				UINT uTimeout;
5524 				UINT uVersion;
5525 			}
5526 			WCHAR[64] szInfoTitle;
5527 			DWORD dwInfoFlags;
5528 			GUID  guidItem;
5529 			HICON hBalloonIcon;
5530 		}
5531 
5532 	}
5533 
5534 	/++
5535 		Note that on Windows, only left, right, and middle buttons are sent.
5536 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5537 		program is meant to be used on Windows too.
5538 	+/
5539 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5540 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5541 		// but on X, we need an Image, so its canonical ctor is there. They should
5542 		// forward to each other though.
5543 		version(X11) {
5544 			this.name = name;
5545 			this.onClick = onClick;
5546 			createXWin();
5547 			this.icon = icon;
5548 		} else version(Windows) {
5549 			this.onClick = onClick;
5550 			this.win32Icon = new WindowsIcon(icon);
5551 
5552 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5553 
5554 			static bool registered = false;
5555 			if(!registered) {
5556 				WNDCLASSEX wc;
5557 				wc.cbSize = wc.sizeof;
5558 				wc.hInstance = hInstance;
5559 				wc.lpfnWndProc = &WndProc;
5560 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5561 				if(!RegisterClassExW(&wc))
5562 					throw new WindowsApiException("RegisterClass", GetLastError());
5563 				registered = true;
5564 			}
5565 
5566 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5567 			if(hwnd is null)
5568 				throw new WindowsApiException("CreateWindow", GetLastError());
5569 
5570 			data.cbSize = data.sizeof;
5571 			data.hWnd = hwnd;
5572 			data.uID = cast(uint) cast(void*) this;
5573 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5574 				// NIF_INFO means show balloon
5575 			data.uCallbackMessage = WM_USER;
5576 			data.hIcon = this.win32Icon.hIcon;
5577 			data.szTip = ""; // FIXME
5578 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5579 			data.dwStateMask = NIS_HIDDEN; // windows vista
5580 
5581 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5582 
5583 
5584 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5585 
5586 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5587 		} else version(OSXCocoa) {
5588 			throw new NotYetImplementedException();
5589 		} else static assert(0);
5590 	}
5591 
5592 	/// ditto
5593 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5594 		version(X11) {
5595 			this.onClick = onClick;
5596 			this.name = name;
5597 			createXWin();
5598 			this.icon = icon;
5599 		} else version(Windows) {
5600 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5601 		} else version(OSXCocoa) {
5602 			throw new NotYetImplementedException();
5603 		} else static assert(0);
5604 	}
5605 
5606 	version(X11) {
5607 		/++
5608 			X-specific extension (for now at least)
5609 		+/
5610 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5611 			this.onClickEx = onClickEx;
5612 			createXWin();
5613 			if (icon !is null) this.icon = icon;
5614 		}
5615 
5616 		/// ditto
5617 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5618 			this.onClickEx = onClickEx;
5619 			createXWin();
5620 			this.icon = icon;
5621 		}
5622 	}
5623 
5624 	private void delegate (MouseButton button) onClick_;
5625 
5626 	///
5627 	@property final void delegate(MouseButton) onClick() {
5628 		if(onClick_ is null)
5629 			onClick_ = delegate void(MouseButton) {};
5630 		return onClick_;
5631 	}
5632 
5633 	/// ditto
5634 	@property final void onClick(void delegate(MouseButton) handler) {
5635 		// I made this a property setter so we can wrap smaller arg
5636 		// delegates and just forward all to onClickEx or something.
5637 		onClick_ = handler;
5638 	}
5639 
5640 
5641 	string name_;
5642 	@property void name(string n) {
5643 		name_ = n;
5644 	}
5645 
5646 	@property string name() {
5647 		return name_;
5648 	}
5649 
5650 	private MemoryImage originalMemoryImage;
5651 
5652 	///
5653 	@property void icon(MemoryImage i) {
5654 		version(X11) {
5655 			this.originalMemoryImage = i;
5656 			if (!active) return;
5657 			if (i !is null) {
5658 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5659 				if(!useAlpha)
5660 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5661 				// writeln("using pixmap ", clippixmap);
5662 				updateNetWmIcon();
5663 				redraw();
5664 			} else {
5665 				if (this.img !is null) {
5666 					this.img = null;
5667 					redraw();
5668 				}
5669 			}
5670 		} else version(Windows) {
5671 			this.win32Icon = new WindowsIcon(i);
5672 
5673 			data.uFlags = NIF_ICON;
5674 			data.hIcon = this.win32Icon.hIcon;
5675 
5676 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5677 		} else version(OSXCocoa) {
5678 			throw new NotYetImplementedException();
5679 		} else static assert(0);
5680 	}
5681 
5682 	/// ditto
5683 	@property void icon (Image i) {
5684 		version(X11) {
5685 			if (!active) return;
5686 			if (i !is img) {
5687 				originalMemoryImage = null;
5688 				img = i;
5689 				redraw();
5690 			}
5691 		} else version(Windows) {
5692 			this.icon(i is null ? null : i.toTrueColorImage());
5693 		} else version(OSXCocoa) {
5694 			throw new NotYetImplementedException();
5695 		} else static assert(0);
5696 	}
5697 
5698 	/++
5699 		Shows a balloon notification. You can only show one balloon at a time, if you call
5700 		it twice while one is already up, the first balloon will be replaced.
5701 
5702 
5703 		The user is free to block notifications and they will automatically disappear after
5704 		a timeout period.
5705 
5706 		Params:
5707 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5708 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5709 			icon = the icon to display with the notification. If null, it uses your existing icon.
5710 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5711 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5712 	+/
5713 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5714 		bool useCustom = true;
5715 		version(libnotify) {
5716 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5717 			try {
5718 				if(!active) return;
5719 
5720 				if(libnotify is null) {
5721 					libnotify = new C_DynamicLibrary("libnotify.so");
5722 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5723 				}
5724 
5725 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5726 
5727 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5728 
5729 				if(onclick) {
5730 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5731 					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);
5732 					libnotify_action_delegates_count++;
5733 				}
5734 
5735 				// FIXME icon
5736 
5737 				// set hint image-data
5738 				// set default action for onclick
5739 
5740 				void* error;
5741 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5742 
5743 				useCustom = false;
5744 			} catch(Exception e) {
5745 
5746 			}
5747 		}
5748 
5749 		version(X11) {
5750 		if(useCustom) {
5751 			if(!active) return;
5752 			if(balloon) {
5753 				hideBalloon();
5754 			}
5755 			// I know there are two specs for this, but one is never
5756 			// implemented by any window manager I have ever seen, and
5757 			// the other is a bloated mess and too complicated for simpledisplay...
5758 			// so doing my own little window instead.
5759 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5760 
5761 			int x, y, width, height;
5762 			getWindowRect(x, y, width, height);
5763 
5764 			int bx = x - balloon.width;
5765 			int by = y - balloon.height;
5766 			if(bx < 0)
5767 				bx = x + width + balloon.width;
5768 			if(by < 0)
5769 				by = y + height;
5770 
5771 			// just in case, make sure it is actually on scren
5772 			if(bx < 0)
5773 				bx = 0;
5774 			if(by < 0)
5775 				by = 0;
5776 
5777 			balloon.move(bx, by);
5778 			auto painter = balloon.draw();
5779 			painter.fillColor = Color(220, 220, 220);
5780 			painter.outlineColor = Color.black;
5781 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5782 			auto iconWidth = icon is null ? 0 : icon.width;
5783 			if(icon)
5784 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5785 			iconWidth += 6; // margin around the icon
5786 
5787 			// draw a close button
5788 			painter.outlineColor = Color(44, 44, 44);
5789 			painter.fillColor = Color(255, 255, 255);
5790 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5791 			painter.pen = Pen(Color.black, 3);
5792 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5793 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5794 			painter.pen = Pen(Color.black, 1);
5795 			painter.fillColor = Color(220, 220, 220);
5796 
5797 			// Draw the title and message
5798 			painter.drawText(Point(4 + iconWidth, 4), title);
5799 			painter.drawLine(
5800 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5801 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5802 			);
5803 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5804 
5805 			balloon.setEventHandlers(
5806 				(MouseEvent ev) {
5807 					if(ev.type == MouseEventType.buttonPressed) {
5808 						if(ev.x > balloon.width - 16 && ev.y < 16)
5809 							hideBalloon();
5810 						else if(onclick)
5811 							onclick();
5812 					}
5813 				}
5814 			);
5815 			balloon.show();
5816 
5817 			version(with_timer)
5818 			timer = new Timer(timeout, &hideBalloon);
5819 			else {} // FIXME
5820 		}
5821 		} else version(Windows) {
5822 			enum NIF_INFO = 0x00000010;
5823 
5824 			data.uFlags = NIF_INFO;
5825 
5826 			// FIXME: go back to the last valid unicode code point
5827 			if(title.length > 40)
5828 				title = title[0 .. 40];
5829 			if(message.length > 220)
5830 				message = message[0 .. 220];
5831 
5832 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5833 			enum NIIF_LARGE_ICON  = 0x00000020;
5834 			enum NIIF_NOSOUND = 0x00000010;
5835 			enum NIIF_USER = 0x00000004;
5836 			enum NIIF_ERROR = 0x00000003;
5837 			enum NIIF_WARNING = 0x00000002;
5838 			enum NIIF_INFO = 0x00000001;
5839 			enum NIIF_NONE = 0;
5840 
5841 			WCharzBuffer t = WCharzBuffer(title);
5842 			WCharzBuffer m = WCharzBuffer(message);
5843 
5844 			t.copyInto(data.szInfoTitle);
5845 			m.copyInto(data.szInfo);
5846 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5847 
5848 			if(icon !is null) {
5849 				auto i = new WindowsIcon(icon);
5850 				data.hBalloonIcon = i.hIcon;
5851 				data.dwInfoFlags |= NIIF_USER;
5852 			}
5853 
5854 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5855 		} else version(OSXCocoa) {
5856 			throw new NotYetImplementedException();
5857 		} else static assert(0);
5858 	}
5859 
5860 	///
5861 	//version(Windows)
5862 	void show() {
5863 		version(X11) {
5864 			if(!hidden)
5865 				return;
5866 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5867 			hidden = false;
5868 		} else version(Windows) {
5869 			data.uFlags = NIF_STATE;
5870 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5871 			data.dwStateMask = NIS_HIDDEN; // windows vista
5872 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5873 		} else version(OSXCocoa) {
5874 			throw new NotYetImplementedException();
5875 		} else static assert(0);
5876 	}
5877 
5878 	version(X11)
5879 		bool hidden = false;
5880 
5881 	///
5882 	//version(Windows)
5883 	void hide() {
5884 		version(X11) {
5885 			if(hidden)
5886 				return;
5887 			hidden = true;
5888 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5889 		} else version(Windows) {
5890 			data.uFlags = NIF_STATE;
5891 			data.dwState = NIS_HIDDEN; // windows vista
5892 			data.dwStateMask = NIS_HIDDEN; // windows vista
5893 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5894 		} else version(OSXCocoa) {
5895 			throw new NotYetImplementedException();
5896 		} else static assert(0);
5897 	}
5898 
5899 	///
5900 	void close () {
5901 		version(X11) {
5902 			if (active) {
5903 				active = false; // event handler will set this too, but meh
5904 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5905 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5906 				flushGui();
5907 			}
5908 		} else version(Windows) {
5909 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5910 		} else version(OSXCocoa) {
5911 			throw new NotYetImplementedException();
5912 		} else static assert(0);
5913 	}
5914 
5915 	~this() {
5916 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5917 		version(X11)
5918 			if(clippixmap != None)
5919 				XFreePixmap(XDisplayConnection.get, clippixmap);
5920 		close();
5921 	}
5922 }
5923 
5924 version(X11)
5925 /// Call `XFreePixmap` on the return value.
5926 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5927 	char[] data = new char[](i.width * i.height / 8 + 2);
5928 	data[] = 0;
5929 
5930 	int bitOffset = 0;
5931 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5932 		ubyte v = c.a > 128 ? 1 : 0;
5933 		data[bitOffset / 8] |= v << (bitOffset%8);
5934 		bitOffset++;
5935 	}
5936 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5937 	return handle;
5938 }
5939 
5940 
5941 // basic functions to make timers
5942 /**
5943 	A timer that will trigger your function on a given interval.
5944 
5945 
5946 	You create a timer with an interval and a callback. It will continue
5947 	to fire on the interval until it is destroyed.
5948 
5949 	There are currently no one-off timers (instead, just create one and
5950 	destroy it when it is triggered) nor are there pause/resume functions -
5951 	the timer must again be destroyed and recreated if you want to pause it.
5952 
5953 	---
5954 	auto timer = new Timer(50, { it happened!; });
5955 	timer.destroy();
5956 	---
5957 
5958 	Timers can only be expected to fire when the event loop is running and only
5959 	once per iteration through the event loop.
5960 
5961 	History:
5962 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5963 		slow could lock up the event loop. It now guarantees other things will
5964 		get a chance to run between timer calls, even if that means not keeping up
5965 		with the requested interval.
5966 */
5967 version(with_timer) {
5968 version(use_arsd_core)
5969 	alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api
5970 else
5971 class Timer {
5972 // FIXME: needs pause and unpause
5973 	// FIXME: I might add overloads for ones that take a count of
5974 	// how many elapsed since last time (on Windows, it will divide
5975 	// the ticks thing given, on Linux it is just available) and
5976 	// maybe one that takes an instance of the Timer itself too
5977 	/// Create a timer with a callback when it triggers.
5978 	this(int intervalInMilliseconds, void delegate() onPulse) @trusted {
5979 		assert(onPulse !is null);
5980 
5981 		this.intervalInMilliseconds = intervalInMilliseconds;
5982 		this.onPulse = onPulse;
5983 
5984 		version(Windows) {
5985 			/*
5986 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5987 			if(handle == 0)
5988 				throw new WindowsApiException("SetTimer", GetLastError());
5989 			*/
5990 
5991 			// thanks to Archival 998 for the WaitableTimer blocks
5992 			handle = CreateWaitableTimer(null, false, null);
5993 			long initialTime = -intervalInMilliseconds;
5994 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5995 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5996 
5997 			mapping[handle] = this;
5998 
5999 		} else version(Emscripten) {
6000 		} else version(linux) {
6001 			static import ep = core.sys.linux.epoll;
6002 
6003 			import core.sys.linux.timerfd;
6004 
6005 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
6006 			if(fd == -1)
6007 				throw new Exception("timer create failed");
6008 
6009 			mapping[fd] = this;
6010 
6011 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6012 
6013 			if(timerfd_settime(fd, 0, &value, null) == -1)
6014 				throw new Exception("couldn't make pulse timer");
6015 
6016 			version(with_eventloop) {
6017 				import arsd.eventloop;
6018 				addFileEventListeners(fd, &trigger, null, null);
6019 			} else {
6020 				prepareEventLoop();
6021 
6022 				ep.epoll_event ev = void;
6023 				ev.events = ep.EPOLLIN;
6024 				ev.data.fd = fd;
6025 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6026 			}
6027 		} else featureNotImplemented();
6028 	}
6029 
6030 	private int intervalInMilliseconds;
6031 
6032 	// just cuz I sometimes call it this.
6033 	alias dispose = destroy;
6034 
6035 	/// Stop and destroy the timer object.
6036 	void destroy() {
6037 		version(Windows) {
6038 			staticDestroy(handle);
6039 			handle = null;
6040 		} else version(linux) {
6041 			staticDestroy(fd);
6042 			fd = -1;
6043 		} else featureNotImplemented();
6044 	}
6045 
6046 	version(Windows)
6047 	static void staticDestroy(HANDLE handle) {
6048 		if(handle) {
6049 			// KillTimer(null, handle);
6050 			CancelWaitableTimer(cast(void*)handle);
6051 			mapping.remove(handle);
6052 			CloseHandle(handle);
6053 		}
6054 	}
6055 	else version(Emscripten)
6056 	static void staticDestroy(int fd) @system {
6057 		assert(0);
6058 	}
6059 	else version(linux)
6060 	static void staticDestroy(int fd) @system {
6061 		if(fd != -1) {
6062 			import unix = core.sys.posix.unistd;
6063 			static import ep = core.sys.linux.epoll;
6064 
6065 			version(with_eventloop) {
6066 				import arsd.eventloop;
6067 				removeFileEventListeners(fd);
6068 			} else {
6069 				ep.epoll_event ev = void;
6070 				ev.events = ep.EPOLLIN;
6071 				ev.data.fd = fd;
6072 
6073 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6074 			}
6075 			unix.close(fd);
6076 			mapping.remove(fd);
6077 		}
6078 	}
6079 
6080 	~this() {
6081 		version(Windows) { if(handle)
6082 			cleanupQueue.queue!staticDestroy(handle);
6083 		} else version(linux) { if(fd != -1)
6084 			cleanupQueue.queue!staticDestroy(fd);
6085 		}
6086 	}
6087 
6088 	void changeTime(int intervalInMilliseconds)
6089 	{
6090 		this.intervalInMilliseconds = intervalInMilliseconds;
6091 		version(Windows)
6092 		{
6093 			if(handle)
6094 			{
6095 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6096 				long initialTime = -intervalInMilliseconds;
6097 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6098 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
6099 			}
6100 		} else version(linux) {
6101 			import core.sys.linux.timerfd;
6102 
6103 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6104 			if(timerfd_settime(fd, 0, &value, null) == -1) {
6105 				throw new Exception("couldn't change pulse timer");
6106 			}
6107 		} else {
6108 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
6109 		}
6110 	}
6111 
6112 
6113 	private:
6114 
6115 	void delegate() onPulse;
6116 
6117 	int lastEventLoopRoundTriggered;
6118 
6119 	version(linux) {
6120 		static auto makeItimerspec(int intervalInMilliseconds) {
6121 			import core.sys.linux.timerfd;
6122 
6123 			itimerspec value;
6124 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6125 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6126 
6127 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6128 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6129 
6130 			return value;
6131 		}
6132 	}
6133 
6134 	void trigger() {
6135 		version(linux) {
6136 			import unix = core.sys.posix.unistd;
6137 			long val;
6138 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
6139 		} else version(Windows) {
6140 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
6141 				return; // never try to actually run faster than the event loop
6142 			lastEventLoopRoundTriggered = eventLoopRound;
6143 		} else featureNotImplemented();
6144 
6145 		onPulse();
6146 	}
6147 
6148 	version(Windows)
6149 	void rearm() {
6150 
6151 	}
6152 
6153 	version(Windows)
6154 		extern(Windows)
6155 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
6156 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
6157 			if(Timer* t = timer in mapping) {
6158 				try
6159 				(*t).trigger();
6160 				catch(Exception e) { sdpy_abort(e); assert(0); }
6161 			}
6162 		}
6163 
6164 	version(Windows) {
6165 		//UINT_PTR handle;
6166 		//static Timer[UINT_PTR] mapping;
6167 		HANDLE handle;
6168 		__gshared Timer[HANDLE] mapping;
6169 	} else version(linux) {
6170 		int fd = -1;
6171 		__gshared Timer[int] mapping;
6172 	} else version(OSXCocoa) {
6173 	} else static assert(0, "timer not supported");
6174 }
6175 }
6176 
6177 version(Windows)
6178 private int eventLoopRound;
6179 
6180 version(Windows)
6181 /// 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
6182 class WindowsHandleReader {
6183 	///
6184 	this(void delegate() onReady, HANDLE handle) {
6185 		this.onReady = onReady;
6186 		this.handle = handle;
6187 
6188 		mapping[handle] = this;
6189 
6190 		enable();
6191 	}
6192 
6193 	version(use_arsd_core)
6194 		ICoreEventLoop.UnregisterToken unregisterToken;
6195 
6196 	///
6197 	void enable() {
6198 		version(use_arsd_core) {
6199 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready));
6200 		} else {
6201 			auto el = EventLoop.get().impl;
6202 			el.handles ~= handle;
6203 		}
6204 	}
6205 
6206 	///
6207 	void disable() {
6208 		version(use_arsd_core) {
6209 			unregisterToken.unregister();
6210 		} else {
6211 			auto el = EventLoop.get().impl;
6212 			for(int i = 0; i < el.handles.length; i++) {
6213 				if(el.handles[i] is handle) {
6214 					el.handles[i] = el.handles[$-1];
6215 					el.handles = el.handles[0 .. $-1];
6216 					return;
6217 				}
6218 			}
6219 		}
6220 	}
6221 
6222 	void dispose() {
6223 		disable();
6224 		if(handle)
6225 			mapping.remove(handle);
6226 		handle = null;
6227 	}
6228 
6229 	void ready() {
6230 		if(onReady)
6231 			onReady();
6232 	}
6233 
6234 	HANDLE handle;
6235 	void delegate() onReady;
6236 
6237 	__gshared WindowsHandleReader[HANDLE] mapping;
6238 }
6239 
6240 version(Posix)
6241 /// Lets you add files to the event loop for reading. Use at your own risk.
6242 class PosixFdReader {
6243 	///
6244 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6245 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
6246 	}
6247 
6248 	///
6249 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6250 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
6251 	}
6252 
6253 	///
6254 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6255 		this.onReady = onReady;
6256 		this.fd = fd;
6257 		this.captureWrites = captureWrites;
6258 		this.captureReads = captureReads;
6259 
6260 		mapping[fd] = this;
6261 
6262 		version(with_eventloop) {
6263 			import arsd.eventloop;
6264 			addFileEventListeners(fd, &readyel);
6265 		} else {
6266 			enable();
6267 		}
6268 	}
6269 
6270 	bool captureReads;
6271 	bool captureWrites;
6272 
6273 	version(use_arsd_core) {
6274 		import arsd.core;
6275 		ICoreEventLoop.UnregisterToken unregisterToken;
6276 	}
6277 
6278 	version(with_eventloop) {} else
6279 	///
6280 	void enable() @system {
6281 		enabled = true;
6282 
6283 		version(use_arsd_core) {
6284 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper(
6285 				() { onReady(fd, true, false); }
6286 			));
6287 			// FIXME: what if it is writeable?
6288 
6289 		} else version(linux) {
6290 			prepareEventLoop();
6291 			static import ep = core.sys.linux.epoll;
6292 			ep.epoll_event ev = void;
6293 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6294 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
6295 			ev.data.fd = fd;
6296 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6297 		} else {
6298 
6299 		}
6300 	}
6301 
6302 	version(with_eventloop) {} else
6303 	///
6304 	void disable() @system {
6305 		enabled = false;
6306 
6307 		version(use_arsd_core) {
6308 			unregisterToken.unregister();
6309 		} else
6310 		version(linux) {
6311 			prepareEventLoop();
6312 			static import ep = core.sys.linux.epoll;
6313 			ep.epoll_event ev = void;
6314 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6315 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
6316 			ev.data.fd = fd;
6317 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6318 		}
6319 	}
6320 
6321 	version(with_eventloop) {} else
6322 	///
6323 	void dispose() {
6324 		if(enabled)
6325 			disable();
6326 		if(fd != -1)
6327 			mapping.remove(fd);
6328 		fd = -1;
6329 	}
6330 
6331 	void delegate(int, bool, bool) onReady;
6332 
6333 	version(with_eventloop)
6334 	void readyel() {
6335 		onReady(fd, true, true);
6336 	}
6337 
6338 	void ready(uint flags) {
6339 		version(Emscripten) {
6340 			assert(0);
6341 		} else version(linux) {
6342 			static import ep = core.sys.linux.epoll;
6343 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
6344 		} else {
6345 			import core.sys.posix.poll;
6346 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
6347 		}
6348 	}
6349 
6350 	void hup(uint flags) {
6351 		if(onHup)
6352 			onHup();
6353 	}
6354 
6355 	void delegate() onHup;
6356 
6357 	int fd = -1;
6358 	private bool enabled;
6359 	__gshared PosixFdReader[int] mapping;
6360 }
6361 
6362 // basic functions to access the clipboard
6363 /+
6364 
6365 
6366 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
6367 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
6368 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6369 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
6370 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
6371 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6372 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
6373 
6374 +/
6375 
6376 /++
6377 	this does a delegate because it is actually an async call on X...
6378 	the receiver may never be called if the clipboard is empty or unavailable
6379 	gets plain text from the clipboard.
6380 +/
6381 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system {
6382 	version(Windows) {
6383 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6384 		if(OpenClipboard(hwndOwner) == 0)
6385 			throw new WindowsApiException("OpenClipboard", GetLastError());
6386 		scope(exit)
6387 			CloseClipboard();
6388 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
6389 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
6390 
6391 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
6392 				scope(exit)
6393 					GlobalUnlock(dataHandle);
6394 
6395 				// FIXME: CR/LF conversions
6396 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
6397 				int len = 0;
6398 				auto d = data;
6399 				while(*d) {
6400 					d++;
6401 					len++;
6402 				}
6403 				string s;
6404 				s.reserve(len);
6405 				foreach(dchar ch; data[0 .. len]) {
6406 					s ~= ch;
6407 				}
6408 				receiver(s);
6409 			}
6410 		}
6411 	} else version(X11) {
6412 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6413 	} else version(OSXCocoa) {
6414 		throw new NotYetImplementedException();
6415 	} else version(Emscripten) {
6416 		throw new NotYetImplementedException();
6417 	} else static assert(0);
6418 }
6419 
6420 // FIXME: a clipboard listener might be cool btw
6421 
6422 /++
6423 	this does a delegate because it is actually an async call on X...
6424 	the receiver may never be called if the clipboard is empty or unavailable
6425 	gets image from the clipboard.
6426 
6427 	templated because it introduces an optional dependency on arsd.bmp
6428 +/
6429 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6430 	version(Windows) {
6431 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6432 		if(OpenClipboard(hwndOwner) == 0)
6433 			throw new WindowsApiException("OpenClipboard", GetLastError());
6434 		scope(exit)
6435 			CloseClipboard();
6436 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6437 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6438 				scope(exit)
6439 					GlobalUnlock(dataHandle);
6440 
6441 				auto len = GlobalSize(dataHandle);
6442 
6443 				import arsd.bmp;
6444 				auto img = readBmp(data[0 .. len], false);
6445 				receiver(img);
6446 			}
6447 		}
6448 	} else version(X11) {
6449 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6450 	} else version(OSXCocoa) {
6451 		throw new NotYetImplementedException();
6452 	} else version(Emscripten) {
6453 		throw new NotYetImplementedException();
6454 	} else static assert(0);
6455 }
6456 
6457 /// Copies some text to the clipboard.
6458 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6459 	assert(clipboardOwner !is null);
6460 	version(Windows) {
6461 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6462 			throw new WindowsApiException("OpenClipboard", GetLastError());
6463 		scope(exit)
6464 			CloseClipboard();
6465 		EmptyClipboard();
6466 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6467 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6468 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6469 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6470 			auto slice = data[0 .. sz];
6471 			scope(failure)
6472 				GlobalUnlock(handle);
6473 
6474 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6475 
6476 			GlobalUnlock(handle);
6477 			SetClipboardData(CF_UNICODETEXT, handle);
6478 		}
6479 	} else version(X11) {
6480 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6481 	} else version(OSXCocoa) {
6482 		throw new NotYetImplementedException();
6483 	} else version(Emscripten) {
6484 		throw new NotYetImplementedException();
6485 	} else static assert(0);
6486 }
6487 
6488 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6489 	assert(clipboardOwner !is null);
6490 	version(Windows) {
6491 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6492 			throw new WindowsApiException("OpenClipboard", GetLastError());
6493 		scope(exit)
6494 			CloseClipboard();
6495 		EmptyClipboard();
6496 
6497 
6498 		import arsd.bmp;
6499 		ubyte[] mdata;
6500 		mdata.reserve(img.width * img.height);
6501 		void sink(ubyte b) {
6502 			mdata ~= b;
6503 		}
6504 		writeBmpIndirect(img, &sink, false);
6505 
6506 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6507 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6508 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6509 			auto slice = data[0 .. mdata.length];
6510 			scope(failure)
6511 				GlobalUnlock(handle);
6512 
6513 			slice[] = mdata[];
6514 
6515 			GlobalUnlock(handle);
6516 			SetClipboardData(CF_DIB, handle);
6517 		}
6518 	} else version(X11) {
6519 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6520 			mixin X11SetSelectionHandler_Basics;
6521 			private const(ubyte)[] mdata;
6522 			private const(ubyte)[] mdata_original;
6523 			this(MemoryImage img) {
6524 				import arsd.bmp;
6525 
6526 				mdata.reserve(img.width * img.height);
6527 				void sink(ubyte b) {
6528 					mdata ~= b;
6529 				}
6530 				writeBmpIndirect(img, &sink, true);
6531 
6532 				mdata_original = mdata;
6533 			}
6534 
6535 			Atom[] availableFormats() {
6536 				auto display = XDisplayConnection.get;
6537 				return [
6538 					GetAtom!"image/bmp"(display),
6539 					GetAtom!"TARGETS"(display)
6540 				];
6541 			}
6542 
6543 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6544 				if(mdata.length < data.length) {
6545 					data[0 .. mdata.length] = mdata[];
6546 					auto ret = data[0 .. mdata.length];
6547 					mdata = mdata[$..$];
6548 					return ret;
6549 				} else {
6550 					data[] = mdata[0 .. data.length];
6551 					mdata = mdata[data.length .. $];
6552 					return data[];
6553 				}
6554 			}
6555 
6556 			void done() {
6557 				mdata = mdata_original;
6558 			}
6559 		}
6560 
6561 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6562 	} else version(OSXCocoa) {
6563 		throw new NotYetImplementedException();
6564 	} else version(Emscripten) {
6565 		throw new NotYetImplementedException();
6566 	} else static assert(0);
6567 }
6568 
6569 
6570 version(X11) {
6571 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6572 
6573 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6574 
6575 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6576 	/// Platform-specific for X11.
6577 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6578 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6579 		__gshared static Atom a;
6580 		if(!a) {
6581 			a = XInternAtom(display, name, !create);
6582 			// FIXME: might need to synchronize this and attach it to the actual object
6583 			interredAtoms ~= &a;
6584 		}
6585 		if(a == None)
6586 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6587 		return a;
6588 	}
6589 
6590 	/// Platform-specific for X11 - gets atom names as a string.
6591 	string getAtomName(Atom atom, Display* display) {
6592 		auto got = XGetAtomName(display, atom);
6593 		scope(exit) XFree(got);
6594 		import core.stdc.string;
6595 		string s = got[0 .. strlen(got)].idup;
6596 		return s;
6597 	}
6598 
6599 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6600 	void setPrimarySelection(SimpleWindow window, string text) {
6601 		setX11Selection!"PRIMARY"(window, text);
6602 	}
6603 
6604 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6605 	void setSecondarySelection(SimpleWindow window, string text) {
6606 		setX11Selection!"SECONDARY"(window, text);
6607 	}
6608 
6609 	interface X11SetSelectionHandler {
6610 		// should include TARGETS right now
6611 		Atom[] availableFormats();
6612 		// Return the slice of data you filled, empty slice if done.
6613 		// this is to support the incremental thing
6614 		ubyte[] getData(Atom format, return scope ubyte[] data);
6615 
6616 		void done();
6617 
6618 		void handleRequest(XEvent);
6619 
6620 		bool matchesIncr(Window, Atom);
6621 		void sendMoreIncr(XPropertyEvent*);
6622 	}
6623 
6624 	mixin template X11SetSelectionHandler_Basics() {
6625 		Window incrWindow;
6626 		Atom incrAtom;
6627 		Atom selectionAtom;
6628 		Atom formatAtom;
6629 		ubyte[] toSend;
6630 		bool matchesIncr(Window w, Atom a) {
6631 			return incrAtom && incrAtom == a && w == incrWindow;
6632 		}
6633 		void sendMoreIncr(XPropertyEvent* event) {
6634 			auto display = XDisplayConnection.get;
6635 
6636 			XChangeProperty (display,
6637 				incrWindow,
6638 				incrAtom,
6639 				formatAtom,
6640 				8 /* bits */, PropModeReplace,
6641 				toSend.ptr, cast(int) toSend.length);
6642 
6643 			if(toSend.length != 0) {
6644 				toSend = this.getData(formatAtom, toSend[]);
6645 			} else {
6646 				this.done();
6647 				incrWindow = None;
6648 				incrAtom = None;
6649 				selectionAtom = None;
6650 				formatAtom = None;
6651 				toSend = null;
6652 			}
6653 		}
6654 		void handleRequest(XEvent ev) {
6655 
6656 			auto display = XDisplayConnection.get;
6657 
6658 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6659 			XSelectionEvent selectionEvent;
6660 			selectionEvent.type = EventType.SelectionNotify;
6661 			selectionEvent.display = event.display;
6662 			selectionEvent.requestor = event.requestor;
6663 			selectionEvent.selection = event.selection;
6664 			selectionEvent.time = event.time;
6665 			selectionEvent.target = event.target;
6666 
6667 			bool supportedType() {
6668 				foreach(t; this.availableFormats())
6669 					if(t == event.target)
6670 						return true;
6671 				return false;
6672 			}
6673 
6674 			if(event.property == None) {
6675 				selectionEvent.property = event.target;
6676 
6677 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6678 				XFlush(display);
6679 			} if(event.target == GetAtom!"TARGETS"(display)) {
6680 				/* respond with the supported types */
6681 				auto tlist = this.availableFormats();
6682 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6683 				selectionEvent.property = event.property;
6684 
6685 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6686 				XFlush(display);
6687 			} else if(supportedType()) {
6688 				auto buffer = new ubyte[](1024 * 64);
6689 				auto toSend = this.getData(event.target, buffer[]);
6690 
6691 				if(toSend.length < 32 * 1024) {
6692 					// small enough to send directly...
6693 					selectionEvent.property = event.property;
6694 					XChangeProperty (display,
6695 						selectionEvent.requestor,
6696 						selectionEvent.property,
6697 						event.target,
6698 						8 /* bits */, 0 /* PropModeReplace */,
6699 						toSend.ptr, cast(int) toSend.length);
6700 
6701 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6702 					XFlush(display);
6703 				} else {
6704 					// large, let's send incrementally
6705 					arch_ulong l = toSend.length;
6706 
6707 					// if I wanted other events from this window don't want to clear that out....
6708 					XWindowAttributes xwa;
6709 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6710 
6711 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6712 
6713 					incrWindow = event.requestor;
6714 					incrAtom = event.property;
6715 					formatAtom = event.target;
6716 					selectionAtom = event.selection;
6717 					this.toSend = toSend;
6718 
6719 					selectionEvent.property = event.property;
6720 					XChangeProperty (display,
6721 						selectionEvent.requestor,
6722 						selectionEvent.property,
6723 						GetAtom!"INCR"(display),
6724 						32 /* bits */, PropModeReplace,
6725 						&l, 1);
6726 
6727 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6728 					XFlush(display);
6729 				}
6730 				//if(after)
6731 					//after();
6732 			} else {
6733 				debug(sdpy_clip) {
6734 					writeln("Unsupported data ", getAtomName(event.target, display));
6735 				}
6736 				selectionEvent.property = None; // I don't know how to handle this type...
6737 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6738 				XFlush(display);
6739 			}
6740 		}
6741 	}
6742 
6743 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6744 		mixin X11SetSelectionHandler_Basics;
6745 		private const(ubyte)[] text;
6746 		private const(ubyte)[] text_original;
6747 		this(string text) {
6748 			this.text = cast(const ubyte[]) text;
6749 			this.text_original = this.text;
6750 		}
6751 		Atom[] availableFormats() {
6752 			auto display = XDisplayConnection.get;
6753 			return [
6754 				GetAtom!"UTF8_STRING"(display),
6755 				GetAtom!"text/plain"(display),
6756 				XA_STRING,
6757 				GetAtom!"TARGETS"(display)
6758 			];
6759 		}
6760 
6761 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6762 			if(text.length < data.length) {
6763 				data[0 .. text.length] = text[];
6764 				return data[0 .. text.length];
6765 			} else {
6766 				data[] = text[0 .. data.length];
6767 				text = text[data.length .. $];
6768 				return data[];
6769 			}
6770 		}
6771 
6772 		void done() {
6773 			text = text_original;
6774 		}
6775 	}
6776 
6777 	/// 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?!)
6778 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6779 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6780 	}
6781 
6782 	private __gshared bool mightShortCircuitClipboard;
6783 
6784 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6785 		assert(window !is null);
6786 
6787 		auto display = XDisplayConnection.get();
6788 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6789 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6790 		else Atom a = GetAtom!atomName(display);
6791 
6792 		if(mightShortCircuitClipboard)
6793 		if(auto ptr = a in window.impl.setSelectionHandlers) {
6794 			// we already have it, don't even need to inform the X server
6795 			// sdpyPrintDebugString("short circuit in set");
6796 			*ptr = data;
6797 			return;
6798 		}
6799 
6800 		// we don't have it, tell X we want it
6801 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6802 		window.impl.setSelectionHandlers[a] = data;
6803 		mightShortCircuitClipboard = true;
6804 	}
6805 
6806 	/+
6807 	/++
6808 		History:
6809 			Added September 28, 2024
6810 	+/
6811 	bool hasX11Selection(string atomName)(SimpleWindow window) {
6812 		auto display = XDisplayConnection.get();
6813 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6814 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6815 		else Atom a = GetAtom!atomName(display);
6816 
6817 		if(a in window.impl.setSelectionHandlers)
6818 			return true;
6819 		else
6820 			return false;
6821 	}
6822 	+/
6823 
6824 	///
6825 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6826 		getX11Selection!"PRIMARY"(window, handler);
6827 	}
6828 
6829 	// added July 28, 2020
6830 	// undocumented as experimental tho
6831 	interface X11GetSelectionHandler {
6832 		void handleData(Atom target, in ubyte[] data);
6833 		Atom findBestFormat(Atom[] answer);
6834 
6835 		void prepareIncremental(Window, Atom);
6836 		bool matchesIncr(Window, Atom);
6837 		void handleIncrData(Atom, in ubyte[] data);
6838 	}
6839 
6840 	mixin template X11GetSelectionHandler_Basics() {
6841 		Window incrWindow;
6842 		Atom incrAtom;
6843 
6844 		void prepareIncremental(Window w, Atom a) {
6845 			incrWindow = w;
6846 			incrAtom = a;
6847 		}
6848 		bool matchesIncr(Window w, Atom a) {
6849 			return incrWindow == w && incrAtom == a;
6850 		}
6851 
6852 		Atom incrFormatAtom;
6853 		ubyte[] incrData;
6854 		void handleIncrData(Atom format, in ubyte[] data) {
6855 			incrFormatAtom = format;
6856 
6857 			if(data.length)
6858 				incrData ~= data;
6859 			else
6860 				handleData(incrFormatAtom, incrData);
6861 
6862 		}
6863 	}
6864 
6865 	static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6866 		this(void delegate(in char[]) handler) {
6867 			this.handler = handler;
6868 		}
6869 
6870 		mixin X11GetSelectionHandler_Basics;
6871 
6872 		void delegate(in char[]) handler;
6873 
6874 		void handleData(Atom target, in ubyte[] data) {
6875 			// import std.stdio; writeln(target, " ", data);
6876 			if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6877 				handler(cast(const char[]) data);
6878 			else if(target == None && data is null)
6879 				handler(null); // no suitable selection exists
6880 		}
6881 
6882 		Atom findBestFormat(Atom[] answer) {
6883 			Atom best = None;
6884 			foreach(option; answer) {
6885 				if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6886 					best = option;
6887 					break;
6888 				} else if(option == XA_STRING) {
6889 					best = option;
6890 				} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6891 					best = option;
6892 				}
6893 			}
6894 			return best;
6895 		}
6896 	}
6897 
6898 	///
6899 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6900 		assert(window !is null);
6901 
6902 		auto display = XDisplayConnection.get();
6903 
6904 		static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY;
6905 		else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY;
6906 		else Atom atom = GetAtom!atomName(display);
6907 
6908 		if(mightShortCircuitClipboard)
6909 		if(auto ptr = atom in window.impl.setSelectionHandlers) {
6910 			if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) {
6911 				// we already have it! short circuit everything
6912 
6913 				// sdpyPrintDebugString("short circuit in get");
6914 				handler(cast(char[]) txt.text_original);
6915 				return;
6916 			}
6917 		}
6918 
6919 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6920 
6921 		auto target = GetAtom!"TARGETS"(display);
6922 
6923 		// SDD_DATA is "simpledisplay.d data"
6924 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6925 	}
6926 
6927 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6928 	/// only supports bmps. using this function will import arsd.bmp.
6929 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6930 		assert(window !is null);
6931 
6932 		auto display = XDisplayConnection.get();
6933 		auto atom = GetAtom!atomName(display);
6934 
6935 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6936 			this(void delegate(MemoryImage) handler) {
6937 				this.handler = handler;
6938 			}
6939 
6940 			mixin X11GetSelectionHandler_Basics;
6941 
6942 			void delegate(MemoryImage) handler;
6943 
6944 			void handleData(Atom target, in ubyte[] data) {
6945 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6946 					import arsd.bmp;
6947 					handler(readBmp(data));
6948 				}
6949 			}
6950 
6951 			Atom findBestFormat(Atom[] answer) {
6952 				Atom best = None;
6953 				foreach(option; answer) {
6954 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6955 						best = option;
6956 					}
6957 				}
6958 				return best;
6959 			}
6960 
6961 		}
6962 
6963 
6964 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6965 
6966 		auto target = GetAtom!"TARGETS"(display);
6967 
6968 		// SDD_DATA is "simpledisplay.d data"
6969 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6970 	}
6971 
6972 
6973 	///
6974 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6975 		Atom actualType;
6976 		int actualFormat;
6977 		arch_ulong actualItems;
6978 		arch_ulong bytesRemaining;
6979 		void* data;
6980 
6981 		auto display = XDisplayConnection.get();
6982 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6983 			if(actualFormat == 0)
6984 				return null;
6985 			else {
6986 				int byteLength;
6987 				if(actualFormat == 32) {
6988 					// 32 means it is a C long... which is variable length
6989 					actualFormat = cast(int) arch_long.sizeof * 8;
6990 				}
6991 
6992 				// then it is just a bit count
6993 				byteLength = cast(int) (actualItems * actualFormat / 8);
6994 
6995 				auto d = new ubyte[](byteLength);
6996 				d[] = cast(ubyte[]) data[0 .. byteLength];
6997 				XFree(data);
6998 				return d;
6999 			}
7000 		}
7001 		return null;
7002 	}
7003 
7004 	/* defined in the systray spec */
7005 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
7006 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
7007 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
7008 
7009 
7010 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
7011 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
7012 	public class GlobalHotkey {
7013 		KeyEvent key;
7014 		void delegate () handler;
7015 
7016 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
7017 
7018 		/// Create from initialzed KeyEvent object
7019 		this (KeyEvent akey, void delegate () ahandler=null) {
7020 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
7021 			key = akey;
7022 			handler = ahandler;
7023 		}
7024 
7025 		/// Create from emacs-like key name ("C-M-Y", etc.)
7026 		this (const(char)[] akey, void delegate () ahandler=null) {
7027 			key = KeyEvent.parse(akey);
7028 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
7029 			handler = ahandler;
7030 		}
7031 
7032 	}
7033 
7034 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7035 		//conwriteln("failed to grab key");
7036 		GlobalHotkeyManager.ghfailed = true;
7037 		return 0;
7038 	}
7039 
7040 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7041 		Image.impl.xshmfailed = true;
7042 		return 0;
7043 	}
7044 
7045 	private __gshared int errorHappened;
7046 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7047 		import core.stdc.stdio;
7048 		char[265] buffer;
7049 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
7050 		debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, evt.serial, evt.request_code, evt.minor_code, evt.resourceid);
7051 		errorHappened = true;
7052 		return 0;
7053 	}
7054 
7055 	/++
7056 		Global hotkey manager. It contains static methods to manage global hotkeys.
7057 
7058 		---
7059 		 try {
7060 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
7061 		} catch (Exception e) {
7062 			conwriteln("ERROR registering hotkey!");
7063 		}
7064 		EventLoop.get.run();
7065 		---
7066 
7067 		The key strings are based on Emacs. In practical terms,
7068 		`M` means `alt` and `H` means the Windows logo key. `C`
7069 		is `ctrl`.
7070 
7071 		$(WARNING
7072 			This is X-specific right now. If you are on
7073 			Windows, try [registerHotKey] instead.
7074 
7075 			We will probably merge these into a single
7076 			interface later.
7077 		)
7078 	+/
7079 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
7080 		version(X11) {
7081 			void recreateAfterDisconnect() {
7082 				throw new Exception("NOT IMPLEMENTED");
7083 			}
7084 			void discardConnectionState() {
7085 				throw new Exception("NOT IMPLEMENTED");
7086 			}
7087 		}
7088 
7089 		private static immutable uint[8] masklist = [ 0,
7090 			KeyOrButtonMask.LockMask,
7091 			KeyOrButtonMask.Mod2Mask,
7092 			KeyOrButtonMask.Mod3Mask,
7093 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
7094 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
7095 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7096 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7097 		];
7098 		private __gshared GlobalHotkeyManager ghmanager;
7099 		private __gshared bool ghfailed = false;
7100 
7101 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
7102 			if (modmask == 0) return false;
7103 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
7104 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
7105 			return true;
7106 		}
7107 
7108 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
7109 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
7110 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
7111 			return modmask;
7112 		}
7113 
7114 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
7115 			uint keycode = cast(uint)ke.key;
7116 			auto dpy = XDisplayConnection.get;
7117 			return XKeysymToKeycode(dpy, keycode);
7118 		}
7119 
7120 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
7121 
7122 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
7123 
7124 		NativeEventHandler getNativeEventHandler () {
7125 			return delegate int (XEvent e) {
7126 				if (e.type != EventType.KeyPress) return 1;
7127 				auto kev = cast(const(XKeyEvent)*)&e;
7128 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
7129 				if (auto ghkp = hash in globalHotkeyList) {
7130 					try {
7131 						ghkp.doHandle();
7132 					} catch (Exception e) {
7133 						import core.stdc.stdio : stderr, fprintf;
7134 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
7135 					}
7136 				}
7137 				return 1;
7138 			};
7139 		}
7140 
7141 		private this () {
7142 			auto dpy = XDisplayConnection.get;
7143 			auto root = RootWindow(dpy, DefaultScreen(dpy));
7144 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
7145 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
7146 		}
7147 
7148 		/// Register new global hotkey with initialized `GlobalHotkey` object.
7149 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
7150 		static void register (GlobalHotkey gh) {
7151 			if (gh is null) return;
7152 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
7153 
7154 			auto dpy = XDisplayConnection.get;
7155 			immutable keycode = keyEvent2KeyCode(gh.key);
7156 
7157 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
7158 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
7159 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
7160 			XSync(dpy, 0/*False*/);
7161 
7162 			Window root = RootWindow(dpy, DefaultScreen(dpy));
7163 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7164 			ghfailed = false;
7165 			foreach (immutable uint ormask; masklist[]) {
7166 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
7167 			}
7168 			XSync(dpy, 0/*False*/);
7169 			XSetErrorHandler(savedErrorHandler);
7170 
7171 			if (ghfailed) {
7172 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7173 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
7174 				XSync(dpy, 0/*False*/);
7175 				XSetErrorHandler(savedErrorHandler);
7176 				throw new Exception("cannot register global hotkey");
7177 			}
7178 
7179 			globalHotkeyList[hash] = gh;
7180 		}
7181 
7182 		/// Ditto
7183 		static void register (const(char)[] akey, void delegate () ahandler) {
7184 			register(new GlobalHotkey(akey, ahandler));
7185 		}
7186 
7187 		private static void removeByHash (ulong hash) {
7188 			if (auto ghp = hash in globalHotkeyList) {
7189 				auto dpy = XDisplayConnection.get;
7190 				immutable keycode = keyEvent2KeyCode(ghp.key);
7191 				Window root = RootWindow(dpy, DefaultScreen(dpy));
7192 				XSync(dpy, 0/*False*/);
7193 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7194 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
7195 				XSync(dpy, 0/*False*/);
7196 				XSetErrorHandler(savedErrorHandler);
7197 				globalHotkeyList.remove(hash);
7198 			}
7199 		}
7200 
7201 		/// Register new global hotkey with previously used `GlobalHotkey` object.
7202 		/// It is safe to unregister unknown or invalid hotkey.
7203 		static void unregister (GlobalHotkey gh) {
7204 			//TODO: add second AA for faster search? prolly doesn't worth it.
7205 			if (gh is null) return;
7206 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
7207 				if (kv.value is gh) {
7208 					removeByHash(kv.key);
7209 					return;
7210 				}
7211 			}
7212 		}
7213 
7214 		/// Ditto.
7215 		static void unregister (const(char)[] key) {
7216 			auto kev = KeyEvent.parse(key);
7217 			immutable keycode = keyEvent2KeyCode(kev);
7218 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
7219 		}
7220 	}
7221 }
7222 
7223 version(Windows) {
7224 	/++
7225 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
7226 
7227 		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).
7228 	+/
7229 	void sendSyntheticInput(wstring s) {
7230 			INPUT[] inputs;
7231 			inputs.reserve(s.length * 2);
7232 
7233 			foreach(wchar c; s) {
7234 				INPUT input;
7235 				input.type = INPUT_KEYBOARD;
7236 				input.ki.wScan = c;
7237 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7238 				inputs ~= input;
7239 
7240 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7241 				inputs ~= input;
7242 			}
7243 
7244 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7245 				throw new WindowsApiException("SendInput", GetLastError());
7246 			}
7247 
7248 	}
7249 
7250 
7251 	// global hotkey helper function
7252 
7253 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
7254 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system {
7255 		__gshared int hotkeyId = 0;
7256 		int id = ++hotkeyId;
7257 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
7258 			throw new Exception("RegisterHotKey");
7259 
7260 		__gshared void delegate()[WPARAM][HWND] handlers;
7261 
7262 		handlers[window.impl.hwnd][id] = handler;
7263 
7264 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
7265 
7266 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7267 			switch(msg) {
7268 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
7269 				case WM_HOTKEY:
7270 					if(auto list = hwnd in handlers) {
7271 						if(auto h = wParam in *list) {
7272 							(*h)();
7273 							return 0;
7274 						}
7275 					}
7276 				goto default;
7277 				default:
7278 			}
7279 			if(oldHandler)
7280 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
7281 			return 1; // pass it on
7282 		};
7283 
7284 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
7285 			oldHandler = window.handleNativeEvent;
7286 			window.handleNativeEvent = nativeEventHandler;
7287 		}
7288 
7289 		return id;
7290 	}
7291 
7292 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
7293 	void unregisterHotKey(SimpleWindow window, int id) {
7294 		if(!UnregisterHotKey(window.impl.hwnd, id))
7295 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
7296 	}
7297 }
7298 
7299 version (X11) {
7300 	pragma(lib, "dl");
7301 	import core.sys.posix.dlfcn;
7302 }
7303 
7304 /++
7305 	Allows for sending synthetic input to the X server via the Xtst
7306 	extension or on Windows using SendInput.
7307 
7308 	Please remember user input is meant to be user - don't use this
7309 	if you have some other alternative!
7310 
7311 	History:
7312 		Added May 17, 2020 with the X implementation.
7313 
7314 		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.)
7315 	Bugs:
7316 		All methods on OSX Cocoa will throw not yet implemented exceptions.
7317 +/
7318 struct SyntheticInput {
7319 	@disable this();
7320 
7321 	private int* refcount;
7322 
7323 	version(X11) {
7324 		private void* lib;
7325 
7326 		private extern(C) {
7327 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
7328 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
7329 		}
7330 	}
7331 
7332 	/// The dummy param must be 0.
7333 	this(int dummy) {
7334 		version(X11) {
7335 			lib = dlopen("libXtst.so", RTLD_NOW);
7336 			if(lib is null)
7337 				throw new Exception("cannot load xtest lib extension");
7338 			scope(failure)
7339 				dlclose(lib);
7340 
7341 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
7342 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
7343 
7344 			if(XTestFakeKeyEvent is null)
7345 				throw new Exception("No XTestFakeKeyEvent");
7346 			if(XTestFakeButtonEvent is null)
7347 				throw new Exception("No XTestFakeButtonEvent");
7348 		}
7349 
7350 		refcount = new int;
7351 		*refcount = 1;
7352 	}
7353 
7354 	this(this) {
7355 		if(refcount)
7356 			*refcount += 1;
7357 	}
7358 
7359 	~this() {
7360 		if(refcount) {
7361 			*refcount -= 1;
7362 			if(*refcount == 0)
7363 				// I commented this because if I close the lib before
7364 				// XCloseDisplay, it is liable to segfault... so just
7365 				// gonna keep it loaded if it is loaded, no big deal
7366 				// anyway.
7367 				{} // dlclose(lib);
7368 		}
7369 	}
7370 
7371 	/++
7372 		Simulates typing a string into the keyboard.
7373 
7374 		Bugs:
7375 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
7376 
7377 			Not implemented except on Windows and X11.
7378 	+/
7379 	void sendSyntheticInput(string s) {
7380 		version(Windows) {
7381 			INPUT[] inputs;
7382 			inputs.reserve(s.length * 2);
7383 
7384 			auto ei = GetMessageExtraInfo();
7385 
7386 			foreach(wchar c; s) {
7387 				INPUT input;
7388 				input.type = INPUT_KEYBOARD;
7389 				input.ki.wScan = c;
7390 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7391 				input.ki.dwExtraInfo = ei;
7392 				inputs ~= input;
7393 
7394 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7395 				inputs ~= input;
7396 			}
7397 
7398 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7399 				throw new WindowsApiException("SendInput", GetLastError());
7400 			}
7401 		} else version(X11) {
7402 			int delay = 0;
7403 			foreach(ch; s) {
7404 				pressKey(cast(Key) ch, true, delay);
7405 				pressKey(cast(Key) ch, false, delay);
7406 				delay += 5;
7407 			}
7408 		} else throw new NotYetImplementedException();
7409 	}
7410 
7411 	/++
7412 		Sends a fake press or release key event.
7413 
7414 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7415 
7416 		Bugs:
7417 			The `delay` parameter is not implemented yet on Windows.
7418 
7419 			Not implemented except on Windows and X11.
7420 	+/
7421 	void pressKey(Key key, bool pressed, int delay = 0) {
7422 		version(Windows) {
7423 			INPUT input;
7424 			input.type = INPUT_KEYBOARD;
7425 			input.ki.wVk = cast(ushort) key;
7426 
7427 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
7428 			input.ki.dwExtraInfo = GetMessageExtraInfo();
7429 
7430 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7431 				throw new WindowsApiException("SendInput", GetLastError());
7432 			}
7433 		} else version(X11) {
7434 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
7435 		} else throw new NotYetImplementedException();
7436 	}
7437 
7438 	/++
7439 		Sends a fake mouse button press or release event.
7440 
7441 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7442 
7443 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
7444 
7445 		Bugs:
7446 			The `delay` parameter is not implemented yet on Windows.
7447 
7448 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
7449 
7450 			All arguments will throw NotYetImplementedException on OSX Cocoa.
7451 	+/
7452 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
7453 		version(Windows) {
7454 			INPUT input;
7455 			input.type = INPUT_MOUSE;
7456 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7457 
7458 			// input.mi.mouseData for a wheel event
7459 
7460 			switch(button) {
7461 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
7462 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
7463 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
7464 				case MouseButton.wheelUp:
7465 				case MouseButton.wheelDown:
7466 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
7467 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
7468 				break;
7469 				case MouseButton.backButton: throw new NotYetImplementedException();
7470 				case MouseButton.forwardButton: throw new NotYetImplementedException();
7471 				default:
7472 			}
7473 
7474 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7475 				throw new WindowsApiException("SendInput", GetLastError());
7476 			}
7477 		} else version(X11) {
7478 			int btn;
7479 
7480 			switch(button) {
7481 				case MouseButton.left: btn = 1; break;
7482 				case MouseButton.middle: btn = 2; break;
7483 				case MouseButton.right: btn = 3; break;
7484 				case MouseButton.wheelUp: btn = 4; break;
7485 				case MouseButton.wheelDown: btn = 5; break;
7486 				case MouseButton.backButton: btn = 8; break;
7487 				case MouseButton.forwardButton: btn = 9; break;
7488 				default:
7489 			}
7490 
7491 			assert(btn);
7492 
7493 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7494 		} else throw new NotYetImplementedException();
7495 	}
7496 
7497 	///
7498 	static void moveMouseArrowBy(int dx, int dy) {
7499 		version(Windows) {
7500 			INPUT input;
7501 			input.type = INPUT_MOUSE;
7502 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7503 			input.mi.dx = dx;
7504 			input.mi.dy = dy;
7505 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7506 
7507 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7508 				throw new WindowsApiException("SendInput", GetLastError());
7509 			}
7510 		} else version(X11) {
7511 			auto disp = XDisplayConnection.get();
7512 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7513 			XFlush(disp);
7514 		} else throw new NotYetImplementedException();
7515 	}
7516 
7517 	///
7518 	static void moveMouseArrowTo(int x, int y) {
7519 		version(Windows) {
7520 			INPUT input;
7521 			input.type = INPUT_MOUSE;
7522 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7523 			input.mi.dx = x;
7524 			input.mi.dy = y;
7525 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
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 			auto root = RootWindow(disp, DefaultScreen(disp));
7533 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7534 			XFlush(disp);
7535 		} else throw new NotYetImplementedException();
7536 	}
7537 }
7538 
7539 
7540 
7541 /++
7542 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7543 
7544 	See_Also:
7545 	$(LIST
7546 		*[ScreenPainter]
7547 		*[ScreenPainter.rasterOp]
7548 	)
7549 +/
7550 enum RasterOp {
7551 	normal, /// Replaces the pixel.
7552 	xor, /// Uses bitwise xor to draw.
7553 }
7554 
7555 // being phobos-free keeps the size WAY down
7556 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7557 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7558 package(arsd) const(wchar)* toWStringz(string s) {
7559 	wstring r;
7560 	foreach(dchar c; s)
7561 		r ~= c;
7562 	r ~= '\0';
7563 	return r.ptr;
7564 }
7565 private string[] split(in void[] a, char c) {
7566 		string[] ret;
7567 		size_t previous = 0;
7568 		foreach(i, char ch; cast(ubyte[]) a) {
7569 			if(ch == c) {
7570 				ret ~= cast(string) a[previous .. i];
7571 				previous = i + 1;
7572 			}
7573 		}
7574 		if(previous != a.length)
7575 			ret ~= cast(string) a[previous .. $];
7576 		return ret;
7577 	}
7578 
7579 version(without_opengl) {
7580 	enum OpenGlOptions {
7581 		no,
7582 	}
7583 } else {
7584 	/++
7585 		Determines if you want an OpenGL context created on the new window.
7586 
7587 
7588 		See more: [#topics-3d|in the 3d topic].
7589 
7590 		---
7591 		import arsd.simpledisplay;
7592 		void main() {
7593 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7594 
7595 			// Set up the matrix
7596 			window.setAsCurrentOpenGlContext(); // make this window active
7597 
7598 			// This is called on each frame, we will draw our scene
7599 			window.redrawOpenGlScene = delegate() {
7600 
7601 			};
7602 
7603 			window.eventLoop(0);
7604 		}
7605 		---
7606 	+/
7607 	enum OpenGlOptions {
7608 		no, /// No OpenGL context is created
7609 		yes, /// Yes, create an OpenGL context
7610 	}
7611 
7612 	version(X11) {
7613 		static if (!SdpyIsUsingIVGLBinds) {
7614 
7615 
7616 			struct __GLXFBConfigRec {}
7617 			alias GLXFBConfig = __GLXFBConfigRec*;
7618 
7619 			//pragma(lib, "GL");
7620 			//pragma(lib, "GLU");
7621 			interface GLX {
7622 			extern(C) nothrow @nogc {
7623 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7624 						const int *attrib_list);
7625 
7626 				 void glXCopyContext(Display *dpy, GLXContext src,
7627 						GLXContext dst, arch_ulong mask);
7628 
7629 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7630 						GLXContext share_list, Bool direct);
7631 
7632 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7633 						Pixmap pixmap);
7634 
7635 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7636 
7637 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7638 
7639 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7640 						int attrib, int *value);
7641 
7642 				 GLXContext glXGetCurrentContext();
7643 
7644 				 GLXDrawable glXGetCurrentDrawable();
7645 
7646 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7647 
7648 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7649 						GLXContext ctx);
7650 
7651 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7652 
7653 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7654 
7655 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7656 
7657 				 void glXUseXFont(Font font, int first, int count, int list_base);
7658 
7659 				 void glXWaitGL();
7660 
7661 				 void glXWaitX();
7662 
7663 
7664 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7665 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7666 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7667 
7668 				char* glXQueryExtensionsString (Display*, int);
7669 				void* glXGetProcAddress (const(char)*);
7670 
7671 			}
7672 			}
7673 
7674 			version(OSX)
7675 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7676 			else
7677 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7678 			shared static this() {
7679 				glx.loadDynamicLibrary();
7680 			}
7681 
7682 			alias glbindGetProcAddress = glXGetProcAddress;
7683 		}
7684 	} else version(Windows) {
7685 		/* it is done below by interface GL */
7686 	} else
7687 		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.");
7688 }
7689 
7690 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7691 alias Resizablity = Resizability;
7692 
7693 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7694 enum Resizability {
7695 	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.
7696 	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.
7697 	/++
7698 		$(PITFALL
7699 			Planned for the future but not implemented.
7700 		)
7701 
7702 		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.
7703 
7704 		History:
7705 			Added November 11, 2022, but not yet implemented and may not be for some time.
7706 	+/
7707 	/*@__future*/ allowResizingMaintainingAspectRatio,
7708 	/++
7709 		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.
7710 
7711 		History:
7712 			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.
7713 
7714 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7715 	+/
7716 	automaticallyScaleIfPossible,
7717 }
7718 /// ditto
7719 alias Resizeability = Resizability;
7720 
7721 
7722 /++
7723 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7724 +/
7725 enum TextAlignment : uint {
7726 	Left = 0, ///
7727 	Center = 1, ///
7728 	Right = 2, ///
7729 
7730 	VerticalTop = 0, ///
7731 	VerticalCenter = 4, ///
7732 	VerticalBottom = 8, ///
7733 }
7734 
7735 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7736 alias Rectangle = arsd.color.Rectangle;
7737 
7738 
7739 /++
7740 	Keyboard press and release events.
7741 +/
7742 struct KeyEvent {
7743 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7744 	Key key;
7745 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7746 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7747 
7748 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7749 
7750 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7751 
7752 	SimpleWindow window; /// associated Window
7753 
7754 	/++
7755 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7756 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7757 		to predict if char events are actually coming..
7758 
7759 		Only available on X systems since this information is not given ahead of time elsewhere.
7760 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7761 
7762 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7763 		and potential quirks I'd recommend avoiding it.
7764 
7765 		History:
7766 			Added April 26, 2021 (dub v9.5)
7767 	+/
7768 	version(X11)
7769 		dchar[] charsPossible;
7770 
7771 	// convert key event to simplified string representation a-la emacs
7772 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7773 		uint dpos = 0;
7774 		void put (const(char)[] s...) nothrow @trusted {
7775 			static if (growdest) {
7776 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7777 			} else {
7778 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7779 			}
7780 		}
7781 
7782 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7783 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7784 		}
7785 
7786 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7787 
7788 		// put modifiers
7789 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7790 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7791 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7792 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7793 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7794 
7795 		if (this.key) {
7796 			foreach (string kn; __traits(allMembers, Key)) {
7797 				if (this.key == __traits(getMember, Key, kn)) {
7798 					// HACK!
7799 					static if (kn == "N0") put("0");
7800 					else static if (kn == "N1") put("1");
7801 					else static if (kn == "N2") put("2");
7802 					else static if (kn == "N3") put("3");
7803 					else static if (kn == "N4") put("4");
7804 					else static if (kn == "N5") put("5");
7805 					else static if (kn == "N6") put("6");
7806 					else static if (kn == "N7") put("7");
7807 					else static if (kn == "N8") put("8");
7808 					else static if (kn == "N9") put("9");
7809 					else put(kn);
7810 					return dest[0..dpos];
7811 				}
7812 			}
7813 			put("Unknown");
7814 		} else {
7815 			if (dpos && dest[dpos-1] == '+') --dpos;
7816 		}
7817 		return dest[0..dpos];
7818 	}
7819 
7820 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7821 
7822 	/** Parse string into key name with modifiers. It accepts things like:
7823 	 *
7824 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7825 	 *
7826 	 * Ctrl+Win+1 -- windows style
7827 	 *
7828 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7829 	 *
7830 	 * Ctrl Win 1 -- and space
7831 	 *
7832 	 * and even "Win + 1 + Ctrl".
7833 	 */
7834 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7835 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7836 
7837 		// remove trailing spaces
7838 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7839 
7840 		// tokens delimited by blank, '+', or '-'
7841 		// null on eol
7842 		const(char)[] getToken () nothrow @trusted @nogc {
7843 			// remove leading spaces and delimiters
7844 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7845 			if (name.length == 0) return null; // oops, no more tokens
7846 			// get token
7847 			size_t epos = 0;
7848 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7849 			assert(epos > 0 && epos <= name.length);
7850 			auto res = name[0..epos];
7851 			name = name[epos..$];
7852 			return res;
7853 		}
7854 
7855 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7856 			if (s0.length != s1.length) return false;
7857 			foreach (immutable ci, char c0; s0) {
7858 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7859 				char c1 = s1[ci];
7860 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7861 				if (c0 != c1) return false;
7862 			}
7863 			return true;
7864 		}
7865 
7866 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7867 		if (updown !is null) *updown = -1;
7868 		KeyEvent res;
7869 		res.key = cast(Key)0; // just in case
7870 		const(char)[] tk, tkn; // last token
7871 		bool allowEmascStyle = true;
7872 		bool ignoreModifiers = false;
7873 		tokenloop: for (;;) {
7874 			tk = tkn;
7875 			tkn = getToken();
7876 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7877 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7878 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7879 			if (allowEmascStyle && tkn.length != 0) {
7880 				if (tk.length == 1) {
7881 					char mdc = tk[0];
7882 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7883 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7884 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7885 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7886 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7887 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7888 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7889 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7890 				}
7891 			}
7892 			allowEmascStyle = false;
7893 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7894 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7895 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7896 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7897 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7898 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7899 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7900 			if (tk.length == 0) continue;
7901 			// try key name
7902 			if (res.key == 0) {
7903 				// little hack
7904 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7905 					final switch (tk[0]) {
7906 						case '0': tk = "N0"; break;
7907 						case '1': tk = "N1"; break;
7908 						case '2': tk = "N2"; break;
7909 						case '3': tk = "N3"; break;
7910 						case '4': tk = "N4"; break;
7911 						case '5': tk = "N5"; break;
7912 						case '6': tk = "N6"; break;
7913 						case '7': tk = "N7"; break;
7914 						case '8': tk = "N8"; break;
7915 						case '9': tk = "N9"; break;
7916 					}
7917 				}
7918 				foreach (string kn; __traits(allMembers, Key)) {
7919 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7920 				}
7921 			}
7922 			// unknown or duplicate key name, get out of here
7923 			break;
7924 		}
7925 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7926 		return res; // something
7927 	}
7928 
7929 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7930 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7931 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7932 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7933 		}
7934 		bool ignoreMods;
7935 		int updown;
7936 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7937 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7938 		if (this.key != ke.key) {
7939 			// things like "ctrl+alt" are complicated
7940 			uint tkm = this.modifierState&modmask;
7941 			uint kkm = ke.modifierState&modmask;
7942 			Key tk = this.key;
7943 			// ke
7944 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7945 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7946 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7947 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7948 			// this
7949 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7950 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7951 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7952 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7953 			return (tk == ke.key && tkm == kkm);
7954 		}
7955 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7956 	}
7957 }
7958 
7959 /// Sets the application name.
7960 @property string ApplicationName(string name) {
7961 	return _applicationName = name;
7962 }
7963 
7964 string _applicationName;
7965 
7966 /// ditto
7967 @property string ApplicationName() {
7968 	if(_applicationName is null) {
7969 		import core.runtime;
7970 		return Runtime.args[0];
7971 	}
7972 	return _applicationName;
7973 }
7974 
7975 
7976 /// Type of a [MouseEvent].
7977 enum MouseEventType : int {
7978 	motion = 0, /// The mouse moved inside the window
7979 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7980 	buttonReleased = 2, /// A mouse button was released
7981 }
7982 
7983 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7984 /++
7985 	Listen for this on your event listeners if you are interested in mouse action.
7986 
7987 	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.
7988 
7989 	Examples:
7990 
7991 	This will draw boxes on the window with the mouse as you hold the left button.
7992 	---
7993 	import arsd.simpledisplay;
7994 
7995 	void main() {
7996 		auto window = new SimpleWindow();
7997 
7998 		window.eventLoop(0,
7999 			(MouseEvent ev) {
8000 				if(ev.modifierState & ModifierState.leftButtonDown) {
8001 					auto painter = window.draw();
8002 					painter.fillColor = Color.red;
8003 					painter.outlineColor = Color.black;
8004 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
8005 				}
8006 			}
8007 		);
8008 	}
8009 	---
8010 +/
8011 struct MouseEvent {
8012 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
8013 
8014 	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.
8015 	int y; /// Current Y position of the cursor when the event fired.
8016 
8017 	int dx; /// Change in X position since last report
8018 	int dy; /// Change in Y position since last report
8019 
8020 	MouseButton button; /// See [MouseButton]
8021 	int modifierState; /// See [ModifierState]
8022 
8023 	version(X11)
8024 		private Time timestamp;
8025 
8026 	/// Returns a linear representation of mouse button,
8027 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
8028 	///
8029 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
8030 	@property ubyte buttonLinear() const {
8031 		import core.bitop;
8032 		if(button == 0)
8033 			return 0;
8034 		return (bsf(button) + 1) & 0b1111;
8035 	}
8036 
8037 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
8038 
8039 	SimpleWindow window; /// The window in which the event happened.
8040 
8041 	Point globalCoordinates() {
8042 		Point p;
8043 		if(window is null)
8044 			throw new Exception("wtf");
8045 		static if(UsingSimpledisplayX11) {
8046 			Window child;
8047 			XTranslateCoordinates(
8048 				XDisplayConnection.get,
8049 				window.impl.window,
8050 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
8051 				x, y, &p.x, &p.y, &child);
8052 			return p;
8053 		} else version(Windows) {
8054 			POINT[1] points;
8055 			points[0].x = x;
8056 			points[0].y = y;
8057 			MapWindowPoints(
8058 				window.impl.hwnd,
8059 				null,
8060 				points.ptr,
8061 				points.length
8062 			);
8063 			p.x = points[0].x;
8064 			p.y = points[0].y;
8065 
8066 			return p;
8067 		} else version(OSXCocoa) {
8068 			throw new NotYetImplementedException();
8069 		} else version(Emscripten) {
8070 			throw new NotYetImplementedException();
8071 		} else static assert(0);
8072 	}
8073 
8074 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
8075 
8076 	/**
8077 	can contain emacs-like modifier prefix
8078 	case-insensitive names:
8079 		lmbX/leftX
8080 		rmbX/rightX
8081 		mmbX/middleX
8082 		wheelX
8083 		motion (no prefix allowed)
8084 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
8085 	*/
8086 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
8087 		if (str.length == 0) return false; // just in case
8088 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
8089 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
8090 		auto anchor = str;
8091 		uint mods = 0; // uint.max == any
8092 		// interesting bits in kmod
8093 		uint kmodmask =
8094 			ModifierState.shift|
8095 			ModifierState.ctrl|
8096 			ModifierState.alt|
8097 			ModifierState.windows|
8098 			ModifierState.leftButtonDown|
8099 			ModifierState.middleButtonDown|
8100 			ModifierState.rightButtonDown|
8101 			0;
8102 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
8103 		bool wasButtons = false;
8104 		while (str.length) {
8105 			if (str.ptr[0] <= ' ') {
8106 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
8107 				continue;
8108 			}
8109 			// one-letter modifier?
8110 			if (str.length >= 2 && str.ptr[1] == '-') {
8111 				switch (str.ptr[0]) {
8112 					case '*': // "any" modifier (cannot be undone)
8113 						mods = mods.max;
8114 						break;
8115 					case 'C': case 'c': // emacs "ctrl"
8116 						if (mods != mods.max) mods |= ModifierState.ctrl;
8117 						break;
8118 					case 'M': case 'm': // emacs "meta"
8119 						if (mods != mods.max) mods |= ModifierState.alt;
8120 						break;
8121 					case 'S': case 's': // emacs "shift"
8122 						if (mods != mods.max) mods |= ModifierState.shift;
8123 						break;
8124 					case 'H': case 'h': // emacs "hyper" (aka winkey)
8125 						if (mods != mods.max) mods |= ModifierState.windows;
8126 						break;
8127 					default:
8128 						return false; // unknown modifier
8129 				}
8130 				str = str[2..$];
8131 				continue;
8132 			}
8133 			// word
8134 			char[16] buf = void; // locased
8135 			auto wep = 0;
8136 			while (str.length) {
8137 				immutable char ch = str.ptr[0];
8138 				if (ch <= ' ' || ch == '-') break;
8139 				str = str[1..$];
8140 				if (wep > buf.length) return false; // too long
8141 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8142 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8143 				else return false; // invalid char
8144 			}
8145 			if (wep == 0) return false; // just in case
8146 			uint bnum;
8147 			enum UpDown { None = -1, Up, Down, Any }
8148 			auto updown = UpDown.None; // 0: up; 1: down
8149 			switch (buf[0..wep]) {
8150 				// left button
8151 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
8152 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
8153 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
8154 				case "lmb": case "left": bnum = 0; break;
8155 				// middle button
8156 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
8157 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
8158 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
8159 				case "mmb": case "middle": bnum = 1; break;
8160 				// right button
8161 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
8162 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
8163 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
8164 				case "rmb": case "right": bnum = 2; break;
8165 				// wheel
8166 				case "wheelup": updown = UpDown.Up; goto case "wheel";
8167 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
8168 				case "wheelany": updown = UpDown.Any; goto case "wheel";
8169 				case "wheel": bnum = 3; break;
8170 				// motion
8171 				case "motion": bnum = 7; break;
8172 				// unknown
8173 				default: return false;
8174 			}
8175 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8176 			// parse possible "-up" or "-down"
8177 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
8178 				wep = 0;
8179 				foreach (immutable idx, immutable char ch; str[1..$]) {
8180 					if (ch <= ' ' || ch == '-') break;
8181 					assert(idx == wep); // for now; trick
8182 					if (wep > buf.length) { wep = 0; break; } // too long
8183 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8184 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8185 					else { wep = 0; break; } // invalid char
8186 				}
8187 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
8188 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
8189 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
8190 				// remove parsed part
8191 				if (updown != UpDown.None) str = str[wep+1..$];
8192 			}
8193 			if (updown == UpDown.None) {
8194 				updown = UpDown.Down;
8195 			}
8196 			wasButtons = wasButtons || (bnum <= 2);
8197 			//assert(updown != UpDown.None);
8198 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8199 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
8200 			if (lastButt != lastButt.max) {
8201 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
8202 				if (mods != mods.max) {
8203 					uint butbit = 0;
8204 					final switch (lastButt&0x03) {
8205 						case 0: butbit = ModifierState.leftButtonDown; break;
8206 						case 1: butbit = ModifierState.middleButtonDown; break;
8207 						case 2: butbit = ModifierState.rightButtonDown; break;
8208 					}
8209 					     if (lastButt&Flag.Down) mods |= butbit;
8210 					else if (lastButt&Flag.Up) mods &= ~butbit;
8211 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
8212 				}
8213 			}
8214 			// remember last button
8215 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
8216 		}
8217 		// no button -- nothing to do
8218 		if (lastButt == lastButt.max) return false;
8219 		// done parsing, check if something's left
8220 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
8221 		// remove action button from mask
8222 		if ((lastButt&0xff) < 3) {
8223 			final switch (lastButt&0x03) {
8224 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
8225 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
8226 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
8227 			}
8228 		}
8229 		// special case: "Motion" means "ignore buttons"
8230 		if ((lastButt&0xff) == 7 && !wasButtons) {
8231 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
8232 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
8233 		}
8234 		uint kmod = event.modifierState&kmodmask;
8235 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
8236 		// check modifier state
8237 		if (mods != mods.max) {
8238 			if (kmod != mods) return false;
8239 		}
8240 		// now check type
8241 		if ((lastButt&0xff) == 7) {
8242 			// motion
8243 			if (event.type != MouseEventType.motion) return false;
8244 		} else if ((lastButt&0xff) == 3) {
8245 			// wheel
8246 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
8247 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
8248 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
8249 			return false;
8250 		} else {
8251 			// buttons
8252 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
8253 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
8254 			{
8255 				return false;
8256 			}
8257 			// button number
8258 			switch (lastButt&0x03) {
8259 				case 0: if (event.button != MouseButton.left) return false; break;
8260 				case 1: if (event.button != MouseButton.middle) return false; break;
8261 				case 2: if (event.button != MouseButton.right) return false; break;
8262 				default: return false;
8263 			}
8264 		}
8265 		return true;
8266 	}
8267 }
8268 
8269 version(arsd_mevent_strcmp_test) unittest {
8270 	MouseEvent event;
8271 	event.type = MouseEventType.buttonPressed;
8272 	event.button = MouseButton.left;
8273 	event.modifierState = ModifierState.ctrl;
8274 	assert(event == "C-LMB");
8275 	assert(event != "C-LMBUP");
8276 	assert(event != "C-LMB-UP");
8277 	assert(event != "C-S-LMB");
8278 	assert(event == "*-LMB");
8279 	assert(event != "*-LMB-UP");
8280 
8281 	event.type = MouseEventType.buttonReleased;
8282 	assert(event != "C-LMB");
8283 	assert(event == "C-LMBUP");
8284 	assert(event == "C-LMB-UP");
8285 	assert(event != "C-S-LMB");
8286 	assert(event != "*-LMB");
8287 	assert(event == "*-LMB-UP");
8288 
8289 	event.button = MouseButton.right;
8290 	event.modifierState |= ModifierState.shift;
8291 	event.type = MouseEventType.buttonPressed;
8292 	assert(event != "C-LMB");
8293 	assert(event != "C-LMBUP");
8294 	assert(event != "C-LMB-UP");
8295 	assert(event != "C-S-LMB");
8296 	assert(event != "*-LMB");
8297 	assert(event != "*-LMB-UP");
8298 
8299 	assert(event != "C-RMB");
8300 	assert(event != "C-RMBUP");
8301 	assert(event != "C-RMB-UP");
8302 	assert(event == "C-S-RMB");
8303 	assert(event == "*-RMB");
8304 	assert(event != "*-RMB-UP");
8305 }
8306 
8307 /// This gives a few more options to drawing lines and such
8308 struct Pen {
8309 	Color color; /// the foreground color
8310 	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.
8311 	Style style; /// See [Style]
8312 /+
8313 // From X.h
8314 
8315 #define LineSolid		0
8316 #define LineOnOffDash		1
8317 #define LineDoubleDash		2
8318        LineDou-        The full path of the line is drawn, but the
8319        bleDash         even dashes are filled differently from the
8320                        odd dashes (see fill-style) with CapButt
8321                        style used where even and odd dashes meet.
8322 
8323 
8324 
8325 /* capStyle */
8326 
8327 #define CapNotLast		0
8328 #define CapButt			1
8329 #define CapRound		2
8330 #define CapProjecting		3
8331 
8332 /* joinStyle */
8333 
8334 #define JoinMiter		0
8335 #define JoinRound		1
8336 #define JoinBevel		2
8337 
8338 /* fillStyle */
8339 
8340 #define FillSolid		0
8341 #define FillTiled		1
8342 #define FillStippled		2
8343 #define FillOpaqueStippled	3
8344 
8345 
8346 +/
8347 	/// Style of lines drawn
8348 	enum Style {
8349 		Solid, /// a solid line
8350 		Dashed, /// a dashed line
8351 		Dotted, /// a dotted line
8352 	}
8353 }
8354 
8355 
8356 /++
8357 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
8358 
8359 
8360 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
8361 
8362 	$(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.)
8363 
8364 	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.
8365 
8366 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
8367 
8368 	$(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.
8369 
8370 	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!
8371 
8372 	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!)
8373 
8374 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
8375 
8376 	---
8377 		auto image = new Image(256, 256);
8378 		scope(exit) destroy(image);
8379 	---
8380 
8381 	As long as you don't hold on to it outside the scope.
8382 
8383 	I might change it to be an owned pointer at some point in the future.
8384 
8385 	)
8386 
8387 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
8388 	you can also often get a fair amount of speedup by getting the raw data format and
8389 	writing some custom code.
8390 
8391 	FIXME INSERT EXAMPLES HERE
8392 
8393 
8394 +/
8395 final class Image {
8396 	///
8397 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
8398 		this.width = width;
8399 		this.height = height;
8400 		this.enableAlpha = enableAlpha;
8401 
8402 		impl.createImage(width, height, forcexshm, enableAlpha);
8403 	}
8404 
8405 	///
8406 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
8407 		this(size.width, size.height, forcexshm, enableAlpha);
8408 	}
8409 
8410 	private bool suppressDestruction;
8411 
8412 	version(X11)
8413 	this(XImage* handle) {
8414 		this.handle = handle;
8415 		this.rawData = cast(ubyte*) handle.data;
8416 		this.width = handle.width;
8417 		this.height = handle.height;
8418 		this.enableAlpha = handle.depth == 32;
8419 		suppressDestruction = true;
8420 	}
8421 
8422 	~this() {
8423 		if(suppressDestruction) return;
8424 		impl.dispose();
8425 	}
8426 
8427 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
8428 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
8429 	pure const @system nothrow {
8430 		/*
8431 			To use these to draw a blue rectangle with size WxH at position X,Y...
8432 
8433 			// make certain that it will fit before we proceed
8434 			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!
8435 
8436 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
8437 			// (though calculating them isn't really that expensive).
8438 			auto nextLineAdjustment = img.adjustmentForNextLine();
8439 			auto offR = img.redByteOffset();
8440 			auto offB = img.blueByteOffset();
8441 			auto offG = img.greenByteOffset();
8442 			auto bpp = img.bytesPerPixel();
8443 
8444 			auto data = img.getDataPointer();
8445 
8446 			// figure out the starting byte offset
8447 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
8448 
8449 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
8450 
8451 			// and now our drawing loop for the rectangle
8452 			foreach(y; 0 .. H) {
8453 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
8454 				foreach(x; 0 .. W) {
8455 					// write our color
8456 					data[offR] = 0;
8457 					data[offG] = 0;
8458 					data[offB] = 255;
8459 
8460 					data += bpp; // moving to the next pixel is just an addition...
8461 				}
8462 				startOfLine += nextLineAdjustment;
8463 			}
8464 
8465 
8466 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
8467 
8468 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
8469 			can be made into a bitmask or something so we can write them as *uint...
8470 		*/
8471 
8472 		///
8473 		int offsetForTopLeftPixel() {
8474 			version(X11) {
8475 				return 0;
8476 			} else version(Windows) {
8477 				if(enableAlpha) {
8478 					return (width * 4) * (height - 1);
8479 				} else {
8480 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
8481 				}
8482 			} else version(OSXCocoa) {
8483 				return 0 ; //throw new NotYetImplementedException();
8484 			} else version(Emscripten) {
8485 				return 0;
8486 			} else static assert(0, "fill in this info for other OSes");
8487 		}
8488 
8489 		///
8490 		int offsetForPixel(int x, int y) {
8491 			version(X11) {
8492 				auto offset = (y * width + x) * 4;
8493 				return offset;
8494 			} else version(Windows) {
8495 				if(enableAlpha) {
8496 					auto itemsPerLine = width * 4;
8497 					// remember, bmps are upside down
8498 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8499 					return offset;
8500 				} else {
8501 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8502 					// remember, bmps are upside down
8503 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8504 					return offset;
8505 				}
8506 			} else version(OSXCocoa) {
8507 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8508 			} else version(Emscripten) {
8509 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8510 			} else static assert(0, "fill in this info for other OSes");
8511 		}
8512 
8513 		///
8514 		int adjustmentForNextLine() {
8515 			version(X11) {
8516 				return width * 4;
8517 			} else version(Windows) {
8518 				// windows bmps are upside down, so the adjustment is actually negative
8519 				if(enableAlpha)
8520 					return - (cast(int) width * 4);
8521 				else
8522 					return -((cast(int) width * 3 + 3) / 4) * 4;
8523 			} else version(OSXCocoa) {
8524 				return width * 4 ; //throw new NotYetImplementedException();
8525 			} else version(Emscripten) {
8526 				return width * 4 ; //throw new NotYetImplementedException();
8527 			} else static assert(0, "fill in this info for other OSes");
8528 		}
8529 
8530 		/// once you have the position of a pixel, use these to get to the proper color
8531 		int redByteOffset() {
8532 			version(X11) {
8533 				return 2;
8534 			} else version(Windows) {
8535 				return 2;
8536 			} else version(OSXCocoa) {
8537 				return 2 ; //throw new NotYetImplementedException();
8538 			} else version(Emscripten) {
8539 				return 2 ; //throw new NotYetImplementedException();
8540 			} else static assert(0, "fill in this info for other OSes");
8541 		}
8542 
8543 		///
8544 		int greenByteOffset() {
8545 			version(X11) {
8546 				return 1;
8547 			} else version(Windows) {
8548 				return 1;
8549 			} else version(OSXCocoa) {
8550 				return 1 ; //throw new NotYetImplementedException();
8551 			} else version(Emscripten) {
8552 				return 1 ; //throw new NotYetImplementedException();
8553 			} else static assert(0, "fill in this info for other OSes");
8554 		}
8555 
8556 		///
8557 		int blueByteOffset() {
8558 			version(X11) {
8559 				return 0;
8560 			} else version(Windows) {
8561 				return 0;
8562 			} else version(OSXCocoa) {
8563 				return 0 ; //throw new NotYetImplementedException();
8564 			} else version(Emscripten) {
8565 				return 0 ; //throw new NotYetImplementedException();
8566 			} else static assert(0, "fill in this info for other OSes");
8567 		}
8568 
8569 		/// Only valid if [enableAlpha] is true
8570 		int alphaByteOffset() {
8571 			version(X11) {
8572 				return 3;
8573 			} else version(Windows) {
8574 				return 3;
8575 			} else version(OSXCocoa) {
8576 				return 3; //throw new NotYetImplementedException();
8577 			} else version(Emscripten) {
8578 				return 3 ; //throw new NotYetImplementedException();
8579 			} else static assert(0, "fill in this info for other OSes");
8580 		}
8581 	}
8582 
8583 	///
8584 	final void putPixel(int x, int y, Color c) {
8585 		if(x < 0 || x >= width)
8586 			return;
8587 		if(y < 0 || y >= height)
8588 			return;
8589 
8590 		impl.setPixel(x, y, c);
8591 	}
8592 
8593 	///
8594 	final Color getPixel(int x, int y) {
8595 		if(x < 0 || x >= width)
8596 			return Color.transparent;
8597 		if(y < 0 || y >= height)
8598 			return Color.transparent;
8599 
8600 		version(OSXCocoa) throw new NotYetImplementedException(); else
8601 		return impl.getPixel(x, y);
8602 	}
8603 
8604 	///
8605 	final void opIndexAssign(Color c, int x, int y) {
8606 		putPixel(x, y, c);
8607 	}
8608 
8609 	///
8610 	TrueColorImage toTrueColorImage() {
8611 		auto tci = new TrueColorImage(width, height);
8612 		convertToRgbaBytes(tci.imageData.bytes);
8613 		return tci;
8614 	}
8615 
8616 	///
8617 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8618 		auto tci = i.getAsTrueColorImage();
8619 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8620 		static if(UsingSimpledisplayX11)
8621 			img.premultiply = premultiply;
8622 		img.setRgbaBytes(tci.imageData.bytes);
8623 		return img;
8624 	}
8625 
8626 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8627 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8628 	/// if you pass null, it will allocate a new one.
8629 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8630 		if(where is null)
8631 			where = new ubyte[this.width*this.height*4];
8632 		convertToRgbaBytes(where);
8633 		return where;
8634 	}
8635 
8636 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8637 	void setRgbaBytes(in ubyte[] from ) {
8638 		assert(from.length == this.width * this.height * 4);
8639 		setFromRgbaBytes(from);
8640 	}
8641 
8642 	// FIXME: make properly cross platform by getting rgba right
8643 
8644 	/// warning: this is not portable across platforms because the data format can change
8645 	ubyte* getDataPointer() {
8646 		return impl.rawData;
8647 	}
8648 
8649 	/// for use with getDataPointer
8650 	final int bytesPerLine() const pure @safe nothrow {
8651 		version(Windows)
8652 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8653 		else version(X11)
8654 			return 4 * width;
8655 		else version(OSXCocoa)
8656 			return 4 * width;
8657 		else static assert(0);
8658 	}
8659 
8660 	/// for use with getDataPointer
8661 	final int bytesPerPixel() const pure @safe nothrow {
8662 		version(Windows)
8663 			return enableAlpha ? 4 : 3;
8664 		else version(X11)
8665 			return 4;
8666 		else version(OSXCocoa)
8667 			return 4;
8668 		else static assert(0);
8669 	}
8670 
8671 	///
8672 	immutable int width;
8673 
8674 	///
8675 	immutable int height;
8676 
8677 	///
8678 	immutable bool enableAlpha;
8679     //private:
8680 	mixin NativeImageImplementation!() impl;
8681 }
8682 
8683 /++
8684 	A convenience function to pop up a window displaying the image.
8685 	If you pass a win, it will draw the image in it. Otherwise, it will
8686 	create a window with the size of the image and run its event loop, closing
8687 	when a key is pressed.
8688 
8689 	History:
8690 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8691 		always block until the application quit which could cause bizarre behavior
8692 		inside a more complex application. Now, the default is to block until
8693 		this window closes if it is the only event loop running, and otherwise,
8694 		not to block at all and just pop up the display window asynchronously.
8695 +/
8696 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8697 	if(win is null) {
8698 		win = new SimpleWindow(image);
8699 		{
8700 			auto p = win.draw;
8701 			p.drawImage(Point(0, 0), image);
8702 		}
8703 		win.eventLoopWithBlockingMode(
8704 			bm, 0,
8705 			(KeyEvent ev) {
8706 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8707 			} );
8708 	} else {
8709 		win.image = image;
8710 	}
8711 }
8712 
8713 enum FontWeight : int {
8714 	dontcare = 0,
8715 	thin = 100,
8716 	extralight = 200,
8717 	light = 300,
8718 	regular = 400,
8719 	medium = 500,
8720 	semibold = 600,
8721 	bold = 700,
8722 	extrabold = 800,
8723 	heavy = 900
8724 }
8725 
8726 /++
8727 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8728 
8729 	History:
8730 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8731 +/
8732 interface MeasurableFont {
8733 	/++
8734 		Returns true if it is a monospace font, meaning each of the
8735 		glyphs (at least the ascii characters) have matching width
8736 		and no kerning, so you can determine the display width of some
8737 		strings by simply multiplying the string width by [averageWidth].
8738 
8739 		(Please note that multiply doesn't $(I actually) work in general,
8740 		consider characters like tab and newline, but it does sometimes.)
8741 	+/
8742 	bool isMonospace();
8743 
8744 	/++
8745 		The average width of glyphs in the font, traditionally equal to the
8746 		width of the lowercase x. Can be used to estimate bounding boxes,
8747 		especially if the font [isMonospace].
8748 
8749 		Given in pixels.
8750 	+/
8751 	int averageWidth();
8752 	/++
8753 		The height of the bounding box of a line.
8754 	+/
8755 	int height();
8756 	/++
8757 		The maximum ascent of a glyph above the baseline.
8758 
8759 		Given in pixels.
8760 	+/
8761 	int ascent();
8762 	/++
8763 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8764 
8765 		Given in pixels.
8766 	+/
8767 	int descent();
8768 	/++
8769 		The display width of the given string, and if you provide a window, it will use it to
8770 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8771 
8772 		Given in pixels.
8773 	+/
8774 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8775 
8776 }
8777 
8778 // FIXME: i need a font cache and it needs to handle disconnects.
8779 
8780 /++
8781 	Represents a font loaded off the operating system or the X server.
8782 
8783 
8784 	While the api here is unified cross platform, the fonts are not necessarily
8785 	available, even across machines of the same platform, so be sure to always check
8786 	for null (using [isNull]) and have a fallback plan.
8787 
8788 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8789 
8790 	Worst case, a null font will automatically fall back to the default font loaded
8791 	for your system.
8792 +/
8793 class OperatingSystemFont : MeasurableFont {
8794 	// FIXME: when the X Connection is lost, these need to be invalidated!
8795 	// that means I need to store the original stuff again to reconstruct it too.
8796 
8797 	version(Emscripten) {
8798 		void* font;
8799 	} else version(X11) {
8800 		XFontStruct* font;
8801 		XFontSet fontset;
8802 
8803 		version(with_xft) {
8804 			XftFont* xftFont;
8805 			bool isXft;
8806 		}
8807 	} else version(Windows) {
8808 		HFONT font;
8809 		int width_;
8810 		int height_;
8811 	} else version(OSXCocoa) {
8812 		NSFont font;
8813 	} else static assert(0);
8814 
8815 	/++
8816 		Constructs the class and immediately calls [load].
8817 	+/
8818 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8819 		load(name, size, weight, italic);
8820 	}
8821 
8822 	/++
8823 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8824 
8825 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8826 
8827 		History:
8828 			Added January 24, 2021.
8829 	+/
8830 	this() {
8831 		// this space intentionally left blank
8832 	}
8833 
8834 	/++
8835 		Constructs a copy of the given font object.
8836 
8837 		History:
8838 			Added January 7, 2023.
8839 	+/
8840 	this(OperatingSystemFont font) {
8841 		if(font is null || font.loadedInfo is LoadedInfo.init)
8842 			loadDefault();
8843 		else
8844 			load(font.loadedInfo.tupleof);
8845 	}
8846 
8847 	/++
8848 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8849 
8850 		History:
8851 			Added November 13, 2020.
8852 	+/
8853 	version(with_xft)
8854 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8855 		unload();
8856 
8857 		if(!XftLibrary.attempted) {
8858 			XftLibrary.loadDynamicLibrary();
8859 		}
8860 
8861 		if(!XftLibrary.loadSuccessful)
8862 			return false;
8863 
8864 		auto display = XDisplayConnection.get;
8865 
8866 		char[256] nameBuffer = void;
8867 		int nbp = 0;
8868 
8869 		void add(in char[] a) {
8870 			nameBuffer[nbp .. nbp + a.length] = a[];
8871 			nbp += a.length;
8872 		}
8873 		add(name);
8874 
8875 		if(size) {
8876 			add(":size=");
8877 			add(toInternal!string(size));
8878 		}
8879 		if(weight != FontWeight.dontcare && weight != 400) {
8880 			if(weight < 400)
8881 				add(":style=Light");
8882 			else
8883 				add(":style=Bold");
8884 			add(":weight=");
8885 			add(weightToString(weight));
8886 		}
8887 		if(italic) {
8888 			if(weight == FontWeight.dontcare)
8889 				add(":style=Italic");
8890 			add(":slant=100");
8891 		}
8892 
8893 		nameBuffer[nbp] = 0;
8894 
8895 		this.xftFont = XftFontOpenName(
8896 			display,
8897 			DefaultScreen(display),
8898 			nameBuffer.ptr
8899 		);
8900 
8901 		this.isXft = true;
8902 
8903 		if(xftFont !is null) {
8904 			isMonospace_ = stringWidth("x") == stringWidth("M");
8905 			ascent_ = xftFont.ascent;
8906 			descent_ = xftFont.descent;
8907 		}
8908 
8909 		return !isNull();
8910 	}
8911 
8912 	/++
8913 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8914 
8915 
8916 		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.
8917 
8918 		If `pattern` is null, it returns all available font families.
8919 
8920 		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.
8921 
8922 		The format of the pattern is platform-specific.
8923 
8924 		History:
8925 			Added May 1, 2021 (dub v9.5)
8926 	+/
8927 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8928 		version(Windows) {
8929 			auto hdc = GetDC(null);
8930 			scope(exit) ReleaseDC(null, hdc);
8931 			LOGFONT logfont;
8932 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8933 				auto localHandler = *(cast(typeof(handler)*) p);
8934 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8935 			}
8936 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8937 		} else version(X11) {
8938 			//import core.stdc.stdio;
8939 			bool done = false;
8940 			version(with_xft) {
8941 				if(!XftLibrary.attempted) {
8942 					XftLibrary.loadDynamicLibrary();
8943 				}
8944 
8945 				if(!XftLibrary.loadSuccessful)
8946 					goto skipXft;
8947 
8948 				if(!FontConfigLibrary.attempted)
8949 					FontConfigLibrary.loadDynamicLibrary();
8950 				if(!FontConfigLibrary.loadSuccessful)
8951 					goto skipXft;
8952 
8953 				{
8954 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8955 					if(got is null)
8956 						goto skipXft;
8957 					scope(exit) FcFontSetDestroy(got);
8958 
8959 					auto fontPatterns = got.fonts[0 .. got.nfont];
8960 					foreach(candidate; fontPatterns) {
8961 						char* where, whereStyle;
8962 
8963 						char* pmg = FcNameUnparse(candidate);
8964 
8965 						//FcPatternGetString(candidate, "family", 0, &where);
8966 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8967 						//if(where && whereStyle) {
8968 						if(pmg) {
8969 							if(!handler(pmg.sliceCString))
8970 								return;
8971 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8972 						}
8973 					}
8974 				}
8975 			}
8976 
8977 			skipXft:
8978 
8979 			if(pattern is null)
8980 				pattern = "*";
8981 
8982 			int count;
8983 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8984 			scope(exit) XFreeFontNames(coreFontsRaw);
8985 
8986 			auto coreFonts = coreFontsRaw[0 .. count];
8987 
8988 			foreach(font; coreFonts) {
8989 				char[128] tmp;
8990 				tmp[0 ..5] = "core:";
8991 				auto cf = font.sliceCString;
8992 				if(5 + cf.length > tmp.length)
8993 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8994 				tmp[5 .. 5 + cf.length] = cf;
8995 				if(!handler(tmp[0 .. 5 + cf.length]))
8996 					return;
8997 			}
8998 		}
8999 	}
9000 
9001 	/++
9002 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
9003 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
9004 
9005 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
9006 		underlying system doesn't support returning the raw bytes.
9007 
9008 		History:
9009 			Added September 10, 2021 (dub v10.3)
9010 	+/
9011 	ubyte[] getTtfBytes() {
9012 		if(isNull)
9013 			return null;
9014 
9015 		version(Windows) {
9016 			auto dc = GetDC(null);
9017 			auto orig = SelectObject(dc, font);
9018 
9019 			scope(exit) {
9020 				SelectObject(dc, orig);
9021 				ReleaseDC(null, dc);
9022 			}
9023 
9024 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
9025 			if(res == GDI_ERROR)
9026 				return null;
9027 
9028 			ubyte[] buffer = new ubyte[](res);
9029 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
9030 			if(res == GDI_ERROR)
9031 				return null; // wtf really tbh
9032 
9033 			return buffer;
9034 		} else version(with_xft) {
9035 			if(isXft && xftFont) {
9036 				if(!FontConfigLibrary.attempted)
9037 					FontConfigLibrary.loadDynamicLibrary();
9038 				if(!FontConfigLibrary.loadSuccessful)
9039 					return null;
9040 
9041 				char* file;
9042 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
9043 					if (file !is null && file[0]) {
9044 						import core.stdc.stdio;
9045 						auto fp = fopen(file, "rb");
9046 						if(fp is null)
9047 							return null;
9048 						scope(exit)
9049 							fclose(fp);
9050 						fseek(fp, 0, SEEK_END);
9051 						ubyte[] buffer = new ubyte[](ftell(fp));
9052 						fseek(fp, 0, SEEK_SET);
9053 
9054 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
9055 						if(got != buffer.length)
9056 							return null;
9057 
9058 						return buffer;
9059 					}
9060 				}
9061 			}
9062 			return null;
9063 		} else throw new NotYetImplementedException();
9064 	}
9065 
9066 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
9067 
9068 	private string weightToString(FontWeight weight) {
9069 		with(FontWeight)
9070 		final switch(weight) {
9071 			case dontcare: return "*";
9072 			case thin: return "extralight";
9073 			case extralight: return "extralight";
9074 			case light: return "light";
9075 			case regular: return "regular";
9076 			case medium: return "medium";
9077 			case semibold: return "demibold";
9078 			case bold: return "bold";
9079 			case extrabold: return "demibold";
9080 			case heavy: return "black";
9081 		}
9082 	}
9083 
9084 	/++
9085 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
9086 
9087 		History:
9088 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9089 	+/
9090 	version(X11)
9091 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9092 		unload();
9093 
9094 		string xfontstr;
9095 
9096 		if(name.length > 3 && name[0 .. 3] == "-*-") {
9097 			// this is kinda a disgusting hack but if the user sends an exact
9098 			// string I'd like to honor it...
9099 			xfontstr = name;
9100 		} else {
9101 			string weightstr = weightToString(weight);
9102 			string sizestr;
9103 			if(size == 0)
9104 				sizestr = "*";
9105 			else
9106 				sizestr = toInternal!string(size);
9107 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
9108 		}
9109 
9110 		// writeln(xfontstr);
9111 
9112 		auto display = XDisplayConnection.get;
9113 
9114 		font = XLoadQueryFont(display, xfontstr.ptr);
9115 		if(font is null)
9116 			return false;
9117 
9118 		char** lol;
9119 		int lol2;
9120 		char* lol3;
9121 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
9122 
9123 		prepareFontInfo();
9124 
9125 		return !isNull();
9126 	}
9127 
9128 	version(X11)
9129 	private void prepareFontInfo() {
9130 		if(font !is null) {
9131 			isMonospace_ = stringWidth("l") == stringWidth("M");
9132 			ascent_ = font.max_bounds.ascent;
9133 			descent_ = font.max_bounds.descent;
9134 		}
9135 	}
9136 
9137 	version(OSXCocoa)
9138 	private void prepareFontInfo() {
9139 		if(font !is null) {
9140 			isMonospace_ = font.isFixedPitch;
9141 			ascent_ = cast(int) font.ascender;
9142 			descent_ = cast(int) - font.descender;
9143 		}
9144 	}
9145 
9146 
9147 	/++
9148 		Loads a Windows font. You probably want to use [load] instead to be more generic.
9149 
9150 		History:
9151 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9152 	+/
9153 	version(Windows)
9154 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
9155 		unload();
9156 
9157 		WCharzBuffer buffer = WCharzBuffer(name);
9158 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
9159 
9160 		prepareFontInfo(hdc);
9161 
9162 		return !isNull();
9163 	}
9164 
9165 	version(Windows)
9166 	void prepareFontInfo(HDC hdc = null) {
9167 		if(font is null)
9168 			return;
9169 
9170 		TEXTMETRIC tm;
9171 		auto dc = hdc ? hdc : GetDC(null);
9172 		auto orig = SelectObject(dc, font);
9173 		GetTextMetrics(dc, &tm);
9174 		SelectObject(dc, orig);
9175 		if(hdc is null)
9176 			ReleaseDC(null, dc);
9177 
9178 		width_ = tm.tmAveCharWidth;
9179 		height_ = tm.tmHeight;
9180 		ascent_ = tm.tmAscent;
9181 		descent_ = tm.tmDescent;
9182 		// 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.
9183 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
9184 	}
9185 
9186 
9187 	/++
9188 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
9189 
9190 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
9191 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
9192 
9193 		On Windows, it forwards directly to [loadWin32].
9194 
9195 		Params:
9196 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
9197 			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.
9198 			weight = approximate boldness, results may vary.
9199 			italic = try to get a slanted version of the given font.
9200 
9201 		History:
9202 			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.
9203 	+/
9204 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9205 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
9206 		version(X11) {
9207 			version(with_xft) {
9208 				if(name.length > 5 && name[0 .. 5] == "core:") {
9209 					goto core;
9210 				}
9211 
9212 				if(loadXft(name, size, weight, italic))
9213 					return true;
9214 				// if xft fails, fallback to core to avoid breaking
9215 				// code that already depended on this.
9216 			}
9217 
9218 			core:
9219 
9220 			if(name.length > 5 && name[0 .. 5] == "core:") {
9221 				name = name[5 .. $];
9222 			}
9223 
9224 			return loadCoreX(name, size, weight, italic);
9225 		} else version(Windows) {
9226 			return loadWin32(name, size, weight, italic);
9227 		} else version(OSXCocoa) {
9228 			return loadCocoa(name, size, weight, italic);
9229 		} else static assert(0);
9230 	}
9231 
9232 	version(OSXCocoa)
9233 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
9234 		unload();
9235 
9236 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
9237 		prepareFontInfo();
9238 
9239 		return !isNull();
9240 	}
9241 
9242 	private struct LoadedInfo {
9243 		string name;
9244 		int size;
9245 		FontWeight weight;
9246 		bool italic;
9247 	}
9248 	private LoadedInfo loadedInfo;
9249 
9250 	///
9251 	void unload() {
9252 		if(isNull())
9253 			return;
9254 
9255 		version(X11) {
9256 			auto display = XDisplayConnection.display;
9257 
9258 			if(display is null)
9259 				return;
9260 
9261 			version(with_xft) {
9262 				if(isXft) {
9263 					if(xftFont)
9264 						XftFontClose(display, xftFont);
9265 					isXft = false;
9266 					xftFont = null;
9267 					return;
9268 				}
9269 			}
9270 
9271 			if(font && font !is ScreenPainterImplementation.defaultfont)
9272 				XFreeFont(display, font);
9273 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
9274 				XFreeFontSet(display, fontset);
9275 
9276 			font = null;
9277 			fontset = null;
9278 		} else version(Windows) {
9279 			DeleteObject(font);
9280 			font = null;
9281 		} else version(OSXCocoa) {
9282 			font.release();
9283 			font = null;
9284 		} else static assert(0);
9285 	}
9286 
9287 	private bool isMonospace_;
9288 
9289 	/++
9290 		History:
9291 			Added January 16, 2021
9292 	+/
9293 	bool isMonospace() {
9294 		return isMonospace_;
9295 	}
9296 
9297 	/++
9298 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
9299 
9300 		History:
9301 			Added March 26, 2020
9302 			Documented January 16, 2021
9303 	+/
9304 	int averageWidth() {
9305 		version(X11) {
9306 			return stringWidth("x");
9307 		} version(OSXCocoa) {
9308 			return stringWidth("x");
9309 		} else version(Windows)
9310 			return width_;
9311 		else assert(0);
9312 	}
9313 
9314 	/++
9315 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
9316 
9317 		History:
9318 			Added January 16, 2021
9319 	+/
9320 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
9321 	// FIXME: what about tab?
9322 		if(isNull)
9323 			return 0;
9324 
9325 		version(X11) {
9326 			version(with_xft)
9327 				if(isXft && xftFont !is null) {
9328 					//return xftFont.max_advance_width;
9329 					XGlyphInfo extents;
9330 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
9331 					// writeln(extents);
9332 					return extents.xOff;
9333 				}
9334 			if(font is null)
9335 				return 0;
9336 			else if(fontset) {
9337 				XRectangle rect;
9338 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
9339 
9340 				return rect.width;
9341 			} else {
9342 				return XTextWidth(font, s.ptr, cast(int) s.length);
9343 			}
9344 		} else version(Windows) {
9345 			WCharzBuffer buffer = WCharzBuffer(s);
9346 
9347 			return stringWidth(buffer.slice, window);
9348 		} else version(OSXCocoa) {
9349 			/+
9350 			int charCount = [string length];
9351 			CGGlyph glyphs[charCount];
9352 			CGRect rects[charCount];
9353 
9354 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
9355 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
9356 
9357 			int totalwidth = 0, maxheight = 0;
9358 			for (int i=0; i < charCount; i++)
9359 			{
9360 				totalwidth += rects[i].size.width;
9361 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
9362 			}
9363 
9364 			dim = CGSizeMake(totalwidth, maxheight);
9365 			+/
9366 
9367 			return 16; // FIXME
9368 		}
9369 		else assert(0);
9370 	}
9371 
9372 	version(Windows)
9373 	/// ditto
9374 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
9375 		if(isNull)
9376 			return 0;
9377 		version(Windows) {
9378 			SIZE size;
9379 
9380 			prepareContext(window);
9381 			scope(exit) releaseContext();
9382 
9383 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
9384 
9385 			return size.cx;
9386 		} else {
9387 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
9388 			static assert(0, "not implemented yet");
9389 			//return stringWidth(s, window);
9390 		}
9391 	}
9392 
9393 	private {
9394 		int prepRefcount;
9395 
9396 		version(Windows) {
9397 			HDC dc;
9398 			HANDLE orig;
9399 			HWND hwnd;
9400 		}
9401 	}
9402 	/++
9403 		[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.
9404 
9405 		History:
9406 			Added January 23, 2021
9407 	+/
9408 	void prepareContext(SimpleWindow window = null) {
9409 		prepRefcount++;
9410 		if(prepRefcount == 1) {
9411 			version(Windows) {
9412 				hwnd = window is null ? null : window.impl.hwnd;
9413 				dc = GetDC(hwnd);
9414 				orig = SelectObject(dc, font);
9415 			}
9416 		}
9417 	}
9418 	/// ditto
9419 	void releaseContext() {
9420 		prepRefcount--;
9421 		if(prepRefcount == 0) {
9422 			version(Windows) {
9423 				SelectObject(dc, orig);
9424 				ReleaseDC(hwnd, dc);
9425 				hwnd = null;
9426 				dc = null;
9427 				orig = null;
9428 			}
9429 		}
9430 	}
9431 
9432 	/+
9433 		FIXME: I think I need advance and kerning pair
9434 
9435 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
9436 	+/
9437 
9438 	/++
9439 		Returns the height of the font.
9440 
9441 		History:
9442 			Added March 26, 2020
9443 			Documented January 16, 2021
9444 	+/
9445 	int height() {
9446 		version(X11) {
9447 			version(with_xft)
9448 				if(isXft && xftFont !is null) {
9449 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
9450 				}
9451 			if(font is null)
9452 				return 0;
9453 			return font.max_bounds.ascent + font.max_bounds.descent;
9454 		} else version(Windows) {
9455 			return height_;
9456 		} else version(OSXCocoa) {
9457 			if(font is null)
9458 				return 0;
9459 			return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight
9460 		}
9461 		else assert(0);
9462 	}
9463 
9464 	private int ascent_;
9465 	private int descent_;
9466 
9467 	/++
9468 		Max ascent above the baseline.
9469 
9470 		History:
9471 			Added January 22, 2021
9472 	+/
9473 	int ascent() {
9474 		return ascent_;
9475 	}
9476 
9477 	/++
9478 		Max descent below the baseline.
9479 
9480 		History:
9481 			Added January 22, 2021
9482 	+/
9483 	int descent() {
9484 		return descent_;
9485 	}
9486 
9487 	/++
9488 		Loads the default font used by [ScreenPainter] if none others are loaded.
9489 
9490 		Returns:
9491 			This method mutates the `this` object, but then returns `this` for
9492 			easy chaining like:
9493 
9494 			---
9495 			auto font = foo.isNull ? foo : foo.loadDefault
9496 			---
9497 
9498 		History:
9499 			Added previously, but left unimplemented until January 24, 2021.
9500 	+/
9501 	OperatingSystemFont loadDefault() {
9502 		unload();
9503 
9504 		loadedInfo = LoadedInfo.init;
9505 
9506 		version(X11) {
9507 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9508 			// but meh since sdpy does its own thing, this should be ok too
9509 
9510 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9511 			this.font = ScreenPainterImplementation.defaultfont;
9512 			this.fontset = ScreenPainterImplementation.defaultfontset;
9513 
9514 			prepareFontInfo();
9515 			return this;
9516 		} else version(Windows) {
9517 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9518 			this.font = ScreenPainterImplementation.defaultGuiFont;
9519 
9520 			prepareFontInfo();
9521 			return this;
9522 		} else version(OSXCocoa) {
9523 			this.font = NSFont.systemFontOfSize(15);
9524 
9525 			prepareFontInfo();
9526 
9527 			// import std.stdio; writeln("Load default: ", this.height());
9528 			return this;
9529 		} else throw new NotYetImplementedException();
9530 	}
9531 
9532 	///
9533 	bool isNull() {
9534 		version(with_xft)
9535 			if(isXft)
9536 				return xftFont is null;
9537 		return font is null;
9538 	}
9539 
9540 	/* Metrics */
9541 	/+
9542 		GetABCWidth
9543 		GetKerningPairs
9544 
9545 		if I do it right, I can size it all here, and match
9546 		what happens when I draw the full string with the OS functions.
9547 
9548 		subclasses might do the same thing while getting the glyphs on images
9549 	struct GlyphInfo {
9550 		int glyph;
9551 
9552 		size_t stringIdxStart;
9553 		size_t stringIdxEnd;
9554 
9555 		Rectangle boundingBox;
9556 	}
9557 	GlyphInfo[] getCharBoxes() {
9558 		// XftTextExtentsUtf8
9559 		return null;
9560 
9561 	}
9562 	+/
9563 
9564 	~this() {
9565 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9566 		unload();
9567 	}
9568 }
9569 
9570 version(Windows)
9571 private string sliceCString(const(wchar)[] w) {
9572 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9573 }
9574 
9575 private inout(char)[] sliceCString(inout(char)* s) {
9576 	import core.stdc.string;
9577 	auto len = strlen(s);
9578 	return s[0 .. len];
9579 }
9580 
9581 version(OSXCocoa)
9582 	alias PaintingHandle = NSObject;
9583 else
9584 	alias PaintingHandle = NativeWindowHandle;
9585 
9586 /**
9587 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9588 	than constructing it directly. Then, it is reference counted so you can pass it
9589 	at around and when the last ref goes out of scope, the buffered drawing activities
9590 	are all carried out.
9591 
9592 
9593 	Most functions use the outlineColor instead of taking a color themselves.
9594 	ScreenPainter is reference counted and draws its buffer to the screen when its
9595 	final reference goes out of scope.
9596 */
9597 struct ScreenPainter {
9598 	CapableOfBeingDrawnUpon window;
9599 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9600 		this.window = window;
9601 		if(window.closed)
9602 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9603 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9604 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9605 		if(window.activeScreenPainter !is null) {
9606 			impl = window.activeScreenPainter;
9607 			if(impl.referenceCount == 0) {
9608 				impl.window = window;
9609 				impl.create(handle);
9610 			}
9611 			impl.manualInvalidations = manualInvalidations;
9612 			impl.referenceCount++;
9613 		//	writeln("refcount ++ ", impl.referenceCount);
9614 		} else {
9615 			impl = new ScreenPainterImplementation;
9616 			impl.window = window;
9617 			impl.create(handle);
9618 			impl.referenceCount = 1;
9619 			impl.manualInvalidations = manualInvalidations;
9620 			window.activeScreenPainter = impl;
9621 			// writeln("constructed");
9622 		}
9623 
9624 		copyActiveOriginals();
9625 	}
9626 
9627 	/++
9628 		EXPERIMENTAL. subject to change.
9629 
9630 		When you draw a cursor, you can draw this to notify your window of where it is,
9631 		for IME systems to use.
9632 	+/
9633 	void notifyCursorPosition(int x, int y, int width, int height) {
9634 		if(auto w = cast(SimpleWindow) window) {
9635 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9636 		}
9637 	}
9638 
9639 	/++
9640 		If you are using manual invalidations, this informs the
9641 		window system that a section needs to be redrawn.
9642 
9643 		If you didn't opt into manual invalidation, you don't
9644 		have to call this.
9645 
9646 		History:
9647 			Added December 30, 2021 (dub v10.5)
9648 	+/
9649 	void invalidateRect(Rectangle rect) {
9650 		if(impl is null) return;
9651 
9652 		// transform(rect)
9653 		rect.left += _originX;
9654 		rect.right += _originX;
9655 		rect.top += _originY;
9656 		rect.bottom += _originY;
9657 
9658 		impl.invalidateRect(rect);
9659 	}
9660 
9661 	private Pen originalPen;
9662 	private Color originalFillColor;
9663 	private arsd.color.Rectangle originalClipRectangle;
9664 	private OperatingSystemFont originalFont;
9665 	void copyActiveOriginals() {
9666 		if(impl is null) return;
9667 		originalPen = impl._activePen;
9668 		originalFillColor = impl._fillColor;
9669 		originalClipRectangle = impl._clipRectangle;
9670 		version(OSXCocoa) {} else
9671 		originalFont = impl._activeFont;
9672 	}
9673 
9674 	~this() {
9675 		if(impl is null) return;
9676 		impl.referenceCount--;
9677 		//writeln("refcount -- ", impl.referenceCount);
9678 		if(impl.referenceCount == 0) {
9679 			// writeln("destructed");
9680 			impl.dispose();
9681 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9682 			// writeln("paint finished");
9683 		} else {
9684 			// there is still an active reference, reset stuff so the
9685 			// next user doesn't get weirdness via the reference
9686 			this.rasterOp = RasterOp.normal;
9687 			pen = originalPen;
9688 			fillColor = originalFillColor;
9689 			if(originalFont)
9690 				setFont(originalFont);
9691 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9692 		}
9693 	}
9694 
9695 	this(this) {
9696 		if(impl is null) return;
9697 		impl.referenceCount++;
9698 		//writeln("refcount ++ ", impl.referenceCount);
9699 
9700 		copyActiveOriginals();
9701 	}
9702 
9703 	private int _originX;
9704 	private int _originY;
9705 	@property int originX() { return _originX; }
9706 	@property int originY() { return _originY; }
9707 	@property int originX(int a) {
9708 		_originX = a;
9709 		return _originX;
9710 	}
9711 	@property int originY(int a) {
9712 		_originY = a;
9713 		return _originY;
9714 	}
9715 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9716 	private void transform(ref Point p) {
9717 		if(impl is null) return;
9718 		p.x += _originX;
9719 		p.y += _originY;
9720 	}
9721 
9722 	// this needs to be checked BEFORE the originX/Y transformation
9723 	private bool isClipped(Point p) {
9724 		return !currentClipRectangle.contains(p);
9725 	}
9726 	private bool isClipped(Point p, int width, int height) {
9727 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9728 	}
9729 	private bool isClipped(Point p, Size s) {
9730 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9731 	}
9732 	private bool isClipped(Point p, Point p2) {
9733 		// need to ensure the end points are actually included inside, so the +1 does that
9734 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9735 	}
9736 
9737 
9738 	/++
9739 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9740 
9741 		Returns:
9742 			The old clip rectangle.
9743 
9744 		History:
9745 			Return value was `void` prior to May 10, 2021.
9746 
9747 	+/
9748 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9749 		if(impl is null) return currentClipRectangle;
9750 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9751 			return currentClipRectangle; // no need to do anything
9752 		auto old = currentClipRectangle;
9753 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9754 		transform(pt);
9755 
9756 		impl.setClipRectangle(pt.x, pt.y, width, height);
9757 
9758 		return old;
9759 	}
9760 
9761 	/// ditto
9762 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9763 		if(impl is null) return currentClipRectangle;
9764 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9765 	}
9766 
9767 	///
9768 	void setFont(OperatingSystemFont font) {
9769 		if(impl is null) return;
9770 		impl.setFont(font);
9771 	}
9772 
9773 	///
9774 	int fontHeight() {
9775 		if(impl is null) return 0;
9776 		return impl.fontHeight();
9777 	}
9778 
9779 	private Pen activePen;
9780 
9781 	///
9782 	@property void pen(Pen p) {
9783 		if(impl is null) return;
9784 		activePen = p;
9785 		impl.pen(p);
9786 	}
9787 
9788 	///
9789 	@scriptable
9790 	@property void outlineColor(Color c) {
9791 		if(impl is null) return;
9792 		if(activePen.color == c)
9793 			return;
9794 		activePen.color = c;
9795 		impl.pen(activePen);
9796 	}
9797 
9798 	///
9799 	@scriptable
9800 	@property void fillColor(Color c) {
9801 		if(impl is null) return;
9802 		impl.fillColor(c);
9803 	}
9804 
9805 	///
9806 	@property void rasterOp(RasterOp op) {
9807 		if(impl is null) return;
9808 		impl.rasterOp(op);
9809 	}
9810 
9811 
9812 	void updateDisplay() {
9813 		// FIXME this should do what the dtor does
9814 	}
9815 
9816 	/// 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)
9817 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9818 		if(impl is null) return;
9819 		if(isClipped(upperLeft, width, height)) return;
9820 		transform(upperLeft);
9821 		version(Windows) {
9822 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9823 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9824 			RECT clip = scroll;
9825 			RECT uncovered;
9826 			HRGN hrgn;
9827 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9828 				throw new WindowsApiException("ScrollDC", GetLastError());
9829 
9830 		} else version(X11) {
9831 			// FIXME: clip stuff outside this rectangle
9832 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9833 		} else version(OSXCocoa) {
9834 			throw new NotYetImplementedException();
9835 		} else static assert(0);
9836 	}
9837 
9838 	///
9839 	void clear(Color color = Color.white()) {
9840 		if(impl is null) return;
9841 		fillColor = color;
9842 		outlineColor = color;
9843 		drawRectangle(Point(0, 0), window.width, window.height);
9844 	}
9845 
9846 	/++
9847 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9848 
9849 		Params:
9850 			upperLeft = point on the window where the upper left corner of the image will be drawn
9851 			imageUpperLeft = point on the image to start the slice to draw
9852 			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.
9853 		History:
9854 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9855 	+/
9856 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9857 		if(impl is null) return;
9858 		if(isClipped(upperLeft, s.width, s.height)) return;
9859 		transform(upperLeft);
9860 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9861 	}
9862 
9863 	///
9864 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9865 		if(impl is null) return;
9866 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9867 		transform(upperLeft);
9868 		if(w == 0 || w > i.width)
9869 			w = i.width;
9870 		if(h == 0 || h > i.height)
9871 			h = i.height;
9872 		if(upperLeftOfImage.x < 0)
9873 			upperLeftOfImage.x = 0;
9874 		if(upperLeftOfImage.y < 0)
9875 			upperLeftOfImage.y = 0;
9876 
9877 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9878 	}
9879 
9880 	///
9881 	Size textSize(in char[] text) {
9882 		if(impl is null) return Size(0, 0);
9883 		return impl.textSize(text);
9884 	}
9885 
9886 	/++
9887 		Draws a string in the window with the set font (see [setFont] to change it).
9888 
9889 		Params:
9890 			upperLeft = the upper left point of the bounding box of the text
9891 			text = the string to draw
9892 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9893 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9894 	+/
9895 	@scriptable
9896 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9897 		if(impl is null) return;
9898 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9899 			if(isClipped(upperLeft, lowerRight)) return;
9900 			transform(lowerRight);
9901 		} else {
9902 			if(isClipped(upperLeft, textSize(text))) return;
9903 		}
9904 		transform(upperLeft);
9905 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9906 	}
9907 
9908 	/++
9909 		Draws text using a custom font.
9910 
9911 		This is still MAJOR work in progress.
9912 
9913 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9914 	+/
9915 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9916 		if(impl is null) return;
9917 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9918 		transform(upperLeft);
9919 		font.drawString(this, upperLeft, text);
9920 	}
9921 
9922 	version(Windows)
9923 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9924 		if(impl is null) return;
9925 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9926 		transform(upperLeft);
9927 
9928 		if(text.length && text[$-1] == '\n')
9929 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9930 
9931 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9932 	}
9933 
9934 	static struct TextDrawingContext {
9935 		Point boundingBoxUpperLeft;
9936 		Point boundingBoxLowerRight;
9937 
9938 		Point currentLocation;
9939 
9940 		Point lastDrewUpperLeft;
9941 		Point lastDrewLowerRight;
9942 
9943 		// how do i do right aligned rich text?
9944 		// i kinda want to do a pre-made drawing then right align
9945 		// draw the whole block.
9946 		//
9947 		// That's exactly the diff: inline vs block stuff.
9948 
9949 		// I need to get coordinates of an inline section out too,
9950 		// not just a bounding box, but a series of bounding boxes
9951 		// should be ok. Consider what's needed to detect a click
9952 		// on a link in the middle of a paragraph breaking a line.
9953 		//
9954 		// Generally, we should be able to get the rectangles of
9955 		// any portion we draw.
9956 		//
9957 		// It also needs to tell what text is left if it overflows
9958 		// out of the box, so we can do stuff like float images around
9959 		// it. It should not attempt to draw a letter that would be
9960 		// clipped.
9961 		//
9962 		// I might also turn off word wrap stuff.
9963 	}
9964 
9965 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9966 		if(impl is null) return;
9967 		// FIXME
9968 	}
9969 
9970 	/// Drawing an individual pixel is slow. Avoid it if possible.
9971 	void drawPixel(Point where) {
9972 		if(impl is null) return;
9973 		if(isClipped(where)) return;
9974 		transform(where);
9975 		impl.drawPixel(where.x, where.y);
9976 	}
9977 
9978 
9979 	/// Draws a pen using the current pen / outlineColor
9980 	@scriptable
9981 	void drawLine(Point starting, Point ending) {
9982 		if(impl is null) return;
9983 		if(isClipped(starting, ending)) return;
9984 		transform(starting);
9985 		transform(ending);
9986 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9987 	}
9988 
9989 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9990 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9991 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9992 	@scriptable
9993 	void drawRectangle(Point upperLeft, int width, int height) {
9994 		if(impl is null) return;
9995 		if(isClipped(upperLeft, width, height)) return;
9996 		transform(upperLeft);
9997 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9998 	}
9999 
10000 	/// ditto
10001 	void drawRectangle(Point upperLeft, Size size) {
10002 		if(impl is null) return;
10003 		if(isClipped(upperLeft, size.width, size.height)) return;
10004 		transform(upperLeft);
10005 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
10006 	}
10007 
10008 	/// ditto
10009 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
10010 		if(impl is null) return;
10011 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
10012 		transform(upperLeft);
10013 		transform(lowerRightInclusive);
10014 		impl.drawRectangle(upperLeft.x, upperLeft.y,
10015 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
10016 	}
10017 
10018 	// overload added on May 12, 2021
10019 	/// ditto
10020 	void drawRectangle(Rectangle rect) {
10021 		drawRectangle(rect.upperLeft, rect.size);
10022 	}
10023 
10024 	/// Arguments are the points of the bounding rectangle
10025 	void drawEllipse(Point upperLeft, Point lowerRight) {
10026 		if(impl is null) return;
10027 		if(isClipped(upperLeft, lowerRight)) return;
10028 		transform(upperLeft);
10029 		transform(lowerRight);
10030 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
10031 	}
10032 
10033 	/++
10034 		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.
10035 
10036 
10037 		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.
10038 
10039 		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.
10040 
10041 		Bugs:
10042 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
10043 
10044 			The arc outline on Linux sometimes goes over the target.
10045 
10046 			The fill on Windows sometimes stops short.
10047 
10048 		History:
10049 			This function was broken af, totally inconsistent on platforms until September 24, 2021.
10050 
10051 			The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024.
10052 	+/
10053 	void drawArc(Point upperLeft, int width, int height, int start, int length) {
10054 		if(impl is null) return;
10055 		// FIXME: not actually implemented
10056 		if(isClipped(upperLeft, width, height)) return;
10057 		transform(upperLeft);
10058 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length);
10059 	}
10060 
10061 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
10062 	void drawCircle(Point upperLeft, int diameter) {
10063 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
10064 	}
10065 
10066 	/++
10067 		Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush.
10068 
10069 
10070 		Bugs:
10071 			Not implemented on Mac; it will instead draw a non-rounded rectangle for now.
10072 
10073 		History:
10074 			Added August 3, 2024
10075 	+/
10076 	void drawRectangleRounded(Rectangle rect, int borderRadius) {
10077 		drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius);
10078 	}
10079 
10080 	/// ditto
10081 	void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) {
10082 		drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius);
10083 	}
10084 
10085 	/// ditto
10086 	void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
10087 		if(borderRadius <= 0) {
10088 			drawRectangle(upperLeft, lowerRight);
10089 			return;
10090 		}
10091 
10092 		transform(upperLeft);
10093 		transform(lowerRight);
10094 
10095 		impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius);
10096 	}
10097 
10098 	/// .
10099 	void drawPolygon(Point[] vertexes) {
10100 		if(impl is null) return;
10101 		assert(vertexes.length);
10102 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
10103 		foreach(ref vertex; vertexes) {
10104 			if(vertex.x < minX)
10105 				minX = vertex.x;
10106 			if(vertex.y < minY)
10107 				minY = vertex.y;
10108 			if(vertex.x > maxX)
10109 				maxX = vertex.x;
10110 			if(vertex.y > maxY)
10111 				maxY = vertex.y;
10112 			transform(vertex);
10113 		}
10114 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
10115 		impl.drawPolygon(vertexes);
10116 	}
10117 
10118 	/// ditto
10119 	void drawPolygon(Point[] vertexes...) {
10120 		if(impl is null) return;
10121 		drawPolygon(vertexes);
10122 	}
10123 
10124 
10125 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
10126 
10127 	//mixin NativeScreenPainterImplementation!() impl;
10128 
10129 
10130 	// HACK: if I mixin the impl directly, it won't let me override the copy
10131 	// constructor! The linker complains about there being multiple definitions.
10132 	// I'll make the best of it and reference count it though.
10133 	ScreenPainterImplementation* impl;
10134 }
10135 
10136 	// HACK: I need a pointer to the implementation so it's separate
10137 	struct ScreenPainterImplementation {
10138 		CapableOfBeingDrawnUpon window;
10139 		int referenceCount;
10140 		mixin NativeScreenPainterImplementation!();
10141 	}
10142 
10143 // FIXME: i haven't actually tested the sprite class on MS Windows
10144 
10145 /**
10146 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
10147 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
10148 
10149 
10150 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
10151 	though I'm not sure that's ideal and the implementation might change.
10152 
10153 	You create one by giving a window and an image. It optimizes for that window,
10154 	and copies the image into it to use as the initial picture. Creating a sprite
10155 	can be quite slow (especially over a network connection) so you should do it
10156 	as little as possible and just hold on to your sprite handles after making them.
10157 	simpledisplay does try to do its best though, using the XSHM extension if available,
10158 	but you should still write your code as if it will always be slow.
10159 
10160 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
10161 	a fast operation - much faster than drawing the Image itself every time.
10162 
10163 	`Sprite` represents a scarce resource which should be freed when you
10164 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
10165 	after it has been disposed. If you are unsure about this, don't take chances,
10166 	just let the garbage collector do it for you. But ideally, you can manage its
10167 	lifetime more efficiently.
10168 
10169 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
10170 	support alpha blending in its drawing at this time. That might change in the
10171 	future, but if you need alpha blending right now, use OpenGL instead. See
10172 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
10173 
10174 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
10175 	in by setting the enableAlpha = true in the constructor.
10176 */
10177 class Sprite : CapableOfBeingDrawnUpon {
10178 
10179 	///
10180 	ScreenPainter draw() {
10181 		return ScreenPainter(this, handle, false);
10182 	}
10183 
10184 	/++
10185 		Copies the sprite's current state into a [TrueColorImage].
10186 
10187 		Be warned: this can be a very slow operation
10188 
10189 		History:
10190 			Actually implemented on March 14, 2021
10191 	+/
10192 	TrueColorImage takeScreenshot() {
10193 		return trueColorImageFromNativeHandle(handle, width, height);
10194 	}
10195 
10196 	void delegate() paintingFinishedDg() { return null; }
10197 	bool closed() { return false; }
10198 	ScreenPainterImplementation* activeScreenPainter_;
10199 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
10200 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
10201 
10202 	version(Windows)
10203 		private ubyte* rawData;
10204 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
10205 	// ditto on the XPicture stuff
10206 
10207 	version(X11) {
10208 		private static XRenderPictFormat* RGB24;
10209 		private static XRenderPictFormat* ARGB32;
10210 
10211 		private Picture xrenderPicture;
10212 	}
10213 
10214 	version(X11)
10215 	private static void requireXRender() {
10216 		if(!XRenderLibrary.loadAttempted) {
10217 			XRenderLibrary.loadDynamicLibrary();
10218 		}
10219 
10220 		if(!XRenderLibrary.loadSuccessful)
10221 			throw new Exception("XRender library load failure");
10222 
10223 		auto display = XDisplayConnection.get;
10224 
10225 		// FIXME: if we migrate X displays, these need to be changed
10226 		if(RGB24 is null)
10227 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
10228 		if(ARGB32 is null)
10229 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
10230 	}
10231 
10232 	protected this() {}
10233 
10234 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
10235 		this._width = width;
10236 		this._height = height;
10237 		this.enableAlpha = enableAlpha;
10238 
10239 		version(X11) {
10240 			auto display = XDisplayConnection.get();
10241 
10242 			if(enableAlpha) {
10243 				requireXRender();
10244 			}
10245 
10246 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
10247 
10248 			if(enableAlpha) {
10249 				XRenderPictureAttributes attrs;
10250 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
10251 			}
10252 		} else version(Windows) {
10253 			version(CRuntime_DigitalMars) {
10254 				//if(enableAlpha)
10255 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
10256 			}
10257 
10258 			BITMAPINFO infoheader;
10259 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10260 			infoheader.bmiHeader.biWidth = width;
10261 			infoheader.bmiHeader.biHeight = height;
10262 			infoheader.bmiHeader.biPlanes = 1;
10263 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
10264 			infoheader.bmiHeader.biCompression = BI_RGB;
10265 
10266 			// FIXME: this should prolly be a device dependent bitmap...
10267 			handle = CreateDIBSection(
10268 				null,
10269 				&infoheader,
10270 				DIB_RGB_COLORS,
10271 				cast(void**) &rawData,
10272 				null,
10273 				0);
10274 
10275 			if(handle is null)
10276 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
10277 		}
10278 	}
10279 
10280 	/// Makes a sprite based on the image with the initial contents from the Image
10281 	this(SimpleWindow win, Image i) {
10282 		this(win, i.width, i.height, i.enableAlpha);
10283 
10284 		version(X11) {
10285 			auto display = XDisplayConnection.get();
10286 			auto gc = XCreateGC(display, this.handle, 0, null);
10287 			scope(exit) XFreeGC(display, gc);
10288 			if(i.usingXshm)
10289 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
10290 			else
10291 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
10292 		} else version(Windows) {
10293 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
10294 			auto arrLength = itemsPerLine * height;
10295 			rawData[0..arrLength] = i.rawData[0..arrLength];
10296 		} else version(OSXCocoa) {
10297 			// FIXME: I have no idea if this is even any good
10298 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
10299 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
10300 				colorSpace,
10301 				kCGImageAlphaPremultipliedLast
10302 				|kCGBitmapByteOrder32Big);
10303 			CGColorSpaceRelease(colorSpace);
10304 			auto rawData = CGBitmapContextGetData(handle);
10305 
10306 			auto rdl = (width * height * 4);
10307 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
10308 		} else static assert(0);
10309 	}
10310 
10311 	/++
10312 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
10313 
10314 		Params:
10315 			where = point on the window where the upper left corner of the image will be drawn
10316 			imageUpperLeft = point on the image to start the slice to draw
10317 			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.
10318 		History:
10319 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
10320 	+/
10321 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
10322 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
10323 	}
10324 
10325 	/// Call this when you're ready to get rid of it
10326 	void dispose() {
10327 		version(X11) {
10328 			staticDispose(xrenderPicture, handle);
10329 			xrenderPicture = None;
10330 			handle = None;
10331 		} else version(Windows) {
10332 			staticDispose(handle);
10333 			handle = null;
10334 		} else version(OSXCocoa) {
10335 			staticDispose(handle);
10336 			handle = null;
10337 		} else static assert(0);
10338 
10339 	}
10340 
10341 	version(X11)
10342 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
10343 		if(xrenderPicture)
10344 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
10345 		if(handle)
10346 			XFreePixmap(XDisplayConnection.get(), handle);
10347 	}
10348 	else version(Windows)
10349 	static void staticDispose(HBITMAP handle) {
10350 		if(handle)
10351 			DeleteObject(handle);
10352 	}
10353 	else version(OSXCocoa)
10354 	static void staticDispose(CGContextRef context) {
10355 		if(context)
10356 			CGContextRelease(context);
10357 	}
10358 
10359 	~this() {
10360 		version(X11) { if(xrenderPicture || handle)
10361 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
10362 		} else version(Windows) { if(handle)
10363 			cleanupQueue.queue!staticDispose(handle);
10364 		} else version(OSXCocoa) { if(handle)
10365 			cleanupQueue.queue!staticDispose(handle);
10366 		} else static assert(0);
10367 	}
10368 
10369 	///
10370 	final @property int width() { return _width; }
10371 
10372 	///
10373 	final @property int height() { return _height; }
10374 
10375 	///
10376 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
10377 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
10378 	}
10379 
10380 	auto nativeHandle() {
10381 		return handle;
10382 	}
10383 
10384 	private:
10385 
10386 	int _width;
10387 	int _height;
10388 	bool enableAlpha;
10389 	version(X11)
10390 		Pixmap handle;
10391 	else version(Windows)
10392 		HBITMAP handle;
10393 	else version(OSXCocoa)
10394 		CGContextRef handle;
10395 	else version(Emscripten)
10396 		void* handle;
10397 	else static assert(0);
10398 }
10399 
10400 /++
10401 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
10402 
10403 	History:
10404 		Added November 20, 2021 (dub v10.4)
10405 +/
10406 version(OSXCocoa) {} else // NotYetImplementedException
10407 abstract class Gradient : Sprite {
10408 	protected this(int w, int h) {
10409 		version(X11) {
10410 			Sprite.requireXRender();
10411 
10412 			super();
10413 			enableAlpha = true;
10414 			_width = w;
10415 			_height = h;
10416 		} else version(Windows) {
10417 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
10418 		}
10419 	}
10420 
10421 	version(Windows)
10422 	final void forEachPixel(scope Color delegate(int x, int y) dg) @system {
10423 		auto ptr = rawData;
10424 		foreach(j; 0 .. _height)
10425 		foreach(i; 0 .. _width) {
10426 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
10427 			*rawData = (color.a * color.b) / 255; rawData++;
10428 			*rawData = (color.a * color.g) / 255; rawData++;
10429 			*rawData = (color.a * color.r) / 255; rawData++;
10430 			*rawData = color.a; rawData++;
10431 		}
10432 	}
10433 
10434 	version(X11)
10435 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
10436 		assert(stops.length > 0);
10437 		assert(stops.length <= 16, "I got lazy with buffers");
10438 
10439 		XFixed[16] stopsPositions = void;
10440 		XRenderColor[16] colors = void;
10441 
10442 		foreach(idx, stop; stops) {
10443 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
10444 			auto c = stop.c;
10445 			colors[idx] = XRenderColor(
10446 				cast(ushort)(c.r * ushort.max / 255),
10447 				cast(ushort)(c.g * ushort.max / 255),
10448 				cast(ushort)(c.b * ushort.max / 255),
10449 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
10450 			);
10451 		}
10452 
10453 		xrenderPicture = dg(stopsPositions, colors);
10454 	}
10455 
10456 	///
10457 	static struct Stop {
10458 		float percentage; /// between 0 and 1.0
10459 		Color c;
10460 	}
10461 }
10462 
10463 /++
10464 	Creates a linear gradient between p1 and p2.
10465 
10466 	X ONLY RIGHT NOW
10467 
10468 	History:
10469 		Added November 20, 2021 (dub v10.4)
10470 
10471 	Bugs:
10472 		Not yet implemented on Windows.
10473 +/
10474 version(OSXCocoa) {} else // NotYetImplementedException
10475 class LinearGradient : Gradient {
10476 	/++
10477 
10478 	+/
10479 	this(Point p1, Point p2, Stop[] stops...) {
10480 		super(p2.x, p2.y);
10481 
10482 		version(X11) {
10483 			XLinearGradient gradient;
10484 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
10485 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
10486 
10487 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10488 				return XRenderCreateLinearGradient(
10489 					XDisplayConnection.get,
10490 					&gradient,
10491 					stopsPositions.ptr,
10492 					colors.ptr,
10493 					cast(int) stops.length);
10494 			});
10495 		} else version(Windows) {
10496 			// FIXME
10497 			forEachPixel((int x, int y) {
10498 				import core.stdc.math;
10499 
10500 				//sqrtf(
10501 
10502 				return Color.transparent;
10503 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10504 			});
10505 		}
10506 	}
10507 }
10508 
10509 /++
10510 	A conical gradient goes from color to color around a circumference from a center point.
10511 
10512 	X ONLY RIGHT NOW
10513 
10514 	History:
10515 		Added November 20, 2021 (dub v10.4)
10516 
10517 	Bugs:
10518 		Not yet implemented on Windows.
10519 +/
10520 version(OSXCocoa) {} else // NotYetImplementedException
10521 class ConicalGradient : Gradient {
10522 	/++
10523 
10524 	+/
10525 	this(Point center, float angleInDegrees, Stop[] stops...) {
10526 		super(center.x * 2, center.y * 2);
10527 
10528 		version(X11) {
10529 			XConicalGradient gradient;
10530 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
10531 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
10532 
10533 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10534 				return XRenderCreateConicalGradient(
10535 					XDisplayConnection.get,
10536 					&gradient,
10537 					stopsPositions.ptr,
10538 					colors.ptr,
10539 					cast(int) stops.length);
10540 			});
10541 		} else version(Windows) {
10542 			// FIXME
10543 			forEachPixel((int x, int y) {
10544 				import core.stdc.math;
10545 
10546 				//sqrtf(
10547 
10548 				return Color.transparent;
10549 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10550 			});
10551 
10552 		}
10553 	}
10554 }
10555 
10556 /++
10557 	A radial gradient goes from color to color based on distance from the center.
10558 	It is like rings of color.
10559 
10560 	X ONLY RIGHT NOW
10561 
10562 
10563 	More specifically, you create two circles: an inner circle and an outer circle.
10564 	The gradient is only drawn in the area outside the inner circle but inside the outer
10565 	circle. The closest line between those two circles forms the line for the gradient
10566 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10567 
10568 	History:
10569 		Added November 20, 2021 (dub v10.4)
10570 
10571 	Bugs:
10572 		Not yet implemented on Windows.
10573 +/
10574 version(OSXCocoa) {} else // NotYetImplementedException
10575 class RadialGradient : Gradient {
10576 	/++
10577 
10578 	+/
10579 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10580 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10581 
10582 		version(X11) {
10583 			XRadialGradient gradient;
10584 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10585 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10586 
10587 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10588 				return XRenderCreateRadialGradient(
10589 					XDisplayConnection.get,
10590 					&gradient,
10591 					stopsPositions.ptr,
10592 					colors.ptr,
10593 					cast(int) stops.length);
10594 			});
10595 		} else version(Windows) {
10596 			// FIXME
10597 			forEachPixel((int x, int y) {
10598 				import core.stdc.math;
10599 
10600 				//sqrtf(
10601 
10602 				return Color.transparent;
10603 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10604 			});
10605 		}
10606 	}
10607 }
10608 
10609 
10610 
10611 /+
10612 	NOT IMPLEMENTED
10613 
10614 	A display-stored image optimized for relatively quick drawing, like
10615 	[Sprite], but this one supports alpha channel blending and does NOT
10616 	support direct drawing upon it with a [ScreenPainter].
10617 
10618 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10619 	plain [ScreenPainter]... sort of.
10620 
10621 	On X11, it requires the Xrender extension and library. This is available
10622 	almost everywhere though.
10623 
10624 	History:
10625 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10626 +/
10627 version(none)
10628 class AlphaSprite {
10629 	/++
10630 		Copies the given image into it.
10631 	+/
10632 	this(MemoryImage img) {
10633 
10634 		if(!XRenderLibrary.loadAttempted) {
10635 			XRenderLibrary.loadDynamicLibrary();
10636 
10637 			// FIXME: this needs to be reconstructed when the X server changes
10638 			repopulateX();
10639 		}
10640 		if(!XRenderLibrary.loadSuccessful)
10641 			throw new Exception("XRender library load failure");
10642 
10643 		// I probably need to put the alpha mask in a separate Picture
10644 		// ugh
10645 		// maybe the Sprite itself can have an alpha bitmask anyway
10646 
10647 
10648 		auto display = XDisplayConnection.get();
10649 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10650 
10651 
10652 		XRenderPictureAttributes attrs;
10653 
10654 		handle = XRenderCreatePicture(
10655 			XDisplayConnection.get,
10656 			pixmap,
10657 			RGBA,
10658 			0,
10659 			&attrs
10660 		);
10661 
10662 	}
10663 
10664 	// maybe i'll use the create gradient functions too with static factories..
10665 
10666 	void drawAt(ScreenPainter painter, Point where) {
10667 		//painter.drawPixmap(this, where);
10668 
10669 		XRenderPictureAttributes attrs;
10670 
10671 		auto pic = XRenderCreatePicture(
10672 			XDisplayConnection.get,
10673 			painter.impl.d,
10674 			RGB,
10675 			0,
10676 			&attrs
10677 		);
10678 
10679 		XRenderComposite(
10680 			XDisplayConnection.get,
10681 			3, // PictOpOver
10682 			handle,
10683 			None,
10684 			pic,
10685 			0, // src
10686 			0,
10687 			0, // mask
10688 			0,
10689 			10, // dest
10690 			10,
10691 			100, // width
10692 			100
10693 		);
10694 
10695 		/+
10696 		XRenderFreePicture(
10697 			XDisplayConnection.get,
10698 			pic
10699 		);
10700 
10701 		XRenderFreePicture(
10702 			XDisplayConnection.get,
10703 			fill
10704 		);
10705 		+/
10706 		// on Windows you can stretch but Xrender still can't :(
10707 	}
10708 
10709 	static XRenderPictFormat* RGB;
10710 	static XRenderPictFormat* RGBA;
10711 	static void repopulateX() {
10712 		auto display = XDisplayConnection.get;
10713 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10714 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10715 	}
10716 
10717 	XPixmap pixmap;
10718 	Picture handle;
10719 }
10720 
10721 ///
10722 interface CapableOfBeingDrawnUpon {
10723 	///
10724 	ScreenPainter draw();
10725 	///
10726 	int width();
10727 	///
10728 	int height();
10729 	protected ScreenPainterImplementation* activeScreenPainter();
10730 	protected void activeScreenPainter(ScreenPainterImplementation*);
10731 	bool closed();
10732 
10733 	void delegate() paintingFinishedDg();
10734 
10735 	/// Be warned: this can be a very slow operation
10736 	TrueColorImage takeScreenshot();
10737 }
10738 
10739 /// 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].
10740 void flushGui() {
10741 	version(X11) {
10742 		auto dpy = XDisplayConnection.get();
10743 		XLockDisplay(dpy);
10744 		scope(exit) XUnlockDisplay(dpy);
10745 		XFlush(dpy);
10746 	}
10747 }
10748 
10749 /++
10750 	Runs the given code in the GUI thread when its event loop
10751 	is available, blocking until it completes. This allows you
10752 	to create and manipulate windows from another thread without
10753 	invoking undefined behavior.
10754 
10755 	If this is the gui thread, it runs the code immediately.
10756 
10757 	If no gui thread exists yet, the current thread is assumed
10758 	to be it. Attempting to create windows or run the event loop
10759 	in any other thread will cause an assertion failure.
10760 
10761 
10762 	$(TIP
10763 		Did you know you can use UFCS on delegate literals?
10764 
10765 		() {
10766 			// code here
10767 		}.runInGuiThread;
10768 	)
10769 
10770 	Returns:
10771 		`true` if the function was called, `false` if it was not.
10772 		The function may not be called because the gui thread had
10773 		already terminated by the time you called this.
10774 
10775 	History:
10776 		Added April 10, 2020 (v7.2.0)
10777 
10778 		Return value added and implementation tweaked to avoid locking
10779 		at program termination on February 24, 2021 (v9.2.1).
10780 +/
10781 bool runInGuiThread(scope void delegate() dg) @trusted {
10782 	claimGuiThread();
10783 
10784 	if(thisIsGuiThread) {
10785 		dg();
10786 		return true;
10787 	}
10788 
10789 	if(guiThreadTerminating)
10790 		return false;
10791 
10792 	import core.sync.semaphore;
10793 	static Semaphore sc;
10794 	if(sc is null)
10795 		sc = new Semaphore();
10796 
10797 	static RunQueueMember* rqm;
10798 	if(rqm is null)
10799 		rqm = new RunQueueMember;
10800 	rqm.dg = cast(typeof(rqm.dg)) dg;
10801 	rqm.signal = sc;
10802 	rqm.thrown = null;
10803 
10804 	synchronized(runInGuiThreadLock) {
10805 		runInGuiThreadQueue ~= rqm;
10806 	}
10807 
10808 	if(!SimpleWindow.eventWakeUp())
10809 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10810 
10811 	rqm.signal.wait();
10812 	auto t = rqm.thrown;
10813 
10814 	if(t)
10815 		throw t;
10816 
10817 	return true;
10818 }
10819 
10820 // note it runs sync if this is the gui thread....
10821 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10822 	claimGuiThread();
10823 
10824 	try {
10825 
10826 		if(thisIsGuiThread) {
10827 			dg();
10828 			return;
10829 		}
10830 
10831 		if(guiThreadTerminating)
10832 			return;
10833 
10834 		RunQueueMember* rqm = new RunQueueMember;
10835 		rqm.dg = cast(typeof(rqm.dg)) dg;
10836 		rqm.signal = null;
10837 		rqm.thrown = null;
10838 
10839 		synchronized(runInGuiThreadLock) {
10840 			runInGuiThreadQueue ~= rqm;
10841 		}
10842 
10843 		if(!SimpleWindow.eventWakeUp())
10844 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10845 	} catch(Exception e) {
10846 		if(handleError)
10847 			handleError(e);
10848 	}
10849 }
10850 
10851 private void runPendingRunInGuiThreadDelegates() {
10852 	more:
10853 	RunQueueMember* next;
10854 	synchronized(runInGuiThreadLock) {
10855 		if(runInGuiThreadQueue.length) {
10856 			next = runInGuiThreadQueue[0];
10857 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10858 		} else {
10859 			next = null;
10860 		}
10861 	}
10862 
10863 	if(next) {
10864 		try {
10865 			next.dg();
10866 			next.thrown = null;
10867 		} catch(Throwable t) {
10868 			next.thrown = t;
10869 		}
10870 
10871 		if(next.signal)
10872 			next.signal.notify();
10873 
10874 		goto more;
10875 	}
10876 }
10877 
10878 private void claimGuiThread() nothrow {
10879 	import core.atomic;
10880 	if(cas(&guiThreadExists_, false, true))
10881 		thisIsGuiThread = true;
10882 }
10883 
10884 private struct RunQueueMember {
10885 	void delegate() dg;
10886 	import core.sync.semaphore;
10887 	Semaphore signal;
10888 	Throwable thrown;
10889 }
10890 
10891 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10892 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10893 private bool thisIsGuiThread = false;
10894 private shared bool guiThreadExists_ = false;
10895 private shared bool guiThreadTerminating = false;
10896 
10897 /++
10898 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10899 	event loop. All windows must be exclusively created and managed by a single thread.
10900 
10901 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10902 	when you call one of its constructors.
10903 
10904 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10905 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10906 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10907 
10908 	The reason this function is available is in case you want to message pass between a gui
10909 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10910 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10911 
10912 	History:
10913 		Added December 3, 2021 (dub v10.5)
10914 +/
10915 public bool guiThreadExists() {
10916 	return guiThreadExists_;
10917 }
10918 
10919 /++
10920 	Returns `true` if this thread is either running or set to be running the
10921 	simpledisplay.d gui core event loop because it owns windows.
10922 
10923 	It is important to keep gui-related functionality in the right thread, so you will
10924 	want to `runInGuiThread` when you call them (with some specific exceptions called
10925 	out in those specific functions' documentation). Notably, all windows must be
10926 	created and managed only from the gui thread.
10927 
10928 	Will return false if simpledisplay's other functions haven't been called
10929 	yet; check [guiThreadExists] in addition to this.
10930 
10931 	History:
10932 		Added December 3, 2021 (dub v10.5)
10933 +/
10934 public bool thisThreadRunningGui() {
10935 	return thisIsGuiThread;
10936 }
10937 
10938 /++
10939 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10940 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10941 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10942 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10943 	file instead if you are in one of those situations).
10944 
10945 	It does not support outputting very many types; just strings and ints are likely to actually work.
10946 
10947 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10948 	is unspecified meaning I can change it at any time. The only point of this function is to help
10949 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10950 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10951 	in those contexts.
10952 
10953 	$(WARNING
10954 		I reserve the right to change this function at any time. You can use it if it helps you
10955 		but do not rely on it for anything permanent.
10956 	)
10957 
10958 	History:
10959 		Added December 3, 2021. Not formally supported under any stable tag.
10960 +/
10961 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10962 	try {
10963 		version(Windows) {
10964 			import core.sys.windows.wincon;
10965 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10966 				AllocConsole();
10967 			const(char)* fn = "CONOUT$";
10968 		} else version(Posix) {
10969 			const(char)* fn = "/dev/tty";
10970 		} else static assert(0, "Function not implemented for your system");
10971 
10972 		if(fileOverride.length)
10973 			fn = fileOverride.ptr;
10974 
10975 		import core.stdc.stdio;
10976 		auto fp = fopen(fn, "wt");
10977 		if(fp is null) return;
10978 		scope(exit) fclose(fp);
10979 
10980 		string str;
10981 		foreach(item; t) {
10982 			static if(is(typeof(item) : const(char)[]))
10983 				str ~= item;
10984 			else
10985 				str ~= toInternal!string(item);
10986 			str ~= " ";
10987 		}
10988 		str ~= "\n";
10989 
10990 		fwrite(str.ptr, 1, str.length, fp);
10991 		fflush(fp);
10992 	} catch(Exception e) {
10993 		// sorry no hope
10994 	}
10995 }
10996 
10997 private void guiThreadFinalize() {
10998 	assert(thisIsGuiThread);
10999 
11000 	guiThreadTerminating = true; // don't add any more from this point on
11001 	runPendingRunInGuiThreadDelegates();
11002 }
11003 
11004 /+
11005 interface IPromise {
11006 	void reportProgress(int current, int max, string message);
11007 
11008 	/+ // not formally in cuz of templates but still
11009 	IPromise Then();
11010 	IPromise Catch();
11011 	IPromise Finally();
11012 	+/
11013 }
11014 
11015 /+
11016 	auto promise = async({ ... });
11017 	promise.Then(whatever).
11018 		Then(whateverelse).
11019 		Catch((exception) { });
11020 
11021 
11022 	A promise is run inside a fiber and it looks something like:
11023 
11024 	try {
11025 		auto res = whatever();
11026 		auto res2 = whateverelse(res);
11027 	} catch(Exception e) {
11028 		{ }(e);
11029 	}
11030 
11031 	When a thing succeeds, it is passed as an arg to the next
11032 +/
11033 class Promise(T) : IPromise {
11034 	auto Then() { return null; }
11035 	auto Catch() { return null; }
11036 	auto Finally() { return null; }
11037 
11038 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
11039 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
11040 	T await();
11041 }
11042 
11043 interface Task {
11044 }
11045 
11046 interface Resolvable(T) : Task {
11047 	void run();
11048 
11049 	void resolve(T);
11050 
11051 	Resolvable!T then(void delegate(T)); // returns a new promise
11052 	Resolvable!T error(Throwable); // js catch
11053 	Resolvable!T completed(); // js finally
11054 
11055 }
11056 
11057 /++
11058 	Runs `work` in a helper thread and sends its return value back to the main gui
11059 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
11060 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
11061 	kill the program.
11062 
11063 	You can call reportProgress(position, max, message) to update your parent window
11064 	on your progress.
11065 
11066 	I should also use `shared` methods. FIXME
11067 
11068 	History:
11069 		Added March 6, 2021 (dub version 9.3).
11070 +/
11071 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
11072 	uponCompletion(work(null));
11073 }
11074 
11075 +/
11076 
11077 /// Used internal to dispatch events to various classes.
11078 interface CapableOfHandlingNativeEvent {
11079 	NativeEventHandler getNativeEventHandler();
11080 
11081 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
11082 
11083 	version(X11) {
11084 		// if this is impossible, you are allowed to just throw from it
11085 		// Note: if you call it from another object, set a flag cuz the manger will call you again
11086 		void recreateAfterDisconnect();
11087 		// discard any *connection specific* state, but keep enough that you
11088 		// can be recreated if possible. discardConnectionState() is always called immediately
11089 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
11090 		// you need initialization order
11091 		void discardConnectionState();
11092 	}
11093 }
11094 
11095 version(X11)
11096 /++
11097 	State of keys on mouse events, especially motion.
11098 
11099 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
11100 +/
11101 enum ModifierState : uint {
11102 	shift = 1, ///
11103 	capsLock = 2, ///
11104 	ctrl = 4, ///
11105 	alt = 8, /// Not always available on Windows
11106 	windows = 64, /// ditto
11107 	numLock = 16, ///
11108 
11109 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11110 	middleButtonDown = 512, /// ditto
11111 	rightButtonDown = 1024, /// ditto
11112 }
11113 else version(Emscripten)
11114 enum ModifierState : uint {
11115 	shift = 1, ///
11116 	capsLock = 2, ///
11117 	ctrl = 4, ///
11118 	alt = 8, /// Not always available on Windows
11119 	windows = 64, /// ditto
11120 	numLock = 16, ///
11121 
11122 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11123 	middleButtonDown = 512, /// ditto
11124 	rightButtonDown = 1024, /// ditto
11125 }
11126 else version(Windows)
11127 /// ditto
11128 enum ModifierState : uint {
11129 	shift = 4, ///
11130 	ctrl = 8, ///
11131 
11132 	// i'm not sure if the next two are available
11133 	alt = 256, /// not always available on Windows
11134 	windows = 512, /// ditto
11135 
11136 	capsLock = 1024, ///
11137 	numLock = 2048, ///
11138 
11139 	leftButtonDown = 1, /// not available on key events
11140 	middleButtonDown = 16, /// ditto
11141 	rightButtonDown = 2, /// ditto
11142 
11143 	backButtonDown = 0x20, /// not available on X
11144 	forwardButtonDown = 0x40, /// ditto
11145 }
11146 else version(OSXCocoa)
11147 // FIXME FIXME NotYetImplementedException
11148 enum ModifierState : uint {
11149 	shift = 1, ///
11150 	capsLock = 2, ///
11151 	ctrl = 4, ///
11152 	alt = 8, /// Not always available on Windows
11153 	windows = 64, /// ditto
11154 	numLock = 16, ///
11155 
11156 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11157 	middleButtonDown = 512, /// ditto
11158 	rightButtonDown = 1024, /// ditto
11159 }
11160 
11161 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
11162 enum MouseButton : int {
11163 	none = 0,
11164 	left = 1, ///
11165 	right = 2, ///
11166 	middle = 4, ///
11167 	wheelUp = 8, ///
11168 	wheelDown = 16, ///
11169 	backButton = 32, /// often found on the thumb and used for back in browsers
11170 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
11171 }
11172 
11173 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
11174 enum MouseButtonLinear : ubyte {
11175 	left = 1, ///
11176 	right, ///
11177 	middle, ///
11178 	wheelUp, ///
11179 	wheelDown, ///
11180 	backButton, /// often found on the thumb and used for back in browsers
11181 	forwardButton, /// often found on the thumb and used for forward in browsers
11182 }
11183 
11184 version(WebAssembly) {
11185 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11186 	enum Key {
11187 		Escape = 0xff1b, ///
11188 		F1 = 0xffbe, ///
11189 		F2 = 0xffbf, ///
11190 		F3 = 0xffc0, ///
11191 		F4 = 0xffc1, ///
11192 		F5 = 0xffc2, ///
11193 		F6 = 0xffc3, ///
11194 		F7 = 0xffc4, ///
11195 		F8 = 0xffc5, ///
11196 		F9 = 0xffc6, ///
11197 		F10 = 0xffc7, ///
11198 		F11 = 0xffc8, ///
11199 		F12 = 0xffc9, ///
11200 		PrintScreen = 0xff61, ///
11201 		ScrollLock = 0xff14, ///
11202 		Pause = 0xff13, ///
11203 		Grave = 0x60, /// The $(BACKTICK) ~ key
11204 		// number keys across the top of the keyboard
11205 		N1 = 0x31, /// Number key atop the keyboard
11206 		N2 = 0x32, ///
11207 		N3 = 0x33, ///
11208 		N4 = 0x34, ///
11209 		N5 = 0x35, ///
11210 		N6 = 0x36, ///
11211 		N7 = 0x37, ///
11212 		N8 = 0x38, ///
11213 		N9 = 0x39, ///
11214 		N0 = 0x30, ///
11215 		Dash = 0x2d, ///
11216 		Equals = 0x3d, ///
11217 		Backslash = 0x5c, /// The \ | key
11218 		Backspace = 0xff08, ///
11219 		Insert = 0xff63, ///
11220 		Home = 0xff50, ///
11221 		PageUp = 0xff55, ///
11222 		Delete = 0xffff, ///
11223 		End = 0xff57, ///
11224 		PageDown = 0xff56, ///
11225 		Up = 0xff52, ///
11226 		Down = 0xff54, ///
11227 		Left = 0xff51, ///
11228 		Right = 0xff53, ///
11229 
11230 		Tab = 0xff09, ///
11231 		Q = 0x71, ///
11232 		W = 0x77, ///
11233 		E = 0x65, ///
11234 		R = 0x72, ///
11235 		T = 0x74, ///
11236 		Y = 0x79, ///
11237 		U = 0x75, ///
11238 		I = 0x69, ///
11239 		O = 0x6f, ///
11240 		P = 0x70, ///
11241 		LeftBracket = 0x5b, /// the [ { key
11242 		RightBracket = 0x5d, /// the ] } key
11243 		CapsLock = 0xffe5, ///
11244 		A = 0x61, ///
11245 		S = 0x73, ///
11246 		D = 0x64, ///
11247 		F = 0x66, ///
11248 		G = 0x67, ///
11249 		H = 0x68, ///
11250 		J = 0x6a, ///
11251 		K = 0x6b, ///
11252 		L = 0x6c, ///
11253 		Semicolon = 0x3b, ///
11254 		Apostrophe = 0x27, ///
11255 		Enter = 0xff0d, ///
11256 		Shift = 0xffe1, ///
11257 		Z = 0x7a, ///
11258 		X = 0x78, ///
11259 		C = 0x63, ///
11260 		V = 0x76, ///
11261 		B = 0x62, ///
11262 		N = 0x6e, ///
11263 		M = 0x6d, ///
11264 		Comma = 0x2c, ///
11265 		Period = 0x2e, ///
11266 		Slash = 0x2f, /// the / ? key
11267 		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
11268 		Ctrl = 0xffe3, ///
11269 		Windows = 0xffeb, ///
11270 		Alt = 0xffe9, ///
11271 		Space = 0x20, ///
11272 		Alt_r = 0xffea, /// ditto of shift_r
11273 		Windows_r = 0xffec, ///
11274 		Menu = 0xff67, ///
11275 		Ctrl_r = 0xffe4, ///
11276 
11277 		NumLock = 0xff7f, ///
11278 		Divide = 0xffaf, /// The / key on the number pad
11279 		Multiply = 0xffaa, /// The * key on the number pad
11280 		Minus = 0xffad, /// The - key on the number pad
11281 		Plus = 0xffab, /// The + key on the number pad
11282 		PadEnter = 0xff8d, /// Numberpad enter key
11283 		Pad1 = 0xff9c, /// Numberpad keys
11284 		Pad2 = 0xff99, ///
11285 		Pad3 = 0xff9b, ///
11286 		Pad4 = 0xff96, ///
11287 		Pad5 = 0xff9d, ///
11288 		Pad6 = 0xff98, ///
11289 		Pad7 = 0xff95, ///
11290 		Pad8 = 0xff97, ///
11291 		Pad9 = 0xff9a, ///
11292 		Pad0 = 0xff9e, ///
11293 		PadDot = 0xff9f, ///
11294 	}
11295 } version(X11) {
11296 	// FIXME: match ASCII whenever we can. Most of it is already there,
11297 	// but there's a few exceptions and mismatches with Windows
11298 
11299 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11300 	enum Key {
11301 		Escape = 0xff1b, ///
11302 		F1 = 0xffbe, ///
11303 		F2 = 0xffbf, ///
11304 		F3 = 0xffc0, ///
11305 		F4 = 0xffc1, ///
11306 		F5 = 0xffc2, ///
11307 		F6 = 0xffc3, ///
11308 		F7 = 0xffc4, ///
11309 		F8 = 0xffc5, ///
11310 		F9 = 0xffc6, ///
11311 		F10 = 0xffc7, ///
11312 		F11 = 0xffc8, ///
11313 		F12 = 0xffc9, ///
11314 		PrintScreen = 0xff61, ///
11315 		ScrollLock = 0xff14, ///
11316 		Pause = 0xff13, ///
11317 		Grave = 0x60, /// The $(BACKTICK) ~ key
11318 		// number keys across the top of the keyboard
11319 		N1 = 0x31, /// Number key atop the keyboard
11320 		N2 = 0x32, ///
11321 		N3 = 0x33, ///
11322 		N4 = 0x34, ///
11323 		N5 = 0x35, ///
11324 		N6 = 0x36, ///
11325 		N7 = 0x37, ///
11326 		N8 = 0x38, ///
11327 		N9 = 0x39, ///
11328 		N0 = 0x30, ///
11329 		Dash = 0x2d, ///
11330 		Equals = 0x3d, ///
11331 		Backslash = 0x5c, /// The \ | key
11332 		Backspace = 0xff08, ///
11333 		Insert = 0xff63, ///
11334 		Home = 0xff50, ///
11335 		PageUp = 0xff55, ///
11336 		Delete = 0xffff, ///
11337 		End = 0xff57, ///
11338 		PageDown = 0xff56, ///
11339 		Up = 0xff52, ///
11340 		Down = 0xff54, ///
11341 		Left = 0xff51, ///
11342 		Right = 0xff53, ///
11343 
11344 		Tab = 0xff09, ///
11345 		Q = 0x71, ///
11346 		W = 0x77, ///
11347 		E = 0x65, ///
11348 		R = 0x72, ///
11349 		T = 0x74, ///
11350 		Y = 0x79, ///
11351 		U = 0x75, ///
11352 		I = 0x69, ///
11353 		O = 0x6f, ///
11354 		P = 0x70, ///
11355 		LeftBracket = 0x5b, /// the [ { key
11356 		RightBracket = 0x5d, /// the ] } key
11357 		CapsLock = 0xffe5, ///
11358 		A = 0x61, ///
11359 		S = 0x73, ///
11360 		D = 0x64, ///
11361 		F = 0x66, ///
11362 		G = 0x67, ///
11363 		H = 0x68, ///
11364 		J = 0x6a, ///
11365 		K = 0x6b, ///
11366 		L = 0x6c, ///
11367 		Semicolon = 0x3b, ///
11368 		Apostrophe = 0x27, ///
11369 		Enter = 0xff0d, ///
11370 		Shift = 0xffe1, ///
11371 		Z = 0x7a, ///
11372 		X = 0x78, ///
11373 		C = 0x63, ///
11374 		V = 0x76, ///
11375 		B = 0x62, ///
11376 		N = 0x6e, ///
11377 		M = 0x6d, ///
11378 		Comma = 0x2c, ///
11379 		Period = 0x2e, ///
11380 		Slash = 0x2f, /// the / ? key
11381 		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
11382 		Ctrl = 0xffe3, ///
11383 		Windows = 0xffeb, ///
11384 		Alt = 0xffe9, ///
11385 		Space = 0x20, ///
11386 		Alt_r = 0xffea, /// ditto of shift_r
11387 		Windows_r = 0xffec, ///
11388 		Menu = 0xff67, ///
11389 		Ctrl_r = 0xffe4, ///
11390 
11391 		NumLock = 0xff7f, ///
11392 		Divide = 0xffaf, /// The / key on the number pad
11393 		Multiply = 0xffaa, /// The * key on the number pad
11394 		Minus = 0xffad, /// The - key on the number pad
11395 		Plus = 0xffab, /// The + key on the number pad
11396 		PadEnter = 0xff8d, /// Numberpad enter key
11397 		Pad1 = 0xff9c, /// Numberpad keys
11398 		Pad2 = 0xff99, ///
11399 		Pad3 = 0xff9b, ///
11400 		Pad4 = 0xff96, ///
11401 		Pad5 = 0xff9d, ///
11402 		Pad6 = 0xff98, ///
11403 		Pad7 = 0xff95, ///
11404 		Pad8 = 0xff97, ///
11405 		Pad9 = 0xff9a, ///
11406 		Pad0 = 0xff9e, ///
11407 		PadDot = 0xff9f, ///
11408 	}
11409 } else version(Windows) {
11410 	// the character here is for en-us layouts and for illustration only
11411 	// if you actually want to get characters, wait for character events
11412 	// (the argument to your event handler is simply a dchar)
11413 	// those will be converted by the OS for the right locale.
11414 
11415 	enum Key {
11416 		Escape = 0x1b,
11417 		F1 = 0x70,
11418 		F2 = 0x71,
11419 		F3 = 0x72,
11420 		F4 = 0x73,
11421 		F5 = 0x74,
11422 		F6 = 0x75,
11423 		F7 = 0x76,
11424 		F8 = 0x77,
11425 		F9 = 0x78,
11426 		F10 = 0x79,
11427 		F11 = 0x7a,
11428 		F12 = 0x7b,
11429 		PrintScreen = 0x2c,
11430 		ScrollLock = 0x91,
11431 		Pause = 0x13,
11432 		Grave = 0xc0,
11433 		// number keys across the top of the keyboard
11434 		N1 = 0x31,
11435 		N2 = 0x32,
11436 		N3 = 0x33,
11437 		N4 = 0x34,
11438 		N5 = 0x35,
11439 		N6 = 0x36,
11440 		N7 = 0x37,
11441 		N8 = 0x38,
11442 		N9 = 0x39,
11443 		N0 = 0x30,
11444 		Dash = 0xbd,
11445 		Equals = 0xbb,
11446 		Backslash = 0xdc,
11447 		Backspace = 0x08,
11448 		Insert = 0x2d,
11449 		Home = 0x24,
11450 		PageUp = 0x21,
11451 		Delete = 0x2e,
11452 		End = 0x23,
11453 		PageDown = 0x22,
11454 		Up = 0x26,
11455 		Down = 0x28,
11456 		Left = 0x25,
11457 		Right = 0x27,
11458 
11459 		Tab = 0x09,
11460 		Q = 0x51,
11461 		W = 0x57,
11462 		E = 0x45,
11463 		R = 0x52,
11464 		T = 0x54,
11465 		Y = 0x59,
11466 		U = 0x55,
11467 		I = 0x49,
11468 		O = 0x4f,
11469 		P = 0x50,
11470 		LeftBracket = 0xdb,
11471 		RightBracket = 0xdd,
11472 		CapsLock = 0x14,
11473 		A = 0x41,
11474 		S = 0x53,
11475 		D = 0x44,
11476 		F = 0x46,
11477 		G = 0x47,
11478 		H = 0x48,
11479 		J = 0x4a,
11480 		K = 0x4b,
11481 		L = 0x4c,
11482 		Semicolon = 0xba,
11483 		Apostrophe = 0xde,
11484 		Enter = 0x0d,
11485 		Shift = 0x10,
11486 		Z = 0x5a,
11487 		X = 0x58,
11488 		C = 0x43,
11489 		V = 0x56,
11490 		B = 0x42,
11491 		N = 0x4e,
11492 		M = 0x4d,
11493 		Comma = 0xbc,
11494 		Period = 0xbe,
11495 		Slash = 0xbf,
11496 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11497 		Ctrl = 0x11,
11498 		Windows = 0x5b,
11499 		Alt = -5, // FIXME
11500 		Space = 0x20,
11501 		Alt_r = 0xffea, // ditto of shift_r
11502 		Windows_r = 0x5c, // ditto of shift_r
11503 		Menu = 0x5d,
11504 		Ctrl_r = 0xa3, // ditto of shift_r
11505 
11506 		NumLock = 0x90,
11507 		Divide = 0x6f,
11508 		Multiply = 0x6a,
11509 		Minus = 0x6d,
11510 		Plus = 0x6b,
11511 		PadEnter = -8, // FIXME
11512 		Pad1 = 0x61,
11513 		Pad2 = 0x62,
11514 		Pad3 = 0x63,
11515 		Pad4 = 0x64,
11516 		Pad5 = 0x65,
11517 		Pad6 = 0x66,
11518 		Pad7 = 0x67,
11519 		Pad8 = 0x68,
11520 		Pad9 = 0x69,
11521 		Pad0 = 0x60,
11522 		PadDot = 0x6e,
11523 	}
11524 
11525 	// I'm keeping this around for reference purposes
11526 	// ideally all these buttons will be listed for all platforms,
11527 	// but now now I'm just focusing on my US keyboard
11528 	version(none)
11529 	enum Key {
11530 		LBUTTON = 0x01,
11531 		RBUTTON = 0x02,
11532 		CANCEL = 0x03,
11533 		MBUTTON = 0x04,
11534 		//static if (_WIN32_WINNT > =  0x500) {
11535 		XBUTTON1 = 0x05,
11536 		XBUTTON2 = 0x06,
11537 		//}
11538 		BACK = 0x08,
11539 		TAB = 0x09,
11540 		CLEAR = 0x0C,
11541 		RETURN = 0x0D,
11542 		SHIFT = 0x10,
11543 		CONTROL = 0x11,
11544 		MENU = 0x12,
11545 		PAUSE = 0x13,
11546 		CAPITAL = 0x14,
11547 		KANA = 0x15,
11548 		HANGEUL = 0x15,
11549 		HANGUL = 0x15,
11550 		JUNJA = 0x17,
11551 		FINAL = 0x18,
11552 		HANJA = 0x19,
11553 		KANJI = 0x19,
11554 		ESCAPE = 0x1B,
11555 		CONVERT = 0x1C,
11556 		NONCONVERT = 0x1D,
11557 		ACCEPT = 0x1E,
11558 		MODECHANGE = 0x1F,
11559 		SPACE = 0x20,
11560 		PRIOR = 0x21,
11561 		NEXT = 0x22,
11562 		END = 0x23,
11563 		HOME = 0x24,
11564 		LEFT = 0x25,
11565 		UP = 0x26,
11566 		RIGHT = 0x27,
11567 		DOWN = 0x28,
11568 		SELECT = 0x29,
11569 		PRINT = 0x2A,
11570 		EXECUTE = 0x2B,
11571 		SNAPSHOT = 0x2C,
11572 		INSERT = 0x2D,
11573 		DELETE = 0x2E,
11574 		HELP = 0x2F,
11575 		LWIN = 0x5B,
11576 		RWIN = 0x5C,
11577 		APPS = 0x5D,
11578 		SLEEP = 0x5F,
11579 		NUMPAD0 = 0x60,
11580 		NUMPAD1 = 0x61,
11581 		NUMPAD2 = 0x62,
11582 		NUMPAD3 = 0x63,
11583 		NUMPAD4 = 0x64,
11584 		NUMPAD5 = 0x65,
11585 		NUMPAD6 = 0x66,
11586 		NUMPAD7 = 0x67,
11587 		NUMPAD8 = 0x68,
11588 		NUMPAD9 = 0x69,
11589 		MULTIPLY = 0x6A,
11590 		ADD = 0x6B,
11591 		SEPARATOR = 0x6C,
11592 		SUBTRACT = 0x6D,
11593 		DECIMAL = 0x6E,
11594 		DIVIDE = 0x6F,
11595 		F1 = 0x70,
11596 		F2 = 0x71,
11597 		F3 = 0x72,
11598 		F4 = 0x73,
11599 		F5 = 0x74,
11600 		F6 = 0x75,
11601 		F7 = 0x76,
11602 		F8 = 0x77,
11603 		F9 = 0x78,
11604 		F10 = 0x79,
11605 		F11 = 0x7A,
11606 		F12 = 0x7B,
11607 		F13 = 0x7C,
11608 		F14 = 0x7D,
11609 		F15 = 0x7E,
11610 		F16 = 0x7F,
11611 		F17 = 0x80,
11612 		F18 = 0x81,
11613 		F19 = 0x82,
11614 		F20 = 0x83,
11615 		F21 = 0x84,
11616 		F22 = 0x85,
11617 		F23 = 0x86,
11618 		F24 = 0x87,
11619 		NUMLOCK = 0x90,
11620 		SCROLL = 0x91,
11621 		LSHIFT = 0xA0,
11622 		RSHIFT = 0xA1,
11623 		LCONTROL = 0xA2,
11624 		RCONTROL = 0xA3,
11625 		LMENU = 0xA4,
11626 		RMENU = 0xA5,
11627 		//static if (_WIN32_WINNT > =  0x500) {
11628 		BROWSER_BACK = 0xA6,
11629 		BROWSER_FORWARD = 0xA7,
11630 		BROWSER_REFRESH = 0xA8,
11631 		BROWSER_STOP = 0xA9,
11632 		BROWSER_SEARCH = 0xAA,
11633 		BROWSER_FAVORITES = 0xAB,
11634 		BROWSER_HOME = 0xAC,
11635 		VOLUME_MUTE = 0xAD,
11636 		VOLUME_DOWN = 0xAE,
11637 		VOLUME_UP = 0xAF,
11638 		MEDIA_NEXT_TRACK = 0xB0,
11639 		MEDIA_PREV_TRACK = 0xB1,
11640 		MEDIA_STOP = 0xB2,
11641 		MEDIA_PLAY_PAUSE = 0xB3,
11642 		LAUNCH_MAIL = 0xB4,
11643 		LAUNCH_MEDIA_SELECT = 0xB5,
11644 		LAUNCH_APP1 = 0xB6,
11645 		LAUNCH_APP2 = 0xB7,
11646 		//}
11647 		OEM_1 = 0xBA,
11648 		//static if (_WIN32_WINNT > =  0x500) {
11649 		OEM_PLUS = 0xBB,
11650 		OEM_COMMA = 0xBC,
11651 		OEM_MINUS = 0xBD,
11652 		OEM_PERIOD = 0xBE,
11653 		//}
11654 		OEM_2 = 0xBF,
11655 		OEM_3 = 0xC0,
11656 		OEM_4 = 0xDB,
11657 		OEM_5 = 0xDC,
11658 		OEM_6 = 0xDD,
11659 		OEM_7 = 0xDE,
11660 		OEM_8 = 0xDF,
11661 		//static if (_WIN32_WINNT > =  0x500) {
11662 		OEM_102 = 0xE2,
11663 		//}
11664 		PROCESSKEY = 0xE5,
11665 		//static if (_WIN32_WINNT > =  0x500) {
11666 		PACKET = 0xE7,
11667 		//}
11668 		ATTN = 0xF6,
11669 		CRSEL = 0xF7,
11670 		EXSEL = 0xF8,
11671 		EREOF = 0xF9,
11672 		PLAY = 0xFA,
11673 		ZOOM = 0xFB,
11674 		NONAME = 0xFC,
11675 		PA1 = 0xFD,
11676 		OEM_CLEAR = 0xFE,
11677 	}
11678 
11679 } else version(OSXCocoa) {
11680 	enum Key {
11681 		Escape = 53,
11682 		F1 = 122,
11683 		F2 = 120,
11684 		F3 = 99,
11685 		F4 = 118,
11686 		F5 = 96,
11687 		F6 = 97,
11688 		F7 = 98,
11689 		F8 = 100,
11690 		F9 = 101,
11691 		F10 = 109,
11692 		F11 = 103,
11693 		F12 = 111,
11694 		PrintScreen = 105,
11695 		ScrollLock = 107,
11696 		Pause = 113,
11697 		Grave = 50,
11698 		// number keys across the top of the keyboard
11699 		N1 = 18,
11700 		N2 = 19,
11701 		N3 = 20,
11702 		N4 = 21,
11703 		N5 = 23,
11704 		N6 = 22,
11705 		N7 = 26,
11706 		N8 = 28,
11707 		N9 = 25,
11708 		N0 = 29,
11709 		Dash = 27,
11710 		Equals = 24,
11711 		Backslash = 42,
11712 		Backspace = 51,
11713 		Insert = 114,
11714 		Home = 115,
11715 		PageUp = 116,
11716 		Delete = 117,
11717 		End = 119,
11718 		PageDown = 121,
11719 		Up = 126,
11720 		Down = 125,
11721 		Left = 123,
11722 		Right = 124,
11723 
11724 		Tab = 48,
11725 		Q = 12,
11726 		W = 13,
11727 		E = 14,
11728 		R = 15,
11729 		T = 17,
11730 		Y = 16,
11731 		U = 32,
11732 		I = 34,
11733 		O = 31,
11734 		P = 35,
11735 		LeftBracket = 33,
11736 		RightBracket = 30,
11737 		CapsLock = 57,
11738 		A = 0,
11739 		S = 1,
11740 		D = 2,
11741 		F = 3,
11742 		G = 5,
11743 		H = 4,
11744 		J = 38,
11745 		K = 40,
11746 		L = 37,
11747 		Semicolon = 41,
11748 		Apostrophe = 39,
11749 		Enter = 36,
11750 		Shift = 56,
11751 		Z = 6,
11752 		X = 7,
11753 		C = 8,
11754 		V = 9,
11755 		B = 11,
11756 		N = 45,
11757 		M = 46,
11758 		Comma = 43,
11759 		Period = 47,
11760 		Slash = 44,
11761 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11762 		Ctrl = 59,
11763 		Windows = 55,
11764 		Alt = 58,
11765 		Space = 49,
11766 		Alt_r = -3, // ditto of shift_r
11767 		Windows_r = -2,
11768 		Menu = 110,
11769 		Ctrl_r = -1,
11770 
11771 		NumLock = 1,
11772 		Divide = 75,
11773 		Multiply = 67,
11774 		Minus = 78,
11775 		Plus = 69,
11776 		PadEnter = 76,
11777 		Pad1 = 83,
11778 		Pad2 = 84,
11779 		Pad3 = 85,
11780 		Pad4 = 86,
11781 		Pad5 = 87,
11782 		Pad6 = 88,
11783 		Pad7 = 89,
11784 		Pad8 = 91,
11785 		Pad9 = 92,
11786 		Pad0 = 82,
11787 		PadDot = 65,
11788 	}
11789 
11790 }
11791 
11792 char keyToLetterCharAssumingLotsOfThingsThatYouMightBetterNotAssume(Key key) {
11793 	version(OSXCocoa) {
11794 		return char.init; // FIXME
11795 	} else {
11796 		return cast(char)(key - Key.A + 'a');
11797 	}
11798 }
11799 
11800 /* Additional utilities */
11801 
11802 
11803 Color fromHsl(real h, real s, real l) {
11804 	return arsd.color.fromHsl([h,s,l]);
11805 }
11806 
11807 
11808 
11809 /* ********** What follows is the system-specific implementations *********/
11810 version(Windows) {
11811 
11812 
11813 	// helpers for making HICONs from MemoryImages
11814 	class WindowsIcon {
11815 		struct Win32Icon {
11816 			align(1):
11817 			uint biSize;
11818 			int biWidth;
11819 			int biHeight;
11820 			ushort biPlanes;
11821 			ushort biBitCount;
11822 			uint biCompression;
11823 			uint biSizeImage;
11824 			int biXPelsPerMeter;
11825 			int biYPelsPerMeter;
11826 			uint biClrUsed;
11827 			uint biClrImportant;
11828 			// RGBQUAD[colorCount] biColors;
11829 			/* Pixels:
11830 			Uint8 pixels[]
11831 			*/
11832 			/* Mask:
11833 			Uint8 mask[]
11834 			*/
11835 		}
11836 
11837 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11838 
11839 			assert(mi.width <= 256, "image too wide");
11840 			assert(mi.height <= 256, "image too tall");
11841 			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
11842 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11843 
11844 			int icon_plen = mi.width * mi.height * 4;
11845 			int icon_mlen = mi.width * mi.height / 8;
11846 
11847 			int colorCount = 0;
11848 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11849 
11850 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11851 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11852 
11853 			auto data = memory[Win32Icon.sizeof .. $];
11854 
11855 			width = mi.width;
11856 			height = mi.height;
11857 
11858 			auto trueColorImage = mi.getAsTrueColorImage();
11859 
11860 			icon_win32.biSize = 40;
11861 			icon_win32.biWidth = mi.width;
11862 			icon_win32.biHeight = mi.height*2;
11863 			icon_win32.biPlanes = 1;
11864 			icon_win32.biBitCount = 32;
11865 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11866 
11867 			int offset = 0;
11868 			int andOff = icon_plen * 8; // the and offset is in bits
11869 
11870 			// leaving the and mask as the default 0 so the rgba alpha blend
11871 			// does its thing instead
11872 			for(int y = height - 1; y >= 0; y--) {
11873 				int off2 = y * width * 4;
11874 				foreach(x; 0 .. width) {
11875 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11876 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11877 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11878 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11879 
11880 					offset += 4;
11881 					off2 += 4;
11882 				}
11883 			}
11884 
11885 			return memory;
11886 		}
11887 
11888 		this(MemoryImage mi) {
11889 			int icon_len, width, height;
11890 
11891 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11892 
11893 			/*
11894 			PNG* png = readPnpngData);
11895 			PNGHeader pngh = getHeader(png);
11896 			void* icon_win32;
11897 			if(pngh.depth == 4) {
11898 				auto i = new Win32Icon!(16);
11899 				i.fromPNG(png, pngh, icon_len, width, height);
11900 				icon_win32 = i;
11901 			}
11902 			else if(pngh.depth == 8) {
11903 				auto i = new Win32Icon!(256);
11904 				i.fromPNG(png, pngh, icon_len, width, height);
11905 				icon_win32 = i;
11906 			} else assert(0);
11907 			*/
11908 
11909 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11910 
11911 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11912 		}
11913 
11914 		~this() {
11915 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11916 			DestroyIcon(hIcon);
11917 		}
11918 
11919 		HICON hIcon;
11920 	}
11921 
11922 
11923 
11924 
11925 
11926 
11927 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11928 	alias HWND NativeWindowHandle;
11929 
11930 	extern(Windows)
11931 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11932 		try {
11933 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11934 				// it returns zero if the message is handled, so we won't do anything more there
11935 				// do I like that though?
11936 				int mustReturn;
11937 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11938 				if(mustReturn)
11939 					return ret;
11940 			}
11941 
11942 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11943 				if(window.getNativeEventHandler !is null) {
11944 					int mustReturn;
11945 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11946 					if(mustReturn)
11947 						return ret;
11948 				}
11949 				if(auto w = cast(SimpleWindow) (*window))
11950 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11951 				else
11952 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11953 			} else {
11954 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11955 			}
11956 		} catch (Exception e) {
11957 			try {
11958 				sdpy_abort(e);
11959 				return 0;
11960 			} catch(Exception e) { assert(0); }
11961 		}
11962 	}
11963 
11964 	void sdpy_abort(Throwable e) nothrow {
11965 		try
11966 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11967 		catch(Exception e)
11968 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11969 		ExitProcess(1);
11970 	}
11971 
11972 	mixin template NativeScreenPainterImplementation() {
11973 		HDC hdc;
11974 		HWND hwnd;
11975 		//HDC windowHdc;
11976 		HBITMAP oldBmp;
11977 
11978 		void create(PaintingHandle window) {
11979 			hwnd = window;
11980 
11981 			if(auto sw = cast(SimpleWindow) this.window) {
11982 				// drawing on a window, double buffer
11983 				auto windowHdc = GetDC(hwnd);
11984 
11985 				auto buffer = sw.impl.buffer;
11986 				if(buffer is null) {
11987 					hdc = windowHdc;
11988 					windowDc = true;
11989 				} else {
11990 					hdc = CreateCompatibleDC(windowHdc);
11991 
11992 					ReleaseDC(hwnd, windowHdc);
11993 
11994 					oldBmp = SelectObject(hdc, buffer);
11995 				}
11996 			} else {
11997 				// drawing on something else, draw directly
11998 				hdc = CreateCompatibleDC(null);
11999 				SelectObject(hdc, window);
12000 			}
12001 
12002 			// X doesn't draw a text background, so neither should we
12003 			SetBkMode(hdc, TRANSPARENT);
12004 
12005 			ensureDefaultFontLoaded();
12006 
12007 			if(defaultGuiFont) {
12008 				SelectObject(hdc, defaultGuiFont);
12009 				// DeleteObject(defaultGuiFont);
12010 			}
12011 		}
12012 
12013 		static HFONT defaultGuiFont;
12014 		static void ensureDefaultFontLoaded() {
12015 			static bool triedDefaultGuiFont = false;
12016 			if(!triedDefaultGuiFont) {
12017 				NONCLIENTMETRICS params;
12018 				params.cbSize = params.sizeof;
12019 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
12020 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
12021 				}
12022 				triedDefaultGuiFont = true;
12023 			}
12024 		}
12025 
12026 		private OperatingSystemFont _activeFont;
12027 
12028 		void setFont(OperatingSystemFont font) {
12029 			_activeFont = font;
12030 			if(font && font.font) {
12031 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
12032 					// error... how to handle tho?
12033 				} else {
12034 
12035 				}
12036 			}
12037 			else if(defaultGuiFont)
12038 				SelectObject(hdc, defaultGuiFont);
12039 		}
12040 
12041 		arsd.color.Rectangle _clipRectangle;
12042 
12043 		void setClipRectangle(int x, int y, int width, int height) {
12044 			auto old = _clipRectangle;
12045 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12046 			if(old == _clipRectangle)
12047 				return;
12048 
12049 			if(width == 0 || height == 0) {
12050 				SelectClipRgn(hdc, null);
12051 			} else {
12052 				auto region = CreateRectRgn(x, y, x + width, y + height);
12053 				SelectClipRgn(hdc, region);
12054 				DeleteObject(region);
12055 			}
12056 		}
12057 
12058 
12059 		// just because we can on Windows...
12060 		//void create(Image image);
12061 
12062 		void invalidateRect(Rectangle invalidRect) {
12063 			RECT rect;
12064 			rect.left = invalidRect.left;
12065 			rect.right = invalidRect.right;
12066 			rect.top = invalidRect.top;
12067 			rect.bottom = invalidRect.bottom;
12068 			InvalidateRect(hwnd, &rect, false);
12069 		}
12070 		bool manualInvalidations;
12071 
12072 		void dispose() {
12073 			// FIXME: this.window.width/height is probably wrong
12074 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
12075 			// ReleaseDC(hwnd, windowHdc);
12076 
12077 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
12078 			if(cast(SimpleWindow) this.window) {
12079 				if(!manualInvalidations)
12080 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
12081 			}
12082 
12083 			if(originalPen !is null)
12084 				SelectObject(hdc, originalPen);
12085 			if(currentPen !is null)
12086 				DeleteObject(currentPen);
12087 			if(originalBrush !is null)
12088 				SelectObject(hdc, originalBrush);
12089 			if(currentBrush !is null)
12090 				DeleteObject(currentBrush);
12091 
12092 			SelectObject(hdc, oldBmp);
12093 
12094 			if(windowDc)
12095 				ReleaseDC(hwnd, hdc);
12096 			else
12097 				DeleteDC(hdc);
12098 
12099 			if(window.paintingFinishedDg !is null)
12100 				window.paintingFinishedDg()();
12101 		}
12102 
12103 		bool windowDc;
12104 		HPEN originalPen;
12105 		HPEN currentPen;
12106 
12107 		Pen _activePen;
12108 
12109 		Color _outlineColor;
12110 
12111 		@property void pen(Pen p) {
12112 			_activePen = p;
12113 			_outlineColor = p.color;
12114 
12115 			HPEN pen;
12116 			if(p.color.a == 0) {
12117 				pen = GetStockObject(NULL_PEN);
12118 			} else {
12119 				int style = PS_SOLID;
12120 				final switch(p.style) {
12121 					case Pen.Style.Solid:
12122 						style = PS_SOLID;
12123 					break;
12124 					case Pen.Style.Dashed:
12125 						style = PS_DASH;
12126 					break;
12127 					case Pen.Style.Dotted:
12128 						style = PS_DOT;
12129 					break;
12130 				}
12131 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
12132 			}
12133 			auto orig = SelectObject(hdc, pen);
12134 			if(originalPen is null)
12135 				originalPen = orig;
12136 
12137 			if(currentPen !is null)
12138 				DeleteObject(currentPen);
12139 
12140 			currentPen = pen;
12141 
12142 			// the outline is like a foreground since it's done that way on X
12143 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
12144 
12145 		}
12146 
12147 		@property void rasterOp(RasterOp op) {
12148 			int mode;
12149 			final switch(op) {
12150 				case RasterOp.normal:
12151 					mode = R2_COPYPEN;
12152 				break;
12153 				case RasterOp.xor:
12154 					mode = R2_XORPEN;
12155 				break;
12156 			}
12157 			SetROP2(hdc, mode);
12158 		}
12159 
12160 		HBRUSH originalBrush;
12161 		HBRUSH currentBrush;
12162 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
12163 		@property void fillColor(Color c) {
12164 			if(c == _fillColor)
12165 				return;
12166 			_fillColor = c;
12167 			HBRUSH brush;
12168 			if(c.a == 0) {
12169 				brush = GetStockObject(HOLLOW_BRUSH);
12170 			} else {
12171 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12172 			}
12173 			auto orig = SelectObject(hdc, brush);
12174 			if(originalBrush is null)
12175 				originalBrush = orig;
12176 
12177 			if(currentBrush !is null)
12178 				DeleteObject(currentBrush);
12179 
12180 			currentBrush = brush;
12181 
12182 			// background color is NOT set because X doesn't draw text backgrounds
12183 			//   SetBkColor(hdc, RGB(255, 255, 255));
12184 		}
12185 
12186 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12187 			BITMAP bm;
12188 
12189 			HDC hdcMem = CreateCompatibleDC(hdc);
12190 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
12191 
12192 			GetObject(i.handle, bm.sizeof, &bm);
12193 
12194 			// or should I AlphaBlend!??!?!
12195 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
12196 
12197 			SelectObject(hdcMem, hbmOld);
12198 			DeleteDC(hdcMem);
12199 		}
12200 
12201 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12202 			BITMAP bm;
12203 
12204 			HDC hdcMem = CreateCompatibleDC(hdc);
12205 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
12206 
12207 			GetObject(s.handle, bm.sizeof, &bm);
12208 
12209 			version(CRuntime_DigitalMars) goto noalpha;
12210 
12211 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
12212 			if(s.enableAlpha) {
12213 				auto dw = w ? w : bm.bmWidth;
12214 				auto dh = h ? h : bm.bmHeight;
12215 				BLENDFUNCTION bf;
12216 				bf.BlendOp = AC_SRC_OVER;
12217 				bf.SourceConstantAlpha = 255;
12218 				bf.AlphaFormat = AC_SRC_ALPHA;
12219 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
12220 			} else {
12221 				noalpha:
12222 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
12223 			}
12224 
12225 			SelectObject(hdcMem, hbmOld);
12226 			DeleteDC(hdcMem);
12227 		}
12228 
12229 		Size textSize(scope const(char)[] text) {
12230 			bool dummyX;
12231 			if(text.length == 0) {
12232 				text = " ";
12233 				dummyX = true;
12234 			}
12235 			RECT rect;
12236 			WCharzBuffer buffer = WCharzBuffer(text);
12237 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
12238 			return Size(dummyX ? 0 : rect.right, rect.bottom);
12239 		}
12240 
12241 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
12242 			if(text.length && text[$-1] == '\n')
12243 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
12244 			if(text.length && text[$-1] == '\r')
12245 				text = text[0 .. $-1];
12246 
12247 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
12248 			if(x2 == 0 && y2 == 0) {
12249 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
12250 			} else {
12251 				RECT rect;
12252 				rect.left = x;
12253 				rect.top = y;
12254 				rect.right = x2;
12255 				rect.bottom = y2;
12256 
12257 				uint mode = DT_LEFT;
12258 				if(alignment & TextAlignment.Right)
12259 					mode = DT_RIGHT;
12260 				else if(alignment & TextAlignment.Center)
12261 					mode = DT_CENTER;
12262 
12263 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
12264 				if(alignment & TextAlignment.VerticalCenter)
12265 					mode |= DT_VCENTER | DT_SINGLELINE;
12266 
12267 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
12268 			}
12269 
12270 			/*
12271 			uint mode;
12272 
12273 			if(alignment & TextAlignment.Center)
12274 				mode = TA_CENTER;
12275 
12276 			SetTextAlign(hdc, mode);
12277 			*/
12278 		}
12279 
12280 		int fontHeight() {
12281 			TEXTMETRIC metric;
12282 			if(GetTextMetricsW(hdc, &metric)) {
12283 				return metric.tmHeight;
12284 			}
12285 
12286 			return 16; // idk just guessing here, maybe we should throw
12287 		}
12288 
12289 		void drawPixel(int x, int y) {
12290 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
12291 		}
12292 
12293 		// The basic shapes, outlined
12294 
12295 		void drawLine(int x1, int y1, int x2, int y2) {
12296 			MoveToEx(hdc, x1, y1, null);
12297 			LineTo(hdc, x2, y2);
12298 		}
12299 
12300 		void drawRectangle(int x, int y, int width, int height) {
12301 			// FIXME: with a wider pen this might not draw quite right. im not sure.
12302 			gdi.Rectangle(hdc, x, y, x + width, y + height);
12303 		}
12304 
12305 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
12306 			RoundRect(
12307 				hdc,
12308 				upperLeft.x, upperLeft.y,
12309 				lowerRight.x, lowerRight.y,
12310 				borderRadius, borderRadius
12311 			);
12312 		}
12313 
12314 		/// Arguments are the points of the bounding rectangle
12315 		void drawEllipse(int x1, int y1, int x2, int y2) {
12316 			Ellipse(hdc, x1, y1, x2, y2);
12317 		}
12318 
12319 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
12320 			//if(length > 360*64)
12321 				//length = 360*64;
12322 
12323 			if((start == 0 && length == 360*64)) {
12324 				drawEllipse(x1, y1, x1 + width, y1 + height);
12325 			} else {
12326 				import core.stdc.math;
12327 
12328 				bool clockwise = false;
12329 				if(length < 0) {
12330 					clockwise = true;
12331 					length = -length;
12332 				}
12333 
12334 				double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323;
12335 				double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323;
12336 
12337 				auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12338 				auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12339 				auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12340 				auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12341 
12342 				if(clockwise) {
12343 					auto t1 = c1;
12344 					auto t2 = c2;
12345 					c1 = c3;
12346 					c2 = c4;
12347 					c3 = t1;
12348 					c4 = t2;
12349 				}
12350 
12351 				//if(_activePen.color.a)
12352 					//Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12353 				//if(_fillColor.a)
12354 
12355 				Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12356 			}
12357 		}
12358 
12359 		void drawPolygon(Point[] vertexes) {
12360 			POINT[] points;
12361 			points.length = vertexes.length;
12362 
12363 			foreach(i, p; vertexes) {
12364 				points[i].x = p.x;
12365 				points[i].y = p.y;
12366 			}
12367 
12368 			Polygon(hdc, points.ptr, cast(int) points.length);
12369 		}
12370 	}
12371 
12372 
12373 	// Mix this into the SimpleWindow class
12374 	mixin template NativeSimpleWindowImplementation() {
12375 		int curHidden = 0; // counter
12376 		__gshared static bool[string] knownWinClasses;
12377 		static bool altPressed = false;
12378 
12379 		HANDLE oldCursor;
12380 
12381 		void hideCursor () {
12382 			if(curHidden == 0)
12383 				oldCursor = SetCursor(null);
12384 			++curHidden;
12385 		}
12386 
12387 		void showCursor () {
12388 			--curHidden;
12389 			if(curHidden == 0) {
12390 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
12391 			}
12392 		}
12393 
12394 
12395 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
12396 
12397 		void setMinSize (int minwidth, int minheight) {
12398 			minWidth = minwidth;
12399 			minHeight = minheight;
12400 		}
12401 		void setMaxSize (int maxwidth, int maxheight) {
12402 			maxWidth = maxwidth;
12403 			maxHeight = maxheight;
12404 		}
12405 
12406 		// FIXME i'm not sure that Windows has this functionality
12407 		// though it is nonessential anyway.
12408 		void setResizeGranularity (int granx, int grany) {}
12409 
12410 		ScreenPainter getPainter(bool manualInvalidations) {
12411 			return ScreenPainter(this, hwnd, manualInvalidations);
12412 		}
12413 
12414 		HBITMAP buffer;
12415 
12416 		void setTitle(string title) {
12417 			WCharzBuffer bfr = WCharzBuffer(title);
12418 			SetWindowTextW(hwnd, bfr.ptr);
12419 		}
12420 
12421 		string getTitle() {
12422 			auto len = GetWindowTextLengthW(hwnd);
12423 			if (!len)
12424 				return null;
12425 			wchar[256] tmpBuffer;
12426 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
12427 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12428 			auto str = buffer[0 .. len2];
12429 			return makeUtf8StringFromWindowsString(str);
12430 		}
12431 
12432 		void move(int x, int y) {
12433 			RECT rect;
12434 			GetWindowRect(hwnd, &rect);
12435 			// move it while maintaining the same size...
12436 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
12437 		}
12438 
12439 		void resize(int w, int h) {
12440 			RECT rect;
12441 			GetWindowRect(hwnd, &rect);
12442 
12443 			RECT client;
12444 			GetClientRect(hwnd, &client);
12445 
12446 			rect.right = rect.right - client.right + w;
12447 			rect.bottom = rect.bottom - client.bottom + h;
12448 
12449 			// same position, new size for the client rectangle
12450 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
12451 
12452 			updateOpenglViewportIfNeeded(w, h);
12453 		}
12454 
12455 		void moveResize (int x, int y, int w, int h) {
12456 			// what's given is the client rectangle, we need to adjust
12457 
12458 			RECT rect;
12459 			rect.left = x;
12460 			rect.top = y;
12461 			rect.right = w + x;
12462 			rect.bottom = h + y;
12463 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
12464 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
12465 
12466 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
12467 			updateOpenglViewportIfNeeded(w, h);
12468 			if (windowResized !is null) windowResized(w, h);
12469 		}
12470 
12471 		version(without_opengl) {} else {
12472 			HGLRC ghRC;
12473 			HDC ghDC;
12474 		}
12475 
12476 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
12477 			string cnamec;
12478 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
12479 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
12480 				cnamec = "DSimpleWindow";
12481 			} else {
12482 				cnamec = sdpyWindowClass;
12483 			}
12484 
12485 			WCharzBuffer cn = WCharzBuffer(cnamec);
12486 
12487 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
12488 
12489 			if(cnamec !in knownWinClasses) {
12490 				WNDCLASSEX wc;
12491 
12492 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
12493 				// to the object. Maybe.
12494 				wc.cbSize = wc.sizeof;
12495 				wc.cbClsExtra = 0;
12496 				wc.cbWndExtra = 0;
12497 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
12498 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
12499 				wc.hIcon = LoadIcon(hInstance, null);
12500 				wc.hInstance = hInstance;
12501 				wc.lpfnWndProc = &WndProc;
12502 				wc.lpszClassName = cn.ptr;
12503 				wc.hIconSm = null;
12504 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
12505 				if(!RegisterClassExW(&wc))
12506 					throw new WindowsApiException("RegisterClassExW", GetLastError());
12507 				knownWinClasses[cnamec] = true;
12508 			}
12509 
12510 			int style;
12511 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
12512 
12513 			// FIXME: windowType and customizationFlags
12514 			final switch(windowType) {
12515 				case WindowTypes.normal:
12516 					if(resizability == Resizability.fixedSize) {
12517 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
12518 					} else {
12519 						style = WS_OVERLAPPEDWINDOW;
12520 					}
12521 				break;
12522 				case WindowTypes.undecorated:
12523 					style = WS_POPUP | WS_SYSMENU;
12524 				break;
12525 				case WindowTypes.eventOnly:
12526 					_hidden = true;
12527 				break;
12528 				case WindowTypes.dropdownMenu:
12529 				case WindowTypes.popupMenu:
12530 				case WindowTypes.notification:
12531 					style = WS_POPUP;
12532 					flags |= WS_EX_NOACTIVATE;
12533 				break;
12534 				case WindowTypes.dialog:
12535 					style = WS_OVERLAPPEDWINDOW;
12536 				break;
12537 				case WindowTypes.nestedChild:
12538 					style = WS_CHILD;
12539 				break;
12540 				case WindowTypes.minimallyWrapped:
12541 					assert(0, "construct minimally wrapped through the other ctor overlad");
12542 			}
12543 
12544 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12545 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
12546 
12547 			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
12548 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
12549 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
12550 
12551 			if(!hwnd)
12552 				throw new WindowsApiException("CreateWindowEx", GetLastError());
12553 
12554 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12555 				setOpacity(255);
12556 
12557 			SimpleWindow.nativeMapping[hwnd] = this;
12558 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
12559 
12560 			if(windowType == WindowTypes.eventOnly)
12561 				return;
12562 
12563 			HDC hdc = GetDC(hwnd);
12564 
12565 			if(!hdc)
12566 				throw new WindowsApiException("GetDC", GetLastError());
12567 
12568 			version(without_opengl) {}
12569 			else {
12570 				if(opengl == OpenGlOptions.yes) {
12571 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12572 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12573 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
12574 					ghDC = hdc;
12575 					PIXELFORMATDESCRIPTOR pfd;
12576 
12577 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
12578 					pfd.nVersion = 1;
12579 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
12580 					pfd.dwLayerMask = PFD_MAIN_PLANE;
12581 					pfd.iPixelType = PFD_TYPE_RGBA;
12582 					pfd.cColorBits = 24;
12583 					pfd.cDepthBits = 24;
12584 					pfd.cAccumBits = 0;
12585 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
12586 
12587 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
12588 
12589 					if (pixelformat == 0)
12590 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
12591 
12592 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
12593 						throw new WindowsApiException("SetPixelFormat", GetLastError());
12594 
12595 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
12596 						// windoze is idiotic: we have to have OpenGL context to get function addresses
12597 						// so we will create fake context to get that stupid address
12598 						auto tmpcc = wglCreateContext(ghDC);
12599 						if (tmpcc !is null) {
12600 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
12601 							wglMakeCurrent(ghDC, tmpcc);
12602 							wglInitOtherFunctions();
12603 						}
12604 					}
12605 
12606 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
12607 						int[9] contextAttribs = [
12608 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
12609 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
12610 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
12611 							// for modern context, set "forward compatibility" flag too
12612 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
12613 							0/*None*/,
12614 						];
12615 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
12616 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
12617 							// activate fallback mode
12618 							// 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;
12619 							ghRC = wglCreateContext(ghDC);
12620 						}
12621 						if (ghRC is null)
12622 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
12623 					} else {
12624 						// try to do at least something
12625 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
12626 							sdpyOpenGLContextVersion = 0;
12627 							ghRC = wglCreateContext(ghDC);
12628 						}
12629 						if (ghRC is null)
12630 							throw new WindowsApiException("wglCreateContext", GetLastError());
12631 					}
12632 				}
12633 			}
12634 
12635 			if(opengl == OpenGlOptions.no) {
12636 				buffer = CreateCompatibleBitmap(hdc, width, height);
12637 
12638 				auto hdcBmp = CreateCompatibleDC(hdc);
12639 				// make sure it's filled with a blank slate
12640 				auto oldBmp = SelectObject(hdcBmp, buffer);
12641 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
12642 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
12643 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
12644 				SelectObject(hdcBmp, oldBmp);
12645 				SelectObject(hdcBmp, oldBrush);
12646 				SelectObject(hdcBmp, oldPen);
12647 				DeleteDC(hdcBmp);
12648 
12649 				bmpWidth = width;
12650 				bmpHeight = height;
12651 
12652 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
12653 			}
12654 
12655 			// We want the window's client area to match the image size
12656 			RECT rcClient, rcWindow;
12657 			POINT ptDiff;
12658 			GetClientRect(hwnd, &rcClient);
12659 			GetWindowRect(hwnd, &rcWindow);
12660 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
12661 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
12662 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
12663 
12664 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
12665 				ShowWindow(hwnd, SW_SHOWNORMAL);
12666 			} else {
12667 				_hidden = true;
12668 			}
12669 			this._visibleForTheFirstTimeCalled = false; // hack!
12670 		}
12671 
12672 
12673 		void dispose() {
12674 			if(buffer)
12675 				DeleteObject(buffer);
12676 		}
12677 
12678 		void closeWindow() {
12679 			if(ghRC) {
12680 				wglDeleteContext(ghRC);
12681 				ghRC = null;
12682 			}
12683 			DestroyWindow(hwnd);
12684 		}
12685 
12686 		bool setOpacity(ubyte alpha) {
12687 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
12688 		}
12689 
12690 		HANDLE currentCursor;
12691 
12692 		// returns zero if it recognized the event
12693 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
12694 			MouseEvent mouse;
12695 
12696 			void mouseEvent(bool isScreen, ulong mods) {
12697 				auto x = LOWORD(lParam);
12698 				auto y = HIWORD(lParam);
12699 				if(isScreen) {
12700 					POINT p;
12701 					p.x = x;
12702 					p.y = y;
12703 					ScreenToClient(hwnd, &p);
12704 					x = cast(ushort) p.x;
12705 					y = cast(ushort) p.y;
12706 				}
12707 
12708 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
12709 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
12710 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
12711 				}
12712 
12713 				mouse.x = x + offsetX;
12714 				mouse.y = y + offsetY;
12715 
12716 				wind.mdx(mouse);
12717 				mouse.modifierState = cast(int) mods;
12718 				mouse.window = wind;
12719 
12720 				if(wind.handleMouseEvent)
12721 					wind.handleMouseEvent(mouse);
12722 			}
12723 
12724 			switch(msg) {
12725 				case WM_GETMINMAXINFO:
12726 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12727 
12728 					if(wind.minWidth > 0) {
12729 						RECT rect;
12730 						rect.left = 100;
12731 						rect.top = 100;
12732 						rect.right = wind.minWidth + 100;
12733 						rect.bottom = wind.minHeight + 100;
12734 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12735 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12736 
12737 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12738 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12739 					}
12740 
12741 					if(wind.maxWidth < int.max) {
12742 						RECT rect;
12743 						rect.left = 100;
12744 						rect.top = 100;
12745 						rect.right = wind.maxWidth + 100;
12746 						rect.bottom = wind.maxHeight + 100;
12747 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12748 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12749 
12750 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12751 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12752 					}
12753 				break;
12754 				case WM_CHAR:
12755 					wchar c = cast(wchar) wParam;
12756 					if(wind.handleCharEvent)
12757 						wind.handleCharEvent(cast(dchar) c);
12758 				break;
12759 				  case WM_SETFOCUS:
12760 				  case WM_KILLFOCUS:
12761 					wind._focused = (msg == WM_SETFOCUS);
12762 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12763 					if(wind.onFocusChange)
12764 						wind.onFocusChange(msg == WM_SETFOCUS);
12765 				  break;
12766 
12767 				case WM_SYSKEYDOWN:
12768 					goto case;
12769 				case WM_SYSKEYUP:
12770 					if(lParam & (1 << 29)) {
12771 						goto case;
12772 					} else {
12773 						// no window has keyboard focus
12774 						goto default;
12775 					}
12776 				case WM_KEYDOWN:
12777 				case WM_KEYUP:
12778 					KeyEvent ev;
12779 					ev.key = cast(Key) wParam;
12780 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12781 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12782 
12783 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12784 
12785 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12786 						ev.modifierState |= ModifierState.shift;
12787 					//k8: this doesn't work; thanks for nothing, windows
12788 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12789 						ev.modifierState |= ModifierState.alt;*/
12790 					// this never seems to actually be set
12791 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12792 
12793 					if (wParam == 0x12) {
12794 						altPressed = (msg == WM_SYSKEYDOWN);
12795 					}
12796 
12797 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12798 						altPressed = false;
12799 					}
12800 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12801 
12802 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12803 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12804 						ev.modifierState |= ModifierState.ctrl;
12805 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12806 						ev.modifierState |= ModifierState.windows;
12807 					if(GetKeyState(Key.NumLock))
12808 						ev.modifierState |= ModifierState.numLock;
12809 					if(GetKeyState(Key.CapsLock))
12810 						ev.modifierState |= ModifierState.capsLock;
12811 
12812 					/+
12813 					// we always want to send the character too, so let's convert it
12814 					ubyte[256] state;
12815 					wchar[16] buffer;
12816 					GetKeyboardState(state.ptr);
12817 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12818 
12819 					foreach(dchar d; buffer) {
12820 						ev.character = d;
12821 						break;
12822 					}
12823 					+/
12824 
12825 					ev.window = wind;
12826 					if(wind.handleKeyEvent)
12827 						wind.handleKeyEvent(ev);
12828 				break;
12829 				case 0x020a /*WM_MOUSEWHEEL*/:
12830 					// send click
12831 					mouse.type = cast(MouseEventType) 1;
12832 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12833 					mouseEvent(true, LOWORD(wParam));
12834 
12835 					// also send release
12836 					mouse.type = cast(MouseEventType) 2;
12837 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12838 					mouseEvent(true, LOWORD(wParam));
12839 				break;
12840 				case WM_MOUSEMOVE:
12841 					mouse.type = cast(MouseEventType) 0;
12842 					mouseEvent(false, wParam);
12843 				break;
12844 				case WM_LBUTTONDOWN:
12845 				case WM_LBUTTONDBLCLK:
12846 					mouse.type = cast(MouseEventType) 1;
12847 					mouse.button = MouseButton.left;
12848 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12849 					mouseEvent(false, wParam);
12850 				break;
12851 				case WM_LBUTTONUP:
12852 					mouse.type = cast(MouseEventType) 2;
12853 					mouse.button = MouseButton.left;
12854 					mouseEvent(false, wParam);
12855 				break;
12856 				case WM_RBUTTONDOWN:
12857 				case WM_RBUTTONDBLCLK:
12858 					mouse.type = cast(MouseEventType) 1;
12859 					mouse.button = MouseButton.right;
12860 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12861 					mouseEvent(false, wParam);
12862 				break;
12863 				case WM_RBUTTONUP:
12864 					mouse.type = cast(MouseEventType) 2;
12865 					mouse.button = MouseButton.right;
12866 					mouseEvent(false, wParam);
12867 				break;
12868 				case WM_MBUTTONDOWN:
12869 				case WM_MBUTTONDBLCLK:
12870 					mouse.type = cast(MouseEventType) 1;
12871 					mouse.button = MouseButton.middle;
12872 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12873 					mouseEvent(false, wParam);
12874 				break;
12875 				case WM_MBUTTONUP:
12876 					mouse.type = cast(MouseEventType) 2;
12877 					mouse.button = MouseButton.middle;
12878 					mouseEvent(false, wParam);
12879 				break;
12880 				case WM_XBUTTONDOWN:
12881 				case WM_XBUTTONDBLCLK:
12882 					mouse.type = cast(MouseEventType) 1;
12883 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12884 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12885 					mouseEvent(false, wParam);
12886 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12887 				case WM_XBUTTONUP:
12888 					mouse.type = cast(MouseEventType) 2;
12889 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12890 					mouseEvent(false, wParam);
12891 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12892 
12893 				default: return 1;
12894 			}
12895 			return 0;
12896 		}
12897 
12898 		HWND hwnd;
12899 		private int oldWidth;
12900 		private int oldHeight;
12901 		private bool inSizeMove;
12902 
12903 		/++
12904 			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.
12905 
12906 			History:
12907 				Added November 23, 2021
12908 
12909 				Not fully stable, may be moved out of the impl struct.
12910 
12911 				Default value changed to `true` on February 15, 2021
12912 		+/
12913 		bool doLiveResizing = true;
12914 
12915 		package int bmpWidth;
12916 		package int bmpHeight;
12917 
12918 		// the extern(Windows) wndproc should just forward to this
12919 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12920 		try {
12921 			assert(hwnd is this.hwnd);
12922 
12923 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12924 			switch(msg) {
12925 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12926 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12927 					// The main things we can do are select, execute, close, or ignore
12928 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12929 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12930 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12931 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12932 
12933 					// returns the value in the *high order word* of the return value
12934 					// hence the << 16
12935 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12936 				case WM_SETCURSOR:
12937 					if(cast(HWND) wParam !is hwnd)
12938 						return 0; // further processing elsewhere
12939 
12940 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12941 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12942 						return 1;
12943 					} else {
12944 						return DefWindowProc(hwnd, msg, wParam, lParam);
12945 					}
12946 				//break;
12947 
12948 				case WM_CLOSE:
12949 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12950 				break;
12951 				case WM_DESTROY:
12952 					if (this.visibilityChanged !is null && this._visible) this.visibilityChanged(false);
12953 
12954 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12955 					SimpleWindow.nativeMapping.remove(hwnd);
12956 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12957 
12958 					bool anyImportant = false;
12959 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12960 						if(w.beingOpenKeepsAppOpen) {
12961 							anyImportant = true;
12962 							break;
12963 						}
12964 					if(!anyImportant) {
12965 						PostQuitMessage(0);
12966 					}
12967 				break;
12968 				case 0x02E0 /*WM_DPICHANGED*/:
12969 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12970 
12971 					RECT* prcNewWindow = cast(RECT*)lParam;
12972 					// docs say this is the recommended position and we should honor it
12973 					SetWindowPos(hwnd,
12974 							null,
12975 							prcNewWindow.left,
12976 							prcNewWindow.top,
12977 							prcNewWindow.right - prcNewWindow.left,
12978 							prcNewWindow.bottom - prcNewWindow.top,
12979 							SWP_NOZORDER | SWP_NOACTIVATE);
12980 
12981 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12982 					// im not sure it is completely correct
12983 					// but without it the tabs and such do look weird as things change.
12984 					if(SystemParametersInfoForDpi) {
12985 						LOGFONT lfText;
12986 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12987 						HFONT hFontNew = CreateFontIndirect(&lfText);
12988 						if (hFontNew)
12989 						{
12990 							//DeleteObject(hFontOld);
12991 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12992 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12993 								return TRUE;
12994 							}
12995 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12996 						}
12997 					}
12998 
12999 					if(this.onDpiChanged)
13000 						this.onDpiChanged();
13001 				break;
13002 				case WM_ENTERIDLE:
13003 					// when a menu is up, it stops normal event processing (modal message loop)
13004 					// but this at least gives us a chance to SOMETIMES catch up
13005 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
13006 					SimpleWindow.processAllCustomEvents;
13007 					SimpleWindow.processAllCustomEvents;
13008 					SleepEx(0, true);
13009 					break;
13010 				case WM_SIZE:
13011 					if(wParam == 1 /* SIZE_MINIMIZED */)
13012 						break;
13013 					_width = LOWORD(lParam);
13014 					_height = HIWORD(lParam);
13015 
13016 					// I want to avoid tearing in the windows (my code is inefficient
13017 					// so this is a hack around that) so while sizing, we don't trigger,
13018 					// but we do want to trigger on events like mazimize.
13019 					if(!inSizeMove || doLiveResizing)
13020 						goto size_changed;
13021 				break;
13022 				/+
13023 				case WM_SIZING:
13024 					writeln("size");
13025 				break;
13026 				+/
13027 				// I don't like the tearing I get when redrawing on WM_SIZE
13028 				// (I know there's other ways to fix that but I don't like that behavior anyway)
13029 				// so instead it is going to redraw only at the end of a size.
13030 				case 0x0231: /* WM_ENTERSIZEMOVE */
13031 					inSizeMove = true;
13032 				break;
13033 				case 0x0232: /* WM_EXITSIZEMOVE */
13034 					inSizeMove = false;
13035 
13036 					size_changed:
13037 
13038 					// nothing relevant changed, don't bother redrawing
13039 					if(oldWidth == _width && oldHeight == _height) {
13040 						if(msg == 0x0232)
13041 							goto finalize_resize;
13042 						break;
13043 					}
13044 
13045 					// note: OpenGL windows don't use a backing bmp, so no need to change them
13046 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
13047 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
13048 						// gotta get the double buffer bmp to match the window
13049 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
13050 
13051 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
13052 						if(resizability != Resizability.automaticallyScaleIfPossible)
13053 						if(_width > bmpWidth || _height > bmpHeight) {
13054 							auto hdc = GetDC(hwnd);
13055 							auto oldBuffer = buffer;
13056 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
13057 
13058 							auto hdcBmp = CreateCompatibleDC(hdc);
13059 							auto oldBmp = SelectObject(hdcBmp, buffer);
13060 
13061 							auto hdcOldBmp = CreateCompatibleDC(hdc);
13062 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
13063 
13064 							/+
13065 							RECT r;
13066 							r.left = 0;
13067 							r.top = 0;
13068 							r.right = width;
13069 							r.bottom = height;
13070 							auto c = Color.green;
13071 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
13072 							FillRect(hdcBmp, &r, brush);
13073 							DeleteObject(brush);
13074 							+/
13075 
13076 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
13077 
13078 							bmpWidth = _width;
13079 							bmpHeight = _height;
13080 
13081 							SelectObject(hdcOldBmp, oldOldBmp);
13082 							DeleteDC(hdcOldBmp);
13083 
13084 							SelectObject(hdcBmp, oldBmp);
13085 							DeleteDC(hdcBmp);
13086 
13087 							ReleaseDC(hwnd, hdc);
13088 
13089 							DeleteObject(oldBuffer);
13090 						}
13091 					}
13092 
13093 					updateOpenglViewportIfNeeded(_width, _height);
13094 
13095 					if(resizability != Resizability.automaticallyScaleIfPossible)
13096 					if(windowResized !is null)
13097 						windowResized(_width, _height);
13098 
13099 					/+
13100 					if(inSizeMove) {
13101 						// SimpleWindow.processAllCustomEvents();
13102 						// SimpleWindow.processAllCustomEvents();
13103 
13104 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13105 						//sdpyPrintDebugString("redraw b");
13106 					} else {
13107 					+/ {
13108 						finalize_resize:
13109 						// when it is all done, make sure everything is freshly drawn or there might be
13110 						// weird bugs left.
13111 						SimpleWindow.processAllCustomEvents();
13112 						SimpleWindow.processAllCustomEvents();
13113 
13114 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13115 						// sdpyPrintDebugString("redraw");
13116 					}
13117 
13118 					oldWidth = this._width;
13119 					oldHeight = this._height;
13120 				break;
13121 				case WM_ERASEBKGND:
13122 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
13123 					if (!this._visibleForTheFirstTimeCalled) {
13124 						this._visibleForTheFirstTimeCalled = true;
13125 						if (this.visibleForTheFirstTime !is null) {
13126 							this.visibleForTheFirstTime();
13127 						}
13128 					}
13129 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
13130 					version(without_opengl) {} else {
13131 						if (openglMode == OpenGlOptions.yes) return 1;
13132 					}
13133 					// call windows default handler, so it can paint standard controls
13134 					goto default;
13135 				case WM_CTLCOLORBTN:
13136 				case WM_CTLCOLORSTATIC:
13137 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
13138 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
13139 					GetSysColorBrush(COLOR_3DFACE);
13140 				//break;
13141 				case WM_SHOWWINDOW:
13142 					auto before = this._visible;
13143 					this._visible = (wParam != 0);
13144 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
13145 						this._visibleForTheFirstTimeCalled = true;
13146 						if (this.visibleForTheFirstTime !is null) {
13147 							this.visibleForTheFirstTime();
13148 						}
13149 					}
13150 					if (this.visibilityChanged !is null && this._visible != before) this.visibilityChanged(this._visible);
13151 					break;
13152 				case WM_PAINT: {
13153 					if (!this._visibleForTheFirstTimeCalled) {
13154 						this._visibleForTheFirstTimeCalled = true;
13155 						if (this.visibleForTheFirstTime !is null) {
13156 							this.visibleForTheFirstTime();
13157 						}
13158 					}
13159 
13160 					BITMAP bm;
13161 					PAINTSTRUCT ps;
13162 
13163 					HDC hdc = BeginPaint(hwnd, &ps);
13164 
13165 					if(openglMode == OpenGlOptions.no) {
13166 
13167 						HDC hdcMem = CreateCompatibleDC(hdc);
13168 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
13169 
13170 						GetObject(buffer, bm.sizeof, &bm);
13171 
13172 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
13173 						if(resizability == Resizability.automaticallyScaleIfPossible)
13174 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
13175 						else
13176 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
13177 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
13178 
13179 						SelectObject(hdcMem, hbmOld);
13180 						DeleteDC(hdcMem);
13181 						EndPaint(hwnd, &ps);
13182 					} else {
13183 						EndPaint(hwnd, &ps);
13184 						version(without_opengl) {} else
13185 							redrawOpenGlSceneSoon();
13186 					}
13187 				} break;
13188 				  default:
13189 					return DefWindowProc(hwnd, msg, wParam, lParam);
13190 			}
13191 			 return 0;
13192 
13193 		}
13194 		catch(Throwable t) {
13195 			sdpyPrintDebugString(t.toString);
13196 			return 0;
13197 		}
13198 		}
13199 	}
13200 
13201 	mixin template NativeImageImplementation() {
13202 		HBITMAP handle;
13203 		ubyte* rawData;
13204 
13205 	final:
13206 
13207 		Color getPixel(int x, int y) @system {
13208 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13209 			// remember, bmps are upside down
13210 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13211 
13212 			Color c;
13213 			if(enableAlpha)
13214 				c.a = rawData[offset + 3];
13215 			else
13216 				c.a = 255;
13217 			c.b = rawData[offset + 0];
13218 			c.g = rawData[offset + 1];
13219 			c.r = rawData[offset + 2];
13220 			c.unPremultiply();
13221 			return c;
13222 		}
13223 
13224 		void setPixel(int x, int y, Color c) @system {
13225 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13226 			// remember, bmps are upside down
13227 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13228 
13229 			if(enableAlpha)
13230 				c.premultiply();
13231 
13232 			rawData[offset + 0] = c.b;
13233 			rawData[offset + 1] = c.g;
13234 			rawData[offset + 2] = c.r;
13235 			if(enableAlpha)
13236 				rawData[offset + 3] = c.a;
13237 		}
13238 
13239 		void convertToRgbaBytes(ubyte[] where) @system {
13240 			assert(where.length == this.width * this.height * 4);
13241 
13242 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13243 			int idx = 0;
13244 			int offset = itemsPerLine * (height - 1);
13245 			// remember, bmps are upside down
13246 			for(int y = height - 1; y >= 0; y--) {
13247 				auto offsetStart = offset;
13248 				for(int x = 0; x < width; x++) {
13249 					where[idx + 0] = rawData[offset + 2]; // r
13250 					where[idx + 1] = rawData[offset + 1]; // g
13251 					where[idx + 2] = rawData[offset + 0]; // b
13252 					if(enableAlpha) {
13253 						where[idx + 3] = rawData[offset + 3]; // a
13254 						unPremultiplyRgba(where[idx .. idx + 4]);
13255 						offset++;
13256 					} else
13257 						where[idx + 3] = 255; // a
13258 					idx += 4;
13259 					offset += 3;
13260 				}
13261 
13262 				offset = offsetStart - itemsPerLine;
13263 			}
13264 		}
13265 
13266 		void setFromRgbaBytes(in ubyte[] what) @system {
13267 			assert(what.length == this.width * this.height * 4);
13268 
13269 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
13270 			int idx = 0;
13271 			int offset = itemsPerLine * (height - 1);
13272 			// remember, bmps are upside down
13273 			for(int y = height - 1; y >= 0; y--) {
13274 				auto offsetStart = offset;
13275 				for(int x = 0; x < width; x++) {
13276 					if(enableAlpha) {
13277 						auto a = what[idx + 3];
13278 
13279 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
13280 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
13281 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
13282 						rawData[offset + 3] = a; // a
13283 						//premultiplyBgra(rawData[offset .. offset + 4]);
13284 						offset++;
13285 					} else {
13286 						rawData[offset + 2] = what[idx + 0]; // r
13287 						rawData[offset + 1] = what[idx + 1]; // g
13288 						rawData[offset + 0] = what[idx + 2]; // b
13289 					}
13290 					idx += 4;
13291 					offset += 3;
13292 				}
13293 
13294 				offset = offsetStart - itemsPerLine;
13295 			}
13296 		}
13297 
13298 
13299 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
13300 			BITMAPINFO infoheader;
13301 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
13302 			infoheader.bmiHeader.biWidth = width;
13303 			infoheader.bmiHeader.biHeight = height;
13304 			infoheader.bmiHeader.biPlanes = 1;
13305 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
13306 			infoheader.bmiHeader.biCompression = BI_RGB;
13307 
13308 			handle = CreateDIBSection(
13309 				null,
13310 				&infoheader,
13311 				DIB_RGB_COLORS,
13312 				cast(void**) &rawData,
13313 				null,
13314 				0);
13315 			if(handle is null)
13316 				throw new WindowsApiException("create image failed", GetLastError());
13317 
13318 		}
13319 
13320 		void dispose() {
13321 			DeleteObject(handle);
13322 		}
13323 	}
13324 
13325 	enum KEY_ESCAPE = 27;
13326 }
13327 
13328 version(Emscripten) {
13329 	alias int delegate(void*) NativeEventHandler;
13330 	alias void* NativeWindowHandle;
13331 
13332 	mixin template NativeSimpleWindowImplementation() { }
13333 	mixin template NativeScreenPainterImplementation() { }
13334 	mixin template NativeImageImplementation() { }
13335 }
13336 
13337 version(X11) {
13338 	/// This is the default font used. You might change this before doing anything else with
13339 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
13340 	/// for cross-platform compatibility.
13341 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13342 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13343 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
13344 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
13345 
13346 	alias int delegate(XEvent) NativeEventHandler;
13347 	alias Window NativeWindowHandle;
13348 
13349 	enum KEY_ESCAPE = 9;
13350 
13351 	mixin template NativeScreenPainterImplementation() {
13352 		Display* display;
13353 		Drawable d;
13354 		Drawable destiny;
13355 
13356 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
13357 		GC gc;
13358 
13359 		__gshared bool fontAttempted;
13360 
13361 		__gshared XFontStruct* defaultfont;
13362 		__gshared XFontSet defaultfontset;
13363 
13364 		XFontStruct* font;
13365 		XFontSet fontset;
13366 
13367 		void create(PaintingHandle window) {
13368 			this.display = XDisplayConnection.get();
13369 
13370 			Drawable buffer = None;
13371 			if(auto sw = cast(SimpleWindow) this.window) {
13372 				buffer = sw.impl.buffer;
13373 				this.destiny = cast(Drawable) window;
13374 			} else {
13375 				buffer = cast(Drawable) window;
13376 				this.destiny = None;
13377 			}
13378 
13379 			this.d = cast(Drawable) buffer;
13380 
13381 			auto dgc = DefaultGC(display, DefaultScreen(display));
13382 
13383 			this.gc = XCreateGC(display, d, 0, null);
13384 
13385 			XCopyGC(display, dgc, 0xffffffff, this.gc);
13386 
13387 			ensureDefaultFontLoaded();
13388 
13389 			font = defaultfont;
13390 			fontset = defaultfontset;
13391 
13392 			if(font) {
13393 				XSetFont(display, gc, font.fid);
13394 			}
13395 		}
13396 
13397 		static void ensureDefaultFontLoaded() {
13398 			if(!fontAttempted) {
13399 				auto display = XDisplayConnection.get;
13400 				auto font = XLoadQueryFont(display, xfontstr.ptr);
13401 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
13402 				if(font is null) {
13403 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
13404 					font = XLoadQueryFont(display, xfontstr.ptr);
13405 				}
13406 
13407 				char** lol;
13408 				int lol2;
13409 				char* lol3;
13410 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
13411 
13412 				fontAttempted = true;
13413 
13414 				defaultfont = font;
13415 				defaultfontset = fontset;
13416 			}
13417 		}
13418 
13419 		arsd.color.Rectangle _clipRectangle;
13420 		void setClipRectangle(int x, int y, int width, int height) {
13421 			auto old = _clipRectangle;
13422 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
13423 			if(old == _clipRectangle)
13424 				return;
13425 
13426 			if(width == 0 || height == 0) {
13427 				XSetClipMask(display, gc, None);
13428 
13429 				if(xrenderPicturePainter) {
13430 
13431 					XRectangle[1] rects;
13432 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
13433 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13434 				}
13435 
13436 				version(with_xft) {
13437 					if(xftFont is null || xftDraw is null)
13438 						return;
13439 					XftDrawSetClip(xftDraw, null);
13440 				}
13441 			} else {
13442 				XRectangle[1] rects;
13443 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
13444 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
13445 
13446 				if(xrenderPicturePainter)
13447 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13448 
13449 				version(with_xft) {
13450 					if(xftFont is null || xftDraw is null)
13451 						return;
13452 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
13453 				}
13454 			}
13455 		}
13456 
13457 		version(with_xft) {
13458 			XftFont* xftFont;
13459 			XftDraw* xftDraw;
13460 
13461 			XftColor xftColor;
13462 
13463 			void updateXftColor() {
13464 				if(xftFont is null)
13465 					return;
13466 
13467 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
13468 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
13469 
13470 				XftColorAllocValue(
13471 					display,
13472 					DefaultVisual(display, DefaultScreen(display)),
13473 					DefaultColormap(display, 0),
13474 					&colorIn,
13475 					&xftColor
13476 				);
13477 			}
13478 		}
13479 
13480 		private OperatingSystemFont _activeFont;
13481 		void setFont(OperatingSystemFont font) {
13482 			_activeFont = font;
13483 			version(with_xft) {
13484 				if(font && font.isXft && font.xftFont)
13485 					this.xftFont = font.xftFont;
13486 				else
13487 					this.xftFont = null;
13488 
13489 				if(this.xftFont) {
13490 					if(xftDraw is null) {
13491 						xftDraw = XftDrawCreate(
13492 							display,
13493 							d,
13494 							DefaultVisual(display, DefaultScreen(display)),
13495 							DefaultColormap(display, 0)
13496 						);
13497 
13498 						updateXftColor();
13499 					}
13500 
13501 					return;
13502 				}
13503 			}
13504 
13505 			if(font && font.font) {
13506 				this.font = font.font;
13507 				this.fontset = font.fontset;
13508 				XSetFont(display, gc, font.font.fid);
13509 			} else {
13510 				this.font = defaultfont;
13511 				this.fontset = defaultfontset;
13512 			}
13513 
13514 		}
13515 
13516 		private Picture xrenderPicturePainter;
13517 
13518 		bool manualInvalidations;
13519 		void invalidateRect(Rectangle invalidRect) {
13520 			// FIXME if manualInvalidations
13521 		}
13522 
13523 		void dispose() {
13524 			this.rasterOp = RasterOp.normal;
13525 
13526 			if(xrenderPicturePainter) {
13527 				XRenderFreePicture(display, xrenderPicturePainter);
13528 				xrenderPicturePainter = None;
13529 			}
13530 
13531 			// FIXME: this.window.width/height is probably wrong
13532 
13533 			// src x,y     then dest x, y
13534 			if(destiny != None) {
13535 				// FIXME: if manual invalidations we can actually only copy some of the area.
13536 				// if(manualInvalidations)
13537 				XSetClipMask(display, gc, None);
13538 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
13539 			}
13540 
13541 			XFreeGC(display, gc);
13542 
13543 			version(with_xft)
13544 			if(xftDraw) {
13545 				XftDrawDestroy(xftDraw);
13546 				xftDraw = null;
13547 			}
13548 
13549 			/+
13550 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
13551 			if(font && font !is defaultfont) {
13552 				XFreeFont(display, font);
13553 				font = null;
13554 			}
13555 			if(fontset && fontset !is defaultfontset) {
13556 				XFreeFontSet(display, fontset);
13557 				fontset = null;
13558 			}
13559 			+/
13560 			XFlush(display);
13561 
13562 			if(window.paintingFinishedDg !is null)
13563 				window.paintingFinishedDg()();
13564 		}
13565 
13566 		bool backgroundIsNotTransparent = true;
13567 		bool foregroundIsNotTransparent = true;
13568 
13569 		bool _penInitialized = false;
13570 		Pen _activePen;
13571 
13572 		Color _outlineColor;
13573 		Color _fillColor;
13574 
13575 		@property void pen(Pen p) {
13576 			if(_penInitialized && p == _activePen) {
13577 				return;
13578 			}
13579 			_penInitialized = true;
13580 			_activePen = p;
13581 			_outlineColor = p.color;
13582 
13583 			int style;
13584 
13585 			byte dashLength;
13586 
13587 			final switch(p.style) {
13588 				case Pen.Style.Solid:
13589 					style = 0 /*LineSolid*/;
13590 				break;
13591 				case Pen.Style.Dashed:
13592 					style = 1 /*LineOnOffDash*/;
13593 					dashLength = 4;
13594 				break;
13595 				case Pen.Style.Dotted:
13596 					style = 1 /*LineOnOffDash*/;
13597 					dashLength = 1;
13598 				break;
13599 			}
13600 
13601 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
13602 			if(dashLength)
13603 				XSetDashes(display, gc, 0, &dashLength, 1);
13604 
13605 			if(p.color.a == 0) {
13606 				foregroundIsNotTransparent = false;
13607 				return;
13608 			}
13609 
13610 			foregroundIsNotTransparent = true;
13611 
13612 			XSetForeground(display, gc, colorToX(p.color, display));
13613 
13614 			version(with_xft)
13615 				updateXftColor();
13616 		}
13617 
13618 		RasterOp _currentRasterOp;
13619 		bool _currentRasterOpInitialized = false;
13620 		@property void rasterOp(RasterOp op) {
13621 			if(_currentRasterOpInitialized && _currentRasterOp == op)
13622 				return;
13623 			_currentRasterOp = op;
13624 			_currentRasterOpInitialized = true;
13625 			int mode;
13626 			final switch(op) {
13627 				case RasterOp.normal:
13628 					mode = GXcopy;
13629 				break;
13630 				case RasterOp.xor:
13631 					mode = GXxor;
13632 				break;
13633 			}
13634 			XSetFunction(display, gc, mode);
13635 		}
13636 
13637 
13638 		bool _fillColorInitialized = false;
13639 
13640 		@property void fillColor(Color c) {
13641 			if(_fillColorInitialized && _fillColor == c)
13642 				return; // already good, no need to waste time calling it
13643 			_fillColor = c;
13644 			_fillColorInitialized = true;
13645 			if(c.a == 0) {
13646 				backgroundIsNotTransparent = false;
13647 				return;
13648 			}
13649 
13650 			backgroundIsNotTransparent = true;
13651 
13652 			XSetBackground(display, gc, colorToX(c, display));
13653 
13654 		}
13655 
13656 		void swapColors() {
13657 			auto tmp = _fillColor;
13658 			fillColor = _outlineColor;
13659 			auto newPen = _activePen;
13660 			newPen.color = tmp;
13661 			pen(newPen);
13662 		}
13663 
13664 		uint colorToX(Color c, Display* display) {
13665 			auto visual = DefaultVisual(display, DefaultScreen(display));
13666 			import core.bitop;
13667 			uint color = 0;
13668 			{
13669 			auto startBit = bsf(visual.red_mask);
13670 			auto lastBit = bsr(visual.red_mask);
13671 			auto r = cast(uint) c.r;
13672 			r >>= 7 - (lastBit - startBit);
13673 			r <<= startBit;
13674 			color |= r;
13675 			}
13676 			{
13677 			auto startBit = bsf(visual.green_mask);
13678 			auto lastBit = bsr(visual.green_mask);
13679 			auto g = cast(uint) c.g;
13680 			g >>= 7 - (lastBit - startBit);
13681 			g <<= startBit;
13682 			color |= g;
13683 			}
13684 			{
13685 			auto startBit = bsf(visual.blue_mask);
13686 			auto lastBit = bsr(visual.blue_mask);
13687 			auto b = cast(uint) c.b;
13688 			b >>= 7 - (lastBit - startBit);
13689 			b <<= startBit;
13690 			color |= b;
13691 			}
13692 
13693 
13694 
13695 			return color;
13696 		}
13697 
13698 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
13699 			// source x, source y
13700 			if(ix >= i.width) return;
13701 			if(iy >= i.height) return;
13702 			if(ix + w > i.width) w = i.width - ix;
13703 			if(iy + h > i.height) h = i.height - iy;
13704 			if(i.usingXshm)
13705 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
13706 			else
13707 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
13708 		}
13709 
13710 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
13711 			if(s.enableAlpha) {
13712 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
13713 				if(this.xrenderPicturePainter == None) {
13714 					XRenderPictureAttributes attrs;
13715 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
13716 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
13717 
13718 					// need to initialize the clip
13719 					XRectangle[1] rects;
13720 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
13721 
13722 					if(_clipRectangle != Rectangle.init)
13723 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13724 				}
13725 
13726 				XRenderComposite(
13727 					display,
13728 					3, // PicOpOver
13729 					s.xrenderPicture,
13730 					None,
13731 					this.xrenderPicturePainter,
13732 					ix,
13733 					iy,
13734 					0,
13735 					0,
13736 					x,
13737 					y,
13738 					w ? w : s.width,
13739 					h ? h : s.height
13740 				);
13741 			} else {
13742 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13743 			}
13744 		}
13745 
13746 		int fontHeight() {
13747 			version(with_xft)
13748 				if(xftFont !is null)
13749 					return xftFont.height;
13750 			if(font)
13751 				return font.max_bounds.ascent + font.max_bounds.descent;
13752 			return 12; // pretty common default...
13753 		}
13754 
13755 		int textWidth(in char[] line) {
13756 			version(with_xft)
13757 			if(xftFont) {
13758 				if(line.length == 0)
13759 					return 0;
13760 				XGlyphInfo extents;
13761 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13762 				return extents.width;
13763 			}
13764 
13765 			if(fontset) {
13766 				if(line.length == 0)
13767 					return 0;
13768 				XRectangle rect;
13769 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13770 
13771 				return rect.width;
13772 			}
13773 
13774 			if(font)
13775 				// FIXME: unicode
13776 				return XTextWidth( font, line.ptr, cast(int) line.length);
13777 			else
13778 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13779 		}
13780 
13781 		Size textSize(in char[] text) {
13782 			auto maxWidth = 0;
13783 			auto lineHeight = fontHeight;
13784 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13785 			foreach(line; text.split('\n')) {
13786 				int textWidth = this.textWidth(line);
13787 				if(textWidth > maxWidth)
13788 					maxWidth = textWidth;
13789 				h += lineHeight + 4;
13790 			}
13791 			return Size(maxWidth, h);
13792 		}
13793 
13794 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13795 			const(char)[] text;
13796 			version(with_xft)
13797 			if(xftFont) {
13798 				text = originalText;
13799 				goto loaded;
13800 			}
13801 
13802 			if(fontset)
13803 				text = originalText;
13804 			else {
13805 				text.reserve(originalText.length);
13806 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13807 				// then strip the rest so there isn't garbage
13808 				foreach(dchar ch; originalText)
13809 					if(ch < 256)
13810 						text ~= cast(ubyte) ch;
13811 					else
13812 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13813 			}
13814 			loaded:
13815 			if(text.length == 0)
13816 				return;
13817 
13818 			// FIXME: should we clip it to the bounding box?
13819 			int textHeight = fontHeight;
13820 
13821 			auto lines = text.split('\n');
13822 
13823 			const lineHeight = textHeight;
13824 			textHeight *= lines.length;
13825 
13826 			int cy = y;
13827 
13828 			if(alignment & TextAlignment.VerticalBottom) {
13829 				if(y2 <= 0)
13830 					return;
13831 				auto h = y2 - y;
13832 				if(h > textHeight) {
13833 					cy += h - textHeight;
13834 					cy -= lineHeight / 2;
13835 				}
13836 			} else if(alignment & TextAlignment.VerticalCenter) {
13837 				if(y2 <= 0)
13838 					return;
13839 				auto h = y2 - y;
13840 				if(textHeight < h) {
13841 					cy += (h - textHeight) / 2;
13842 					//cy -= lineHeight / 4;
13843 				}
13844 			}
13845 
13846 			foreach(line; text.split('\n')) {
13847 				int textWidth = this.textWidth(line);
13848 
13849 				int px = x, py = cy;
13850 
13851 				if(alignment & TextAlignment.Center) {
13852 					if(x2 <= 0)
13853 						return;
13854 					auto w = x2 - x;
13855 					if(w > textWidth)
13856 						px += (w - textWidth) / 2;
13857 				} else if(alignment & TextAlignment.Right) {
13858 					if(x2 <= 0)
13859 						return;
13860 					auto pos = x2 - textWidth;
13861 					if(pos > x)
13862 						px = pos;
13863 				}
13864 
13865 				version(with_xft)
13866 				if(xftFont) {
13867 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13868 
13869 					goto carry_on;
13870 				}
13871 
13872 				if(fontset)
13873 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13874 				else
13875 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13876 				carry_on:
13877 				cy += lineHeight + 4;
13878 			}
13879 		}
13880 
13881 		void drawPixel(int x, int y) {
13882 			XDrawPoint(display, d, gc, x, y);
13883 		}
13884 
13885 		// The basic shapes, outlined
13886 
13887 		void drawLine(int x1, int y1, int x2, int y2) {
13888 			if(foregroundIsNotTransparent)
13889 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13890 		}
13891 
13892 		void drawRectangle(int x, int y, int width, int height) {
13893 			if(backgroundIsNotTransparent) {
13894 				swapColors();
13895 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13896 				swapColors();
13897 			}
13898 			// 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
13899 			if(foregroundIsNotTransparent)
13900 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13901 		}
13902 
13903 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
13904 			int[4] radii = borderRadius;
13905 			auto r = Rectangle(upperLeft, lowerRight);
13906 
13907 			if(backgroundIsNotTransparent) {
13908 				swapColors();
13909 				// FIXME these overlap and thus draw the pixels multiple times
13910 				XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius);
13911 				XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13912 				swapColors();
13913 			}
13914 
13915 			drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top);
13916 			drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1);
13917 			drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2);
13918 			drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2);
13919 
13920 			//drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13921 
13922 			drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64);
13923 			drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64);
13924 			drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64);
13925 			drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64);
13926 		}
13927 
13928 
13929 		/// Arguments are the points of the bounding rectangle
13930 		void drawEllipse(int x1, int y1, int x2, int y2) {
13931 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13932 		}
13933 
13934 		// NOTE: start and finish are in units of degrees * 64
13935 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
13936 			if(backgroundIsNotTransparent) {
13937 				swapColors();
13938 				XFillArc(display, d, gc, x1, y1, width, height, start, length);
13939 				swapColors();
13940 			}
13941 			if(foregroundIsNotTransparent) {
13942 				XDrawArc(display, d, gc, x1, y1, width, height, start, length);
13943 
13944 				// Windows draws the straight lines on the edges too so FIXME sort of
13945 			}
13946 		}
13947 
13948 		void drawPolygon(Point[] vertexes) {
13949 			XPoint[16] pointsBuffer;
13950 			XPoint[] points;
13951 			if(vertexes.length <= pointsBuffer.length)
13952 				points = pointsBuffer[0 .. vertexes.length];
13953 			else
13954 				points.length = vertexes.length;
13955 
13956 			foreach(i, p; vertexes) {
13957 				points[i].x = cast(short) p.x;
13958 				points[i].y = cast(short) p.y;
13959 			}
13960 
13961 			if(backgroundIsNotTransparent) {
13962 				swapColors();
13963 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13964 				swapColors();
13965 			}
13966 			if(foregroundIsNotTransparent) {
13967 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13968 			}
13969 		}
13970 	}
13971 
13972 	/* XRender { */
13973 
13974 	struct XRenderColor {
13975 		ushort red;
13976 		ushort green;
13977 		ushort blue;
13978 		ushort alpha;
13979 	}
13980 
13981 	alias Picture = XID;
13982 	alias PictFormat = XID;
13983 
13984 	struct XGlyphInfo {
13985 		ushort width;
13986 		ushort height;
13987 		short x;
13988 		short y;
13989 		short xOff;
13990 		short yOff;
13991 	}
13992 
13993 struct XRenderDirectFormat {
13994     short   red;
13995     short   redMask;
13996     short   green;
13997     short   greenMask;
13998     short   blue;
13999     short   blueMask;
14000     short   alpha;
14001     short   alphaMask;
14002 }
14003 
14004 struct XRenderPictFormat {
14005     PictFormat		id;
14006     int			type;
14007     int			depth;
14008     XRenderDirectFormat	direct;
14009     Colormap		colormap;
14010 }
14011 
14012 enum PictFormatID	=   (1 << 0);
14013 enum PictFormatType	=   (1 << 1);
14014 enum PictFormatDepth	=   (1 << 2);
14015 enum PictFormatRed	=   (1 << 3);
14016 enum PictFormatRedMask  =(1 << 4);
14017 enum PictFormatGreen	=   (1 << 5);
14018 enum PictFormatGreenMask=(1 << 6);
14019 enum PictFormatBlue	=   (1 << 7);
14020 enum PictFormatBlueMask =(1 << 8);
14021 enum PictFormatAlpha	=   (1 << 9);
14022 enum PictFormatAlphaMask=(1 << 10);
14023 enum PictFormatColormap =(1 << 11);
14024 
14025 struct XRenderPictureAttributes {
14026 	int 		repeat;
14027 	Picture		alpha_map;
14028 	int			alpha_x_origin;
14029 	int			alpha_y_origin;
14030 	int			clip_x_origin;
14031 	int			clip_y_origin;
14032 	Pixmap		clip_mask;
14033 	Bool		graphics_exposures;
14034 	int			subwindow_mode;
14035 	int			poly_edge;
14036 	int			poly_mode;
14037 	Atom		dither;
14038 	Bool		component_alpha;
14039 }
14040 
14041 alias int XFixed;
14042 
14043 struct XPointFixed {
14044     XFixed  x, y;
14045 }
14046 
14047 struct XCircle {
14048     XFixed x;
14049     XFixed y;
14050     XFixed radius;
14051 }
14052 
14053 struct XTransform {
14054     XFixed[3][3]  matrix;
14055 }
14056 
14057 struct XFilters {
14058     int	    nfilter;
14059     char    **filter;
14060     int	    nalias;
14061     short   *alias_;
14062 }
14063 
14064 struct XIndexValue {
14065     c_ulong    pixel;
14066     ushort   red, green, blue, alpha;
14067 }
14068 
14069 struct XAnimCursor {
14070     Cursor	    cursor;
14071     c_ulong   delay;
14072 }
14073 
14074 struct XLinearGradient {
14075     XPointFixed p1;
14076     XPointFixed p2;
14077 }
14078 
14079 struct XRadialGradient {
14080     XCircle inner;
14081     XCircle outer;
14082 }
14083 
14084 struct XConicalGradient {
14085     XPointFixed center;
14086     XFixed angle; /* in degrees */
14087 }
14088 
14089 enum PictStandardARGB32  = 0;
14090 enum PictStandardRGB24   = 1;
14091 enum PictStandardA8	 =  2;
14092 enum PictStandardA4	 =  3;
14093 enum PictStandardA1	 =  4;
14094 enum PictStandardNUM	 =  5;
14095 
14096 interface XRender {
14097 extern(C) @nogc:
14098 
14099 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
14100 
14101 	Status XRenderQueryVersion (Display *dpy,
14102 			int     *major_versionp,
14103 			int     *minor_versionp);
14104 
14105 	Status XRenderQueryFormats (Display *dpy);
14106 
14107 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
14108 
14109 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
14110 
14111 	XRenderPictFormat *
14112 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
14113 
14114 	XRenderPictFormat *
14115 		XRenderFindFormat (Display			*dpy,
14116 				c_ulong		mask,
14117 				const XRenderPictFormat	*templ,
14118 				int				count);
14119 	XRenderPictFormat *
14120 		XRenderFindStandardFormat (Display		*dpy,
14121 				int			format);
14122 
14123 	XIndexValue *
14124 		XRenderQueryPictIndexValues(Display			*dpy,
14125 				const XRenderPictFormat	*format,
14126 				int				*num);
14127 
14128 	Picture XRenderCreatePicture(
14129 		Display *dpy,
14130 		Drawable drawable,
14131 		const XRenderPictFormat *format,
14132 		c_ulong valuemask,
14133 		const XRenderPictureAttributes *attributes);
14134 
14135 	void XRenderChangePicture (Display				*dpy,
14136 				Picture				picture,
14137 				c_ulong			valuemask,
14138 				const XRenderPictureAttributes  *attributes);
14139 
14140 	void
14141 		XRenderSetPictureClipRectangles (Display	    *dpy,
14142 				Picture	    picture,
14143 				int		    xOrigin,
14144 				int		    yOrigin,
14145 				const XRectangle *rects,
14146 				int		    n);
14147 
14148 	void
14149 		XRenderSetPictureClipRegion (Display	    *dpy,
14150 				Picture	    picture,
14151 				Region	    r);
14152 
14153 	void
14154 		XRenderSetPictureTransform (Display	    *dpy,
14155 				Picture	    picture,
14156 				XTransform	    *transform);
14157 
14158 	void
14159 		XRenderFreePicture (Display                   *dpy,
14160 				Picture                   picture);
14161 
14162 	void
14163 		XRenderComposite (Display   *dpy,
14164 				int	    op,
14165 				Picture   src,
14166 				Picture   mask,
14167 				Picture   dst,
14168 				int	    src_x,
14169 				int	    src_y,
14170 				int	    mask_x,
14171 				int	    mask_y,
14172 				int	    dst_x,
14173 				int	    dst_y,
14174 				uint	width,
14175 				uint	height);
14176 
14177 
14178 	Picture XRenderCreateSolidFill (Display *dpy,
14179 			const XRenderColor *color);
14180 
14181 	Picture XRenderCreateLinearGradient (Display *dpy,
14182 			const XLinearGradient *gradient,
14183 			const XFixed *stops,
14184 			const XRenderColor *colors,
14185 			int nstops);
14186 
14187 	Picture XRenderCreateRadialGradient (Display *dpy,
14188 			const XRadialGradient *gradient,
14189 			const XFixed *stops,
14190 			const XRenderColor *colors,
14191 			int nstops);
14192 
14193 	Picture XRenderCreateConicalGradient (Display *dpy,
14194 			const XConicalGradient *gradient,
14195 			const XFixed *stops,
14196 			const XRenderColor *colors,
14197 			int nstops);
14198 
14199 
14200 
14201 	Cursor
14202 		XRenderCreateCursor (Display	    *dpy,
14203 				Picture	    source,
14204 				uint   x,
14205 				uint   y);
14206 
14207 	XFilters *
14208 		XRenderQueryFilters (Display *dpy, Drawable drawable);
14209 
14210 	void
14211 		XRenderSetPictureFilter (Display    *dpy,
14212 				Picture    picture,
14213 				const char *filter,
14214 				XFixed	    *params,
14215 				int	    nparams);
14216 
14217 	Cursor
14218 		XRenderCreateAnimCursor (Display	*dpy,
14219 				int		ncursor,
14220 				XAnimCursor	*cursors);
14221 }
14222 
14223 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
14224 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
14225 
14226 	/* XRender } */
14227 
14228 	/* Xrandr { */
14229 
14230 struct XRRMonitorInfo {
14231     Atom name;
14232     Bool primary;
14233     Bool automatic;
14234     int noutput;
14235     int x;
14236     int y;
14237     int width;
14238     int height;
14239     int mwidth;
14240     int mheight;
14241     /*RROutput*/ void *outputs;
14242 }
14243 
14244 struct XRRScreenChangeNotifyEvent {
14245     int type;                   /* event base */
14246     c_ulong serial;       /* # of last request processed by server */
14247     Bool send_event;            /* true if this came from a SendEvent request */
14248     Display *display;           /* Display the event was read from */
14249     Window window;              /* window which selected for this event */
14250     Window root;                /* Root window for changed screen */
14251     Time timestamp;             /* when the screen change occurred */
14252     Time config_timestamp;      /* when the last configuration change */
14253     ushort/*SizeID*/ size_index;
14254     ushort/*SubpixelOrder*/ subpixel_order;
14255     ushort/*Rotation*/ rotation;
14256     int width;
14257     int height;
14258     int mwidth;
14259     int mheight;
14260 }
14261 
14262 enum RRScreenChangeNotify = 0;
14263 
14264 enum RRScreenChangeNotifyMask = 1;
14265 
14266 __gshared int xrrEventBase = -1;
14267 
14268 
14269 interface XRandr {
14270 extern(C) @nogc:
14271 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
14272 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
14273 
14274 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
14275 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
14276 
14277 	void XRRSelectInput(Display *dpy, Window window, int mask);
14278 }
14279 
14280 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
14281 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
14282 	/* Xrandr } */
14283 
14284 	/* Xft { */
14285 
14286 	// actually freetype
14287 	alias void FT_Face;
14288 
14289 	// actually fontconfig
14290 	private alias FcBool = int;
14291 	alias void FcCharSet;
14292 	alias void FcPattern;
14293 	alias void FcResult;
14294 	enum FcEndian { FcEndianBig, FcEndianLittle }
14295 	struct FcFontSet {
14296 		int nfont;
14297 		int sfont;
14298 		FcPattern** fonts;
14299 	}
14300 
14301 	// actually XRegion
14302 	struct BOX {
14303 		short x1, x2, y1, y2;
14304 	}
14305 	struct _XRegion {
14306 		c_long size;
14307 		c_long numRects;
14308 		BOX* rects;
14309 		BOX extents;
14310 	}
14311 
14312 	alias Region = _XRegion*;
14313 
14314 	// ok actually Xft
14315 
14316 	struct XftFontInfo;
14317 
14318 	struct XftFont {
14319 		int         ascent;
14320 		int         descent;
14321 		int         height;
14322 		int         max_advance_width;
14323 		FcCharSet*  charset;
14324 		FcPattern*  pattern;
14325 	}
14326 
14327 	struct XftDraw;
14328 
14329 	struct XftColor {
14330 		c_ulong pixel;
14331 		XRenderColor color;
14332 	}
14333 
14334 	struct XftCharSpec {
14335 		dchar           ucs4;
14336 		short           x;
14337 		short           y;
14338 	}
14339 
14340 	struct XftCharFontSpec {
14341 		XftFont         *font;
14342 		dchar           ucs4;
14343 		short           x;
14344 		short           y;
14345 	}
14346 
14347 	struct XftGlyphSpec {
14348 		uint            glyph;
14349 		short           x;
14350 		short           y;
14351 	}
14352 
14353 	struct XftGlyphFontSpec {
14354 		XftFont         *font;
14355 		uint            glyph;
14356 		short           x;
14357 		short           y;
14358 	}
14359 
14360 	interface Xft {
14361 	extern(C) @nogc pure:
14362 
14363 	Bool XftColorAllocName (Display  *dpy,
14364 				const Visual   *visual,
14365 				Colormap cmap,
14366 				const char     *name,
14367 				XftColor *result);
14368 
14369 	Bool XftColorAllocValue (Display         *dpy,
14370 				Visual          *visual,
14371 				Colormap        cmap,
14372 				const XRenderColor    *color,
14373 				XftColor        *result);
14374 
14375 	void XftColorFree (Display   *dpy,
14376 				Visual    *visual,
14377 				Colormap  cmap,
14378 				XftColor  *color);
14379 
14380 	Bool XftDefaultHasRender (Display *dpy);
14381 
14382 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
14383 
14384 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
14385 
14386 	XftDraw * XftDrawCreate (Display   *dpy,
14387 		       Drawable  drawable,
14388 		       Visual    *visual,
14389 		       Colormap  colormap);
14390 
14391 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
14392 			     Pixmap   bitmap);
14393 
14394 	XftDraw * XftDrawCreateAlpha (Display *dpy,
14395 			    Pixmap  pixmap,
14396 			    int     depth);
14397 
14398 	void XftDrawChange (XftDraw  *draw,
14399 		       Drawable drawable);
14400 
14401 	Display * XftDrawDisplay (XftDraw *draw);
14402 
14403 	Drawable XftDrawDrawable (XftDraw *draw);
14404 
14405 	Colormap XftDrawColormap (XftDraw *draw);
14406 
14407 	Visual * XftDrawVisual (XftDraw *draw);
14408 
14409 	void XftDrawDestroy (XftDraw *draw);
14410 
14411 	Picture XftDrawPicture (XftDraw *draw);
14412 
14413 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
14414 
14415 	void XftDrawGlyphs (XftDraw          *draw,
14416 				const XftColor *color,
14417 				XftFont          *pub,
14418 				int              x,
14419 				int              y,
14420 				const uint  *glyphs,
14421 				int              nglyphs);
14422 
14423 	void XftDrawString8 (XftDraw             *draw,
14424 				const XftColor    *color,
14425 				XftFont             *pub,
14426 				int                 x,
14427 				int                 y,
14428 				const char     *string,
14429 				int                 len);
14430 
14431 	void XftDrawString16 (XftDraw            *draw,
14432 				const XftColor   *color,
14433 				XftFont            *pub,
14434 				int                x,
14435 				int                y,
14436 				const wchar   *string,
14437 				int                len);
14438 
14439 	void XftDrawString32 (XftDraw            *draw,
14440 				const XftColor   *color,
14441 				XftFont            *pub,
14442 				int                x,
14443 				int                y,
14444 				const dchar   *string,
14445 				int                len);
14446 
14447 	void XftDrawStringUtf8 (XftDraw          *draw,
14448 				const XftColor *color,
14449 				XftFont          *pub,
14450 				int              x,
14451 				int              y,
14452 				const char  *string,
14453 				int              len);
14454 	void XftDrawStringUtf16 (XftDraw             *draw,
14455 				const XftColor    *color,
14456 				XftFont             *pub,
14457 				int                 x,
14458 				int                 y,
14459 				const char     *string,
14460 				FcEndian            endian,
14461 				int                 len);
14462 
14463 	void XftDrawCharSpec (XftDraw                *draw,
14464 				const XftColor       *color,
14465 				XftFont                *pub,
14466 				const XftCharSpec    *chars,
14467 				int                    len);
14468 
14469 	void XftDrawCharFontSpec (XftDraw                    *draw,
14470 				const XftColor           *color,
14471 				const XftCharFontSpec    *chars,
14472 				int                        len);
14473 
14474 	void XftDrawGlyphSpec (XftDraw               *draw,
14475 				const XftColor      *color,
14476 				XftFont               *pub,
14477 				const XftGlyphSpec  *glyphs,
14478 				int                   len);
14479 
14480 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
14481 				const XftColor          *color,
14482 				const XftGlyphFontSpec  *glyphs,
14483 				int                       len);
14484 
14485 	void XftDrawRect (XftDraw            *draw,
14486 				const XftColor   *color,
14487 				int                x,
14488 				int                y,
14489 				uint       width,
14490 				uint       height);
14491 
14492 	Bool XftDrawSetClip (XftDraw     *draw,
14493 				Region      r);
14494 
14495 
14496 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
14497 				int                   xOrigin,
14498 				int                   yOrigin,
14499 				const XRectangle    *rects,
14500 				int                   n);
14501 
14502 	void XftDrawSetSubwindowMode (XftDraw    *draw,
14503 				int        mode);
14504 
14505 	void XftGlyphExtents (Display            *dpy,
14506 				XftFont            *pub,
14507 				const uint    *glyphs,
14508 				int                nglyphs,
14509 				XGlyphInfo         *extents);
14510 
14511 	void XftTextExtents8 (Display            *dpy,
14512 				XftFont            *pub,
14513 				const char    *string,
14514 				int                len,
14515 				XGlyphInfo         *extents);
14516 
14517 	void XftTextExtents16 (Display           *dpy,
14518 				XftFont           *pub,
14519 				const wchar  *string,
14520 				int               len,
14521 				XGlyphInfo        *extents);
14522 
14523 	void XftTextExtents32 (Display           *dpy,
14524 				XftFont           *pub,
14525 				const dchar  *string,
14526 				int               len,
14527 				XGlyphInfo        *extents);
14528 
14529 	void XftTextExtentsUtf8 (Display         *dpy,
14530 				XftFont         *pub,
14531 				const char *string,
14532 				int             len,
14533 				XGlyphInfo      *extents);
14534 
14535 	void XftTextExtentsUtf16 (Display            *dpy,
14536 				XftFont            *pub,
14537 				const char    *string,
14538 				FcEndian           endian,
14539 				int                len,
14540 				XGlyphInfo         *extents);
14541 
14542 	FcPattern * XftFontMatch (Display           *dpy,
14543 				int               screen,
14544 				const FcPattern *pattern,
14545 				FcResult          *result);
14546 
14547 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
14548 
14549 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
14550 
14551 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
14552 
14553 	FT_Face XftLockFace (XftFont *pub);
14554 
14555 	void XftUnlockFace (XftFont *pub);
14556 
14557 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
14558 
14559 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
14560 
14561 	dchar XftFontInfoHash (const XftFontInfo *fi);
14562 
14563 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
14564 
14565 	XftFont * XftFontOpenInfo (Display        *dpy,
14566 				FcPattern      *pattern,
14567 				XftFontInfo    *fi);
14568 
14569 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
14570 
14571 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
14572 
14573 	void XftFontClose (Display *dpy, XftFont *pub);
14574 
14575 	FcBool XftInitFtLibrary();
14576 	void XftFontLoadGlyphs (Display          *dpy,
14577 				XftFont          *pub,
14578 				FcBool           need_bitmaps,
14579 				const uint  *glyphs,
14580 				int              nglyph);
14581 
14582 	void XftFontUnloadGlyphs (Display            *dpy,
14583 				XftFont            *pub,
14584 				const uint    *glyphs,
14585 				int                nglyph);
14586 
14587 	FcBool XftFontCheckGlyph (Display  *dpy,
14588 				XftFont  *pub,
14589 				FcBool   need_bitmaps,
14590 				uint  glyph,
14591 				uint  *missing,
14592 				int      *nmissing);
14593 
14594 	FcBool XftCharExists (Display      *dpy,
14595 				XftFont      *pub,
14596 				dchar    ucs4);
14597 
14598 	uint XftCharIndex (Display       *dpy,
14599 				XftFont       *pub,
14600 				dchar      ucs4);
14601 	FcBool XftInit (const char *config);
14602 
14603 	int XftGetVersion ();
14604 
14605 	FcFontSet * XftListFonts (Display   *dpy,
14606 				int       screen,
14607 				...);
14608 
14609 	FcPattern *XftNameParse (const char *name);
14610 
14611 	void XftGlyphRender (Display         *dpy,
14612 				int             op,
14613 				Picture         src,
14614 				XftFont         *pub,
14615 				Picture         dst,
14616 				int             srcx,
14617 				int             srcy,
14618 				int             x,
14619 				int             y,
14620 				const uint *glyphs,
14621 				int             nglyphs);
14622 
14623 	void XftGlyphSpecRender (Display                 *dpy,
14624 				int                     op,
14625 				Picture                 src,
14626 				XftFont                 *pub,
14627 				Picture                 dst,
14628 				int                     srcx,
14629 				int                     srcy,
14630 				const XftGlyphSpec    *glyphs,
14631 				int                     nglyphs);
14632 
14633 	void XftCharSpecRender (Display              *dpy,
14634 				int                  op,
14635 				Picture              src,
14636 				XftFont              *pub,
14637 				Picture              dst,
14638 				int                  srcx,
14639 				int                  srcy,
14640 				const XftCharSpec  *chars,
14641 				int                  len);
14642 	void XftGlyphFontSpecRender (Display                     *dpy,
14643 				int                         op,
14644 				Picture                     src,
14645 				Picture                     dst,
14646 				int                         srcx,
14647 				int                         srcy,
14648 				const XftGlyphFontSpec    *glyphs,
14649 				int                         nglyphs);
14650 
14651 	void XftCharFontSpecRender (Display                  *dpy,
14652 				int                      op,
14653 				Picture                  src,
14654 				Picture                  dst,
14655 				int                      srcx,
14656 				int                      srcy,
14657 				const XftCharFontSpec  *chars,
14658 				int                      len);
14659 
14660 	void XftTextRender8 (Display         *dpy,
14661 				int             op,
14662 				Picture         src,
14663 				XftFont         *pub,
14664 				Picture         dst,
14665 				int             srcx,
14666 				int             srcy,
14667 				int             x,
14668 				int             y,
14669 				const char *string,
14670 				int             len);
14671 	void XftTextRender16 (Display            *dpy,
14672 				int                op,
14673 				Picture            src,
14674 				XftFont            *pub,
14675 				Picture            dst,
14676 				int                srcx,
14677 				int                srcy,
14678 				int                x,
14679 				int                y,
14680 				const wchar   *string,
14681 				int                len);
14682 
14683 	void XftTextRender16BE (Display          *dpy,
14684 				int              op,
14685 				Picture          src,
14686 				XftFont          *pub,
14687 				Picture          dst,
14688 				int              srcx,
14689 				int              srcy,
14690 				int              x,
14691 				int              y,
14692 				const char  *string,
14693 				int              len);
14694 
14695 	void XftTextRender16LE (Display          *dpy,
14696 				int              op,
14697 				Picture          src,
14698 				XftFont          *pub,
14699 				Picture          dst,
14700 				int              srcx,
14701 				int              srcy,
14702 				int              x,
14703 				int              y,
14704 				const char  *string,
14705 				int              len);
14706 
14707 	void XftTextRender32 (Display            *dpy,
14708 				int                op,
14709 				Picture            src,
14710 				XftFont            *pub,
14711 				Picture            dst,
14712 				int                srcx,
14713 				int                srcy,
14714 				int                x,
14715 				int                y,
14716 				const dchar   *string,
14717 				int                len);
14718 
14719 	void XftTextRender32BE (Display          *dpy,
14720 				int              op,
14721 				Picture          src,
14722 				XftFont          *pub,
14723 				Picture          dst,
14724 				int              srcx,
14725 				int              srcy,
14726 				int              x,
14727 				int              y,
14728 				const char  *string,
14729 				int              len);
14730 
14731 	void XftTextRender32LE (Display          *dpy,
14732 				int              op,
14733 				Picture          src,
14734 				XftFont          *pub,
14735 				Picture          dst,
14736 				int              srcx,
14737 				int              srcy,
14738 				int              x,
14739 				int              y,
14740 				const char  *string,
14741 				int              len);
14742 
14743 	void XftTextRenderUtf8 (Display          *dpy,
14744 				int              op,
14745 				Picture          src,
14746 				XftFont          *pub,
14747 				Picture          dst,
14748 				int              srcx,
14749 				int              srcy,
14750 				int              x,
14751 				int              y,
14752 				const char  *string,
14753 				int              len);
14754 
14755 	void XftTextRenderUtf16 (Display         *dpy,
14756 				int             op,
14757 				Picture         src,
14758 				XftFont         *pub,
14759 				Picture         dst,
14760 				int             srcx,
14761 				int             srcy,
14762 				int             x,
14763 				int             y,
14764 				const char *string,
14765 				FcEndian        endian,
14766 				int             len);
14767 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14768 
14769 	}
14770 
14771 	interface FontConfig {
14772 	extern(C) @nogc pure:
14773 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14774 		void FcFontSetDestroy(FcFontSet*);
14775 		char* FcNameUnparse(const FcPattern *);
14776 	}
14777 
14778 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14779 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14780 
14781 
14782 	/* Xft } */
14783 
14784 	class XDisconnectException : Exception {
14785 		bool userRequested;
14786 		this(bool userRequested = true) {
14787 			this.userRequested = userRequested;
14788 			super("X disconnected");
14789 		}
14790 	}
14791 
14792 	/++
14793 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14794 
14795 		Please note that it returns
14796 	+/
14797 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14798 
14799 		static XErrorEvent[] errorBuffer;
14800 
14801 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14802 			errorBuffer ~= *evt;
14803 			return 0;
14804 		}
14805 
14806 		auto savedErrorHandler = XSetErrorHandler(&handler);
14807 
14808 		try {
14809 			dg();
14810 		} finally {
14811 			XSync(XDisplayConnection.get, 0/*False*/);
14812 			XSetErrorHandler(savedErrorHandler);
14813 		}
14814 
14815 		auto bfr = errorBuffer;
14816 		errorBuffer = null;
14817 
14818 		return bfr;
14819 	}
14820 
14821 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14822 	class XDisplayConnection {
14823 		private __gshared Display* display;
14824 		private __gshared XIM xim;
14825 		private __gshared char* displayName;
14826 
14827 		private __gshared int connectionSequence_;
14828 		private __gshared bool isLocal_;
14829 
14830 		/// use this for lazy caching when reconnection
14831 		static int connectionSequenceNumber() { return connectionSequence_; }
14832 
14833 		/++
14834 			Guesses if the connection appears to be local.
14835 
14836 			History:
14837 				Added June 3, 2021
14838 		+/
14839 		static @property bool isLocal() nothrow @trusted @nogc {
14840 			return isLocal_;
14841 		}
14842 
14843 		/// Attempts recreation of state, may require application assistance
14844 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14845 		/// then call this, and if successful, reenter the loop.
14846 		static void discardAndRecreate(string newDisplayString = null) {
14847 			if(insideXEventLoop)
14848 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14849 
14850 			// 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
14851 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14852 
14853 			foreach(handle; chnenhm) {
14854 				handle.discardConnectionState();
14855 			}
14856 
14857 			discardState();
14858 
14859 			if(newDisplayString !is null)
14860 				setDisplayName(newDisplayString);
14861 
14862 			auto display = get();
14863 
14864 			foreach(handle; chnenhm) {
14865 				handle.recreateAfterDisconnect();
14866 			}
14867 		}
14868 
14869 		private __gshared EventMask rootEventMask;
14870 
14871 		/++
14872 			Requests the specified input from the root window on the connection, in addition to any other request.
14873 
14874 
14875 			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.
14876 
14877 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14878 		+/
14879 		static void addRootInput(EventMask mask) {
14880 			auto old = rootEventMask;
14881 			rootEventMask |= mask;
14882 			get(); // to ensure display connected
14883 			if(display !is null && rootEventMask != old)
14884 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14885 		}
14886 
14887 		static void discardState() {
14888 			freeImages();
14889 
14890 			foreach(atomPtr; interredAtoms)
14891 				*atomPtr = 0;
14892 			interredAtoms = null;
14893 			interredAtoms.assumeSafeAppend();
14894 
14895 			ScreenPainterImplementation.fontAttempted = false;
14896 			ScreenPainterImplementation.defaultfont = null;
14897 			ScreenPainterImplementation.defaultfontset = null;
14898 
14899 			Image.impl.xshmQueryCompleted = false;
14900 			Image.impl._xshmAvailable = false;
14901 
14902 			SimpleWindow.nativeMapping = null;
14903 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14904 			// GlobalHotkeyManager
14905 
14906 			display = null;
14907 			xim = null;
14908 		}
14909 
14910 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14911 		private static void createXIM () {
14912 			import core.stdc.locale : setlocale, LC_ALL;
14913 			import core.stdc.stdio : stderr, fprintf;
14914 			import core.stdc.stdlib : free;
14915 			import core.stdc.string : strdup;
14916 
14917 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14918 
14919 			auto olocale = strdup(setlocale(LC_ALL, null));
14920 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14921 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14922 
14923 			//fprintf(stderr, "opening IM...\n");
14924 			foreach (string s; mtry) {
14925 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14926 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14927 			}
14928 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14929 		}
14930 
14931 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14932 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14933 		static struct ImgList {
14934 			size_t img; // class; hide it from GC
14935 			ImgList* next;
14936 		}
14937 
14938 		static __gshared ImgList* imglist = null;
14939 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14940 
14941 		static void registerImage (Image img) {
14942 			if (!imglistLocked && img !is null) {
14943 				import core.stdc.stdlib : malloc;
14944 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14945 				assert(it !is null); // do proper checks
14946 				it.img = cast(size_t)cast(void*)img;
14947 				it.next = imglist;
14948 				imglist = it;
14949 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14950 			}
14951 		}
14952 
14953 		static void unregisterImage (Image img) {
14954 			if (!imglistLocked && img !is null) {
14955 				import core.stdc.stdlib : free;
14956 				ImgList* prev = null;
14957 				ImgList* cur = imglist;
14958 				while (cur !is null) {
14959 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14960 					prev = cur;
14961 					cur = cur.next;
14962 				}
14963 				if (cur !is null) {
14964 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14965 					free(cur);
14966 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14967 				} else {
14968 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14969 				}
14970 			}
14971 		}
14972 
14973 		static void freeImages () { // needed for discardAndRecreate
14974 			imglistLocked = true;
14975 			scope(exit) imglistLocked = false;
14976 			ImgList* cur = imglist;
14977 			ImgList* next = null;
14978 			while (cur !is null) {
14979 				import core.stdc.stdlib : free;
14980 				next = cur.next;
14981 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14982 				(cast(Image)cast(void*)cur.img).dispose();
14983 				free(cur);
14984 				cur = next;
14985 			}
14986 			imglist = null;
14987 		}
14988 
14989 		/// can be used to override normal handling of display name
14990 		/// from environment and/or command line
14991 		static setDisplayName(string newDisplayName) {
14992 			displayName = cast(char*) (newDisplayName ~ '\0');
14993 		}
14994 
14995 		/// resets to the default display string
14996 		static resetDisplayName() {
14997 			displayName = null;
14998 		}
14999 
15000 		///
15001 		static Display* get() {
15002 			if(display is null) {
15003 				if(!librariesSuccessfullyLoaded)
15004 					throw new Exception("Unable to load X11 client libraries");
15005 				display = XOpenDisplay(displayName);
15006 
15007 				isLocal_ = false;
15008 
15009 				connectionSequence_++;
15010 				if(display is null)
15011 					throw new Exception("Unable to open X display");
15012 
15013 				auto str = display.display_name;
15014 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
15015 				// and otherwise it probably isn't
15016 				if(str is null || (str[0] != ':' && str[0] != '/'))
15017 					isLocal_ = false;
15018 				else
15019 					isLocal_ = true;
15020 
15021 				XSetErrorHandler(&adrlogger);
15022 
15023 				debug(sdpy_x_errors) {
15024 					XSynchronize(display, true);
15025 
15026 					extern(C) int wtf() {
15027 						if(errorHappened) {
15028 							asm { int 3; }
15029 							errorHappened = false;
15030 						}
15031 						return 0;
15032 					}
15033 					XSetAfterFunction(display, &wtf);
15034 				}
15035 
15036 
15037 				XSetIOErrorHandler(&x11ioerrCB);
15038 				Bool sup;
15039 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
15040 				createXIM();
15041 				version(with_eventloop) {
15042 					import arsd.eventloop;
15043 					addFileEventListeners(display.fd, &eventListener, null, null);
15044 				}
15045 			}
15046 
15047 			return display;
15048 		}
15049 
15050 		extern(C)
15051 		static int x11ioerrCB(Display* dpy) {
15052 			throw new XDisconnectException(false);
15053 		}
15054 
15055 		version(with_eventloop) {
15056 			import arsd.eventloop;
15057 			static void eventListener(OsFileHandle fd) {
15058 				//this.mtLock();
15059 				//scope(exit) this.mtUnlock();
15060 				while(XPending(display))
15061 					doXNextEvent(display);
15062 			}
15063 		}
15064 
15065 		// close connection on program exit -- we need this to properly free all images
15066 		static ~this () {
15067 			// the gui thread must clean up after itself or else Xlib might deadlock
15068 			// using this flag on any thread destruction is the easiest way i know of
15069 			// (shared static this is run by the LAST thread to exit, which may not be
15070 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
15071 			if(thisIsGuiThread)
15072 				close();
15073 		}
15074 
15075 		///
15076 		static void close() {
15077 			if(display is null)
15078 				return;
15079 
15080 			version(with_eventloop) {
15081 				import arsd.eventloop;
15082 				removeFileEventListeners(display.fd);
15083 			}
15084 
15085 			// now remove all registered images to prevent shared memory leaks
15086 			freeImages();
15087 
15088 			// tbh I don't know why it is doing this but like if this happens to run
15089 			// from the other thread there's frequent hanging inside here.
15090 			if(thisIsGuiThread)
15091 				XCloseDisplay(display);
15092 			display = null;
15093 		}
15094 	}
15095 
15096 	mixin template NativeImageImplementation() {
15097 		XImage* handle;
15098 		ubyte* rawData;
15099 
15100 		XShmSegmentInfo shminfo;
15101 		bool premultiply = true;
15102 
15103 		__gshared bool xshmQueryCompleted;
15104 		__gshared bool _xshmAvailable;
15105 		public static @property bool xshmAvailable() {
15106 			if(!xshmQueryCompleted) {
15107 				int i1, i2, i3;
15108 				xshmQueryCompleted = true;
15109 
15110 				if(!XDisplayConnection.isLocal)
15111 					_xshmAvailable = false;
15112 				else
15113 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
15114 			}
15115 			return _xshmAvailable;
15116 		}
15117 
15118 		bool usingXshm;
15119 	final:
15120 
15121 		private __gshared bool xshmfailed;
15122 
15123 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
15124 			auto display = XDisplayConnection.get();
15125 			assert(display !is null);
15126 			auto screen = DefaultScreen(display);
15127 
15128 			// it will only use shared memory for somewhat largish images,
15129 			// since otherwise we risk wasting shared memory handles on a lot of little ones
15130 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
15131 
15132 
15133 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
15134 				// the actual use still fails. For example, if the program is in a container and permission denied
15135 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
15136 				//
15137 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
15138 
15139 
15140 				// synchronize so preexisting buffers are clear
15141 				XSync(display, false);
15142 				xshmfailed = false;
15143 
15144 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
15145 
15146 
15147 				usingXshm = true;
15148 				handle = XShmCreateImage(
15149 					display,
15150 					DefaultVisual(display, screen),
15151 					enableAlpha ? 32: 24,
15152 					ImageFormat.ZPixmap,
15153 					null,
15154 					&shminfo,
15155 					width, height);
15156 				if(handle is null)
15157 					goto abortXshm1;
15158 
15159 				if(handle.bytes_per_line != 4 * width)
15160 					goto abortXshm2;
15161 
15162 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
15163 				if(shminfo.shmid < 0)
15164 					goto abortXshm3;
15165 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
15166 				if(rawData == cast(ubyte*) -1)
15167 					goto abortXshm4;
15168 				shminfo.readOnly = 0;
15169 				XShmAttach(display, &shminfo);
15170 
15171 				// and now to the final error check to ensure it actually worked.
15172 				XSync(display, false);
15173 				if(xshmfailed)
15174 					goto abortXshm5;
15175 
15176 				XSetErrorHandler(oldErrorHandler);
15177 
15178 				XDisplayConnection.registerImage(this);
15179 				// if I don't flush here there's a chance the dtor will run before the
15180 				// ctor and lead to a bad value X error. While this hurts the efficiency
15181 				// it is local anyway so prolly better to keep it simple
15182 				XFlush(display);
15183 
15184 				return;
15185 
15186 				abortXshm5:
15187 					shmdt(shminfo.shmaddr);
15188 					rawData = null;
15189 
15190 				abortXshm4:
15191 					shmctl(shminfo.shmid, IPC_RMID, null);
15192 
15193 				abortXshm3:
15194 					// nothing needed, the shmget failed so there's nothing to free
15195 
15196 				abortXshm2:
15197 					XDestroyImage(handle);
15198 					handle = null;
15199 
15200 				abortXshm1:
15201 					XSetErrorHandler(oldErrorHandler);
15202 					usingXshm = false;
15203 					handle = null;
15204 
15205 					shminfo = typeof(shminfo).init;
15206 
15207 					_xshmAvailable = false; // don't try again in the future
15208 
15209 					// writeln("fallingback");
15210 
15211 					goto fallback;
15212 
15213 			} else {
15214 				fallback:
15215 
15216 				if (forcexshm) throw new Exception("can't create XShm Image");
15217 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
15218 				import core.stdc.stdlib : malloc;
15219 				rawData = cast(ubyte*) malloc(width * height * 4);
15220 
15221 				handle = XCreateImage(
15222 					display,
15223 					DefaultVisual(display, screen),
15224 					enableAlpha ? 32 : 24, // bpp
15225 					ImageFormat.ZPixmap,
15226 					0, // offset
15227 					rawData,
15228 					width, height,
15229 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
15230 			}
15231 		}
15232 
15233 		void dispose() {
15234 			// note: this calls free(rawData) for us
15235 			if(handle) {
15236 				if (usingXshm) {
15237 					XDisplayConnection.unregisterImage(this);
15238 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
15239 				}
15240 				XDestroyImage(handle);
15241 				if(usingXshm) {
15242 					shmdt(shminfo.shmaddr);
15243 					shmctl(shminfo.shmid, IPC_RMID, null);
15244 				}
15245 				handle = null;
15246 			}
15247 		}
15248 
15249 		Color getPixel(int x, int y) @system {
15250 			auto offset = (y * width + x) * 4;
15251 			Color c;
15252 			c.a = enableAlpha ? rawData[offset + 3] : 255;
15253 			c.b = rawData[offset + 0];
15254 			c.g = rawData[offset + 1];
15255 			c.r = rawData[offset + 2];
15256 			if(enableAlpha && premultiply)
15257 				c.unPremultiply;
15258 			return c;
15259 		}
15260 
15261 		void setPixel(int x, int y, Color c) @system {
15262 			if(enableAlpha && premultiply)
15263 				c.premultiply();
15264 			auto offset = (y * width + x) * 4;
15265 			rawData[offset + 0] = c.b;
15266 			rawData[offset + 1] = c.g;
15267 			rawData[offset + 2] = c.r;
15268 			if(enableAlpha)
15269 				rawData[offset + 3] = c.a;
15270 		}
15271 
15272 		void convertToRgbaBytes(ubyte[] where) @system {
15273 			assert(where.length == this.width * this.height * 4);
15274 
15275 			// if rawData had a length....
15276 			//assert(rawData.length == where.length);
15277 			for(int idx = 0; idx < where.length; idx += 4) {
15278 				where[idx + 0] = rawData[idx + 2]; // r
15279 				where[idx + 1] = rawData[idx + 1]; // g
15280 				where[idx + 2] = rawData[idx + 0]; // b
15281 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
15282 
15283 				if(enableAlpha && premultiply)
15284 					unPremultiplyRgba(where[idx .. idx + 4]);
15285 			}
15286 		}
15287 
15288 		void setFromRgbaBytes(in ubyte[] where) @system {
15289 			assert(where.length == this.width * this.height * 4);
15290 
15291 			// if rawData had a length....
15292 			//assert(rawData.length == where.length);
15293 			for(int idx = 0; idx < where.length; idx += 4) {
15294 				rawData[idx + 2] = where[idx + 0]; // r
15295 				rawData[idx + 1] = where[idx + 1]; // g
15296 				rawData[idx + 0] = where[idx + 2]; // b
15297 				if(enableAlpha) {
15298 					rawData[idx + 3] = where[idx + 3]; // a
15299 					if(premultiply)
15300 						premultiplyBgra(rawData[idx .. idx + 4]);
15301 				}
15302 			}
15303 		}
15304 
15305 	}
15306 
15307 	mixin template NativeSimpleWindowImplementation() {
15308 		GC gc;
15309 		Window window;
15310 		Display* display;
15311 
15312 		Pixmap buffer;
15313 		int bufferw, bufferh; // size of the buffer; can be bigger than window
15314 		XIC xic; // input context
15315 		int curHidden = 0; // counter
15316 		Cursor blankCurPtr = 0;
15317 		int cursorSequenceNumber = 0;
15318 		int warpEventCount = 0; // number of mouse movement events to eat
15319 
15320 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS
15321 		X11GetSelectionHandler[Atom] getSelectionHandlers;
15322 
15323 		version(without_opengl) {} else
15324 		GLXContext glc;
15325 
15326 		private void fixFixedSize(bool forced=false) (int width, int height) {
15327 			if (forced || this.resizability == Resizability.fixedSize) {
15328 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
15329 				XSizeHints sh;
15330 				static if (!forced) {
15331 					c_long spr;
15332 					XGetWMNormalHints(display, window, &sh, &spr);
15333 					sh.flags |= PMaxSize | PMinSize;
15334 				} else {
15335 					sh.flags = PMaxSize | PMinSize;
15336 				}
15337 				sh.min_width = width;
15338 				sh.min_height = height;
15339 				sh.max_width = width;
15340 				sh.max_height = height;
15341 				XSetWMNormalHints(display, window, &sh);
15342 				//XFlush(display);
15343 			}
15344 		}
15345 
15346 		ScreenPainter getPainter(bool manualInvalidations) {
15347 			return ScreenPainter(this, window, manualInvalidations);
15348 		}
15349 
15350 		void move(int x, int y) {
15351 			XMoveWindow(display, window, x, y);
15352 		}
15353 
15354 		void resize(int w, int h) {
15355 			if (w < 1) w = 1;
15356 			if (h < 1) h = 1;
15357 			XResizeWindow(display, window, w, h);
15358 
15359 			// calling this now to avoid waiting for the server to
15360 			// acknowledge the resize; draws without returning to the
15361 			// event loop will thus actually work. the server's event
15362 			// btw might overrule this and resize it again
15363 			recordX11Resize(display, this, w, h);
15364 
15365 			updateOpenglViewportIfNeeded(w, h);
15366 		}
15367 
15368 		void moveResize (int x, int y, int w, int h) {
15369 			if (w < 1) w = 1;
15370 			if (h < 1) h = 1;
15371 			XMoveResizeWindow(display, window, x, y, w, h);
15372 			updateOpenglViewportIfNeeded(w, h);
15373 		}
15374 
15375 		void hideCursor () {
15376 			if (curHidden++ == 0) {
15377 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
15378 					static const(char)[1] cmbmp = 0;
15379 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
15380 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
15381 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
15382 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
15383 					XFreePixmap(display, pm);
15384 				}
15385 				XDefineCursor(display, window, blankCurPtr);
15386 			}
15387 		}
15388 
15389 		void showCursor () {
15390 			if (--curHidden == 0) XUndefineCursor(display, window);
15391 		}
15392 
15393 		void warpMouse (int x, int y) {
15394 			// here i will send dummy "ignore next mouse motion" event,
15395 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
15396 			// and we don't need to report it to the user (as warping is
15397 			// used when the user needs movement deltas).
15398 			//XClientMessageEvent xclient;
15399 			XEvent e;
15400 			e.xclient.type = EventType.ClientMessage;
15401 			e.xclient.window = window;
15402 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15403 			e.xclient.format = 32;
15404 			e.xclient.data.l[0] = 0;
15405 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
15406 			//{ 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]); }
15407 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15408 			// now warp pointer...
15409 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
15410 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
15411 			// ...and flush
15412 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
15413 			XFlush(display);
15414 		}
15415 
15416 		void sendDummyEvent () {
15417 			// here i will send dummy event to ping event queue
15418 			XEvent e;
15419 			e.xclient.type = EventType.ClientMessage;
15420 			e.xclient.window = window;
15421 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15422 			e.xclient.format = 32;
15423 			e.xclient.data.l[0] = 0;
15424 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15425 			XFlush(display);
15426 		}
15427 
15428 		void setTitle(string title) {
15429 			if (title.ptr is null) title = "";
15430 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15431 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15432 			XTextProperty windowName;
15433 			windowName.value = title.ptr;
15434 			windowName.encoding = XA_UTF8; //XA_STRING;
15435 			windowName.format = 8;
15436 			windowName.nitems = cast(uint)title.length;
15437 			XSetWMName(display, window, &windowName);
15438 			char[1024] namebuf = 0;
15439 			auto maxlen = namebuf.length-1;
15440 			if (maxlen > title.length) maxlen = title.length;
15441 			namebuf[0..maxlen] = title[0..maxlen];
15442 			XStoreName(display, window, namebuf.ptr);
15443 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
15444 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
15445 		}
15446 
15447 		string[] getTitles() {
15448 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15449 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15450 			XTextProperty textProp;
15451 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
15452 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
15453 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
15454 				} else
15455 					return [];
15456 			} else
15457 				return null;
15458 		}
15459 
15460 		string getTitle() {
15461 			auto titles = getTitles();
15462 			return titles.length ? titles[0] : null;
15463 		}
15464 
15465 		void setMinSize (int minwidth, int minheight) {
15466 			import core.stdc.config : c_long;
15467 			if (minwidth < 1) minwidth = 1;
15468 			if (minheight < 1) minheight = 1;
15469 			XSizeHints sh;
15470 			c_long spr;
15471 			XGetWMNormalHints(display, window, &sh, &spr);
15472 			sh.min_width = minwidth;
15473 			sh.min_height = minheight;
15474 			sh.flags |= PMinSize;
15475 			XSetWMNormalHints(display, window, &sh);
15476 			flushGui();
15477 		}
15478 
15479 		void setMaxSize (int maxwidth, int maxheight) {
15480 			import core.stdc.config : c_long;
15481 			if (maxwidth < 1) maxwidth = 1;
15482 			if (maxheight < 1) maxheight = 1;
15483 			XSizeHints sh;
15484 			c_long spr;
15485 			XGetWMNormalHints(display, window, &sh, &spr);
15486 			sh.max_width = maxwidth;
15487 			sh.max_height = maxheight;
15488 			sh.flags |= PMaxSize;
15489 			XSetWMNormalHints(display, window, &sh);
15490 			flushGui();
15491 		}
15492 
15493 		void setResizeGranularity (int granx, int grany) {
15494 			import core.stdc.config : c_long;
15495 			if (granx < 1) granx = 1;
15496 			if (grany < 1) grany = 1;
15497 			XSizeHints sh;
15498 			c_long spr;
15499 			XGetWMNormalHints(display, window, &sh, &spr);
15500 			sh.width_inc = granx;
15501 			sh.height_inc = grany;
15502 			sh.flags |= PResizeInc;
15503 			XSetWMNormalHints(display, window, &sh);
15504 			flushGui();
15505 		}
15506 
15507 		void setOpacity (uint opacity) {
15508 			arch_ulong o = opacity;
15509 			if (opacity == uint.max)
15510 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
15511 			else
15512 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
15513 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
15514 		}
15515 
15516 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted {
15517 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
15518 			display = XDisplayConnection.get();
15519 			auto screen = DefaultScreen(display);
15520 
15521 			bool overrideRedirect = false;
15522 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
15523 				overrideRedirect = true;
15524 
15525 			version(without_opengl) {}
15526 			else {
15527 				if(opengl == OpenGlOptions.yes) {
15528 					GLXFBConfig fbconf = null;
15529 					XVisualInfo* vi = null;
15530 					bool useLegacy = false;
15531 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
15532 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
15533 						int[23] visualAttribs = [
15534 							GLX_X_RENDERABLE , 1/*True*/,
15535 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
15536 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
15537 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
15538 							GLX_RED_SIZE     , 8,
15539 							GLX_GREEN_SIZE   , 8,
15540 							GLX_BLUE_SIZE    , 8,
15541 							GLX_ALPHA_SIZE   , 8,
15542 							GLX_DEPTH_SIZE   , 24,
15543 							GLX_STENCIL_SIZE , 8,
15544 							GLX_DOUBLEBUFFER , 1/*True*/,
15545 							0/*None*/,
15546 						];
15547 						int fbcount;
15548 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
15549 						if (fbcount == 0) {
15550 							useLegacy = true; // try to do at least something
15551 						} else {
15552 							// pick the FB config/visual with the most samples per pixel
15553 							int bestidx = -1, bestns = -1;
15554 							foreach (int fbi; 0..fbcount) {
15555 								int sb, samples;
15556 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
15557 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
15558 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
15559 							}
15560 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
15561 							fbconf = fbc[bestidx];
15562 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
15563 							XFree(fbc);
15564 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
15565 						}
15566 					}
15567 					if (vi is null || useLegacy) {
15568 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
15569 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
15570 						useLegacy = true;
15571 					}
15572 					if (vi is null) throw new Exception("no open gl visual found");
15573 
15574 					XSetWindowAttributes swa;
15575 					auto root = RootWindow(display, screen);
15576 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
15577 
15578 					swa.override_redirect = overrideRedirect;
15579 
15580 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15581 						0, 0, width, height,
15582 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
15583 
15584 					// now try to use `glXCreateContextAttribsARB()` if it's here
15585 					if (!useLegacy) {
15586 						// request fairly advanced context, even with stencil buffer!
15587 						int[9] contextAttribs = [
15588 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
15589 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
15590 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
15591 							// for modern context, set "forward compatibility" flag too
15592 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
15593 							0/*None*/,
15594 						];
15595 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
15596 						if (glc is null && sdpyOpenGLContextAllowFallback) {
15597 							sdpyOpenGLContextVersion = 0;
15598 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15599 						}
15600 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
15601 					} else {
15602 						// fallback to old GLX call
15603 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
15604 							sdpyOpenGLContextVersion = 0;
15605 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15606 						}
15607 					}
15608 					// sync to ensure any errors generated are processed
15609 					XSync(display, 0/*False*/);
15610 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
15611 					if(glc is null)
15612 						throw new Exception("glc");
15613 				}
15614 			}
15615 
15616 			if(opengl == OpenGlOptions.no) {
15617 
15618 				XSetWindowAttributes swa;
15619 				swa.background_pixel = WhitePixel(display, screen);
15620 				swa.border_pixel = BlackPixel(display, screen);
15621 				swa.override_redirect = overrideRedirect;
15622 				auto root = RootWindow(display, screen);
15623 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
15624 
15625 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15626 					0, 0, width, height,
15627 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
15628 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
15629 
15630 
15631 
15632 				/*
15633 				window = XCreateSimpleWindow(
15634 					display,
15635 					parent is null ? RootWindow(display, screen) : parent.impl.window,
15636 					0, 0, // x, y
15637 					width, height,
15638 					1, // border width
15639 					BlackPixel(display, screen), // border
15640 					WhitePixel(display, screen)); // background
15641 				*/
15642 
15643 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
15644 				bufferw = width;
15645 				bufferh = height;
15646 
15647 				gc = DefaultGC(display, screen);
15648 
15649 				// clear out the buffer to get us started...
15650 				XSetForeground(display, gc, WhitePixel(display, screen));
15651 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
15652 				XSetForeground(display, gc, BlackPixel(display, screen));
15653 			}
15654 
15655 			// input context
15656 			//TODO: create this only for top-level windows, and reuse that?
15657 			populateXic();
15658 
15659 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
15660 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
15661 			// window class
15662 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
15663 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
15664 				XClassHint klass;
15665 				XWMHints wh;
15666 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15667 					wh.input = true;
15668 					wh.flags |= InputHint;
15669 				}
15670 				XSizeHints size;
15671 				klass.res_name = sdpyWindowClassStr;
15672 				klass.res_class = sdpyWindowClassStr;
15673 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
15674 			}
15675 
15676 			setTitle(title);
15677 			SimpleWindow.nativeMapping[window] = this;
15678 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
15679 
15680 			// This gives our window a close button
15681 			if (windowType != WindowTypes.eventOnly) {
15682 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
15683 				int useAtoms;
15684 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15685 					useAtoms = 2;
15686 				} else {
15687 					useAtoms = 1;
15688 				}
15689 				assert(useAtoms <= atoms.length);
15690 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
15691 			}
15692 
15693 			// FIXME: windowType and customizationFlags
15694 			Atom[8] wsatoms; // here, due to goto
15695 			int wmsacount = 0; // here, due to goto
15696 
15697 			try
15698 			final switch(windowType) {
15699 				case WindowTypes.normal:
15700 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15701 				break;
15702 				case WindowTypes.undecorated:
15703 					motifHideDecorations();
15704 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15705 				break;
15706 				case WindowTypes.eventOnly:
15707 					_hidden = true;
15708 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
15709 					goto hiddenWindow;
15710 				//break;
15711 				case WindowTypes.nestedChild:
15712 					// handled in XCreateWindow calls
15713 				break;
15714 
15715 				case WindowTypes.dropdownMenu:
15716 					motifHideDecorations();
15717 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
15718 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15719 				break;
15720 				case WindowTypes.popupMenu:
15721 					motifHideDecorations();
15722 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
15723 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15724 				break;
15725 				case WindowTypes.notification:
15726 					motifHideDecorations();
15727 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
15728 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15729 				break;
15730 				case WindowTypes.minimallyWrapped:
15731 					assert(0, "don't create a minimallyWrapped thing explicitly!");
15732 
15733 				case WindowTypes.dialog:
15734 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display));
15735 				break;
15736 				/+
15737 				case WindowTypes.menu:
15738 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15739 					motifHideDecorations();
15740 				break;
15741 				case WindowTypes.desktop:
15742 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
15743 				break;
15744 				case WindowTypes.dock:
15745 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
15746 				break;
15747 				case WindowTypes.toolbar:
15748 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
15749 				break;
15750 				case WindowTypes.menu:
15751 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15752 				break;
15753 				case WindowTypes.utility:
15754 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
15755 				break;
15756 				case WindowTypes.splash:
15757 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
15758 				break;
15759 				case WindowTypes.tooltip:
15760 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
15761 				break;
15762 				case WindowTypes.notification:
15763 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
15764 				break;
15765 				case WindowTypes.combo:
15766 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
15767 				break;
15768 				case WindowTypes.dnd:
15769 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
15770 				break;
15771 				+/
15772 			}
15773 			catch(Exception e) {
15774 				// XInternAtom failed, prolly a WM
15775 				// that doesn't support these things
15776 			}
15777 
15778 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15779 			// the two following flags may be ignored by WM
15780 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15781 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15782 
15783 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15784 
15785 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15786 
15787 			// What would be ideal here is if they only were
15788 			// selected if there was actually an event handler
15789 			// for them...
15790 
15791 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15792 
15793 			hiddenWindow:
15794 
15795 			// set the pid property for lookup later by window managers
15796 			// a standard convenience
15797 			import core.sys.posix.unistd;
15798 			arch_ulong pid = getpid();
15799 
15800 			XChangeProperty(
15801 				display,
15802 				impl.window,
15803 				GetAtom!("_NET_WM_PID", true)(display),
15804 				XA_CARDINAL,
15805 				32 /* bits */,
15806 				0 /*PropModeReplace*/,
15807 				&pid,
15808 				1);
15809 
15810 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15811 				if(parent is null) assert(0);
15812 				// sdpyPrintDebugString("transient");
15813 				XChangeProperty(
15814 					display,
15815 					impl.window,
15816 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15817 					XA_WINDOW,
15818 					32 /* bits */,
15819 					0 /*PropModeReplace*/,
15820 					&parent.impl.window,
15821 					1);
15822 
15823 			}
15824 
15825 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15826 				XMapWindow(display, window);
15827 			} else {
15828 				_hidden = true;
15829 			}
15830 		}
15831 
15832 		void populateXic() {
15833 			if (XDisplayConnection.xim !is null) {
15834 				xic = XCreateIC(XDisplayConnection.xim,
15835 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15836 						/*XNClientWindow*/"clientWindow".ptr, window,
15837 						/*XNFocusWindow*/"focusWindow".ptr, window,
15838 						null);
15839 				if (xic is null) {
15840 					import core.stdc.stdio : stderr, fprintf;
15841 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15842 				}
15843 			}
15844 		}
15845 
15846 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15847 			auto mask = EventMask.ExposureMask |
15848 				EventMask.KeyPressMask |
15849 				EventMask.KeyReleaseMask |
15850 				EventMask.PropertyChangeMask |
15851 				EventMask.FocusChangeMask |
15852 				EventMask.StructureNotifyMask |
15853 				EventMask.SubstructureNotifyMask |
15854 				EventMask.VisibilityChangeMask
15855 				| EventMask.ButtonPressMask
15856 				| EventMask.ButtonReleaseMask
15857 			;
15858 
15859 			// xshm is our shortcut for local connections
15860 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15861 				mask |= EventMask.PointerMotionMask;
15862 			else
15863 				mask |= EventMask.ButtonMotionMask;
15864 
15865 			XSelectInput(display, window, mask);
15866 		}
15867 
15868 
15869 		void setNetWMWindowType(Atom type) {
15870 			Atom[2] atoms;
15871 
15872 			atoms[0] = type;
15873 			// generic fallback
15874 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15875 
15876 			XChangeProperty(
15877 				display,
15878 				impl.window,
15879 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15880 				XA_ATOM,
15881 				32 /* bits */,
15882 				0 /*PropModeReplace*/,
15883 				atoms.ptr,
15884 				cast(int) atoms.length);
15885 		}
15886 
15887 		void motifHideDecorations(bool hide = true) {
15888 			MwmHints hints;
15889 			hints.flags = MWM_HINTS_DECORATIONS;
15890 			hints.decorations = hide ? 0 : 1;
15891 
15892 			XChangeProperty(
15893 				display,
15894 				impl.window,
15895 				GetAtom!"_MOTIF_WM_HINTS"(display),
15896 				GetAtom!"_MOTIF_WM_HINTS"(display),
15897 				32 /* bits */,
15898 				0 /*PropModeReplace*/,
15899 				&hints,
15900 				hints.sizeof / 4);
15901 		}
15902 
15903 		/*k8: unused
15904 		void createOpenGlContext() {
15905 
15906 		}
15907 		*/
15908 
15909 		void closeWindow() {
15910 			// I can't close this or a child window closing will
15911 			// break events for everyone. So I'm just leaking it right
15912 			// now and that is probably perfectly fine...
15913 			version(none)
15914 			if (customEventFDRead != -1) {
15915 				import core.sys.posix.unistd : close;
15916 				auto same = customEventFDRead == customEventFDWrite;
15917 
15918 				close(customEventFDRead);
15919 				if(!same)
15920 					close(customEventFDWrite);
15921 				customEventFDRead = -1;
15922 				customEventFDWrite = -1;
15923 			}
15924 
15925 			version(without_opengl) {} else
15926 			if(glc !is null) {
15927 				glXDestroyContext(display, glc);
15928 				glc = null;
15929 			}
15930 
15931 			if(buffer)
15932 				XFreePixmap(display, buffer);
15933 			bufferw = bufferh = 0;
15934 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15935 			XDestroyWindow(display, window);
15936 			XFlush(display);
15937 		}
15938 
15939 		void dispose() {
15940 		}
15941 
15942 		bool destroyed = false;
15943 	}
15944 
15945 	bool insideXEventLoop;
15946 }
15947 
15948 version(X11) {
15949 
15950 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15951 
15952 	private class ResizeEvent {
15953 		int width, height;
15954 	}
15955 
15956 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15957 		if(win.windowType == WindowTypes.minimallyWrapped)
15958 			return;
15959 
15960 		if(win.pendingResizeEvent is null) {
15961 			win.pendingResizeEvent = new ResizeEvent();
15962 			win.addEventListener((ResizeEvent re) {
15963 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15964 			});
15965 		}
15966 		win.pendingResizeEvent.width = width;
15967 		win.pendingResizeEvent.height = height;
15968 		if(!win.eventQueued!ResizeEvent) {
15969 			win.postEvent(win.pendingResizeEvent);
15970 		}
15971 	}
15972 
15973 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15974 		if(win.windowType == WindowTypes.minimallyWrapped)
15975 			return;
15976 		if(win.closed)
15977 			return;
15978 
15979 		if(width != win.width || height != win.height) {
15980 
15981 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15982 			win._width = width;
15983 			win._height = height;
15984 
15985 			if(win.openglMode == OpenGlOptions.no) {
15986 				// FIXME: could this be more efficient?
15987 
15988 				if (win.bufferw < width || win.bufferh < height) {
15989 					//{ 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); }
15990 					// grow the internal buffer to match the window...
15991 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15992 					{
15993 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15994 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15995 						scope(exit) XFreeGC(win.display, xgc);
15996 						XSetClipMask(win.display, xgc, None);
15997 						XSetForeground(win.display, xgc, 0);
15998 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15999 					}
16000 					XCopyArea(display,
16001 						cast(Drawable) win.buffer,
16002 						cast(Drawable) newPixmap,
16003 						win.gc, 0, 0,
16004 						win.bufferw < width ? win.bufferw : win.width,
16005 						win.bufferh < height ? win.bufferh : win.height,
16006 						0, 0);
16007 
16008 					XFreePixmap(display, win.buffer);
16009 					win.buffer = newPixmap;
16010 					win.bufferw = width;
16011 					win.bufferh = height;
16012 				}
16013 
16014 				// clear unused parts of the buffer
16015 				if (win.bufferw > width || win.bufferh > height) {
16016 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
16017 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
16018 					scope(exit) XFreeGC(win.display, xgc);
16019 					XSetClipMask(win.display, xgc, None);
16020 					XSetForeground(win.display, xgc, 0);
16021 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
16022 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
16023 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
16024 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
16025 				}
16026 
16027 			}
16028 
16029 			win.updateOpenglViewportIfNeeded(width, height);
16030 
16031 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
16032 
16033 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
16034 			if(win.windowResized !is null) {
16035 				XUnlockDisplay(display);
16036 				scope(exit) XLockDisplay(display);
16037 				win.windowResized(width, height);
16038 			}
16039 		}
16040 	}
16041 
16042 
16043 	/// Platform-specific, you might use it when doing a custom event loop.
16044 	bool doXNextEvent(Display* display) {
16045 		bool done;
16046 		XEvent e;
16047 		XNextEvent(display, &e);
16048 		version(sddddd) {
16049 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16050 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
16051 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
16052 			}
16053 		}
16054 
16055 		// filter out compose events
16056 		if (XFilterEvent(&e, None)) {
16057 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
16058 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
16059 			return false;
16060 		}
16061 		// process keyboard mapping changes
16062 		if (e.type == EventType.KeymapNotify) {
16063 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
16064 			XRefreshKeyboardMapping(&e.xmapping);
16065 			return false;
16066 		}
16067 
16068 		version(with_eventloop)
16069 			import arsd.eventloop;
16070 
16071 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
16072 			// see windows impl's comments
16073 			XUnlockDisplay(display);
16074 			scope(exit) XLockDisplay(display);
16075 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
16076 			if(ret == 0)
16077 				return done;
16078 		}
16079 
16080 
16081 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16082 			if(win.getNativeEventHandler !is null) {
16083 				XUnlockDisplay(display);
16084 				scope(exit) XLockDisplay(display);
16085 				auto ret = win.getNativeEventHandler()(e);
16086 				if(ret == 0)
16087 					return done;
16088 			}
16089 		}
16090 
16091 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
16092 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
16093 				// we get this because of the RRScreenChangeNotifyMask
16094 
16095 				// this isn't actually an ideal way to do it since it wastes time
16096 				// but meh it is simple and it works.
16097 				win.actualDpiLoadAttempted = false;
16098 				SimpleWindow.xRandrInfoLoadAttemped = false;
16099 				win.updateActualDpi(); // trigger a reload
16100 			}
16101 		}
16102 
16103 		switch(e.type) {
16104 		  case EventType.SelectionClear:
16105 		  	// writeln("SelectionClear");
16106 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
16107 				// FIXME so it is supposed to finish any in progress transfers... but idk...
16108 				// writeln("SelectionClear");
16109 			}
16110 			SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
16111 			mightShortCircuitClipboard = false;
16112 		  break;
16113 		  case EventType.SelectionRequest:
16114 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
16115 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
16116 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
16117 				XUnlockDisplay(display);
16118 				scope(exit) XLockDisplay(display);
16119 				(*ssh).handleRequest(e);
16120 			}
16121 		  break;
16122 		  case EventType.PropertyNotify:
16123 			// import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
16124 
16125 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
16126 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
16127 					ssh.sendMoreIncr(&e.xproperty);
16128 			}
16129 
16130 
16131 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
16132 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16133 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
16134 					Atom target;
16135 					int format;
16136 					arch_ulong bytesafter, length;
16137 					void* value;
16138 
16139 					ubyte[] s;
16140 					Atom targetToKeep;
16141 
16142 					XGetWindowProperty(
16143 						e.xproperty.display,
16144 						e.xproperty.window,
16145 						e.xproperty.atom,
16146 						0,
16147 						100000 /* length */,
16148 						true, /* erase it to signal we got it and want more */
16149 						0 /*AnyPropertyType*/,
16150 						&target, &format, &length, &bytesafter, &value);
16151 
16152 					if(!targetToKeep)
16153 						targetToKeep = target;
16154 
16155 					auto id = (cast(ubyte*) value)[0 .. length];
16156 
16157 					handler.handleIncrData(targetToKeep, id);
16158 					if(length == 0) {
16159 						win.getSelectionHandlers.remove(e.xproperty.atom);
16160 					}
16161 
16162 					XFree(value);
16163 				}
16164 			}
16165 		  break;
16166 		  case EventType.SelectionNotify:
16167 		  	// import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom);
16168 			if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
16169 			if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16170 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
16171 					XUnlockDisplay(display);
16172 					scope(exit) XLockDisplay(display);
16173 					handler.handleData(None, null);
16174 					win.getSelectionHandlers.remove(e.xproperty.atom);
16175 				} else {
16176 					Atom target;
16177 					int format;
16178 					arch_ulong bytesafter, length;
16179 					void* value;
16180 					XGetWindowProperty(
16181 						e.xselection.display,
16182 						e.xselection.requestor,
16183 						e.xselection.property,
16184 						0,
16185 						100000 /* length */,
16186 						//false, /* don't erase it */
16187 						true, /* do erase it lol */
16188 						0 /*AnyPropertyType*/,
16189 						&target, &format, &length, &bytesafter, &value);
16190 
16191 					// FIXME: I don't have to copy it now since it is in char[] instead of string
16192 
16193 					{
16194 						XUnlockDisplay(display);
16195 						scope(exit) XLockDisplay(display);
16196 
16197 						if(target == XA_ATOM) {
16198 							// initial request, see what they are able to work with and request the best one
16199 							// we can handle, if available
16200 
16201 							Atom[] answer = (cast(Atom*) value)[0 .. length];
16202 							Atom best = handler.findBestFormat(answer);
16203 
16204 							/+
16205 							writeln("got ", answer);
16206 							foreach(a; answer)
16207 								writeln(XGetAtomName(display, a).stringz);
16208 							writeln("best ", best);
16209 							+/
16210 
16211 							if(best != None) {
16212 								// actually request the best format
16213 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
16214 							}
16215 						} else if(target == GetAtom!"INCR"(display)) {
16216 							// incremental
16217 
16218 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
16219 
16220 							// signal the sending program that we see
16221 							// the incr and are ready to receive more.
16222 							XDeleteProperty(
16223 								e.xselection.display,
16224 								e.xselection.requestor,
16225 								e.xselection.property);
16226 						} else {
16227 							// unsupported type... maybe, forward, then we done with it
16228 							if(target != None) {
16229 								handler.handleData(target, cast(ubyte[]) value[0 .. length]);
16230 								win.getSelectionHandlers.remove(e.xproperty.atom);
16231 							}
16232 						}
16233 					}
16234 					XFree(value);
16235 					/*
16236 					XDeleteProperty(
16237 						e.xselection.display,
16238 						e.xselection.requestor,
16239 						e.xselection.property);
16240 					*/
16241 				}
16242 			}
16243 			break;
16244 		  case EventType.ConfigureNotify:
16245 			auto event = e.xconfigure;
16246 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
16247 				if(win.windowType == WindowTypes.minimallyWrapped)
16248 					break;
16249 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
16250 
16251 				/+
16252 					The ICCCM says window managers must send a synthetic event when the window
16253 					is moved but NOT when it is resized. In the resize case, an event is sent
16254 					with position (0, 0) which can be wrong and break the dpi calculations.
16255 
16256 					So we only consider the synthetic events from the WM and otherwise
16257 					need to wait for some other event to get the position which... sucks.
16258 
16259 					I'd rather not have windows changing their layout on mouse motion after
16260 					switching monitors... might be forced to but for now just ignoring it.
16261 
16262 					Easiest way to switch monitors without sending a size position is by
16263 					maximize or fullscreen in a setup like mine, but on most setups those
16264 					work on the monitor it is already living on, so it should be ok most the
16265 					time.
16266 				+/
16267 				if(event.send_event) {
16268 					win.screenPositionKnown = true;
16269 					win.screenPositionX = event.x;
16270 					win.screenPositionY = event.y;
16271 					win.updateActualDpi();
16272 				}
16273 
16274 				win.updateIMEPopupLocation();
16275 				recordX11ResizeAsync(display, *win, event.width, event.height);
16276 			}
16277 		  break;
16278 		  case EventType.Expose:
16279 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
16280 				if(win.windowType == WindowTypes.minimallyWrapped)
16281 					break;
16282 				// if it is closing from a popup menu, it can get
16283 				// an Expose event right by the end and trigger a
16284 				// BadDrawable error ... we'll just check
16285 				// closed to handle that.
16286 				if((*win).closed) break;
16287 				if((*win).openglMode == OpenGlOptions.no) {
16288 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
16289 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
16290 					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);
16291 				} else {
16292 					// need to redraw the scene somehow
16293 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
16294 						XUnlockDisplay(display);
16295 						scope(exit) XLockDisplay(display);
16296 						version(without_opengl) {} else
16297 						win.redrawOpenGlSceneSoon();
16298 					}
16299 				}
16300 			}
16301 		  break;
16302 		  case EventType.FocusIn:
16303 		  case EventType.FocusOut:
16304 			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
16305 
16306 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16307 				/+
16308 
16309 				void info(string detail) {
16310 					string s;
16311 					// import std.conv;
16312 					// import std.datetime;
16313 					s ~= to!string(Clock.currTime);
16314 					s ~= " ";
16315 					s ~= e.type == EventType.FocusIn ? "in " : "out";
16316 					s ~= " ";
16317 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
16318 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
16319 					s ~= detail;
16320 					s ~= " ";
16321 
16322 					sdpyPrintDebugString(s);
16323 
16324 				}
16325 
16326 				switch(e.xfocus.detail) {
16327 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
16328 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
16329 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
16330 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
16331 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
16332 					case NotifyDetail.NotifyPointer: info("pointer"); break;
16333 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
16334 					case NotifyDetail.NotifyDetailNone: info("none"); break;
16335 					default:
16336 
16337 				}
16338 				+/
16339 
16340 
16341 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
16342 					break; // just ignore these they seem irrelevant
16343 
16344 				auto old = win._focused;
16345 				win._focused = e.type == EventType.FocusIn;
16346 
16347 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
16348 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
16349 					win._focused = true;
16350 
16351 				if(win.demandingAttention)
16352 					demandAttention(*win, false);
16353 
16354 				win.updateIMEFocused();
16355 
16356 				if(old != win._focused && win.onFocusChange) {
16357 					XUnlockDisplay(display);
16358 					scope(exit) XLockDisplay(display);
16359 					win.onFocusChange(win._focused);
16360 				}
16361 			}
16362 		  break;
16363 		  case EventType.VisibilityNotify:
16364 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16365 					auto before = (*win)._visible;
16366 					(*win)._visible = (e.xvisibility.state != VisibilityNotify.VisibilityFullyObscured);
16367 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
16368 						if (win.visibilityChanged !is null && before == true) {
16369 								XUnlockDisplay(display);
16370 								scope(exit) XLockDisplay(display);
16371 								win.visibilityChanged(false);
16372 							}
16373 					} else {
16374 						if (win.visibilityChanged !is null && before == false) {
16375 							XUnlockDisplay(display);
16376 							scope(exit) XLockDisplay(display);
16377 							win.visibilityChanged(true);
16378 						}
16379 					}
16380 				}
16381 				break;
16382 		  case EventType.ClientMessage:
16383 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
16384 					// "ignore next mouse motion" event, increment ignore counter for teh window
16385 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16386 						++(*win).warpEventCount;
16387 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
16388 					} else {
16389 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
16390 					}
16391 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
16392 					// user clicked the close button on the window manager
16393 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16394 						XUnlockDisplay(display);
16395 						scope(exit) XLockDisplay(display);
16396 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
16397 					}
16398 
16399 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
16400 					// writeln("HAPPENED");
16401 					// user clicked the close button on the window manager
16402 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16403 						XUnlockDisplay(display);
16404 						scope(exit) XLockDisplay(display);
16405 
16406 						auto setTo = *win;
16407 
16408 						if(win.setRequestedInputFocus !is null) {
16409 							auto s = win.setRequestedInputFocus();
16410 							if(s !is null) {
16411 								setTo = s;
16412 							}
16413 						}
16414 
16415 						assert(setTo !is null);
16416 
16417 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
16418 
16419 						// sdpyPrintDebugString("WM_TAKE_FOCUS ", setTo.impl.window);
16420 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
16421 					}
16422 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
16423 					foreach(nai; NotificationAreaIcon.activeIcons)
16424 						nai.newManager();
16425 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16426 
16427 					bool xDragWindow = true;
16428 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
16429 						//XDefineCursor(display, xDragWindow.impl.window,
16430 							//writeln("XdndStatus ", e.xclient.data.l);
16431 					}
16432 					if(auto dh = win.dropHandler) {
16433 
16434 						static Atom[3] xFormatsBuffer;
16435 						static Atom[] xFormats;
16436 
16437 						void resetXFormats() {
16438 							xFormatsBuffer[] = 0;
16439 							xFormats = xFormatsBuffer[];
16440 						}
16441 
16442 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
16443 							// on Windows it is supposed to return the effect you actually do FIXME
16444 
16445 							auto sourceWindow =  e.xclient.data.l[0];
16446 
16447 							xFormatsBuffer[0] = e.xclient.data.l[2];
16448 							xFormatsBuffer[1] = e.xclient.data.l[3];
16449 							xFormatsBuffer[2] = e.xclient.data.l[4];
16450 
16451 							if(e.xclient.data.l[1] & 1) {
16452 								// can just grab it all but like we don't necessarily need them...
16453 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
16454 							} else {
16455 								int len;
16456 								foreach(fmt; xFormatsBuffer)
16457 									if(fmt) len++;
16458 								xFormats = xFormatsBuffer[0 .. len];
16459 							}
16460 
16461 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
16462 
16463 							dh.dragEnter(&pkg);
16464 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
16465 
16466 							auto pack = e.xclient.data.l[2];
16467 
16468 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
16469 
16470 
16471 							XClientMessageEvent xclient;
16472 
16473 							xclient.type = EventType.ClientMessage;
16474 							xclient.window = e.xclient.data.l[0];
16475 							xclient.message_type = GetAtom!"XdndStatus"(display);
16476 							xclient.format = 32;
16477 							xclient.data.l[0] = win.impl.window;
16478 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
16479 							auto r = result.consistentWithin;
16480 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
16481 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
16482 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
16483 
16484 							XSendEvent(
16485 								display,
16486 								e.xclient.data.l[0],
16487 								false,
16488 								EventMask.NoEventMask,
16489 								cast(XEvent*) &xclient
16490 							);
16491 
16492 
16493 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
16494 							//writeln("XdndLeave");
16495 							// drop cancelled.
16496 							// data.l[0] is the source window
16497 							dh.dragLeave();
16498 
16499 							resetXFormats();
16500 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
16501 							// drop happening, should fetch data, then send finished
16502 							// writeln("XdndDrop");
16503 
16504 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
16505 
16506 							dh.drop(&pkg);
16507 
16508 							resetXFormats();
16509 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
16510 							// writeln("XdndFinished");
16511 
16512 							dh.finish();
16513 						}
16514 
16515 					}
16516 				}
16517 		  break;
16518 		  case EventType.MapNotify:
16519 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
16520 					auto before = (*win)._visible;
16521 					(*win)._visible = true;
16522 					if (!(*win)._visibleForTheFirstTimeCalled) {
16523 						(*win)._visibleForTheFirstTimeCalled = true;
16524 						if ((*win).visibleForTheFirstTime !is null) {
16525 							XUnlockDisplay(display);
16526 							scope(exit) XLockDisplay(display);
16527 							(*win).visibleForTheFirstTime();
16528 						}
16529 					}
16530 					if ((*win).visibilityChanged !is null && before == false) {
16531 						XUnlockDisplay(display);
16532 						scope(exit) XLockDisplay(display);
16533 						(*win).visibilityChanged(true);
16534 					}
16535 				}
16536 		  break;
16537 		  case EventType.UnmapNotify:
16538 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
16539 					auto before = (*win)._visible;
16540 					win._visible = false;
16541 					if (win.visibilityChanged !is null && before == true) {
16542 						XUnlockDisplay(display);
16543 						scope(exit) XLockDisplay(display);
16544 						win.visibilityChanged(false);
16545 					}
16546 			}
16547 		  break;
16548 		  case EventType.DestroyNotify:
16549 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
16550 				if(win.destroyed)
16551 					break; // might get a notification both for itself and from its parent
16552 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
16553 				win._closed = true; // just in case
16554 				win.destroyed = true;
16555 				if (win.xic !is null) {
16556 					XDestroyIC(win.xic);
16557 					win.xic = null; // just in case
16558 				}
16559 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
16560 				bool anyImportant = false;
16561 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
16562 					if(w.beingOpenKeepsAppOpen) {
16563 						anyImportant = true;
16564 						break;
16565 					}
16566 				if(!anyImportant) {
16567 					EventLoop.quitApplication();
16568 					done = true;
16569 				}
16570 			}
16571 			auto window = e.xdestroywindow.window;
16572 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
16573 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
16574 
16575 			version(with_eventloop) {
16576 				if(done) exit();
16577 			}
16578 		  break;
16579 
16580 		  case EventType.MotionNotify:
16581 			MouseEvent mouse;
16582 			auto event = e.xmotion;
16583 
16584 			mouse.type = MouseEventType.motion;
16585 			mouse.x = event.x;
16586 			mouse.y = event.y;
16587 			mouse.modifierState = event.state;
16588 
16589 			mouse.timestamp = event.time;
16590 
16591 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
16592 				mouse.window = *win;
16593 				if (win.warpEventCount > 0) {
16594 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
16595 					--(*win).warpEventCount;
16596 					(*win).mdx(mouse); // so deltas will be correctly updated
16597 				} else {
16598 					win.warpEventCount = 0; // just in case
16599 					(*win).mdx(mouse);
16600 					if((*win).handleMouseEvent) {
16601 						XUnlockDisplay(display);
16602 						scope(exit) XLockDisplay(display);
16603 						(*win).handleMouseEvent(mouse);
16604 					}
16605 				}
16606 			}
16607 
16608 		  	version(with_eventloop)
16609 				send(mouse);
16610 		  break;
16611 		  case EventType.ButtonPress:
16612 		  case EventType.ButtonRelease:
16613 			MouseEvent mouse;
16614 			auto event = e.xbutton;
16615 
16616 			mouse.timestamp = event.time;
16617 
16618 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
16619 			mouse.x = event.x;
16620 			mouse.y = event.y;
16621 
16622 			static Time lastMouseDownTime = 0;
16623 			static int lastMouseDownButton = -1;
16624 
16625 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
16626 			if(e.type == EventType.ButtonPress) {
16627 				lastMouseDownTime = event.time;
16628 				lastMouseDownButton = event.button;
16629 			}
16630 
16631 			switch(event.button) {
16632 				case 1: mouse.button = MouseButton.left; break; // left
16633 				case 2: mouse.button = MouseButton.middle; break; // middle
16634 				case 3: mouse.button = MouseButton.right; break; // right
16635 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
16636 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
16637 				case 6: break; // idk
16638 				case 7: break; // idk
16639 				case 8: mouse.button = MouseButton.backButton; break;
16640 				case 9: mouse.button = MouseButton.forwardButton; break;
16641 				default:
16642 			}
16643 
16644 			// FIXME: double check this
16645 			mouse.modifierState = event.state;
16646 
16647 			//mouse.modifierState = event.detail;
16648 
16649 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
16650 				mouse.window = *win;
16651 				(*win).mdx(mouse);
16652 				if((*win).handleMouseEvent) {
16653 					XUnlockDisplay(display);
16654 					scope(exit) XLockDisplay(display);
16655 					(*win).handleMouseEvent(mouse);
16656 				}
16657 			}
16658 			version(with_eventloop)
16659 				send(mouse);
16660 		  break;
16661 
16662 		  case EventType.KeyPress:
16663 		  case EventType.KeyRelease:
16664 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
16665 			KeyEvent ke;
16666 			ke.pressed = e.type == EventType.KeyPress;
16667 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
16668 
16669 			auto sym = XKeycodeToKeysym(
16670 				XDisplayConnection.get(),
16671 				e.xkey.keycode,
16672 				0);
16673 
16674 			ke.key = cast(Key) sym;//e.xkey.keycode;
16675 
16676 			ke.modifierState = e.xkey.state;
16677 
16678 			// writefln("%x", sym);
16679 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
16680 			int charbuflen = 0; // return value of XwcLookupString
16681 			if (ke.pressed) {
16682 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
16683 				if (win !is null && win.xic !is null) {
16684 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
16685 					Status status;
16686 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
16687 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
16688 				} else {
16689 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
16690 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
16691 					char[16] buffer;
16692 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
16693 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
16694 				}
16695 			}
16696 
16697 			// if there's no char, subst one
16698 			if (charbuflen == 0) {
16699 				switch (sym) {
16700 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
16701 					case 0xff8d: // keypad enter
16702 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
16703 					default : // ignore
16704 				}
16705 			}
16706 
16707 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
16708 				ke.window = *win;
16709 
16710 
16711 				if(win.inputProxy)
16712 					win = &win.inputProxy;
16713 
16714 				// char events are separate since they are on Windows too
16715 				// also, xcompose can generate long char sequences
16716 				// don't send char events if Meta and/or Hyper is pressed
16717 				// TODO: ctrl+char should only send control chars; not yet
16718 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
16719 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
16720 				}
16721 
16722 				dchar[32] charsComingBuffer;
16723 				int charsComingPosition;
16724 				dchar[] charsComing = charsComingBuffer[];
16725 
16726 				if (ke.pressed && charbuflen > 0) {
16727 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
16728 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
16729 						if(charsComingPosition >= charsComing.length)
16730 							charsComing.length = charsComingPosition + 8;
16731 
16732 						charsComing[charsComingPosition++] = ch;
16733 					}
16734 
16735 					charsComing = charsComing[0 .. charsComingPosition];
16736 				} else {
16737 					charsComing = null;
16738 				}
16739 
16740 				ke.charsPossible = charsComing;
16741 
16742 				if (win.handleKeyEvent) {
16743 					XUnlockDisplay(display);
16744 					scope(exit) XLockDisplay(display);
16745 					win.handleKeyEvent(ke);
16746 				}
16747 
16748 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
16749 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
16750 					XUnlockDisplay(display);
16751 					scope(exit) XLockDisplay(display);
16752 					foreach(ch; charsComing)
16753 						win.handleCharEvent(ch);
16754 				}
16755 			}
16756 
16757 			version(with_eventloop)
16758 				send(ke);
16759 		  break;
16760 		  default:
16761 		}
16762 
16763 		return done;
16764 	}
16765 }
16766 
16767 /* *************************************** */
16768 /*      Done with simpledisplay stuff      */
16769 /* *************************************** */
16770 
16771 // Necessary C library bindings follow
16772 version(Windows) {} else
16773 version(Emscripten) {} else
16774 version(X11) {
16775 
16776 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
16777 
16778 // X11 bindings needed here
16779 /*
16780 	A little of this is from the bindings project on
16781 	D Source and some of it is copy/paste from the C
16782 	header.
16783 
16784 	The DSource listing consistently used D's long
16785 	where C used long. That's wrong - C long is 32 bit, so
16786 	it should be int in D. I changed that here.
16787 
16788 	Note:
16789 	This isn't complete, just took what I needed for myself.
16790 */
16791 
16792 import core.stdc.stddef : wchar_t;
16793 
16794 interface XLib {
16795 extern(C) nothrow @nogc {
16796 	char* XResourceManagerString(Display*);
16797 	void XrmInitialize();
16798 	XrmDatabase XrmGetStringDatabase(char* data);
16799 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16800 
16801 	Cursor XCreateFontCursor(Display*, uint shape);
16802 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16803 	int XUndefineCursor(Display* display, Window w);
16804 
16805 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16806 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16807 	int XFreeCursor(Display* display, Cursor cursor);
16808 
16809 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16810 
16811 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16812 
16813 	XVaNestedList XVaCreateNestedList(int unused, ...);
16814 
16815 	char *XKeysymToString(KeySym keysym);
16816 	KeySym XKeycodeToKeysym(
16817 		Display*		/* display */,
16818 		KeyCode		/* keycode */,
16819 		int			/* index */
16820 	);
16821 
16822 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16823 
16824 	int XFree(void*);
16825 	int XDeleteProperty(Display *display, Window w, Atom property);
16826 
16827 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16828 
16829 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16830 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16831 		*actual_type_return, int *actual_format_return, arch_ulong
16832 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16833 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16834 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16835 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16836 
16837 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16838 
16839 	Window XGetSelectionOwner(Display *display, Atom selection);
16840 
16841 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16842 
16843 	char** XListFonts(Display*, const char*, int, int*);
16844 	void XFreeFontNames(char**);
16845 
16846 	Display* XOpenDisplay(const char*);
16847 	int XCloseDisplay(Display*);
16848 
16849 	int function() XSynchronize(Display*, bool);
16850 	int function() XSetAfterFunction(Display*, int function() proc);
16851 
16852 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16853 
16854 	Bool XSupportsLocale();
16855 	char* XSetLocaleModifiers(const(char)* modifier_list);
16856 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16857 	Status XCloseOM(XOM om);
16858 
16859 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16860 	Status XCloseIM(XIM im);
16861 
16862 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16863 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16864 	Display* XDisplayOfIM(XIM im);
16865 	char* XLocaleOfIM(XIM im);
16866 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16867 	void XDestroyIC(XIC ic);
16868 	void XSetICFocus(XIC ic);
16869 	void XUnsetICFocus(XIC ic);
16870 	//wchar_t* XwcResetIC(XIC ic);
16871 	char* XmbResetIC(XIC ic);
16872 	char* Xutf8ResetIC(XIC ic);
16873 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16874 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16875 	XIM XIMOfIC(XIC ic);
16876 
16877 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16878 
16879 
16880 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16881 	int XFreeFont(Display *display, XFontStruct *font_struct);
16882 	int XSetFont(Display* display, GC gc, Font font);
16883 	int XTextWidth(XFontStruct*, scope const char*, int);
16884 
16885 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16886 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16887 
16888 	Window XCreateSimpleWindow(
16889 		Display*	/* display */,
16890 		Window		/* parent */,
16891 		int			/* x */,
16892 		int			/* y */,
16893 		uint		/* width */,
16894 		uint		/* height */,
16895 		uint		/* border_width */,
16896 		uint		/* border */,
16897 		uint		/* background */
16898 	);
16899 	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);
16900 
16901 	int XReparentWindow(Display*, Window, Window, int, int);
16902 	int XClearWindow(Display*, Window);
16903 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16904 	int XMoveWindow(Display*, Window, int, int);
16905 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16906 
16907 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16908 
16909 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16910 
16911 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16912 
16913 	XImage *XCreateImage(
16914 		Display*		/* display */,
16915 		Visual*		/* visual */,
16916 		uint	/* depth */,
16917 		int			/* format */,
16918 		int			/* offset */,
16919 		ubyte*		/* data */,
16920 		uint	/* width */,
16921 		uint	/* height */,
16922 		int			/* bitmap_pad */,
16923 		int			/* bytes_per_line */
16924 	);
16925 
16926 	Status XInitImage (XImage* image);
16927 
16928 	Atom XInternAtom(
16929 		Display*		/* display */,
16930 		const char*	/* atom_name */,
16931 		Bool		/* only_if_exists */
16932 	);
16933 
16934 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16935 	char* XGetAtomName(Display*, Atom);
16936 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16937 
16938 	int XPutImage(
16939 		Display*	/* display */,
16940 		Drawable	/* d */,
16941 		GC			/* gc */,
16942 		XImage*	/* image */,
16943 		int			/* src_x */,
16944 		int			/* src_y */,
16945 		int			/* dest_x */,
16946 		int			/* dest_y */,
16947 		uint		/* width */,
16948 		uint		/* height */
16949 	);
16950 
16951 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16952 
16953 
16954 	int XDestroyWindow(
16955 		Display*	/* display */,
16956 		Window		/* w */
16957 	);
16958 
16959 	int XDestroyImage(XImage*);
16960 
16961 	int XSelectInput(
16962 		Display*	/* display */,
16963 		Window		/* w */,
16964 		EventMask	/* event_mask */
16965 	);
16966 
16967 	int XMapWindow(
16968 		Display*	/* display */,
16969 		Window		/* w */
16970 	);
16971 
16972 	Status XIconifyWindow(Display*, Window, int);
16973 	int XMapRaised(Display*, Window);
16974 	int XMapSubwindows(Display*, Window);
16975 
16976 	int XNextEvent(
16977 		Display*	/* display */,
16978 		XEvent*		/* event_return */
16979 	);
16980 
16981 	int XMaskEvent(Display*, arch_long, XEvent*);
16982 
16983 	Bool XFilterEvent(XEvent *event, Window window);
16984 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16985 
16986 	Status XSetWMProtocols(
16987 		Display*	/* display */,
16988 		Window		/* w */,
16989 		Atom*		/* protocols */,
16990 		int			/* count */
16991 	);
16992 
16993 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16994 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16995 
16996 
16997 	Status XInitThreads();
16998 	void XLockDisplay (Display* display);
16999 	void XUnlockDisplay (Display* display);
17000 
17001 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
17002 
17003 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
17004 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
17005 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
17006 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
17007 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
17008 
17009 
17010 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
17011 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
17012 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
17013 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
17014 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
17015 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
17016 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
17017 	int XDrawPoint(Display*, Drawable, GC, int, int);
17018 	int XSetForeground(Display*, GC, uint);
17019 	int XSetBackground(Display*, GC, uint);
17020 
17021 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
17022 	void XFreeFontSet(Display*, XFontSet);
17023 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
17024 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
17025 
17026 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
17027 
17028 
17029 //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);
17030 
17031 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
17032 	int XSetFunction(Display*, GC, int);
17033 
17034 	GC XCreateGC(Display*, Drawable, uint, void*);
17035 	int XCopyGC(Display*, GC, uint, GC);
17036 	int XFreeGC(Display*, GC);
17037 
17038 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
17039 	bool XCheckMaskEvent(Display*, int, XEvent*);
17040 
17041 	int XPending(Display*);
17042 	int XEventsQueued(Display* display, int mode);
17043 
17044 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
17045 	int XFreePixmap(Display*, Pixmap);
17046 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
17047 	int XFlush(Display*);
17048 	int XBell(Display*, int);
17049 	int XSync(Display*, bool);
17050 
17051 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
17052 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
17053 
17054 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
17055 	int XUngrabKeyboard(Display*, Time);
17056 
17057 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
17058 
17059 	KeySym XStringToKeysym(const char *string);
17060 
17061 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
17062 
17063 	Window XDefaultRootWindow(Display*);
17064 
17065 	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);
17066 
17067 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
17068 
17069 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
17070 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
17071 
17072 	Status XAllocColor(Display*, Colormap, XColor*);
17073 
17074 	int XWithdrawWindow(Display*, Window, int);
17075 	int XUnmapWindow(Display*, Window);
17076 	int XLowerWindow(Display*, Window);
17077 	int XRaiseWindow(Display*, Window);
17078 
17079 	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);
17080 	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);
17081 
17082 	int XGetInputFocus(Display*, Window*, int*);
17083 	int XSetInputFocus(Display*, Window, int, Time);
17084 
17085 	XErrorHandler XSetErrorHandler(XErrorHandler);
17086 
17087 	int XGetErrorText(Display*, int, char*, int);
17088 
17089 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
17090 
17091 
17092 	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);
17093 	int XUngrabPointer(Display *display, Time time);
17094 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
17095 
17096 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
17097 
17098 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
17099 	int XSetClipMask(Display*, GC, Pixmap);
17100 	int XSetClipOrigin(Display*, GC, int, int);
17101 
17102 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
17103 
17104 	void XSetWMName(Display*, Window, XTextProperty*);
17105 	Status XGetWMName(Display*, Window, XTextProperty*);
17106 	int XStoreName(Display* display, Window w, const(char)* window_name);
17107 
17108 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
17109 
17110 }
17111 }
17112 
17113 interface Xext {
17114 extern(C) nothrow @nogc {
17115 	Status XShmAttach(Display*, XShmSegmentInfo*);
17116 	Status XShmDetach(Display*, XShmSegmentInfo*);
17117 	Status XShmPutImage(
17118 		Display*            /* dpy */,
17119 		Drawable            /* d */,
17120 		GC                  /* gc */,
17121 		XImage*             /* image */,
17122 		int                 /* src_x */,
17123 		int                 /* src_y */,
17124 		int                 /* dst_x */,
17125 		int                 /* dst_y */,
17126 		uint        /* src_width */,
17127 		uint        /* src_height */,
17128 		Bool                /* send_event */
17129 	);
17130 
17131 	Status XShmQueryExtension(Display*);
17132 
17133 	XImage *XShmCreateImage(
17134 		Display*            /* dpy */,
17135 		Visual*             /* visual */,
17136 		uint        /* depth */,
17137 		int                 /* format */,
17138 		char*               /* data */,
17139 		XShmSegmentInfo*    /* shminfo */,
17140 		uint        /* width */,
17141 		uint        /* height */
17142 	);
17143 
17144 	Pixmap XShmCreatePixmap(
17145 		Display*            /* dpy */,
17146 		Drawable            /* d */,
17147 		char*               /* data */,
17148 		XShmSegmentInfo*    /* shminfo */,
17149 		uint        /* width */,
17150 		uint        /* height */,
17151 		uint        /* depth */
17152 	);
17153 
17154 }
17155 }
17156 
17157 	// this requires -lXpm
17158 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
17159 
17160 
17161 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
17162 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
17163 shared static this() {
17164 	xlib.loadDynamicLibrary();
17165 	xext.loadDynamicLibrary();
17166 }
17167 
17168 
17169 extern(C) nothrow @nogc {
17170 
17171 alias XrmDatabase = void*;
17172 struct XrmValue {
17173 	uint size;
17174 	void* addr;
17175 }
17176 
17177 struct XVisualInfo {
17178 	Visual* visual;
17179 	VisualID visualid;
17180 	int screen;
17181 	uint depth;
17182 	int c_class;
17183 	c_ulong red_mask;
17184 	c_ulong green_mask;
17185 	c_ulong blue_mask;
17186 	int colormap_size;
17187 	int bits_per_rgb;
17188 }
17189 
17190 enum VisualNoMask=	0x0;
17191 enum VisualIDMask=	0x1;
17192 enum VisualScreenMask=0x2;
17193 enum VisualDepthMask=	0x4;
17194 enum VisualClassMask=	0x8;
17195 enum VisualRedMaskMask=0x10;
17196 enum VisualGreenMaskMask=0x20;
17197 enum VisualBlueMaskMask=0x40;
17198 enum VisualColormapSizeMask=0x80;
17199 enum VisualBitsPerRGBMask=0x100;
17200 enum VisualAllMask=	0x1FF;
17201 
17202 enum AnyKey = 0;
17203 enum AnyModifier = 1 << 15;
17204 
17205 // XIM and other crap
17206 struct _XOM {}
17207 struct _XIM {}
17208 struct _XIC {}
17209 alias XOM = _XOM*;
17210 alias XIM = _XIM*;
17211 alias XIC = _XIC*;
17212 
17213 alias XVaNestedList = void*;
17214 
17215 alias XIMStyle = arch_ulong;
17216 enum : arch_ulong {
17217 	XIMPreeditArea      = 0x0001,
17218 	XIMPreeditCallbacks = 0x0002,
17219 	XIMPreeditPosition  = 0x0004,
17220 	XIMPreeditNothing   = 0x0008,
17221 	XIMPreeditNone      = 0x0010,
17222 	XIMStatusArea       = 0x0100,
17223 	XIMStatusCallbacks  = 0x0200,
17224 	XIMStatusNothing    = 0x0400,
17225 	XIMStatusNone       = 0x0800,
17226 }
17227 
17228 
17229 /* X Shared Memory Extension functions */
17230 	//pragma(lib, "Xshm");
17231 	alias arch_ulong ShmSeg;
17232 	struct XShmSegmentInfo {
17233 		ShmSeg shmseg;
17234 		int shmid;
17235 		ubyte* shmaddr;
17236 		Bool readOnly;
17237 	}
17238 
17239 	// and the necessary OS functions
17240 	int shmget(int, size_t, int);
17241 	void* shmat(int, scope const void*, int);
17242 	int shmdt(scope const void*);
17243 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
17244 
17245 	enum IPC_PRIVATE = 0;
17246 	enum IPC_CREAT = 512;
17247 	enum IPC_RMID = 0;
17248 
17249 /* MIT-SHM end */
17250 
17251 
17252 enum MappingType:int {
17253 	MappingModifier		=0,
17254 	MappingKeyboard		=1,
17255 	MappingPointer		=2
17256 }
17257 
17258 /* ImageFormat -- PutImage, GetImage */
17259 enum ImageFormat:int {
17260 	XYBitmap	=0,	/* depth 1, XYFormat */
17261 	XYPixmap	=1,	/* depth == drawable depth */
17262 	ZPixmap	=2	/* depth == drawable depth */
17263 }
17264 
17265 enum ModifierName:int {
17266 	ShiftMapIndex	=0,
17267 	LockMapIndex	=1,
17268 	ControlMapIndex	=2,
17269 	Mod1MapIndex	=3,
17270 	Mod2MapIndex	=4,
17271 	Mod3MapIndex	=5,
17272 	Mod4MapIndex	=6,
17273 	Mod5MapIndex	=7
17274 }
17275 
17276 enum ButtonMask:int {
17277 	Button1Mask	=1<<8,
17278 	Button2Mask	=1<<9,
17279 	Button3Mask	=1<<10,
17280 	Button4Mask	=1<<11,
17281 	Button5Mask	=1<<12,
17282 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17283 }
17284 
17285 enum KeyOrButtonMask:uint {
17286 	ShiftMask	=1<<0,
17287 	LockMask	=1<<1,
17288 	ControlMask	=1<<2,
17289 	Mod1Mask	=1<<3,
17290 	Mod2Mask	=1<<4,
17291 	Mod3Mask	=1<<5,
17292 	Mod4Mask	=1<<6,
17293 	Mod5Mask	=1<<7,
17294 	Button1Mask	=1<<8,
17295 	Button2Mask	=1<<9,
17296 	Button3Mask	=1<<10,
17297 	Button4Mask	=1<<11,
17298 	Button5Mask	=1<<12,
17299 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17300 }
17301 
17302 enum ButtonName:int {
17303 	Button1	=1,
17304 	Button2	=2,
17305 	Button3	=3,
17306 	Button4	=4,
17307 	Button5	=5
17308 }
17309 
17310 /* Notify modes */
17311 enum NotifyModes:int
17312 {
17313 	NotifyNormal		=0,
17314 	NotifyGrab			=1,
17315 	NotifyUngrab		=2,
17316 	NotifyWhileGrabbed	=3
17317 }
17318 enum NotifyHint = 1;	/* for MotionNotify events */
17319 
17320 /* Notify detail */
17321 enum NotifyDetail:int
17322 {
17323 	NotifyAncestor			=0,
17324 	NotifyVirtual			=1,
17325 	NotifyInferior			=2,
17326 	NotifyNonlinear			=3,
17327 	NotifyNonlinearVirtual	=4,
17328 	NotifyPointer			=5,
17329 	NotifyPointerRoot		=6,
17330 	NotifyDetailNone		=7
17331 }
17332 
17333 /* Visibility notify */
17334 
17335 enum VisibilityNotify:int
17336 {
17337 VisibilityUnobscured		=0,
17338 VisibilityPartiallyObscured	=1,
17339 VisibilityFullyObscured		=2
17340 }
17341 
17342 
17343 enum WindowStackingMethod:int
17344 {
17345 	Above		=0,
17346 	Below		=1,
17347 	TopIf		=2,
17348 	BottomIf	=3,
17349 	Opposite	=4
17350 }
17351 
17352 /* Circulation request */
17353 enum CirculationRequest:int
17354 {
17355 	PlaceOnTop		=0,
17356 	PlaceOnBottom	=1
17357 }
17358 
17359 enum PropertyNotification:int
17360 {
17361 	PropertyNewValue	=0,
17362 	PropertyDelete		=1
17363 }
17364 
17365 enum ColorMapNotification:int
17366 {
17367 	ColormapUninstalled	=0,
17368 	ColormapInstalled		=1
17369 }
17370 
17371 
17372 	struct _XPrivate {}
17373 	struct _XrmHashBucketRec {}
17374 
17375 	alias void* XPointer;
17376 	alias void* XExtData;
17377 
17378 	version( X86_64 ) {
17379 		alias ulong XID;
17380 		alias ulong arch_ulong;
17381 		alias long arch_long;
17382 	} else version (AArch64) {
17383 		alias ulong XID;
17384 		alias ulong arch_ulong;
17385 		alias long arch_long;
17386 	} else {
17387 		alias uint XID;
17388 		alias uint arch_ulong;
17389 		alias int arch_long;
17390 	}
17391 
17392 	alias XID Window;
17393 	alias XID Drawable;
17394 	alias XID Pixmap;
17395 
17396 	alias arch_ulong Atom;
17397 	alias int Bool;
17398 	alias Display XDisplay;
17399 
17400 	alias int ByteOrder;
17401 	alias arch_ulong Time;
17402 	alias void ScreenFormat;
17403 
17404 	struct XImage {
17405 		int width, height;			/* size of image */
17406 		int xoffset;				/* number of pixels offset in X direction */
17407 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
17408 		void *data;					/* pointer to image data */
17409 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
17410 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
17411 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
17412 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
17413 		int depth;					/* depth of image */
17414 		int bytes_per_line;			/* accelarator to next line */
17415 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
17416 		arch_ulong red_mask;	/* bits in z arrangment */
17417 		arch_ulong green_mask;
17418 		arch_ulong blue_mask;
17419 		XPointer obdata;			/* hook for the object routines to hang on */
17420 		static struct F {				/* image manipulation routines */
17421 			XImage* function(
17422 				XDisplay* 			/* display */,
17423 				Visual*				/* visual */,
17424 				uint				/* depth */,
17425 				int					/* format */,
17426 				int					/* offset */,
17427 				ubyte*				/* data */,
17428 				uint				/* width */,
17429 				uint				/* height */,
17430 				int					/* bitmap_pad */,
17431 				int					/* bytes_per_line */) create_image;
17432 			int function(XImage *) destroy_image;
17433 			arch_ulong function(XImage *, int, int) get_pixel;
17434 			int function(XImage *, int, int, arch_ulong) put_pixel;
17435 			XImage* function(XImage *, int, int, uint, uint) sub_image;
17436 			int function(XImage *, arch_long) add_pixel;
17437 		}
17438 		F f;
17439 	}
17440 	version(X86_64) static assert(XImage.sizeof == 136);
17441 	else version(X86) static assert(XImage.sizeof == 88);
17442 
17443 struct XCharStruct {
17444 	short       lbearing;       /* origin to left edge of raster */
17445 	short       rbearing;       /* origin to right edge of raster */
17446 	short       width;          /* advance to next char's origin */
17447 	short       ascent;         /* baseline to top edge of raster */
17448 	short       descent;        /* baseline to bottom edge of raster */
17449 	ushort attributes;  /* per char flags (not predefined) */
17450 }
17451 
17452 /*
17453  * To allow arbitrary information with fonts, there are additional properties
17454  * returned.
17455  */
17456 struct XFontProp {
17457 	Atom name;
17458 	arch_ulong card32;
17459 }
17460 
17461 alias Atom Font;
17462 
17463 struct XFontStruct {
17464 	XExtData *ext_data;           /* Hook for extension to hang data */
17465 	Font fid;                     /* Font ID for this font */
17466 	uint direction;           /* Direction the font is painted */
17467 	uint min_char_or_byte2;   /* First character */
17468 	uint max_char_or_byte2;   /* Last character */
17469 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
17470 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
17471 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
17472 	uint default_char;        /* Char to print for undefined character */
17473 	int n_properties;             /* How many properties there are */
17474 	XFontProp *properties;        /* Pointer to array of additional properties*/
17475 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
17476 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
17477 	XCharStruct *per_char;        /* first_char to last_char information */
17478 	int ascent;                   /* Max extent above baseline for spacing */
17479 	int descent;                  /* Max descent below baseline for spacing */
17480 }
17481 
17482 
17483 /*
17484  * Definitions of specific events.
17485  */
17486 struct XKeyEvent
17487 {
17488 	int type;			/* of event */
17489 	arch_ulong serial;		/* # of last request processed by server */
17490 	Bool send_event;	/* true if this came from a SendEvent request */
17491 	Display *display;	/* Display the event was read from */
17492 	Window window;	        /* "event" window it is reported relative to */
17493 	Window root;	        /* root window that the event occurred on */
17494 	Window subwindow;	/* child window */
17495 	Time time;		/* milliseconds */
17496 	int x, y;		/* pointer x, y coordinates in event window */
17497 	int x_root, y_root;	/* coordinates relative to root */
17498 	KeyOrButtonMask state;	/* key or button mask */
17499 	uint keycode;	/* detail */
17500 	Bool same_screen;	/* same screen flag */
17501 }
17502 version(X86_64) static assert(XKeyEvent.sizeof == 96);
17503 alias XKeyEvent XKeyPressedEvent;
17504 alias XKeyEvent XKeyReleasedEvent;
17505 
17506 struct XButtonEvent
17507 {
17508 	int type;		/* of event */
17509 	arch_ulong serial;	/* # of last request processed by server */
17510 	Bool send_event;	/* true if this came from a SendEvent request */
17511 	Display *display;	/* Display the event was read from */
17512 	Window window;	        /* "event" window it is reported relative to */
17513 	Window root;	        /* root window that the event occurred on */
17514 	Window subwindow;	/* child window */
17515 	Time time;		/* milliseconds */
17516 	int x, y;		/* pointer x, y coordinates in event window */
17517 	int x_root, y_root;	/* coordinates relative to root */
17518 	KeyOrButtonMask state;	/* key or button mask */
17519 	uint button;	/* detail */
17520 	Bool same_screen;	/* same screen flag */
17521 }
17522 alias XButtonEvent XButtonPressedEvent;
17523 alias XButtonEvent XButtonReleasedEvent;
17524 
17525 struct XMotionEvent{
17526 	int type;		/* of event */
17527 	arch_ulong serial;	/* # of last request processed by server */
17528 	Bool send_event;	/* true if this came from a SendEvent request */
17529 	Display *display;	/* Display the event was read from */
17530 	Window window;	        /* "event" window reported relative to */
17531 	Window root;	        /* root window that the event occurred on */
17532 	Window subwindow;	/* child window */
17533 	Time time;		/* milliseconds */
17534 	int x, y;		/* pointer x, y coordinates in event window */
17535 	int x_root, y_root;	/* coordinates relative to root */
17536 	KeyOrButtonMask state;	/* key or button mask */
17537 	byte is_hint;		/* detail */
17538 	Bool same_screen;	/* same screen flag */
17539 }
17540 alias XMotionEvent XPointerMovedEvent;
17541 
17542 struct XCrossingEvent{
17543 	int type;		/* of event */
17544 	arch_ulong serial;	/* # of last request processed by server */
17545 	Bool send_event;	/* true if this came from a SendEvent request */
17546 	Display *display;	/* Display the event was read from */
17547 	Window window;	        /* "event" window reported relative to */
17548 	Window root;	        /* root window that the event occurred on */
17549 	Window subwindow;	/* child window */
17550 	Time time;		/* milliseconds */
17551 	int x, y;		/* pointer x, y coordinates in event window */
17552 	int x_root, y_root;	/* coordinates relative to root */
17553 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
17554 	NotifyDetail detail;
17555 	/*
17556 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17557 	 * NotifyNonlinear,NotifyNonlinearVirtual
17558 	 */
17559 	Bool same_screen;	/* same screen flag */
17560 	Bool focus;		/* Boolean focus */
17561 	KeyOrButtonMask state;	/* key or button mask */
17562 }
17563 alias XCrossingEvent XEnterWindowEvent;
17564 alias XCrossingEvent XLeaveWindowEvent;
17565 
17566 struct XFocusChangeEvent{
17567 	int type;		/* FocusIn or FocusOut */
17568 	arch_ulong serial;	/* # of last request processed by server */
17569 	Bool send_event;	/* true if this came from a SendEvent request */
17570 	Display *display;	/* Display the event was read from */
17571 	Window window;		/* window of event */
17572 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
17573 				   NotifyGrab, NotifyUngrab */
17574 	NotifyDetail detail;
17575 	/*
17576 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17577 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
17578 	 * NotifyPointerRoot, NotifyDetailNone
17579 	 */
17580 }
17581 alias XFocusChangeEvent XFocusInEvent;
17582 alias XFocusChangeEvent XFocusOutEvent;
17583 
17584 enum CWBackPixmap              = (1L<<0);
17585 enum CWBackPixel               = (1L<<1);
17586 enum CWBorderPixmap            = (1L<<2);
17587 enum CWBorderPixel             = (1L<<3);
17588 enum CWBitGravity              = (1L<<4);
17589 enum CWWinGravity              = (1L<<5);
17590 enum CWBackingStore            = (1L<<6);
17591 enum CWBackingPlanes           = (1L<<7);
17592 enum CWBackingPixel            = (1L<<8);
17593 enum CWOverrideRedirect        = (1L<<9);
17594 enum CWSaveUnder               = (1L<<10);
17595 enum CWEventMask               = (1L<<11);
17596 enum CWDontPropagate           = (1L<<12);
17597 enum CWColormap                = (1L<<13);
17598 enum CWCursor                  = (1L<<14);
17599 
17600 struct XWindowAttributes {
17601 	int x, y;			/* location of window */
17602 	int width, height;		/* width and height of window */
17603 	int border_width;		/* border width of window */
17604 	int depth;			/* depth of window */
17605 	Visual *visual;			/* the associated visual structure */
17606 	Window root;			/* root of screen containing window */
17607 	int class_;			/* InputOutput, InputOnly*/
17608 	int bit_gravity;		/* one of the bit gravity values */
17609 	int win_gravity;		/* one of the window gravity values */
17610 	int backing_store;		/* NotUseful, WhenMapped, Always */
17611 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
17612 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
17613 	Bool save_under;		/* boolean, should bits under be saved? */
17614 	Colormap colormap;		/* color map to be associated with window */
17615 	Bool map_installed;		/* boolean, is color map currently installed*/
17616 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
17617 	arch_long all_event_masks;		/* set of events all people have interest in*/
17618 	arch_long your_event_mask;		/* my event mask */
17619 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
17620 	Bool override_redirect;		/* boolean value for override-redirect */
17621 	Screen *screen;			/* back pointer to correct screen */
17622 }
17623 
17624 enum IsUnmapped = 0;
17625 enum IsUnviewable = 1;
17626 enum IsViewable = 2;
17627 
17628 struct XSetWindowAttributes {
17629 	Pixmap background_pixmap;/* background, None, or ParentRelative */
17630 	arch_ulong background_pixel;/* background pixel */
17631 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
17632 	arch_ulong border_pixel;/* border pixel value */
17633 	int bit_gravity;         /* one of bit gravity values */
17634 	int win_gravity;         /* one of the window gravity values */
17635 	int backing_store;       /* NotUseful, WhenMapped, Always */
17636 	arch_ulong backing_planes;/* planes to be preserved if possible */
17637 	arch_ulong backing_pixel;/* value to use in restoring planes */
17638 	Bool save_under;         /* should bits under be saved? (popups) */
17639 	arch_long event_mask;         /* set of events that should be saved */
17640 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
17641 	Bool override_redirect;  /* boolean value for override_redirect */
17642 	Colormap colormap;       /* color map to be associated with window */
17643 	Cursor cursor;           /* cursor to be displayed (or None) */
17644 }
17645 
17646 
17647 alias int Status;
17648 
17649 
17650 enum EventMask:int
17651 {
17652 	NoEventMask				=0,
17653 	KeyPressMask			=1<<0,
17654 	KeyReleaseMask			=1<<1,
17655 	ButtonPressMask			=1<<2,
17656 	ButtonReleaseMask		=1<<3,
17657 	EnterWindowMask			=1<<4,
17658 	LeaveWindowMask			=1<<5,
17659 	PointerMotionMask		=1<<6,
17660 	PointerMotionHintMask	=1<<7,
17661 	Button1MotionMask		=1<<8,
17662 	Button2MotionMask		=1<<9,
17663 	Button3MotionMask		=1<<10,
17664 	Button4MotionMask		=1<<11,
17665 	Button5MotionMask		=1<<12,
17666 	ButtonMotionMask		=1<<13,
17667 	KeymapStateMask		=1<<14,
17668 	ExposureMask			=1<<15,
17669 	VisibilityChangeMask	=1<<16,
17670 	StructureNotifyMask		=1<<17,
17671 	ResizeRedirectMask		=1<<18,
17672 	SubstructureNotifyMask	=1<<19,
17673 	SubstructureRedirectMask=1<<20,
17674 	FocusChangeMask			=1<<21,
17675 	PropertyChangeMask		=1<<22,
17676 	ColormapChangeMask		=1<<23,
17677 	OwnerGrabButtonMask		=1<<24
17678 }
17679 
17680 struct MwmHints {
17681 	c_ulong flags;
17682 	c_ulong functions;
17683 	c_ulong decorations;
17684 	c_long input_mode;
17685 	c_ulong status;
17686 }
17687 
17688 enum {
17689 	MWM_HINTS_FUNCTIONS = (1L << 0),
17690 	MWM_HINTS_DECORATIONS =  (1L << 1),
17691 
17692 	MWM_FUNC_ALL = (1L << 0),
17693 	MWM_FUNC_RESIZE = (1L << 1),
17694 	MWM_FUNC_MOVE = (1L << 2),
17695 	MWM_FUNC_MINIMIZE = (1L << 3),
17696 	MWM_FUNC_MAXIMIZE = (1L << 4),
17697 	MWM_FUNC_CLOSE = (1L << 5),
17698 
17699 	MWM_DECOR_ALL = (1L << 0),
17700 	MWM_DECOR_BORDER = (1L << 1),
17701 	MWM_DECOR_RESIZEH = (1L << 2),
17702 	MWM_DECOR_TITLE = (1L << 3),
17703 	MWM_DECOR_MENU = (1L << 4),
17704 	MWM_DECOR_MINIMIZE = (1L << 5),
17705 	MWM_DECOR_MAXIMIZE = (1L << 6),
17706 }
17707 
17708 import core.stdc.config : c_long, c_ulong;
17709 
17710 	/* Size hints mask bits */
17711 
17712 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
17713 	enum   USSize      = (1L << 1)          /* user specified width, height */;
17714 	enum   PPosition   = (1L << 2)          /* program specified position */;
17715 	enum   PSize       = (1L << 3)          /* program specified size */;
17716 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
17717 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
17718 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
17719 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
17720 	enum   PBaseSize   = (1L << 8);
17721 	enum   PWinGravity = (1L << 9);
17722 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
17723 	struct XSizeHints {
17724 		arch_long flags;         /* marks which fields in this structure are defined */
17725 		int x, y;           /* Obsolete */
17726 		int width, height;  /* Obsolete */
17727 		int min_width, min_height;
17728 		int max_width, max_height;
17729 		int width_inc, height_inc;
17730 		struct Aspect {
17731 			int x;       /* numerator */
17732 			int y;       /* denominator */
17733 		}
17734 
17735 		Aspect min_aspect;
17736 		Aspect max_aspect;
17737 		int base_width, base_height;
17738 		int win_gravity;
17739 		/* this structure may be extended in the future */
17740 	}
17741 
17742 
17743 
17744 enum EventType:int
17745 {
17746 	KeyPress			=2,
17747 	KeyRelease			=3,
17748 	ButtonPress			=4,
17749 	ButtonRelease		=5,
17750 	MotionNotify		=6,
17751 	EnterNotify			=7,
17752 	LeaveNotify			=8,
17753 	FocusIn				=9,
17754 	FocusOut			=10,
17755 	KeymapNotify		=11,
17756 	Expose				=12,
17757 	GraphicsExpose		=13,
17758 	NoExpose			=14,
17759 	VisibilityNotify	=15,
17760 	CreateNotify		=16,
17761 	DestroyNotify		=17,
17762 	UnmapNotify		=18,
17763 	MapNotify			=19,
17764 	MapRequest			=20,
17765 	ReparentNotify		=21,
17766 	ConfigureNotify		=22,
17767 	ConfigureRequest	=23,
17768 	GravityNotify		=24,
17769 	ResizeRequest		=25,
17770 	CirculateNotify		=26,
17771 	CirculateRequest	=27,
17772 	PropertyNotify		=28,
17773 	SelectionClear		=29,
17774 	SelectionRequest	=30,
17775 	SelectionNotify		=31,
17776 	ColormapNotify		=32,
17777 	ClientMessage		=33,
17778 	MappingNotify		=34,
17779 	LASTEvent			=35	/* must be bigger than any event # */
17780 }
17781 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
17782 struct XKeymapEvent
17783 {
17784 	int type;
17785 	arch_ulong serial;	/* # of last request processed by server */
17786 	Bool send_event;	/* true if this came from a SendEvent request */
17787 	Display *display;	/* Display the event was read from */
17788 	Window window;
17789 	byte[32] key_vector;
17790 }
17791 
17792 struct XExposeEvent
17793 {
17794 	int type;
17795 	arch_ulong serial;	/* # of last request processed by server */
17796 	Bool send_event;	/* true if this came from a SendEvent request */
17797 	Display *display;	/* Display the event was read from */
17798 	Window window;
17799 	int x, y;
17800 	int width, height;
17801 	int count;		/* if non-zero, at least this many more */
17802 }
17803 
17804 struct XGraphicsExposeEvent{
17805 	int type;
17806 	arch_ulong serial;	/* # of last request processed by server */
17807 	Bool send_event;	/* true if this came from a SendEvent request */
17808 	Display *display;	/* Display the event was read from */
17809 	Drawable drawable;
17810 	int x, y;
17811 	int width, height;
17812 	int count;		/* if non-zero, at least this many more */
17813 	int major_code;		/* core is CopyArea or CopyPlane */
17814 	int minor_code;		/* not defined in the core */
17815 }
17816 
17817 struct XNoExposeEvent{
17818 	int type;
17819 	arch_ulong serial;	/* # of last request processed by server */
17820 	Bool send_event;	/* true if this came from a SendEvent request */
17821 	Display *display;	/* Display the event was read from */
17822 	Drawable drawable;
17823 	int major_code;		/* core is CopyArea or CopyPlane */
17824 	int minor_code;		/* not defined in the core */
17825 }
17826 
17827 struct XVisibilityEvent{
17828 	int type;
17829 	arch_ulong serial;	/* # of last request processed by server */
17830 	Bool send_event;	/* true if this came from a SendEvent request */
17831 	Display *display;	/* Display the event was read from */
17832 	Window window;
17833 	VisibilityNotify state;		/* Visibility state */
17834 }
17835 
17836 struct XCreateWindowEvent{
17837 	int type;
17838 	arch_ulong serial;	/* # of last request processed by server */
17839 	Bool send_event;	/* true if this came from a SendEvent request */
17840 	Display *display;	/* Display the event was read from */
17841 	Window parent;		/* parent of the window */
17842 	Window window;		/* window id of window created */
17843 	int x, y;		/* window location */
17844 	int width, height;	/* size of window */
17845 	int border_width;	/* border width */
17846 	Bool override_redirect;	/* creation should be overridden */
17847 }
17848 
17849 struct XDestroyWindowEvent
17850 {
17851 	int type;
17852 	arch_ulong serial;		/* # of last request processed by server */
17853 	Bool send_event;	/* true if this came from a SendEvent request */
17854 	Display *display;	/* Display the event was read from */
17855 	Window event;
17856 	Window window;
17857 }
17858 
17859 struct XUnmapEvent
17860 {
17861 	int type;
17862 	arch_ulong serial;		/* # of last request processed by server */
17863 	Bool send_event;	/* true if this came from a SendEvent request */
17864 	Display *display;	/* Display the event was read from */
17865 	Window event;
17866 	Window window;
17867 	Bool from_configure;
17868 }
17869 
17870 struct XMapEvent
17871 {
17872 	int type;
17873 	arch_ulong serial;		/* # of last request processed by server */
17874 	Bool send_event;	/* true if this came from a SendEvent request */
17875 	Display *display;	/* Display the event was read from */
17876 	Window event;
17877 	Window window;
17878 	Bool override_redirect;	/* Boolean, is override set... */
17879 }
17880 
17881 struct XMapRequestEvent
17882 {
17883 	int type;
17884 	arch_ulong serial;	/* # of last request processed by server */
17885 	Bool send_event;	/* true if this came from a SendEvent request */
17886 	Display *display;	/* Display the event was read from */
17887 	Window parent;
17888 	Window window;
17889 }
17890 
17891 struct XReparentEvent
17892 {
17893 	int type;
17894 	arch_ulong serial;	/* # of last request processed by server */
17895 	Bool send_event;	/* true if this came from a SendEvent request */
17896 	Display *display;	/* Display the event was read from */
17897 	Window event;
17898 	Window window;
17899 	Window parent;
17900 	int x, y;
17901 	Bool override_redirect;
17902 }
17903 
17904 struct XConfigureEvent
17905 {
17906 	int type;
17907 	arch_ulong serial;	/* # of last request processed by server */
17908 	Bool send_event;	/* true if this came from a SendEvent request */
17909 	Display *display;	/* Display the event was read from */
17910 	Window event;
17911 	Window window;
17912 	int x, y;
17913 	int width, height;
17914 	int border_width;
17915 	Window above;
17916 	Bool override_redirect;
17917 }
17918 
17919 struct XGravityEvent
17920 {
17921 	int type;
17922 	arch_ulong serial;	/* # of last request processed by server */
17923 	Bool send_event;	/* true if this came from a SendEvent request */
17924 	Display *display;	/* Display the event was read from */
17925 	Window event;
17926 	Window window;
17927 	int x, y;
17928 }
17929 
17930 struct XResizeRequestEvent
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 window;
17937 	int width, height;
17938 }
17939 
17940 struct  XConfigureRequestEvent
17941 {
17942 	int type;
17943 	arch_ulong serial;	/* # of last request processed by server */
17944 	Bool send_event;	/* true if this came from a SendEvent request */
17945 	Display *display;	/* Display the event was read from */
17946 	Window parent;
17947 	Window window;
17948 	int x, y;
17949 	int width, height;
17950 	int border_width;
17951 	Window above;
17952 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17953 	arch_ulong value_mask;
17954 }
17955 
17956 struct XCirculateEvent
17957 {
17958 	int type;
17959 	arch_ulong serial;	/* # of last request processed by server */
17960 	Bool send_event;	/* true if this came from a SendEvent request */
17961 	Display *display;	/* Display the event was read from */
17962 	Window event;
17963 	Window window;
17964 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17965 }
17966 
17967 struct XCirculateRequestEvent
17968 {
17969 	int type;
17970 	arch_ulong serial;	/* # of last request processed by server */
17971 	Bool send_event;	/* true if this came from a SendEvent request */
17972 	Display *display;	/* Display the event was read from */
17973 	Window parent;
17974 	Window window;
17975 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17976 }
17977 
17978 struct XPropertyEvent
17979 {
17980 	int type;
17981 	arch_ulong serial;	/* # of last request processed by server */
17982 	Bool send_event;	/* true if this came from a SendEvent request */
17983 	Display *display;	/* Display the event was read from */
17984 	Window window;
17985 	Atom atom;
17986 	Time time;
17987 	PropertyNotification state;		/* NewValue, Deleted */
17988 }
17989 
17990 struct XSelectionClearEvent
17991 {
17992 	int type;
17993 	arch_ulong serial;	/* # of last request processed by server */
17994 	Bool send_event;	/* true if this came from a SendEvent request */
17995 	Display *display;	/* Display the event was read from */
17996 	Window window;
17997 	Atom selection;
17998 	Time time;
17999 }
18000 
18001 struct XSelectionRequestEvent
18002 {
18003 	int type;
18004 	arch_ulong serial;	/* # of last request processed by server */
18005 	Bool send_event;	/* true if this came from a SendEvent request */
18006 	Display *display;	/* Display the event was read from */
18007 	Window owner;
18008 	Window requestor;
18009 	Atom selection;
18010 	Atom target;
18011 	Atom property;
18012 	Time time;
18013 }
18014 
18015 struct XSelectionEvent
18016 {
18017 	int type;
18018 	arch_ulong serial;	/* # of last request processed by server */
18019 	Bool send_event;	/* true if this came from a SendEvent request */
18020 	Display *display;	/* Display the event was read from */
18021 	Window requestor;
18022 	Atom selection;
18023 	Atom target;
18024 	Atom property;		/* ATOM or None */
18025 	Time time;
18026 }
18027 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
18028 
18029 struct XColormapEvent
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 	Colormap colormap;	/* COLORMAP or None */
18037 	Bool new_;		/* C++ */
18038 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
18039 }
18040 version(X86_64) static assert(XColormapEvent.sizeof == 56);
18041 
18042 struct XClientMessageEvent
18043 {
18044 	int type;
18045 	arch_ulong serial;	/* # of last request processed by server */
18046 	Bool send_event;	/* true if this came from a SendEvent request */
18047 	Display *display;	/* Display the event was read from */
18048 	Window window;
18049 	Atom message_type;
18050 	int format;
18051 	union Data{
18052 		byte[20] b;
18053 		short[10] s;
18054 		arch_ulong[5] l;
18055 	}
18056 	Data data;
18057 
18058 }
18059 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
18060 
18061 struct XMappingEvent
18062 {
18063 	int type;
18064 	arch_ulong serial;	/* # of last request processed by server */
18065 	Bool send_event;	/* true if this came from a SendEvent request */
18066 	Display *display;	/* Display the event was read from */
18067 	Window window;		/* unused */
18068 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
18069 				   MappingPointer */
18070 	int first_keycode;	/* first keycode */
18071 	int count;		/* defines range of change w. first_keycode*/
18072 }
18073 
18074 struct XErrorEvent
18075 {
18076 	int type;
18077 	Display *display;	/* Display the event was read from */
18078 	XID resourceid;		/* resource id */
18079 	arch_ulong serial;	/* serial number of failed request */
18080 	ubyte error_code;	/* error code of failed request */
18081 	ubyte request_code;	/* Major op-code of failed request */
18082 	ubyte minor_code;	/* Minor op-code of failed request */
18083 }
18084 
18085 struct XAnyEvent
18086 {
18087 	int type;
18088 	arch_ulong serial;	/* # of last request processed by server */
18089 	Bool send_event;	/* true if this came from a SendEvent request */
18090 	Display *display;/* Display the event was read from */
18091 	Window window;	/* window on which event was requested in event mask */
18092 }
18093 
18094 union XEvent{
18095 	int type;		/* must not be changed; first element */
18096 	XAnyEvent xany;
18097 	XKeyEvent xkey;
18098 	XButtonEvent xbutton;
18099 	XMotionEvent xmotion;
18100 	XCrossingEvent xcrossing;
18101 	XFocusChangeEvent xfocus;
18102 	XExposeEvent xexpose;
18103 	XGraphicsExposeEvent xgraphicsexpose;
18104 	XNoExposeEvent xnoexpose;
18105 	XVisibilityEvent xvisibility;
18106 	XCreateWindowEvent xcreatewindow;
18107 	XDestroyWindowEvent xdestroywindow;
18108 	XUnmapEvent xunmap;
18109 	XMapEvent xmap;
18110 	XMapRequestEvent xmaprequest;
18111 	XReparentEvent xreparent;
18112 	XConfigureEvent xconfigure;
18113 	XGravityEvent xgravity;
18114 	XResizeRequestEvent xresizerequest;
18115 	XConfigureRequestEvent xconfigurerequest;
18116 	XCirculateEvent xcirculate;
18117 	XCirculateRequestEvent xcirculaterequest;
18118 	XPropertyEvent xproperty;
18119 	XSelectionClearEvent xselectionclear;
18120 	XSelectionRequestEvent xselectionrequest;
18121 	XSelectionEvent xselection;
18122 	XColormapEvent xcolormap;
18123 	XClientMessageEvent xclient;
18124 	XMappingEvent xmapping;
18125 	XErrorEvent xerror;
18126 	XKeymapEvent xkeymap;
18127 	arch_ulong[24] pad;
18128 }
18129 
18130 
18131 	struct Display {
18132 		XExtData *ext_data;	/* hook for extension to hang data */
18133 		_XPrivate *private1;
18134 		int fd;			/* Network socket. */
18135 		int private2;
18136 		int proto_major_version;/* major version of server's X protocol */
18137 		int proto_minor_version;/* minor version of servers X protocol */
18138 		char *vendor;		/* vendor of the server hardware */
18139 	    	XID private3;
18140 		XID private4;
18141 		XID private5;
18142 		int private6;
18143 		XID function(Display*)resource_alloc;/* allocator function */
18144 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
18145 		int bitmap_unit;	/* padding and data requirements */
18146 		int bitmap_pad;		/* padding requirements on bitmaps */
18147 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
18148 		int nformats;		/* number of pixmap formats in list */
18149 		ScreenFormat *pixmap_format;	/* pixmap format list */
18150 		int private8;
18151 		int release;		/* release of the server */
18152 		_XPrivate *private9;
18153 		_XPrivate *private10;
18154 		int qlen;		/* Length of input event queue */
18155 		arch_ulong last_request_read; /* seq number of last event read */
18156 		arch_ulong request;	/* sequence number of last request. */
18157 		XPointer private11;
18158 		XPointer private12;
18159 		XPointer private13;
18160 		XPointer private14;
18161 		uint max_request_size; /* maximum number 32 bit words in request*/
18162 		_XrmHashBucketRec *db;
18163 		int function  (Display*)private15;
18164 		char *display_name;	/* "host:display" string used on this connect*/
18165 		int default_screen;	/* default screen for operations */
18166 		int nscreens;		/* number of screens on this server*/
18167 		Screen *screens;	/* pointer to list of screens */
18168 		arch_ulong motion_buffer;	/* size of motion buffer */
18169 		arch_ulong private16;
18170 		int min_keycode;	/* minimum defined keycode */
18171 		int max_keycode;	/* maximum defined keycode */
18172 		XPointer private17;
18173 		XPointer private18;
18174 		int private19;
18175 		byte *xdefaults;	/* contents of defaults from server */
18176 		/* there is more to this structure, but it is private to Xlib */
18177 	}
18178 
18179 	// I got these numbers from a C program as a sanity test
18180 	version(X86_64) {
18181 		static assert(Display.sizeof == 296);
18182 		static assert(XPointer.sizeof == 8);
18183 		static assert(XErrorEvent.sizeof == 40);
18184 		static assert(XAnyEvent.sizeof == 40);
18185 		static assert(XMappingEvent.sizeof == 56);
18186 		static assert(XEvent.sizeof == 192);
18187     	} else version (AArch64) {
18188 		// omit check for aarch64
18189 	} else {
18190 		static assert(Display.sizeof == 176);
18191 		static assert(XPointer.sizeof == 4);
18192 		static assert(XEvent.sizeof == 96);
18193 	}
18194 
18195 struct Depth
18196 {
18197 	int depth;		/* this depth (Z) of the depth */
18198 	int nvisuals;		/* number of Visual types at this depth */
18199 	Visual *visuals;	/* list of visuals possible at this depth */
18200 }
18201 
18202 alias void* GC;
18203 alias c_ulong VisualID;
18204 alias XID Colormap;
18205 alias XID Cursor;
18206 alias XID KeySym;
18207 alias uint KeyCode;
18208 enum None = 0;
18209 }
18210 
18211 version(without_opengl) {}
18212 else {
18213 extern(C) nothrow @nogc {
18214 
18215 
18216 static if(!SdpyIsUsingIVGLBinds) {
18217 enum GLX_USE_GL=            1;       /* support GLX rendering */
18218 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
18219 enum GLX_LEVEL=             3;       /* level in plane stacking */
18220 enum GLX_RGBA=              4;       /* true if RGBA mode */
18221 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
18222 enum GLX_STEREO=            6;       /* stereo buffering supported */
18223 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
18224 enum GLX_RED_SIZE=          8;       /* number of red component bits */
18225 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
18226 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
18227 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
18228 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
18229 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
18230 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
18231 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
18232 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
18233 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
18234 
18235 
18236 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
18237 
18238 
18239 
18240 enum GL_TRUE = 1;
18241 enum GL_FALSE = 0;
18242 }
18243 
18244 alias XID GLXContextID;
18245 alias XID GLXPixmap;
18246 alias XID GLXDrawable;
18247 alias XID GLXPbuffer;
18248 alias XID GLXWindow;
18249 alias XID GLXFBConfigID;
18250 alias void* GLXContext;
18251 
18252 }
18253 }
18254 
18255 enum AllocNone = 0;
18256 
18257 extern(C) {
18258 	/* WARNING, this type not in Xlib spec */
18259 	extern(C) alias XIOErrorHandler = int function (Display* display);
18260 }
18261 
18262 extern(C) nothrow
18263 alias XErrorHandler = int function(Display*, XErrorEvent*);
18264 
18265 extern(C) nothrow @nogc {
18266 struct Screen{
18267 	XExtData *ext_data;		/* hook for extension to hang data */
18268 	Display *display;		/* back pointer to display structure */
18269 	Window root;			/* Root window id. */
18270 	int width, height;		/* width and height of screen */
18271 	int mwidth, mheight;	/* width and height of  in millimeters */
18272 	int ndepths;			/* number of depths possible */
18273 	Depth *depths;			/* list of allowable depths on the screen */
18274 	int root_depth;			/* bits per pixel */
18275 	Visual *root_visual;	/* root visual */
18276 	GC default_gc;			/* GC for the root root visual */
18277 	Colormap cmap;			/* default color map */
18278 	uint white_pixel;
18279 	uint black_pixel;		/* White and Black pixel values */
18280 	int max_maps, min_maps;	/* max and min color maps */
18281 	int backing_store;		/* Never, WhenMapped, Always */
18282 	bool save_unders;
18283 	int root_input_mask;	/* initial root input mask */
18284 }
18285 
18286 struct Visual
18287 {
18288 	XExtData *ext_data;	/* hook for extension to hang data */
18289 	VisualID visualid;	/* visual id of this visual */
18290 	int class_;			/* class of screen (monochrome, etc.) */
18291 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
18292 	int bits_per_rgb;	/* log base 2 of distinct color values */
18293 	int map_entries;	/* color map entries */
18294 }
18295 
18296 	alias Display* _XPrivDisplay;
18297 
18298 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system {
18299 		assert(dpy !is null);
18300 		return &dpy.screens[scr];
18301 	}
18302 
18303 	extern(D) Window RootWindow(Display *dpy,int scr) {
18304 		return ScreenOfDisplay(dpy,scr).root;
18305 	}
18306 
18307 	struct XWMHints {
18308 		arch_long flags;
18309 		Bool input;
18310 		int initial_state;
18311 		Pixmap icon_pixmap;
18312 		Window icon_window;
18313 		int icon_x, icon_y;
18314 		Pixmap icon_mask;
18315 		XID window_group;
18316 	}
18317 
18318 	struct XClassHint {
18319 		char* res_name;
18320 		char* res_class;
18321 	}
18322 
18323 	extern(D) int DefaultScreen(Display *dpy) {
18324 		return dpy.default_screen;
18325 	}
18326 
18327 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
18328 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
18329 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
18330 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
18331 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
18332 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
18333 
18334 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
18335 
18336 	enum int AnyPropertyType = 0;
18337 	enum int Success = 0;
18338 
18339 	enum int RevertToNone = None;
18340 	enum int PointerRoot = 1;
18341 	enum Time CurrentTime = 0;
18342 	enum int RevertToPointerRoot = PointerRoot;
18343 	enum int RevertToParent = 2;
18344 
18345 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
18346 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
18347 	}
18348 
18349 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
18350 		return ScreenOfDisplay(dpy,scr).root_visual;
18351 	}
18352 
18353 	extern(D) GC DefaultGC(Display *dpy,int scr) {
18354 		return ScreenOfDisplay(dpy,scr).default_gc;
18355 	}
18356 
18357 	extern(D) uint BlackPixel(Display *dpy,int scr) {
18358 		return ScreenOfDisplay(dpy,scr).black_pixel;
18359 	}
18360 
18361 	extern(D) uint WhitePixel(Display *dpy,int scr) {
18362 		return ScreenOfDisplay(dpy,scr).white_pixel;
18363 	}
18364 
18365 	alias void* XFontSet; // i think
18366 	struct XmbTextItem {
18367 		char* chars;
18368 		int nchars;
18369 		int delta;
18370 		XFontSet font_set;
18371 	}
18372 
18373 	struct XTextItem {
18374 		char* chars;
18375 		int nchars;
18376 		int delta;
18377 		Font font;
18378 	}
18379 
18380 	enum {
18381 		GXclear        = 0x0, /* 0 */
18382 		GXand          = 0x1, /* src AND dst */
18383 		GXandReverse   = 0x2, /* src AND NOT dst */
18384 		GXcopy         = 0x3, /* src */
18385 		GXandInverted  = 0x4, /* NOT src AND dst */
18386 		GXnoop         = 0x5, /* dst */
18387 		GXxor          = 0x6, /* src XOR dst */
18388 		GXor           = 0x7, /* src OR dst */
18389 		GXnor          = 0x8, /* NOT src AND NOT dst */
18390 		GXequiv        = 0x9, /* NOT src XOR dst */
18391 		GXinvert       = 0xa, /* NOT dst */
18392 		GXorReverse    = 0xb, /* src OR NOT dst */
18393 		GXcopyInverted = 0xc, /* NOT src */
18394 		GXorInverted   = 0xd, /* NOT src OR dst */
18395 		GXnand         = 0xe, /* NOT src OR NOT dst */
18396 		GXset          = 0xf, /* 1 */
18397 	}
18398 	enum QueueMode : int {
18399 		QueuedAlready,
18400 		QueuedAfterReading,
18401 		QueuedAfterFlush
18402 	}
18403 
18404 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
18405 
18406 	struct XPoint {
18407 		short x;
18408 		short y;
18409 	}
18410 
18411 	enum CoordMode:int {
18412 		CoordModeOrigin = 0,
18413 		CoordModePrevious = 1
18414 	}
18415 
18416 	enum PolygonShape:int {
18417 		Complex = 0,
18418 		Nonconvex = 1,
18419 		Convex = 2
18420 	}
18421 
18422 	struct XTextProperty {
18423 		const(char)* value;		/* same as Property routines */
18424 		Atom encoding;			/* prop type */
18425 		int format;				/* prop data format: 8, 16, or 32 */
18426 		arch_ulong nitems;		/* number of data items in value */
18427 	}
18428 
18429 	version( X86_64 ) {
18430 		static assert(XTextProperty.sizeof == 32);
18431 	}
18432 
18433 
18434 	struct XGCValues {
18435 		int function_;           /* logical operation */
18436 		arch_ulong plane_mask;/* plane mask */
18437 		arch_ulong foreground;/* foreground pixel */
18438 		arch_ulong background;/* background pixel */
18439 		int line_width;         /* line width */
18440 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
18441 		int cap_style;          /* CapNotLast, CapButt,
18442 					   CapRound, CapProjecting */
18443 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
18444 		int fill_style;         /* FillSolid, FillTiled,
18445 					   FillStippled, FillOpaeueStippled */
18446 		int fill_rule;          /* EvenOddRule, WindingRule */
18447 		int arc_mode;           /* ArcChord, ArcPieSlice */
18448 		Pixmap tile;            /* tile pixmap for tiling operations */
18449 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
18450 		int ts_x_origin;        /* offset for tile or stipple operations */
18451 		int ts_y_origin;
18452 		Font font;              /* default text font for text operations */
18453 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
18454 		Bool graphics_exposures;/* boolean, should exposures be generated */
18455 		int clip_x_origin;      /* origin for clipping */
18456 		int clip_y_origin;
18457 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
18458 		int dash_offset;        /* patterned/dashed line information */
18459 		char dashes;
18460 	}
18461 
18462 	struct XColor {
18463 		arch_ulong pixel;
18464 		ushort red, green, blue;
18465 		byte flags;
18466 		byte pad;
18467 	}
18468 
18469 	struct XRectangle {
18470 		short x;
18471 		short y;
18472 		ushort width;
18473 		ushort height;
18474 	}
18475 
18476 	enum ClipByChildren = 0;
18477 	enum IncludeInferiors = 1;
18478 
18479 	enum Atom XA_PRIMARY = 1;
18480 	enum Atom XA_SECONDARY = 2;
18481 	enum Atom XA_STRING = 31;
18482 	enum Atom XA_CARDINAL = 6;
18483 	enum Atom XA_WM_NAME = 39;
18484 	enum Atom XA_ATOM = 4;
18485 	enum Atom XA_WINDOW = 33;
18486 	enum Atom XA_WM_HINTS = 35;
18487 	enum int PropModeAppend = 2;
18488 	enum int PropModeReplace = 0;
18489 	enum int PropModePrepend = 1;
18490 
18491 	enum int CopyFromParent = 0;
18492 	enum int InputOutput = 1;
18493 
18494 	// XWMHints
18495 	enum InputHint = 1 << 0;
18496 	enum StateHint = 1 << 1;
18497 	enum IconPixmapHint = (1L << 2);
18498 	enum IconWindowHint = (1L << 3);
18499 	enum IconPositionHint = (1L << 4);
18500 	enum IconMaskHint = (1L << 5);
18501 	enum WindowGroupHint = (1L << 6);
18502 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
18503 	enum XUrgencyHint = (1L << 8);
18504 
18505 	// GC Components
18506 	enum GCFunction           =   (1L<<0);
18507 	enum GCPlaneMask         =    (1L<<1);
18508 	enum GCForeground       =     (1L<<2);
18509 	enum GCBackground      =      (1L<<3);
18510 	enum GCLineWidth      =       (1L<<4);
18511 	enum GCLineStyle     =        (1L<<5);
18512 	enum GCCapStyle     =         (1L<<6);
18513 	enum GCJoinStyle   =          (1L<<7);
18514 	enum GCFillStyle  =           (1L<<8);
18515 	enum GCFillRule  =            (1L<<9);
18516 	enum GCTile     =             (1L<<10);
18517 	enum GCStipple           =    (1L<<11);
18518 	enum GCTileStipXOrigin  =     (1L<<12);
18519 	enum GCTileStipYOrigin =      (1L<<13);
18520 	enum GCFont               =   (1L<<14);
18521 	enum GCSubwindowMode     =    (1L<<15);
18522 	enum GCGraphicsExposures=     (1L<<16);
18523 	enum GCClipXOrigin     =      (1L<<17);
18524 	enum GCClipYOrigin    =       (1L<<18);
18525 	enum GCClipMask      =        (1L<<19);
18526 	enum GCDashOffset   =         (1L<<20);
18527 	enum GCDashList    =          (1L<<21);
18528 	enum GCArcMode    =           (1L<<22);
18529 	enum GCLastBit   =            22;
18530 
18531 
18532 	enum int WithdrawnState = 0;
18533 	enum int NormalState = 1;
18534 	enum int IconicState = 3;
18535 
18536 }
18537 } else version (OSXCocoa) {
18538 
18539 /+
18540 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
18541 +/
18542 
18543 	private __gshared AppDelegate globalAppDelegate;
18544 
18545 	extern(Objective-C)
18546 	class AppDelegate : NSObject, NSApplicationDelegate {
18547 		override static AppDelegate alloc() @selector("alloc");
18548 
18549 
18550 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
18551 			SimpleWindow.processAllCustomEvents();
18552 		}
18553 
18554 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
18555 			immutable style = NSWindowStyleMask.resizable |
18556 				NSWindowStyleMask.closable |
18557 				NSWindowStyleMask.miniaturizable |
18558 				NSWindowStyleMask.titled;
18559 
18560 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
18561 
18562 			{
18563 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
18564 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
18565 				mainMenu.setSubmenu(menu, item);
18566 
18567 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
18568 				newItem.target = NSApp;
18569 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
18570 				newItem2.target = NSApp;
18571 			}
18572 
18573 			{
18574 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
18575 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
18576 				mainMenu.setSubmenu(menu, item);
18577 
18578 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
18579 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
18580 			}
18581 
18582 
18583 			NSApp.menu = mainMenu;
18584 
18585 
18586 			// auto controller = ViewController.alloc.init;
18587 
18588 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
18589 
18590 			/+
18591 			this.window = window;
18592 			this.controller = controller;
18593 			+/
18594 		}
18595 
18596 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
18597 			NSApplication.shared_.activateIgnoringOtherApps(true);
18598 		}
18599 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
18600 			return true;
18601 		}
18602 	}
18603 
18604 	extern(Objective-C)
18605 	class SDWindowDelegate : NSObject, NSWindowDelegate {
18606 		override static SDWindowDelegate alloc() @selector("alloc");
18607 		override SDWindowDelegate init() @selector("init");
18608 
18609 		SimpleWindow simpleWindow;
18610 
18611 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
18612 			auto window = cast(void*) notification.object;
18613 
18614 			// FIXME: do i need to release it?
18615 			SimpleWindow.nativeMapping.remove(window);
18616 		}
18617 
18618 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
18619 			if(simpleWindow.windowResized) {
18620 				// FIXME: automaticallyScaleIfPossible behaviors
18621 
18622 				simpleWindow._width = cast(int) frameSize.width;
18623 				simpleWindow._height = cast(int) frameSize.height;
18624 
18625 				simpleWindow.view.setFrameSize(frameSize);
18626 
18627 				/+
18628 				auto size = simpleWindow.view.frame.size;
18629 				writeln(cast(int) size.width, "x", cast(int) size.height);
18630 				+/
18631 
18632 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
18633 
18634 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
18635 
18636 				// simpleWindow.view.setNeedsDisplay(true);
18637 			}
18638 
18639 			return frameSize;
18640 		}
18641 
18642 		/+
18643 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
18644 			if(simpleWindow.windowResized) {
18645 				auto window = simpleWindow.window;
18646 				auto rect = window.contentRectForFrameRect(window.frame);
18647 				import std.stdio; writeln(window.frame.size);
18648 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
18649 			}
18650 		}
18651 		+/
18652 	}
18653 
18654 	extern(Objective-C)
18655 	class SDGraphicsView : NSView {
18656 		SimpleWindow simpleWindow;
18657 
18658 		override static SDGraphicsView alloc() @selector("alloc");
18659 		override SDGraphicsView init() @selector("init") {
18660 			super.init();
18661 			return this;
18662 		}
18663 
18664 		override void drawRect(NSRect rect) @selector("drawRect:") {
18665 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
18666 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18667 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
18668 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18669 			CGImageRelease(cgImage);
18670 		}
18671 
18672 		extern(D)
18673 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
18674 			MouseEvent me;
18675 			me.type = type;
18676 
18677 			auto pos = event.locationInWindow;
18678 
18679 			me.x = cast(int) pos.x;
18680 			me.y = cast(int) (simpleWindow.height - pos.y);
18681 
18682 			me.dx = 0; // FIXME
18683 			me.dy = 0; // FIXME
18684 
18685 			me.button = button;
18686 			me.modifierState = cast(uint) event.modifierFlags;
18687 			me.window = simpleWindow;
18688 
18689 			me.doubleClick = false;
18690 
18691 			if(simpleWindow && simpleWindow.handleMouseEvent)
18692 				simpleWindow.handleMouseEvent(me);
18693 		}
18694 
18695 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
18696 			// writeln(event.pressedMouseButtons);
18697 
18698 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18699 		}
18700 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
18701 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
18702 		}
18703 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
18704 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
18705 		}
18706 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
18707 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
18708 		}
18709 		/+
18710 			// FIXME
18711 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
18712 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18713 		}
18714 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
18715 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18716 		}
18717 		+/
18718 
18719 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
18720 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
18721 		}
18722 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
18723 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
18724 		}
18725 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
18726 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
18727 		}
18728 
18729 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
18730 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
18731 		}
18732 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
18733 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
18734 		}
18735 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
18736 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
18737 		}
18738 
18739 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
18740 			// import std.stdio; writeln(event.deltaY);
18741 		}
18742 
18743 		override void keyDown(NSEvent event) @selector("keyDown:") {
18744 			// the event may have multiple characters, and we send them all at once.
18745 			if (simpleWindow.handleCharEvent) {
18746 				auto chars = DeifiedNSString(event.characters);
18747 				foreach (dchar dc; chars.str)
18748 					simpleWindow.handleCharEvent(dc);
18749 			}
18750 
18751 			keyHelper(event, true);
18752 		}
18753 
18754 		override void keyUp(NSEvent event) @selector("keyUp:") {
18755 			keyHelper(event, false);
18756 		}
18757 
18758 		extern(D)
18759 		private void keyHelper(NSEvent event, bool pressed) {
18760 			if(simpleWindow.handleKeyEvent) {
18761 				KeyEvent ev;
18762 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
18763 				ev.pressed = pressed;
18764 				ev.hardwareCode = cast(ubyte) event.keyCode;
18765 				ev.modifierState = cast(uint) event.modifierFlags;
18766 				ev.window = simpleWindow;
18767 
18768 				simpleWindow.handleKeyEvent(ev);
18769 			}
18770 		}
18771 
18772 		override bool isFlipped() @selector("isFlipped") {
18773 			return true;
18774 		}
18775 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
18776 			return true;
18777 		}
18778 
18779 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
18780 			if(simpleWindow && simpleWindow.handlePulse)
18781 				simpleWindow.handlePulse();
18782 			/+
18783 			setNeedsDisplay = true;
18784 			+/
18785 		}
18786 	}
18787 
18788 private:
18789 	alias const(void)* CFStringRef;
18790 	alias const(void)* CFAllocatorRef;
18791 	alias const(void)* CFTypeRef;
18792 	alias const(void)* CGColorSpaceRef;
18793 	alias const(void)* CGImageRef;
18794 	alias ulong CGBitmapInfo;
18795 	alias NSGraphicsContext CGContextRef;
18796 
18797 	alias NSPoint CGPoint;
18798 	alias NSSize CGSize;
18799 	alias NSRect CGRect;
18800 
18801 	struct CGAffineTransform {
18802 		double a, b, c, d, tx, ty;
18803 	}
18804 
18805 	enum NSApplicationActivationPolicyRegular = 0;
18806 	enum NSBackingStoreBuffered = 2;
18807 	enum kCFStringEncodingUTF8 = 0x08000100;
18808 
18809 	enum : size_t {
18810 		NSBorderlessWindowMask = 0,
18811 		NSTitledWindowMask = 1 << 0,
18812 		NSClosableWindowMask = 1 << 1,
18813 		NSMiniaturizableWindowMask = 1 << 2,
18814 		NSResizableWindowMask = 1 << 3,
18815 		NSTexturedBackgroundWindowMask = 1 << 8
18816 	}
18817 
18818 	enum : ulong {
18819 		kCGImageAlphaNone,
18820 		kCGImageAlphaPremultipliedLast,
18821 		kCGImageAlphaPremultipliedFirst,
18822 		kCGImageAlphaLast,
18823 		kCGImageAlphaFirst,
18824 		kCGImageAlphaNoneSkipLast,
18825 		kCGImageAlphaNoneSkipFirst
18826 	}
18827 	enum : ulong {
18828 		kCGBitmapAlphaInfoMask = 0x1F,
18829 		kCGBitmapFloatComponents = (1 << 8),
18830 		kCGBitmapByteOrderMask = 0x7000,
18831 		kCGBitmapByteOrderDefault = (0 << 12),
18832 		kCGBitmapByteOrder16Little = (1 << 12),
18833 		kCGBitmapByteOrder32Little = (2 << 12),
18834 		kCGBitmapByteOrder16Big = (3 << 12),
18835 		kCGBitmapByteOrder32Big = (4 << 12)
18836 	}
18837 	enum CGPathDrawingMode {
18838 		kCGPathFill,
18839 		kCGPathEOFill,
18840 		kCGPathStroke,
18841 		kCGPathFillStroke,
18842 		kCGPathEOFillStroke
18843 	}
18844 	enum objc_AssociationPolicy : size_t {
18845 		OBJC_ASSOCIATION_ASSIGN = 0,
18846 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18847 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18848 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18849 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18850 	}
18851 
18852 	extern(C) {
18853 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18854 		void CGContextRelease(CGContextRef c);
18855 		ubyte* CGBitmapContextGetData(CGContextRef c);
18856 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18857 		size_t CGBitmapContextGetWidth(CGContextRef c);
18858 		size_t CGBitmapContextGetHeight(CGContextRef c);
18859 
18860 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18861 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18862 
18863 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18864 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18865 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18866 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18867 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18868 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
18869 
18870 		void CGContextBeginPath(CGContextRef c);
18871 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18872 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18873 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18874 		void CGContextAddRect(CGContextRef c, CGRect rect);
18875 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18876 		void CGContextSaveGState(CGContextRef c);
18877 		void CGContextRestoreGState(CGContextRef c);
18878 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18879 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18880 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18881 
18882 		void CGImageRelease(CGImageRef image);
18883 	}
18884 } else static assert(0, "Unsupported operating system");
18885 
18886 
18887 version(OSXCocoa) {
18888 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18889 	//
18890 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18891 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18892 	//
18893 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18894 	// Probably won't even fully compile right now
18895 
18896 	private enum double PI = 3.14159265358979323;
18897 
18898 	alias NSWindow NativeWindowHandle;
18899 	alias void delegate(NSid) NativeEventHandler;
18900 
18901 	enum KEY_ESCAPE = 27;
18902 
18903 	mixin template NativeImageImplementation() {
18904 		CGContextRef context;
18905 		ubyte* rawData;
18906 
18907 		final:
18908 
18909 		void convertToRgbaBytes(ubyte[] where) @system {
18910 			assert(where.length == this.width * this.height * 4);
18911 
18912 			// if rawData had a length....
18913 			//assert(rawData.length == where.length);
18914 			for(long idx = 0; idx < where.length; idx += 4) {
18915 				auto alpha = rawData[idx + 3];
18916 				if(alpha == 255) {
18917 					where[idx + 0] = rawData[idx + 0]; // r
18918 					where[idx + 1] = rawData[idx + 1]; // g
18919 					where[idx + 2] = rawData[idx + 2]; // b
18920 					where[idx + 3] = rawData[idx + 3]; // a
18921 				} else {
18922 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18923 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18924 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18925 					where[idx + 3] = rawData[idx + 3]; // a
18926 
18927 				}
18928 			}
18929 		}
18930 
18931 		void setFromRgbaBytes(in ubyte[] where) @system {
18932 			// FIXME: this is probably wrong
18933 			assert(where.length == this.width * this.height * 4);
18934 
18935 			// if rawData had a length....
18936 			//assert(rawData.length == where.length);
18937 			for(long idx = 0; idx < where.length; idx += 4) {
18938 				auto alpha = where[idx + 3];
18939 				if(alpha == 255) {
18940 					rawData[idx + 0] = where[idx + 0]; // r
18941 					rawData[idx + 1] = where[idx + 1]; // g
18942 					rawData[idx + 2] = where[idx + 2]; // b
18943 					rawData[idx + 3] = where[idx + 3]; // a
18944 				} else if(alpha == 0) {
18945 					rawData[idx + 0] = 0;
18946 					rawData[idx + 1] = 0;
18947 					rawData[idx + 2] = 0;
18948 					rawData[idx + 3] = 0;
18949 				} else {
18950 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18951 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18952 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18953 					rawData[idx + 3] = where[idx + 3]; // a
18954 				}
18955 			}
18956 		}
18957 
18958 
18959 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18960 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18961 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18962 			CGColorSpaceRelease(colorSpace);
18963 			rawData = CGBitmapContextGetData(context);
18964 		}
18965 		void dispose() {
18966 			CGContextRelease(context);
18967 		}
18968 
18969 		void setPixel(int x, int y, Color c) @system {
18970 			auto offset = (y * width + x) * 4;
18971 			if (c.a == 255) {
18972 				rawData[offset + 0] = c.r;
18973 				rawData[offset + 1] = c.g;
18974 				rawData[offset + 2] = c.b;
18975 				rawData[offset + 3] = c.a;
18976 			} else {
18977 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18978 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18979 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18980 				rawData[offset + 3] = c.a;
18981 			}
18982 		}
18983 	}
18984 
18985 	mixin template NativeScreenPainterImplementation() {
18986 		CGContextRef context;
18987 		ubyte[4] _outlineComponents;
18988 		NSView view;
18989 
18990 		Pen _activePen;
18991 		Color _fillColor;
18992 		Rectangle _clipRectangle;
18993 		OperatingSystemFont _font;
18994 
18995 		OperatingSystemFont getFont() {
18996 			if(_font is null) {
18997 				static OperatingSystemFont _defaultFont;
18998 				if(_defaultFont is null) {
18999 					_defaultFont = new OperatingSystemFont();
19000 					_defaultFont.loadDefault();
19001 				}
19002 				_font = _defaultFont;
19003 			}
19004 
19005 			return _font;
19006 		}
19007 
19008 		void create(PaintingHandle window) {
19009 			// this.destiny = window;
19010 			if(auto sw = cast(SimpleWindow) this.window) {
19011 				context = sw.drawingContext;
19012 				view = sw.view;
19013 			} else {
19014 				throw new NotYetImplementedException();
19015 			}
19016 		}
19017 
19018 		void dispose() {
19019 			view.setNeedsDisplay(true);
19020 		}
19021 
19022 		bool manualInvalidations;
19023 		void invalidateRect(Rectangle invalidRect) { }
19024 
19025 		// NotYetImplementedException
19026 		void rasterOp(RasterOp op) {
19027 		}
19028 		void setClipRectangle(int, int, int, int) {
19029 		}
19030 		Size textSize(in char[] txt) {
19031 			auto font = getFont();
19032 			return Size(font.stringWidth(txt), font.height());
19033 		}
19034 
19035 		void setFont(OperatingSystemFont font) {
19036 			_font = font;
19037 			//font.font.setInContext(context);
19038 		}
19039 		int fontHeight() {
19040 			auto font = getFont();
19041 			return font.height;
19042 		}
19043 
19044 		// end
19045 
19046 		void pen(Pen pen) {
19047 			_activePen = pen;
19048 			auto color = pen.color; // FIXME
19049 			double alphaComponent = color.a/255.0f;
19050 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
19051 
19052 			double[2] patternBuffer;
19053 			double[] pattern;
19054 			final switch(pen.style) {
19055 				case Pen.Style.Solid:
19056 					pattern = null;
19057 				break;
19058 				case Pen.Style.Dashed:
19059 					patternBuffer[0] = 4;
19060 					patternBuffer[1] = 1;
19061 					pattern = patternBuffer[];
19062 				break;
19063 				case Pen.Style.Dotted:
19064 					patternBuffer[0] = 1;
19065 					patternBuffer[1] = 1;
19066 					pattern = patternBuffer[];
19067 				break;
19068 			}
19069 
19070 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
19071 
19072 			if (color.a != 255) {
19073 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
19074 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
19075 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
19076 				_outlineComponents[3] = color.a;
19077 			} else {
19078 				_outlineComponents[0] = color.r;
19079 				_outlineComponents[1] = color.g;
19080 				_outlineComponents[2] = color.b;
19081 				_outlineComponents[3] = color.a;
19082 			}
19083 		}
19084 
19085 		@property void fillColor(Color color) {
19086 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
19087 		}
19088 
19089 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
19090 		// NotYetImplementedException for upper left/width/height
19091 			auto cgImage = CGBitmapContextCreateImage(image.context);
19092 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
19093 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
19094 			CGImageRelease(cgImage);
19095 		}
19096 
19097 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
19098 		// FIXME: is this efficient?
19099 			auto cgImage = CGBitmapContextCreateImage(s.handle);
19100 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
19101 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
19102 			CGImageRelease(cgImage);
19103 		}
19104 
19105 
19106 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
19107 		// FIXME: alignment
19108 			if (_outlineComponents[3] != 0) {
19109 				CGContextSaveGState(context);
19110 				auto invAlpha = 1.0f/_outlineComponents[3];
19111 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
19112 												  _outlineComponents[1]*invAlpha,
19113 												  _outlineComponents[2]*invAlpha,
19114 												  _outlineComponents[3]/255.0f);
19115 
19116 
19117 
19118 				// FIXME: should we clip it to the bounding box?
19119 				int textHeight = fontHeight;
19120 
19121 				auto lines = text.split('\n');
19122 
19123 				const lineHeight = textHeight;
19124 				textHeight *= lines.length;
19125 
19126 				int cy = y;
19127 
19128 				if(alignment & TextAlignment.VerticalBottom) {
19129 					if(y2 <= 0)
19130 						return;
19131 					auto h = y2 - y;
19132 					if(h > textHeight) {
19133 						cy += h - textHeight;
19134 						cy -= lineHeight / 2;
19135 					}
19136 				} else if(alignment & TextAlignment.VerticalCenter) {
19137 					if(y2 <= 0)
19138 						return;
19139 					auto h = y2 - y;
19140 					if(textHeight < h) {
19141 						cy += (h - textHeight) / 2;
19142 						//cy -= lineHeight / 4;
19143 					}
19144 				}
19145 
19146 				foreach(line; text.split('\n')) {
19147 					int textWidth = this.textSize(line).width;
19148 
19149 					int px = x, py = cy;
19150 
19151 					if(alignment & TextAlignment.Center) {
19152 						if(x2 <= 0)
19153 							return;
19154 						auto w = x2 - x;
19155 						if(w > textWidth)
19156 							px += (w - textWidth) / 2;
19157 					} else if(alignment & TextAlignment.Right) {
19158 						if(x2 <= 0)
19159 							return;
19160 						auto pos = x2 - textWidth;
19161 						if(pos > x)
19162 							px = pos;
19163 					}
19164 
19165 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
19166 
19167 					carry_on:
19168 					cy += lineHeight + 4;
19169 				}
19170 
19171 // auto cfstr = cast(NSid)createCFString(text);
19172 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
19173 // NSPoint(x, y), null);
19174 // CFRelease(cfstr);
19175 				CGContextRestoreGState(context);
19176 			}
19177 		}
19178 
19179 		void drawPixel(int x, int y) {
19180 			auto rawData = CGBitmapContextGetData(context);
19181 			auto width = CGBitmapContextGetWidth(context);
19182 			auto height = CGBitmapContextGetHeight(context);
19183 			auto offset = ((height - y - 1) * width + x) * 4;
19184 			rawData[offset .. offset+4] = _outlineComponents;
19185 		}
19186 
19187 		void drawLine(int x1, int y1, int x2, int y2) {
19188 			CGPoint[2] linePoints;
19189 			linePoints[0] = CGPoint(x1, y1);
19190 			linePoints[1] = CGPoint(x2, y2);
19191 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
19192 		}
19193 
19194 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
19195 			drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded
19196 		}
19197 
19198 		void drawRectangle(int x, int y, int width, int height) {
19199 			CGContextBeginPath(context);
19200 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
19201 			CGContextAddRect(context, rect);
19202 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19203 		}
19204 
19205 		void drawEllipse(int x1, int y1, int x2, int y2) {
19206 			CGContextBeginPath(context);
19207 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
19208 			CGContextAddEllipseInRect(context, rect);
19209 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19210 		}
19211 
19212 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
19213 			// @@@BUG@@@ Does not support elliptic arc (width != height).
19214 			CGContextBeginPath(context);
19215 			int clockwise = 0;
19216 			if(length < 0) {
19217 				clockwise = 1;
19218 				length = -length;
19219 			}
19220 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
19221 							start*PI/(180*64), (start+length)*PI/(180*64), clockwise);
19222 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19223 		}
19224 
19225 		void drawPolygon(Point[] intPoints) {
19226 			CGContextBeginPath(context);
19227 			CGPoint[16] pointsBuffer;
19228 			CGPoint[] points;
19229 			if(intPoints.length <= pointsBuffer.length)
19230 				points = pointsBuffer[0 .. intPoints.length];
19231 			else
19232 				points = new CGPoint[](intPoints.length);
19233 
19234 			foreach(idx, pt; intPoints)
19235 				points[idx] = CGPoint(pt.x, pt.y);
19236 
19237 			CGContextAddLines(context, points.ptr, points.length);
19238 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19239 		}
19240 	}
19241 
19242 	private bool appInitialized = false;
19243 	void initializeApp() {
19244 		if(appInitialized)
19245 			return;
19246 		synchronized {
19247 			if(appInitialized)
19248 				return;
19249 
19250 			auto app = NSApp(); // ensure the is initialized
19251 
19252 			auto dg = AppDelegate.alloc;
19253 			globalAppDelegate = dg;
19254 			NSApp.delegate_ = dg;
19255 
19256 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
19257 
19258 			appInitialized = true;
19259 		}
19260 	}
19261 
19262 	mixin template NativeSimpleWindowImplementation() {
19263 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
19264 			initializeApp();
19265 
19266 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
19267 
19268 			auto window = NSWindow.alloc.initWithContentRect(
19269 				contentRect,
19270 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
19271 				NSBackingStoreType.buffered,
19272 				true
19273 			);
19274 
19275 			SimpleWindow.nativeMapping[cast(void*) window] = this;
19276 
19277 			window.title = MacString(title).borrow;
19278 
19279 			auto dg = SDWindowDelegate.alloc.init;
19280 			dg.simpleWindow = this;
19281 			window.delegate_ = dg;
19282 
19283 			auto view = SDGraphicsView.alloc.init;
19284 			assert(view !is null);
19285 			window.contentView = view;
19286 			this.view = view;
19287 			view.simpleWindow = this;
19288 
19289 			window.center();
19290 
19291 			window.makeKeyAndOrderFront(null);
19292 
19293 			// no need to make a bitmap on mac since everything is double buffered already
19294 
19295 			// create area to draw on.
19296 			createNewDrawingContext(width, height);
19297 
19298 			window.setBackgroundColor(NSColor.whiteColor);
19299 		}
19300 
19301 		void createNewDrawingContext(int width, int height) {
19302 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
19303 			if(this.drawingContext)
19304 				CGContextRelease(this.drawingContext);
19305 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
19306 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
19307 			CGColorSpaceRelease(colorSpace);
19308 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
19309 			auto matrix = CGContextGetTextMatrix(drawingContext);
19310 			matrix.c = -matrix.c;
19311 			matrix.d = -matrix.d;
19312 			CGContextSetTextMatrix(drawingContext, matrix);
19313 
19314 		}
19315 
19316 		void dispose() {
19317 			closeWindow();
19318 			// window.release(); // closing the window does this automatically i think
19319 		}
19320 		void closeWindow() {
19321 			if(timer)
19322 				timer.invalidate();
19323 			window.close();
19324 		}
19325 
19326 		ScreenPainter getPainter(bool manualInvalidations) {
19327 			return ScreenPainter(this, this.window, manualInvalidations);
19328 		}
19329 
19330 		NSWindow window;
19331 		NSTimer timer;
19332 		NSView view;
19333 		CGContextRef drawingContext;
19334 	}
19335 }
19336 
19337 version(without_opengl) {} else
19338 extern(System) nothrow @nogc {
19339 	//enum uint GL_VERSION = 0x1F02;
19340 	//const(char)* glGetString (/*GLenum*/uint);
19341 	version(X11) {
19342 	static if (!SdpyIsUsingIVGLBinds) {
19343 
19344 		enum GLX_X_RENDERABLE = 0x8012;
19345 		enum GLX_DRAWABLE_TYPE = 0x8010;
19346 		enum GLX_RENDER_TYPE = 0x8011;
19347 		enum GLX_X_VISUAL_TYPE = 0x22;
19348 		enum GLX_TRUE_COLOR = 0x8002;
19349 		enum GLX_WINDOW_BIT = 0x00000001;
19350 		enum GLX_RGBA_BIT = 0x00000001;
19351 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
19352 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
19353 		enum GLX_SAMPLES = 0x186a1;
19354 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19355 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19356 	}
19357 
19358 		// GLX_EXT_swap_control
19359 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
19360 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
19361 
19362 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
19363 		extern(System) {
19364 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
19365 		}
19366 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
19367 
19368 		// this made public so we don't have to get it again and again
19369 		public bool glXCreateContextAttribsARB_present () @system {
19370 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
19371 				// get it
19372 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
19373 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
19374 			}
19375 			return (glXCreateContextAttribsARBFn !is null);
19376 		}
19377 
19378 		// this made public so we don't have to get it again and again
19379 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system {
19380 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
19381 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
19382 		}
19383 
19384 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
19385 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
19386 
19387 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
19388 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
19389 			if (_glx_swapInterval_fn is null) {
19390 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
19391 				if (_glx_swapInterval_fn is null) {
19392 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
19393 					return;
19394 				}
19395 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
19396 			}
19397 
19398 			if(glXSwapIntervalMESA is null) {
19399 				// it seems to require both to actually take effect on many computers
19400 				// idk why
19401 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
19402 				if(glXSwapIntervalMESA is null)
19403 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
19404 			}
19405 
19406 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
19407 				glXSwapIntervalMESA(wait ? 1 : 0);
19408 
19409 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
19410 		}
19411 	} else version(Windows) {
19412 	static if (!SdpyIsUsingIVGLBinds) {
19413 	enum GL_TRUE = 1;
19414 	enum GL_FALSE = 0;
19415 
19416 	public void* glbindGetProcAddress (const(char)* name) {
19417 		void* res = wglGetProcAddress(name);
19418 		if (res is null) {
19419 			/+
19420 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
19421 			import core.sys.windows.windef, core.sys.windows.winbase;
19422 			__gshared HINSTANCE dll = null;
19423 			if (dll is null) {
19424 				dll = LoadLibraryA("opengl32.dll");
19425 				if (dll is null) return null; // <32, but idc
19426 			}
19427 			res = GetProcAddress(dll, name);
19428 			+/
19429 			res = GetProcAddress(gl.libHandle, name);
19430 		}
19431 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
19432 		return res;
19433 	}
19434 	}
19435 
19436 
19437  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
19438 	void wglSetVSync(bool wait) {
19439 		if(wglSwapIntervalEXT is null) {
19440 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
19441 			if(wglSwapIntervalEXT is null)
19442 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
19443 		}
19444 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
19445 			return;
19446 
19447 		wglSwapIntervalEXT(wait ? 1 : 0);
19448 	}
19449 
19450 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19451 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19452 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
19453 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
19454 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
19455 
19456 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
19457 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
19458 
19459 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
19460 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
19461 
19462 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
19463 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
19464 
19465 		void wglInitOtherFunctions () {
19466 			if (wglCreateContextAttribsARB is null) {
19467 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
19468 			}
19469 		}
19470 	}
19471 
19472 	static if (!SdpyIsUsingIVGLBinds) {
19473 
19474 	interface GL {
19475 		extern(System) @nogc nothrow:
19476 
19477 		void glGetIntegerv(int, void*);
19478 		void glMatrixMode(int);
19479 		void glPushMatrix();
19480 		void glLoadIdentity();
19481 		void glOrtho(double, double, double, double, double, double);
19482 		void glFrustum(double, double, double, double, double, double);
19483 
19484 		void glPopMatrix();
19485 		void glEnable(int);
19486 		void glDisable(int);
19487 		void glClear(int);
19488 		void glBegin(int);
19489 		void glVertex2f(float, float);
19490 		void glVertex3f(float, float, float);
19491 		void glEnd();
19492 		void glColor3b(byte, byte, byte);
19493 		void glColor3ub(ubyte, ubyte, ubyte);
19494 		void glColor4b(byte, byte, byte, byte);
19495 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
19496 		void glColor3i(int, int, int);
19497 		void glColor3ui(uint, uint, uint);
19498 		void glColor4i(int, int, int, int);
19499 		void glColor4ui(uint, uint, uint, uint);
19500 		void glColor3f(float, float, float);
19501 		void glColor4f(float, float, float, float);
19502 		void glTranslatef(float, float, float);
19503 		void glScalef(float, float, float);
19504 		version(X11) {
19505 			void glSecondaryColor3b(byte, byte, byte);
19506 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
19507 			void glSecondaryColor3i(int, int, int);
19508 			void glSecondaryColor3ui(uint, uint, uint);
19509 			void glSecondaryColor3f(float, float, float);
19510 		}
19511 
19512 		void glDrawElements(int, int, int, void*);
19513 
19514 		void glRotatef(float, float, float, float);
19515 
19516 		uint glGetError();
19517 
19518 		void glDeleteTextures(int, uint*);
19519 
19520 
19521 		void glRasterPos2i(int, int);
19522 		void glDrawPixels(int, int, uint, uint, void*);
19523 		void glClearColor(float, float, float, float);
19524 
19525 
19526 		void glPixelStorei(uint, int);
19527 
19528 		void glGenTextures(uint, uint*);
19529 		void glBindTexture(int, int);
19530 		void glTexParameteri(uint, uint, int);
19531 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19532 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
19533 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
19534 			/*GLsizei*/int width, /*GLsizei*/int height,
19535 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19536 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19537 
19538 		void glLineWidth(int);
19539 
19540 
19541 		void glTexCoord2f(float, float);
19542 		void glVertex2i(int, int);
19543 		void glBlendFunc (int, int);
19544 		void glDepthFunc (int);
19545 		void glViewport(int, int, int, int);
19546 
19547 		void glClearDepth(double);
19548 
19549 		void glReadBuffer(uint);
19550 		void glReadPixels(int, int, int, int, int, int, void*);
19551 
19552 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
19553 
19554 		void glFlush();
19555 		void glFinish();
19556 
19557 		version(Windows) {
19558 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
19559 			HGLRC wglCreateContext(HDC);
19560 			HGLRC wglCreateLayerContext(HDC, int);
19561 			BOOL wglDeleteContext(HGLRC);
19562 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
19563 			HGLRC wglGetCurrentContext();
19564 			HDC wglGetCurrentDC();
19565 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
19566 			PROC wglGetProcAddress(LPCSTR);
19567 			BOOL wglMakeCurrent(HDC, HGLRC);
19568 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
19569 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
19570 			BOOL wglShareLists(HGLRC, HGLRC);
19571 			BOOL wglSwapLayerBuffers(HDC, UINT);
19572 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
19573 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
19574 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19575 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19576 		}
19577 
19578 	}
19579 
19580 	interface GL3 {
19581 		extern(System) @nogc nothrow:
19582 
19583 		void glGenVertexArrays(GLsizei, GLuint*);
19584 		void glBindVertexArray(GLuint);
19585 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
19586 		void glGenerateMipmap(GLenum);
19587 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
19588 		void glStencilMask(GLuint);
19589 		void glStencilFunc(GLenum, GLint, GLuint);
19590 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19591 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19592 		GLuint glCreateProgram();
19593 		GLuint glCreateShader(GLenum);
19594 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
19595 		void glCompileShader(GLuint);
19596 		void glGetShaderiv(GLuint, GLenum, GLint*);
19597 		void glAttachShader(GLuint, GLuint);
19598 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
19599 		void glLinkProgram(GLuint);
19600 		void glGetProgramiv(GLuint, GLenum, GLint*);
19601 		void glDeleteProgram(GLuint);
19602 		void glDeleteShader(GLuint);
19603 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
19604 		void glGenBuffers(GLsizei, GLuint*);
19605 
19606 		void glUniform1f(GLint location, GLfloat v0);
19607 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
19608 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
19609 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
19610 		void glUniform1i(GLint location, GLint v0);
19611 		void glUniform2i(GLint location, GLint v0, GLint v1);
19612 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
19613 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
19614 		void glUniform1ui(GLint location, GLuint v0);
19615 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
19616 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
19617 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
19618 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
19619 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
19620 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
19621 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
19622 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
19623 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
19624 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
19625 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
19626 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
19627 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
19628 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
19629 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
19630 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19631 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19632 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19633 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19634 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19635 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19636 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19637 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19638 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19639 
19640 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
19641 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
19642 		void glDrawArrays(GLenum, GLint, GLsizei);
19643 		void glStencilOp(GLenum, GLenum, GLenum);
19644 		void glUseProgram(GLuint);
19645 		void glCullFace(GLenum);
19646 		void glFrontFace(GLenum);
19647 		void glActiveTexture(GLenum);
19648 		void glBindBuffer(GLenum, GLuint);
19649 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
19650 		void glEnableVertexAttribArray(GLuint);
19651 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
19652 		void glUniform1i(GLint, GLint);
19653 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
19654 		void glDisableVertexAttribArray(GLuint);
19655 		void glDeleteBuffers(GLsizei, const(GLuint)*);
19656 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
19657 		void glLogicOp (GLenum opcode);
19658 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
19659 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
19660 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
19661 		GLenum glCheckFramebufferStatus (GLenum target);
19662 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
19663 	}
19664 
19665 	interface GL4 {
19666 		extern(System) @nogc nothrow:
19667 
19668 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
19669 			/*GLsizei*/int width, /*GLsizei*/int height,
19670 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19671 	}
19672 
19673 	interface GLU {
19674 		extern(System) @nogc nothrow:
19675 
19676 		void gluLookAt(double, double, double, double, double, double, double, double, double);
19677 		void gluPerspective(double, double, double, double);
19678 
19679 		char* gluErrorString(uint);
19680 	}
19681 
19682 
19683 	enum GL_RED = 0x1903;
19684 	enum GL_ALPHA = 0x1906;
19685 
19686 	enum uint GL_FRONT = 0x0404;
19687 
19688 	enum uint GL_BLEND = 0x0be2;
19689 	enum uint GL_LEQUAL = 0x0203;
19690 
19691 
19692 	enum uint GL_RGB = 0x1907;
19693 	enum uint GL_BGRA = 0x80e1;
19694 	enum uint GL_RGBA = 0x1908;
19695 	enum uint GL_RGBA8 = 0x8058;
19696 	enum uint GL_TEXTURE_2D =   0x0DE1;
19697 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
19698 	enum uint GL_NEAREST = 0x2600;
19699 	enum uint GL_LINEAR = 0x2601;
19700 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
19701 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
19702 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
19703 	enum uint GL_REPEAT = 0x2901;
19704 	enum uint GL_CLAMP = 0x2900;
19705 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
19706 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
19707 	enum uint GL_DECAL = 0x2101;
19708 	enum uint GL_MODULATE = 0x2100;
19709 	enum uint GL_TEXTURE_ENV = 0x2300;
19710 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
19711 	enum uint GL_REPLACE = 0x1E01;
19712 	enum uint GL_LIGHTING = 0x0B50;
19713 	enum uint GL_DITHER = 0x0BD0;
19714 
19715 	enum uint GL_NO_ERROR = 0;
19716 
19717 
19718 
19719 	enum int GL_VIEWPORT = 0x0BA2;
19720 	enum int GL_MODELVIEW = 0x1700;
19721 	enum int GL_TEXTURE = 0x1702;
19722 	enum int GL_PROJECTION = 0x1701;
19723 	enum int GL_DEPTH_TEST = 0x0B71;
19724 
19725 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
19726 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
19727 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
19728 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
19729 
19730 	enum int GL_POINTS = 0x0000;
19731 	enum int GL_LINES =  0x0001;
19732 	enum int GL_LINE_LOOP = 0x0002;
19733 	enum int GL_LINE_STRIP = 0x0003;
19734 	enum int GL_TRIANGLES = 0x0004;
19735 	enum int GL_TRIANGLE_STRIP = 5;
19736 	enum int GL_TRIANGLE_FAN = 6;
19737 	enum int GL_QUADS = 7;
19738 	enum int GL_QUAD_STRIP = 8;
19739 	enum int GL_POLYGON = 9;
19740 
19741 	alias GLvoid = void;
19742 	alias GLboolean = ubyte;
19743 	alias GLint = int;
19744 	alias GLuint = uint;
19745 	alias GLenum = uint;
19746 	alias GLchar = char;
19747 	alias GLsizei = int;
19748 	alias GLfloat = float;
19749 	alias GLintptr = size_t;
19750 	alias GLsizeiptr = ptrdiff_t;
19751 
19752 
19753 	enum uint GL_INVALID_ENUM = 0x0500;
19754 
19755 	enum uint GL_ZERO = 0;
19756 	enum uint GL_ONE = 1;
19757 
19758 	enum uint GL_BYTE = 0x1400;
19759 	enum uint GL_UNSIGNED_BYTE = 0x1401;
19760 	enum uint GL_SHORT = 0x1402;
19761 	enum uint GL_UNSIGNED_SHORT = 0x1403;
19762 	enum uint GL_INT = 0x1404;
19763 	enum uint GL_UNSIGNED_INT = 0x1405;
19764 	enum uint GL_FLOAT = 0x1406;
19765 	enum uint GL_2_BYTES = 0x1407;
19766 	enum uint GL_3_BYTES = 0x1408;
19767 	enum uint GL_4_BYTES = 0x1409;
19768 	enum uint GL_DOUBLE = 0x140A;
19769 
19770 	enum uint GL_STREAM_DRAW = 0x88E0;
19771 
19772 	enum uint GL_CCW = 0x0901;
19773 
19774 	enum uint GL_STENCIL_TEST = 0x0B90;
19775 	enum uint GL_SCISSOR_TEST = 0x0C11;
19776 
19777 	enum uint GL_EQUAL = 0x0202;
19778 	enum uint GL_NOTEQUAL = 0x0205;
19779 
19780 	enum uint GL_ALWAYS = 0x0207;
19781 	enum uint GL_KEEP = 0x1E00;
19782 
19783 	enum uint GL_INCR = 0x1E02;
19784 
19785 	enum uint GL_INCR_WRAP = 0x8507;
19786 	enum uint GL_DECR_WRAP = 0x8508;
19787 
19788 	enum uint GL_CULL_FACE = 0x0B44;
19789 	enum uint GL_BACK = 0x0405;
19790 
19791 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
19792 	enum uint GL_VERTEX_SHADER = 0x8B31;
19793 
19794 	enum uint GL_COMPILE_STATUS = 0x8B81;
19795 	enum uint GL_LINK_STATUS = 0x8B82;
19796 
19797 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
19798 
19799 	enum uint GL_STATIC_DRAW = 0x88E4;
19800 
19801 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
19802 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
19803 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
19804 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
19805 
19806 	enum uint GL_GENERATE_MIPMAP = 0x8191;
19807 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
19808 
19809 	enum uint GL_TEXTURE0 = 0x84C0U;
19810 	enum uint GL_TEXTURE1 = 0x84C1U;
19811 
19812 	enum uint GL_ARRAY_BUFFER = 0x8892;
19813 
19814 	enum uint GL_SRC_COLOR = 0x0300;
19815 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
19816 	enum uint GL_SRC_ALPHA = 0x0302;
19817 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
19818 	enum uint GL_DST_ALPHA = 0x0304;
19819 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
19820 	enum uint GL_DST_COLOR = 0x0306;
19821 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
19822 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
19823 
19824 	enum uint GL_INVERT = 0x150AU;
19825 
19826 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
19827 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
19828 
19829 	enum uint GL_FRAMEBUFFER = 0x8D40U;
19830 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
19831 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
19832 
19833 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
19834 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
19835 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
19836 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
19837 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
19838 
19839 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
19840 	enum uint GL_CLEAR = 0x1500U;
19841 	enum uint GL_COPY = 0x1503U;
19842 	enum uint GL_XOR = 0x1506U;
19843 
19844 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
19845 
19846 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
19847 
19848 	}
19849 }
19850 
19851 /++
19852 	History:
19853 		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.
19854 +/
19855 __gshared bool gluSuccessfullyLoaded = true;
19856 
19857 version(without_opengl) {} else {
19858 static if(!SdpyIsUsingIVGLBinds) {
19859 	version(Windows) {
19860 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
19861 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
19862 	} else {
19863 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
19864 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
19865 	}
19866 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
19867 
19868 
19869 	shared static this() {
19870 		gl.loadDynamicLibrary();
19871 
19872 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
19873 		// unless those functions are actually used
19874 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
19875 		glu.loadDynamicLibrary();
19876 	}
19877 }
19878 }
19879 
19880 /++
19881 	Convenience method for converting D arrays to opengl buffer data
19882 
19883 	I would LOVE to overload it with the original glBufferData, but D won't
19884 	let me since glBufferData is a function pointer :(
19885 
19886 	Added: August 25, 2020 (version 8.5)
19887 +/
19888 version(without_opengl) {} else
19889 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
19890 	glBufferData(target, data.length, data.ptr, usage);
19891 }
19892 
19893 /++
19894 	History:
19895 		Added September 1, 2024
19896 +/
19897 version(without_opengl) {} else
19898 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) {
19899 	glBufferSubData(target, offset, data.length, data.ptr);
19900 }
19901 
19902 /++
19903 	Convenience class for using opengl shaders.
19904 
19905 	Ensure that you've loaded opengl 3+ and set your active
19906 	context before trying to use this.
19907 
19908 	Added: August 25, 2020 (version 8.5)
19909 +/
19910 version(without_opengl) {} else
19911 final class OpenGlShader {
19912 	private int shaderProgram_;
19913 	private @property void shaderProgram(int a) {
19914 		shaderProgram_ = a;
19915 	}
19916 	/// Get the program ID for use in OpenGL functions.
19917 	public @property int shaderProgram() {
19918 		return shaderProgram_;
19919 	}
19920 
19921 	/++
19922 
19923 	+/
19924 	static struct Source {
19925 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19926 		string code; ///
19927 	}
19928 
19929 	/++
19930 		Helper method to just compile some shader code and check for errors
19931 		while you do glCreateShader, etc. on the outside yourself.
19932 
19933 		This just does `glShaderSource` and `glCompileShader` for the given code.
19934 
19935 		If you the OpenGlShader class constructor, you never need to call this yourself.
19936 	+/
19937 	static void compile(int sid, Source code) {
19938 		const(char)*[1] buffer;
19939 		int[1] lengthBuffer;
19940 
19941 		buffer[0] = code.code.ptr;
19942 		lengthBuffer[0] = cast(int) code.code.length;
19943 
19944 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19945 		glCompileShader(sid);
19946 
19947 		int success;
19948 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19949 		if(!success) {
19950 			char[512] info;
19951 			int len;
19952 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19953 
19954 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19955 		}
19956 	}
19957 
19958 	/++
19959 		Calls `glLinkProgram` and throws if error a occurs.
19960 
19961 		If you the OpenGlShader class constructor, you never need to call this yourself.
19962 	+/
19963 	static void link(int shaderProgram) {
19964 		glLinkProgram(shaderProgram);
19965 		int success;
19966 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19967 		if(!success) {
19968 			char[512] info;
19969 			int len;
19970 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19971 
19972 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19973 		}
19974 	}
19975 
19976 	/++
19977 		Constructs the shader object by calling `glCreateProgram`, then
19978 		compiling each given [Source], and finally, linking them together.
19979 
19980 		Throws: on compile or link failure.
19981 	+/
19982 	this(Source[] codes...) {
19983 		shaderProgram = glCreateProgram();
19984 
19985 		int[16] shadersBufferStack;
19986 
19987 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19988 			shadersBufferStack[0 .. codes.length] :
19989 			new int[](codes.length);
19990 
19991 		foreach(idx, code; codes) {
19992 			shadersBuffer[idx] = glCreateShader(code.type);
19993 
19994 			compile(shadersBuffer[idx], code);
19995 
19996 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19997 		}
19998 
19999 		link(shaderProgram);
20000 
20001 		foreach(s; shadersBuffer)
20002 			glDeleteShader(s);
20003 	}
20004 
20005 	/// Calls `glUseProgram(this.shaderProgram)`
20006 	void use() {
20007 		glUseProgram(this.shaderProgram);
20008 	}
20009 
20010 	/// Deletes the program.
20011 	void delete_() {
20012 		glDeleteProgram(shaderProgram);
20013 		shaderProgram = 0;
20014 	}
20015 
20016 	/++
20017 		[OpenGlShader.uniforms].name gives you one of these.
20018 
20019 		You can get the id out of it or just assign
20020 	+/
20021 	static struct Uniform {
20022 		/// the id passed to glUniform*
20023 		int id;
20024 
20025 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
20026 		void opAssign(float x, float y, float z, float w) {
20027 			if(id != -1)
20028 			glUniform4f(id, x, y, z, w);
20029 		}
20030 
20031 		void opAssign(float x) {
20032 			if(id != -1)
20033 			glUniform1f(id, x);
20034 		}
20035 
20036 		void opAssign(float x, float y) {
20037 			if(id != -1)
20038 			glUniform2f(id, x, y);
20039 		}
20040 
20041 		void opAssign(T)(T t) {
20042 			t.glUniform(id);
20043 		}
20044 	}
20045 
20046 	static struct UniformsHelper {
20047 		OpenGlShader _shader;
20048 
20049 		@property Uniform opDispatch(string name)() {
20050 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
20051 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
20052 			//if(i == -1)
20053 				//throw new Exception("Could not find uniform " ~ name);
20054 			return Uniform(i);
20055 		}
20056 
20057 		@property void opDispatch(string name, T)(T t) {
20058 			Uniform f = this.opDispatch!name;
20059 			t.glUniform(f);
20060 		}
20061 	}
20062 
20063 	/++
20064 		Gives access to the uniforms through dot access.
20065 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
20066 	+/
20067 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
20068 }
20069 
20070 version(without_opengl) {} else {
20071 /++
20072 	A static container of experimental types and value constructors for opengl 3+ shaders.
20073 
20074 
20075 	You can declare variables like:
20076 
20077 	```
20078 	OGL.vec3f something;
20079 	```
20080 
20081 	But generally it would be used with [OpenGlShader]'s uniform helpers like
20082 
20083 	```
20084 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
20085 	```
20086 
20087 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
20088 
20089 
20090 	History:
20091 		Added December 7, 2021. Not yet stable.
20092 +/
20093 final class OGL {
20094 	static:
20095 
20096 	private template typeFromSpecifier(string specifier) {
20097 		static if(specifier == "f")
20098 			alias typeFromSpecifier = GLfloat;
20099 		else static if(specifier == "i")
20100 			alias typeFromSpecifier = GLint;
20101 		else static if(specifier == "ui")
20102 			alias typeFromSpecifier = GLuint;
20103 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
20104 	}
20105 
20106 	private template CommonType(T...) {
20107 		static if(T.length == 1)
20108 			alias CommonType = T[0];
20109 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
20110 			alias CommonType = CommonType!(C, T[2 .. $]);
20111 	}
20112 
20113 	private template typesToSpecifier(T...) {
20114 		static if(is(CommonType!T == float))
20115 			enum typesToSpecifier = "f";
20116 		else static if(is(CommonType!T == int))
20117 			enum typesToSpecifier = "i";
20118 		else static if(is(CommonType!T == uint))
20119 			enum typesToSpecifier = "ui";
20120 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
20121 	}
20122 
20123 	private template genNames(size_t dim, size_t dim2 = 0) {
20124 		string helper() {
20125 			string s;
20126 			if(dim2) {
20127 				static if(__VERSION__ < 2102)
20128 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug
20129 				else
20130 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;";
20131 			} else {
20132 				if(dim > 0) s ~= "type x = 0;";
20133 				if(dim > 1) s ~= "type y = 0;";
20134 				if(dim > 2) s ~= "type z = 0;";
20135 				if(dim > 3) s ~= "type w = 0;";
20136 			}
20137 
20138 			s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }";
20139 			if(dim2)
20140 				s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }";
20141 
20142 			return s;
20143 		}
20144 
20145 		enum genNames = helper();
20146 	}
20147 
20148 	// there's vec, arrays of vec, mat, and arrays of mat
20149 	template opDispatch(string name)
20150 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
20151 	{
20152 		static if(name[4] == 'x') {
20153 			enum dimX = cast(int) (name[3] - '0');
20154 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
20155 
20156 			enum dimY = cast(int) (name[5] - '0');
20157 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
20158 
20159 			enum isArray = name[$ - 1] == 'v';
20160 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
20161 			alias type = typeFromSpecifier!typeSpecifier;
20162 		} else {
20163 			enum dim = cast(int) (name[3] - '0');
20164 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
20165 			enum isArray = name[$ - 1] == 'v';
20166 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
20167 			alias type = typeFromSpecifier!typeSpecifier;
20168 		}
20169 
20170 		align(1)
20171 		struct opDispatch {
20172 			align(1):
20173 			static if(name[4] == 'x')
20174 				mixin(genNames!(dimX, dimY));
20175 			else
20176 				mixin(genNames!dim);
20177 
20178 			private void glUniform(OpenGlShader.Uniform assignTo) {
20179 				glUniform(assignTo.id);
20180 			}
20181 			private void glUniform(int assignTo) {
20182 				static if(name[4] == 'x') {
20183 					static if(name[3] == name[5]) {
20184 						// import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY);
20185 						mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]);
20186 					} else {
20187 						mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr);
20188 					}
20189 				} else
20190 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
20191 			}
20192 		}
20193 	}
20194 
20195 	auto vec(T...)(T members) {
20196 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
20197 	}
20198 }
20199 
20200 void checkGlError() {
20201 	auto error = glGetError();
20202 	int[] errors;
20203 	string[] errorStrings;
20204 	while(error != GL_NO_ERROR) {
20205 		errors ~= error;
20206 		switch(error) {
20207 			case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break;
20208 			case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break;
20209 			case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break;
20210 			case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break;
20211 			case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break;
20212 			case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break;
20213 			default: errorStrings ~= "idk";
20214 		}
20215 		error = glGetError();
20216 	}
20217 	if(errors.length)
20218 		throw ArsdException!"glGetError"(errors, errorStrings);
20219 }
20220 
20221 /++
20222 	A matrix for simple uses that easily integrates with [OpenGlShader].
20223 
20224 	Might not be useful to you since it only as some simple functions and
20225 	probably isn't that fast.
20226 
20227 	Note it uses an inline static array for its storage, so copying it
20228 	may be expensive.
20229 +/
20230 struct BasicMatrix(int columns, int rows, T = float) {
20231 	static import core.stdc.math;
20232 	static if(is(T == float)) {
20233 		alias cos = core.stdc.math.cosf;
20234 		alias sin = core.stdc.math.sinf;
20235 	} else {
20236 		alias cos = core.stdc.math.cos;
20237 		alias sin = core.stdc.math.sin;
20238 	}
20239 
20240 	T[columns * rows] data = 0.0;
20241 
20242 	/++
20243 
20244 	+/
20245 	this(T[columns * rows] data) {
20246 		this.data = data;
20247 	}
20248 
20249 	/++
20250 		Basic operations that operate *in place*.
20251 	+/
20252 	static if(columns == 4 && rows == 4)
20253 	void translate(T x, T y, T z) {
20254 		BasicMatrix m = [
20255 			1, 0, 0, x,
20256 			0, 1, 0, y,
20257 			0, 0, 1, z,
20258 			0, 0, 0, 1
20259 		];
20260 
20261 		this *= m;
20262 	}
20263 
20264 	/// ditto
20265 	static if(columns == 4 && rows == 4)
20266 	void scale(T x, T y, T z) {
20267 		BasicMatrix m = [
20268 			x, 0, 0, 0,
20269 			0, y, 0, 0,
20270 			0, 0, z, 0,
20271 			0, 0, 0, 1
20272 		];
20273 
20274 		this *= m;
20275 	}
20276 
20277 	/// ditto
20278 	static if(columns == 4 && rows == 4)
20279 	void rotateX(T theta) {
20280 		BasicMatrix m = [
20281 			1,          0,           0, 0,
20282 			0, cos(theta), -sin(theta), 0,
20283 			0, sin(theta),  cos(theta), 0,
20284 			0,          0,           0, 1
20285 		];
20286 
20287 		this *= m;
20288 	}
20289 
20290 	/// ditto
20291 	static if(columns == 4 && rows == 4)
20292 	void rotateY(T theta) {
20293 		BasicMatrix m = [
20294 			 cos(theta), 0,  sin(theta), 0,
20295 			          0, 1,           0, 0,
20296 			-sin(theta), 0,  cos(theta), 0,
20297 			          0, 0,           0, 1
20298 		];
20299 
20300 		this *= m;
20301 	}
20302 
20303 	/// ditto
20304 	static if(columns == 4 && rows == 4)
20305 	void rotateZ(T theta) {
20306 		BasicMatrix m = [
20307 			cos(theta), -sin(theta), 0, 0,
20308 			sin(theta),  cos(theta), 0, 0,
20309 			         0,           0, 1, 0,
20310 				 0,           0, 0, 1
20311 		];
20312 
20313 		this *= m;
20314 	}
20315 
20316 	/++
20317 
20318 	+/
20319 	static if(columns == rows)
20320 	static BasicMatrix identity() {
20321 		BasicMatrix m;
20322 		foreach(i; 0 .. columns)
20323 			m.data[0 + i + i * columns] = 1.0;
20324 		return m;
20325 	}
20326 
20327 	static if(columns == rows)
20328 	void loadIdentity() {
20329 		this = identity();
20330 	}
20331 
20332 	static if(columns == 4 && rows == 4)
20333 	static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) {
20334 		return BasicMatrix([
20335 			2/(r-l),       0,        0, -(r+l)/(r-l),
20336 			      0, 2/(t-b),        0, -(t+b)/(t-b),
20337 			      0,       0, -2/(f-n), -(f+n)/(f-n),
20338 			      0,       0,        0,            1
20339 		]);
20340 	}
20341 
20342 	static if(columns == 4 && rows == 4)
20343 	void loadOrtho(T l, T r, T b, T t, T n, T f) {
20344 		this = ortho(l, r, b, t, n, f);
20345 	}
20346 
20347 	void opOpAssign(string op : "+")(const BasicMatrix rhs) {
20348 		this.data[] += rhs.data;
20349 	}
20350 	void opOpAssign(string op : "-")(const BasicMatrix rhs) {
20351 		this.data[] -= rhs.data;
20352 	}
20353 	void opOpAssign(string op : "*")(const T rhs) {
20354 		this.data[] *= rhs;
20355 	}
20356 	void opOpAssign(string op : "/")(const T rhs) {
20357 		this.data[] /= rhs;
20358 	}
20359 	void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) {
20360 		static assert(columns == rhsRows);
20361 		auto multiplySize = columns;
20362 
20363 		auto tmp = this.data; // copy cuz it is a value type
20364 
20365 		int idx = 0;
20366 		foreach(r; 0 .. rows)
20367 		foreach(c; 0 .. columns) {
20368 			T sum = 0.0;
20369 
20370 			foreach(i; 0 .. multiplySize)
20371 				sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c];
20372 
20373 			tmp[idx++] = sum;
20374 		}
20375 
20376 		this.data = tmp;
20377 	}
20378 }
20379 
20380 unittest {
20381 	auto m = BasicMatrix!(2, 2)([
20382 		1, 2,
20383 		3, 4
20384 	]);
20385 
20386 	auto m2 = BasicMatrix!(2, 2)([
20387 		5, 6,
20388 		7, 8
20389 	]);
20390 
20391 	//import std.conv;
20392 	m *= m2;
20393 	assert(m.data == [
20394 		19, 22,
20395 		43, 50
20396 	]);//, to!string(m.data));
20397 }
20398 
20399 
20400 
20401 class GlObjectBase {
20402 	protected uint _vao;
20403 	protected uint _elementsCount;
20404 
20405 	protected uint element_buffer;
20406 
20407 	void gen() {
20408 		glGenVertexArrays(1, &_vao);
20409 	}
20410 
20411 	void bind() {
20412 		glBindVertexArray(_vao);
20413 	}
20414 
20415 	void dispose() {
20416 		glDeleteVertexArrays(1, &_vao);
20417 	}
20418 
20419 	void draw() {
20420 		bind();
20421 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20422 		glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null);
20423 	}
20424 }
20425 
20426 /++
20427 
20428 +/
20429 class GlObject(T) : GlObjectBase {
20430 	protected uint VBO;
20431 
20432 	this(T[] arr, uint[] indices) {
20433 		gen();
20434 		bind();
20435 
20436 		glGenBuffers(1, &VBO);
20437 		glGenBuffers(1, &element_buffer);
20438 
20439 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
20440 		glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW);
20441 
20442 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20443 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
20444 		_elementsCount = cast(int) indices.length;
20445 
20446 		foreach(int idx, memberName; __traits(allMembers, T)) {
20447 			static if(memberName != "__ctor") {
20448 			static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) {
20449 				glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof);
20450 				glEnableVertexAttribArray(idx);
20451 			} else static assert(0); }
20452 		}
20453 	}
20454 
20455 	static string generateShaderDefinitions() {
20456 		string code;
20457 
20458 		foreach(idx, memberName; __traits(allMembers, T)) {
20459 			// never use stringof ladies and gents it has a LU thing at the end of it
20460 			static if(memberName != "__ctor")
20461 			code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n";
20462 		}
20463 
20464 		return code;
20465 	}
20466 }
20467 
20468 private string typeToGl(T)() {
20469 	static if(is(T == float[4]))
20470 		return "vec4";
20471 	else static if(is(T == float[3]))
20472 		return "vec3";
20473 	else static if(is(T == float[2]))
20474 		return "vec2";
20475 	else static assert(0, T.stringof);
20476 }
20477 
20478 
20479 }
20480 
20481 version(Emscripten) {
20482 
20483 } else version(linux) {
20484 	version(with_eventloop) {} else {
20485 		private int epollFd = -1;
20486 		void prepareEventLoop() {
20487 			if(epollFd != -1)
20488 				return; // already initialized, no need to do it again
20489 			import ep = core.sys.linux.epoll;
20490 
20491 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
20492 			if(epollFd == -1)
20493 				throw new Exception("epoll create failure");
20494 		}
20495 	}
20496 } else version(Posix) {
20497 	void prepareEventLoop() {}
20498 }
20499 
20500 version(X11) {
20501 	import core.stdc.locale : LC_ALL; // rdmd fix
20502 	__gshared bool sdx_isUTF8Locale;
20503 
20504 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
20505 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
20506 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
20507 	// anal magic is here. I (Ketmar) hope you like it.
20508 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
20509 	// always return correct unicode symbols. The detection is here 'cause user can change locale
20510 	// later.
20511 
20512 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
20513 	shared static this () @system {
20514 		if(!librariesSuccessfullyLoaded)
20515 			return;
20516 
20517 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
20518 
20519 		// this doesn't hurt; it may add some locking, but the speed is still
20520 		// allows doing 60 FPS videogames; also, ignore the result, as most
20521 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
20522 		// never seen this failing).
20523 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
20524 
20525 		setlocale(LC_ALL, "");
20526 		// check if out locale is UTF-8
20527 		auto lct = setlocale(LC_CTYPE, null);
20528 		if (lct is null) {
20529 			sdx_isUTF8Locale = false;
20530 		} else {
20531 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
20532 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
20533 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
20534 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
20535 				{
20536 					sdx_isUTF8Locale = true;
20537 					break;
20538 				}
20539 			}
20540 		}
20541 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
20542 	}
20543 }
20544 
20545 class ExperimentalTextComponent2 {
20546 	/+
20547 		Stage 1: get it working monospace
20548 		Stage 2: use proportional font
20549 		Stage 3: allow changes in inline style
20550 		Stage 4: allow new fonts and sizes in the middle
20551 		Stage 5: optimize gap buffer
20552 		Stage 6: optimize layout
20553 		Stage 7: word wrap
20554 		Stage 8: justification
20555 		Stage 9: editing, selection, etc.
20556 
20557 			Operations:
20558 				insert text
20559 				overstrike text
20560 				select
20561 				cut
20562 				modify
20563 	+/
20564 
20565 	/++
20566 		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.
20567 	+/
20568 	this(SimpleWindow window) {
20569 		this.window = window;
20570 	}
20571 
20572 	private SimpleWindow window;
20573 
20574 
20575 	/++
20576 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
20577 		representing the internal parts. The first pass is focused on the x parameter, then the
20578 		renderer is responsible for going back to the parts in the current line and calling
20579 		adjustDownForAscent to change the y params.
20580 	+/
20581 	static interface ComponentRenderHelper {
20582 
20583 		/+
20584 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
20585 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
20586 			to move (adjust y to make room for new line) until you get back to the same position,
20587 			then you can stop - if one thing is unchanged, nothing after it is changed too.
20588 
20589 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
20590 			once you reach something that is unchanged, you can stop.
20591 		+/
20592 
20593 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
20594 
20595 		int ascent() const;
20596 		int descent() const;
20597 
20598 		int advance() const;
20599 
20600 		bool endsWithExplititLineBreak() const;
20601 	}
20602 
20603 	static interface RenderResult {
20604 		/++
20605 			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.
20606 		+/
20607 		void popFront();
20608 		@property bool empty() const;
20609 		@property ComponentRenderHelper front() const;
20610 
20611 		void repositionForNextLine(Point baseline, int availableWidth);
20612 	}
20613 
20614 	static interface ComponentInFlow {
20615 		void draw(ScreenPainter painter);
20616 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
20617 
20618 		bool startsWithExplicitLineBreak() const;
20619 	}
20620 
20621 	static class TextFlowComponent : ComponentInFlow {
20622 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
20623 
20624 		Color foreground;
20625 		Color background;
20626 
20627 		OperatingSystemFont font; // should NEVER be null
20628 
20629 		ubyte attributes; // underline, strike through, display on new block
20630 
20631 		version(Windows)
20632 			const(wchar)[] content;
20633 		else
20634 			const(char)[] content; // this should NEVER have a newline, except at the end
20635 
20636 		RenderedComponent[] rendered; // entirely controlled by [rerender]
20637 
20638 		// could prolly put some spacing around it too like margin / padding
20639 
20640 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
20641 			in { assert(font !is null);
20642 			     assert(!font.isNull); }
20643 			do
20644 		{
20645 			this.foreground = f;
20646 			this.background = b;
20647 			this.font = font;
20648 
20649 			this.attributes = attr;
20650 			version(Windows) {
20651 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
20652 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
20653 				auto buffer = new wchar[](sz);
20654 				this.content = makeWindowsString(c, buffer, conversionFlags);
20655 			} else {
20656 				this.content = c.dup;
20657 			}
20658 		}
20659 
20660 		void draw(ScreenPainter painter) {
20661 			painter.setFont(this.font);
20662 			painter.outlineColor = this.foreground;
20663 			painter.fillColor = Color.transparent;
20664 			foreach(rendered; this.rendered) {
20665 				// the component works in term of baseline,
20666 				// but the painter works in term of upper left bounding box
20667 				// so need to translate that
20668 
20669 				if(this.background.a) {
20670 					painter.fillColor = this.background;
20671 					painter.outlineColor = this.background;
20672 
20673 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
20674 
20675 					painter.outlineColor = this.foreground;
20676 					painter.fillColor = Color.transparent;
20677 				}
20678 
20679 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
20680 
20681 				// FIXME: strike through, underline, highlight selection, etc.
20682 			}
20683 		}
20684 	}
20685 
20686 	// I could split the parts into words on render
20687 	// for easier word-wrap, each one being an unbreakable "inline-block"
20688 	private TextFlowComponent[] parts;
20689 	private int needsRerenderFrom;
20690 
20691 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
20692 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
20693 		parts ~= new TextFlowComponent(f, b, font, attr, c);
20694 	}
20695 
20696 	static struct RenderedComponent {
20697 		int startX;
20698 		int startY;
20699 		short width;
20700 		// 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!
20701 		// for individual chars in here you've gotta process on demand
20702 		version(Windows)
20703 			const(wchar)[] slice;
20704 		else
20705 			const(char)[] slice;
20706 	}
20707 
20708 
20709 	void rerender(Rectangle boundingBox) {
20710 		Point baseline = boundingBox.upperLeft;
20711 
20712 		this.boundingBox.left = boundingBox.left;
20713 		this.boundingBox.top = boundingBox.top;
20714 
20715 		auto remainingParts = parts;
20716 
20717 		int largestX;
20718 
20719 
20720 		foreach(part; parts)
20721 			part.font.prepareContext(window);
20722 		scope(exit)
20723 		foreach(part; parts)
20724 			part.font.releaseContext();
20725 
20726 		calculateNextLine:
20727 
20728 		int nextLineHeight = 0;
20729 		int nextBiggestDescent = 0;
20730 
20731 		foreach(part; remainingParts) {
20732 			auto height = part.font.ascent;
20733 			if(height > nextLineHeight)
20734 				nextLineHeight = height;
20735 			if(part.font.descent > nextBiggestDescent)
20736 				nextBiggestDescent = part.font.descent;
20737 			if(part.content.length && part.content[$-1] == '\n')
20738 				break;
20739 		}
20740 
20741 		baseline.y += nextLineHeight;
20742 		auto lineStart = baseline;
20743 
20744 		while(remainingParts.length) {
20745 			remainingParts[0].rendered = null;
20746 
20747 			bool eol;
20748 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
20749 				eol = true;
20750 
20751 			// FIXME: word wrap
20752 			auto font = remainingParts[0].font;
20753 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
20754 			auto width = font.stringWidth(slice, window);
20755 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
20756 
20757 			remainingParts = remainingParts[1 .. $];
20758 			baseline.x += width;
20759 
20760 			if(eol) {
20761 				baseline.y += nextBiggestDescent;
20762 				if(baseline.x > largestX)
20763 					largestX = baseline.x;
20764 				baseline.x = lineStart.x;
20765 				goto calculateNextLine;
20766 			}
20767 		}
20768 
20769 		if(baseline.x > largestX)
20770 			largestX = baseline.x;
20771 
20772 		this.boundingBox.right = largestX;
20773 		this.boundingBox.bottom = baseline.y;
20774 	}
20775 
20776 	// you must call rerender first!
20777 	void draw(ScreenPainter painter) {
20778 		foreach(part; parts) {
20779 			part.draw(painter);
20780 		}
20781 	}
20782 
20783 	struct IdentifyResult {
20784 		TextFlowComponent part;
20785 		int charIndexInPart;
20786 		int totalCharIndex = -1; // if this is -1, it just means the end
20787 
20788 		Rectangle boundingBox;
20789 	}
20790 
20791 	IdentifyResult identify(Point pt, bool exact = false) {
20792 		if(parts.length == 0)
20793 			return IdentifyResult(null, 0);
20794 
20795 		if(pt.y < boundingBox.top) {
20796 			if(exact)
20797 				return IdentifyResult(null, 1);
20798 			return IdentifyResult(parts[0], 0);
20799 		}
20800 		if(pt.y > boundingBox.bottom) {
20801 			if(exact)
20802 				return IdentifyResult(null, 2);
20803 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
20804 		}
20805 
20806 		int tci = 0;
20807 
20808 		// I should probably like binary search this or something...
20809 		foreach(ref part; parts) {
20810 			foreach(rendered; part.rendered) {
20811 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
20812 				if(rect.contains(pt)) {
20813 					auto x = pt.x - rendered.startX;
20814 					auto estimatedIdx = x / part.font.averageWidth;
20815 
20816 					if(estimatedIdx < 0)
20817 						estimatedIdx = 0;
20818 
20819 					if(estimatedIdx > rendered.slice.length)
20820 						estimatedIdx = cast(int) rendered.slice.length;
20821 
20822 					int idx;
20823 					int x1, x2;
20824 					if(part.font.isMonospace) {
20825 						auto w = part.font.averageWidth;
20826 						if(!exact && x > (estimatedIdx + 1) * w)
20827 							return IdentifyResult(null, 4);
20828 						idx = estimatedIdx;
20829 						x1 = idx * w;
20830 						x2 = (idx + 1) * w;
20831 					} else {
20832 						idx = estimatedIdx;
20833 
20834 						part.font.prepareContext(window);
20835 						scope(exit) part.font.releaseContext();
20836 
20837 						// int iterations;
20838 
20839 						while(true) {
20840 							// iterations++;
20841 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
20842 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
20843 
20844 							x1 += rendered.startX;
20845 							x2 += rendered.startX;
20846 
20847 							if(pt.x < x1) {
20848 								if(idx == 0) {
20849 									if(exact)
20850 										return IdentifyResult(null, 6);
20851 									else
20852 										break;
20853 								}
20854 								idx--;
20855 							} else if(pt.x > x2) {
20856 								idx++;
20857 								if(idx > rendered.slice.length) {
20858 									if(exact)
20859 										return IdentifyResult(null, 5);
20860 									else
20861 										break;
20862 								}
20863 							} else if(pt.x >= x1 && pt.x <= x2) {
20864 								if(idx)
20865 									idx--; // point it at the original index
20866 								break; // we fit
20867 							}
20868 						}
20869 
20870 						// writeln(iterations)
20871 					}
20872 
20873 
20874 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
20875 				}
20876 			}
20877 			tci += cast(int) part.content.length; // FIXME: utf-8?
20878 		}
20879 		return IdentifyResult(null, 3);
20880 	}
20881 
20882 	Rectangle boundingBox; // only set after [rerender]
20883 
20884 	// text will be positioned around the exclusion zone
20885 	static struct ExclusionZone {
20886 
20887 	}
20888 
20889 	ExclusionZone[] exclusionZones;
20890 }
20891 
20892 
20893 // Don't use this yet. When I'm happy with it, I will move it to the
20894 // regular module namespace.
20895 mixin template ExperimentalTextComponent() {
20896 
20897 static:
20898 
20899 	alias Rectangle = arsd.color.Rectangle;
20900 
20901 	struct ForegroundColor {
20902 		Color color;
20903 		alias color this;
20904 
20905 		this(Color c) {
20906 			color = c;
20907 		}
20908 
20909 		this(int r, int g, int b, int a = 255) {
20910 			color = Color(r, g, b, a);
20911 		}
20912 
20913 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
20914 			return ForegroundColor(mixin("Color." ~ s));
20915 		}
20916 	}
20917 
20918 	struct BackgroundColor {
20919 		Color color;
20920 		alias color this;
20921 
20922 		this(Color c) {
20923 			color = c;
20924 		}
20925 
20926 		this(int r, int g, int b, int a = 255) {
20927 			color = Color(r, g, b, a);
20928 		}
20929 
20930 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
20931 			return BackgroundColor(mixin("Color." ~ s));
20932 		}
20933 	}
20934 
20935 	static class InlineElement {
20936 		string text;
20937 
20938 		BlockElement containingBlock;
20939 
20940 		Color color = Color.black;
20941 		Color backgroundColor = Color.transparent;
20942 		ushort styles;
20943 
20944 		string font;
20945 		int fontSize;
20946 
20947 		int lineHeight;
20948 
20949 		void* identifier;
20950 
20951 		Rectangle boundingBox;
20952 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
20953 
20954 		bool isMergeCompatible(InlineElement other) {
20955 			return
20956 				containingBlock is other.containingBlock &&
20957 				color == other.color &&
20958 				backgroundColor == other.backgroundColor &&
20959 				styles == other.styles &&
20960 				font == other.font &&
20961 				fontSize == other.fontSize &&
20962 				lineHeight == other.lineHeight &&
20963 				true;
20964 		}
20965 
20966 		int xOfIndex(size_t index) {
20967 			if(index < letterXs.length)
20968 				return letterXs[index];
20969 			else
20970 				return boundingBox.right;
20971 		}
20972 
20973 		InlineElement clone() {
20974 			auto ie = new InlineElement();
20975 			ie.tupleof = this.tupleof;
20976 			return ie;
20977 		}
20978 
20979 		InlineElement getPreviousInlineElement() {
20980 			InlineElement prev = null;
20981 			foreach(ie; this.containingBlock.parts) {
20982 				if(ie is this)
20983 					break;
20984 				prev = ie;
20985 			}
20986 			if(prev is null) {
20987 				BlockElement pb;
20988 				BlockElement cb = this.containingBlock;
20989 				moar:
20990 				foreach(ie; this.containingBlock.containingLayout.blocks) {
20991 					if(ie is cb)
20992 						break;
20993 					pb = ie;
20994 				}
20995 				if(pb is null)
20996 					return null;
20997 				if(pb.parts.length == 0) {
20998 					cb = pb;
20999 					goto moar;
21000 				}
21001 
21002 				prev = pb.parts[$-1];
21003 
21004 			}
21005 			return prev;
21006 		}
21007 
21008 		InlineElement getNextInlineElement() {
21009 			InlineElement next = null;
21010 			foreach(idx, ie; this.containingBlock.parts) {
21011 				if(ie is this) {
21012 					if(idx + 1 < this.containingBlock.parts.length)
21013 						next = this.containingBlock.parts[idx + 1];
21014 					break;
21015 				}
21016 			}
21017 			if(next is null) {
21018 				BlockElement n;
21019 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
21020 					if(ie is this.containingBlock) {
21021 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
21022 							n = this.containingBlock.containingLayout.blocks[idx + 1];
21023 						break;
21024 					}
21025 				}
21026 				if(n is null)
21027 					return null;
21028 
21029 				if(n.parts.length)
21030 					next = n.parts[0];
21031 				else {} // FIXME
21032 
21033 			}
21034 			return next;
21035 		}
21036 
21037 	}
21038 
21039 	// Block elements are used entirely for positioning inline elements,
21040 	// which are the things that are actually drawn.
21041 	class BlockElement {
21042 		InlineElement[] parts;
21043 		uint alignment;
21044 
21045 		int whiteSpace; // pre, pre-wrap, wrap
21046 
21047 		TextLayout containingLayout;
21048 
21049 		// inputs
21050 		Point where;
21051 		Size minimumSize;
21052 		Size maximumSize;
21053 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
21054 		void* identifier;
21055 
21056 		Rectangle margin;
21057 		Rectangle padding;
21058 
21059 		// outputs
21060 		Rectangle[] boundingBoxes;
21061 	}
21062 
21063 	struct TextIdentifyResult {
21064 		InlineElement element;
21065 		int offset;
21066 
21067 		private TextIdentifyResult fixupNewline() {
21068 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
21069 				offset--;
21070 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
21071 				offset--;
21072 			}
21073 			return this;
21074 		}
21075 	}
21076 
21077 	class TextLayout {
21078 		BlockElement[] blocks;
21079 		Rectangle boundingBox_;
21080 		Rectangle boundingBox() { return boundingBox_; }
21081 		void boundingBox(Rectangle r) {
21082 			if(r != boundingBox_) {
21083 				boundingBox_ = r;
21084 				layoutInvalidated = true;
21085 			}
21086 		}
21087 
21088 		Rectangle contentBoundingBox() {
21089 			Rectangle r;
21090 			foreach(block; blocks)
21091 			foreach(ie; block.parts) {
21092 				if(ie.boundingBox.right > r.right)
21093 					r.right = ie.boundingBox.right;
21094 				if(ie.boundingBox.bottom > r.bottom)
21095 					r.bottom = ie.boundingBox.bottom;
21096 			}
21097 			return r;
21098 		}
21099 
21100 		BlockElement[] getBlocks() {
21101 			return blocks;
21102 		}
21103 
21104 		InlineElement[] getTexts() {
21105 			InlineElement[] elements;
21106 			foreach(block; blocks)
21107 				elements ~= block.parts;
21108 			return elements;
21109 		}
21110 
21111 		string getPlainText() {
21112 			string text;
21113 			foreach(block; blocks)
21114 				foreach(part; block.parts)
21115 					text ~= part.text;
21116 			return text;
21117 		}
21118 
21119 		string getHtml() {
21120 			return null; // FIXME
21121 		}
21122 
21123 		this(Rectangle boundingBox) {
21124 			this.boundingBox = boundingBox;
21125 		}
21126 
21127 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
21128 			auto be = new BlockElement();
21129 			be.containingLayout = this;
21130 			if(after is null)
21131 				blocks ~= be;
21132 			else {
21133 				foreach(idx, b; blocks) {
21134 					if(b is after.containingBlock) {
21135 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
21136 						break;
21137 					}
21138 				}
21139 			}
21140 			return be;
21141 		}
21142 
21143 		void clear() {
21144 			blocks = null;
21145 			selectionStart = selectionEnd = caret = Caret.init;
21146 		}
21147 
21148 		void addText(Args...)(Args args) {
21149 			if(blocks.length == 0)
21150 				addBlock();
21151 
21152 			InlineElement ie = new InlineElement();
21153 			foreach(idx, arg; args) {
21154 				static if(is(typeof(arg) == ForegroundColor))
21155 					ie.color = arg;
21156 				else static if(is(typeof(arg) == TextFormat)) {
21157 					if(arg & 0x8000) // ~TextFormat.something turns it off
21158 						ie.styles &= arg;
21159 					else
21160 						ie.styles |= arg;
21161 				} else static if(is(typeof(arg) == string)) {
21162 					static if(idx == 0 && args.length > 1)
21163 						static assert(0, "Put styles before the string.");
21164 					size_t lastLineIndex;
21165 					foreach(cidx, char a; arg) {
21166 						if(a == '\n') {
21167 							ie.text = arg[lastLineIndex .. cidx + 1];
21168 							lastLineIndex = cidx + 1;
21169 							ie.containingBlock = blocks[$-1];
21170 							blocks[$-1].parts ~= ie.clone;
21171 							ie.text = null;
21172 						} else {
21173 
21174 						}
21175 					}
21176 
21177 					ie.text = arg[lastLineIndex .. $];
21178 					ie.containingBlock = blocks[$-1];
21179 					blocks[$-1].parts ~= ie.clone;
21180 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
21181 				}
21182 			}
21183 
21184 			invalidateLayout();
21185 		}
21186 
21187 		void tryMerge(InlineElement into, InlineElement what) {
21188 			if(!into.isMergeCompatible(what)) {
21189 				return; // cannot merge, different configs
21190 			}
21191 
21192 			// cool, can merge, bring text together...
21193 			into.text ~= what.text;
21194 
21195 			// and remove what
21196 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
21197 				if(what.containingBlock.parts[a] is what) {
21198 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
21199 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
21200 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
21201 
21202 				}
21203 			}
21204 
21205 			// FIXME: ensure no other carets have a reference to it
21206 		}
21207 
21208 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
21209 		TextIdentifyResult identify(int x, int y, bool exact = false) {
21210 			TextIdentifyResult inexactMatch;
21211 			foreach(block; blocks) {
21212 				foreach(part; block.parts) {
21213 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
21214 
21215 						// FIXME binary search
21216 						int tidx;
21217 						int lastX;
21218 						foreach_reverse(idxo, lx; part.letterXs) {
21219 							int idx = cast(int) idxo;
21220 							if(lx <= x) {
21221 								if(lastX && lastX - x < x - lx)
21222 									tidx = idx + 1;
21223 								else
21224 									tidx = idx;
21225 								break;
21226 							}
21227 							lastX = lx;
21228 						}
21229 
21230 						return TextIdentifyResult(part, tidx).fixupNewline;
21231 					} else if(!exact) {
21232 						// we're not in the box, but are we on the same line?
21233 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
21234 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
21235 					}
21236 				}
21237 			}
21238 
21239 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
21240 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
21241 
21242 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
21243 		}
21244 
21245 		void moveCaretToPixelCoordinates(int x, int y) {
21246 			auto result = identify(x, y);
21247 			caret.inlineElement = result.element;
21248 			caret.offset = result.offset;
21249 		}
21250 
21251 		void selectToPixelCoordinates(int x, int y) {
21252 			auto result = identify(x, y);
21253 
21254 			if(y < caretLastDrawnY1) {
21255 				// on a previous line, carat is selectionEnd
21256 				selectionEnd = caret;
21257 
21258 				selectionStart = Caret(this, result.element, result.offset);
21259 			} else if(y > caretLastDrawnY2) {
21260 				// on a later line
21261 				selectionStart = caret;
21262 
21263 				selectionEnd = Caret(this, result.element, result.offset);
21264 			} else {
21265 				// on the same line...
21266 				if(x <= caretLastDrawnX) {
21267 					selectionEnd = caret;
21268 					selectionStart = Caret(this, result.element, result.offset);
21269 				} else {
21270 					selectionStart = caret;
21271 					selectionEnd = Caret(this, result.element, result.offset);
21272 				}
21273 
21274 			}
21275 		}
21276 
21277 
21278 		/// Call this if the inputs change. It will reflow everything
21279 		void redoLayout(ScreenPainter painter) {
21280 			//painter.setClipRectangle(boundingBox);
21281 			auto pos = Point(boundingBox.left, boundingBox.top);
21282 
21283 			int lastHeight;
21284 			void nl() {
21285 				pos.x = boundingBox.left;
21286 				pos.y += lastHeight;
21287 			}
21288 			foreach(block; blocks) {
21289 				nl();
21290 				foreach(part; block.parts) {
21291 					part.letterXs = null;
21292 
21293 					auto size = painter.textSize(part.text);
21294 					version(Windows)
21295 						if(part.text.length && part.text[$-1] == '\n')
21296 							size.height /= 2; // windows counts the new line at the end, but we don't want that
21297 
21298 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
21299 
21300 					foreach(idx, char c; part.text) {
21301 							// FIXME: unicode
21302 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
21303 					}
21304 
21305 					pos.x += size.width;
21306 					if(pos.x >= boundingBox.right) {
21307 						pos.y += size.height;
21308 						pos.x = boundingBox.left;
21309 						lastHeight = 0;
21310 					} else {
21311 						lastHeight = size.height;
21312 					}
21313 
21314 					if(part.text.length && part.text[$-1] == '\n')
21315 						nl();
21316 				}
21317 			}
21318 
21319 			layoutInvalidated = false;
21320 		}
21321 
21322 		bool layoutInvalidated = true;
21323 		void invalidateLayout() {
21324 			layoutInvalidated = true;
21325 		}
21326 
21327 // FIXME: caret can remain sometimes when inserting
21328 // FIXME: inserting at the beginning once you already have something can eff it up.
21329 		void drawInto(ScreenPainter painter, bool focused = false) {
21330 			if(layoutInvalidated)
21331 				redoLayout(painter);
21332 			foreach(block; blocks) {
21333 				foreach(part; block.parts) {
21334 					painter.outlineColor = part.color;
21335 					painter.fillColor = part.backgroundColor;
21336 
21337 					auto pos = part.boundingBox.upperLeft;
21338 					auto size = part.boundingBox.size;
21339 
21340 					painter.drawText(pos, part.text);
21341 					if(part.styles & TextFormat.underline)
21342 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
21343 					if(part.styles & TextFormat.strikethrough)
21344 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
21345 				}
21346 			}
21347 
21348 			// on every redraw, I will force the caret to be
21349 			// redrawn too, in order to eliminate perceived lag
21350 			// when moving around with the mouse.
21351 			eraseCaret(painter);
21352 
21353 			if(focused) {
21354 				highlightSelection(painter);
21355 				drawCaret(painter);
21356 			}
21357 		}
21358 
21359 		Color selectionXorColor = Color(255, 255, 127);
21360 
21361 		void highlightSelection(ScreenPainter painter) {
21362 			if(selectionStart is selectionEnd)
21363 				return; // no selection
21364 
21365 			if(selectionStart.inlineElement is null) return;
21366 			if(selectionEnd.inlineElement is null) return;
21367 
21368 			assert(selectionStart.inlineElement !is null);
21369 			assert(selectionEnd.inlineElement !is null);
21370 
21371 			painter.rasterOp = RasterOp.xor;
21372 			painter.outlineColor = Color.transparent;
21373 			painter.fillColor = selectionXorColor;
21374 
21375 			auto at = selectionStart.inlineElement;
21376 			auto atOffset = selectionStart.offset;
21377 			bool done;
21378 			while(at) {
21379 				auto box = at.boundingBox;
21380 				if(atOffset < at.letterXs.length)
21381 					box.left = at.letterXs[atOffset];
21382 
21383 				if(at is selectionEnd.inlineElement) {
21384 					if(selectionEnd.offset < at.letterXs.length)
21385 						box.right = at.letterXs[selectionEnd.offset];
21386 					done = true;
21387 				}
21388 
21389 				painter.drawRectangle(box.upperLeft, box.width, box.height);
21390 
21391 				if(done)
21392 					break;
21393 
21394 				at = at.getNextInlineElement();
21395 				atOffset = 0;
21396 			}
21397 		}
21398 
21399 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
21400 		bool caretShowingOnScreen = false;
21401 		void drawCaret(ScreenPainter painter) {
21402 			//painter.setClipRectangle(boundingBox);
21403 			int x, y1, y2;
21404 			if(caret.inlineElement is null) {
21405 				x = boundingBox.left;
21406 				y1 = boundingBox.top + 2;
21407 				y2 = boundingBox.top + painter.fontHeight;
21408 			} else {
21409 				x = caret.inlineElement.xOfIndex(caret.offset);
21410 				y1 = caret.inlineElement.boundingBox.top + 2;
21411 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21412 			}
21413 
21414 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
21415 				eraseCaret(painter);
21416 
21417 			painter.pen = Pen(Color.white, 1);
21418 			painter.rasterOp = RasterOp.xor;
21419 			painter.drawLine(
21420 				Point(x, y1),
21421 				Point(x, y2)
21422 			);
21423 			painter.rasterOp = RasterOp.normal;
21424 			caretShowingOnScreen = !caretShowingOnScreen;
21425 
21426 			if(caretShowingOnScreen) {
21427 				caretLastDrawnX = x;
21428 				caretLastDrawnY1 = y1;
21429 				caretLastDrawnY2 = y2;
21430 			}
21431 		}
21432 
21433 		Rectangle caretBoundingBox() {
21434 			int x, y1, y2;
21435 			if(caret.inlineElement is null) {
21436 				x = boundingBox.left;
21437 				y1 = boundingBox.top + 2;
21438 				y2 = boundingBox.top + 16;
21439 			} else {
21440 				x = caret.inlineElement.xOfIndex(caret.offset);
21441 				y1 = caret.inlineElement.boundingBox.top + 2;
21442 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21443 			}
21444 
21445 			return Rectangle(x, y1, x + 1, y2);
21446 		}
21447 
21448 		void eraseCaret(ScreenPainter painter) {
21449 			//painter.setClipRectangle(boundingBox);
21450 			if(!caretShowingOnScreen) return;
21451 			painter.pen = Pen(Color.white, 1);
21452 			painter.rasterOp = RasterOp.xor;
21453 			painter.drawLine(
21454 				Point(caretLastDrawnX, caretLastDrawnY1),
21455 				Point(caretLastDrawnX, caretLastDrawnY2)
21456 			);
21457 
21458 			caretShowingOnScreen = false;
21459 			painter.rasterOp = RasterOp.normal;
21460 		}
21461 
21462 		/// Caret movement api
21463 		/// These should give the user a logical result based on what they see on screen...
21464 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
21465 		void moveUp() {
21466 			if(caret.inlineElement is null) return;
21467 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21468 			auto y = caret.inlineElement.boundingBox.top + 2;
21469 
21470 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21471 			if(y < 0)
21472 				return;
21473 
21474 			auto i = identify(x, y);
21475 
21476 			if(i.element) {
21477 				caret.inlineElement = i.element;
21478 				caret.offset = i.offset;
21479 			}
21480 		}
21481 		void moveDown() {
21482 			if(caret.inlineElement is null) return;
21483 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21484 			auto y = caret.inlineElement.boundingBox.bottom - 2;
21485 
21486 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21487 
21488 			auto i = identify(x, y);
21489 			if(i.element) {
21490 				caret.inlineElement = i.element;
21491 				caret.offset = i.offset;
21492 			}
21493 		}
21494 		void moveLeft() {
21495 			if(caret.inlineElement is null) return;
21496 			if(caret.offset)
21497 				caret.offset--;
21498 			else {
21499 				auto p = caret.inlineElement.getPreviousInlineElement();
21500 				if(p) {
21501 					caret.inlineElement = p;
21502 					if(p.text.length && p.text[$-1] == '\n')
21503 						caret.offset = cast(int) p.text.length - 1;
21504 					else
21505 						caret.offset = cast(int) p.text.length;
21506 				}
21507 			}
21508 		}
21509 		void moveRight() {
21510 			if(caret.inlineElement is null) return;
21511 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
21512 				caret.offset++;
21513 			} else {
21514 				auto p = caret.inlineElement.getNextInlineElement();
21515 				if(p) {
21516 					caret.inlineElement = p;
21517 					caret.offset = 0;
21518 				}
21519 			}
21520 		}
21521 		void moveHome() {
21522 			if(caret.inlineElement is null) return;
21523 			auto x = 0;
21524 			auto y = caret.inlineElement.boundingBox.top + 2;
21525 
21526 			auto i = identify(x, y);
21527 
21528 			if(i.element) {
21529 				caret.inlineElement = i.element;
21530 				caret.offset = i.offset;
21531 			}
21532 		}
21533 		void moveEnd() {
21534 			if(caret.inlineElement is null) return;
21535 			auto x = int.max;
21536 			auto y = caret.inlineElement.boundingBox.top + 2;
21537 
21538 			auto i = identify(x, y);
21539 
21540 			if(i.element) {
21541 				caret.inlineElement = i.element;
21542 				caret.offset = i.offset;
21543 			}
21544 
21545 		}
21546 		void movePageUp(ref Caret caret) {}
21547 		void movePageDown(ref Caret caret) {}
21548 
21549 		void moveDocumentStart(ref Caret caret) {
21550 			if(blocks.length && blocks[0].parts.length)
21551 				caret = Caret(this, blocks[0].parts[0], 0);
21552 			else
21553 				caret = Caret.init;
21554 		}
21555 
21556 		void moveDocumentEnd(ref Caret caret) {
21557 			if(blocks.length) {
21558 				auto parts = blocks[$-1].parts;
21559 				if(parts.length) {
21560 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
21561 				} else {
21562 					caret = Caret.init;
21563 				}
21564 			} else
21565 				caret = Caret.init;
21566 		}
21567 
21568 		void deleteSelection() {
21569 			if(selectionStart is selectionEnd)
21570 				return;
21571 
21572 			if(selectionStart.inlineElement is null) return;
21573 			if(selectionEnd.inlineElement is null) return;
21574 
21575 			assert(selectionStart.inlineElement !is null);
21576 			assert(selectionEnd.inlineElement !is null);
21577 
21578 			auto at = selectionStart.inlineElement;
21579 
21580 			if(selectionEnd.inlineElement is at) {
21581 				// same element, need to chop out
21582 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
21583 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
21584 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
21585 			} else {
21586 				// different elements, we can do it with slicing
21587 				at.text = at.text[0 .. selectionStart.offset];
21588 				if(selectionStart.offset < at.letterXs.length)
21589 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
21590 
21591 				at = at.getNextInlineElement();
21592 
21593 				while(at) {
21594 					if(at is selectionEnd.inlineElement) {
21595 						at.text = at.text[selectionEnd.offset .. $];
21596 						if(selectionEnd.offset < at.letterXs.length)
21597 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
21598 						selectionEnd.offset = 0;
21599 						break;
21600 					} else {
21601 						auto cfd = at;
21602 						cfd.text = null; // delete the whole thing
21603 
21604 						at = at.getNextInlineElement();
21605 
21606 						if(cfd.text.length == 0) {
21607 							// and remove cfd
21608 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
21609 								if(cfd.containingBlock.parts[a] is cfd) {
21610 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
21611 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
21612 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
21613 
21614 								}
21615 							}
21616 						}
21617 					}
21618 				}
21619 			}
21620 
21621 			caret = selectionEnd;
21622 			selectNone();
21623 
21624 			invalidateLayout();
21625 
21626 		}
21627 
21628 		/// Plain text editing api. These work at the current caret inside the selected inline element.
21629 		void insert(in char[] text) {
21630 			foreach(dchar ch; text)
21631 				insert(ch);
21632 		}
21633 		/// ditto
21634 		void insert(dchar ch) {
21635 
21636 			bool selectionDeleted = false;
21637 			if(selectionStart !is selectionEnd) {
21638 				deleteSelection();
21639 				selectionDeleted = true;
21640 			}
21641 
21642 			if(ch == 127) {
21643 				delete_();
21644 				return;
21645 			}
21646 			if(ch == 8) {
21647 				if(!selectionDeleted)
21648 					backspace();
21649 				return;
21650 			}
21651 
21652 			invalidateLayout();
21653 
21654 			if(ch == 13) ch = 10;
21655 			auto e = caret.inlineElement;
21656 			if(e is null) {
21657 				addText("" ~ cast(char) ch) ; // FIXME
21658 				return;
21659 			}
21660 
21661 			if(caret.offset == e.text.length) {
21662 				e.text ~= cast(char) ch; // FIXME
21663 				caret.offset++;
21664 				if(ch == 10) {
21665 					auto c = caret.inlineElement.clone;
21666 					c.text = null;
21667 					c.letterXs = null;
21668 					insertPartAfter(c,e);
21669 					caret = Caret(this, c, 0);
21670 				}
21671 			} else {
21672 				// FIXME cast char sucks
21673 				if(ch == 10) {
21674 					auto c = caret.inlineElement.clone;
21675 					c.text = e.text[caret.offset .. $];
21676 					if(caret.offset < c.letterXs.length)
21677 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
21678 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
21679 					if(caret.offset <= e.letterXs.length) {
21680 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
21681 					}
21682 					insertPartAfter(c,e);
21683 					caret = Caret(this, c, 0);
21684 				} else {
21685 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
21686 					caret.offset++;
21687 				}
21688 			}
21689 		}
21690 
21691 		void insertPartAfter(InlineElement what, InlineElement where) {
21692 			foreach(idx, p; where.containingBlock.parts) {
21693 				if(p is where) {
21694 					if(idx + 1 == where.containingBlock.parts.length)
21695 						where.containingBlock.parts ~= what;
21696 					else
21697 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
21698 					return;
21699 				}
21700 			}
21701 		}
21702 
21703 		void cleanupStructures() {
21704 			for(size_t i = 0; i < blocks.length; i++) {
21705 				auto block = blocks[i];
21706 				for(size_t a = 0; a < block.parts.length; a++) {
21707 					auto part = block.parts[a];
21708 					if(part.text.length == 0) {
21709 						for(size_t b = a; b < block.parts.length - 1; b++)
21710 							block.parts[b] = block.parts[b+1];
21711 						block.parts = block.parts[0 .. $-1];
21712 					}
21713 				}
21714 				if(block.parts.length == 0) {
21715 					for(size_t a = i; a < blocks.length - 1; a++)
21716 						blocks[a] = blocks[a+1];
21717 					blocks = blocks[0 .. $-1];
21718 				}
21719 			}
21720 		}
21721 
21722 		void backspace() {
21723 			try_again:
21724 			auto e = caret.inlineElement;
21725 			if(e is null)
21726 				return;
21727 			if(caret.offset == 0) {
21728 				auto prev = e.getPreviousInlineElement();
21729 				if(prev is null)
21730 					return;
21731 				auto newOffset = cast(int) prev.text.length;
21732 				tryMerge(prev, e);
21733 				caret.inlineElement = prev;
21734 				caret.offset = prev is null ? 0 : newOffset;
21735 
21736 				goto try_again;
21737 			} else if(caret.offset == e.text.length) {
21738 				e.text = e.text[0 .. $-1];
21739 				caret.offset--;
21740 			} else {
21741 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
21742 				caret.offset--;
21743 			}
21744 			//cleanupStructures();
21745 
21746 			invalidateLayout();
21747 		}
21748 		void delete_() {
21749 			if(selectionStart !is selectionEnd)
21750 				deleteSelection();
21751 			else {
21752 				auto before = caret;
21753 				moveRight();
21754 				if(caret != before) {
21755 					backspace();
21756 				}
21757 			}
21758 
21759 			invalidateLayout();
21760 		}
21761 		void overstrike() {}
21762 
21763 		/// Selection API. See also: caret movement.
21764 		void selectAll() {
21765 			moveDocumentStart(selectionStart);
21766 			moveDocumentEnd(selectionEnd);
21767 		}
21768 		bool selectNone() {
21769 			if(selectionStart != selectionEnd) {
21770 				selectionStart = selectionEnd = Caret.init;
21771 				return true;
21772 			}
21773 			return false;
21774 		}
21775 
21776 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
21777 		/// They will modify the current selection if there is one and will splice one in if needed.
21778 		void changeAttributes() {}
21779 
21780 
21781 		/// Text search api. They manipulate the selection and/or caret.
21782 		void findText(string text) {}
21783 		void findIndex(size_t textIndex) {}
21784 
21785 		// sample event handlers
21786 
21787 		void handleEvent(KeyEvent event) {
21788 			//if(event.type == KeyEvent.Type.KeyPressed) {
21789 
21790 			//}
21791 		}
21792 
21793 		void handleEvent(dchar ch) {
21794 
21795 		}
21796 
21797 		void handleEvent(MouseEvent event) {
21798 
21799 		}
21800 
21801 		bool contentEditable; // can it be edited?
21802 		bool contentCaretable; // is there a caret/cursor that moves around in there?
21803 		bool contentSelectable; // selectable?
21804 
21805 		Caret caret;
21806 		Caret selectionStart;
21807 		Caret selectionEnd;
21808 
21809 		bool insertMode;
21810 	}
21811 
21812 	struct Caret {
21813 		TextLayout layout;
21814 		InlineElement inlineElement;
21815 		int offset;
21816 	}
21817 
21818 	enum TextFormat : ushort {
21819 		// decorations
21820 		underline = 1,
21821 		strikethrough = 2,
21822 
21823 		// font selectors
21824 
21825 		bold = 0x4000 | 1, // weight 700
21826 		light = 0x4000 | 2, // weight 300
21827 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
21828 		// bold | light is really invalid but should give weight 500
21829 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
21830 
21831 		italic = 0x4000 | 8,
21832 		smallcaps = 0x4000 | 16,
21833 	}
21834 
21835 	void* findFont(string family, int weight, TextFormat formats) {
21836 		return null;
21837 	}
21838 
21839 }
21840 
21841 /++
21842 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21843 
21844 	History:
21845 		Added February 19, 2021
21846 +/
21847 /// Group: drag_and_drop
21848 interface DropHandler {
21849 	/++
21850 		Called when the drag enters the handler's area.
21851 	+/
21852 	DragAndDropAction dragEnter(DropPackage*);
21853 	/++
21854 		Called when the drag leaves the handler's area or is
21855 		cancelled. You should free your resources when this is called.
21856 	+/
21857 	void dragLeave();
21858 	/++
21859 		Called continually as the drag moves over the handler's area.
21860 
21861 		Returns: feedback to the dragger
21862 	+/
21863 	DropParameters dragOver(Point pt);
21864 	/++
21865 		The user dropped the data and you should process it now. You can
21866 		access the data through the given [DropPackage].
21867 	+/
21868 	void drop(scope DropPackage*);
21869 	/++
21870 		Called when the drop is complete. You should free whatever temporary
21871 		resources you were using. It is often reasonable to simply forward
21872 		this call to [dragLeave].
21873 	+/
21874 	void finish();
21875 
21876 	/++
21877 		Parameters returned by [DropHandler.drop].
21878 	+/
21879 	static struct DropParameters {
21880 		/++
21881 			Acceptable action over this area.
21882 		+/
21883 		DragAndDropAction action;
21884 		/++
21885 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
21886 
21887 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
21888 		+/
21889 		Rectangle consistentWithin;
21890 	}
21891 }
21892 
21893 /++
21894 	History:
21895 		Added February 19, 2021
21896 +/
21897 /// Group: drag_and_drop
21898 enum DragAndDropAction {
21899 	none = 0,
21900 	copy,
21901 	move,
21902 	link,
21903 	ask,
21904 	custom
21905 }
21906 
21907 /++
21908 	An opaque structure representing dropped data. It contains
21909 	private, platform-specific data that your `drop` function
21910 	should simply forward.
21911 
21912 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21913 
21914 	History:
21915 		Added February 19, 2021
21916 +/
21917 /// Group: drag_and_drop
21918 struct DropPackage {
21919 	/++
21920 		Lists the available formats as magic numbers. You should compare these
21921 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
21922 		understand the passed data.
21923 	+/
21924 	DraggableData.FormatId[] availableFormats() {
21925 		version(X11) {
21926 			return xFormats;
21927 		} else version(Windows) {
21928 			if(pDataObj is null)
21929 				return null;
21930 
21931 			typeof(return) ret;
21932 
21933 			IEnumFORMATETC ef;
21934 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
21935 				FORMATETC fmt;
21936 				ULONG fetched;
21937 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
21938 					if(fetched == 0)
21939 						break;
21940 
21941 					if(fmt.lindex != -1)
21942 						continue;
21943 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
21944 						continue;
21945 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
21946 						continue;
21947 
21948 					ret ~= fmt.cfFormat;
21949 				}
21950 			}
21951 
21952 			return ret;
21953 		} else throw new NotYetImplementedException();
21954 	}
21955 
21956 	/++
21957 		Gets data from the drop and optionally accepts it.
21958 
21959 		Returns:
21960 			void because the data is fed asynchronously through the `dg` parameter.
21961 
21962 		Params:
21963 			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.
21964 
21965 			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.
21966 
21967 			Calling `getData` again after accepting a drop is not permitted.
21968 
21969 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
21970 
21971 			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.
21972 
21973 		Throws:
21974 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
21975 
21976 		History:
21977 			Included in first release of [DropPackage].
21978 	+/
21979 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
21980 		version(X11) {
21981 
21982 			auto display = XDisplayConnection.get();
21983 			auto selectionAtom = GetAtom!"XdndSelection"(display);
21984 			auto best = format;
21985 
21986 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
21987 
21988 				XDisplay* display;
21989 				Atom selectionAtom;
21990 				DraggableData.FormatId best;
21991 				DraggableData.FormatId format;
21992 				void delegate(scope ubyte[] data) dg;
21993 				DragAndDropAction acceptedAction;
21994 				Window sourceWindow;
21995 				SimpleWindow win;
21996 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
21997 					this.display = display;
21998 					this.win = win;
21999 					this.sourceWindow = sourceWindow;
22000 					this.format = format;
22001 					this.selectionAtom = selectionAtom;
22002 					this.best = best;
22003 					this.dg = dg;
22004 					this.acceptedAction = acceptedAction;
22005 				}
22006 
22007 
22008 				mixin X11GetSelectionHandler_Basics;
22009 
22010 				void handleData(Atom target, in ubyte[] data) {
22011 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
22012 
22013 					dg(cast(ubyte[]) data);
22014 
22015 					if(acceptedAction != DragAndDropAction.none) {
22016 						auto display = XDisplayConnection.get;
22017 
22018 						XClientMessageEvent xclient;
22019 
22020 						xclient.type = EventType.ClientMessage;
22021 						xclient.window = sourceWindow;
22022 						xclient.message_type = GetAtom!"XdndFinished"(display);
22023 						xclient.format = 32;
22024 						xclient.data.l[0] = win.impl.window;
22025 						xclient.data.l[1] = 1; // drop successful
22026 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
22027 
22028 						XSendEvent(
22029 							display,
22030 							sourceWindow,
22031 							false,
22032 							EventMask.NoEventMask,
22033 							cast(XEvent*) &xclient
22034 						);
22035 
22036 						XFlush(display);
22037 					}
22038 				}
22039 
22040 				Atom findBestFormat(Atom[] answer) {
22041 					Atom best = None;
22042 					foreach(option; answer) {
22043 						if(option == format) {
22044 							best = option;
22045 							break;
22046 						}
22047 						/*
22048 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
22049 							best = option;
22050 							break;
22051 						} else if(option == XA_STRING) {
22052 							best = option;
22053 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
22054 							best = option;
22055 						}
22056 						*/
22057 					}
22058 					return best;
22059 				}
22060 			}
22061 
22062 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
22063 
22064 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
22065 
22066 		} else version(Windows) {
22067 
22068 			// clean up like DragLeave
22069 			// pass effect back up
22070 
22071 			FORMATETC t;
22072 			assert(format >= 0 && format <= ushort.max);
22073 			t.cfFormat = cast(ushort) format;
22074 			t.lindex = -1;
22075 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22076 			t.tymed = TYMED.TYMED_HGLOBAL;
22077 
22078 			STGMEDIUM m;
22079 
22080 			if(pDataObj.GetData(&t, &m) != S_OK) {
22081 				// fail
22082 			} else {
22083 				// succeed, take the data and clean up
22084 
22085 				// FIXME: ensure it is legit HGLOBAL
22086 				auto handle = m.hGlobal;
22087 
22088 				if(handle) {
22089 					auto sz = GlobalSize(handle);
22090 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
22091 						scope(exit) GlobalUnlock(handle);
22092 						scope(exit) GlobalFree(handle);
22093 
22094 						auto data = ptr[0 .. sz];
22095 
22096 						dg(data);
22097 					}
22098 				}
22099 			}
22100 		}
22101 	}
22102 
22103 	private:
22104 
22105 	version(X11) {
22106 		SimpleWindow win;
22107 		Window sourceWindow;
22108 		Time dataTimestamp;
22109 
22110 		Atom[] xFormats;
22111 	}
22112 	version(Windows) {
22113 		IDataObject pDataObj;
22114 	}
22115 }
22116 
22117 /++
22118 	A generic helper base class for making a drop handler with a preference list of custom types.
22119 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
22120 	droppers too.
22121 
22122 	It assumes the whole window it used, but you can subclass to change that.
22123 
22124 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22125 
22126 	History:
22127 		Added February 19, 2021
22128 +/
22129 /// Group: drag_and_drop
22130 class GenericDropHandlerBase : DropHandler {
22131 	// no fancy state here so no need to do anything here
22132 	void finish() { }
22133 	void dragLeave() { }
22134 
22135 	private DragAndDropAction acceptedAction;
22136 	private DraggableData.FormatId acceptedFormat;
22137 	private void delegate(scope ubyte[]) acceptedHandler;
22138 
22139 	struct FormatHandler {
22140 		DraggableData.FormatId format;
22141 		void delegate(scope ubyte[]) handler;
22142 	}
22143 
22144 	protected abstract FormatHandler[] formatHandlers();
22145 
22146 	DragAndDropAction dragEnter(DropPackage* pkg) {
22147 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
22148 		foreach(fmt; formatHandlers())
22149 		foreach(f; pkg.availableFormats())
22150 			if(f == fmt.format) {
22151 				acceptedFormat = f;
22152 				acceptedHandler = fmt.handler;
22153 				return acceptedAction = DragAndDropAction.copy;
22154 			}
22155 		return acceptedAction = DragAndDropAction.none;
22156 	}
22157 	DropParameters dragOver(Point pt) {
22158 		return DropParameters(acceptedAction);
22159 	}
22160 
22161 	void drop(scope DropPackage* dropPackage) {
22162 		if(!acceptedFormat || acceptedHandler is null) {
22163 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
22164 			return; // prolly shouldn't happen anyway...
22165 		}
22166 
22167 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
22168 	}
22169 }
22170 
22171 /++
22172 	A simple handler for making your window accept drops of plain text.
22173 
22174 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22175 
22176 	History:
22177 		Added February 22, 2021
22178 +/
22179 /// Group: drag_and_drop
22180 class TextDropHandler : GenericDropHandlerBase {
22181 	private void delegate(in char[] text) dg;
22182 
22183 	/++
22184 
22185 	+/
22186 	this(void delegate(in char[] text) dg) {
22187 		this.dg = dg;
22188 	}
22189 
22190 	protected override FormatHandler[] formatHandlers() {
22191 		version(X11)
22192 			return [
22193 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
22194 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
22195 			];
22196 		else version(Windows)
22197 			return [
22198 				FormatHandler(CF_UNICODETEXT, &translator),
22199 			];
22200 		else throw new NotYetImplementedException();
22201 	}
22202 
22203 	private void translator(scope ubyte[] data) {
22204 		version(X11)
22205 			dg(cast(char[]) data);
22206 		else version(Windows)
22207 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
22208 	}
22209 }
22210 
22211 /++
22212 	A simple handler for making your window accept drops of files, issued to you as file names.
22213 
22214 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22215 
22216 	History:
22217 		Added February 22, 2021
22218 +/
22219 /// Group: drag_and_drop
22220 
22221 class FilesDropHandler : GenericDropHandlerBase {
22222 	private void delegate(in char[][]) dg;
22223 
22224 	/++
22225 
22226 	+/
22227 	this(void delegate(in char[][] fileNames) dg) {
22228 		this.dg = dg;
22229 	}
22230 
22231 	protected override FormatHandler[] formatHandlers() {
22232 		version(X11)
22233 			return [
22234 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
22235 			];
22236 		else version(Windows)
22237 			return [
22238 				FormatHandler(CF_HDROP, &translator),
22239 			];
22240 		else throw new NotYetImplementedException();
22241 	}
22242 
22243 	private void translator(scope ubyte[] data) @system {
22244 		version(X11) {
22245 			char[] listString = cast(char[]) data;
22246 			char[][16] buffer;
22247 			int count;
22248 			char[][] result = buffer[];
22249 
22250 			void commit(char[] s) {
22251 				if(count == result.length)
22252 					result.length += 16;
22253 				if(s.length > 7 && s[0 ..7] == "file://")
22254 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
22255 				result[count++] = s;
22256 			}
22257 
22258 			size_t last;
22259 			foreach(idx, char c; listString) {
22260 				if(c == '\n') {
22261 					commit(listString[last .. idx - 1]); // a \r
22262 					last = idx + 1; // a \n
22263 				}
22264 			}
22265 
22266 			if(last < listString.length) {
22267 				commit(listString[last .. $]);
22268 			}
22269 
22270 			// FIXME: they are uris now, should I translate it to local file names?
22271 			// of course the host name is supposed to be there cuz of X rokking...
22272 
22273 			dg(result[0 .. count]);
22274 		} else version(Windows) {
22275 
22276 			static struct DROPFILES {
22277 				DWORD pFiles;
22278 				POINT pt;
22279 				BOOL  fNC;
22280 				BOOL  fWide;
22281 			}
22282 
22283 
22284 			const(char)[][16] buffer;
22285 			int count;
22286 			const(char)[][] result = buffer[];
22287 			size_t last;
22288 
22289 			void commitA(in char[] stuff) {
22290 				if(count == result.length)
22291 					result.length += 16;
22292 				result[count++] = stuff;
22293 			}
22294 
22295 			void commitW(in wchar[] stuff) {
22296 				commitA(makeUtf8StringFromWindowsString(stuff));
22297 			}
22298 
22299 			void magic(T)(T chars) {
22300 				size_t idx;
22301 				while(chars[idx]) {
22302 					last = idx;
22303 					while(chars[idx]) {
22304 						idx++;
22305 					}
22306 					static if(is(T == char*))
22307 						commitA(chars[last .. idx]);
22308 					else
22309 						commitW(chars[last .. idx]);
22310 					idx++;
22311 				}
22312 			}
22313 
22314 			auto df = cast(DROPFILES*) data.ptr;
22315 			if(df.fWide) {
22316 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
22317 				magic(chars);
22318 			} else {
22319 				char* chars = cast(char*) (data.ptr + df.pFiles);
22320 				magic(chars);
22321 			}
22322 			dg(result[0 .. count]);
22323 		}
22324 		else throw new NotYetImplementedException();
22325 	}
22326 }
22327 
22328 /++
22329 	Interface to describe data being dragged. See also [draggable] helper function.
22330 
22331 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22332 
22333 	History:
22334 		Added February 19, 2021
22335 +/
22336 interface DraggableData {
22337 	version(X11)
22338 		alias FormatId = Atom;
22339 	else
22340 		alias FormatId = uint;
22341 	/++
22342 		Gets the platform-specific FormatId associated with the given named format.
22343 
22344 		This may be a MIME type, but may also be other various strings defined by the
22345 		programs you want to interoperate with.
22346 
22347 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
22348 		and convert it to some particular type for you.
22349 	+/
22350 	static FormatId getFormatId(string name)() {
22351 		version(X11)
22352 			return GetAtom!name(XDisplayConnection.get);
22353 		else version(Windows) {
22354 			static UINT cache;
22355 			if(!cache)
22356 				cache = RegisterClipboardFormatA(name);
22357 			return cache;
22358 		} else
22359 			throw new NotYetImplementedException();
22360 	}
22361 
22362 	/++
22363 		Looks up a string to represent the name for the given format, if there is one.
22364 
22365 		You should avoid using this function because it is slow. It is provided more for
22366 		debugging than for primary use.
22367 	+/
22368 	static string getFormatName(FormatId format) {
22369 		version(X11) {
22370 			if(format == 0)
22371 				return "None";
22372 			else
22373 				return getAtomName(format, XDisplayConnection.get);
22374 		} else version(Windows) {
22375 			switch(format) {
22376 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
22377 				case CF_DIBV5: return "CF_DIBV5";
22378 				case CF_RIFF: return "CF_RIFF";
22379 				case CF_WAVE: return "CF_WAVE";
22380 				case CF_HDROP: return "CF_HDROP";
22381 				default:
22382 					char[1024] name;
22383 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
22384 					return name[0 .. count].idup;
22385 			}
22386 		} else throw new NotYetImplementedException();
22387 	}
22388 
22389 	FormatId[] availableFormats();
22390 	// Return the slice of data you filled, empty slice if done.
22391 	// this is to support the incremental thing
22392 	ubyte[] getData(FormatId format, return scope ubyte[] data);
22393 
22394 	size_t dataLength(FormatId format);
22395 }
22396 
22397 /++
22398 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22399 
22400 	History:
22401 		Added February 19, 2021
22402 +/
22403 DraggableData draggable(string s) {
22404 	version(X11)
22405 	return new class X11SetSelectionHandler_Text, DraggableData {
22406 		this() {
22407 			super(s);
22408 		}
22409 
22410 		override FormatId[] availableFormats() {
22411 			return X11SetSelectionHandler_Text.availableFormats();
22412 		}
22413 
22414 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
22415 			return X11SetSelectionHandler_Text.getData(format, data);
22416 		}
22417 
22418 		size_t dataLength(FormatId format) {
22419 			return s.length;
22420 		}
22421 	};
22422 	else version(Windows)
22423 	return new class DraggableData {
22424 		FormatId[] availableFormats() {
22425 			return [CF_UNICODETEXT];
22426 		}
22427 
22428 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
22429 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
22430 		}
22431 
22432 		size_t dataLength(FormatId format) {
22433 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
22434 		}
22435 	};
22436 	else
22437 	throw new NotYetImplementedException();
22438 }
22439 
22440 /++
22441 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22442 
22443 	History:
22444 		Added February 19, 2021
22445 +/
22446 /// Group: drag_and_drop
22447 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
22448 in {
22449 	assert(window !is null);
22450 	assert(handler !is null);
22451 }
22452 do
22453 {
22454 	version(X11) {
22455 		auto sh = cast(X11SetSelectionHandler) handler;
22456 		if(sh is null) {
22457 			// gotta make my own adapter.
22458 			sh = new class X11SetSelectionHandler {
22459 				mixin X11SetSelectionHandler_Basics;
22460 
22461 				Atom[] availableFormats() { return handler.availableFormats(); }
22462 				ubyte[] getData(Atom format, return scope ubyte[] data) {
22463 					return handler.getData(format, data);
22464 				}
22465 
22466 				// since the drop selection is only ever used once it isn't important
22467 				// to reset it.
22468 				void done() {}
22469 			};
22470 		}
22471 		return doDragDropX11(window, sh, action);
22472 	} else version(Windows) {
22473 		return doDragDropWindows(window, handler, action);
22474 	} else throw new NotYetImplementedException();
22475 }
22476 
22477 version(Windows)
22478 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
22479 	IDataObject obj = new class IDataObject {
22480 		ULONG refCount;
22481 		ULONG AddRef() {
22482 			return ++refCount;
22483 		}
22484 		ULONG Release() {
22485 			return --refCount;
22486 		}
22487 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22488 			if (IID_IUnknown == *riid) {
22489 				*ppv = cast(void*) cast(IUnknown) this;
22490 			}
22491 			else if (IID_IDataObject == *riid) {
22492 				*ppv = cast(void*) cast(IDataObject) this;
22493 			}
22494 			else {
22495 				*ppv = null;
22496 				return E_NOINTERFACE;
22497 			}
22498 
22499 			AddRef();
22500 			return NOERROR;
22501 		}
22502 
22503 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
22504 			//  writeln("Advise");
22505 			return E_NOTIMPL;
22506 		}
22507 		HRESULT DUnadvise(DWORD dwConnection) {
22508 			return E_NOTIMPL;
22509 		}
22510 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
22511 			//  writeln("EnumDAdvise");
22512 			return OLE_E_ADVISENOTSUPPORTED;
22513 		}
22514 		// tell what formats it supports
22515 
22516 		FORMATETC[] types;
22517 		this() {
22518 			FORMATETC t;
22519 			foreach(ty; handler.availableFormats()) {
22520 				assert(ty <= ushort.max && ty >= 0);
22521 				t.cfFormat = cast(ushort) ty;
22522 				t.lindex = -1;
22523 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22524 				t.tymed = TYMED.TYMED_HGLOBAL;
22525 			}
22526 			types ~= t;
22527 		}
22528 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
22529 			if(dwDirection == DATADIR.DATADIR_GET) {
22530 				*ppenumFormatEtc = new class IEnumFORMATETC {
22531 					ULONG refCount;
22532 					ULONG AddRef() {
22533 						return ++refCount;
22534 					}
22535 					ULONG Release() {
22536 						return --refCount;
22537 					}
22538 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22539 						if (IID_IUnknown == *riid) {
22540 							*ppv = cast(void*) cast(IUnknown) this;
22541 						}
22542 						else if (IID_IEnumFORMATETC == *riid) {
22543 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
22544 						}
22545 						else {
22546 							*ppv = null;
22547 							return E_NOINTERFACE;
22548 						}
22549 
22550 						AddRef();
22551 						return NOERROR;
22552 					}
22553 
22554 
22555 					int pos;
22556 					this() {
22557 						pos = 0;
22558 					}
22559 
22560 					HRESULT Clone(IEnumFORMATETC* ppenum) {
22561 						// writeln("clone");
22562 						return E_NOTIMPL; // FIXME
22563 					}
22564 
22565 					// Caller is responsible for freeing memory
22566 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
22567 						// fetched may be null if celt is one
22568 						if(celt != 1)
22569 							return E_NOTIMPL; // FIXME
22570 
22571 						if(celt + pos > types.length)
22572 							return S_FALSE;
22573 
22574 						*rgelt = types[pos++];
22575 
22576 						if(pceltFetched !is null)
22577 							*pceltFetched = 1;
22578 
22579 						// writeln("ok celt ", celt);
22580 						return S_OK;
22581 					}
22582 
22583 					HRESULT Reset() {
22584 						pos = 0;
22585 						return S_OK;
22586 					}
22587 
22588 					HRESULT Skip(ULONG celt) {
22589 						if(celt + pos <= types.length) {
22590 							pos += celt;
22591 							return S_OK;
22592 						}
22593 						return S_FALSE;
22594 					}
22595 				};
22596 
22597 				return S_OK;
22598 			} else
22599 				return E_NOTIMPL;
22600 		}
22601 		// given a format, return the format you'd prefer to use cuz it is identical
22602 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
22603 			// FIXME: prolly could be better but meh
22604 			// writeln("gcf: ", *pformatectIn);
22605 			*pformatetcOut = *pformatectIn;
22606 			return S_OK;
22607 		}
22608 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22609 			foreach(ty; types) {
22610 				if(ty == *pformatetcIn) {
22611 					auto format = ty.cfFormat;
22612 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
22613 					STGMEDIUM medium;
22614 					medium.tymed = TYMED.TYMED_HGLOBAL;
22615 
22616 					auto sz = handler.dataLength(format);
22617 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
22618 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
22619 					if(auto data = cast(wchar*) GlobalLock(handle)) {
22620 						auto slice = data[0 .. sz];
22621 						scope(exit)
22622 							GlobalUnlock(handle);
22623 
22624 						handler.getData(format, cast(ubyte[]) slice[]);
22625 					}
22626 
22627 
22628 					medium.hGlobal = handle; // FIXME
22629 					*pmedium = medium;
22630 					return S_OK;
22631 				}
22632 			}
22633 			return DV_E_FORMATETC;
22634 		}
22635 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22636 			// writeln("GDH: ", *pformatetcIn);
22637 			return E_NOTIMPL; // FIXME
22638 		}
22639 		HRESULT QueryGetData(FORMATETC* pformatetc) {
22640 			auto search = *pformatetc;
22641 			search.tymed &= TYMED.TYMED_HGLOBAL;
22642 			foreach(ty; types)
22643 				if(ty == search) {
22644 					// writeln("QueryGetData ", search, " ", types[0]);
22645 					return S_OK;
22646 				}
22647 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
22648 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
22649 			}
22650 			return S_FALSE;
22651 		}
22652 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
22653 			//  writeln("SetData: ");
22654 			return E_NOTIMPL;
22655 		}
22656 	};
22657 
22658 
22659 	IDropSource src = new class IDropSource {
22660 		ULONG refCount;
22661 		ULONG AddRef() {
22662 			return ++refCount;
22663 		}
22664 		ULONG Release() {
22665 			return --refCount;
22666 		}
22667 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22668 			if (IID_IUnknown == *riid) {
22669 				*ppv = cast(void*) cast(IUnknown) this;
22670 			}
22671 			else if (IID_IDropSource == *riid) {
22672 				*ppv = cast(void*) cast(IDropSource) this;
22673 			}
22674 			else {
22675 				*ppv = null;
22676 				return E_NOINTERFACE;
22677 			}
22678 
22679 			AddRef();
22680 			return NOERROR;
22681 		}
22682 
22683 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
22684 			if(fEscapePressed)
22685 				return DRAGDROP_S_CANCEL;
22686 			if(!(grfKeyState & MK_LBUTTON))
22687 				return DRAGDROP_S_DROP;
22688 			return S_OK;
22689 		}
22690 
22691 		int GiveFeedback(uint dwEffect) {
22692 			return DRAGDROP_S_USEDEFAULTCURSORS;
22693 		}
22694 	};
22695 
22696 	DWORD effect;
22697 
22698 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
22699 
22700 	DROPEFFECT de = win32DragAndDropAction(action);
22701 
22702 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
22703 	// but still prolly a FIXME
22704 
22705 	auto ret = DoDragDrop(obj, src, de, &effect);
22706 	/+
22707 	if(ret == DRAGDROP_S_DROP)
22708 		writeln("drop ", effect);
22709 	else if(ret == DRAGDROP_S_CANCEL)
22710 		writeln("cancel");
22711 	else if(ret == S_OK)
22712 		writeln("ok");
22713 	else writeln(ret);
22714 	+/
22715 
22716 	return ret;
22717 }
22718 
22719 version(Windows)
22720 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
22721 	DROPEFFECT de;
22722 
22723 	with(DragAndDropAction)
22724 	with(DROPEFFECT)
22725 	final switch(action) {
22726 		case none: de = DROPEFFECT_NONE; break;
22727 		case copy: de = DROPEFFECT_COPY; break;
22728 		case move: de = DROPEFFECT_MOVE; break;
22729 		case link: de = DROPEFFECT_LINK; break;
22730 		case ask: throw new Exception("ask not implemented yet");
22731 		case custom: throw new Exception("custom not implemented yet");
22732 	}
22733 
22734 	return de;
22735 }
22736 
22737 
22738 /++
22739 	History:
22740 		Added February 19, 2021
22741 +/
22742 /// Group: drag_and_drop
22743 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
22744 	version(X11) {
22745 		auto display = XDisplayConnection.get;
22746 
22747 		Atom atom = 5; // right???
22748 
22749 		XChangeProperty(
22750 			display,
22751 			window.impl.window,
22752 			GetAtom!"XdndAware"(display),
22753 			XA_ATOM,
22754 			32 /* bits */,
22755 			PropModeReplace,
22756 			&atom,
22757 			1);
22758 
22759 		window.dropHandler = handler;
22760 	} else version(Windows) {
22761 
22762 		initDnd();
22763 
22764 		auto dropTarget = new class (handler) IDropTarget {
22765 			DropHandler handler;
22766 			this(DropHandler handler) {
22767 				this.handler = handler;
22768 			}
22769 			ULONG refCount;
22770 			ULONG AddRef() {
22771 				return ++refCount;
22772 			}
22773 			ULONG Release() {
22774 				return --refCount;
22775 			}
22776 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22777 				if (IID_IUnknown == *riid) {
22778 					*ppv = cast(void*) cast(IUnknown) this;
22779 				}
22780 				else if (IID_IDropTarget == *riid) {
22781 					*ppv = cast(void*) cast(IDropTarget) this;
22782 				}
22783 				else {
22784 					*ppv = null;
22785 					return E_NOINTERFACE;
22786 				}
22787 
22788 				AddRef();
22789 				return NOERROR;
22790 			}
22791 
22792 
22793 			// ///////////////////
22794 
22795 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22796 				DropPackage dropPackage = DropPackage(pDataObj);
22797 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
22798 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
22799 			}
22800 
22801 			HRESULT DragLeave() {
22802 				handler.dragLeave();
22803 				// release the IDataObject if needed
22804 				return S_OK;
22805 			}
22806 
22807 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22808 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
22809 
22810 				*pdwEffect = win32DragAndDropAction(res.action);
22811 				// same as DragEnter basically
22812 				return S_OK;
22813 			}
22814 
22815 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22816 				DropPackage pkg = DropPackage(pDataObj);
22817 				handler.drop(&pkg);
22818 
22819 				return S_OK;
22820 			}
22821 		};
22822 		// Windows can hold on to the handler and try to call it
22823 		// during which time the GC can't see it. so important to
22824 		// manually manage this. At some point i'll FIXME and make
22825 		// all my com instances manually managed since they supposed
22826 		// to respect the refcount.
22827 		import core.memory;
22828 		GC.addRoot(cast(void*) dropTarget);
22829 
22830 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
22831 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
22832 
22833 		window.dropHandler = handler;
22834 	} else throw new NotYetImplementedException();
22835 }
22836 
22837 
22838 
22839 static if(UsingSimpledisplayX11) {
22840 
22841 enum _NET_WM_STATE_ADD = 1;
22842 enum _NET_WM_STATE_REMOVE = 0;
22843 enum _NET_WM_STATE_TOGGLE = 2;
22844 
22845 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
22846 void demandAttention(SimpleWindow window, bool needs = true) {
22847 	demandAttention(window.impl.window, needs);
22848 }
22849 
22850 /// ditto
22851 void demandAttention(Window window, bool needs = true) {
22852 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
22853 }
22854 
22855 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
22856 	auto display = XDisplayConnection.get();
22857 	if(atom == None)
22858 		return; // non-failure error
22859 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
22860 
22861 	XClientMessageEvent xclient;
22862 
22863 	xclient.type = EventType.ClientMessage;
22864 	xclient.window = window;
22865 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
22866 	xclient.format = 32;
22867 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
22868 	xclient.data.l[1] = atom;
22869 	xclient.data.l[2] = atom2;
22870 	xclient.data.l[3] = 1;
22871 	// [3] == source. 0 == unknown, 1 == app, 2 == else
22872 
22873 	XSendEvent(
22874 		display,
22875 		RootWindow(display, DefaultScreen(display)),
22876 		false,
22877 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
22878 		cast(XEvent*) &xclient
22879 	);
22880 
22881 	/+
22882 	XChangeProperty(
22883 		display,
22884 		window.impl.window,
22885 		GetAtom!"_NET_WM_STATE"(display),
22886 		XA_ATOM,
22887 		32 /* bits */,
22888 		PropModeAppend,
22889 		&atom,
22890 		1);
22891 	+/
22892 }
22893 
22894 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
22895 	Atom actionAtom;
22896 	with(DragAndDropAction)
22897 	final switch(action) {
22898 		case none: actionAtom = None; break;
22899 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
22900 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
22901 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
22902 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
22903 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
22904 	}
22905 
22906 	return actionAtom;
22907 }
22908 
22909 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
22910 	// FIXME: I need to show user feedback somehow.
22911 	auto display = XDisplayConnection.get;
22912 
22913 	auto actionAtom = dndActionAtom(display, action);
22914 	assert(actionAtom, "Don't use action none to accept a drop");
22915 
22916 	setX11Selection!"XdndSelection"(window, handler, null);
22917 
22918 	auto oldKeyHandler = window.handleKeyEvent;
22919 	scope(exit) window.handleKeyEvent = oldKeyHandler;
22920 
22921 	auto oldCharHandler = window.handleCharEvent;
22922 	scope(exit) window.handleCharEvent = oldCharHandler;
22923 
22924 	auto oldMouseHandler = window.handleMouseEvent;
22925 	scope(exit) window.handleMouseEvent = oldMouseHandler;
22926 
22927 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
22928 
22929 	import core.sys.posix.sys.time;
22930 	timeval tv;
22931 	gettimeofday(&tv, null);
22932 
22933 	Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 );
22934 
22935 	Time lastMouseTimestamp;
22936 
22937 	bool dnding = true;
22938 	Window lastIn = None;
22939 
22940 	void leave() {
22941 		if(lastIn == None)
22942 			return;
22943 
22944 		XEvent ev;
22945 		ev.xclient.type = EventType.ClientMessage;
22946 		ev.xclient.window = lastIn;
22947 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
22948 		ev.xclient.format = 32;
22949 		ev.xclient.data.l[0] = window.impl.window;
22950 
22951 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22952 		XFlush(display);
22953 
22954 		lastIn = None;
22955 	}
22956 
22957 	void enter(Window w) {
22958 		assert(lastIn == None);
22959 
22960 		lastIn = w;
22961 
22962 		XEvent ev;
22963 		ev.xclient.type = EventType.ClientMessage;
22964 		ev.xclient.window = lastIn;
22965 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
22966 		ev.xclient.format = 32;
22967 		ev.xclient.data.l[0] = window.impl.window;
22968 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
22969 
22970 		auto types = handler.availableFormats();
22971 		assert(types.length > 0);
22972 
22973 		ev.xclient.data.l[2] = types[0];
22974 		if(types.length > 1)
22975 			ev.xclient.data.l[3] = types[1];
22976 		if(types.length > 2)
22977 			ev.xclient.data.l[4] = types[2];
22978 
22979 		// FIXME: other types?!?!? and make sure we skip TARGETS
22980 
22981 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22982 		XFlush(display);
22983 	}
22984 
22985 	void position(int rootX, int rootY) {
22986 		assert(lastIn != None);
22987 
22988 		XEvent ev;
22989 		ev.xclient.type = EventType.ClientMessage;
22990 		ev.xclient.window = lastIn;
22991 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
22992 		ev.xclient.format = 32;
22993 		ev.xclient.data.l[0] = window.impl.window;
22994 		ev.xclient.data.l[1] = 0; // reserved
22995 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
22996 		ev.xclient.data.l[3] = dataTimestamp;
22997 		ev.xclient.data.l[4] = actionAtom;
22998 
22999 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
23000 		XFlush(display);
23001 
23002 	}
23003 
23004 	void drop() {
23005 		XEvent ev;
23006 		ev.xclient.type = EventType.ClientMessage;
23007 		ev.xclient.window = lastIn;
23008 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
23009 		ev.xclient.format = 32;
23010 		ev.xclient.data.l[0] = window.impl.window;
23011 		ev.xclient.data.l[1] = 0; // reserved
23012 		ev.xclient.data.l[2] = dataTimestamp;
23013 
23014 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
23015 		XFlush(display);
23016 
23017 		lastIn = None;
23018 		dnding = false;
23019 	}
23020 
23021 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
23022 	// but idk if i should...
23023 
23024 	window.setEventHandlers(
23025 		delegate(KeyEvent ev) {
23026 			if(ev.pressed == true && ev.key == Key.Escape) {
23027 				// cancel
23028 				dnding = false;
23029 			}
23030 		},
23031 		delegate(MouseEvent ev) {
23032 			if(ev.timestamp < lastMouseTimestamp)
23033 				return;
23034 
23035 			lastMouseTimestamp = ev.timestamp;
23036 
23037 			if(ev.type == MouseEventType.motion) {
23038 				auto display = XDisplayConnection.get;
23039 				auto root = RootWindow(display, DefaultScreen(display));
23040 
23041 				Window topWindow;
23042 				int rootX, rootY;
23043 
23044 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
23045 
23046 				if(topWindow == None)
23047 					return;
23048 
23049 				top:
23050 				if(auto result = topWindow in eligibility) {
23051 					auto dropWindow = *result;
23052 					if(dropWindow == None) {
23053 						leave();
23054 						return;
23055 					}
23056 
23057 					if(dropWindow != lastIn) {
23058 						leave();
23059 						enter(dropWindow);
23060 						position(rootX, rootY);
23061 					} else {
23062 						position(rootX, rootY);
23063 					}
23064 				} else {
23065 					// determine eligibility
23066 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
23067 					if(data.length == 1) {
23068 						// in case there is no WM or it isn't reparenting
23069 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
23070 					} else {
23071 
23072 						Window tryScanChildren(Window search, int maxRecurse) {
23073 							// could be reparenting window manager, so gotta check the next few children too
23074 							Window child;
23075 							int x;
23076 							int y;
23077 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
23078 
23079 							if(child == None)
23080 								return None;
23081 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
23082 							if(data.length == 1) {
23083 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
23084 							} else {
23085 								if(maxRecurse)
23086 									return tryScanChildren(child, maxRecurse - 1);
23087 								else
23088 									return None;
23089 							}
23090 
23091 						}
23092 
23093 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
23094 						auto topResult = tryScanChildren(topWindow, 3);
23095 						// it is easy to have a false negative due to the mouse going over a WM
23096 						// child window like the close button if separate from the frame... so I
23097 						// can't really cache negatives, :(
23098 						if(topResult != None) {
23099 							eligibility[topWindow] = topResult;
23100 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
23101 						}
23102 					}
23103 
23104 				}
23105 
23106 			} else if(ev.type == MouseEventType.buttonReleased) {
23107 				drop();
23108 				dnding = false;
23109 			}
23110 		}
23111 	);
23112 
23113 	window.grabInput();
23114 	scope(exit)
23115 		window.releaseInputGrab();
23116 
23117 
23118 	EventLoop.get.run(() => dnding);
23119 
23120 	return 0;
23121 }
23122 
23123 /// X-specific
23124 TrueColorImage getWindowNetWmIcon(Window window) {
23125 	try {
23126 		auto display = XDisplayConnection.get;
23127 
23128 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
23129 
23130 		if (data.length > arch_ulong.sizeof * 2) {
23131 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
23132 			// these are an array of rgba images that we have to convert into pixmaps ourself
23133 
23134 			int width = cast(int) meta[0];
23135 			int height = cast(int) meta[1];
23136 
23137 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
23138 
23139 			static if(arch_ulong.sizeof == 4) {
23140 				bytes = bytes[0 .. width * height * 4];
23141 				alias imageData = bytes;
23142 			} else static if(arch_ulong.sizeof == 8) {
23143 				bytes = bytes[0 .. width * height * 8];
23144 				auto imageData = new ubyte[](4 * width * height);
23145 			} else static assert(0);
23146 
23147 
23148 
23149 			// this returns ARGB. Remember it is little-endian so
23150 			//                                         we have BGRA
23151 			// our thing uses RGBA, which in little endian, is ABGR
23152 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
23153 				auto r = bytes[idx + 2];
23154 				auto g = bytes[idx + 1];
23155 				auto b = bytes[idx + 0];
23156 				auto a = bytes[idx + 3];
23157 
23158 				imageData[idx2 + 0] = r;
23159 				imageData[idx2 + 1] = g;
23160 				imageData[idx2 + 2] = b;
23161 				imageData[idx2 + 3] = a;
23162 			}
23163 
23164 			return new TrueColorImage(width, height, imageData);
23165 		}
23166 
23167 		return null;
23168 	} catch(Exception e) {
23169 		return null;
23170 	}
23171 }
23172 
23173 } /* UsingSimpledisplayX11 */
23174 
23175 
23176 void loadBinNameToWindowClassName () {
23177 	import core.stdc.stdlib : realloc;
23178 	version(linux) {
23179 		// args[0] MAY be empty, so we'll just use this
23180 		import core.sys.posix.unistd : readlink;
23181 		char[1024] ebuf = void; // 1KB should be enough for everyone!
23182 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
23183 		if (len < 1) return;
23184 	} else /*version(Windows)*/ {
23185 		import core.runtime : Runtime;
23186 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
23187 		auto ebuf = Runtime.args[0];
23188 		auto len = ebuf.length;
23189 	}
23190 	auto pos = len;
23191 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
23192 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
23193 	if (sdpyWindowClassStr is null) return; // oops
23194 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
23195 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
23196 }
23197 
23198 /++
23199 	An interface representing a font that is drawn with custom facilities.
23200 
23201 	You might want [OperatingSystemFont] instead, which represents
23202 	a font loaded and drawn by functions native to the operating system.
23203 
23204 	WARNING: I might still change this.
23205 +/
23206 interface DrawableFont : MeasurableFont {
23207 	/++
23208 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
23209 
23210 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
23211 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
23212 		fill color, but that's up to the implementation.
23213 	+/
23214 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
23215 
23216 	/++
23217 		Requests that the given string is added to the image cache. You should only do this rarely, but
23218 		if you have a string that you know will be used over and over again, adding it to a cache can
23219 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
23220 		to implement this as a do-nothing method).
23221 	+/
23222 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
23223 }
23224 
23225 /++
23226 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
23227 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
23228 
23229 	You should also consider [OperatingSystemFont], which loads and draws a font with
23230 	facilities native to the user's operating system. You might also consider
23231 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
23232 	of game, as they have their own ways to draw text too.
23233 
23234 	Be warned: this can be slow, especially on remote connections to the X server, since
23235 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
23236 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
23237 	experiment in your specific case.
23238 
23239 	Please note that the return type of [DrawableFont] also includes an implementation of
23240 	[MeasurableFont].
23241 +/
23242 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
23243 	import arsd.ttf;
23244 	static class ArsdTtfFont : DrawableFont {
23245 		TtfFont font;
23246 		int size;
23247 		this(in ubyte[] data, int size) {
23248 			font = TtfFont(data);
23249 			this.size = size;
23250 
23251 
23252 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
23253 			int ascent_, descent_, line_gap;
23254 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
23255 
23256 			int advance, lsb;
23257 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
23258 			xWidth = cast(int) (advance * scale);
23259 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
23260 			MWidth = cast(int) (advance * scale);
23261 		}
23262 
23263 		private int ascent_;
23264 		private int descent_;
23265 		private int xWidth;
23266 		private int MWidth;
23267 
23268 		bool isMonospace() {
23269 			return xWidth == MWidth;
23270 		}
23271 		int averageWidth() {
23272 			return xWidth;
23273 		}
23274 		int height() {
23275 			return size;
23276 		}
23277 		int ascent() {
23278 			return ascent_;
23279 		}
23280 		int descent() {
23281 			return descent_;
23282 		}
23283 
23284 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
23285 			int width, height;
23286 			font.getStringSize(s, size, width, height);
23287 			return width;
23288 		}
23289 
23290 
23291 
23292 		Sprite[string] cache;
23293 
23294 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
23295 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
23296 			cache[text] = sprite;
23297 		}
23298 
23299 		Image stringToImage(Color fg, Color bg, in char[] text) {
23300 			int width, height;
23301 			auto data = font.renderString(text, size, width, height);
23302 			auto image = new TrueColorImage(width, height);
23303 			int pos = 0;
23304 			foreach(y; 0 .. height)
23305 			foreach(x; 0 .. width) {
23306 				fg.a = data[0];
23307 				bg.a = 255;
23308 				auto color = alphaBlend(fg, bg);
23309 				image.imageData.bytes[pos++] = color.r;
23310 				image.imageData.bytes[pos++] = color.g;
23311 				image.imageData.bytes[pos++] = color.b;
23312 				image.imageData.bytes[pos++] = data[0];
23313 				data = data[1 .. $];
23314 			}
23315 			assert(data.length == 0);
23316 
23317 			return Image.fromMemoryImage(image);
23318 		}
23319 
23320 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
23321 			Sprite sprite = (text in cache) ? *(text in cache) : null;
23322 
23323 			auto fg = painter.impl._outlineColor;
23324 			auto bg = painter.impl._fillColor;
23325 
23326 			if(sprite !is null) {
23327 				auto w = cast(SimpleWindow) painter.window;
23328 				assert(w !is null);
23329 
23330 				sprite.drawAt(painter, upperLeft);
23331 			} else {
23332 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
23333 			}
23334 		}
23335 	}
23336 
23337 	return new ArsdTtfFont(data, size);
23338 }
23339 
23340 class NotYetImplementedException : Exception {
23341 	this(string file = __FILE__, size_t line = __LINE__) {
23342 		super("Not yet implemented", file, line);
23343 	}
23344 }
23345 
23346 ///
23347 __gshared bool librariesSuccessfullyLoaded = true;
23348 ///
23349 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
23350 
23351 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
23352 	// mixin(staticForeachReplacement!Iface);
23353 	static foreach(name; __traits(derivedMembers, Iface))
23354 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23355 
23356 	void loadDynamicLibrary() @nogc {
23357 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23358 	}
23359 
23360 	void loadDynamicLibraryForReal() {
23361 		foreach(name; __traits(derivedMembers, Iface)) {
23362 			mixin("alias tmp = " ~ name ~ ";");
23363 			tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
23364 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
23365 		}
23366 	}
23367 }
23368 
23369 /+
23370 private const(char)[] staticForeachReplacement(Iface)() pure {
23371 /*
23372 	// just this for gdc 9....
23373 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
23374 
23375 	static foreach(name; __traits(derivedMembers, Iface))
23376 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23377 */
23378 
23379 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
23380 	size_t pos;
23381 
23382 	void append(in char[] what) {
23383 		if(pos + what.length > code.length)
23384 			code.length = (code.length * 3) / 2;
23385 		code[pos .. pos + what.length] = what[];
23386 		pos += what.length;
23387 	}
23388 
23389 	foreach(name; __traits(derivedMembers, Iface)) {
23390 		append(`__gshared typeof(&__traits(getMember, Iface, "`);
23391 		append(name);
23392 		append(`")) `);
23393 		append(name);
23394 		append(";");
23395 	}
23396 
23397 	return code[0 .. pos];
23398 }
23399 +/
23400 
23401 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
23402 	//mixin(staticForeachReplacement!Iface);
23403 	static foreach(name; __traits(derivedMembers, Iface))
23404 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23405 
23406 	private __gshared void* libHandle;
23407 	private __gshared bool attempted;
23408 
23409 	void loadDynamicLibrary() @nogc {
23410 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23411 	}
23412 
23413 	bool loadAttempted() {
23414 		return attempted;
23415 	}
23416 	bool loadSuccessful() {
23417 		return libHandle !is null;
23418 	}
23419 
23420 	void loadDynamicLibraryForReal() {
23421 		attempted = true;
23422 		version(Posix) {
23423 			import core.sys.posix.dlfcn;
23424 			version(OSX) {
23425 				version(X11)
23426 					libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
23427 				else
23428 					libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
23429 			} else {
23430 				version(apitrace) {
23431 					if(library == "GL" || library == "GLX") {
23432 						libHandle = dlopen("glxtrace.so", RTLD_NOW);
23433 						if(libHandle is null) {
23434 							assert(false, "Failed to load `glxtrace.so`.");
23435 						}
23436 					}
23437 					else {
23438 						libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23439 					}
23440 				}
23441 				else {
23442 					libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23443 				}
23444 				if(libHandle is null) {
23445 					libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
23446 				}
23447 			}
23448 
23449 			static void* loadsym(void* l, const char* name) {
23450 				import core.stdc.stdlib;
23451 				if(l is null)
23452 					return &abort;
23453 				return dlsym(l, name);
23454 			}
23455 		} else version(Windows) {
23456 			import core.sys.windows.winbase;
23457 			libHandle = LoadLibrary(library ~ ".dll");
23458 			static void* loadsym(void* l, const char* name) {
23459 				import core.stdc.stdlib;
23460 				if(l is null)
23461 					return &abort;
23462 				return GetProcAddress(l, name);
23463 			}
23464 		}
23465 		if(libHandle is null) {
23466 			success = false;
23467 			//throw new Exception("load failure of library " ~ library);
23468 		}
23469 		foreach(name; __traits(derivedMembers, Iface)) {
23470 			mixin("alias tmp = " ~ name ~ ";");
23471 			tmp = cast(typeof(tmp)) loadsym(libHandle, name);
23472 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
23473 		}
23474 	}
23475 
23476 	void unloadDynamicLibrary() {
23477 		version(Posix) {
23478 			import core.sys.posix.dlfcn;
23479 			dlclose(libHandle);
23480 		} else version(Windows) {
23481 			import core.sys.windows.winbase;
23482 			FreeLibrary(libHandle);
23483 		}
23484 		foreach(name; __traits(derivedMembers, Iface))
23485 			mixin(name ~ " = null;");
23486 	}
23487 }
23488 
23489 // version(X11)
23490 /++
23491 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
23492 
23493 	$(WARNING
23494 		This function is exempted from stability guarantees.
23495 	)
23496 +/
23497 float customScalingFactorForMonitor(int monitorNumber) @system {
23498 	import core.stdc.stdlib;
23499 	auto val = getenv("ARSD_SCALING_FACTOR");
23500 
23501 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
23502 	if(val is null)
23503 		return 1.0;
23504 
23505 	char[16] buffer = 0;
23506 	int pos;
23507 
23508 	const(char)* at = val;
23509 
23510 	foreach(item; 0 .. monitorNumber + 1) {
23511 		if(*at == 0)
23512 			break; // reuse the last number when we at the end of the string
23513 		pos = 0;
23514 		while(pos + 1 < buffer.length && *at && *at != ';') {
23515 			buffer[pos++] = *at;
23516 			at++;
23517 		}
23518 		if(*at)
23519 			at++; // skip the semicolon
23520 		buffer[pos] = 0;
23521 	}
23522 
23523 	//sdpyPrintDebugString(buffer[0 .. pos]);
23524 
23525 	import core.stdc.math;
23526 	auto f = atof(buffer.ptr);
23527 
23528 	if(f <= 0.0 || isnan(f) || isinf(f))
23529 		return 1.0;
23530 
23531 	return f;
23532 }
23533 
23534 void guiAbortProcess(string msg) {
23535 	import core.stdc.stdlib;
23536 	version(Windows) {
23537 		WCharzBuffer t = WCharzBuffer(msg);
23538 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
23539 	} else {
23540 		import core.stdc.stdio;
23541 		fwrite(msg.ptr, 1, msg.length, stderr);
23542 		msg = "\n";
23543 		fwrite(msg.ptr, 1, msg.length, stderr);
23544 		fflush(stderr);
23545 	}
23546 
23547 	abort();
23548 }
23549 
23550 private int minInternal(int a, int b) {
23551 	return (a < b) ? a : b;
23552 }
23553 
23554 private alias scriptable = arsd_jsvar_compatible;