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 
1168 version(OSXCocoa) {
1169 	version=without_opengl;
1170 	version=allow_unimplemented_features;
1171 	// version=OSXCocoa;
1172 	// pragma(linkerDirective, "-framework Cocoa");
1173 }
1174 
1175 version(without_opengl) {
1176 	enum SdpyIsUsingIVGLBinds = false;
1177 } else /*version(Posix)*/ {
1178 	static if (__traits(compiles, (){import iv.glbinds;})) {
1179 		enum SdpyIsUsingIVGLBinds = true;
1180 		public import iv.glbinds;
1181 		//pragma(msg, "SDPY: using iv.glbinds");
1182 	} else {
1183 		enum SdpyIsUsingIVGLBinds = false;
1184 	}
1185 //} else {
1186 //	enum SdpyIsUsingIVGLBinds = false;
1187 }
1188 
1189 
1190 version(Windows) {
1191 	//import core.sys.windows.windows;
1192 	import core.sys.windows.winnls;
1193 	import core.sys.windows.windef;
1194 	import core.sys.windows.basetyps;
1195 	import core.sys.windows.winbase;
1196 	import core.sys.windows.winuser;
1197 	import core.sys.windows.shellapi;
1198 	import core.sys.windows.wingdi;
1199 	static import gdi = core.sys.windows.wingdi; // so i
1200 
1201 	pragma(lib, "gdi32");
1202 	pragma(lib, "user32");
1203 
1204 	// for AlphaBlend... a breaking change....
1205 	version(CRuntime_DigitalMars) { } else
1206 		pragma(lib, "msimg32");
1207 
1208 	// core.sys.windows.dwmapi
1209 	private {
1210 		/++
1211 			See_also:
1212 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute
1213 		 +/
1214 		extern extern(Windows) HRESULT DwmGetWindowAttribute(
1215 			HWND hwnd,
1216 			DWORD dwAttribute,
1217 			PVOID pvAttribute,
1218 			DWORD cbAttribute
1219 		) nothrow @nogc;
1220 
1221 		/++
1222 			See_also:
1223 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
1224 		 +/
1225 		extern extern(Windows) HRESULT DwmSetWindowAttribute(
1226 			HWND hwnd,
1227 			DWORD dwAttribute,
1228 			LPCVOID pvAttribute,
1229 			DWORD cbAttribute
1230 		) nothrow @nogc;
1231 
1232 		/++
1233 			See_also:
1234 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
1235 		 +/
1236 		enum DWMWINDOWATTRIBUTE {
1237 			// incomplete, only declare what we need
1238 
1239 			/++
1240 				Usage:
1241 					pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*`
1242 
1243 				$(NOTE
1244 					Requires Windows 11 or later.
1245 				)
1246 			 +/
1247 			DWMWA_WINDOW_CORNER_PREFERENCE = 33,
1248 		}
1249 
1250 		/++
1251 			See_also:
1252 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
1253 		 +/
1254 		enum DWM_WINDOW_CORNER_PREFERENCE {
1255 			/// System decision
1256 			DWMWCP_DEFAULT = 0,
1257 
1258 			/// Never
1259 			DWMWCP_DONOTROUND = 1,
1260 
1261 			// If "appropriate"
1262 			DWMWCP_ROUND = 2,
1263 
1264 			// If "appropriate", but smaller radius
1265 			DWMWCP_ROUNDSMALL = 3
1266 		}
1267 
1268 		bool fromDWM(
1269 			DWM_WINDOW_CORNER_PREFERENCE value,
1270 			out CornerStyle result,
1271 		) @safe pure nothrow @nogc {
1272 			switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1273 				case DWMWCP_DEFAULT:
1274 					result = CornerStyle.automatic;
1275 					return true;
1276 				case DWMWCP_DONOTROUND:
1277 					result = CornerStyle.rectangular;
1278 					return true;
1279 				case DWMWCP_ROUND:
1280 					result = CornerStyle.rounded;
1281 					return true;
1282 				case DWMWCP_ROUNDSMALL:
1283 					result = CornerStyle.roundedSlightly;
1284 					return true;
1285 				default:
1286 					return false;
1287 			}
1288 		}
1289 
1290 		bool toDWM(
1291 			CornerStyle value,
1292 			out DWM_WINDOW_CORNER_PREFERENCE result,
1293 		) @safe pure nothrow @nogc {
1294 			final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1295 				case CornerStyle.automatic:
1296 					result = DWMWCP_DEFAULT;
1297 					return true;
1298 				case CornerStyle.rectangular:
1299 					result = DWMWCP_DONOTROUND;
1300 					return true;
1301 				case CornerStyle.rounded:
1302 					result = DWMWCP_ROUND;
1303 					return true;
1304 				case CornerStyle.roundedSlightly:
1305 					result = DWMWCP_ROUNDSMALL;
1306 					return true;
1307 			}
1308 		}
1309 
1310 		pragma(lib, "dwmapi");
1311 	}
1312 } else version (linux) {
1313 	//k8: this is hack for rdmd. sorry.
1314 	static import core.sys.linux.epoll;
1315 	static import core.sys.linux.timerfd;
1316 }
1317 
1318 
1319 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1320 
1321 // http://wiki.dlang.org/Simpledisplay.d
1322 
1323 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1324 
1325 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1326 // but can i control the scroll lock led
1327 
1328 
1329 // Note: if you are using Image on X, you might want to do:
1330 /*
1331 	static if(UsingSimpledisplayX11) {
1332 		if(!Image.impl.xshmAvailable) {
1333 			// the images will use the slower XPutImage, you might
1334 			// want to consider an alternative method to get better speed
1335 		}
1336 	}
1337 
1338 	If the shared memory extension is available though, simpledisplay uses it
1339 	for a significant speed boost whenever you draw large Images.
1340 */
1341 
1342 // 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.
1343 
1344 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1345 
1346 /*
1347 	Biggest FIXME:
1348 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1349 
1350 		clean up opengl contexts when their windows close
1351 
1352 		fix resizing the bitmaps/pixmaps
1353 */
1354 
1355 // BTW on Windows:
1356 // -L/SUBSYSTEM:WINDOWS:5.0
1357 // to dmd will make a nice windows binary w/o a console if you want that.
1358 
1359 /*
1360 	Stuff to add:
1361 
1362 	use multibyte functions everywhere we can
1363 
1364 	OpenGL windows
1365 	more event stuff
1366 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1367 
1368 
1369 	resizeEvent
1370 		and make the windows non-resizable by default,
1371 		or perhaps stretched (if I can find something in X like StretchBlt)
1372 
1373 	take a screenshot function!
1374 
1375 	Pens and brushes?
1376 	Maybe a global event loop?
1377 
1378 	Mouse deltas
1379 	Key items
1380 */
1381 
1382 /*
1383 From MSDN:
1384 
1385 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1386 
1387 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.
1388 
1389 */
1390 
1391 version(linux) {
1392 	version = X11;
1393 	version(without_libnotify) {
1394 		// we cool
1395 	}
1396 	else
1397 		version = libnotify;
1398 }
1399 
1400 version(libnotify) {
1401 	pragma(lib, "dl");
1402 	import core.sys.posix.dlfcn;
1403 
1404 	void delegate()[int] libnotify_action_delegates;
1405 	int libnotify_action_delegates_count;
1406 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1407 		auto idx = cast(int) user_data;
1408 		if(auto dgptr = idx in libnotify_action_delegates) {
1409 			(*dgptr)();
1410 			libnotify_action_delegates.remove(idx);
1411 		}
1412 	}
1413 
1414 	struct C_DynamicLibrary {
1415 		void* handle;
1416 		this(string name) {
1417 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1418 			if(handle is null)
1419 				throw new Exception("dlopen");
1420 		}
1421 
1422 		void close() {
1423 			dlclose(handle);
1424 		}
1425 
1426 		~this() {
1427 			// close
1428 		}
1429 
1430 		// FIXME: this looks up by name every time....
1431 		template call(string func, Ret, Args...) {
1432 			extern(C) Ret function(Args) fptr;
1433 			typeof(fptr) call() {
1434 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1435 				return fptr;
1436 			}
1437 		}
1438 	}
1439 
1440 	C_DynamicLibrary* libnotify;
1441 }
1442 
1443 version(OSX) {
1444 	version(OSXCocoa) {}
1445 	else { version = X11; }
1446 }
1447 	//version = OSXCocoa; // this was written by KennyTM
1448 version(FreeBSD)
1449 	version = X11;
1450 version(Solaris)
1451 	version = X11;
1452 
1453 version(X11) {
1454 	version(without_xft) {}
1455 	else version=with_xft;
1456 }
1457 
1458 void featureNotImplemented()() {
1459 	version(allow_unimplemented_features)
1460 		throw new NotYetImplementedException();
1461 	else
1462 		static assert(0);
1463 }
1464 
1465 // these are so the static asserts don't trigger unless you want to
1466 // add support to it for an OS
1467 version(Windows)
1468 	version = with_timer;
1469 version(linux)
1470 	version = with_timer;
1471 version(OSXCocoa)
1472 	version = with_timer;
1473 
1474 version(with_timer)
1475 	enum bool SimpledisplayTimerAvailable = true;
1476 else
1477 	enum bool SimpledisplayTimerAvailable = false;
1478 
1479 /// 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.
1480 version(Windows)
1481 	enum bool UsingSimpledisplayWindows = true;
1482 else
1483 	enum bool UsingSimpledisplayWindows = false;
1484 
1485 /// 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.
1486 version(X11)
1487 	enum bool UsingSimpledisplayX11 = true;
1488 else
1489 	enum bool UsingSimpledisplayX11 = false;
1490 
1491 /// 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.
1492 version(OSXCocoa)
1493 	enum bool UsingSimpledisplayCocoa = true;
1494 else
1495 	enum bool UsingSimpledisplayCocoa = false;
1496 
1497 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1498 version(Windows)
1499 	enum multipleWindowsSupported = true;
1500 else version(X11)
1501 	enum multipleWindowsSupported = true;
1502 else version(OSXCocoa)
1503 	enum multipleWindowsSupported = true;
1504 else
1505 	static assert(0);
1506 
1507 version(without_opengl)
1508 	enum bool OpenGlEnabled = false;
1509 else
1510 	enum bool OpenGlEnabled = true;
1511 
1512 /++
1513 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1514 	If you mix this in above your `main` function, you no longer need to use the linker
1515 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1516 
1517 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1518 
1519 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1520 	stderr writeln. It will fail and throw an exception.
1521 
1522 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1523 
1524 	History:
1525 		Added November 24, 2021 (dub v10.4)
1526 +/
1527 mixin template EnableWindowsSubsystem() {
1528 	version(Windows)
1529 	version(CRuntime_Microsoft) {
1530 		pragma(linkerDirective, "/subsystem:windows");
1531 		version(LDC)
1532 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1533 		else
1534 			pragma(linkerDirective, "/entry:mainCRTStartup");
1535 	}
1536 }
1537 
1538 
1539 /++
1540 	After selecting a type from [WindowTypes], you may further customize
1541 	its behavior by setting one or more of these flags.
1542 
1543 
1544 	The different window types have different meanings of `normal`. If the
1545 	window type already is a good match for what you want to do, you should
1546 	just use [WindowFlags.normal], the default, which will do the right thing
1547 	for your users.
1548 
1549 	The window flags will not always be honored by the operating system
1550 	and window managers; they are hints, not commands.
1551 +/
1552 enum WindowFlags : int {
1553 	normal = 0, ///
1554 	skipTaskbar = 1, ///
1555 	alwaysOnTop = 2, ///
1556 	alwaysOnBottom = 4, ///
1557 	cannotBeActivated = 8, ///
1558 	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.
1559 	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.
1560 	/++
1561 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1562 		it is still a top-level window. This should NOT be set separately for most window types.
1563 
1564 		A transient window will not keep the application open if its main window closes.
1565 
1566 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1567 
1568 
1569 		From the ICCM:
1570 
1571 		$(BLOCKQUOTE
1572 			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.
1573 
1574 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1575 		)
1576 
1577 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1578 
1579 		History:
1580 			Added February 23, 2021 but not yet stabilized.
1581 	+/
1582 	transient = 64,
1583 	/++
1584 		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.
1585 
1586 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1587 
1588 		History:
1589 			Added April 1, 2022
1590 	+/
1591 	managesChildWindowFocus = 128,
1592 
1593 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1594 }
1595 
1596 /++
1597 	When creating a window, you can pass a type to SimpleWindow's constructor,
1598 	then further customize the window by changing `WindowFlags`.
1599 
1600 
1601 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1602 	use. The others are there to build a foundation for a higher level GUI toolkit,
1603 	but are themselves not as high level as you might think from their names.
1604 
1605 	This list is based on the EMWH spec for X11.
1606 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1607 +/
1608 enum WindowTypes : int {
1609 	/// An ordinary application window.
1610 	normal,
1611 	/// 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.
1612 	undecorated,
1613 	/// 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.
1614 	eventOnly,
1615 	/// A drop down menu, such as from a menu bar
1616 	dropdownMenu,
1617 	/// A popup menu, such as from a right click
1618 	popupMenu,
1619 	/// A popup bubble notification
1620 	notification,
1621 	/*
1622 	menu, /// a tearable menu bar
1623 	splashScreen, /// a loading splash screen for your application
1624 	tooltip, /// A tiny window showing temporary help text or something.
1625 	comboBoxDropdown,
1626 	toolbar
1627 	*/
1628 	/// a dialog box of some sort
1629 	dialog,
1630 	/// a child nested inside the parent. You must pass a parent window to the ctor
1631 	nestedChild,
1632 
1633 	/++
1634 		The type you get when you pass in an existing browser handle, which means most
1635 		of simpledisplay's fancy things will not be done since they were never set up.
1636 
1637 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1638 		failure; you should use the existing handle constructor.
1639 
1640 		History:
1641 			Added November 17, 2022 (previously it would have type `normal`)
1642 	+/
1643 	minimallyWrapped
1644 }
1645 
1646 
1647 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1648 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1649 private __gshared char* sdpyWindowClassStr = null;
1650 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1651 
1652 /**
1653 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1654 	You may want to change context version if you want to use advanced shaders or
1655 	other modern OpenGL techinques. This setting doesn't affect already created
1656 	windows. You may use version 2.1 as your default, which should be supported
1657 	by any box since 2006, so seems to be a reasonable choice.
1658 
1659 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1660 	old context creation code without any version specified. This is the safest
1661 	way to init OpenGL, but it may not give you access to advanced features.
1662 
1663 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1664 */
1665 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1666 
1667 /**
1668 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1669 	pipeline functions, and without "compatible" mode you won't be able to use
1670 	your old non-shader-based code with such contexts. By default SimpleDisplay
1671 	creates compatible context, so you can gradually upgrade your OpenGL code if
1672 	you want to (or leave it as is, as it should "just work").
1673 */
1674 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1675 
1676 /**
1677 	Set to `true` to allow creating OpenGL context with lower version than requested
1678 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1679 	`openGLContextFallbackActivated()` will return `true`.
1680 	*/
1681 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1682 
1683 /**
1684 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1685 	*/
1686 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1687 
1688 /++
1689 	History:
1690 		Added April 24, 2023  (dub v11.0)
1691 +/
1692 version(without_opengl) {} else
1693 auto openGLCurrentContext() {
1694 	version(Windows)
1695 		return wglGetCurrentContext();
1696 	else
1697 		return glXGetCurrentContext();
1698 }
1699 
1700 
1701 /**
1702 	Set window class name for all following `new SimpleWindow()` calls.
1703 
1704 	WARNING! For Windows, you should set your class name before creating any
1705 	window, and NEVER change it after that!
1706 */
1707 void sdpyWindowClass (const(char)[] v) {
1708 	import core.stdc.stdlib : realloc;
1709 	if (v.length == 0) v = "SimpleDisplayWindow";
1710 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1711 	if (sdpyWindowClassStr is null) return; // oops
1712 	sdpyWindowClassStr[0..v.length+1] = 0;
1713 	sdpyWindowClassStr[0..v.length] = v[];
1714 }
1715 
1716 /**
1717 	Get current window class name.
1718 */
1719 string sdpyWindowClass () @trusted {
1720 	if (sdpyWindowClassStr is null) return null;
1721 	foreach (immutable idx; 0..size_t.max-1) {
1722 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1723 	}
1724 	return null;
1725 }
1726 
1727 /++
1728 	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.
1729 
1730 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1731 +/
1732 float[2] getDpi() {
1733 	float[2] dpi;
1734 	version(Windows) {
1735 		HDC screen = GetDC(null);
1736 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1737 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1738 	} else version(X11) {
1739 		auto display = XDisplayConnection.get;
1740 		auto screen = DefaultScreen(display);
1741 
1742 		void fallback() {
1743 			/+
1744 			// 25.4 millimeters in an inch...
1745 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1746 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1747 			+/
1748 
1749 			// the physical size isn't actually as important as the logical size since this is
1750 			// all about scaling really
1751 			dpi[0] = 96;
1752 			dpi[1] = 96;
1753 		}
1754 
1755 		auto xft = getXftDpi();
1756 		if(xft is float.init)
1757 			fallback();
1758 		else {
1759 			dpi[0] = xft;
1760 			dpi[1] = xft;
1761 		}
1762 	}
1763 
1764 	return dpi;
1765 }
1766 
1767 version(X11)
1768 float getXftDpi() {
1769 	auto display = XDisplayConnection.get;
1770 
1771 	char* resourceString = XResourceManagerString(display);
1772 	XrmInitialize();
1773 
1774 	if (resourceString) {
1775 		auto db = XrmGetStringDatabase(resourceString);
1776 		XrmValue value;
1777 		char* type;
1778 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1779 			if (value.addr) {
1780 				import core.stdc.stdlib;
1781 				return atof(cast(char*) value.addr);
1782 			}
1783 		}
1784 	}
1785 
1786 	return float.init;
1787 }
1788 
1789 /++
1790 	Implementation used by [SimpleWindow.takeScreenshot].
1791 
1792 	Params:
1793 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1794 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1795 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1796 		x = the x-offset of the image to capture, from the left.
1797 		y = the y-offset of the image to capture, from the top.
1798 
1799 	History:
1800 		Added on March 14, 2021
1801 
1802 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1803 
1804 +/
1805 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1806 	TrueColorImage got;
1807 	version(X11) {
1808 		auto display = XDisplayConnection.get;
1809 		if(handle == 0)
1810 			handle = RootWindow(display, DefaultScreen(display));
1811 
1812 		if(width == 0 || height == 0) {
1813 			Window root;
1814 			int xpos, ypos;
1815 			uint widthret, heightret, borderret, depthret;
1816 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1817 
1818 			if(width == 0)
1819 				width = widthret;
1820 			if(height == 0)
1821 				height = heightret;
1822 		}
1823 
1824 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1825 
1826 		// https://github.com/adamdruppe/arsd/issues/98
1827 
1828 		auto i = new Image(image);
1829 		got = i.toTrueColorImage();
1830 
1831 		XDestroyImage(image);
1832 	} else version(Windows) {
1833 		auto hdc = GetDC(handle);
1834 		scope(exit) ReleaseDC(handle, hdc);
1835 
1836 		if(width == 0 || height == 0) {
1837 			BITMAP bmHeader;
1838 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1839 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1840 			if(width == 0)
1841 				width = bmHeader.bmWidth;
1842 			if(height == 0)
1843 				height = bmHeader.bmHeight;
1844 		}
1845 
1846 		auto i = new Image(width, height);
1847 		HDC hdcMem = CreateCompatibleDC(hdc);
1848 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1849 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1850 		SelectObject(hdcMem, hbmOld);
1851 		DeleteDC(hdcMem);
1852 
1853 		got = i.toTrueColorImage();
1854 	} else featureNotImplemented();
1855 
1856 	return got;
1857 }
1858 
1859 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1860 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1861 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1862 
1863 version(Windows)
1864 shared static this() {
1865 	auto lib = LoadLibrary("User32.dll");
1866 	if(lib is null)
1867 		return;
1868 	//scope(exit)
1869 		//FreeLibrary(lib);
1870 
1871 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1872 
1873 	if(SetProcessDpiAwarenessContext is null)
1874 		return;
1875 
1876 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1877 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1878 		//writeln(GetLastError());
1879 	}
1880 
1881 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1882 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1883 }
1884 
1885 /++
1886 	Blocking mode for event loop calls associated with a window instance.
1887 
1888 	History:
1889 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1890 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1891 		is, all would block until the application quit.
1892 
1893 		That behavior can still be achieved here with `untilApplicationQuits`,
1894 		or explicitly calling the top-level `EventLoop.get.run` function.
1895 +/
1896 enum BlockingMode {
1897 	/++
1898 		The event loop call will block until the whole application is ready
1899 		to quit if it is the only one running, but if it is nested inside
1900 		another one, it will only block until the window you're calling it on
1901 		closes.
1902 	+/
1903 	automatic             = 0x00,
1904 	/++
1905 		The event loop call will only return when the whole application
1906 		is ready to quit. This usually means all windows have been closed.
1907 
1908 		This is appropriate for your main application event loop.
1909 	+/
1910 	untilApplicationQuits = 0x01,
1911 	/++
1912 		The event loop will return when the window you're calling it on
1913 		closes. If there are other windows still open, they may be destroyed
1914 		unless you have another event loop running later.
1915 
1916 		This might be appropriate for a modal dialog box loop. Remember that
1917 		other windows are still processing input though, so you can end up
1918 		with a lengthy call stack if this happens in a loop, similar to a
1919 		recursive function (well, it literally is a recursive function, just
1920 		not an obvious looking one).
1921 	+/
1922 	untilWindowCloses     = 0x02,
1923 	/++
1924 		If an event loop is already running, this call will immediately
1925 		return, allowing the existing loop to handle it. If not, this call
1926 		will block until the condition you bitwise-or into the flag.
1927 
1928 		The default is to block until the application quits, same as with
1929 		the `automatic` setting (since if it were nested, which triggers until
1930 		window closes in automatic, this flag would instead not block at all),
1931 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1932 		it will only nest until the window closes. You might want that if you are
1933 		going to open two windows simultaneously and want closing just one of them
1934 		to trigger the event loop return.
1935 	+/
1936 	onlyIfNotNested       = 0x10,
1937 }
1938 
1939 /++
1940 	Window corner visuals preference
1941  +/
1942 enum CornerStyle {
1943 	/++
1944 		Use the default style automatically applied by the system or its window manager/compositor.
1945 	 +/
1946 	automatic,
1947 
1948 	/++
1949 		Prefer rectangular window corners
1950 	 +/
1951 	rectangular,
1952 
1953 	/++
1954 		Prefer rounded window corners
1955 	 +/
1956 	rounded,
1957 
1958 	/++
1959 		Prefer slightly-rounded window corners
1960 	 +/
1961 	roundedSlightly,
1962 }
1963 
1964 /++
1965 	The flagship window class.
1966 
1967 
1968 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1969 	out of more advanced or complex features of the underlying windowing system.
1970 
1971 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1972 	and get a suitable window to work with.
1973 
1974 	From there, you can opt into additional features, like custom resizability and OpenGL support
1975 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1976 	and customization flags with the final two constructor arguments.
1977 
1978 	If none of that works for you, you can also create a window using native function calls, then
1979 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1980 	though, if you do this, managing the window is still your own responsibility! Notably, you
1981 	will need to destroy it yourself.
1982 +/
1983 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1984 
1985 	/++
1986 		Copies the window's current state into a [TrueColorImage].
1987 
1988 		Be warned: this can be a very slow operation
1989 
1990 		History:
1991 			Actually implemented on March 14, 2021
1992 	+/
1993 	TrueColorImage takeScreenshot() {
1994 		version(Windows)
1995 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1996 		else version(OSXCocoa)
1997 			throw new NotYetImplementedException();
1998 		else
1999 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
2000 	}
2001 
2002 	/++
2003 		Returns the actual logical DPI for the window on its current display monitor. If the window
2004 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
2005 
2006 		Please note this function may return zero if it doesn't know the answer!
2007 
2008 
2009 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
2010 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
2011 
2012 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
2013 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
2014 		window primarily resides on by checking the center point of the window against the monitor map.
2015 
2016 		Returns:
2017 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
2018 			assumes the X and Y dpi are the same.
2019 
2020 		History:
2021 			Added November 26, 2021 (dub v10.4)
2022 
2023 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
2024 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
2025 			that.
2026 
2027 		Bugs:
2028 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
2029 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
2030 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
2031 			and 1.5 on the secondary monitor.
2032 
2033 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
2034 			is a historical misnomer - the real thing of interest is the scale factor and due to
2035 			compatibility concerns the scale would modify dpi values to trick applications. But since
2036 			that's the terminology common out there, I used it too.
2037 
2038 		See_Also:
2039 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
2040 			as this since the window many be on a different monitor, but it is a reasonable fallback
2041 			to use if `actualDpi` returns 0.
2042 
2043 			[onDpiChanged] is changed when `actualDpi` has changed.
2044 	+/
2045 	int actualDpi() {
2046 		version(X11) bool useFallbackDpi = false;
2047 		if(!actualDpiLoadAttempted) {
2048 			// FIXME: do the actual monitor we are on
2049 			// and on X this is a good chance to load the monitor map.
2050 			version(Windows) {
2051 				if(GetDpiForWindow)
2052 					actualDpi_ = GetDpiForWindow(impl.hwnd);
2053 			} else version(X11) {
2054 				if(!xRandrInfoLoadAttemped) {
2055 					xRandrInfoLoadAttemped = true;
2056 					if(!XRandrLibrary.attempted) {
2057 						XRandrLibrary.loadDynamicLibrary();
2058 					}
2059 
2060 					if(XRandrLibrary.loadSuccessful) {
2061 						auto display = XDisplayConnection.get;
2062 						int scratch;
2063 						int major, minor;
2064 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
2065 							goto fallback;
2066 
2067 						XRRQueryVersion(display, &major, &minor);
2068 						if(major <= 1 && minor < 5)
2069 							goto fallback;
2070 
2071 						int count;
2072 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
2073 						if(monitors is null)
2074 							goto fallback;
2075 						scope(exit) XRRFreeMonitors(monitors);
2076 
2077 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
2078 						MonitorInfo.info.assumeSafeAppend();
2079 						foreach(idx, monitor; monitors[0 .. count]) {
2080 							MonitorInfo.info ~= MonitorInfo(
2081 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2082 								Size(monitor.mwidth, monitor.mheight),
2083 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
2084 							);
2085 
2086 							/+
2087 							if(monitor.mwidth == 0 || monitor.mheight == 0)
2088 							// unknown physical size, just guess 96 to avoid divide by zero
2089 							MonitorInfo.info ~= MonitorInfo(
2090 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2091 								Size(monitor.mwidth, monitor.mheight),
2092 								96
2093 							);
2094 							else
2095 							// and actual thing
2096 							MonitorInfo.info ~= MonitorInfo(
2097 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2098 								Size(monitor.mwidth, monitor.mheight),
2099 								minInternal(
2100 									// millimeter to int then rounding up.
2101 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
2102 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
2103 								)
2104 							);
2105 							+/
2106 						}
2107 					// writeln("Here", MonitorInfo.info);
2108 					}
2109 				}
2110 
2111 				if(XRandrLibrary.loadSuccessful) {
2112 					updateActualDpi(true);
2113 					// writeln("updated");
2114 
2115 					if(!requestedInput) {
2116 						// this is what requests live updates should the configuration change
2117 						// each time you select input, it sends an initial event, so very important
2118 						// to not get into a loop of selecting input, getting event, updating data,
2119 						// and reselecting input...
2120 						requestedInput = true;
2121 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
2122 						// writeln("requested input");
2123 					}
2124 				} else {
2125 					fallback:
2126 					// make sure we disable events that aren't coming
2127 					xrrEventBase = -1;
2128 					// best guess... respect the custom scaling user command to some extent at least though
2129 					useFallbackDpi = true;
2130 				}
2131 			} else version(OSXCocoa) {
2132 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
2133 			}
2134 			actualDpiLoadAttempted = true;
2135 		} else version(X11) if(MonitorInfo.info.length == 0) {
2136 			useFallbackDpi = true;
2137 		}
2138 
2139 		version(X11)
2140 		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...
2141 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
2142 		return actualDpi_;
2143 	}
2144 
2145 	private int actualDpi_;
2146 	private bool actualDpiLoadAttempted;
2147 
2148 	version(X11) private {
2149 		bool requestedInput;
2150 		static bool xRandrInfoLoadAttemped;
2151 		struct MonitorInfo {
2152 			Rectangle position;
2153 			Size size;
2154 			int dpi;
2155 
2156 			static MonitorInfo[] info;
2157 		}
2158 		bool screenPositionKnown;
2159 		int screenPositionX;
2160 		int screenPositionY;
2161 		void updateActualDpi(bool loadingNow = false) {
2162 			if(!loadingNow && !actualDpiLoadAttempted)
2163 				actualDpi(); // just to make it do the load
2164 			foreach(idx, m; MonitorInfo.info) {
2165 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
2166 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
2167 					actualDpi_ = m.dpi;
2168 					// writeln("monitor ", idx);
2169 					if(changed && onDpiChanged)
2170 						onDpiChanged();
2171 					break;
2172 				}
2173 			}
2174 		}
2175 	}
2176 
2177 	/++
2178 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2179 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2180 
2181 		History:
2182 			Added November 26, 2021 (dub v10.4)
2183 
2184 		See_Also:
2185 			[actualDpi]
2186 	+/
2187 	void delegate() onDpiChanged;
2188 
2189 	version(X11) {
2190 		void recreateAfterDisconnect() {
2191 			if(!stateDiscarded) return;
2192 
2193 			if(_parent !is null && _parent.stateDiscarded)
2194 				_parent.recreateAfterDisconnect();
2195 
2196 			bool wasHidden = hidden;
2197 
2198 			activeScreenPainter = null; // should already be done but just to confirm
2199 
2200 			actualDpi_ = 0;
2201 			actualDpiLoadAttempted = false;
2202 			xRandrInfoLoadAttemped = false;
2203 
2204 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2205 
2206 			if(auto dh = dropHandler) {
2207 				dropHandler = null;
2208 				enableDragAndDrop(this, dh);
2209 			}
2210 
2211 			if(recreateAdditionalConnectionState)
2212 				recreateAdditionalConnectionState();
2213 
2214 			hidden = wasHidden;
2215 			stateDiscarded = false;
2216 		}
2217 
2218 		bool stateDiscarded;
2219 		void discardConnectionState() {
2220 			if(XDisplayConnection.display)
2221 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2222 			if(discardAdditionalConnectionState)
2223 				discardAdditionalConnectionState();
2224 			stateDiscarded = true;
2225 		}
2226 
2227 		void delegate() discardAdditionalConnectionState;
2228 		void delegate() recreateAdditionalConnectionState;
2229 
2230 	}
2231 
2232 	private DropHandler dropHandler;
2233 
2234 	SimpleWindow _parent;
2235 	bool beingOpenKeepsAppOpen = true;
2236 	/++
2237 		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.
2238 
2239 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2240 
2241 		Params:
2242 
2243 		width = the width of the window's client area, in pixels
2244 		height = the height of the window's client area, in pixels
2245 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2246 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2247 		resizable = [Resizability] has three options:
2248 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2249 			$(P `fixedSize` will not allow the user to resize the window.)
2250 			$(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.)
2251 		windowType = The type of window you want to make.
2252 		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.
2253 		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".
2254 	+/
2255 	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) {
2256 		claimGuiThread();
2257 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2258 		this._width = this._virtualWidth = width;
2259 		this._height = this._virtualHeight = height;
2260 		this.openglMode = opengl;
2261 		version(X11) {
2262 			// auto scale not implemented except with opengl and even there it is kinda weird
2263 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2264 				resizable = Resizability.fixedSize;
2265 		}
2266 		this.resizability = resizable;
2267 		this.windowType = windowType;
2268 		this.customizationFlags = customizationFlags;
2269 		this._title = (title is null ? "D Application" : title);
2270 		this._parent = parent;
2271 		impl.createWindow(width, height, this._title, opengl, parent);
2272 
2273 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2274 			beingOpenKeepsAppOpen = false;
2275 	}
2276 
2277 	/// ditto
2278 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2279 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2280 	}
2281 
2282 	/// Same as above, except using the `Size` struct instead of separate width and height.
2283 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2284 		this(size.width, size.height, title, opengl, resizable);
2285 	}
2286 
2287 	/// ditto
2288 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2289 		this(size, title, opengl, resizable);
2290 	}
2291 
2292 
2293 	/++
2294 		Creates a window based on the given [Image]. It's client area
2295 		width and height is equal to the image. (A window's client area
2296 		is the drawable space inside; it excludes the title bar, etc.)
2297 
2298 		Windows based on images will not be resizable and do not use OpenGL.
2299 
2300 		It will draw the image in upon creation, but this will be overwritten
2301 		upon any draws, including the initial window visible event.
2302 
2303 		You probably do not want to use this and it may be removed from
2304 		the library eventually, or I might change it to be a "permanent"
2305 		background image; one that is automatically drawn on it before any
2306 		other drawing event. idk.
2307 	+/
2308 	this(Image image, string title = null) {
2309 		this(image.width, image.height, title);
2310 		this.image = image;
2311 	}
2312 
2313 	/++
2314 		Wraps a native window handle with very little additional processing - notably no destruction
2315 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2316 		windows created through the low level API (so you can use platform-specific options and
2317 		other details SimpleWindow does not expose) available to the event loop wrappers.
2318 	+/
2319 	this(NativeWindowHandle nativeWindow) {
2320 		windowType = WindowTypes.minimallyWrapped;
2321 		version(Windows)
2322 			impl.hwnd = nativeWindow;
2323 		else version(X11) {
2324 			impl.window = nativeWindow;
2325 			if(nativeWindow)
2326 				display = XDisplayConnection.get(); // get initial display to not segfault
2327 		} else version(OSXCocoa) {
2328 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2329 		} else featureNotImplemented();
2330 		// FIXME: set the size correctly
2331 		_width = 1;
2332 		_height = 1;
2333 		if(nativeWindow)
2334 			nativeMapping[cast(void*) nativeWindow] = this;
2335 
2336 		beingOpenKeepsAppOpen = false;
2337 
2338 		if(nativeWindow)
2339 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2340 		_suppressDestruction = true; // so it doesn't try to close
2341 	}
2342 
2343 	/++
2344 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2345 		The delegate will be called when the window manager asks you to take focus.
2346 
2347 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2348 
2349 		History:
2350 			Added April 1, 2022 (dub v10.8)
2351 	+/
2352 	SimpleWindow delegate() setRequestedInputFocus;
2353 
2354 	/// Experimental, do not use yet
2355 	/++
2356 		Grabs exclusive input from the user until you release it with
2357 		[releaseInputGrab].
2358 
2359 
2360 		Note: it is extremely rude to do this without good reason.
2361 		Reasons may include doing some kind of mouse drag operation
2362 		or popping up a temporary menu that should get events and will
2363 		be dismissed at ease by the user clicking away.
2364 
2365 		Params:
2366 			keyboard = do you want to grab keyboard input?
2367 			mouse = grab mouse input?
2368 			confine = confine the mouse cursor to inside this window?
2369 
2370 		History:
2371 			Prior to March 11, 2021, grabbing the keyboard would always also
2372 			set the X input focus. Now, it only focuses if it is a non-transient
2373 			window and otherwise manages the input direction internally.
2374 
2375 			This means spurious focus/blur events will no longer be sent and the
2376 			application will not steal focus from other applications (which the
2377 			window manager may have rejected anyway).
2378 	+/
2379 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2380 		static if(UsingSimpledisplayX11) {
2381 			XSync(XDisplayConnection.get, 0);
2382 			if(keyboard) {
2383 				if(isTransient && _parent) {
2384 					/*
2385 					FIXME:
2386 						setting the keyboard focus is not actually that helpful, what I more likely want
2387 						is the events from the parent window to be sent over here if we're transient.
2388 					*/
2389 
2390 					_parent.inputProxy = this;
2391 				} else {
2392 
2393 					SimpleWindow setTo;
2394 					if(setRequestedInputFocus !is null)
2395 						setTo = setRequestedInputFocus();
2396 					if(setTo is null)
2397 						setTo = this;
2398 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2399 				}
2400 			}
2401 			if(mouse) {
2402 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2403 				EventMask.PointerMotionMask // FIXME: not efficient
2404 				| EventMask.ButtonPressMask
2405 				| EventMask.ButtonReleaseMask
2406 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2407 				)
2408 			{
2409 				XSync(XDisplayConnection.get, 0);
2410 				import core.stdc.stdio;
2411 				printf("Grab input failed %d\n", res);
2412 				//throw new Exception("Grab input failed");
2413 			} else {
2414 				// cool
2415 			}
2416 			}
2417 
2418 		} else version(Windows) {
2419 			// FIXME: keyboard?
2420 			SetCapture(impl.hwnd);
2421 			if(confine) {
2422 				RECT rcClip;
2423 				//RECT rcOldClip;
2424 				//GetClipCursor(&rcOldClip);
2425 				GetWindowRect(hwnd, &rcClip);
2426 				ClipCursor(&rcClip);
2427 			}
2428 		} else version(OSXCocoa) {
2429 			// throw new NotYetImplementedException();
2430 		} else static assert(0);
2431 	}
2432 
2433 	private Point imePopupLocation = Point(0, 0);
2434 
2435 	/++
2436 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2437 
2438 		Bugs:
2439 			Not implemented outside X11.
2440 	+/
2441 	void setIMEPopupLocation(Point location) {
2442 		static if(UsingSimpledisplayX11) {
2443 			imePopupLocation = location;
2444 			updateIMEPopupLocation();
2445 		} else {
2446 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2447 			// throw new NotYetImplementedException();
2448 		}
2449 	}
2450 
2451 	/// ditto
2452 	void setIMEPopupLocation(int x, int y) {
2453 		return setIMEPopupLocation(Point(x, y));
2454 	}
2455 
2456 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2457 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2458 	// receives a ConfigureNotify event
2459 	private void updateIMEPopupLocation() {
2460 		static if(UsingSimpledisplayX11) {
2461 			if (xic is null) {
2462 				return;
2463 			}
2464 
2465 			XPoint nspot;
2466 			nspot.x = cast(short) imePopupLocation.x;
2467 			nspot.y = cast(short) imePopupLocation.y;
2468 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2469 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2470 			XFree(preeditAttr);
2471 		}
2472 	}
2473 
2474 	private bool imeFocused = true;
2475 
2476 	/++
2477 		Tells the IME whether or not an input field is currently focused in the window.
2478 
2479 		Bugs:
2480 			Not implemented outside X11.
2481 	+/
2482 	void setIMEFocused(bool value) {
2483 		imeFocused = value;
2484 		updateIMEFocused();
2485 	}
2486 
2487 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2488 	private void updateIMEFocused() {
2489 		static if(UsingSimpledisplayX11) {
2490 			if (xic is null) {
2491 				return;
2492 			}
2493 
2494 			if (focused && imeFocused) {
2495 				XSetICFocus(xic);
2496 			} else {
2497 				XUnsetICFocus(xic);
2498 			}
2499 		}
2500 	}
2501 
2502 	/++
2503 		Returns the native window.
2504 
2505 		History:
2506 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2507 			to access it through the `impl` member (which is semi-supported
2508 			but platform specific and here it is simple enough to offer an accessor).
2509 
2510 		Bugs:
2511 			Not implemented outside Windows or X11.
2512 	+/
2513 	NativeWindowHandle nativeWindowHandle() {
2514 		version(X11)
2515 			return impl.window;
2516 		else version(Windows)
2517 			return impl.hwnd;
2518 		else
2519 			throw new NotYetImplementedException();
2520 	}
2521 
2522 	private bool isTransient() {
2523 		with(WindowTypes)
2524 		final switch(windowType) {
2525 			case normal, undecorated, eventOnly:
2526 			case nestedChild, minimallyWrapped:
2527 				return (customizationFlags & WindowFlags.transient) ? true : false;
2528 			case dropdownMenu, popupMenu, notification, dialog:
2529 				return true;
2530 		}
2531 	}
2532 
2533 	private SimpleWindow inputProxy;
2534 
2535 	/++
2536 		Releases the grab acquired by [grabInput].
2537 	+/
2538 	void releaseInputGrab() {
2539 		static if(UsingSimpledisplayX11) {
2540 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2541 			if(_parent)
2542 				_parent.inputProxy = null;
2543 		} else version(Windows) {
2544 			ReleaseCapture();
2545 			ClipCursor(null);
2546 		} else version(OSXCocoa) {
2547 			// throw new NotYetImplementedException();
2548 		} else static assert(0);
2549 	}
2550 
2551 	/++
2552 		Sets the input focus to this window.
2553 
2554 		You shouldn't call this very often - please let the user control the input focus.
2555 	+/
2556 	void focus() {
2557 		static if(UsingSimpledisplayX11) {
2558 			SimpleWindow setTo;
2559 			if(setRequestedInputFocus !is null)
2560 				setTo = setRequestedInputFocus();
2561 			if(setTo is null)
2562 				setTo = this;
2563 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2564 		} else version(Windows) {
2565 			SetFocus(this.impl.hwnd);
2566 		} else version(OSXCocoa) {
2567 			throw new NotYetImplementedException();
2568 		} else static assert(0);
2569 	}
2570 
2571 	/++
2572 		Requests attention from the user for this window.
2573 
2574 
2575 		The typical result of this function is to change the color
2576 		of the taskbar icon, though it may be tweaked on specific
2577 		platforms.
2578 
2579 		It is meant to unobtrusively tell the user that something
2580 		relevant to them happened in the background and they should
2581 		check the window when they get a chance. Upon receiving the
2582 		keyboard focus, the window will automatically return to its
2583 		natural state.
2584 
2585 		If the window already has the keyboard focus, this function
2586 		may do nothing, because the user is presumed to already be
2587 		giving the window attention.
2588 
2589 		Implementation_note:
2590 
2591 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2592 		atom on X11 and the FlashWindow function on Windows.
2593 	+/
2594 	void requestAttention() {
2595 		if(_focused)
2596 			return;
2597 
2598 		version(Windows) {
2599 			FLASHWINFO info;
2600 			info.cbSize = info.sizeof;
2601 			info.hwnd = impl.hwnd;
2602 			info.dwFlags = FLASHW_TRAY;
2603 			info.uCount = 1;
2604 
2605 			FlashWindowEx(&info);
2606 
2607 		} else version(X11) {
2608 			demandingAttention = true;
2609 			demandAttention(this, true);
2610 		} else version(OSXCocoa) {
2611 			throw new NotYetImplementedException();
2612 		} else static assert(0);
2613 	}
2614 
2615 	private bool _focused;
2616 
2617 	version(X11) private bool demandingAttention;
2618 
2619 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2620 	/// You'll have to call `close()` manually if you set this delegate.
2621 	void delegate () closeQuery;
2622 
2623 	/// This will be called when window visibility was changed.
2624 	void delegate (bool becomesVisible) visibilityChanged;
2625 
2626 	/// This will be called when window becomes visible for the first time.
2627 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2628 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2629 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2630 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2631 	private bool _visibleForTheFirstTimeCalled;
2632 	void delegate () visibleForTheFirstTime;
2633 
2634 	/// Returns true if the window has been closed.
2635 	final @property bool closed() { return _closed; }
2636 
2637 	private final @property bool notClosed() { return !_closed; }
2638 
2639 	/// Returns true if the window is focused.
2640 	final @property bool focused() { return _focused; }
2641 
2642 	private bool _visible;
2643 	/// Returns true if the window is visible (mapped).
2644 	final @property bool visible() { return _visible; }
2645 
2646 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2647 	void close() {
2648 		if (!_closed) {
2649 			runInGuiThread( {
2650 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2651 				if (onClosing !is null) onClosing();
2652 				impl.closeWindow();
2653 				_closed = true;
2654 			} );
2655 		}
2656 	}
2657 
2658 	/++
2659 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2660 
2661 		History:
2662 			Overload added on March 7, 2021.
2663 	+/
2664 	void close() shared {
2665 		(cast() this).close();
2666 	}
2667 
2668 	/++
2669 
2670 	+/
2671 	void maximize() {
2672 		version(Windows)
2673 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2674 		else version(X11) {
2675 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2676 
2677 			// also note _NET_WM_STATE_FULLSCREEN
2678 		}
2679 
2680 	}
2681 
2682 	private bool _fullscreen;
2683 	version(Windows)
2684 	private WINDOWPLACEMENT g_wpPrev;
2685 
2686 	/// not fully implemented but planned for a future release
2687 	void fullscreen(bool yes) {
2688 		version(Windows) {
2689 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2690 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2691 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2692 				MONITORINFO mi;
2693 				mi.cbSize = MONITORINFO.sizeof;
2694 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2695 					GetMonitorInfo(MonitorFromWindow(hwnd,
2696 					               MONITOR_DEFAULTTOPRIMARY), &mi)) {
2697 					SetWindowLong(hwnd, GWL_STYLE,
2698 					              dwStyle & ~WS_OVERLAPPEDWINDOW);
2699 					SetWindowPos(hwnd, HWND_TOP,
2700 					             mi.rcMonitor.left, mi.rcMonitor.top,
2701 					             mi.rcMonitor.right - mi.rcMonitor.left,
2702 					             mi.rcMonitor.bottom - mi.rcMonitor.top,
2703 					             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2704 				}
2705 			} else {
2706 				SetWindowLong(hwnd, GWL_STYLE,
2707 				              dwStyle | WS_OVERLAPPEDWINDOW);
2708 				SetWindowPlacement(hwnd, &g_wpPrev);
2709 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2710 				             SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2711 				             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2712 			}
2713 
2714 		} else version(X11) {
2715 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2716 		}
2717 
2718 		_fullscreen = yes;
2719 
2720 	}
2721 
2722 	bool fullscreen() {
2723 		return _fullscreen;
2724 	}
2725 
2726 	/++
2727 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2728 
2729 	+/
2730 	void minimize() {
2731 		version(Windows)
2732 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2733 		//else version(X11)
2734 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2735 	}
2736 
2737 	/// Alias for `hidden = false`
2738 	void show() {
2739 		hidden = false;
2740 	}
2741 
2742 	/// Alias for `hidden = true`
2743 	void hide() {
2744 		hidden = true;
2745 	}
2746 
2747 	/// Hide cursor when it enters the window.
2748 	void hideCursor() {
2749 		version(OSXCocoa) throw new NotYetImplementedException(); else
2750 		if (!_closed) impl.hideCursor();
2751 	}
2752 
2753 	/// Don't hide cursor when it enters the window.
2754 	void showCursor() {
2755 		version(OSXCocoa) throw new NotYetImplementedException(); else
2756 		if (!_closed) impl.showCursor();
2757 	}
2758 
2759 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2760 	 *
2761 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2762 	 * control. Try to think for other approaches before using this function.
2763 	 *
2764 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2765 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2766 	 *       receive "mouse moved here" event.
2767 	 */
2768 	bool warpMouse (int x, int y) {
2769 		version(X11) {
2770 			if (!_closed) { impl.warpMouse(x, y); return true; }
2771 		} else version(Windows) {
2772 			if (!_closed) {
2773 				POINT point;
2774 				point.x = x;
2775 				point.y = y;
2776 				if(ClientToScreen(impl.hwnd, &point)) {
2777 					SetCursorPos(point.x, point.y);
2778 					return true;
2779 				}
2780 			}
2781 		}
2782 		return false;
2783 	}
2784 
2785 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2786 	void sendDummyEvent () {
2787 		version(X11) {
2788 			if (!_closed) { impl.sendDummyEvent(); }
2789 		}
2790 	}
2791 
2792 	/// Set window minimal size.
2793 	void setMinSize (int minwidth, int minheight) {
2794 		version(OSXCocoa) throw new NotYetImplementedException(); else
2795 		if (!_closed) impl.setMinSize(minwidth, minheight);
2796 	}
2797 
2798 	/// Set window maximal size.
2799 	void setMaxSize (int maxwidth, int maxheight) {
2800 		version(OSXCocoa) throw new NotYetImplementedException(); else
2801 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2802 	}
2803 
2804 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2805 	/// Currently only supported on X11.
2806 	void setResizeGranularity (int granx, int grany) {
2807 		version(OSXCocoa) throw new NotYetImplementedException(); else
2808 		if (!_closed) impl.setResizeGranularity(granx, grany);
2809 	}
2810 
2811 	/// Move window.
2812 	void move(int x, int y) {
2813 		version(OSXCocoa) throw new NotYetImplementedException(); else
2814 		if (!_closed) impl.move(x, y);
2815 	}
2816 
2817 	/// ditto
2818 	void move(Point p) {
2819 		version(OSXCocoa) throw new NotYetImplementedException(); else
2820 		if (!_closed) impl.move(p.x, p.y);
2821 	}
2822 
2823 	/++
2824 		Resize window.
2825 
2826 		Note that the width and height of the window are NOT instantly
2827 		updated - it waits for the window manager to approve the resize
2828 		request, which means you must return to the event loop before the
2829 		width and height are actually changed.
2830 	+/
2831 	void resize(int w, int h) {
2832 		if(!_closed && _fullscreen) fullscreen = false;
2833 		version(OSXCocoa) throw new NotYetImplementedException(); else
2834 		if (!_closed) impl.resize(w, h);
2835 	}
2836 
2837 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2838 	void moveResize (int x, int y, int w, int h) {
2839 		if(!_closed && _fullscreen) fullscreen = false;
2840 		version(OSXCocoa) throw new NotYetImplementedException(); else
2841 		if (!_closed) impl.moveResize(x, y, w, h);
2842 	}
2843 
2844 	private bool _hidden;
2845 
2846 	/// Returns true if the window is hidden.
2847 	final @property bool hidden() {
2848 		return _hidden;
2849 	}
2850 
2851 	/// Shows or hides the window based on the bool argument.
2852 	final @property void hidden(bool b) {
2853 		_hidden = b;
2854 		version(Windows) {
2855 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2856 		} else version(X11) {
2857 			if(b)
2858 				//XUnmapWindow(impl.display, impl.window);
2859 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2860 			else
2861 				XMapWindow(impl.display, impl.window);
2862 		} else version(OSXCocoa) {
2863 			// throw new NotYetImplementedException();
2864 		} else static assert(0);
2865 	}
2866 
2867 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2868 	void opacity(double opacity) @property
2869 	in {
2870 		assert(opacity >= 0 && opacity <= 1);
2871 	} do {
2872 		version (Windows) {
2873 			impl.setOpacity(cast(ubyte)(255 * opacity));
2874 		} else version (X11) {
2875 			impl.setOpacity(cast(uint)(uint.max * opacity));
2876 		} else throw new NotYetImplementedException();
2877 	}
2878 
2879 	/++
2880 		Sets your event handlers, without entering the event loop. Useful if you
2881 		have multiple windows - set the handlers on each window, then only do
2882 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2883 
2884 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2885 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2886 		delegate signatures.
2887 	+/
2888 	void setEventHandlers(T...)(T eventHandlers) {
2889 		// FIXME: add more events
2890 		foreach(handler; eventHandlers) {
2891 			static if(__traits(compiles, handleKeyEvent = handler)) {
2892 				handleKeyEvent = handler;
2893 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2894 				handleCharEvent = handler;
2895 			} else static if(__traits(compiles, handlePulse = handler)) {
2896 				handlePulse = handler;
2897 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2898 				handleMouseEvent = handler;
2899 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2900 		}
2901 	}
2902 
2903 	/++
2904 		The event loop automatically returns when the window is closed
2905 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2906 		pulse timer is created. The event loop will block until an event
2907 		arrives or the pulse timer goes off.
2908 
2909 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2910 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2911 		[handleMouseEvent], based on the signature of delegates you provide.
2912 
2913 		Give one with no parameters to set a timer pulse handler. Give one that
2914 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2915 		and one that takes `dchar` for a char event handler. You can use as many
2916 		or as few handlers as you need for your application.
2917 
2918 		Bugs:
2919 
2920 		$(PITFALL
2921 			You should always have one event loop live for your application.
2922 			If you make two windows in sequence, the second call to eventLoop
2923 			might fail:
2924 
2925 			---
2926 			// don't do this!
2927 			auto window = new SimpleWindow();
2928 			window.eventLoop(0);
2929 
2930 			auto window2 = new SimpleWindow();
2931 			window2.eventLoop(0); // problematic! might crash
2932 			---
2933 
2934 			simpledisplay's current implementation assumes that final cleanup is
2935 			done when the event loop refcount reaches zero. So after the first
2936 			eventLoop returns, when there isn't already another one active, it assumes
2937 			the program will exit soon and cleans up.
2938 
2939 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2940 			it eventually, but in the mean time, there's an easy solution:
2941 
2942 			---
2943 			// do this
2944 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2945 
2946 			auto window = new SimpleWindow();
2947 			window.eventLoop(0);
2948 
2949 			auto window2 = new SimpleWindow();
2950 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2951 			---
2952 
2953 			By adding a top-level reference to the event loop, it ensures the final cleanup
2954 			is not performed until it goes out of scope too, letting the individual window loops
2955 			work without trouble despite the bug.
2956 		)
2957 
2958 		History:
2959 			The overload without `pulseTimeout` was added on December 8, 2021.
2960 
2961 			On December 9, 2021, the default blocking mode (which is now configurable
2962 			because [eventLoopWithBlockingMode] was added) switched from
2963 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2964 			should almost never be noticeable to you since the typical simpledisplay
2965 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2966 
2967 		See_Also:
2968 			[eventLoopWithBlockingMode]
2969 	+/
2970 	final int eventLoop(T...)(
2971 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2972 		T eventHandlers) /// delegate list like std.concurrency.receive
2973 	{
2974 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2975 	}
2976 
2977 	/// ditto
2978 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2979 	{
2980 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2981 	}
2982 
2983 	/++
2984 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2985 
2986 		History:
2987 			Added December 8, 2021 (dub v10.5)
2988 
2989 			Previously, this implementation was right inside [eventLoop], but when I wanted
2990 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2991 			just renamed it instead of adding as an overload. Besides, the new name makes it
2992 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2993 
2994 		See_Also:
2995 			[SimpleWindow.eventLoop], [EventLoop]
2996 
2997 		Bugs:
2998 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2999 	+/
3000 	final int eventLoopWithBlockingMode(T...)(
3001 		BlockingMode blockingMode, /// when you want this function to block until
3002 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3003 		T eventHandlers) /// delegate list like std.concurrency.receive
3004 	{
3005 		setEventHandlers(eventHandlers);
3006 
3007 		version(with_eventloop) {
3008 			// delegates event loop to my other module
3009 			version(X11)
3010 				XFlush(display);
3011 
3012 			import arsd.eventloop;
3013 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
3014 			scope(exit) clearInterval(handle);
3015 
3016 			loop();
3017 			return 0;
3018 		} else version(OSXCocoa) {
3019 			// FIXME
3020 			if (handlePulse !is null && pulseTimeout != 0) {
3021 				timer = NSTimer.schedule(pulseTimeout*1e-3,
3022 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
3023 					null, true);
3024 			}
3025 
3026 			view.setNeedsDisplay(true);
3027 
3028 			NSApp.run();
3029 			return 0;
3030 		} else {
3031 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
3032 
3033 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
3034 				return 0;
3035 
3036 			return el.run(
3037 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
3038 					null :
3039 					&this.notClosed
3040 			);
3041 		}
3042 	}
3043 
3044 	/++
3045 		This lets you draw on the window (or its backing buffer) using basic
3046 		2D primitives.
3047 
3048 		Be sure to call this in a limited scope because your changes will not
3049 		actually appear on the window until ScreenPainter's destructor runs.
3050 
3051 		Returns: an instance of [ScreenPainter], which has the drawing methods
3052 		on it to draw on this window.
3053 
3054 		Params:
3055 			manualInvalidations = if you set this to true, you will need to
3056 			set the invalid rectangle on the painter yourself. If false, it
3057 			assumes the whole window has been redrawn each time you draw.
3058 
3059 			Only invalidated rectangles are blitted back to the window when
3060 			the destructor runs. Doing this yourself can reduce flickering
3061 			of child windows.
3062 
3063 		History:
3064 			The `manualInvalidations` parameter overload was added on
3065 			December 30, 2021 (dub v10.5)
3066 	+/
3067 	ScreenPainter draw() {
3068 		return draw(false);
3069 	}
3070 	/// ditto
3071 	ScreenPainter draw(bool manualInvalidations) {
3072 		return impl.getPainter(manualInvalidations);
3073 	}
3074 
3075 	// This is here to implement the interface we use for various native handlers.
3076 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
3077 
3078 	// maps native window handles to SimpleWindow instances, if there are any
3079 	// you shouldn't need this, but it is public in case you do in a native event handler or something
3080 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
3081 	version(OSXCocoa)
3082 	public __gshared SimpleWindow[void*] nativeMapping;
3083 	else
3084 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
3085 
3086 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
3087 	private int _virtualWidth;
3088 	private int _virtualHeight;
3089 
3090 	/// Width of the window's drawable client area, in pixels.
3091 	@scriptable
3092 	final @property int width() const pure nothrow @safe @nogc {
3093 		if(resizability == Resizability.automaticallyScaleIfPossible)
3094 			return _virtualWidth;
3095 		else
3096 			return _width;
3097 	}
3098 
3099 	/// Height of the window's drawable client area, in pixels.
3100 	@scriptable
3101 	final @property int height() const pure nothrow @safe @nogc {
3102 		if(resizability == Resizability.automaticallyScaleIfPossible)
3103 			return _virtualHeight;
3104 		else
3105 			return _height;
3106 	}
3107 
3108 	/++
3109 		Returns the actual size of the window, bypassing the logical
3110 		illusions of [Resizability.automaticallyScaleIfPossible].
3111 
3112 		History:
3113 			Added November 11, 2022 (dub v10.10)
3114 	+/
3115 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
3116 		return Size(_width, _height);
3117 	}
3118 
3119 
3120 	private int _width;
3121 	private int _height;
3122 
3123 	// HACK: making the best of some copy constructor woes with refcounting
3124 	private ScreenPainterImplementation* activeScreenPainter_;
3125 
3126 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
3127 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
3128 
3129 	private OpenGlOptions openglMode;
3130 	private Resizability resizability;
3131 	private WindowTypes windowType;
3132 	private int customizationFlags;
3133 
3134 	/// `true` if OpenGL was initialized for this window.
3135 	@property bool isOpenGL () const pure nothrow @safe @nogc {
3136 		version(without_opengl)
3137 			return false;
3138 		else
3139 			return (openglMode == OpenGlOptions.yes);
3140 	}
3141 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
3142 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
3143 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
3144 
3145 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3146 	/// to call this, as it's not recommended to share window between threads.
3147 	void mtLock () {
3148 		version(X11) {
3149 			XLockDisplay(this.display);
3150 		}
3151 	}
3152 
3153 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3154 	/// to call this, as it's not recommended to share window between threads.
3155 	void mtUnlock () {
3156 		version(X11) {
3157 			XUnlockDisplay(this.display);
3158 		}
3159 	}
3160 
3161 	/// Emit a beep to get user's attention.
3162 	void beep () {
3163 		version(X11) {
3164 			XBell(this.display, 100);
3165 		} else version(Windows) {
3166 			MessageBeep(0xFFFFFFFF);
3167 		}
3168 	}
3169 
3170 
3171 
3172 	version(without_opengl) {} else {
3173 
3174 		/// 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`.
3175 		void delegate() redrawOpenGlScene;
3176 
3177 		/// This will allow you to change OpenGL vsync state.
3178 		final @property void vsync (bool wait) {
3179 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3180 			version(X11) {
3181 				setAsCurrentOpenGlContext();
3182 				glxSetVSync(display, impl.window, wait);
3183 			} else version(Windows) {
3184 				setAsCurrentOpenGlContext();
3185 				wglSetVSync(wait);
3186 			}
3187 		}
3188 
3189 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3190 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3191 		/// enough without waiting 'em to finish their frame business.
3192 		bool useGLFinish = true;
3193 
3194 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3195 		/// 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.
3196 		void redrawOpenGlSceneNow() {
3197 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3198 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3199 			if(redrawOpenGlScene is null)
3200 				return;
3201 
3202 			this.mtLock();
3203 			scope(exit) this.mtUnlock();
3204 
3205 			this.setAsCurrentOpenGlContext();
3206 
3207 			redrawOpenGlScene();
3208 
3209 			this.swapOpenGlBuffers();
3210 			// 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.
3211 			if (useGLFinish) glFinish();
3212 		}
3213 
3214 		private bool redrawOpenGlSceneSoonSet = false;
3215 		private static class RedrawOpenGlSceneEvent {
3216 			SimpleWindow w;
3217 			this(SimpleWindow w) { this.w = w; }
3218 		}
3219 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3220 		/++
3221 			Queues an opengl redraw as soon as the other pending events are cleared.
3222 		+/
3223 		void redrawOpenGlSceneSoon() {
3224 			if(redrawOpenGlScene is null)
3225 				return;
3226 
3227 			if(!redrawOpenGlSceneSoonSet) {
3228 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3229 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3230 				redrawOpenGlSceneSoonSet = true;
3231 			}
3232 			this.postEvent(redrawOpenGlSceneEvent, true);
3233 		}
3234 
3235 
3236 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3237 		void setAsCurrentOpenGlContext() {
3238 			assert(openglMode == OpenGlOptions.yes);
3239 			version(X11) {
3240 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3241 					throw new Exception("glXMakeCurrent");
3242 			} else version(Windows) {
3243 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3244 				if (!wglMakeCurrent(ghDC, ghRC))
3245 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3246 			}
3247 		}
3248 
3249 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3250 		/// This doesn't throw, returning success flag instead.
3251 		bool setAsCurrentOpenGlContextNT() nothrow {
3252 			assert(openglMode == OpenGlOptions.yes);
3253 			version(X11) {
3254 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3255 			} else version(Windows) {
3256 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3257 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3258 			}
3259 		}
3260 
3261 		/// 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.
3262 		/// This doesn't throw, returning success flag instead.
3263 		bool releaseCurrentOpenGlContext() nothrow {
3264 			assert(openglMode == OpenGlOptions.yes);
3265 			version(X11) {
3266 				return (glXMakeCurrent(display, 0, null) != 0);
3267 			} else version(Windows) {
3268 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3269 				return wglMakeCurrent(ghDC, null) ? true : false;
3270 			}
3271 		}
3272 
3273 		/++
3274 			simpledisplay always uses double buffering, usually automatically. This
3275 			manually swaps the OpenGL buffers. You should only use this if you are NOT
3276 			using the [redrawOpenGlScene] delegate.
3277 
3278 
3279 			You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it
3280 			for you after calling your `redrawOpenGlScene`. Please note that once you swap
3281 			buffers, the contents become undefined - the implementation, in the OpenGL driver
3282 			or the desktop compositor, may not actually just swap two buffers. The back buffer's
3283 			contents are $(B undefined) after calling this function.
3284 
3285 			See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers
3286 			and https://linux.die.net/man/3/glxswapbuffers
3287 
3288 			Remember that this may throw an exception, which you can catch in a multithreaded
3289 			application to keep your thread from dying from an unhandled exception.
3290 		+/
3291 		void swapOpenGlBuffers() {
3292 			assert(openglMode == OpenGlOptions.yes);
3293 			version(X11) {
3294 				if (!this._visible) return; // no need to do this if window is invisible
3295 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3296 				glXSwapBuffers(display, impl.window);
3297 			} else version(Windows) {
3298 				SwapBuffers(ghDC);
3299 			}
3300 		}
3301 	}
3302 
3303 	/++
3304 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3305 
3306 
3307 		---
3308 			auto window = new SimpleWindow(100, 100, "First title");
3309 			window.title = "A new title";
3310 		---
3311 
3312 		You may call this function at any time.
3313 	+/
3314 	@property void title(string title) {
3315 		_title = title;
3316 		version(OSXCocoa) throw new NotYetImplementedException(); else
3317 		impl.setTitle(title);
3318 	}
3319 
3320 	private string _title;
3321 
3322 	/// Gets the title
3323 	@property string title() {
3324 		if(_title is null)
3325 			_title = getRealTitle();
3326 		return _title;
3327 	}
3328 
3329 	/++
3330 		Get the title as set by the window manager.
3331 		May not match what you attempted to set.
3332 	+/
3333 	string getRealTitle() {
3334 		static if(is(typeof(impl.getTitle())))
3335 			return impl.getTitle();
3336 		else
3337 			return null;
3338 	}
3339 
3340 	// don't use this generally it is not yet really released
3341 	version(X11)
3342 	@property Image secret_icon() {
3343 		return secret_icon_inner;
3344 	}
3345 	private Image secret_icon_inner;
3346 
3347 
3348 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3349 	@property void icon(MemoryImage icon) {
3350 		if(icon is null)
3351 			return;
3352 		auto tci = icon.getAsTrueColorImage();
3353 		version(Windows) {
3354 			winIcon = new WindowsIcon(icon);
3355 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3356 		} else version(X11) {
3357 			secret_icon_inner = Image.fromMemoryImage(icon);
3358 			// FIXME: ensure this is correct
3359 			auto display = XDisplayConnection.get;
3360 			arch_ulong[] buffer;
3361 			buffer ~= icon.width;
3362 			buffer ~= icon.height;
3363 			foreach(c; tci.imageData.colors) {
3364 				arch_ulong b;
3365 				b |= c.a << 24;
3366 				b |= c.r << 16;
3367 				b |= c.g << 8;
3368 				b |= c.b;
3369 				buffer ~= b;
3370 			}
3371 
3372 			XChangeProperty(
3373 				display,
3374 				impl.window,
3375 				GetAtom!("_NET_WM_ICON", true)(display),
3376 				GetAtom!"CARDINAL"(display),
3377 				32 /* bits */,
3378 				0 /*PropModeReplace*/,
3379 				buffer.ptr,
3380 				cast(int) buffer.length);
3381 		} else version(OSXCocoa) {
3382 			throw new NotYetImplementedException();
3383 		} else static assert(0);
3384 	}
3385 
3386 	version(Windows)
3387 		private WindowsIcon winIcon;
3388 
3389 	bool _suppressDestruction;
3390 
3391 	~this() {
3392 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3393 		if(_suppressDestruction)
3394 			return;
3395 		impl.dispose();
3396 	}
3397 
3398 	private bool _closed;
3399 
3400 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3401 	/*
3402 	ScreenPainter drawTransiently() {
3403 		return impl.getPainter();
3404 	}
3405 	*/
3406 
3407 	/// Draws an image on the window. This is meant to provide quick look
3408 	/// of a static image generated elsewhere.
3409 	@property void image(Image i) {
3410 	/+
3411 		version(Windows) {
3412 			BITMAP bm;
3413 			HDC hdc = GetDC(hwnd);
3414 			HDC hdcMem = CreateCompatibleDC(hdc);
3415 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3416 
3417 			GetObject(i.handle, bm.sizeof, &bm);
3418 
3419 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3420 
3421 			SelectObject(hdcMem, hbmOld);
3422 			DeleteDC(hdcMem);
3423 			ReleaseDC(hwnd, hdc);
3424 
3425 			/*
3426 			RECT r;
3427 			r.right = i.width;
3428 			r.bottom = i.height;
3429 			InvalidateRect(hwnd, &r, false);
3430 			*/
3431 		} else
3432 		version(X11) {
3433 			if(!destroyed) {
3434 				if(i.usingXshm)
3435 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3436 				else
3437 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3438 			}
3439 		} else
3440 		version(OSXCocoa) {
3441 			draw().drawImage(Point(0, 0), i);
3442 			setNeedsDisplay(view, true);
3443 		} else static assert(0);
3444 	+/
3445 		auto painter = this.draw;
3446 		painter.drawImage(Point(0, 0), i);
3447 	}
3448 
3449 	/++
3450 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3451 
3452 		---
3453 		window.cursor = GenericCursor.Help;
3454 		// now the window mouse cursor is set to a generic help
3455 		---
3456 
3457 	+/
3458 	@property void cursor(MouseCursor cursor) {
3459 		version(OSXCocoa)
3460 			{} // featureNotImplemented();
3461 		else
3462 		if(this.impl.curHidden <= 0) {
3463 			static if(UsingSimpledisplayX11) {
3464 				auto ch = cursor.cursorHandle;
3465 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3466 			} else version(Windows) {
3467 				auto ch = cursor.cursorHandle;
3468 				impl.currentCursor = ch;
3469 				SetCursor(ch); // redraw without waiting for mouse movement to update
3470 			} else featureNotImplemented();
3471 		}
3472 
3473 	}
3474 
3475 	/// What follows are the event handlers. These are set automatically
3476 	/// by the eventLoop function, but are still public so you can change
3477 	/// them later. wasPressed == true means key down. false == key up.
3478 
3479 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3480 	void delegate(KeyEvent ke) handleKeyEvent;
3481 
3482 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3483 	void delegate(dchar c) handleCharEvent;
3484 
3485 	/// Handles a timer pulse. Settable through setEventHandlers.
3486 	void delegate() handlePulse;
3487 
3488 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3489 	void delegate(bool) onFocusChange;
3490 
3491 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3492 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3493 	void delegate() onClosing;
3494 
3495 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3496 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3497 	 * last minute cleanup. */
3498 	void delegate() onDestroyed;
3499 
3500 	static if (UsingSimpledisplayX11)
3501 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3502 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3503 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3504 	 *
3505 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3506 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3507 
3508 	//version(Windows)
3509 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3510 
3511 	private {
3512 		int lastMouseX = int.min;
3513 		int lastMouseY = int.min;
3514 		void mdx(ref MouseEvent ev) {
3515 			if(lastMouseX == int.min || lastMouseY == int.min) {
3516 				ev.dx = 0;
3517 				ev.dy = 0;
3518 			} else {
3519 				ev.dx = ev.x - lastMouseX;
3520 				ev.dy = ev.y - lastMouseY;
3521 			}
3522 
3523 			lastMouseX = ev.x;
3524 			lastMouseY = ev.y;
3525 		}
3526 	}
3527 
3528 	/// Mouse event handler. Settable through setEventHandlers.
3529 	void delegate(MouseEvent) handleMouseEvent;
3530 
3531 	/// use to redraw child widgets if you use system apis to add stuff
3532 	void delegate() paintingFinished;
3533 
3534 	void delegate() paintingFinishedDg() {
3535 		return paintingFinished;
3536 	}
3537 
3538 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3539 	/// for this to ever happen.
3540 	void delegate(int width, int height) windowResized;
3541 
3542 	/++
3543 		Platform specific - handle any native message this window gets.
3544 
3545 		Note: this is called *in addition to* other event handlers, unless you either:
3546 
3547 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3548 
3549 		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.
3550 
3551 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3552 
3553 		On X, it takes the form of `int delegate(XEvent)`.
3554 
3555 		History:
3556 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3557 
3558 			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.
3559 	+/
3560 	NativeEventHandler handleNativeEvent_;
3561 
3562 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3563 		return handleNativeEvent_;
3564 	}
3565 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3566 		handleNativeEvent_ = neh;
3567 	}
3568 
3569 	version(Windows)
3570 	// compatibility shim with the old deprecated way
3571 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3572 	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) {
3573 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3574 			auto ret = dg(h, m, w, l);
3575 			if(ret == 0)
3576 				r = 1;
3577 			return ret;
3578 		};
3579 	}
3580 
3581 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3582 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3583 	/// this instead and it will work the same way.
3584 	__gshared NativeEventHandler handleNativeGlobalEvent;
3585 
3586 //  private:
3587 	/// The native implementation is available, but you shouldn't use it unless you are
3588 	/// familiar with the underlying operating system, don't mind depending on it, and
3589 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3590 	/// do what you need to do with handleNativeEvent instead.
3591 	///
3592 	/// This is likely to eventually change to be just a struct holding platform-specific
3593 	/// handles instead of a template mixin at some point because I'm not happy with the
3594 	/// code duplication here (ironically).
3595 	mixin NativeSimpleWindowImplementation!() impl;
3596 
3597 	/**
3598 		This is in-process one-way (from anything to window) event sending mechanics.
3599 		It is thread-safe, so it can be used in multi-threaded applications to send,
3600 		for example, "wake up and repaint" events when thread completed some operation.
3601 		This will allow to avoid using timer pulse to check events with synchronization,
3602 		'cause event handler will be called in UI thread. You can stop guessing which
3603 		pulse frequency will be enough for your app.
3604 		Note that events handlers may be called in arbitrary order, i.e. last registered
3605 		handler can be called first, and vice versa.
3606 	*/
3607 public:
3608 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3609 	 * "spamming" window with events it can't cope with.
3610 	 * It is safe to call this from non-UI threads.
3611 	 */
3612 	@property bool eventQueueEmpty() () {
3613 		synchronized(this) {
3614 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3615 		}
3616 		return true;
3617 	}
3618 
3619 	/** Does our custom event queue contains at least one with the given type?
3620 	 * Can be used in simple cases to prevent "spamming" window with events
3621 	 * it can't cope with.
3622 	 * It is safe to call this from non-UI threads.
3623 	 */
3624 	@property bool eventQueued(ET:Object) () {
3625 		synchronized(this) {
3626 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3627 				if (!o.doProcess) {
3628 					if (cast(ET)(o.evt)) return true;
3629 				}
3630 			}
3631 		}
3632 		return false;
3633 	}
3634 
3635 	/++
3636 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3637 
3638 		History:
3639 			Added May 12, 2021
3640 	+/
3641 	void delegate(Exception e) nothrow eventUncaughtException;
3642 
3643 	/** Add listener for custom event. Can be used like this:
3644 	 *
3645 	 * ---------------------
3646 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3647 	 *   ...
3648 	 *   win.removeEventListener(eid);
3649 	 * ---------------------
3650 	 *
3651 	 * Returns: 0 on failure (should never happen, so ignore it)
3652 	 *
3653 	 * $(WARNING Don't use this method in object destructors!)
3654 	 *
3655 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3656 	 *           'cause if event handler id counter will overflow, you won't be able
3657 	 *           to register any more events.)
3658 	 */
3659 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3660 		if (dg is null) return 0; // ignore empty handlers
3661 		synchronized(this) {
3662 			//FIXME: abort on overflow?
3663 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3664 			EventHandlerEntry e;
3665 			e.dg = delegate (Object o) {
3666 				if (auto co = cast(ET)o) {
3667 					try {
3668 						dg(co);
3669 					} catch (Exception e) {
3670 						// sorry!
3671 						if(eventUncaughtException)
3672 							eventUncaughtException(e);
3673 					}
3674 					return true;
3675 				}
3676 				return false;
3677 			};
3678 			e.id = lastUsedHandlerId;
3679 			auto optr = eventHandlers.ptr;
3680 			eventHandlers ~= e;
3681 			if (eventHandlers.ptr !is optr) {
3682 				import core.memory : GC;
3683 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3684 			}
3685 			return lastUsedHandlerId;
3686 		}
3687 	}
3688 
3689 	/// Remove event listener. It is safe to pass invalid event id here.
3690 	/// $(WARNING Don't use this method in object destructors!)
3691 	void removeEventListener() (uint id) {
3692 		if (id == 0 || id > lastUsedHandlerId) return;
3693 		synchronized(this) {
3694 			foreach (immutable idx; 0..eventHandlers.length) {
3695 				if (eventHandlers[idx].id == id) {
3696 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3697 					eventHandlers[$-1].dg = null;
3698 					eventHandlers.length -= 1;
3699 					eventHandlers.assumeSafeAppend;
3700 					return;
3701 				}
3702 			}
3703 		}
3704 	}
3705 
3706 	/// Post event to queue. It is safe to call this from non-UI threads.
3707 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3708 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3709 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3710 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3711 		if (this.closed) return false; // closed windows can't handle events
3712 
3713 		// remove all events of type `ET`
3714 		void removeAllET () {
3715 			uint eidx = 0, ec = eventQueueUsed;
3716 			auto eptr = eventQueue.ptr;
3717 			while (eidx < ec) {
3718 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3719 				if (cast(ET)eptr.evt !is null) {
3720 					// i found her!
3721 					if (inCustomEventProcessor) {
3722 						// if we're in custom event processing loop, processor will clear it for us
3723 						eptr.evt = null;
3724 						++eidx;
3725 						++eptr;
3726 					} else {
3727 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3728 						ec = --eventQueueUsed;
3729 						// clear last event (it is already copied)
3730 						eventQueue.ptr[ec].evt = null;
3731 					}
3732 				} else {
3733 					++eidx;
3734 					++eptr;
3735 				}
3736 			}
3737 		}
3738 
3739 		if (evt is null) {
3740 			if (replace) { synchronized(this) removeAllET(); }
3741 			// ignore empty events, they can't be handled anyway
3742 			return false;
3743 		}
3744 
3745 		// add events even if no event FD/event object created yet
3746 		synchronized(this) {
3747 			if (replace) removeAllET();
3748 			if (eventQueueUsed == uint.max) return false; // just in case
3749 			if (eventQueueUsed < eventQueue.length) {
3750 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3751 			} else {
3752 				if (eventQueue.capacity == eventQueue.length) {
3753 					// need to reallocate; do a trick to ensure that old array is cleared
3754 					auto oarr = eventQueue;
3755 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3756 					// just in case, do yet another check
3757 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3758 					import core.memory : GC;
3759 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3760 				} else {
3761 					auto optr = eventQueue.ptr;
3762 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3763 					assert(eventQueue.ptr is optr);
3764 				}
3765 				++eventQueueUsed;
3766 				assert(eventQueueUsed == eventQueue.length);
3767 			}
3768 			if (!eventWakeUp()) {
3769 				// can't wake up event processor, so there is no reason to keep the event
3770 				assert(eventQueueUsed > 0);
3771 				eventQueue[--eventQueueUsed].evt = null;
3772 				return false;
3773 			}
3774 			return true;
3775 		}
3776 	}
3777 
3778 	/// Post event to queue. It is safe to call this from non-UI threads.
3779 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3780 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3781 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3782 		return postTimeout!ET(evt, 0, replace);
3783 	}
3784 
3785 private:
3786 	private import core.time : MonoTime;
3787 
3788 	version(Posix) {
3789 		__gshared int customEventFDRead = -1;
3790 		__gshared int customEventFDWrite = -1;
3791 		__gshared int customSignalFD = -1;
3792 	} else version(Windows) {
3793 		__gshared HANDLE customEventH = null;
3794 	}
3795 
3796 	// wake up event processor
3797 	static bool eventWakeUp () {
3798 		version(X11) {
3799 			import core.sys.posix.unistd : write;
3800 			ulong n = 1;
3801 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3802 			return true;
3803 		} else version(Windows) {
3804 			if (customEventH !is null) SetEvent(customEventH);
3805 			return true;
3806 		} else version(OSXCocoa) {
3807 			if(globalAppDelegate)
3808 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3809 			return true;
3810 		} else {
3811 			// not implemented for other OSes
3812 			return false;
3813 		}
3814 	}
3815 
3816 	static struct QueuedEvent {
3817 		Object evt;
3818 		bool timed = false;
3819 		MonoTime hittime = MonoTime.zero;
3820 		bool doProcess = false; // process event at the current iteration (internal flag)
3821 
3822 		this (Object aevt, uint toutmsecs) {
3823 			evt = aevt;
3824 			if (toutmsecs > 0) {
3825 				import core.time : msecs;
3826 				timed = true;
3827 				hittime = MonoTime.currTime+toutmsecs.msecs;
3828 			}
3829 		}
3830 	}
3831 
3832 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3833 	static struct EventHandlerEntry {
3834 		CustomEventHandler dg;
3835 		uint id;
3836 	}
3837 
3838 	uint lastUsedHandlerId;
3839 	EventHandlerEntry[] eventHandlers;
3840 	QueuedEvent[] eventQueue = null;
3841 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3842 	bool inCustomEventProcessor = false; // required to properly remove events
3843 
3844 	// process queued events and call custom event handlers
3845 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3846 	void processCustomEvents () @system {
3847 		bool hasSomethingToDo = false;
3848 		uint ecount;
3849 		bool ocep;
3850 		synchronized(this) {
3851 			ocep = inCustomEventProcessor;
3852 			inCustomEventProcessor = true;
3853 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3854 			auto ctt = MonoTime.currTime;
3855 			bool hasEmpty = false;
3856 			// mark events to process (this is required for `eventQueued()`)
3857 			foreach (ref qe; eventQueue[0..ecount]) {
3858 				if (qe.evt is null) { hasEmpty = true; continue; }
3859 				if (qe.timed) {
3860 					qe.doProcess = (qe.hittime <= ctt);
3861 				} else {
3862 					qe.doProcess = true;
3863 				}
3864 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3865 			}
3866 			if (!hasSomethingToDo) {
3867 				// remove empty events
3868 				if (hasEmpty) {
3869 					uint eidx = 0, ec = eventQueueUsed;
3870 					auto eptr = eventQueue.ptr;
3871 					while (eidx < ec) {
3872 						if (eptr.evt is null) {
3873 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3874 							ec = --eventQueueUsed;
3875 							eventQueue.ptr[ec].evt = null; // make GC life easier
3876 						} else {
3877 							++eidx;
3878 							++eptr;
3879 						}
3880 					}
3881 				}
3882 				inCustomEventProcessor = ocep;
3883 				return;
3884 			}
3885 		}
3886 		// process marked events
3887 		uint efree = 0; // non-processed events will be put at this index
3888 		EventHandlerEntry[] eh;
3889 		Object evt;
3890 		foreach (immutable eidx; 0..ecount) {
3891 			synchronized(this) {
3892 				if (!eventQueue[eidx].doProcess) {
3893 					// skip this event
3894 					assert(efree <= eidx);
3895 					if (efree != eidx) {
3896 						// copy this event to queue start
3897 						eventQueue[efree] = eventQueue[eidx];
3898 						eventQueue[eidx].evt = null; // just in case
3899 					}
3900 					++efree;
3901 					continue;
3902 				}
3903 				evt = eventQueue[eidx].evt;
3904 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3905 				if (evt is null) continue; // just in case
3906 				// try all handlers; this can be slow, but meh...
3907 				eh = eventHandlers;
3908 			}
3909 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3910 			evt = null;
3911 			eh = null;
3912 		}
3913 		synchronized(this) {
3914 			// move all unprocessed events to queue top; efree holds first "free index"
3915 			foreach (immutable eidx; ecount..eventQueueUsed) {
3916 				assert(efree <= eidx);
3917 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3918 				++efree;
3919 			}
3920 			eventQueueUsed = efree;
3921 			// wake up event processor on next event loop iteration if we have more queued events
3922 			// also, remove empty events
3923 			bool awaken = false;
3924 			uint eidx = 0, ec = eventQueueUsed;
3925 			auto eptr = eventQueue.ptr;
3926 			while (eidx < ec) {
3927 				if (eptr.evt is null) {
3928 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3929 					ec = --eventQueueUsed;
3930 					eventQueue.ptr[ec].evt = null; // make GC life easier
3931 				} else {
3932 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3933 					++eidx;
3934 					++eptr;
3935 				}
3936 			}
3937 			inCustomEventProcessor = ocep;
3938 		}
3939 	}
3940 
3941 	// for all windows in nativeMapping
3942 	package static void processAllCustomEvents () @system {
3943 
3944 		cleanupQueue.process();
3945 
3946 		justCommunication.processCustomEvents();
3947 
3948 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3949 			if (sw is null || sw.closed) continue;
3950 			sw.processCustomEvents();
3951 		}
3952 
3953 		runPendingRunInGuiThreadDelegates();
3954 	}
3955 
3956 	// 0: infinite (i.e. no scheduled events in queue)
3957 	uint eventQueueTimeoutMSecs () {
3958 		synchronized(this) {
3959 			if (eventQueueUsed == 0) return 0;
3960 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3961 			uint res = int.max;
3962 			auto ctt = MonoTime.currTime;
3963 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3964 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3965 				if (qe.doProcess) continue; // just in case
3966 				if (!qe.timed) return 1; // minimal
3967 				if (qe.hittime <= ctt) return 1; // minimal
3968 				auto tms = (qe.hittime-ctt).total!"msecs";
3969 				if (tms < 1) tms = 1; // safety net
3970 				if (tms >= int.max) tms = int.max-1; // and another safety net
3971 				if (res > tms) res = cast(uint)tms;
3972 			}
3973 			return (res >= int.max ? 0 : res);
3974 		}
3975 	}
3976 
3977 	// for all windows in nativeMapping
3978 	static uint eventAllQueueTimeoutMSecs () {
3979 		uint res = uint.max;
3980 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3981 			if (sw is null || sw.closed) continue;
3982 			uint to = sw.eventQueueTimeoutMSecs();
3983 			if (to && to < res) {
3984 				res = to;
3985 				if (to == 1) break; // can't have less than this
3986 			}
3987 		}
3988 		return (res >= int.max ? 0 : res);
3989 	}
3990 
3991 	version(X11) {
3992 		ResizeEvent pendingResizeEvent;
3993 	}
3994 
3995 	/++
3996 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3997 
3998 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3999 		worth so you can disable it by setting this to `true`.
4000 
4001 		History:
4002 			Added November 13, 2022.
4003 	+/
4004 	public bool suppressAutoOpenglViewport = false;
4005 	private void updateOpenglViewportIfNeeded(int width, int height) {
4006 		if(suppressAutoOpenglViewport) return;
4007 
4008 		version(without_opengl) {} else
4009 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
4010 		// writeln(width, " ", height);
4011 			setAsCurrentOpenGlContextNT();
4012 			glViewport(0, 0, width, height);
4013 		}
4014 	}
4015 
4016 	// TODO: Implement on non-Windows platforms (where available).
4017 	private CornerStyle _fauxCornerStyle = CornerStyle.automatic;
4018 
4019 	/++
4020 		Style of the window's corners
4021 
4022 		$(WARNING
4023 			Currently only implemented on Windows targets.
4024 			Has no visual effect elsewhere.
4025 
4026 			Windows: Requires Windows 11 or later.
4027 		)
4028 
4029 		History:
4030 			Added September 09, 2024.
4031 	 +/
4032 	public CornerStyle cornerStyle() @trusted {
4033 		version(Windows) {
4034 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4035 			const apiResult = DwmGetWindowAttribute(
4036 				this.hwnd,
4037 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4038 				&dwmCorner,
4039 				typeof(dwmCorner).sizeof
4040 			);
4041 
4042 			if (apiResult != S_OK) {
4043 				// Unsupported?
4044 				if (apiResult == E_INVALIDARG) {
4045 					// Feature unsupported; Windows version probably too old.
4046 					// Requires Windows 11 (build 22000) or later.
4047 					return _fauxCornerStyle;
4048 				}
4049 
4050 				throw new WindowsApiException("DwmGetWindowAttribute", apiResult);
4051 			}
4052 
4053 			CornerStyle corner;
4054 			if (!dwmCorner.fromDWM(corner)) {
4055 				throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner);
4056 			}
4057 			return corner;
4058 		} else {
4059 			return _fauxCornerStyle;
4060 		}
4061 	}
4062 
4063 	/// ditto
4064 	public void cornerStyle(const CornerStyle corner) @trusted {
4065 		version(Windows) {
4066 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4067 			if (!corner.toDWM(dwmCorner)) {
4068 				assert(false, "This should have been impossible because of a final switch.");
4069 			}
4070 
4071 			const apiResult = DwmSetWindowAttribute(
4072 				this.hwnd,
4073 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4074 				&dwmCorner,
4075 				typeof(dwmCorner).sizeof
4076 			);
4077 
4078 			if (apiResult != S_OK) {
4079 				// Unsupported?
4080 				if (apiResult == E_INVALIDARG) {
4081 					// Feature unsupported; Windows version probably too old.
4082 					// Requires Windows 11 (build 22000) or later.
4083 					_fauxCornerStyle = corner;
4084 					return;
4085 				}
4086 
4087 				throw new WindowsApiException("DwmSetWindowAttribute", apiResult);
4088 			}
4089 		} else {
4090 			_fauxCornerStyle = corner;
4091 		}
4092 	}
4093 }
4094 
4095 version(OSXCocoa)
4096 	enum NSWindow NullWindow = null;
4097 else
4098 	enum NullWindow = NativeWindowHandle.init;
4099 
4100 /++
4101 	Magic pseudo-window for just posting events to a global queue.
4102 
4103 	Not entirely supported, I might delete it at any time.
4104 
4105 	Added Nov 5, 2021.
4106 +/
4107 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
4108 
4109 /* Drag and drop support { */
4110 version(X11) {
4111 
4112 } else version(Windows) {
4113 	import core.sys.windows.uuid;
4114 	import core.sys.windows.ole2;
4115 	import core.sys.windows.oleidl;
4116 	import core.sys.windows.objidl;
4117 	import core.sys.windows.wtypes;
4118 
4119 	pragma(lib, "ole32");
4120 	void initDnd() {
4121 		auto err = OleInitialize(null);
4122 		if(err != S_OK && err != S_FALSE)
4123 			throw new Exception("init");//err);
4124 	}
4125 }
4126 /* } End drag and drop support */
4127 
4128 
4129 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
4130 /// See [GenericCursor].
4131 class MouseCursor {
4132 	int osId;
4133 	bool isStockCursor;
4134 	private this(int osId) {
4135 		this.osId = osId;
4136 		this.isStockCursor = true;
4137 	}
4138 
4139 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
4140 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
4141 
4142 	version(Windows) {
4143 		HCURSOR cursor_;
4144 		HCURSOR cursorHandle() {
4145 			if(cursor_ is null)
4146 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
4147 			return cursor_;
4148 		}
4149 
4150 	} else static if(UsingSimpledisplayX11) {
4151 		Cursor cursor_ = None;
4152 		int xDisplaySequence;
4153 
4154 		Cursor cursorHandle() {
4155 			if(this.osId == None)
4156 				return None;
4157 
4158 			// we need to reload if we on a new X connection
4159 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
4160 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
4161 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
4162 			}
4163 			return cursor_;
4164 		}
4165 	}
4166 }
4167 
4168 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
4169 // https://tronche.com/gui/x/xlib/appendix/b/
4170 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
4171 /// 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.
4172 enum GenericCursorType {
4173 	Default, /// The default arrow pointer.
4174 	Wait, /// A cursor indicating something is loading and the user must wait.
4175 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
4176 	Help, /// A cursor indicating the user can get help about the pointer location.
4177 	Cross, /// A crosshair.
4178 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
4179 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
4180 	UpArrow, /// An arrow pointing straight up.
4181 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
4182 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
4183 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
4184 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
4185 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
4186 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
4187 
4188 }
4189 
4190 /*
4191 	X_plus == css cell == Windows ?
4192 */
4193 
4194 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
4195 static struct GenericCursor {
4196 	static:
4197 	///
4198 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
4199 		static MouseCursor mc;
4200 
4201 		auto type = __traits(getMember, GenericCursorType, str);
4202 
4203 		if(mc is null) {
4204 
4205 			version(Windows) {
4206 				int osId;
4207 				final switch(type) {
4208 					case GenericCursorType.Default: osId = IDC_ARROW; break;
4209 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
4210 					case GenericCursorType.Hand: osId = IDC_HAND; break;
4211 					case GenericCursorType.Help: osId = IDC_HELP; break;
4212 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
4213 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
4214 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
4215 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
4216 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
4217 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
4218 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
4219 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
4220 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
4221 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
4222 				}
4223 			} else static if(UsingSimpledisplayX11) {
4224 				int osId;
4225 				final switch(type) {
4226 					case GenericCursorType.Default: osId = None; break;
4227 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
4228 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
4229 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
4230 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
4231 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
4232 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
4233 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
4234 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
4235 
4236 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
4237 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
4238 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
4239 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
4240 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
4241 				}
4242 
4243 			} else {
4244 				int osId;
4245 				// featureNotImplemented();
4246 			}
4247 
4248 			mc = new MouseCursor(osId);
4249 		}
4250 		return mc;
4251 	}
4252 }
4253 
4254 
4255 /++
4256 	If you want to get more control over the event loop, you can use this.
4257 
4258 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4259 	to `EventLoop.get.run`.
4260 +/
4261 struct EventLoop {
4262 	@disable this();
4263 
4264 	/// Gets a reference to an existing event loop
4265 	static EventLoop get() {
4266 		return EventLoop(0, null);
4267 	}
4268 
4269 	static void quitApplication() {
4270 		version(use_arsd_core) {
4271 			import arsd.core;
4272 			ICoreEventLoop.exitApplication();
4273 		}
4274 		EventLoop.get().exit();
4275 	}
4276 
4277 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4278 
4279 	/// Construct an application-global event loop for yourself
4280 	/// See_Also: [SimpleWindow.setEventHandlers]
4281 	this(long pulseTimeout, void delegate() handlePulse) {
4282 		synchronized(monitor) {
4283 			if(impl is null) {
4284 				claimGuiThread();
4285 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4286 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4287 			} else {
4288 				if(pulseTimeout) {
4289 					impl.pulseTimeout = pulseTimeout;
4290 					impl.handlePulse = handlePulse;
4291 				}
4292 			}
4293 			impl.refcount++;
4294 		}
4295 	}
4296 
4297 	~this() {
4298 		if(impl is null)
4299 			return;
4300 		impl.refcount--;
4301 		if(impl.refcount == 0) {
4302 			impl.dispose();
4303 			if(thisIsGuiThread)
4304 				guiThreadFinalize();
4305 		}
4306 
4307 	}
4308 
4309 	this(this) {
4310 		if(impl is null)
4311 			return;
4312 		impl.refcount++;
4313 	}
4314 
4315 	/// Runs the event loop until the whileCondition, if present, returns false
4316 	int run(bool delegate() whileCondition = null) {
4317 		assert(impl !is null);
4318 		impl.notExited = true;
4319 		return impl.run(whileCondition);
4320 	}
4321 
4322 	/// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program)
4323 	void exit() {
4324 		assert(impl !is null);
4325 		impl.notExited = false;
4326 
4327 		version(use_arsd_core) {
4328 			import arsd.core;
4329 			ICoreEventLoop.exitApplication();
4330 		}
4331 	}
4332 
4333 	version(linux)
4334 	ref void delegate(int) signalHandler() {
4335 		assert(impl !is null);
4336 		return impl.signalHandler;
4337 	}
4338 
4339 	__gshared static EventLoopImpl* impl;
4340 }
4341 
4342 version(linux)
4343 	void delegate(int, int) globalHupHandler;
4344 
4345 version(Posix)
4346 	void makeNonBlocking(int fd) {
4347 		import fcntl = core.sys.posix.fcntl;
4348 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4349 		if(flags == -1)
4350 			throw new Exception("fcntl get");
4351 		flags |= fcntl.O_NONBLOCK;
4352 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4353 		if(s == -1)
4354 			throw new Exception("fcntl set");
4355 	}
4356 
4357 struct EventLoopImpl {
4358 	int refcount;
4359 
4360 	bool notExited = true;
4361 
4362 	version(linux) {
4363 		static import ep = core.sys.linux.epoll;
4364 		static import unix = core.sys.posix.unistd;
4365 		static import err = core.stdc.errno;
4366 		import core.sys.linux.timerfd;
4367 
4368 		void delegate(int) signalHandler;
4369 	}
4370 
4371 	version(X11) {
4372 		int pulseFd = -1;
4373 		version(linux) ep.epoll_event[16] events = void;
4374 	} else version(Windows) {
4375 		Timer pulser;
4376 		HANDLE[] handles;
4377 	}
4378 
4379 
4380 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4381 	/// to call this, as it's not recommended to share window between threads.
4382 	void mtLock () {
4383 		version(X11) {
4384 			XLockDisplay(this.display);
4385 		}
4386 	}
4387 
4388 	version(X11)
4389 	auto display() { return XDisplayConnection.get; }
4390 
4391 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4392 	/// to call this, as it's not recommended to share window between threads.
4393 	void mtUnlock () {
4394 		version(X11) {
4395 			XUnlockDisplay(this.display);
4396 		}
4397 	}
4398 
4399 	version(with_eventloop)
4400 	void initialize(long pulseTimeout) {}
4401 	else
4402 	void initialize(long pulseTimeout) @system {
4403 		version(Windows) {
4404 			if(pulseTimeout && handlePulse !is null)
4405 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4406 
4407 			if (customEventH is null) {
4408 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4409 				if (customEventH !is null) {
4410 					handles ~= customEventH;
4411 				} else {
4412 					// this is something that should not be; better be safe than sorry
4413 					throw new Exception("can't create eventfd for custom event processing");
4414 				}
4415 			}
4416 
4417 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4418 		}
4419 
4420 		version(linux) {
4421 			prepareEventLoop();
4422 			{
4423 				auto display = XDisplayConnection.get;
4424 				// adding Xlib file
4425 				ep.epoll_event ev = void;
4426 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4427 				ev.events = ep.EPOLLIN;
4428 				ev.data.fd = display.fd;
4429 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4430 					throw new Exception("add x fd");// ~ to!string(epollFd));
4431 				displayFd = display.fd;
4432 			}
4433 
4434 			if(pulseTimeout && handlePulse !is null) {
4435 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4436 				if(pulseFd == -1)
4437 					throw new Exception("pulse timer create failed");
4438 
4439 				itimerspec value;
4440 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4441 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4442 
4443 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4444 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4445 
4446 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4447 					throw new Exception("couldn't make pulse timer");
4448 
4449 				ep.epoll_event ev = void;
4450 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4451 				ev.events = ep.EPOLLIN;
4452 				ev.data.fd = pulseFd;
4453 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4454 			}
4455 
4456 			// eventfd for custom events
4457 			if (customEventFDWrite == -1) {
4458 				customEventFDWrite = eventfd(0, 0);
4459 				customEventFDRead = customEventFDWrite;
4460 				if (customEventFDRead >= 0) {
4461 					ep.epoll_event ev = void;
4462 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4463 					ev.events = ep.EPOLLIN;
4464 					ev.data.fd = customEventFDRead;
4465 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4466 				} else {
4467 					// this is something that should not be; better be safe than sorry
4468 					throw new Exception("can't create eventfd for custom event processing");
4469 				}
4470 			}
4471 
4472 			if (customSignalFD == -1) {
4473 				import core.sys.linux.sys.signalfd;
4474 
4475 				sigset_t sigset;
4476 				auto err = sigemptyset(&sigset);
4477 				assert(!err);
4478 				err = sigaddset(&sigset, SIGINT);
4479 				assert(!err);
4480 				err = sigaddset(&sigset, SIGHUP);
4481 				assert(!err);
4482 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4483 				assert(!err);
4484 
4485 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4486 				assert(customSignalFD != -1);
4487 
4488 				ep.epoll_event ev = void;
4489 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4490 				ev.events = ep.EPOLLIN;
4491 				ev.data.fd = customSignalFD;
4492 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4493 			}
4494 		} else version(Posix) {
4495 			prepareEventLoop();
4496 			if (customEventFDRead == -1) {
4497 				int[2] bfr;
4498 				import core.sys.posix.unistd;
4499 				auto ret = pipe(bfr);
4500 				if(ret == -1) throw new Exception("pipe");
4501 				customEventFDRead = bfr[0];
4502 				customEventFDWrite = bfr[1];
4503 			}
4504 
4505 		}
4506 
4507 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4508 
4509 		version(linux) {
4510 			this.mtLock();
4511 			scope(exit) this.mtUnlock();
4512 			XPending(display); // no, really
4513 		}
4514 
4515 		disposed = false;
4516 	}
4517 
4518 	bool disposed = true;
4519 	version(X11)
4520 		int displayFd = -1;
4521 
4522 	version(with_eventloop)
4523 	void dispose() {}
4524 	else
4525 	void dispose() @system {
4526 		disposed = true;
4527 		version(X11) {
4528 			if(pulseFd != -1) {
4529 				import unix = core.sys.posix.unistd;
4530 				unix.close(pulseFd);
4531 				pulseFd = -1;
4532 			}
4533 
4534 				version(linux)
4535 				if(displayFd != -1) {
4536 					// 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
4537 					ep.epoll_event ev = void;
4538 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4539 					ev.events = ep.EPOLLIN;
4540 					ev.data.fd = displayFd;
4541 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4542 					displayFd = -1;
4543 				}
4544 
4545 		} else version(Windows) {
4546 			if(pulser !is null) {
4547 				pulser.destroy();
4548 				pulser = null;
4549 			}
4550 			if (customEventH !is null) {
4551 				CloseHandle(customEventH);
4552 				customEventH = null;
4553 			}
4554 		}
4555 	}
4556 
4557 	this(long pulseTimeout, void delegate() handlePulse) {
4558 		this.pulseTimeout = pulseTimeout;
4559 		this.handlePulse = handlePulse;
4560 		initialize(pulseTimeout);
4561 	}
4562 
4563 	private long pulseTimeout;
4564 	void delegate() handlePulse;
4565 
4566 	~this() {
4567 		dispose();
4568 	}
4569 
4570 	version(Posix)
4571 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4572 	version(Posix)
4573 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4574 	version(linux)
4575 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4576 	version(Windows)
4577 	ref auto customEventH() { return SimpleWindow.customEventH; }
4578 
4579 	version(X11) {
4580 		bool doXPending() {
4581 			bool done = false;
4582 
4583 			this.mtLock();
4584 			scope(exit) this.mtUnlock();
4585 			//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4586 			while(!done && XPending(display)) {
4587 				done = doXNextEvent(this.display);
4588 			}
4589 
4590 			return done;
4591 		}
4592 		void doXNextEventVoid() {
4593 			doXPending();
4594 		}
4595 	}
4596 
4597 	version(with_eventloop) {
4598 		int loopHelper(bool delegate() whileCondition) {
4599 			// FIXME: whileCondition
4600 			import arsd.eventloop;
4601 			loop();
4602 			return 0;
4603 		}
4604 	} else
4605 	int loopHelper(bool delegate() whileCondition) {
4606 		version(X11) {
4607 			bool done = false;
4608 
4609 			XFlush(display);
4610 			insideXEventLoop = true;
4611 			scope(exit) insideXEventLoop = false;
4612 
4613 			version(use_arsd_core) {
4614 				import arsd.core;
4615 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4616 
4617 				static bool loopInitialized = false;
4618 				if(!loopInitialized) {
4619 					el.addDelegateOnLoopIteration(&doXNextEventVoid, 0);
4620 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4621 
4622 					if(customSignalFD != -1)
4623 					cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() {
4624 						version(linux) {
4625 							import core.sys.linux.sys.signalfd;
4626 							import core.sys.posix.unistd : read;
4627 							signalfd_siginfo info;
4628 							read(customSignalFD, &info, info.sizeof);
4629 
4630 							auto sig = info.ssi_signo;
4631 
4632 							if(EventLoop.get.signalHandler !is null) {
4633 								EventLoop.get.signalHandler()(sig);
4634 							} else {
4635 								EventLoop.get.exit();
4636 							}
4637 						}
4638 					}));
4639 
4640 					if(display.fd != -1)
4641 					cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() {
4642 						this.mtLock();
4643 						scope(exit) this.mtUnlock();
4644 						while(!done && XPending(display)) {
4645 							done = doXNextEvent(this.display);
4646 						}
4647 					}));
4648 
4649 					if(pulseFd != -1)
4650 					cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() {
4651 						long expirationCount;
4652 						// 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...
4653 
4654 						handlePulse();
4655 
4656 						// read just to clear the buffer so poll doesn't trigger again
4657 						// BTW I read AFTER the pulse because if the pulse handler takes
4658 						// a lot of time to execute, we don't want the app to get stuck
4659 						// in a loop of timer hits without a chance to do anything else
4660 						//
4661 						// IOW handlePulse happens at most once per pulse interval.
4662 						unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4663 					}));
4664 
4665 					if(customEventFDRead != -1)
4666 					cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() {
4667 						// we have some custom events; process 'em
4668 						import core.sys.posix.unistd : read;
4669 						ulong n;
4670 						read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4671 						//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4672 						//SimpleWindow.processAllCustomEvents();
4673 					}));
4674 
4675 					// FIXME: posix fds
4676 					// FIXME up?
4677 
4678 
4679 					loopInitialized = true;
4680 				}
4681 
4682 				el.run(() => !whileCondition());
4683 			} else version(linux) {
4684 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4685 					bool forceXPending = false;
4686 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4687 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4688 					{
4689 						this.mtLock();
4690 						scope(exit) this.mtUnlock();
4691 						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
4692 					}
4693 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4694 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4695 					if(nfds == -1) {
4696 						if(err.errno == err.EINTR) {
4697 							//if(forceXPending) goto xpending;
4698 							continue; // interrupted by signal, just try again
4699 						}
4700 						throw new Exception("epoll wait failure");
4701 					}
4702 					// writeln(nfds, " ", events[0].data.fd);
4703 
4704 					SimpleWindow.processAllCustomEvents(); // anyway
4705 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4706 					foreach(idx; 0 .. nfds) {
4707 						if(done) break;
4708 						auto fd = events[idx].data.fd;
4709 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4710 						auto flags = events[idx].events;
4711 						if(flags & ep.EPOLLIN) {
4712 							if (fd == customSignalFD) {
4713 								version(linux) {
4714 									import core.sys.linux.sys.signalfd;
4715 									import core.sys.posix.unistd : read;
4716 									signalfd_siginfo info;
4717 									read(customSignalFD, &info, info.sizeof);
4718 
4719 									auto sig = info.ssi_signo;
4720 
4721 									if(EventLoop.get.signalHandler !is null) {
4722 										EventLoop.get.signalHandler()(sig);
4723 									} else {
4724 										EventLoop.get.exit();
4725 									}
4726 								}
4727 							} else if(fd == display.fd) {
4728 								version(sdddd) { writeln("X EVENT PENDING!"); }
4729 								this.mtLock();
4730 								scope(exit) this.mtUnlock();
4731 								while(!done && XPending(display)) {
4732 									done = doXNextEvent(this.display);
4733 								}
4734 								forceXPending = false;
4735 							} else if(fd == pulseFd) {
4736 								long expirationCount;
4737 								// 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...
4738 
4739 								handlePulse();
4740 
4741 								// read just to clear the buffer so poll doesn't trigger again
4742 								// BTW I read AFTER the pulse because if the pulse handler takes
4743 								// a lot of time to execute, we don't want the app to get stuck
4744 								// in a loop of timer hits without a chance to do anything else
4745 								//
4746 								// IOW handlePulse happens at most once per pulse interval.
4747 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4748 								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
4749 							} else if (fd == customEventFDRead) {
4750 								// we have some custom events; process 'em
4751 								import core.sys.posix.unistd : read;
4752 								ulong n;
4753 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4754 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4755 								//SimpleWindow.processAllCustomEvents();
4756 
4757 								forceXPending = true;
4758 							} else {
4759 								// some other timer
4760 								version(sdddd) { writeln("unknown fd: ", fd); }
4761 
4762 								if(Timer* t = fd in Timer.mapping)
4763 									(*t).trigger();
4764 
4765 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4766 									(*pfr).ready(flags);
4767 
4768 								// we don't know what the user did in this timer, so we need to assume that
4769 								// there's X data to be flushed and potentially processed
4770 								forceXPending = true;
4771 
4772 								// or i might add support for other FDs too
4773 								// but for now it is just timer
4774 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4775 							}
4776 						}
4777 						if(flags & ep.EPOLLHUP) {
4778 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4779 								(*pfr).hup(flags);
4780 							if(globalHupHandler)
4781 								globalHupHandler(fd, flags);
4782 						}
4783 						/+
4784 						} else {
4785 							// not interested in OUT, we are just reading here.
4786 							//
4787 							// error or hup might also be reported
4788 							// but it shouldn't here since we are only
4789 							// using a few types of FD and Xlib will report
4790 							// if it dies.
4791 							// so instead of thoughtfully handling it, I'll
4792 							// just throw. for now at least
4793 
4794 							throw new Exception("epoll did something else");
4795 						}
4796 						+/
4797 					}
4798 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4799 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4800 					xpending:
4801 					if (!done && forceXPending) {
4802 						done = doXPending();
4803 					}
4804 				}
4805 			} else {
4806 				// Generic fallback: yes to simple pulse support,
4807 				// but NO timer support!
4808 
4809 				// FIXME: we could probably support the POSIX timer_create
4810 				// signal-based option, but I'm in no rush to write it since
4811 				// I prefer the fd-based functions.
4812 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4813 
4814 					import core.sys.posix.poll;
4815 
4816 					pollfd[] pfds;
4817 					pollfd[32] pfdsBuffer;
4818 					auto len = PosixFdReader.mapping.length + 2;
4819 					// FIXME: i should just reuse the buffer
4820 					if(len < pfdsBuffer.length)
4821 						pfds = pfdsBuffer[0 .. len];
4822 					else
4823 						pfds = new pollfd[](len);
4824 
4825 					pfds[0].fd = display.fd;
4826 					pfds[0].events = POLLIN;
4827 					pfds[0].revents = 0;
4828 
4829 					int slot = 1;
4830 
4831 					if(customEventFDRead != -1) {
4832 						pfds[slot].fd = customEventFDRead;
4833 						pfds[slot].events = POLLIN;
4834 						pfds[slot].revents = 0;
4835 
4836 						slot++;
4837 					}
4838 
4839 					foreach(fd, obj; PosixFdReader.mapping) {
4840 						if(!obj.enabled) continue;
4841 						pfds[slot].fd = fd;
4842 						pfds[slot].events = POLLIN;
4843 						pfds[slot].revents = 0;
4844 
4845 						slot++;
4846 					}
4847 
4848 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4849 					if(ret == -1) throw new Exception("poll");
4850 
4851 					if(ret == 0) {
4852 						// FIXME it may not necessarily time out if events keep coming
4853 						if(handlePulse !is null)
4854 							handlePulse();
4855 					} else {
4856 						foreach(s; 0 .. slot) {
4857 							if(pfds[s].revents == 0) continue;
4858 
4859 							if(pfds[s].fd == display.fd) {
4860 								while(!done && XPending(display)) {
4861 									this.mtLock();
4862 									scope(exit) this.mtUnlock();
4863 									done = doXNextEvent(this.display);
4864 								}
4865 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4866 
4867 								import core.sys.posix.unistd : read;
4868 								ulong n;
4869 								read(customEventFDRead, &n, n.sizeof);
4870 								SimpleWindow.processAllCustomEvents();
4871 							} else {
4872 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4873 								if(pfds[s].revents & POLLNVAL) {
4874 									obj.dispose();
4875 								} else {
4876 									obj.ready(pfds[s].revents);
4877 								}
4878 							}
4879 
4880 							ret--;
4881 							if(ret == 0) break;
4882 						}
4883 					}
4884 				}
4885 			}
4886 		}
4887 
4888 		version(Windows) {
4889 
4890 			version(use_arsd_core) {
4891 				import arsd.core;
4892 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4893 				static bool loopInitialized = false;
4894 				if(!loopInitialized) {
4895 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4896 					el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0);
4897 					loopInitialized = true;
4898 				}
4899 				el.run(() => !whileCondition());
4900 			} else {
4901 				int ret = -1;
4902 				MSG message;
4903 				while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4904 					eventLoopRound++;
4905 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4906 					auto waitResult = MsgWaitForMultipleObjectsEx(
4907 						cast(int) handles.length, handles.ptr,
4908 						(wto == 0 ? INFINITE : wto), /* timeout */
4909 						0x04FF, /* QS_ALLINPUT */
4910 						0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4911 
4912 					SimpleWindow.processAllCustomEvents(); // anyway
4913 					enum WAIT_OBJECT_0 = 0;
4914 					if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4915 						auto h = handles[waitResult - WAIT_OBJECT_0];
4916 						if(auto e = h in WindowsHandleReader.mapping) {
4917 							(*e).ready();
4918 						}
4919 					} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4920 						// message ready
4921 						int count;
4922 						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
4923 							ret = GetMessage(&message, null, 0, 0);
4924 							if(ret == -1)
4925 								throw new WindowsApiException("GetMessage", GetLastError());
4926 							TranslateMessage(&message);
4927 							DispatchMessage(&message);
4928 
4929 							count++;
4930 							if(count > 10)
4931 								break; // take the opportunity to catch up on other events
4932 
4933 							if(ret == 0) { // WM_QUIT
4934 								EventLoop.quitApplication();
4935 								break;
4936 							}
4937 						}
4938 					} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4939 						SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4940 					} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4941 						// timeout, should never happen since we aren't using it
4942 					} else if(waitResult == 0xFFFFFFFF) {
4943 							// failed
4944 							throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4945 					} else {
4946 						// idk....
4947 					}
4948 				}
4949 			}
4950 
4951 			// return message.wParam;
4952 			return 0;
4953 		} else {
4954 			return 0;
4955 		}
4956 	}
4957 
4958 	int run(bool delegate() whileCondition = null) {
4959 		if(disposed)
4960 			initialize(this.pulseTimeout);
4961 
4962 		version(X11) {
4963 			try {
4964 				return loopHelper(whileCondition);
4965 			} catch(XDisconnectException e) {
4966 				if(e.userRequested) {
4967 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4968 						item.discardConnectionState();
4969 					XCloseDisplay(XDisplayConnection.display);
4970 				}
4971 
4972 				XDisplayConnection.display = null;
4973 
4974 				this.dispose();
4975 
4976 				throw e;
4977 			}
4978 		} else {
4979 			return loopHelper(whileCondition);
4980 		}
4981 	}
4982 }
4983 
4984 
4985 /++
4986 	Provides an icon on the system notification area (also known as the system tray).
4987 
4988 
4989 	If a notification area is not available with the NotificationIcon object is created,
4990 	it will silently succeed and simply attempt to create one when an area becomes available.
4991 
4992 
4993 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
4994 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
4995 	with true color was added at that time. I was just too lazy to write the fallback.
4996 
4997 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
4998 	you use arsd 10.x when targeting Windows XP.
4999 +/
5000 version(OSXCocoa) {} else // NotYetImplementedException
5001 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
5002 
5003 	version(X11) {
5004 		void recreateAfterDisconnect() {
5005 			stateDiscarded = false;
5006 			clippixmap = None;
5007 			throw new Exception("NOT IMPLEMENTED");
5008 		}
5009 
5010 		bool stateDiscarded;
5011 		void discardConnectionState() {
5012 			stateDiscarded = true;
5013 		}
5014 	}
5015 
5016 
5017 	version(X11) {
5018 		Image img;
5019 
5020 		NativeEventHandler getNativeEventHandler() {
5021 			return delegate int(XEvent e) {
5022 				switch(e.type) {
5023 					case EventType.Expose:
5024 					//case EventType.VisibilityNotify:
5025 						redraw();
5026 					break;
5027 					case EventType.ClientMessage:
5028 						version(sddddd) {
5029 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
5030 						writeln("\t", e.xclient.format);
5031 						writeln("\t", e.xclient.data.l);
5032 						}
5033 					break;
5034 					case EventType.ButtonPress:
5035 						auto event = e.xbutton;
5036 						if (onClick !is null || onClickEx !is null) {
5037 							MouseButton mb = cast(MouseButton)0;
5038 							switch (event.button) {
5039 								case 1: mb = MouseButton.left; break; // left
5040 								case 2: mb = MouseButton.middle; break; // middle
5041 								case 3: mb = MouseButton.right; break; // right
5042 								case 4: mb = MouseButton.wheelUp; break; // scroll up
5043 								case 5: mb = MouseButton.wheelDown; break; // scroll down
5044 								case 6: break; // scroll left...
5045 								case 7: break; // scroll right...
5046 								case 8: mb = MouseButton.backButton; break;
5047 								case 9: mb = MouseButton.forwardButton; break;
5048 								default:
5049 							}
5050 							if (mb) {
5051 								try { onClick()(mb); } catch (Exception) {}
5052 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
5053 							}
5054 						}
5055 					break;
5056 					case EventType.EnterNotify:
5057 						if (onEnter !is null) {
5058 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
5059 						}
5060 						break;
5061 					case EventType.LeaveNotify:
5062 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
5063 						break;
5064 					case EventType.DestroyNotify:
5065 						active = false;
5066 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
5067 					break;
5068 					case EventType.ConfigureNotify:
5069 						auto event = e.xconfigure;
5070 						this.width = event.width;
5071 						this.height = event.height;
5072 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
5073 						redraw();
5074 					break;
5075 					default: return 1;
5076 				}
5077 				return 1;
5078 			};
5079 		}
5080 
5081 		/* private */ void hideBalloon() {
5082 			balloon.close();
5083 			version(with_timer)
5084 				timer.destroy();
5085 			balloon = null;
5086 			version(with_timer)
5087 				timer = null;
5088 		}
5089 
5090 		void redraw() {
5091 			if (!active) return;
5092 
5093 			auto display = XDisplayConnection.get;
5094 			GC gc;
5095 
5096 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
5097 
5098 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
5099 				Visual *visual;
5100 				XVisualInfo vis_info;
5101 				XSetWindowAttributes win_attr;
5102 				c_ulong win_mask;
5103 
5104 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
5105 					assert(0);
5106 					// return 1;
5107 				}
5108 
5109 				visual = vis_info.visual;
5110 
5111 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
5112 				win_attr.background_pixel = 0;
5113 				win_attr.border_pixel = 0;
5114 
5115 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
5116 
5117 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
5118 
5119 				return 0;
5120 			}
5121 
5122 			if(useAlpha)
5123 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
5124 			else
5125 				gc = DefaultGC(display, DefaultScreen(display));
5126 
5127 			XClearWindow(display, nativeHandle);
5128 
5129 			if(!useAlpha && img !is null)
5130 				XSetClipMask(display, gc, clippixmap);
5131 
5132 			/+
5133 			XSetForeground(display, gc,
5134 				cast(uint) 0 << 16 |
5135 				cast(uint) 0 << 8 |
5136 				cast(uint) 0);
5137 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
5138 			+/
5139 
5140 			if (img is null) {
5141 				XSetForeground(display, gc,
5142 					cast(uint) 0 << 16 |
5143 					cast(uint) 127 << 8 |
5144 					cast(uint) 0);
5145 				XFillArc(display, nativeHandle,
5146 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
5147 			} else {
5148 				int dx = 0;
5149 				int dy = 0;
5150 				if(width > img.width)
5151 					dx = (width - img.width) / 2;
5152 				if(height > img.height)
5153 					dy = (height - img.height) / 2;
5154 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
5155 				XSetClipOrigin(display, gc, dx, dy);
5156 
5157 				int max(int a, int b) {
5158 					if(a > b) return a; else return b;
5159 				}
5160 
5161 				if (img.usingXshm)
5162 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
5163 				else
5164 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
5165 			}
5166 			XSetClipMask(display, gc, None);
5167 			flushGui();
5168 		}
5169 
5170 		static Window getTrayOwner() {
5171 			auto display = XDisplayConnection.get;
5172 			auto i = cast(int) DefaultScreen(display);
5173 			if(i < 10 && i >= 0) {
5174 				static Atom atom;
5175 				if(atom == None)
5176 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
5177 				return XGetSelectionOwner(display, atom);
5178 			}
5179 			return None;
5180 		}
5181 
5182 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
5183 			auto to = getTrayOwner();
5184 			auto display = XDisplayConnection.get;
5185 			XEvent ev;
5186 			ev.xclient.type = EventType.ClientMessage;
5187 			ev.xclient.window = to;
5188 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
5189 			ev.xclient.format = 32;
5190 			ev.xclient.data.l[0] = CurrentTime;
5191 			ev.xclient.data.l[1] = message;
5192 			ev.xclient.data.l[2] = d1;
5193 			ev.xclient.data.l[3] = d2;
5194 			ev.xclient.data.l[4] = d3;
5195 
5196 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
5197 		}
5198 
5199 		private static NotificationAreaIcon[] activeIcons;
5200 
5201 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
5202 		private void newManager() {
5203 			close();
5204 			createXWin();
5205 
5206 			if(this.clippixmap)
5207 				XFreePixmap(XDisplayConnection.get, clippixmap);
5208 			if(this.originalMemoryImage)
5209 				this.icon = this.originalMemoryImage;
5210 			else if(this.img)
5211 				this.icon = this.img;
5212 		}
5213 
5214 		private bool useAlpha = false;
5215 
5216 		private void createXWin () {
5217 			// create window
5218 			auto display = XDisplayConnection.get;
5219 
5220 			// to check for MANAGER on root window to catch new/changed tray owners
5221 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
5222 			// so if a thing does appear, we can handle it
5223 			foreach(ai; activeIcons)
5224 				if(ai is this)
5225 					goto alreadythere;
5226 			activeIcons ~= this;
5227 			alreadythere:
5228 
5229 			// and check for an existing tray
5230 			auto trayOwner = getTrayOwner();
5231 			if(trayOwner == None)
5232 				return;
5233 				//throw new Exception("No notification area found");
5234 
5235 			Visual* v = cast(Visual*) CopyFromParent;
5236 
5237 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
5238 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
5239 			// a resize event later.
5240 			width = 22;
5241 			height = 22;
5242 
5243 			// if they system gave us a 32 bit visual we need to switch to it too
5244 			int depth = 24;
5245 
5246 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
5247 			if(visualProp !is null) {
5248 				c_ulong[] info = cast(c_ulong[]) visualProp;
5249 				if(info.length == 1) {
5250 					auto vid = info[0];
5251 					int returned;
5252 					XVisualInfo t;
5253 					t.visualid = vid;
5254 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
5255 					if(got !is null) {
5256 						if(returned == 1) {
5257 							v = got.visual;
5258 							depth = got.depth;
5259 							// writeln("using special visual ", got.depth);
5260 							// writeln(depth);
5261 						}
5262 						XFree(got);
5263 					}
5264 				}
5265 			}
5266 
5267 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
5268 			XSetWindowAttributes attr;
5269 			attr.background_pixel = 0;
5270 			attr.border_pixel = 0;
5271 			attr.override_redirect = 0;
5272 			if(v !is cast(Visual*) CopyFromParent) {
5273 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
5274 				CWFlags |= CWColormap;
5275 				if(depth == 32)
5276 					useAlpha = true;
5277 				else
5278 					goto plain;
5279 			} else {
5280 				plain:
5281 				attr.background_pixmap = 1 /* ParentRelative */;
5282 				CWFlags |= CWBackPixmap;
5283 			}
5284 
5285 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
5286 
5287 			assert(nativeWindow);
5288 
5289 			if(!useAlpha)
5290 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
5291 
5292 			nativeHandle = nativeWindow;
5293 
5294 			///+
5295 			arch_ulong[2] info;
5296 			info[0] = 0;
5297 			info[1] = 1;
5298 
5299 			string title = this.name is null ? "simpledisplay.d program" : this.name;
5300 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
5301 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
5302 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
5303 
5304 			XChangeProperty(
5305 				display,
5306 				nativeWindow,
5307 				GetAtom!("_XEMBED_INFO", true)(display),
5308 				GetAtom!("_XEMBED_INFO", true)(display),
5309 				32 /* bits */,
5310 				0 /*PropModeReplace*/,
5311 				info.ptr,
5312 				2);
5313 
5314 			import core.sys.posix.unistd;
5315 			arch_ulong pid = getpid();
5316 
5317 			XChangeProperty(
5318 				display,
5319 				nativeWindow,
5320 				GetAtom!("_NET_WM_PID", true)(display),
5321 				XA_CARDINAL,
5322 				32 /* bits */,
5323 				0 /*PropModeReplace*/,
5324 				&pid,
5325 				1);
5326 
5327 			updateNetWmIcon();
5328 
5329 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
5330 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
5331 				XClassHint klass;
5332 				XWMHints wh;
5333 				XSizeHints size;
5334 				klass.res_name = sdpyWindowClassStr;
5335 				klass.res_class = sdpyWindowClassStr;
5336 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
5337 			}
5338 
5339 				// believe it or not, THIS is what xfce needed for the 9999 issue
5340 				XSizeHints sh;
5341 				c_long spr;
5342 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
5343 				sh.flags |= PMaxSize | PMinSize;
5344 				// FIXME maybe nicer resizing
5345 				sh.min_width = 16;
5346 				sh.min_height = 16;
5347 				sh.max_width = 22;
5348 				sh.max_height = 22;
5349 				XSetWMNormalHints(display, nativeWindow, &sh);
5350 
5351 
5352 			//+/
5353 
5354 
5355 			XSelectInput(display, nativeWindow,
5356 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
5357 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5358 
5359 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5360 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5361 
5362 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5363 			active = true;
5364 		}
5365 
5366 		void updateNetWmIcon() {
5367 			if(img is null) return;
5368 			auto display = XDisplayConnection.get;
5369 			// FIXME: ensure this is correct
5370 			arch_ulong[] buffer;
5371 			auto imgMi = img.toTrueColorImage;
5372 			buffer ~= imgMi.width;
5373 			buffer ~= imgMi.height;
5374 			foreach(c; imgMi.imageData.colors) {
5375 				arch_ulong b;
5376 				b |= c.a << 24;
5377 				b |= c.r << 16;
5378 				b |= c.g << 8;
5379 				b |= c.b;
5380 				buffer ~= b;
5381 			}
5382 
5383 			XChangeProperty(
5384 				display,
5385 				nativeHandle,
5386 				GetAtom!"_NET_WM_ICON"(display),
5387 				GetAtom!"CARDINAL"(display),
5388 				32 /* bits */,
5389 				0 /*PropModeReplace*/,
5390 				buffer.ptr,
5391 				cast(int) buffer.length);
5392 		}
5393 
5394 
5395 
5396 		private SimpleWindow balloon;
5397 		version(with_timer)
5398 		private Timer timer;
5399 
5400 		private Window nativeHandle;
5401 		private Pixmap clippixmap = None;
5402 		private int width = 16;
5403 		private int height = 16;
5404 		private bool active = false;
5405 
5406 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5407 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5408 		void delegate () onLeave; /// X11 only.
5409 
5410 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5411 
5412 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5413 		void getWindowRect (out int x, out int y, out int width, out int height) {
5414 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5415 			Window dummyw;
5416 			auto dpy = XDisplayConnection.get;
5417 			//XWindowAttributes xwa;
5418 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5419 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5420 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5421 			width = this.width;
5422 			height = this.height;
5423 		}
5424 	}
5425 
5426 	/+
5427 		What I actually want from this:
5428 
5429 		* set / change: icon, tooltip
5430 		* handle: mouse click, right click
5431 		* show: notification bubble.
5432 	+/
5433 
5434 	version(Windows) {
5435 		WindowsIcon win32Icon;
5436 		HWND hwnd;
5437 
5438 		NOTIFYICONDATAW data;
5439 
5440 		NativeEventHandler getNativeEventHandler() {
5441 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5442 				if(msg == WM_USER) {
5443 					auto event = LOWORD(lParam);
5444 					auto iconId = HIWORD(lParam);
5445 					//auto x = GET_X_LPARAM(wParam);
5446 					//auto y = GET_Y_LPARAM(wParam);
5447 					switch(event) {
5448 						case WM_LBUTTONDOWN:
5449 							onClick()(MouseButton.left);
5450 						break;
5451 						case WM_RBUTTONDOWN:
5452 							onClick()(MouseButton.right);
5453 						break;
5454 						case WM_MBUTTONDOWN:
5455 							onClick()(MouseButton.middle);
5456 						break;
5457 						case WM_MOUSEMOVE:
5458 							// sent, we could use it.
5459 						break;
5460 						case WM_MOUSEWHEEL:
5461 							// NOT SENT
5462 						break;
5463 						//case NIN_KEYSELECT:
5464 						//case NIN_SELECT:
5465 						//break;
5466 						default: {}
5467 					}
5468 				}
5469 				return 0;
5470 			};
5471 		}
5472 
5473 		enum NIF_SHOWTIP = 0x00000080;
5474 
5475 		private static struct NOTIFYICONDATAW {
5476 			DWORD cbSize;
5477 			HWND  hWnd;
5478 			UINT  uID;
5479 			UINT  uFlags;
5480 			UINT  uCallbackMessage;
5481 			HICON hIcon;
5482 			WCHAR[128] szTip;
5483 			DWORD dwState;
5484 			DWORD dwStateMask;
5485 			WCHAR[256] szInfo;
5486 			union {
5487 				UINT uTimeout;
5488 				UINT uVersion;
5489 			}
5490 			WCHAR[64] szInfoTitle;
5491 			DWORD dwInfoFlags;
5492 			GUID  guidItem;
5493 			HICON hBalloonIcon;
5494 		}
5495 
5496 	}
5497 
5498 	/++
5499 		Note that on Windows, only left, right, and middle buttons are sent.
5500 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5501 		program is meant to be used on Windows too.
5502 	+/
5503 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5504 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5505 		// but on X, we need an Image, so its canonical ctor is there. They should
5506 		// forward to each other though.
5507 		version(X11) {
5508 			this.name = name;
5509 			this.onClick = onClick;
5510 			createXWin();
5511 			this.icon = icon;
5512 		} else version(Windows) {
5513 			this.onClick = onClick;
5514 			this.win32Icon = new WindowsIcon(icon);
5515 
5516 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5517 
5518 			static bool registered = false;
5519 			if(!registered) {
5520 				WNDCLASSEX wc;
5521 				wc.cbSize = wc.sizeof;
5522 				wc.hInstance = hInstance;
5523 				wc.lpfnWndProc = &WndProc;
5524 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5525 				if(!RegisterClassExW(&wc))
5526 					throw new WindowsApiException("RegisterClass", GetLastError());
5527 				registered = true;
5528 			}
5529 
5530 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5531 			if(hwnd is null)
5532 				throw new WindowsApiException("CreateWindow", GetLastError());
5533 
5534 			data.cbSize = data.sizeof;
5535 			data.hWnd = hwnd;
5536 			data.uID = cast(uint) cast(void*) this;
5537 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5538 				// NIF_INFO means show balloon
5539 			data.uCallbackMessage = WM_USER;
5540 			data.hIcon = this.win32Icon.hIcon;
5541 			data.szTip = ""; // FIXME
5542 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5543 			data.dwStateMask = NIS_HIDDEN; // windows vista
5544 
5545 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5546 
5547 
5548 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5549 
5550 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5551 		} else version(OSXCocoa) {
5552 			throw new NotYetImplementedException();
5553 		} else static assert(0);
5554 	}
5555 
5556 	/// ditto
5557 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5558 		version(X11) {
5559 			this.onClick = onClick;
5560 			this.name = name;
5561 			createXWin();
5562 			this.icon = icon;
5563 		} else version(Windows) {
5564 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5565 		} else version(OSXCocoa) {
5566 			throw new NotYetImplementedException();
5567 		} else static assert(0);
5568 	}
5569 
5570 	version(X11) {
5571 		/++
5572 			X-specific extension (for now at least)
5573 		+/
5574 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5575 			this.onClickEx = onClickEx;
5576 			createXWin();
5577 			if (icon !is null) this.icon = icon;
5578 		}
5579 
5580 		/// ditto
5581 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5582 			this.onClickEx = onClickEx;
5583 			createXWin();
5584 			this.icon = icon;
5585 		}
5586 	}
5587 
5588 	private void delegate (MouseButton button) onClick_;
5589 
5590 	///
5591 	@property final void delegate(MouseButton) onClick() {
5592 		if(onClick_ is null)
5593 			onClick_ = delegate void(MouseButton) {};
5594 		return onClick_;
5595 	}
5596 
5597 	/// ditto
5598 	@property final void onClick(void delegate(MouseButton) handler) {
5599 		// I made this a property setter so we can wrap smaller arg
5600 		// delegates and just forward all to onClickEx or something.
5601 		onClick_ = handler;
5602 	}
5603 
5604 
5605 	string name_;
5606 	@property void name(string n) {
5607 		name_ = n;
5608 	}
5609 
5610 	@property string name() {
5611 		return name_;
5612 	}
5613 
5614 	private MemoryImage originalMemoryImage;
5615 
5616 	///
5617 	@property void icon(MemoryImage i) {
5618 		version(X11) {
5619 			this.originalMemoryImage = i;
5620 			if (!active) return;
5621 			if (i !is null) {
5622 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5623 				if(!useAlpha)
5624 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5625 				// writeln("using pixmap ", clippixmap);
5626 				updateNetWmIcon();
5627 				redraw();
5628 			} else {
5629 				if (this.img !is null) {
5630 					this.img = null;
5631 					redraw();
5632 				}
5633 			}
5634 		} else version(Windows) {
5635 			this.win32Icon = new WindowsIcon(i);
5636 
5637 			data.uFlags = NIF_ICON;
5638 			data.hIcon = this.win32Icon.hIcon;
5639 
5640 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5641 		} else version(OSXCocoa) {
5642 			throw new NotYetImplementedException();
5643 		} else static assert(0);
5644 	}
5645 
5646 	/// ditto
5647 	@property void icon (Image i) {
5648 		version(X11) {
5649 			if (!active) return;
5650 			if (i !is img) {
5651 				originalMemoryImage = null;
5652 				img = i;
5653 				redraw();
5654 			}
5655 		} else version(Windows) {
5656 			this.icon(i is null ? null : i.toTrueColorImage());
5657 		} else version(OSXCocoa) {
5658 			throw new NotYetImplementedException();
5659 		} else static assert(0);
5660 	}
5661 
5662 	/++
5663 		Shows a balloon notification. You can only show one balloon at a time, if you call
5664 		it twice while one is already up, the first balloon will be replaced.
5665 
5666 
5667 		The user is free to block notifications and they will automatically disappear after
5668 		a timeout period.
5669 
5670 		Params:
5671 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5672 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5673 			icon = the icon to display with the notification. If null, it uses your existing icon.
5674 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5675 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5676 	+/
5677 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5678 		bool useCustom = true;
5679 		version(libnotify) {
5680 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5681 			try {
5682 				if(!active) return;
5683 
5684 				if(libnotify is null) {
5685 					libnotify = new C_DynamicLibrary("libnotify.so");
5686 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5687 				}
5688 
5689 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5690 
5691 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5692 
5693 				if(onclick) {
5694 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5695 					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);
5696 					libnotify_action_delegates_count++;
5697 				}
5698 
5699 				// FIXME icon
5700 
5701 				// set hint image-data
5702 				// set default action for onclick
5703 
5704 				void* error;
5705 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5706 
5707 				useCustom = false;
5708 			} catch(Exception e) {
5709 
5710 			}
5711 		}
5712 
5713 		version(X11) {
5714 		if(useCustom) {
5715 			if(!active) return;
5716 			if(balloon) {
5717 				hideBalloon();
5718 			}
5719 			// I know there are two specs for this, but one is never
5720 			// implemented by any window manager I have ever seen, and
5721 			// the other is a bloated mess and too complicated for simpledisplay...
5722 			// so doing my own little window instead.
5723 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5724 
5725 			int x, y, width, height;
5726 			getWindowRect(x, y, width, height);
5727 
5728 			int bx = x - balloon.width;
5729 			int by = y - balloon.height;
5730 			if(bx < 0)
5731 				bx = x + width + balloon.width;
5732 			if(by < 0)
5733 				by = y + height;
5734 
5735 			// just in case, make sure it is actually on scren
5736 			if(bx < 0)
5737 				bx = 0;
5738 			if(by < 0)
5739 				by = 0;
5740 
5741 			balloon.move(bx, by);
5742 			auto painter = balloon.draw();
5743 			painter.fillColor = Color(220, 220, 220);
5744 			painter.outlineColor = Color.black;
5745 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5746 			auto iconWidth = icon is null ? 0 : icon.width;
5747 			if(icon)
5748 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5749 			iconWidth += 6; // margin around the icon
5750 
5751 			// draw a close button
5752 			painter.outlineColor = Color(44, 44, 44);
5753 			painter.fillColor = Color(255, 255, 255);
5754 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5755 			painter.pen = Pen(Color.black, 3);
5756 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5757 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5758 			painter.pen = Pen(Color.black, 1);
5759 			painter.fillColor = Color(220, 220, 220);
5760 
5761 			// Draw the title and message
5762 			painter.drawText(Point(4 + iconWidth, 4), title);
5763 			painter.drawLine(
5764 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5765 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5766 			);
5767 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5768 
5769 			balloon.setEventHandlers(
5770 				(MouseEvent ev) {
5771 					if(ev.type == MouseEventType.buttonPressed) {
5772 						if(ev.x > balloon.width - 16 && ev.y < 16)
5773 							hideBalloon();
5774 						else if(onclick)
5775 							onclick();
5776 					}
5777 				}
5778 			);
5779 			balloon.show();
5780 
5781 			version(with_timer)
5782 			timer = new Timer(timeout, &hideBalloon);
5783 			else {} // FIXME
5784 		}
5785 		} else version(Windows) {
5786 			enum NIF_INFO = 0x00000010;
5787 
5788 			data.uFlags = NIF_INFO;
5789 
5790 			// FIXME: go back to the last valid unicode code point
5791 			if(title.length > 40)
5792 				title = title[0 .. 40];
5793 			if(message.length > 220)
5794 				message = message[0 .. 220];
5795 
5796 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5797 			enum NIIF_LARGE_ICON  = 0x00000020;
5798 			enum NIIF_NOSOUND = 0x00000010;
5799 			enum NIIF_USER = 0x00000004;
5800 			enum NIIF_ERROR = 0x00000003;
5801 			enum NIIF_WARNING = 0x00000002;
5802 			enum NIIF_INFO = 0x00000001;
5803 			enum NIIF_NONE = 0;
5804 
5805 			WCharzBuffer t = WCharzBuffer(title);
5806 			WCharzBuffer m = WCharzBuffer(message);
5807 
5808 			t.copyInto(data.szInfoTitle);
5809 			m.copyInto(data.szInfo);
5810 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5811 
5812 			if(icon !is null) {
5813 				auto i = new WindowsIcon(icon);
5814 				data.hBalloonIcon = i.hIcon;
5815 				data.dwInfoFlags |= NIIF_USER;
5816 			}
5817 
5818 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5819 		} else version(OSXCocoa) {
5820 			throw new NotYetImplementedException();
5821 		} else static assert(0);
5822 	}
5823 
5824 	///
5825 	//version(Windows)
5826 	void show() {
5827 		version(X11) {
5828 			if(!hidden)
5829 				return;
5830 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5831 			hidden = false;
5832 		} else version(Windows) {
5833 			data.uFlags = NIF_STATE;
5834 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5835 			data.dwStateMask = NIS_HIDDEN; // windows vista
5836 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5837 		} else version(OSXCocoa) {
5838 			throw new NotYetImplementedException();
5839 		} else static assert(0);
5840 	}
5841 
5842 	version(X11)
5843 		bool hidden = false;
5844 
5845 	///
5846 	//version(Windows)
5847 	void hide() {
5848 		version(X11) {
5849 			if(hidden)
5850 				return;
5851 			hidden = true;
5852 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5853 		} else version(Windows) {
5854 			data.uFlags = NIF_STATE;
5855 			data.dwState = NIS_HIDDEN; // windows vista
5856 			data.dwStateMask = NIS_HIDDEN; // windows vista
5857 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5858 		} else version(OSXCocoa) {
5859 			throw new NotYetImplementedException();
5860 		} else static assert(0);
5861 	}
5862 
5863 	///
5864 	void close () {
5865 		version(X11) {
5866 			if (active) {
5867 				active = false; // event handler will set this too, but meh
5868 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5869 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5870 				flushGui();
5871 			}
5872 		} else version(Windows) {
5873 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5874 		} else version(OSXCocoa) {
5875 			throw new NotYetImplementedException();
5876 		} else static assert(0);
5877 	}
5878 
5879 	~this() {
5880 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5881 		version(X11)
5882 			if(clippixmap != None)
5883 				XFreePixmap(XDisplayConnection.get, clippixmap);
5884 		close();
5885 	}
5886 }
5887 
5888 version(X11)
5889 /// Call `XFreePixmap` on the return value.
5890 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5891 	char[] data = new char[](i.width * i.height / 8 + 2);
5892 	data[] = 0;
5893 
5894 	int bitOffset = 0;
5895 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5896 		ubyte v = c.a > 128 ? 1 : 0;
5897 		data[bitOffset / 8] |= v << (bitOffset%8);
5898 		bitOffset++;
5899 	}
5900 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5901 	return handle;
5902 }
5903 
5904 
5905 // basic functions to make timers
5906 /**
5907 	A timer that will trigger your function on a given interval.
5908 
5909 
5910 	You create a timer with an interval and a callback. It will continue
5911 	to fire on the interval until it is destroyed.
5912 
5913 	There are currently no one-off timers (instead, just create one and
5914 	destroy it when it is triggered) nor are there pause/resume functions -
5915 	the timer must again be destroyed and recreated if you want to pause it.
5916 
5917 	---
5918 	auto timer = new Timer(50, { it happened!; });
5919 	timer.destroy();
5920 	---
5921 
5922 	Timers can only be expected to fire when the event loop is running and only
5923 	once per iteration through the event loop.
5924 
5925 	History:
5926 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5927 		slow could lock up the event loop. It now guarantees other things will
5928 		get a chance to run between timer calls, even if that means not keeping up
5929 		with the requested interval.
5930 */
5931 version(with_timer) {
5932 version(use_arsd_core)
5933 	alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api
5934 else
5935 class Timer {
5936 // FIXME: needs pause and unpause
5937 	// FIXME: I might add overloads for ones that take a count of
5938 	// how many elapsed since last time (on Windows, it will divide
5939 	// the ticks thing given, on Linux it is just available) and
5940 	// maybe one that takes an instance of the Timer itself too
5941 	/// Create a timer with a callback when it triggers.
5942 	this(int intervalInMilliseconds, void delegate() onPulse) @trusted {
5943 		assert(onPulse !is null);
5944 
5945 		this.intervalInMilliseconds = intervalInMilliseconds;
5946 		this.onPulse = onPulse;
5947 
5948 		version(Windows) {
5949 			/*
5950 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5951 			if(handle == 0)
5952 				throw new WindowsApiException("SetTimer", GetLastError());
5953 			*/
5954 
5955 			// thanks to Archival 998 for the WaitableTimer blocks
5956 			handle = CreateWaitableTimer(null, false, null);
5957 			long initialTime = -intervalInMilliseconds;
5958 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5959 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5960 
5961 			mapping[handle] = this;
5962 
5963 		} else version(linux) {
5964 			static import ep = core.sys.linux.epoll;
5965 
5966 			import core.sys.linux.timerfd;
5967 
5968 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5969 			if(fd == -1)
5970 				throw new Exception("timer create failed");
5971 
5972 			mapping[fd] = this;
5973 
5974 			itimerspec value = makeItimerspec(intervalInMilliseconds);
5975 
5976 			if(timerfd_settime(fd, 0, &value, null) == -1)
5977 				throw new Exception("couldn't make pulse timer");
5978 
5979 			version(with_eventloop) {
5980 				import arsd.eventloop;
5981 				addFileEventListeners(fd, &trigger, null, null);
5982 			} else {
5983 				prepareEventLoop();
5984 
5985 				ep.epoll_event ev = void;
5986 				ev.events = ep.EPOLLIN;
5987 				ev.data.fd = fd;
5988 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5989 			}
5990 		} else featureNotImplemented();
5991 	}
5992 
5993 	private int intervalInMilliseconds;
5994 
5995 	// just cuz I sometimes call it this.
5996 	alias dispose = destroy;
5997 
5998 	/// Stop and destroy the timer object.
5999 	void destroy() {
6000 		version(Windows) {
6001 			staticDestroy(handle);
6002 			handle = null;
6003 		} else version(linux) {
6004 			staticDestroy(fd);
6005 			fd = -1;
6006 		} else featureNotImplemented();
6007 	}
6008 
6009 	version(Windows)
6010 	static void staticDestroy(HANDLE handle) {
6011 		if(handle) {
6012 			// KillTimer(null, handle);
6013 			CancelWaitableTimer(cast(void*)handle);
6014 			mapping.remove(handle);
6015 			CloseHandle(handle);
6016 		}
6017 	}
6018 	else version(linux)
6019 	static void staticDestroy(int fd) @system {
6020 		if(fd != -1) {
6021 			import unix = core.sys.posix.unistd;
6022 			static import ep = core.sys.linux.epoll;
6023 
6024 			version(with_eventloop) {
6025 				import arsd.eventloop;
6026 				removeFileEventListeners(fd);
6027 			} else {
6028 				ep.epoll_event ev = void;
6029 				ev.events = ep.EPOLLIN;
6030 				ev.data.fd = fd;
6031 
6032 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6033 			}
6034 			unix.close(fd);
6035 			mapping.remove(fd);
6036 		}
6037 	}
6038 
6039 	~this() {
6040 		version(Windows) { if(handle)
6041 			cleanupQueue.queue!staticDestroy(handle);
6042 		} else version(linux) { if(fd != -1)
6043 			cleanupQueue.queue!staticDestroy(fd);
6044 		}
6045 	}
6046 
6047 	void changeTime(int intervalInMilliseconds)
6048 	{
6049 		this.intervalInMilliseconds = intervalInMilliseconds;
6050 		version(Windows)
6051 		{
6052 			if(handle)
6053 			{
6054 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6055 				long initialTime = -intervalInMilliseconds;
6056 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6057 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
6058 			}
6059 		} else version(linux) {
6060 			import core.sys.linux.timerfd;
6061 
6062 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6063 			if(timerfd_settime(fd, 0, &value, null) == -1) {
6064 				throw new Exception("couldn't change pulse timer");
6065 			}
6066 		} else {
6067 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
6068 		}
6069 	}
6070 
6071 
6072 	private:
6073 
6074 	void delegate() onPulse;
6075 
6076 	int lastEventLoopRoundTriggered;
6077 
6078 	version(linux) {
6079 		static auto makeItimerspec(int intervalInMilliseconds) {
6080 			import core.sys.linux.timerfd;
6081 
6082 			itimerspec value;
6083 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6084 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6085 
6086 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6087 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6088 
6089 			return value;
6090 		}
6091 	}
6092 
6093 	void trigger() {
6094 		version(linux) {
6095 			import unix = core.sys.posix.unistd;
6096 			long val;
6097 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
6098 		} else version(Windows) {
6099 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
6100 				return; // never try to actually run faster than the event loop
6101 			lastEventLoopRoundTriggered = eventLoopRound;
6102 		} else featureNotImplemented();
6103 
6104 		onPulse();
6105 	}
6106 
6107 	version(Windows)
6108 	void rearm() {
6109 
6110 	}
6111 
6112 	version(Windows)
6113 		extern(Windows)
6114 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
6115 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
6116 			if(Timer* t = timer in mapping) {
6117 				try
6118 				(*t).trigger();
6119 				catch(Exception e) { sdpy_abort(e); assert(0); }
6120 			}
6121 		}
6122 
6123 	version(Windows) {
6124 		//UINT_PTR handle;
6125 		//static Timer[UINT_PTR] mapping;
6126 		HANDLE handle;
6127 		__gshared Timer[HANDLE] mapping;
6128 	} else version(linux) {
6129 		int fd = -1;
6130 		__gshared Timer[int] mapping;
6131 	} else version(OSXCocoa) {
6132 	} else static assert(0, "timer not supported");
6133 }
6134 }
6135 
6136 version(Windows)
6137 private int eventLoopRound;
6138 
6139 version(Windows)
6140 /// 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
6141 class WindowsHandleReader {
6142 	///
6143 	this(void delegate() onReady, HANDLE handle) {
6144 		this.onReady = onReady;
6145 		this.handle = handle;
6146 
6147 		mapping[handle] = this;
6148 
6149 		enable();
6150 	}
6151 
6152 	version(use_arsd_core)
6153 		ICoreEventLoop.UnregisterToken unregisterToken;
6154 
6155 	///
6156 	void enable() {
6157 		version(use_arsd_core) {
6158 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready));
6159 		} else {
6160 			auto el = EventLoop.get().impl;
6161 			el.handles ~= handle;
6162 		}
6163 	}
6164 
6165 	///
6166 	void disable() {
6167 		version(use_arsd_core) {
6168 			unregisterToken.unregister();
6169 		} else {
6170 			auto el = EventLoop.get().impl;
6171 			for(int i = 0; i < el.handles.length; i++) {
6172 				if(el.handles[i] is handle) {
6173 					el.handles[i] = el.handles[$-1];
6174 					el.handles = el.handles[0 .. $-1];
6175 					return;
6176 				}
6177 			}
6178 		}
6179 	}
6180 
6181 	void dispose() {
6182 		disable();
6183 		if(handle)
6184 			mapping.remove(handle);
6185 		handle = null;
6186 	}
6187 
6188 	void ready() {
6189 		if(onReady)
6190 			onReady();
6191 	}
6192 
6193 	HANDLE handle;
6194 	void delegate() onReady;
6195 
6196 	__gshared WindowsHandleReader[HANDLE] mapping;
6197 }
6198 
6199 version(Posix)
6200 /// Lets you add files to the event loop for reading. Use at your own risk.
6201 class PosixFdReader {
6202 	///
6203 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6204 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
6205 	}
6206 
6207 	///
6208 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6209 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
6210 	}
6211 
6212 	///
6213 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6214 		this.onReady = onReady;
6215 		this.fd = fd;
6216 		this.captureWrites = captureWrites;
6217 		this.captureReads = captureReads;
6218 
6219 		mapping[fd] = this;
6220 
6221 		version(with_eventloop) {
6222 			import arsd.eventloop;
6223 			addFileEventListeners(fd, &readyel);
6224 		} else {
6225 			enable();
6226 		}
6227 	}
6228 
6229 	bool captureReads;
6230 	bool captureWrites;
6231 
6232 	version(use_arsd_core) {
6233 		import arsd.core;
6234 		ICoreEventLoop.UnregisterToken unregisterToken;
6235 	}
6236 
6237 	version(with_eventloop) {} else
6238 	///
6239 	void enable() @system {
6240 		enabled = true;
6241 
6242 		version(use_arsd_core) {
6243 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper(
6244 				() { onReady(fd, true, false); }
6245 			));
6246 			// FIXME: what if it is writeable?
6247 
6248 		} else version(linux) {
6249 			prepareEventLoop();
6250 			static import ep = core.sys.linux.epoll;
6251 			ep.epoll_event ev = void;
6252 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6253 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
6254 			ev.data.fd = fd;
6255 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6256 		} else {
6257 
6258 		}
6259 	}
6260 
6261 	version(with_eventloop) {} else
6262 	///
6263 	void disable() @system {
6264 		enabled = false;
6265 
6266 		version(use_arsd_core) {
6267 			unregisterToken.unregister();
6268 		} else
6269 		version(linux) {
6270 			prepareEventLoop();
6271 			static import ep = core.sys.linux.epoll;
6272 			ep.epoll_event ev = void;
6273 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6274 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
6275 			ev.data.fd = fd;
6276 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6277 		}
6278 	}
6279 
6280 	version(with_eventloop) {} else
6281 	///
6282 	void dispose() {
6283 		if(enabled)
6284 			disable();
6285 		if(fd != -1)
6286 			mapping.remove(fd);
6287 		fd = -1;
6288 	}
6289 
6290 	void delegate(int, bool, bool) onReady;
6291 
6292 	version(with_eventloop)
6293 	void readyel() {
6294 		onReady(fd, true, true);
6295 	}
6296 
6297 	void ready(uint flags) {
6298 		version(linux) {
6299 			static import ep = core.sys.linux.epoll;
6300 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
6301 		} else {
6302 			import core.sys.posix.poll;
6303 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
6304 		}
6305 	}
6306 
6307 	void hup(uint flags) {
6308 		if(onHup)
6309 			onHup();
6310 	}
6311 
6312 	void delegate() onHup;
6313 
6314 	int fd = -1;
6315 	private bool enabled;
6316 	__gshared PosixFdReader[int] mapping;
6317 }
6318 
6319 // basic functions to access the clipboard
6320 /+
6321 
6322 
6323 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
6324 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
6325 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6326 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
6327 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
6328 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6329 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
6330 
6331 +/
6332 
6333 /++
6334 	this does a delegate because it is actually an async call on X...
6335 	the receiver may never be called if the clipboard is empty or unavailable
6336 	gets plain text from the clipboard.
6337 +/
6338 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system {
6339 	version(Windows) {
6340 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6341 		if(OpenClipboard(hwndOwner) == 0)
6342 			throw new WindowsApiException("OpenClipboard", GetLastError());
6343 		scope(exit)
6344 			CloseClipboard();
6345 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
6346 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
6347 
6348 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
6349 				scope(exit)
6350 					GlobalUnlock(dataHandle);
6351 
6352 				// FIXME: CR/LF conversions
6353 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
6354 				int len = 0;
6355 				auto d = data;
6356 				while(*d) {
6357 					d++;
6358 					len++;
6359 				}
6360 				string s;
6361 				s.reserve(len);
6362 				foreach(dchar ch; data[0 .. len]) {
6363 					s ~= ch;
6364 				}
6365 				receiver(s);
6366 			}
6367 		}
6368 	} else version(X11) {
6369 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6370 	} else version(OSXCocoa) {
6371 		throw new NotYetImplementedException();
6372 	} else static assert(0);
6373 }
6374 
6375 // FIXME: a clipboard listener might be cool btw
6376 
6377 /++
6378 	this does a delegate because it is actually an async call on X...
6379 	the receiver may never be called if the clipboard is empty or unavailable
6380 	gets image from the clipboard.
6381 
6382 	templated because it introduces an optional dependency on arsd.bmp
6383 +/
6384 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6385 	version(Windows) {
6386 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6387 		if(OpenClipboard(hwndOwner) == 0)
6388 			throw new WindowsApiException("OpenClipboard", GetLastError());
6389 		scope(exit)
6390 			CloseClipboard();
6391 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6392 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6393 				scope(exit)
6394 					GlobalUnlock(dataHandle);
6395 
6396 				auto len = GlobalSize(dataHandle);
6397 
6398 				import arsd.bmp;
6399 				auto img = readBmp(data[0 .. len], false);
6400 				receiver(img);
6401 			}
6402 		}
6403 	} else version(X11) {
6404 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6405 	} else version(OSXCocoa) {
6406 		throw new NotYetImplementedException();
6407 	} else static assert(0);
6408 }
6409 
6410 /// Copies some text to the clipboard.
6411 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6412 	assert(clipboardOwner !is null);
6413 	version(Windows) {
6414 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6415 			throw new WindowsApiException("OpenClipboard", GetLastError());
6416 		scope(exit)
6417 			CloseClipboard();
6418 		EmptyClipboard();
6419 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6420 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6421 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6422 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6423 			auto slice = data[0 .. sz];
6424 			scope(failure)
6425 				GlobalUnlock(handle);
6426 
6427 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6428 
6429 			GlobalUnlock(handle);
6430 			SetClipboardData(CF_UNICODETEXT, handle);
6431 		}
6432 	} else version(X11) {
6433 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6434 	} else version(OSXCocoa) {
6435 		throw new NotYetImplementedException();
6436 	} else static assert(0);
6437 }
6438 
6439 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6440 	assert(clipboardOwner !is null);
6441 	version(Windows) {
6442 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6443 			throw new WindowsApiException("OpenClipboard", GetLastError());
6444 		scope(exit)
6445 			CloseClipboard();
6446 		EmptyClipboard();
6447 
6448 
6449 		import arsd.bmp;
6450 		ubyte[] mdata;
6451 		mdata.reserve(img.width * img.height);
6452 		void sink(ubyte b) {
6453 			mdata ~= b;
6454 		}
6455 		writeBmpIndirect(img, &sink, false);
6456 
6457 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6458 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6459 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6460 			auto slice = data[0 .. mdata.length];
6461 			scope(failure)
6462 				GlobalUnlock(handle);
6463 
6464 			slice[] = mdata[];
6465 
6466 			GlobalUnlock(handle);
6467 			SetClipboardData(CF_DIB, handle);
6468 		}
6469 	} else version(X11) {
6470 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6471 			mixin X11SetSelectionHandler_Basics;
6472 			private const(ubyte)[] mdata;
6473 			private const(ubyte)[] mdata_original;
6474 			this(MemoryImage img) {
6475 				import arsd.bmp;
6476 
6477 				mdata.reserve(img.width * img.height);
6478 				void sink(ubyte b) {
6479 					mdata ~= b;
6480 				}
6481 				writeBmpIndirect(img, &sink, true);
6482 
6483 				mdata_original = mdata;
6484 			}
6485 
6486 			Atom[] availableFormats() {
6487 				auto display = XDisplayConnection.get;
6488 				return [
6489 					GetAtom!"image/bmp"(display),
6490 					GetAtom!"TARGETS"(display)
6491 				];
6492 			}
6493 
6494 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6495 				if(mdata.length < data.length) {
6496 					data[0 .. mdata.length] = mdata[];
6497 					auto ret = data[0 .. mdata.length];
6498 					mdata = mdata[$..$];
6499 					return ret;
6500 				} else {
6501 					data[] = mdata[0 .. data.length];
6502 					mdata = mdata[data.length .. $];
6503 					return data[];
6504 				}
6505 			}
6506 
6507 			void done() {
6508 				mdata = mdata_original;
6509 			}
6510 		}
6511 
6512 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6513 	} else version(OSXCocoa) {
6514 		throw new NotYetImplementedException();
6515 	} else static assert(0);
6516 }
6517 
6518 
6519 version(X11) {
6520 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6521 
6522 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6523 
6524 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6525 	/// Platform-specific for X11.
6526 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6527 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6528 		__gshared static Atom a;
6529 		if(!a) {
6530 			a = XInternAtom(display, name, !create);
6531 			// FIXME: might need to synchronize this and attach it to the actual object
6532 			interredAtoms ~= &a;
6533 		}
6534 		if(a == None)
6535 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6536 		return a;
6537 	}
6538 
6539 	/// Platform-specific for X11 - gets atom names as a string.
6540 	string getAtomName(Atom atom, Display* display) {
6541 		auto got = XGetAtomName(display, atom);
6542 		scope(exit) XFree(got);
6543 		import core.stdc.string;
6544 		string s = got[0 .. strlen(got)].idup;
6545 		return s;
6546 	}
6547 
6548 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6549 	void setPrimarySelection(SimpleWindow window, string text) {
6550 		setX11Selection!"PRIMARY"(window, text);
6551 	}
6552 
6553 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6554 	void setSecondarySelection(SimpleWindow window, string text) {
6555 		setX11Selection!"SECONDARY"(window, text);
6556 	}
6557 
6558 	interface X11SetSelectionHandler {
6559 		// should include TARGETS right now
6560 		Atom[] availableFormats();
6561 		// Return the slice of data you filled, empty slice if done.
6562 		// this is to support the incremental thing
6563 		ubyte[] getData(Atom format, return scope ubyte[] data);
6564 
6565 		void done();
6566 
6567 		void handleRequest(XEvent);
6568 
6569 		bool matchesIncr(Window, Atom);
6570 		void sendMoreIncr(XPropertyEvent*);
6571 	}
6572 
6573 	mixin template X11SetSelectionHandler_Basics() {
6574 		Window incrWindow;
6575 		Atom incrAtom;
6576 		Atom selectionAtom;
6577 		Atom formatAtom;
6578 		ubyte[] toSend;
6579 		bool matchesIncr(Window w, Atom a) {
6580 			return incrAtom && incrAtom == a && w == incrWindow;
6581 		}
6582 		void sendMoreIncr(XPropertyEvent* event) {
6583 			auto display = XDisplayConnection.get;
6584 
6585 			XChangeProperty (display,
6586 				incrWindow,
6587 				incrAtom,
6588 				formatAtom,
6589 				8 /* bits */, PropModeReplace,
6590 				toSend.ptr, cast(int) toSend.length);
6591 
6592 			if(toSend.length != 0) {
6593 				toSend = this.getData(formatAtom, toSend[]);
6594 			} else {
6595 				this.done();
6596 				incrWindow = None;
6597 				incrAtom = None;
6598 				selectionAtom = None;
6599 				formatAtom = None;
6600 				toSend = null;
6601 			}
6602 		}
6603 		void handleRequest(XEvent ev) {
6604 
6605 			auto display = XDisplayConnection.get;
6606 
6607 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6608 			XSelectionEvent selectionEvent;
6609 			selectionEvent.type = EventType.SelectionNotify;
6610 			selectionEvent.display = event.display;
6611 			selectionEvent.requestor = event.requestor;
6612 			selectionEvent.selection = event.selection;
6613 			selectionEvent.time = event.time;
6614 			selectionEvent.target = event.target;
6615 
6616 			bool supportedType() {
6617 				foreach(t; this.availableFormats())
6618 					if(t == event.target)
6619 						return true;
6620 				return false;
6621 			}
6622 
6623 			if(event.property == None) {
6624 				selectionEvent.property = event.target;
6625 
6626 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6627 				XFlush(display);
6628 			} if(event.target == GetAtom!"TARGETS"(display)) {
6629 				/* respond with the supported types */
6630 				auto tlist = this.availableFormats();
6631 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6632 				selectionEvent.property = event.property;
6633 
6634 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6635 				XFlush(display);
6636 			} else if(supportedType()) {
6637 				auto buffer = new ubyte[](1024 * 64);
6638 				auto toSend = this.getData(event.target, buffer[]);
6639 
6640 				if(toSend.length < 32 * 1024) {
6641 					// small enough to send directly...
6642 					selectionEvent.property = event.property;
6643 					XChangeProperty (display,
6644 						selectionEvent.requestor,
6645 						selectionEvent.property,
6646 						event.target,
6647 						8 /* bits */, 0 /* PropModeReplace */,
6648 						toSend.ptr, cast(int) toSend.length);
6649 
6650 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6651 					XFlush(display);
6652 				} else {
6653 					// large, let's send incrementally
6654 					arch_ulong l = toSend.length;
6655 
6656 					// if I wanted other events from this window don't want to clear that out....
6657 					XWindowAttributes xwa;
6658 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6659 
6660 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6661 
6662 					incrWindow = event.requestor;
6663 					incrAtom = event.property;
6664 					formatAtom = event.target;
6665 					selectionAtom = event.selection;
6666 					this.toSend = toSend;
6667 
6668 					selectionEvent.property = event.property;
6669 					XChangeProperty (display,
6670 						selectionEvent.requestor,
6671 						selectionEvent.property,
6672 						GetAtom!"INCR"(display),
6673 						32 /* bits */, PropModeReplace,
6674 						&l, 1);
6675 
6676 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6677 					XFlush(display);
6678 				}
6679 				//if(after)
6680 					//after();
6681 			} else {
6682 				debug(sdpy_clip) {
6683 					writeln("Unsupported data ", getAtomName(event.target, display));
6684 				}
6685 				selectionEvent.property = None; // I don't know how to handle this type...
6686 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6687 				XFlush(display);
6688 			}
6689 		}
6690 	}
6691 
6692 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6693 		mixin X11SetSelectionHandler_Basics;
6694 		private const(ubyte)[] text;
6695 		private const(ubyte)[] text_original;
6696 		this(string text) {
6697 			this.text = cast(const ubyte[]) text;
6698 			this.text_original = this.text;
6699 		}
6700 		Atom[] availableFormats() {
6701 			auto display = XDisplayConnection.get;
6702 			return [
6703 				GetAtom!"UTF8_STRING"(display),
6704 				GetAtom!"text/plain"(display),
6705 				XA_STRING,
6706 				GetAtom!"TARGETS"(display)
6707 			];
6708 		}
6709 
6710 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6711 			if(text.length < data.length) {
6712 				data[0 .. text.length] = text[];
6713 				return data[0 .. text.length];
6714 			} else {
6715 				data[] = text[0 .. data.length];
6716 				text = text[data.length .. $];
6717 				return data[];
6718 			}
6719 		}
6720 
6721 		void done() {
6722 			text = text_original;
6723 		}
6724 	}
6725 
6726 	/// 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?!)
6727 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6728 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6729 	}
6730 
6731 	private __gshared bool mightShortCircuitClipboard;
6732 
6733 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6734 		assert(window !is null);
6735 
6736 		auto display = XDisplayConnection.get();
6737 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6738 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6739 		else Atom a = GetAtom!atomName(display);
6740 
6741 		if(mightShortCircuitClipboard)
6742 		if(auto ptr = a in window.impl.setSelectionHandlers) {
6743 			// we already have it, don't even need to inform the X server
6744 			// sdpyPrintDebugString("short circuit in set");
6745 			*ptr = data;
6746 			return;
6747 		}
6748 
6749 		// we don't have it, tell X we want it
6750 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6751 		window.impl.setSelectionHandlers[a] = data;
6752 		mightShortCircuitClipboard = true;
6753 	}
6754 
6755 	/+
6756 	/++
6757 		History:
6758 			Added September 28, 2024
6759 	+/
6760 	bool hasX11Selection(string atomName)(SimpleWindow window) {
6761 		auto display = XDisplayConnection.get();
6762 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6763 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6764 		else Atom a = GetAtom!atomName(display);
6765 
6766 		if(a in window.impl.setSelectionHandlers)
6767 			return true;
6768 		else
6769 			return false;
6770 	}
6771 	+/
6772 
6773 	///
6774 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6775 		getX11Selection!"PRIMARY"(window, handler);
6776 	}
6777 
6778 	// added July 28, 2020
6779 	// undocumented as experimental tho
6780 	interface X11GetSelectionHandler {
6781 		void handleData(Atom target, in ubyte[] data);
6782 		Atom findBestFormat(Atom[] answer);
6783 
6784 		void prepareIncremental(Window, Atom);
6785 		bool matchesIncr(Window, Atom);
6786 		void handleIncrData(Atom, in ubyte[] data);
6787 	}
6788 
6789 	mixin template X11GetSelectionHandler_Basics() {
6790 		Window incrWindow;
6791 		Atom incrAtom;
6792 
6793 		void prepareIncremental(Window w, Atom a) {
6794 			incrWindow = w;
6795 			incrAtom = a;
6796 		}
6797 		bool matchesIncr(Window w, Atom a) {
6798 			return incrWindow == w && incrAtom == a;
6799 		}
6800 
6801 		Atom incrFormatAtom;
6802 		ubyte[] incrData;
6803 		void handleIncrData(Atom format, in ubyte[] data) {
6804 			incrFormatAtom = format;
6805 
6806 			if(data.length)
6807 				incrData ~= data;
6808 			else
6809 				handleData(incrFormatAtom, incrData);
6810 
6811 		}
6812 	}
6813 
6814 	static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6815 		this(void delegate(in char[]) handler) {
6816 			this.handler = handler;
6817 		}
6818 
6819 		mixin X11GetSelectionHandler_Basics;
6820 
6821 		void delegate(in char[]) handler;
6822 
6823 		void handleData(Atom target, in ubyte[] data) {
6824 			// import std.stdio; writeln(target, " ", data);
6825 			if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6826 				handler(cast(const char[]) data);
6827 			else if(target == None && data is null)
6828 				handler(null); // no suitable selection exists
6829 		}
6830 
6831 		Atom findBestFormat(Atom[] answer) {
6832 			Atom best = None;
6833 			foreach(option; answer) {
6834 				if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6835 					best = option;
6836 					break;
6837 				} else if(option == XA_STRING) {
6838 					best = option;
6839 				} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6840 					best = option;
6841 				}
6842 			}
6843 			return best;
6844 		}
6845 	}
6846 
6847 	///
6848 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6849 		assert(window !is null);
6850 
6851 		auto display = XDisplayConnection.get();
6852 
6853 		static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY;
6854 		else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY;
6855 		else Atom atom = GetAtom!atomName(display);
6856 
6857 		if(mightShortCircuitClipboard)
6858 		if(auto ptr = atom in window.impl.setSelectionHandlers) {
6859 			if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) {
6860 				// we already have it! short circuit everything
6861 
6862 				// sdpyPrintDebugString("short circuit in get");
6863 				handler(cast(char[]) txt.text_original);
6864 				return;
6865 			}
6866 		}
6867 
6868 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6869 
6870 		auto target = GetAtom!"TARGETS"(display);
6871 
6872 		// SDD_DATA is "simpledisplay.d data"
6873 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6874 	}
6875 
6876 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6877 	/// only supports bmps. using this function will import arsd.bmp.
6878 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6879 		assert(window !is null);
6880 
6881 		auto display = XDisplayConnection.get();
6882 		auto atom = GetAtom!atomName(display);
6883 
6884 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6885 			this(void delegate(MemoryImage) handler) {
6886 				this.handler = handler;
6887 			}
6888 
6889 			mixin X11GetSelectionHandler_Basics;
6890 
6891 			void delegate(MemoryImage) handler;
6892 
6893 			void handleData(Atom target, in ubyte[] data) {
6894 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6895 					import arsd.bmp;
6896 					handler(readBmp(data));
6897 				}
6898 			}
6899 
6900 			Atom findBestFormat(Atom[] answer) {
6901 				Atom best = None;
6902 				foreach(option; answer) {
6903 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6904 						best = option;
6905 					}
6906 				}
6907 				return best;
6908 			}
6909 
6910 		}
6911 
6912 
6913 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6914 
6915 		auto target = GetAtom!"TARGETS"(display);
6916 
6917 		// SDD_DATA is "simpledisplay.d data"
6918 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6919 	}
6920 
6921 
6922 	///
6923 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6924 		Atom actualType;
6925 		int actualFormat;
6926 		arch_ulong actualItems;
6927 		arch_ulong bytesRemaining;
6928 		void* data;
6929 
6930 		auto display = XDisplayConnection.get();
6931 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6932 			if(actualFormat == 0)
6933 				return null;
6934 			else {
6935 				int byteLength;
6936 				if(actualFormat == 32) {
6937 					// 32 means it is a C long... which is variable length
6938 					actualFormat = cast(int) arch_long.sizeof * 8;
6939 				}
6940 
6941 				// then it is just a bit count
6942 				byteLength = cast(int) (actualItems * actualFormat / 8);
6943 
6944 				auto d = new ubyte[](byteLength);
6945 				d[] = cast(ubyte[]) data[0 .. byteLength];
6946 				XFree(data);
6947 				return d;
6948 			}
6949 		}
6950 		return null;
6951 	}
6952 
6953 	/* defined in the systray spec */
6954 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6955 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6956 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6957 
6958 
6959 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6960 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6961 	public class GlobalHotkey {
6962 		KeyEvent key;
6963 		void delegate () handler;
6964 
6965 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6966 
6967 		/// Create from initialzed KeyEvent object
6968 		this (KeyEvent akey, void delegate () ahandler=null) {
6969 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6970 			key = akey;
6971 			handler = ahandler;
6972 		}
6973 
6974 		/// Create from emacs-like key name ("C-M-Y", etc.)
6975 		this (const(char)[] akey, void delegate () ahandler=null) {
6976 			key = KeyEvent.parse(akey);
6977 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6978 			handler = ahandler;
6979 		}
6980 
6981 	}
6982 
6983 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6984 		//conwriteln("failed to grab key");
6985 		GlobalHotkeyManager.ghfailed = true;
6986 		return 0;
6987 	}
6988 
6989 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6990 		Image.impl.xshmfailed = true;
6991 		return 0;
6992 	}
6993 
6994 	private __gshared int errorHappened;
6995 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6996 		import core.stdc.stdio;
6997 		char[265] buffer;
6998 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6999 		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);
7000 		errorHappened = true;
7001 		return 0;
7002 	}
7003 
7004 	/++
7005 		Global hotkey manager. It contains static methods to manage global hotkeys.
7006 
7007 		---
7008 		 try {
7009 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
7010 		} catch (Exception e) {
7011 			conwriteln("ERROR registering hotkey!");
7012 		}
7013 		EventLoop.get.run();
7014 		---
7015 
7016 		The key strings are based on Emacs. In practical terms,
7017 		`M` means `alt` and `H` means the Windows logo key. `C`
7018 		is `ctrl`.
7019 
7020 		$(WARNING
7021 			This is X-specific right now. If you are on
7022 			Windows, try [registerHotKey] instead.
7023 
7024 			We will probably merge these into a single
7025 			interface later.
7026 		)
7027 	+/
7028 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
7029 		version(X11) {
7030 			void recreateAfterDisconnect() {
7031 				throw new Exception("NOT IMPLEMENTED");
7032 			}
7033 			void discardConnectionState() {
7034 				throw new Exception("NOT IMPLEMENTED");
7035 			}
7036 		}
7037 
7038 		private static immutable uint[8] masklist = [ 0,
7039 			KeyOrButtonMask.LockMask,
7040 			KeyOrButtonMask.Mod2Mask,
7041 			KeyOrButtonMask.Mod3Mask,
7042 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
7043 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
7044 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7045 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7046 		];
7047 		private __gshared GlobalHotkeyManager ghmanager;
7048 		private __gshared bool ghfailed = false;
7049 
7050 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
7051 			if (modmask == 0) return false;
7052 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
7053 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
7054 			return true;
7055 		}
7056 
7057 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
7058 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
7059 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
7060 			return modmask;
7061 		}
7062 
7063 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
7064 			uint keycode = cast(uint)ke.key;
7065 			auto dpy = XDisplayConnection.get;
7066 			return XKeysymToKeycode(dpy, keycode);
7067 		}
7068 
7069 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
7070 
7071 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
7072 
7073 		NativeEventHandler getNativeEventHandler () {
7074 			return delegate int (XEvent e) {
7075 				if (e.type != EventType.KeyPress) return 1;
7076 				auto kev = cast(const(XKeyEvent)*)&e;
7077 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
7078 				if (auto ghkp = hash in globalHotkeyList) {
7079 					try {
7080 						ghkp.doHandle();
7081 					} catch (Exception e) {
7082 						import core.stdc.stdio : stderr, fprintf;
7083 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
7084 					}
7085 				}
7086 				return 1;
7087 			};
7088 		}
7089 
7090 		private this () {
7091 			auto dpy = XDisplayConnection.get;
7092 			auto root = RootWindow(dpy, DefaultScreen(dpy));
7093 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
7094 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
7095 		}
7096 
7097 		/// Register new global hotkey with initialized `GlobalHotkey` object.
7098 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
7099 		static void register (GlobalHotkey gh) {
7100 			if (gh is null) return;
7101 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
7102 
7103 			auto dpy = XDisplayConnection.get;
7104 			immutable keycode = keyEvent2KeyCode(gh.key);
7105 
7106 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
7107 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
7108 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
7109 			XSync(dpy, 0/*False*/);
7110 
7111 			Window root = RootWindow(dpy, DefaultScreen(dpy));
7112 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7113 			ghfailed = false;
7114 			foreach (immutable uint ormask; masklist[]) {
7115 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
7116 			}
7117 			XSync(dpy, 0/*False*/);
7118 			XSetErrorHandler(savedErrorHandler);
7119 
7120 			if (ghfailed) {
7121 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7122 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
7123 				XSync(dpy, 0/*False*/);
7124 				XSetErrorHandler(savedErrorHandler);
7125 				throw new Exception("cannot register global hotkey");
7126 			}
7127 
7128 			globalHotkeyList[hash] = gh;
7129 		}
7130 
7131 		/// Ditto
7132 		static void register (const(char)[] akey, void delegate () ahandler) {
7133 			register(new GlobalHotkey(akey, ahandler));
7134 		}
7135 
7136 		private static void removeByHash (ulong hash) {
7137 			if (auto ghp = hash in globalHotkeyList) {
7138 				auto dpy = XDisplayConnection.get;
7139 				immutable keycode = keyEvent2KeyCode(ghp.key);
7140 				Window root = RootWindow(dpy, DefaultScreen(dpy));
7141 				XSync(dpy, 0/*False*/);
7142 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7143 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
7144 				XSync(dpy, 0/*False*/);
7145 				XSetErrorHandler(savedErrorHandler);
7146 				globalHotkeyList.remove(hash);
7147 			}
7148 		}
7149 
7150 		/// Register new global hotkey with previously used `GlobalHotkey` object.
7151 		/// It is safe to unregister unknown or invalid hotkey.
7152 		static void unregister (GlobalHotkey gh) {
7153 			//TODO: add second AA for faster search? prolly doesn't worth it.
7154 			if (gh is null) return;
7155 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
7156 				if (kv.value is gh) {
7157 					removeByHash(kv.key);
7158 					return;
7159 				}
7160 			}
7161 		}
7162 
7163 		/// Ditto.
7164 		static void unregister (const(char)[] key) {
7165 			auto kev = KeyEvent.parse(key);
7166 			immutable keycode = keyEvent2KeyCode(kev);
7167 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
7168 		}
7169 	}
7170 }
7171 
7172 version(Windows) {
7173 	/++
7174 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
7175 
7176 		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).
7177 	+/
7178 	void sendSyntheticInput(wstring s) {
7179 			INPUT[] inputs;
7180 			inputs.reserve(s.length * 2);
7181 
7182 			foreach(wchar c; s) {
7183 				INPUT input;
7184 				input.type = INPUT_KEYBOARD;
7185 				input.ki.wScan = c;
7186 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7187 				inputs ~= input;
7188 
7189 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7190 				inputs ~= input;
7191 			}
7192 
7193 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7194 				throw new WindowsApiException("SendInput", GetLastError());
7195 			}
7196 
7197 	}
7198 
7199 
7200 	// global hotkey helper function
7201 
7202 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
7203 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system {
7204 		__gshared int hotkeyId = 0;
7205 		int id = ++hotkeyId;
7206 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
7207 			throw new Exception("RegisterHotKey");
7208 
7209 		__gshared void delegate()[WPARAM][HWND] handlers;
7210 
7211 		handlers[window.impl.hwnd][id] = handler;
7212 
7213 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
7214 
7215 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7216 			switch(msg) {
7217 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
7218 				case WM_HOTKEY:
7219 					if(auto list = hwnd in handlers) {
7220 						if(auto h = wParam in *list) {
7221 							(*h)();
7222 							return 0;
7223 						}
7224 					}
7225 				goto default;
7226 				default:
7227 			}
7228 			if(oldHandler)
7229 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
7230 			return 1; // pass it on
7231 		};
7232 
7233 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
7234 			oldHandler = window.handleNativeEvent;
7235 			window.handleNativeEvent = nativeEventHandler;
7236 		}
7237 
7238 		return id;
7239 	}
7240 
7241 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
7242 	void unregisterHotKey(SimpleWindow window, int id) {
7243 		if(!UnregisterHotKey(window.impl.hwnd, id))
7244 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
7245 	}
7246 }
7247 
7248 version (X11) {
7249 	pragma(lib, "dl");
7250 	import core.sys.posix.dlfcn;
7251 }
7252 
7253 /++
7254 	Allows for sending synthetic input to the X server via the Xtst
7255 	extension or on Windows using SendInput.
7256 
7257 	Please remember user input is meant to be user - don't use this
7258 	if you have some other alternative!
7259 
7260 	History:
7261 		Added May 17, 2020 with the X implementation.
7262 
7263 		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.)
7264 	Bugs:
7265 		All methods on OSX Cocoa will throw not yet implemented exceptions.
7266 +/
7267 struct SyntheticInput {
7268 	@disable this();
7269 
7270 	private int* refcount;
7271 
7272 	version(X11) {
7273 		private void* lib;
7274 
7275 		private extern(C) {
7276 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
7277 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
7278 		}
7279 	}
7280 
7281 	/// The dummy param must be 0.
7282 	this(int dummy) {
7283 		version(X11) {
7284 			lib = dlopen("libXtst.so", RTLD_NOW);
7285 			if(lib is null)
7286 				throw new Exception("cannot load xtest lib extension");
7287 			scope(failure)
7288 				dlclose(lib);
7289 
7290 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
7291 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
7292 
7293 			if(XTestFakeKeyEvent is null)
7294 				throw new Exception("No XTestFakeKeyEvent");
7295 			if(XTestFakeButtonEvent is null)
7296 				throw new Exception("No XTestFakeButtonEvent");
7297 		}
7298 
7299 		refcount = new int;
7300 		*refcount = 1;
7301 	}
7302 
7303 	this(this) {
7304 		if(refcount)
7305 			*refcount += 1;
7306 	}
7307 
7308 	~this() {
7309 		if(refcount) {
7310 			*refcount -= 1;
7311 			if(*refcount == 0)
7312 				// I commented this because if I close the lib before
7313 				// XCloseDisplay, it is liable to segfault... so just
7314 				// gonna keep it loaded if it is loaded, no big deal
7315 				// anyway.
7316 				{} // dlclose(lib);
7317 		}
7318 	}
7319 
7320 	/++
7321 		Simulates typing a string into the keyboard.
7322 
7323 		Bugs:
7324 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
7325 
7326 			Not implemented except on Windows and X11.
7327 	+/
7328 	void sendSyntheticInput(string s) {
7329 		version(Windows) {
7330 			INPUT[] inputs;
7331 			inputs.reserve(s.length * 2);
7332 
7333 			auto ei = GetMessageExtraInfo();
7334 
7335 			foreach(wchar c; s) {
7336 				INPUT input;
7337 				input.type = INPUT_KEYBOARD;
7338 				input.ki.wScan = c;
7339 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7340 				input.ki.dwExtraInfo = ei;
7341 				inputs ~= input;
7342 
7343 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7344 				inputs ~= input;
7345 			}
7346 
7347 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7348 				throw new WindowsApiException("SendInput", GetLastError());
7349 			}
7350 		} else version(X11) {
7351 			int delay = 0;
7352 			foreach(ch; s) {
7353 				pressKey(cast(Key) ch, true, delay);
7354 				pressKey(cast(Key) ch, false, delay);
7355 				delay += 5;
7356 			}
7357 		} else throw new NotYetImplementedException();
7358 	}
7359 
7360 	/++
7361 		Sends a fake press or release key event.
7362 
7363 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7364 
7365 		Bugs:
7366 			The `delay` parameter is not implemented yet on Windows.
7367 
7368 			Not implemented except on Windows and X11.
7369 	+/
7370 	void pressKey(Key key, bool pressed, int delay = 0) {
7371 		version(Windows) {
7372 			INPUT input;
7373 			input.type = INPUT_KEYBOARD;
7374 			input.ki.wVk = cast(ushort) key;
7375 
7376 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
7377 			input.ki.dwExtraInfo = GetMessageExtraInfo();
7378 
7379 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7380 				throw new WindowsApiException("SendInput", GetLastError());
7381 			}
7382 		} else version(X11) {
7383 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
7384 		} else throw new NotYetImplementedException();
7385 	}
7386 
7387 	/++
7388 		Sends a fake mouse button press or release event.
7389 
7390 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7391 
7392 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
7393 
7394 		Bugs:
7395 			The `delay` parameter is not implemented yet on Windows.
7396 
7397 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
7398 
7399 			All arguments will throw NotYetImplementedException on OSX Cocoa.
7400 	+/
7401 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
7402 		version(Windows) {
7403 			INPUT input;
7404 			input.type = INPUT_MOUSE;
7405 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7406 
7407 			// input.mi.mouseData for a wheel event
7408 
7409 			switch(button) {
7410 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
7411 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
7412 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
7413 				case MouseButton.wheelUp:
7414 				case MouseButton.wheelDown:
7415 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
7416 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
7417 				break;
7418 				case MouseButton.backButton: throw new NotYetImplementedException();
7419 				case MouseButton.forwardButton: throw new NotYetImplementedException();
7420 				default:
7421 			}
7422 
7423 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7424 				throw new WindowsApiException("SendInput", GetLastError());
7425 			}
7426 		} else version(X11) {
7427 			int btn;
7428 
7429 			switch(button) {
7430 				case MouseButton.left: btn = 1; break;
7431 				case MouseButton.middle: btn = 2; break;
7432 				case MouseButton.right: btn = 3; break;
7433 				case MouseButton.wheelUp: btn = 4; break;
7434 				case MouseButton.wheelDown: btn = 5; break;
7435 				case MouseButton.backButton: btn = 8; break;
7436 				case MouseButton.forwardButton: btn = 9; break;
7437 				default:
7438 			}
7439 
7440 			assert(btn);
7441 
7442 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7443 		} else throw new NotYetImplementedException();
7444 	}
7445 
7446 	///
7447 	static void moveMouseArrowBy(int dx, int dy) {
7448 		version(Windows) {
7449 			INPUT input;
7450 			input.type = INPUT_MOUSE;
7451 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7452 			input.mi.dx = dx;
7453 			input.mi.dy = dy;
7454 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7455 
7456 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7457 				throw new WindowsApiException("SendInput", GetLastError());
7458 			}
7459 		} else version(X11) {
7460 			auto disp = XDisplayConnection.get();
7461 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7462 			XFlush(disp);
7463 		} else throw new NotYetImplementedException();
7464 	}
7465 
7466 	///
7467 	static void moveMouseArrowTo(int x, int y) {
7468 		version(Windows) {
7469 			INPUT input;
7470 			input.type = INPUT_MOUSE;
7471 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7472 			input.mi.dx = x;
7473 			input.mi.dy = y;
7474 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7475 
7476 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7477 				throw new WindowsApiException("SendInput", GetLastError());
7478 			}
7479 		} else version(X11) {
7480 			auto disp = XDisplayConnection.get();
7481 			auto root = RootWindow(disp, DefaultScreen(disp));
7482 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7483 			XFlush(disp);
7484 		} else throw new NotYetImplementedException();
7485 	}
7486 }
7487 
7488 
7489 
7490 /++
7491 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7492 
7493 	See_Also:
7494 	$(LIST
7495 		*[ScreenPainter]
7496 		*[ScreenPainter.rasterOp]
7497 	)
7498 +/
7499 enum RasterOp {
7500 	normal, /// Replaces the pixel.
7501 	xor, /// Uses bitwise xor to draw.
7502 }
7503 
7504 // being phobos-free keeps the size WAY down
7505 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7506 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7507 package(arsd) const(wchar)* toWStringz(string s) {
7508 	wstring r;
7509 	foreach(dchar c; s)
7510 		r ~= c;
7511 	r ~= '\0';
7512 	return r.ptr;
7513 }
7514 private string[] split(in void[] a, char c) {
7515 		string[] ret;
7516 		size_t previous = 0;
7517 		foreach(i, char ch; cast(ubyte[]) a) {
7518 			if(ch == c) {
7519 				ret ~= cast(string) a[previous .. i];
7520 				previous = i + 1;
7521 			}
7522 		}
7523 		if(previous != a.length)
7524 			ret ~= cast(string) a[previous .. $];
7525 		return ret;
7526 	}
7527 
7528 version(without_opengl) {
7529 	enum OpenGlOptions {
7530 		no,
7531 	}
7532 } else {
7533 	/++
7534 		Determines if you want an OpenGL context created on the new window.
7535 
7536 
7537 		See more: [#topics-3d|in the 3d topic].
7538 
7539 		---
7540 		import arsd.simpledisplay;
7541 		void main() {
7542 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7543 
7544 			// Set up the matrix
7545 			window.setAsCurrentOpenGlContext(); // make this window active
7546 
7547 			// This is called on each frame, we will draw our scene
7548 			window.redrawOpenGlScene = delegate() {
7549 
7550 			};
7551 
7552 			window.eventLoop(0);
7553 		}
7554 		---
7555 	+/
7556 	enum OpenGlOptions {
7557 		no, /// No OpenGL context is created
7558 		yes, /// Yes, create an OpenGL context
7559 	}
7560 
7561 	version(X11) {
7562 		static if (!SdpyIsUsingIVGLBinds) {
7563 
7564 
7565 			struct __GLXFBConfigRec {}
7566 			alias GLXFBConfig = __GLXFBConfigRec*;
7567 
7568 			//pragma(lib, "GL");
7569 			//pragma(lib, "GLU");
7570 			interface GLX {
7571 			extern(C) nothrow @nogc {
7572 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7573 						const int *attrib_list);
7574 
7575 				 void glXCopyContext(Display *dpy, GLXContext src,
7576 						GLXContext dst, arch_ulong mask);
7577 
7578 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7579 						GLXContext share_list, Bool direct);
7580 
7581 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7582 						Pixmap pixmap);
7583 
7584 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7585 
7586 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7587 
7588 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7589 						int attrib, int *value);
7590 
7591 				 GLXContext glXGetCurrentContext();
7592 
7593 				 GLXDrawable glXGetCurrentDrawable();
7594 
7595 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7596 
7597 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7598 						GLXContext ctx);
7599 
7600 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7601 
7602 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7603 
7604 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7605 
7606 				 void glXUseXFont(Font font, int first, int count, int list_base);
7607 
7608 				 void glXWaitGL();
7609 
7610 				 void glXWaitX();
7611 
7612 
7613 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7614 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7615 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7616 
7617 				char* glXQueryExtensionsString (Display*, int);
7618 				void* glXGetProcAddress (const(char)*);
7619 
7620 			}
7621 			}
7622 
7623 			version(OSX)
7624 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7625 			else
7626 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7627 			shared static this() {
7628 				glx.loadDynamicLibrary();
7629 			}
7630 
7631 			alias glbindGetProcAddress = glXGetProcAddress;
7632 		}
7633 	} else version(Windows) {
7634 		/* it is done below by interface GL */
7635 	} else
7636 		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.");
7637 }
7638 
7639 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7640 alias Resizablity = Resizability;
7641 
7642 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7643 enum Resizability {
7644 	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.
7645 	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.
7646 	/++
7647 		$(PITFALL
7648 			Planned for the future but not implemented.
7649 		)
7650 
7651 		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.
7652 
7653 		History:
7654 			Added November 11, 2022, but not yet implemented and may not be for some time.
7655 	+/
7656 	/*@__future*/ allowResizingMaintainingAspectRatio,
7657 	/++
7658 		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.
7659 
7660 		History:
7661 			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.
7662 
7663 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7664 	+/
7665 	automaticallyScaleIfPossible,
7666 }
7667 /// ditto
7668 alias Resizeability = Resizability;
7669 
7670 
7671 /++
7672 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7673 +/
7674 enum TextAlignment : uint {
7675 	Left = 0, ///
7676 	Center = 1, ///
7677 	Right = 2, ///
7678 
7679 	VerticalTop = 0, ///
7680 	VerticalCenter = 4, ///
7681 	VerticalBottom = 8, ///
7682 }
7683 
7684 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7685 alias Rectangle = arsd.color.Rectangle;
7686 
7687 
7688 /++
7689 	Keyboard press and release events.
7690 +/
7691 struct KeyEvent {
7692 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7693 	Key key;
7694 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7695 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7696 
7697 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7698 
7699 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7700 
7701 	SimpleWindow window; /// associated Window
7702 
7703 	/++
7704 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7705 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7706 		to predict if char events are actually coming..
7707 
7708 		Only available on X systems since this information is not given ahead of time elsewhere.
7709 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7710 
7711 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7712 		and potential quirks I'd recommend avoiding it.
7713 
7714 		History:
7715 			Added April 26, 2021 (dub v9.5)
7716 	+/
7717 	version(X11)
7718 		dchar[] charsPossible;
7719 
7720 	// convert key event to simplified string representation a-la emacs
7721 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7722 		uint dpos = 0;
7723 		void put (const(char)[] s...) nothrow @trusted {
7724 			static if (growdest) {
7725 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7726 			} else {
7727 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7728 			}
7729 		}
7730 
7731 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7732 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7733 		}
7734 
7735 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7736 
7737 		// put modifiers
7738 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7739 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7740 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7741 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7742 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7743 
7744 		if (this.key) {
7745 			foreach (string kn; __traits(allMembers, Key)) {
7746 				if (this.key == __traits(getMember, Key, kn)) {
7747 					// HACK!
7748 					static if (kn == "N0") put("0");
7749 					else static if (kn == "N1") put("1");
7750 					else static if (kn == "N2") put("2");
7751 					else static if (kn == "N3") put("3");
7752 					else static if (kn == "N4") put("4");
7753 					else static if (kn == "N5") put("5");
7754 					else static if (kn == "N6") put("6");
7755 					else static if (kn == "N7") put("7");
7756 					else static if (kn == "N8") put("8");
7757 					else static if (kn == "N9") put("9");
7758 					else put(kn);
7759 					return dest[0..dpos];
7760 				}
7761 			}
7762 			put("Unknown");
7763 		} else {
7764 			if (dpos && dest[dpos-1] == '+') --dpos;
7765 		}
7766 		return dest[0..dpos];
7767 	}
7768 
7769 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7770 
7771 	/** Parse string into key name with modifiers. It accepts things like:
7772 	 *
7773 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7774 	 *
7775 	 * Ctrl+Win+1 -- windows style
7776 	 *
7777 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7778 	 *
7779 	 * Ctrl Win 1 -- and space
7780 	 *
7781 	 * and even "Win + 1 + Ctrl".
7782 	 */
7783 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7784 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7785 
7786 		// remove trailing spaces
7787 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7788 
7789 		// tokens delimited by blank, '+', or '-'
7790 		// null on eol
7791 		const(char)[] getToken () nothrow @trusted @nogc {
7792 			// remove leading spaces and delimiters
7793 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7794 			if (name.length == 0) return null; // oops, no more tokens
7795 			// get token
7796 			size_t epos = 0;
7797 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7798 			assert(epos > 0 && epos <= name.length);
7799 			auto res = name[0..epos];
7800 			name = name[epos..$];
7801 			return res;
7802 		}
7803 
7804 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7805 			if (s0.length != s1.length) return false;
7806 			foreach (immutable ci, char c0; s0) {
7807 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7808 				char c1 = s1[ci];
7809 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7810 				if (c0 != c1) return false;
7811 			}
7812 			return true;
7813 		}
7814 
7815 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7816 		if (updown !is null) *updown = -1;
7817 		KeyEvent res;
7818 		res.key = cast(Key)0; // just in case
7819 		const(char)[] tk, tkn; // last token
7820 		bool allowEmascStyle = true;
7821 		bool ignoreModifiers = false;
7822 		tokenloop: for (;;) {
7823 			tk = tkn;
7824 			tkn = getToken();
7825 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7826 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7827 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7828 			if (allowEmascStyle && tkn.length != 0) {
7829 				if (tk.length == 1) {
7830 					char mdc = tk[0];
7831 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7832 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7833 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7834 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7835 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7836 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7837 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7838 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7839 				}
7840 			}
7841 			allowEmascStyle = false;
7842 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7843 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7844 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7845 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7846 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7847 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7848 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7849 			if (tk.length == 0) continue;
7850 			// try key name
7851 			if (res.key == 0) {
7852 				// little hack
7853 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7854 					final switch (tk[0]) {
7855 						case '0': tk = "N0"; break;
7856 						case '1': tk = "N1"; break;
7857 						case '2': tk = "N2"; break;
7858 						case '3': tk = "N3"; break;
7859 						case '4': tk = "N4"; break;
7860 						case '5': tk = "N5"; break;
7861 						case '6': tk = "N6"; break;
7862 						case '7': tk = "N7"; break;
7863 						case '8': tk = "N8"; break;
7864 						case '9': tk = "N9"; break;
7865 					}
7866 				}
7867 				foreach (string kn; __traits(allMembers, Key)) {
7868 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7869 				}
7870 			}
7871 			// unknown or duplicate key name, get out of here
7872 			break;
7873 		}
7874 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7875 		return res; // something
7876 	}
7877 
7878 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7879 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7880 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7881 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7882 		}
7883 		bool ignoreMods;
7884 		int updown;
7885 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7886 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7887 		if (this.key != ke.key) {
7888 			// things like "ctrl+alt" are complicated
7889 			uint tkm = this.modifierState&modmask;
7890 			uint kkm = ke.modifierState&modmask;
7891 			Key tk = this.key;
7892 			// ke
7893 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7894 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7895 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7896 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7897 			// this
7898 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7899 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7900 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7901 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7902 			return (tk == ke.key && tkm == kkm);
7903 		}
7904 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7905 	}
7906 }
7907 
7908 /// Sets the application name.
7909 @property string ApplicationName(string name) {
7910 	return _applicationName = name;
7911 }
7912 
7913 string _applicationName;
7914 
7915 /// ditto
7916 @property string ApplicationName() {
7917 	if(_applicationName is null) {
7918 		import core.runtime;
7919 		return Runtime.args[0];
7920 	}
7921 	return _applicationName;
7922 }
7923 
7924 
7925 /// Type of a [MouseEvent].
7926 enum MouseEventType : int {
7927 	motion = 0, /// The mouse moved inside the window
7928 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7929 	buttonReleased = 2, /// A mouse button was released
7930 }
7931 
7932 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7933 /++
7934 	Listen for this on your event listeners if you are interested in mouse action.
7935 
7936 	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.
7937 
7938 	Examples:
7939 
7940 	This will draw boxes on the window with the mouse as you hold the left button.
7941 	---
7942 	import arsd.simpledisplay;
7943 
7944 	void main() {
7945 		auto window = new SimpleWindow();
7946 
7947 		window.eventLoop(0,
7948 			(MouseEvent ev) {
7949 				if(ev.modifierState & ModifierState.leftButtonDown) {
7950 					auto painter = window.draw();
7951 					painter.fillColor = Color.red;
7952 					painter.outlineColor = Color.black;
7953 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7954 				}
7955 			}
7956 		);
7957 	}
7958 	---
7959 +/
7960 struct MouseEvent {
7961 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7962 
7963 	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.
7964 	int y; /// Current Y position of the cursor when the event fired.
7965 
7966 	int dx; /// Change in X position since last report
7967 	int dy; /// Change in Y position since last report
7968 
7969 	MouseButton button; /// See [MouseButton]
7970 	int modifierState; /// See [ModifierState]
7971 
7972 	version(X11)
7973 		private Time timestamp;
7974 
7975 	/// Returns a linear representation of mouse button,
7976 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7977 	///
7978 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7979 	@property ubyte buttonLinear() const {
7980 		import core.bitop;
7981 		if(button == 0)
7982 			return 0;
7983 		return (bsf(button) + 1) & 0b1111;
7984 	}
7985 
7986 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7987 
7988 	SimpleWindow window; /// The window in which the event happened.
7989 
7990 	Point globalCoordinates() {
7991 		Point p;
7992 		if(window is null)
7993 			throw new Exception("wtf");
7994 		static if(UsingSimpledisplayX11) {
7995 			Window child;
7996 			XTranslateCoordinates(
7997 				XDisplayConnection.get,
7998 				window.impl.window,
7999 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
8000 				x, y, &p.x, &p.y, &child);
8001 			return p;
8002 		} else version(Windows) {
8003 			POINT[1] points;
8004 			points[0].x = x;
8005 			points[0].y = y;
8006 			MapWindowPoints(
8007 				window.impl.hwnd,
8008 				null,
8009 				points.ptr,
8010 				points.length
8011 			);
8012 			p.x = points[0].x;
8013 			p.y = points[0].y;
8014 
8015 			return p;
8016 		} else version(OSXCocoa) {
8017 			throw new NotYetImplementedException();
8018 		} else static assert(0);
8019 	}
8020 
8021 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
8022 
8023 	/**
8024 	can contain emacs-like modifier prefix
8025 	case-insensitive names:
8026 		lmbX/leftX
8027 		rmbX/rightX
8028 		mmbX/middleX
8029 		wheelX
8030 		motion (no prefix allowed)
8031 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
8032 	*/
8033 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
8034 		if (str.length == 0) return false; // just in case
8035 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
8036 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
8037 		auto anchor = str;
8038 		uint mods = 0; // uint.max == any
8039 		// interesting bits in kmod
8040 		uint kmodmask =
8041 			ModifierState.shift|
8042 			ModifierState.ctrl|
8043 			ModifierState.alt|
8044 			ModifierState.windows|
8045 			ModifierState.leftButtonDown|
8046 			ModifierState.middleButtonDown|
8047 			ModifierState.rightButtonDown|
8048 			0;
8049 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
8050 		bool wasButtons = false;
8051 		while (str.length) {
8052 			if (str.ptr[0] <= ' ') {
8053 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
8054 				continue;
8055 			}
8056 			// one-letter modifier?
8057 			if (str.length >= 2 && str.ptr[1] == '-') {
8058 				switch (str.ptr[0]) {
8059 					case '*': // "any" modifier (cannot be undone)
8060 						mods = mods.max;
8061 						break;
8062 					case 'C': case 'c': // emacs "ctrl"
8063 						if (mods != mods.max) mods |= ModifierState.ctrl;
8064 						break;
8065 					case 'M': case 'm': // emacs "meta"
8066 						if (mods != mods.max) mods |= ModifierState.alt;
8067 						break;
8068 					case 'S': case 's': // emacs "shift"
8069 						if (mods != mods.max) mods |= ModifierState.shift;
8070 						break;
8071 					case 'H': case 'h': // emacs "hyper" (aka winkey)
8072 						if (mods != mods.max) mods |= ModifierState.windows;
8073 						break;
8074 					default:
8075 						return false; // unknown modifier
8076 				}
8077 				str = str[2..$];
8078 				continue;
8079 			}
8080 			// word
8081 			char[16] buf = void; // locased
8082 			auto wep = 0;
8083 			while (str.length) {
8084 				immutable char ch = str.ptr[0];
8085 				if (ch <= ' ' || ch == '-') break;
8086 				str = str[1..$];
8087 				if (wep > buf.length) return false; // too long
8088 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8089 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8090 				else return false; // invalid char
8091 			}
8092 			if (wep == 0) return false; // just in case
8093 			uint bnum;
8094 			enum UpDown { None = -1, Up, Down, Any }
8095 			auto updown = UpDown.None; // 0: up; 1: down
8096 			switch (buf[0..wep]) {
8097 				// left button
8098 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
8099 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
8100 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
8101 				case "lmb": case "left": bnum = 0; break;
8102 				// middle button
8103 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
8104 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
8105 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
8106 				case "mmb": case "middle": bnum = 1; break;
8107 				// right button
8108 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
8109 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
8110 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
8111 				case "rmb": case "right": bnum = 2; break;
8112 				// wheel
8113 				case "wheelup": updown = UpDown.Up; goto case "wheel";
8114 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
8115 				case "wheelany": updown = UpDown.Any; goto case "wheel";
8116 				case "wheel": bnum = 3; break;
8117 				// motion
8118 				case "motion": bnum = 7; break;
8119 				// unknown
8120 				default: return false;
8121 			}
8122 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8123 			// parse possible "-up" or "-down"
8124 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
8125 				wep = 0;
8126 				foreach (immutable idx, immutable char ch; str[1..$]) {
8127 					if (ch <= ' ' || ch == '-') break;
8128 					assert(idx == wep); // for now; trick
8129 					if (wep > buf.length) { wep = 0; break; } // too long
8130 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8131 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8132 					else { wep = 0; break; } // invalid char
8133 				}
8134 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
8135 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
8136 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
8137 				// remove parsed part
8138 				if (updown != UpDown.None) str = str[wep+1..$];
8139 			}
8140 			if (updown == UpDown.None) {
8141 				updown = UpDown.Down;
8142 			}
8143 			wasButtons = wasButtons || (bnum <= 2);
8144 			//assert(updown != UpDown.None);
8145 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8146 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
8147 			if (lastButt != lastButt.max) {
8148 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
8149 				if (mods != mods.max) {
8150 					uint butbit = 0;
8151 					final switch (lastButt&0x03) {
8152 						case 0: butbit = ModifierState.leftButtonDown; break;
8153 						case 1: butbit = ModifierState.middleButtonDown; break;
8154 						case 2: butbit = ModifierState.rightButtonDown; break;
8155 					}
8156 					     if (lastButt&Flag.Down) mods |= butbit;
8157 					else if (lastButt&Flag.Up) mods &= ~butbit;
8158 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
8159 				}
8160 			}
8161 			// remember last button
8162 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
8163 		}
8164 		// no button -- nothing to do
8165 		if (lastButt == lastButt.max) return false;
8166 		// done parsing, check if something's left
8167 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
8168 		// remove action button from mask
8169 		if ((lastButt&0xff) < 3) {
8170 			final switch (lastButt&0x03) {
8171 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
8172 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
8173 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
8174 			}
8175 		}
8176 		// special case: "Motion" means "ignore buttons"
8177 		if ((lastButt&0xff) == 7 && !wasButtons) {
8178 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
8179 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
8180 		}
8181 		uint kmod = event.modifierState&kmodmask;
8182 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
8183 		// check modifier state
8184 		if (mods != mods.max) {
8185 			if (kmod != mods) return false;
8186 		}
8187 		// now check type
8188 		if ((lastButt&0xff) == 7) {
8189 			// motion
8190 			if (event.type != MouseEventType.motion) return false;
8191 		} else if ((lastButt&0xff) == 3) {
8192 			// wheel
8193 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
8194 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
8195 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
8196 			return false;
8197 		} else {
8198 			// buttons
8199 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
8200 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
8201 			{
8202 				return false;
8203 			}
8204 			// button number
8205 			switch (lastButt&0x03) {
8206 				case 0: if (event.button != MouseButton.left) return false; break;
8207 				case 1: if (event.button != MouseButton.middle) return false; break;
8208 				case 2: if (event.button != MouseButton.right) return false; break;
8209 				default: return false;
8210 			}
8211 		}
8212 		return true;
8213 	}
8214 }
8215 
8216 version(arsd_mevent_strcmp_test) unittest {
8217 	MouseEvent event;
8218 	event.type = MouseEventType.buttonPressed;
8219 	event.button = MouseButton.left;
8220 	event.modifierState = ModifierState.ctrl;
8221 	assert(event == "C-LMB");
8222 	assert(event != "C-LMBUP");
8223 	assert(event != "C-LMB-UP");
8224 	assert(event != "C-S-LMB");
8225 	assert(event == "*-LMB");
8226 	assert(event != "*-LMB-UP");
8227 
8228 	event.type = MouseEventType.buttonReleased;
8229 	assert(event != "C-LMB");
8230 	assert(event == "C-LMBUP");
8231 	assert(event == "C-LMB-UP");
8232 	assert(event != "C-S-LMB");
8233 	assert(event != "*-LMB");
8234 	assert(event == "*-LMB-UP");
8235 
8236 	event.button = MouseButton.right;
8237 	event.modifierState |= ModifierState.shift;
8238 	event.type = MouseEventType.buttonPressed;
8239 	assert(event != "C-LMB");
8240 	assert(event != "C-LMBUP");
8241 	assert(event != "C-LMB-UP");
8242 	assert(event != "C-S-LMB");
8243 	assert(event != "*-LMB");
8244 	assert(event != "*-LMB-UP");
8245 
8246 	assert(event != "C-RMB");
8247 	assert(event != "C-RMBUP");
8248 	assert(event != "C-RMB-UP");
8249 	assert(event == "C-S-RMB");
8250 	assert(event == "*-RMB");
8251 	assert(event != "*-RMB-UP");
8252 }
8253 
8254 /// This gives a few more options to drawing lines and such
8255 struct Pen {
8256 	Color color; /// the foreground color
8257 	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.
8258 	Style style; /// See [Style]
8259 /+
8260 // From X.h
8261 
8262 #define LineSolid		0
8263 #define LineOnOffDash		1
8264 #define LineDoubleDash		2
8265        LineDou-        The full path of the line is drawn, but the
8266        bleDash         even dashes are filled differently from the
8267                        odd dashes (see fill-style) with CapButt
8268                        style used where even and odd dashes meet.
8269 
8270 
8271 
8272 /* capStyle */
8273 
8274 #define CapNotLast		0
8275 #define CapButt			1
8276 #define CapRound		2
8277 #define CapProjecting		3
8278 
8279 /* joinStyle */
8280 
8281 #define JoinMiter		0
8282 #define JoinRound		1
8283 #define JoinBevel		2
8284 
8285 /* fillStyle */
8286 
8287 #define FillSolid		0
8288 #define FillTiled		1
8289 #define FillStippled		2
8290 #define FillOpaqueStippled	3
8291 
8292 
8293 +/
8294 	/// Style of lines drawn
8295 	enum Style {
8296 		Solid, /// a solid line
8297 		Dashed, /// a dashed line
8298 		Dotted, /// a dotted line
8299 	}
8300 }
8301 
8302 
8303 /++
8304 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
8305 
8306 
8307 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
8308 
8309 	$(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.)
8310 
8311 	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.
8312 
8313 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
8314 
8315 	$(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.
8316 
8317 	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!
8318 
8319 	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!)
8320 
8321 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
8322 
8323 	---
8324 		auto image = new Image(256, 256);
8325 		scope(exit) destroy(image);
8326 	---
8327 
8328 	As long as you don't hold on to it outside the scope.
8329 
8330 	I might change it to be an owned pointer at some point in the future.
8331 
8332 	)
8333 
8334 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
8335 	you can also often get a fair amount of speedup by getting the raw data format and
8336 	writing some custom code.
8337 
8338 	FIXME INSERT EXAMPLES HERE
8339 
8340 
8341 +/
8342 final class Image {
8343 	///
8344 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
8345 		this.width = width;
8346 		this.height = height;
8347 		this.enableAlpha = enableAlpha;
8348 
8349 		impl.createImage(width, height, forcexshm, enableAlpha);
8350 	}
8351 
8352 	///
8353 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
8354 		this(size.width, size.height, forcexshm, enableAlpha);
8355 	}
8356 
8357 	private bool suppressDestruction;
8358 
8359 	version(X11)
8360 	this(XImage* handle) {
8361 		this.handle = handle;
8362 		this.rawData = cast(ubyte*) handle.data;
8363 		this.width = handle.width;
8364 		this.height = handle.height;
8365 		this.enableAlpha = handle.depth == 32;
8366 		suppressDestruction = true;
8367 	}
8368 
8369 	~this() {
8370 		if(suppressDestruction) return;
8371 		impl.dispose();
8372 	}
8373 
8374 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
8375 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
8376 	pure const @system nothrow {
8377 		/*
8378 			To use these to draw a blue rectangle with size WxH at position X,Y...
8379 
8380 			// make certain that it will fit before we proceed
8381 			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!
8382 
8383 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
8384 			// (though calculating them isn't really that expensive).
8385 			auto nextLineAdjustment = img.adjustmentForNextLine();
8386 			auto offR = img.redByteOffset();
8387 			auto offB = img.blueByteOffset();
8388 			auto offG = img.greenByteOffset();
8389 			auto bpp = img.bytesPerPixel();
8390 
8391 			auto data = img.getDataPointer();
8392 
8393 			// figure out the starting byte offset
8394 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
8395 
8396 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
8397 
8398 			// and now our drawing loop for the rectangle
8399 			foreach(y; 0 .. H) {
8400 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
8401 				foreach(x; 0 .. W) {
8402 					// write our color
8403 					data[offR] = 0;
8404 					data[offG] = 0;
8405 					data[offB] = 255;
8406 
8407 					data += bpp; // moving to the next pixel is just an addition...
8408 				}
8409 				startOfLine += nextLineAdjustment;
8410 			}
8411 
8412 
8413 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
8414 
8415 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
8416 			can be made into a bitmask or something so we can write them as *uint...
8417 		*/
8418 
8419 		///
8420 		int offsetForTopLeftPixel() {
8421 			version(X11) {
8422 				return 0;
8423 			} else version(Windows) {
8424 				if(enableAlpha) {
8425 					return (width * 4) * (height - 1);
8426 				} else {
8427 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
8428 				}
8429 			} else version(OSXCocoa) {
8430 				return 0 ; //throw new NotYetImplementedException();
8431 			} else static assert(0, "fill in this info for other OSes");
8432 		}
8433 
8434 		///
8435 		int offsetForPixel(int x, int y) {
8436 			version(X11) {
8437 				auto offset = (y * width + x) * 4;
8438 				return offset;
8439 			} else version(Windows) {
8440 				if(enableAlpha) {
8441 					auto itemsPerLine = width * 4;
8442 					// remember, bmps are upside down
8443 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8444 					return offset;
8445 				} else {
8446 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8447 					// remember, bmps are upside down
8448 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8449 					return offset;
8450 				}
8451 			} else version(OSXCocoa) {
8452 				return 0 ; //throw new NotYetImplementedException();
8453 			} else static assert(0, "fill in this info for other OSes");
8454 		}
8455 
8456 		///
8457 		int adjustmentForNextLine() {
8458 			version(X11) {
8459 				return width * 4;
8460 			} else version(Windows) {
8461 				// windows bmps are upside down, so the adjustment is actually negative
8462 				if(enableAlpha)
8463 					return - (cast(int) width * 4);
8464 				else
8465 					return -((cast(int) width * 3 + 3) / 4) * 4;
8466 			} else version(OSXCocoa) {
8467 				return 0 ; //throw new NotYetImplementedException();
8468 			} else static assert(0, "fill in this info for other OSes");
8469 		}
8470 
8471 		/// once you have the position of a pixel, use these to get to the proper color
8472 		int redByteOffset() {
8473 			version(X11) {
8474 				return 2;
8475 			} else version(Windows) {
8476 				return 2;
8477 			} else version(OSXCocoa) {
8478 				return 0 ; //throw new NotYetImplementedException();
8479 			} else static assert(0, "fill in this info for other OSes");
8480 		}
8481 
8482 		///
8483 		int greenByteOffset() {
8484 			version(X11) {
8485 				return 1;
8486 			} else version(Windows) {
8487 				return 1;
8488 			} else version(OSXCocoa) {
8489 				return 0 ; //throw new NotYetImplementedException();
8490 			} else static assert(0, "fill in this info for other OSes");
8491 		}
8492 
8493 		///
8494 		int blueByteOffset() {
8495 			version(X11) {
8496 				return 0;
8497 			} else version(Windows) {
8498 				return 0;
8499 			} else version(OSXCocoa) {
8500 				return 0 ; //throw new NotYetImplementedException();
8501 			} else static assert(0, "fill in this info for other OSes");
8502 		}
8503 
8504 		/// Only valid if [enableAlpha] is true
8505 		int alphaByteOffset() {
8506 			version(X11) {
8507 				return 3;
8508 			} else version(Windows) {
8509 				return 3;
8510 			} else version(OSXCocoa) {
8511 				return 3; //throw new NotYetImplementedException();
8512 			} else static assert(0, "fill in this info for other OSes");
8513 		}
8514 	}
8515 
8516 	///
8517 	final void putPixel(int x, int y, Color c) {
8518 		if(x < 0 || x >= width)
8519 			return;
8520 		if(y < 0 || y >= height)
8521 			return;
8522 
8523 		impl.setPixel(x, y, c);
8524 	}
8525 
8526 	///
8527 	final Color getPixel(int x, int y) {
8528 		if(x < 0 || x >= width)
8529 			return Color.transparent;
8530 		if(y < 0 || y >= height)
8531 			return Color.transparent;
8532 
8533 		version(OSXCocoa) throw new NotYetImplementedException(); else
8534 		return impl.getPixel(x, y);
8535 	}
8536 
8537 	///
8538 	final void opIndexAssign(Color c, int x, int y) {
8539 		putPixel(x, y, c);
8540 	}
8541 
8542 	///
8543 	TrueColorImage toTrueColorImage() {
8544 		auto tci = new TrueColorImage(width, height);
8545 		convertToRgbaBytes(tci.imageData.bytes);
8546 		return tci;
8547 	}
8548 
8549 	///
8550 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8551 		auto tci = i.getAsTrueColorImage();
8552 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8553 		static if(UsingSimpledisplayX11)
8554 			img.premultiply = premultiply;
8555 		img.setRgbaBytes(tci.imageData.bytes);
8556 		return img;
8557 	}
8558 
8559 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8560 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8561 	/// if you pass null, it will allocate a new one.
8562 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8563 		if(where is null)
8564 			where = new ubyte[this.width*this.height*4];
8565 		convertToRgbaBytes(where);
8566 		return where;
8567 	}
8568 
8569 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8570 	void setRgbaBytes(in ubyte[] from ) {
8571 		assert(from.length == this.width * this.height * 4);
8572 		setFromRgbaBytes(from);
8573 	}
8574 
8575 	// FIXME: make properly cross platform by getting rgba right
8576 
8577 	/// warning: this is not portable across platforms because the data format can change
8578 	ubyte* getDataPointer() {
8579 		return impl.rawData;
8580 	}
8581 
8582 	/// for use with getDataPointer
8583 	final int bytesPerLine() const pure @safe nothrow {
8584 		version(Windows)
8585 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8586 		else version(X11)
8587 			return 4 * width;
8588 		else version(OSXCocoa)
8589 			return 4 * width;
8590 		else static assert(0);
8591 	}
8592 
8593 	/// for use with getDataPointer
8594 	final int bytesPerPixel() const pure @safe nothrow {
8595 		version(Windows)
8596 			return enableAlpha ? 4 : 3;
8597 		else version(X11)
8598 			return 4;
8599 		else version(OSXCocoa)
8600 			return 4;
8601 		else static assert(0);
8602 	}
8603 
8604 	///
8605 	immutable int width;
8606 
8607 	///
8608 	immutable int height;
8609 
8610 	///
8611 	immutable bool enableAlpha;
8612     //private:
8613 	mixin NativeImageImplementation!() impl;
8614 }
8615 
8616 /++
8617 	A convenience function to pop up a window displaying the image.
8618 	If you pass a win, it will draw the image in it. Otherwise, it will
8619 	create a window with the size of the image and run its event loop, closing
8620 	when a key is pressed.
8621 
8622 	History:
8623 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8624 		always block until the application quit which could cause bizarre behavior
8625 		inside a more complex application. Now, the default is to block until
8626 		this window closes if it is the only event loop running, and otherwise,
8627 		not to block at all and just pop up the display window asynchronously.
8628 +/
8629 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8630 	if(win is null) {
8631 		win = new SimpleWindow(image);
8632 		{
8633 			auto p = win.draw;
8634 			p.drawImage(Point(0, 0), image);
8635 		}
8636 		win.eventLoopWithBlockingMode(
8637 			bm, 0,
8638 			(KeyEvent ev) {
8639 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8640 			} );
8641 	} else {
8642 		win.image = image;
8643 	}
8644 }
8645 
8646 enum FontWeight : int {
8647 	dontcare = 0,
8648 	thin = 100,
8649 	extralight = 200,
8650 	light = 300,
8651 	regular = 400,
8652 	medium = 500,
8653 	semibold = 600,
8654 	bold = 700,
8655 	extrabold = 800,
8656 	heavy = 900
8657 }
8658 
8659 /++
8660 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8661 
8662 	History:
8663 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8664 +/
8665 interface MeasurableFont {
8666 	/++
8667 		Returns true if it is a monospace font, meaning each of the
8668 		glyphs (at least the ascii characters) have matching width
8669 		and no kerning, so you can determine the display width of some
8670 		strings by simply multiplying the string width by [averageWidth].
8671 
8672 		(Please note that multiply doesn't $(I actually) work in general,
8673 		consider characters like tab and newline, but it does sometimes.)
8674 	+/
8675 	bool isMonospace();
8676 
8677 	/++
8678 		The average width of glyphs in the font, traditionally equal to the
8679 		width of the lowercase x. Can be used to estimate bounding boxes,
8680 		especially if the font [isMonospace].
8681 
8682 		Given in pixels.
8683 	+/
8684 	int averageWidth();
8685 	/++
8686 		The height of the bounding box of a line.
8687 	+/
8688 	int height();
8689 	/++
8690 		The maximum ascent of a glyph above the baseline.
8691 
8692 		Given in pixels.
8693 	+/
8694 	int ascent();
8695 	/++
8696 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8697 
8698 		Given in pixels.
8699 	+/
8700 	int descent();
8701 	/++
8702 		The display width of the given string, and if you provide a window, it will use it to
8703 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8704 
8705 		Given in pixels.
8706 	+/
8707 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8708 
8709 }
8710 
8711 // FIXME: i need a font cache and it needs to handle disconnects.
8712 
8713 /++
8714 	Represents a font loaded off the operating system or the X server.
8715 
8716 
8717 	While the api here is unified cross platform, the fonts are not necessarily
8718 	available, even across machines of the same platform, so be sure to always check
8719 	for null (using [isNull]) and have a fallback plan.
8720 
8721 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8722 
8723 	Worst case, a null font will automatically fall back to the default font loaded
8724 	for your system.
8725 +/
8726 class OperatingSystemFont : MeasurableFont {
8727 	// FIXME: when the X Connection is lost, these need to be invalidated!
8728 	// that means I need to store the original stuff again to reconstruct it too.
8729 
8730 	version(X11) {
8731 		XFontStruct* font;
8732 		XFontSet fontset;
8733 
8734 		version(with_xft) {
8735 			XftFont* xftFont;
8736 			bool isXft;
8737 		}
8738 	} else version(Windows) {
8739 		HFONT font;
8740 		int width_;
8741 		int height_;
8742 	} else version(OSXCocoa) {
8743 		NSFont font;
8744 	} else static assert(0);
8745 
8746 	/++
8747 		Constructs the class and immediately calls [load].
8748 	+/
8749 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8750 		load(name, size, weight, italic);
8751 	}
8752 
8753 	/++
8754 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8755 
8756 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8757 
8758 		History:
8759 			Added January 24, 2021.
8760 	+/
8761 	this() {
8762 		// this space intentionally left blank
8763 	}
8764 
8765 	/++
8766 		Constructs a copy of the given font object.
8767 
8768 		History:
8769 			Added January 7, 2023.
8770 	+/
8771 	this(OperatingSystemFont font) {
8772 		if(font is null || font.loadedInfo is LoadedInfo.init)
8773 			loadDefault();
8774 		else
8775 			load(font.loadedInfo.tupleof);
8776 	}
8777 
8778 	/++
8779 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8780 
8781 		History:
8782 			Added November 13, 2020.
8783 	+/
8784 	version(with_xft)
8785 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8786 		unload();
8787 
8788 		if(!XftLibrary.attempted) {
8789 			XftLibrary.loadDynamicLibrary();
8790 		}
8791 
8792 		if(!XftLibrary.loadSuccessful)
8793 			return false;
8794 
8795 		auto display = XDisplayConnection.get;
8796 
8797 		char[256] nameBuffer = void;
8798 		int nbp = 0;
8799 
8800 		void add(in char[] a) {
8801 			nameBuffer[nbp .. nbp + a.length] = a[];
8802 			nbp += a.length;
8803 		}
8804 		add(name);
8805 
8806 		if(size) {
8807 			add(":size=");
8808 			add(toInternal!string(size));
8809 		}
8810 		if(weight != FontWeight.dontcare && weight != 400) {
8811 			if(weight < 400)
8812 				add(":style=Light");
8813 			else
8814 				add(":style=Bold");
8815 			add(":weight=");
8816 			add(weightToString(weight));
8817 		}
8818 		if(italic) {
8819 			if(weight == FontWeight.dontcare)
8820 				add(":style=Italic");
8821 			add(":slant=100");
8822 		}
8823 
8824 		nameBuffer[nbp] = 0;
8825 
8826 		this.xftFont = XftFontOpenName(
8827 			display,
8828 			DefaultScreen(display),
8829 			nameBuffer.ptr
8830 		);
8831 
8832 		this.isXft = true;
8833 
8834 		if(xftFont !is null) {
8835 			isMonospace_ = stringWidth("x") == stringWidth("M");
8836 			ascent_ = xftFont.ascent;
8837 			descent_ = xftFont.descent;
8838 		}
8839 
8840 		return !isNull();
8841 	}
8842 
8843 	/++
8844 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8845 
8846 
8847 		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.
8848 
8849 		If `pattern` is null, it returns all available font families.
8850 
8851 		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.
8852 
8853 		The format of the pattern is platform-specific.
8854 
8855 		History:
8856 			Added May 1, 2021 (dub v9.5)
8857 	+/
8858 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8859 		version(Windows) {
8860 			auto hdc = GetDC(null);
8861 			scope(exit) ReleaseDC(null, hdc);
8862 			LOGFONT logfont;
8863 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8864 				auto localHandler = *(cast(typeof(handler)*) p);
8865 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8866 			}
8867 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8868 		} else version(X11) {
8869 			//import core.stdc.stdio;
8870 			bool done = false;
8871 			version(with_xft) {
8872 				if(!XftLibrary.attempted) {
8873 					XftLibrary.loadDynamicLibrary();
8874 				}
8875 
8876 				if(!XftLibrary.loadSuccessful)
8877 					goto skipXft;
8878 
8879 				if(!FontConfigLibrary.attempted)
8880 					FontConfigLibrary.loadDynamicLibrary();
8881 				if(!FontConfigLibrary.loadSuccessful)
8882 					goto skipXft;
8883 
8884 				{
8885 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8886 					if(got is null)
8887 						goto skipXft;
8888 					scope(exit) FcFontSetDestroy(got);
8889 
8890 					auto fontPatterns = got.fonts[0 .. got.nfont];
8891 					foreach(candidate; fontPatterns) {
8892 						char* where, whereStyle;
8893 
8894 						char* pmg = FcNameUnparse(candidate);
8895 
8896 						//FcPatternGetString(candidate, "family", 0, &where);
8897 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8898 						//if(where && whereStyle) {
8899 						if(pmg) {
8900 							if(!handler(pmg.sliceCString))
8901 								return;
8902 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8903 						}
8904 					}
8905 				}
8906 			}
8907 
8908 			skipXft:
8909 
8910 			if(pattern is null)
8911 				pattern = "*";
8912 
8913 			int count;
8914 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8915 			scope(exit) XFreeFontNames(coreFontsRaw);
8916 
8917 			auto coreFonts = coreFontsRaw[0 .. count];
8918 
8919 			foreach(font; coreFonts) {
8920 				char[128] tmp;
8921 				tmp[0 ..5] = "core:";
8922 				auto cf = font.sliceCString;
8923 				if(5 + cf.length > tmp.length)
8924 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8925 				tmp[5 .. 5 + cf.length] = cf;
8926 				if(!handler(tmp[0 .. 5 + cf.length]))
8927 					return;
8928 			}
8929 		}
8930 	}
8931 
8932 	/++
8933 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8934 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8935 
8936 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8937 		underlying system doesn't support returning the raw bytes.
8938 
8939 		History:
8940 			Added September 10, 2021 (dub v10.3)
8941 	+/
8942 	ubyte[] getTtfBytes() {
8943 		if(isNull)
8944 			return null;
8945 
8946 		version(Windows) {
8947 			auto dc = GetDC(null);
8948 			auto orig = SelectObject(dc, font);
8949 
8950 			scope(exit) {
8951 				SelectObject(dc, orig);
8952 				ReleaseDC(null, dc);
8953 			}
8954 
8955 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8956 			if(res == GDI_ERROR)
8957 				return null;
8958 
8959 			ubyte[] buffer = new ubyte[](res);
8960 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8961 			if(res == GDI_ERROR)
8962 				return null; // wtf really tbh
8963 
8964 			return buffer;
8965 		} else version(with_xft) {
8966 			if(isXft && xftFont) {
8967 				if(!FontConfigLibrary.attempted)
8968 					FontConfigLibrary.loadDynamicLibrary();
8969 				if(!FontConfigLibrary.loadSuccessful)
8970 					return null;
8971 
8972 				char* file;
8973 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8974 					if (file !is null && file[0]) {
8975 						import core.stdc.stdio;
8976 						auto fp = fopen(file, "rb");
8977 						if(fp is null)
8978 							return null;
8979 						scope(exit)
8980 							fclose(fp);
8981 						fseek(fp, 0, SEEK_END);
8982 						ubyte[] buffer = new ubyte[](ftell(fp));
8983 						fseek(fp, 0, SEEK_SET);
8984 
8985 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8986 						if(got != buffer.length)
8987 							return null;
8988 
8989 						return buffer;
8990 					}
8991 				}
8992 			}
8993 			return null;
8994 		} else throw new NotYetImplementedException();
8995 	}
8996 
8997 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8998 
8999 	private string weightToString(FontWeight weight) {
9000 		with(FontWeight)
9001 		final switch(weight) {
9002 			case dontcare: return "*";
9003 			case thin: return "extralight";
9004 			case extralight: return "extralight";
9005 			case light: return "light";
9006 			case regular: return "regular";
9007 			case medium: return "medium";
9008 			case semibold: return "demibold";
9009 			case bold: return "bold";
9010 			case extrabold: return "demibold";
9011 			case heavy: return "black";
9012 		}
9013 	}
9014 
9015 	/++
9016 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
9017 
9018 		History:
9019 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9020 	+/
9021 	version(X11)
9022 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9023 		unload();
9024 
9025 		string xfontstr;
9026 
9027 		if(name.length > 3 && name[0 .. 3] == "-*-") {
9028 			// this is kinda a disgusting hack but if the user sends an exact
9029 			// string I'd like to honor it...
9030 			xfontstr = name;
9031 		} else {
9032 			string weightstr = weightToString(weight);
9033 			string sizestr;
9034 			if(size == 0)
9035 				sizestr = "*";
9036 			else
9037 				sizestr = toInternal!string(size);
9038 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
9039 		}
9040 
9041 		// writeln(xfontstr);
9042 
9043 		auto display = XDisplayConnection.get;
9044 
9045 		font = XLoadQueryFont(display, xfontstr.ptr);
9046 		if(font is null)
9047 			return false;
9048 
9049 		char** lol;
9050 		int lol2;
9051 		char* lol3;
9052 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
9053 
9054 		prepareFontInfo();
9055 
9056 		return !isNull();
9057 	}
9058 
9059 	version(X11)
9060 	private void prepareFontInfo() {
9061 		if(font !is null) {
9062 			isMonospace_ = stringWidth("l") == stringWidth("M");
9063 			ascent_ = font.max_bounds.ascent;
9064 			descent_ = font.max_bounds.descent;
9065 		}
9066 	}
9067 
9068 	version(OSXCocoa)
9069 	private void prepareFontInfo() {
9070 		if(font !is null) {
9071 			isMonospace_ = font.isFixedPitch;
9072 			ascent_ = cast(int) font.ascender;
9073 			descent_ = cast(int) - font.descender;
9074 		}
9075 	}
9076 
9077 
9078 	/++
9079 		Loads a Windows font. You probably want to use [load] instead to be more generic.
9080 
9081 		History:
9082 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9083 	+/
9084 	version(Windows)
9085 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
9086 		unload();
9087 
9088 		WCharzBuffer buffer = WCharzBuffer(name);
9089 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
9090 
9091 		prepareFontInfo(hdc);
9092 
9093 		return !isNull();
9094 	}
9095 
9096 	version(Windows)
9097 	void prepareFontInfo(HDC hdc = null) {
9098 		if(font is null)
9099 			return;
9100 
9101 		TEXTMETRIC tm;
9102 		auto dc = hdc ? hdc : GetDC(null);
9103 		auto orig = SelectObject(dc, font);
9104 		GetTextMetrics(dc, &tm);
9105 		SelectObject(dc, orig);
9106 		if(hdc is null)
9107 			ReleaseDC(null, dc);
9108 
9109 		width_ = tm.tmAveCharWidth;
9110 		height_ = tm.tmHeight;
9111 		ascent_ = tm.tmAscent;
9112 		descent_ = tm.tmDescent;
9113 		// 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.
9114 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
9115 	}
9116 
9117 
9118 	/++
9119 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
9120 
9121 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
9122 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
9123 
9124 		On Windows, it forwards directly to [loadWin32].
9125 
9126 		Params:
9127 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
9128 			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.
9129 			weight = approximate boldness, results may vary.
9130 			italic = try to get a slanted version of the given font.
9131 
9132 		History:
9133 			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.
9134 	+/
9135 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9136 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
9137 		version(X11) {
9138 			version(with_xft) {
9139 				if(name.length > 5 && name[0 .. 5] == "core:") {
9140 					goto core;
9141 				}
9142 
9143 				if(loadXft(name, size, weight, italic))
9144 					return true;
9145 				// if xft fails, fallback to core to avoid breaking
9146 				// code that already depended on this.
9147 			}
9148 
9149 			core:
9150 
9151 			if(name.length > 5 && name[0 .. 5] == "core:") {
9152 				name = name[5 .. $];
9153 			}
9154 
9155 			return loadCoreX(name, size, weight, italic);
9156 		} else version(Windows) {
9157 			return loadWin32(name, size, weight, italic);
9158 		} else version(OSXCocoa) {
9159 			return loadCocoa(name, size, weight, italic);
9160 		} else static assert(0);
9161 	}
9162 
9163 	version(OSXCocoa)
9164 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
9165 		unload();
9166 
9167 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
9168 		prepareFontInfo();
9169 
9170 		return !isNull();
9171 	}
9172 
9173 	private struct LoadedInfo {
9174 		string name;
9175 		int size;
9176 		FontWeight weight;
9177 		bool italic;
9178 	}
9179 	private LoadedInfo loadedInfo;
9180 
9181 	///
9182 	void unload() {
9183 		if(isNull())
9184 			return;
9185 
9186 		version(X11) {
9187 			auto display = XDisplayConnection.display;
9188 
9189 			if(display is null)
9190 				return;
9191 
9192 			version(with_xft) {
9193 				if(isXft) {
9194 					if(xftFont)
9195 						XftFontClose(display, xftFont);
9196 					isXft = false;
9197 					xftFont = null;
9198 					return;
9199 				}
9200 			}
9201 
9202 			if(font && font !is ScreenPainterImplementation.defaultfont)
9203 				XFreeFont(display, font);
9204 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
9205 				XFreeFontSet(display, fontset);
9206 
9207 			font = null;
9208 			fontset = null;
9209 		} else version(Windows) {
9210 			DeleteObject(font);
9211 			font = null;
9212 		} else version(OSXCocoa) {
9213 			font.release();
9214 			font = null;
9215 		} else static assert(0);
9216 	}
9217 
9218 	private bool isMonospace_;
9219 
9220 	/++
9221 		History:
9222 			Added January 16, 2021
9223 	+/
9224 	bool isMonospace() {
9225 		return isMonospace_;
9226 	}
9227 
9228 	/++
9229 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
9230 
9231 		History:
9232 			Added March 26, 2020
9233 			Documented January 16, 2021
9234 	+/
9235 	int averageWidth() {
9236 		version(X11) {
9237 			return stringWidth("x");
9238 		} version(OSXCocoa) {
9239 			return stringWidth("x");
9240 		} else version(Windows)
9241 			return width_;
9242 		else assert(0);
9243 	}
9244 
9245 	/++
9246 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
9247 
9248 		History:
9249 			Added January 16, 2021
9250 	+/
9251 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
9252 	// FIXME: what about tab?
9253 		if(isNull)
9254 			return 0;
9255 
9256 		version(X11) {
9257 			version(with_xft)
9258 				if(isXft && xftFont !is null) {
9259 					//return xftFont.max_advance_width;
9260 					XGlyphInfo extents;
9261 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
9262 					// writeln(extents);
9263 					return extents.xOff;
9264 				}
9265 			if(font is null)
9266 				return 0;
9267 			else if(fontset) {
9268 				XRectangle rect;
9269 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
9270 
9271 				return rect.width;
9272 			} else {
9273 				return XTextWidth(font, s.ptr, cast(int) s.length);
9274 			}
9275 		} else version(Windows) {
9276 			WCharzBuffer buffer = WCharzBuffer(s);
9277 
9278 			return stringWidth(buffer.slice, window);
9279 		} else version(OSXCocoa) {
9280 			/+
9281 			int charCount = [string length];
9282 			CGGlyph glyphs[charCount];
9283 			CGRect rects[charCount];
9284 
9285 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
9286 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
9287 
9288 			int totalwidth = 0, maxheight = 0;
9289 			for (int i=0; i < charCount; i++)
9290 			{
9291 				totalwidth += rects[i].size.width;
9292 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
9293 			}
9294 
9295 			dim = CGSizeMake(totalwidth, maxheight);
9296 			+/
9297 
9298 			return 16; // FIXME
9299 		}
9300 		else assert(0);
9301 	}
9302 
9303 	version(Windows)
9304 	/// ditto
9305 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
9306 		if(isNull)
9307 			return 0;
9308 		version(Windows) {
9309 			SIZE size;
9310 
9311 			prepareContext(window);
9312 			scope(exit) releaseContext();
9313 
9314 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
9315 
9316 			return size.cx;
9317 		} else {
9318 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
9319 			static assert(0, "not implemented yet");
9320 			//return stringWidth(s, window);
9321 		}
9322 	}
9323 
9324 	private {
9325 		int prepRefcount;
9326 
9327 		version(Windows) {
9328 			HDC dc;
9329 			HANDLE orig;
9330 			HWND hwnd;
9331 		}
9332 	}
9333 	/++
9334 		[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.
9335 
9336 		History:
9337 			Added January 23, 2021
9338 	+/
9339 	void prepareContext(SimpleWindow window = null) {
9340 		prepRefcount++;
9341 		if(prepRefcount == 1) {
9342 			version(Windows) {
9343 				hwnd = window is null ? null : window.impl.hwnd;
9344 				dc = GetDC(hwnd);
9345 				orig = SelectObject(dc, font);
9346 			}
9347 		}
9348 	}
9349 	/// ditto
9350 	void releaseContext() {
9351 		prepRefcount--;
9352 		if(prepRefcount == 0) {
9353 			version(Windows) {
9354 				SelectObject(dc, orig);
9355 				ReleaseDC(hwnd, dc);
9356 				hwnd = null;
9357 				dc = null;
9358 				orig = null;
9359 			}
9360 		}
9361 	}
9362 
9363 	/+
9364 		FIXME: I think I need advance and kerning pair
9365 
9366 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
9367 	+/
9368 
9369 	/++
9370 		Returns the height of the font.
9371 
9372 		History:
9373 			Added March 26, 2020
9374 			Documented January 16, 2021
9375 	+/
9376 	int height() {
9377 		version(X11) {
9378 			version(with_xft)
9379 				if(isXft && xftFont !is null) {
9380 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
9381 				}
9382 			if(font is null)
9383 				return 0;
9384 			return font.max_bounds.ascent + font.max_bounds.descent;
9385 		} else version(Windows) {
9386 			return height_;
9387 		} else version(OSXCocoa) {
9388 			if(font is null)
9389 				return 0;
9390 			return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight
9391 		}
9392 		else assert(0);
9393 	}
9394 
9395 	private int ascent_;
9396 	private int descent_;
9397 
9398 	/++
9399 		Max ascent above the baseline.
9400 
9401 		History:
9402 			Added January 22, 2021
9403 	+/
9404 	int ascent() {
9405 		return ascent_;
9406 	}
9407 
9408 	/++
9409 		Max descent below the baseline.
9410 
9411 		History:
9412 			Added January 22, 2021
9413 	+/
9414 	int descent() {
9415 		return descent_;
9416 	}
9417 
9418 	/++
9419 		Loads the default font used by [ScreenPainter] if none others are loaded.
9420 
9421 		Returns:
9422 			This method mutates the `this` object, but then returns `this` for
9423 			easy chaining like:
9424 
9425 			---
9426 			auto font = foo.isNull ? foo : foo.loadDefault
9427 			---
9428 
9429 		History:
9430 			Added previously, but left unimplemented until January 24, 2021.
9431 	+/
9432 	OperatingSystemFont loadDefault() {
9433 		unload();
9434 
9435 		loadedInfo = LoadedInfo.init;
9436 
9437 		version(X11) {
9438 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9439 			// but meh since sdpy does its own thing, this should be ok too
9440 
9441 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9442 			this.font = ScreenPainterImplementation.defaultfont;
9443 			this.fontset = ScreenPainterImplementation.defaultfontset;
9444 
9445 			prepareFontInfo();
9446 			return this;
9447 		} else version(Windows) {
9448 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9449 			this.font = ScreenPainterImplementation.defaultGuiFont;
9450 
9451 			prepareFontInfo();
9452 			return this;
9453 		} else version(OSXCocoa) {
9454 			this.font = NSFont.systemFontOfSize(15);
9455 
9456 			prepareFontInfo();
9457 
9458 			// import std.stdio; writeln("Load default: ", this.height());
9459 			return this;
9460 		} else throw new NotYetImplementedException();
9461 	}
9462 
9463 	///
9464 	bool isNull() {
9465 		version(with_xft)
9466 			if(isXft)
9467 				return xftFont is null;
9468 		return font is null;
9469 	}
9470 
9471 	/* Metrics */
9472 	/+
9473 		GetABCWidth
9474 		GetKerningPairs
9475 
9476 		if I do it right, I can size it all here, and match
9477 		what happens when I draw the full string with the OS functions.
9478 
9479 		subclasses might do the same thing while getting the glyphs on images
9480 	struct GlyphInfo {
9481 		int glyph;
9482 
9483 		size_t stringIdxStart;
9484 		size_t stringIdxEnd;
9485 
9486 		Rectangle boundingBox;
9487 	}
9488 	GlyphInfo[] getCharBoxes() {
9489 		// XftTextExtentsUtf8
9490 		return null;
9491 
9492 	}
9493 	+/
9494 
9495 	~this() {
9496 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9497 		unload();
9498 	}
9499 }
9500 
9501 version(Windows)
9502 private string sliceCString(const(wchar)[] w) {
9503 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9504 }
9505 
9506 private inout(char)[] sliceCString(inout(char)* s) {
9507 	import core.stdc.string;
9508 	auto len = strlen(s);
9509 	return s[0 .. len];
9510 }
9511 
9512 version(OSXCocoa)
9513 	alias PaintingHandle = NSObject;
9514 else
9515 	alias PaintingHandle = NativeWindowHandle;
9516 
9517 /**
9518 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9519 	than constructing it directly. Then, it is reference counted so you can pass it
9520 	at around and when the last ref goes out of scope, the buffered drawing activities
9521 	are all carried out.
9522 
9523 
9524 	Most functions use the outlineColor instead of taking a color themselves.
9525 	ScreenPainter is reference counted and draws its buffer to the screen when its
9526 	final reference goes out of scope.
9527 */
9528 struct ScreenPainter {
9529 	CapableOfBeingDrawnUpon window;
9530 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9531 		this.window = window;
9532 		if(window.closed)
9533 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9534 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9535 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9536 		if(window.activeScreenPainter !is null) {
9537 			impl = window.activeScreenPainter;
9538 			if(impl.referenceCount == 0) {
9539 				impl.window = window;
9540 				impl.create(handle);
9541 			}
9542 			impl.manualInvalidations = manualInvalidations;
9543 			impl.referenceCount++;
9544 		//	writeln("refcount ++ ", impl.referenceCount);
9545 		} else {
9546 			impl = new ScreenPainterImplementation;
9547 			impl.window = window;
9548 			impl.create(handle);
9549 			impl.referenceCount = 1;
9550 			impl.manualInvalidations = manualInvalidations;
9551 			window.activeScreenPainter = impl;
9552 			// writeln("constructed");
9553 		}
9554 
9555 		copyActiveOriginals();
9556 	}
9557 
9558 	/++
9559 		EXPERIMENTAL. subject to change.
9560 
9561 		When you draw a cursor, you can draw this to notify your window of where it is,
9562 		for IME systems to use.
9563 	+/
9564 	void notifyCursorPosition(int x, int y, int width, int height) {
9565 		if(auto w = cast(SimpleWindow) window) {
9566 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9567 		}
9568 	}
9569 
9570 	/++
9571 		If you are using manual invalidations, this informs the
9572 		window system that a section needs to be redrawn.
9573 
9574 		If you didn't opt into manual invalidation, you don't
9575 		have to call this.
9576 
9577 		History:
9578 			Added December 30, 2021 (dub v10.5)
9579 	+/
9580 	void invalidateRect(Rectangle rect) {
9581 		if(impl is null) return;
9582 
9583 		// transform(rect)
9584 		rect.left += _originX;
9585 		rect.right += _originX;
9586 		rect.top += _originY;
9587 		rect.bottom += _originY;
9588 
9589 		impl.invalidateRect(rect);
9590 	}
9591 
9592 	private Pen originalPen;
9593 	private Color originalFillColor;
9594 	private arsd.color.Rectangle originalClipRectangle;
9595 	private OperatingSystemFont originalFont;
9596 	void copyActiveOriginals() {
9597 		if(impl is null) return;
9598 		originalPen = impl._activePen;
9599 		originalFillColor = impl._fillColor;
9600 		originalClipRectangle = impl._clipRectangle;
9601 		version(OSXCocoa) {} else
9602 		originalFont = impl._activeFont;
9603 	}
9604 
9605 	~this() {
9606 		if(impl is null) return;
9607 		impl.referenceCount--;
9608 		//writeln("refcount -- ", impl.referenceCount);
9609 		if(impl.referenceCount == 0) {
9610 			// writeln("destructed");
9611 			impl.dispose();
9612 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9613 			// writeln("paint finished");
9614 		} else {
9615 			// there is still an active reference, reset stuff so the
9616 			// next user doesn't get weirdness via the reference
9617 			this.rasterOp = RasterOp.normal;
9618 			pen = originalPen;
9619 			fillColor = originalFillColor;
9620 			if(originalFont)
9621 				setFont(originalFont);
9622 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9623 		}
9624 	}
9625 
9626 	this(this) {
9627 		if(impl is null) return;
9628 		impl.referenceCount++;
9629 		//writeln("refcount ++ ", impl.referenceCount);
9630 
9631 		copyActiveOriginals();
9632 	}
9633 
9634 	private int _originX;
9635 	private int _originY;
9636 	@property int originX() { return _originX; }
9637 	@property int originY() { return _originY; }
9638 	@property int originX(int a) {
9639 		_originX = a;
9640 		return _originX;
9641 	}
9642 	@property int originY(int a) {
9643 		_originY = a;
9644 		return _originY;
9645 	}
9646 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9647 	private void transform(ref Point p) {
9648 		if(impl is null) return;
9649 		p.x += _originX;
9650 		p.y += _originY;
9651 	}
9652 
9653 	// this needs to be checked BEFORE the originX/Y transformation
9654 	private bool isClipped(Point p) {
9655 		return !currentClipRectangle.contains(p);
9656 	}
9657 	private bool isClipped(Point p, int width, int height) {
9658 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9659 	}
9660 	private bool isClipped(Point p, Size s) {
9661 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9662 	}
9663 	private bool isClipped(Point p, Point p2) {
9664 		// need to ensure the end points are actually included inside, so the +1 does that
9665 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9666 	}
9667 
9668 
9669 	/++
9670 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9671 
9672 		Returns:
9673 			The old clip rectangle.
9674 
9675 		History:
9676 			Return value was `void` prior to May 10, 2021.
9677 
9678 	+/
9679 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9680 		if(impl is null) return currentClipRectangle;
9681 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9682 			return currentClipRectangle; // no need to do anything
9683 		auto old = currentClipRectangle;
9684 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9685 		transform(pt);
9686 
9687 		impl.setClipRectangle(pt.x, pt.y, width, height);
9688 
9689 		return old;
9690 	}
9691 
9692 	/// ditto
9693 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9694 		if(impl is null) return currentClipRectangle;
9695 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9696 	}
9697 
9698 	///
9699 	void setFont(OperatingSystemFont font) {
9700 		if(impl is null) return;
9701 		impl.setFont(font);
9702 	}
9703 
9704 	///
9705 	int fontHeight() {
9706 		if(impl is null) return 0;
9707 		return impl.fontHeight();
9708 	}
9709 
9710 	private Pen activePen;
9711 
9712 	///
9713 	@property void pen(Pen p) {
9714 		if(impl is null) return;
9715 		activePen = p;
9716 		impl.pen(p);
9717 	}
9718 
9719 	///
9720 	@scriptable
9721 	@property void outlineColor(Color c) {
9722 		if(impl is null) return;
9723 		if(activePen.color == c)
9724 			return;
9725 		activePen.color = c;
9726 		impl.pen(activePen);
9727 	}
9728 
9729 	///
9730 	@scriptable
9731 	@property void fillColor(Color c) {
9732 		if(impl is null) return;
9733 		impl.fillColor(c);
9734 	}
9735 
9736 	///
9737 	@property void rasterOp(RasterOp op) {
9738 		if(impl is null) return;
9739 		impl.rasterOp(op);
9740 	}
9741 
9742 
9743 	void updateDisplay() {
9744 		// FIXME this should do what the dtor does
9745 	}
9746 
9747 	/// 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)
9748 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9749 		if(impl is null) return;
9750 		if(isClipped(upperLeft, width, height)) return;
9751 		transform(upperLeft);
9752 		version(Windows) {
9753 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9754 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9755 			RECT clip = scroll;
9756 			RECT uncovered;
9757 			HRGN hrgn;
9758 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9759 				throw new WindowsApiException("ScrollDC", GetLastError());
9760 
9761 		} else version(X11) {
9762 			// FIXME: clip stuff outside this rectangle
9763 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9764 		} else version(OSXCocoa) {
9765 			throw new NotYetImplementedException();
9766 		} else static assert(0);
9767 	}
9768 
9769 	///
9770 	void clear(Color color = Color.white()) {
9771 		if(impl is null) return;
9772 		fillColor = color;
9773 		outlineColor = color;
9774 		drawRectangle(Point(0, 0), window.width, window.height);
9775 	}
9776 
9777 	/++
9778 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9779 
9780 		Params:
9781 			upperLeft = point on the window where the upper left corner of the image will be drawn
9782 			imageUpperLeft = point on the image to start the slice to draw
9783 			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.
9784 		History:
9785 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9786 	+/
9787 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9788 		if(impl is null) return;
9789 		if(isClipped(upperLeft, s.width, s.height)) return;
9790 		transform(upperLeft);
9791 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9792 	}
9793 
9794 	///
9795 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9796 		if(impl is null) return;
9797 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9798 		transform(upperLeft);
9799 		if(w == 0 || w > i.width)
9800 			w = i.width;
9801 		if(h == 0 || h > i.height)
9802 			h = i.height;
9803 		if(upperLeftOfImage.x < 0)
9804 			upperLeftOfImage.x = 0;
9805 		if(upperLeftOfImage.y < 0)
9806 			upperLeftOfImage.y = 0;
9807 
9808 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9809 	}
9810 
9811 	///
9812 	Size textSize(in char[] text) {
9813 		if(impl is null) return Size(0, 0);
9814 		return impl.textSize(text);
9815 	}
9816 
9817 	/++
9818 		Draws a string in the window with the set font (see [setFont] to change it).
9819 
9820 		Params:
9821 			upperLeft = the upper left point of the bounding box of the text
9822 			text = the string to draw
9823 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9824 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9825 	+/
9826 	@scriptable
9827 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9828 		if(impl is null) return;
9829 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9830 			if(isClipped(upperLeft, lowerRight)) return;
9831 			transform(lowerRight);
9832 		} else {
9833 			if(isClipped(upperLeft, textSize(text))) return;
9834 		}
9835 		transform(upperLeft);
9836 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9837 	}
9838 
9839 	/++
9840 		Draws text using a custom font.
9841 
9842 		This is still MAJOR work in progress.
9843 
9844 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9845 	+/
9846 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9847 		if(impl is null) return;
9848 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9849 		transform(upperLeft);
9850 		font.drawString(this, upperLeft, text);
9851 	}
9852 
9853 	version(Windows)
9854 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9855 		if(impl is null) return;
9856 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9857 		transform(upperLeft);
9858 
9859 		if(text.length && text[$-1] == '\n')
9860 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9861 
9862 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9863 	}
9864 
9865 	static struct TextDrawingContext {
9866 		Point boundingBoxUpperLeft;
9867 		Point boundingBoxLowerRight;
9868 
9869 		Point currentLocation;
9870 
9871 		Point lastDrewUpperLeft;
9872 		Point lastDrewLowerRight;
9873 
9874 		// how do i do right aligned rich text?
9875 		// i kinda want to do a pre-made drawing then right align
9876 		// draw the whole block.
9877 		//
9878 		// That's exactly the diff: inline vs block stuff.
9879 
9880 		// I need to get coordinates of an inline section out too,
9881 		// not just a bounding box, but a series of bounding boxes
9882 		// should be ok. Consider what's needed to detect a click
9883 		// on a link in the middle of a paragraph breaking a line.
9884 		//
9885 		// Generally, we should be able to get the rectangles of
9886 		// any portion we draw.
9887 		//
9888 		// It also needs to tell what text is left if it overflows
9889 		// out of the box, so we can do stuff like float images around
9890 		// it. It should not attempt to draw a letter that would be
9891 		// clipped.
9892 		//
9893 		// I might also turn off word wrap stuff.
9894 	}
9895 
9896 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9897 		if(impl is null) return;
9898 		// FIXME
9899 	}
9900 
9901 	/// Drawing an individual pixel is slow. Avoid it if possible.
9902 	void drawPixel(Point where) {
9903 		if(impl is null) return;
9904 		if(isClipped(where)) return;
9905 		transform(where);
9906 		impl.drawPixel(where.x, where.y);
9907 	}
9908 
9909 
9910 	/// Draws a pen using the current pen / outlineColor
9911 	@scriptable
9912 	void drawLine(Point starting, Point ending) {
9913 		if(impl is null) return;
9914 		if(isClipped(starting, ending)) return;
9915 		transform(starting);
9916 		transform(ending);
9917 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9918 	}
9919 
9920 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9921 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9922 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9923 	@scriptable
9924 	void drawRectangle(Point upperLeft, int width, int height) {
9925 		if(impl is null) return;
9926 		if(isClipped(upperLeft, width, height)) return;
9927 		transform(upperLeft);
9928 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9929 	}
9930 
9931 	/// ditto
9932 	void drawRectangle(Point upperLeft, Size size) {
9933 		if(impl is null) return;
9934 		if(isClipped(upperLeft, size.width, size.height)) return;
9935 		transform(upperLeft);
9936 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9937 	}
9938 
9939 	/// ditto
9940 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9941 		if(impl is null) return;
9942 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9943 		transform(upperLeft);
9944 		transform(lowerRightInclusive);
9945 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9946 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9947 	}
9948 
9949 	// overload added on May 12, 2021
9950 	/// ditto
9951 	void drawRectangle(Rectangle rect) {
9952 		drawRectangle(rect.upperLeft, rect.size);
9953 	}
9954 
9955 	/// Arguments are the points of the bounding rectangle
9956 	void drawEllipse(Point upperLeft, Point lowerRight) {
9957 		if(impl is null) return;
9958 		if(isClipped(upperLeft, lowerRight)) return;
9959 		transform(upperLeft);
9960 		transform(lowerRight);
9961 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9962 	}
9963 
9964 	/++
9965 		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.
9966 
9967 
9968 		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.
9969 
9970 		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.
9971 
9972 		Bugs:
9973 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9974 
9975 			The arc outline on Linux sometimes goes over the target.
9976 
9977 			The fill on Windows sometimes stops short.
9978 
9979 		History:
9980 			This function was broken af, totally inconsistent on platforms until September 24, 2021.
9981 
9982 			The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024.
9983 	+/
9984 	void drawArc(Point upperLeft, int width, int height, int start, int length) {
9985 		if(impl is null) return;
9986 		// FIXME: not actually implemented
9987 		if(isClipped(upperLeft, width, height)) return;
9988 		transform(upperLeft);
9989 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length);
9990 	}
9991 
9992 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9993 	void drawCircle(Point upperLeft, int diameter) {
9994 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9995 	}
9996 
9997 	/++
9998 		Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush.
9999 
10000 
10001 		Bugs:
10002 			Not implemented on Mac; it will instead draw a non-rounded rectangle for now.
10003 
10004 		History:
10005 			Added August 3, 2024
10006 	+/
10007 	void drawRectangleRounded(Rectangle rect, int borderRadius) {
10008 		drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius);
10009 	}
10010 
10011 	/// ditto
10012 	void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) {
10013 		drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius);
10014 	}
10015 
10016 	/// ditto
10017 	void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
10018 		if(borderRadius <= 0) {
10019 			drawRectangle(upperLeft, lowerRight);
10020 			return;
10021 		}
10022 
10023 		transform(upperLeft);
10024 		transform(lowerRight);
10025 
10026 		impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius);
10027 	}
10028 
10029 	/// .
10030 	void drawPolygon(Point[] vertexes) {
10031 		if(impl is null) return;
10032 		assert(vertexes.length);
10033 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
10034 		foreach(ref vertex; vertexes) {
10035 			if(vertex.x < minX)
10036 				minX = vertex.x;
10037 			if(vertex.y < minY)
10038 				minY = vertex.y;
10039 			if(vertex.x > maxX)
10040 				maxX = vertex.x;
10041 			if(vertex.y > maxY)
10042 				maxY = vertex.y;
10043 			transform(vertex);
10044 		}
10045 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
10046 		impl.drawPolygon(vertexes);
10047 	}
10048 
10049 	/// ditto
10050 	void drawPolygon(Point[] vertexes...) {
10051 		if(impl is null) return;
10052 		drawPolygon(vertexes);
10053 	}
10054 
10055 
10056 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
10057 
10058 	//mixin NativeScreenPainterImplementation!() impl;
10059 
10060 
10061 	// HACK: if I mixin the impl directly, it won't let me override the copy
10062 	// constructor! The linker complains about there being multiple definitions.
10063 	// I'll make the best of it and reference count it though.
10064 	ScreenPainterImplementation* impl;
10065 }
10066 
10067 	// HACK: I need a pointer to the implementation so it's separate
10068 	struct ScreenPainterImplementation {
10069 		CapableOfBeingDrawnUpon window;
10070 		int referenceCount;
10071 		mixin NativeScreenPainterImplementation!();
10072 	}
10073 
10074 // FIXME: i haven't actually tested the sprite class on MS Windows
10075 
10076 /**
10077 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
10078 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
10079 
10080 
10081 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
10082 	though I'm not sure that's ideal and the implementation might change.
10083 
10084 	You create one by giving a window and an image. It optimizes for that window,
10085 	and copies the image into it to use as the initial picture. Creating a sprite
10086 	can be quite slow (especially over a network connection) so you should do it
10087 	as little as possible and just hold on to your sprite handles after making them.
10088 	simpledisplay does try to do its best though, using the XSHM extension if available,
10089 	but you should still write your code as if it will always be slow.
10090 
10091 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
10092 	a fast operation - much faster than drawing the Image itself every time.
10093 
10094 	`Sprite` represents a scarce resource which should be freed when you
10095 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
10096 	after it has been disposed. If you are unsure about this, don't take chances,
10097 	just let the garbage collector do it for you. But ideally, you can manage its
10098 	lifetime more efficiently.
10099 
10100 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
10101 	support alpha blending in its drawing at this time. That might change in the
10102 	future, but if you need alpha blending right now, use OpenGL instead. See
10103 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
10104 
10105 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
10106 	in by setting the enableAlpha = true in the constructor.
10107 */
10108 class Sprite : CapableOfBeingDrawnUpon {
10109 
10110 	///
10111 	ScreenPainter draw() {
10112 		return ScreenPainter(this, handle, false);
10113 	}
10114 
10115 	/++
10116 		Copies the sprite's current state into a [TrueColorImage].
10117 
10118 		Be warned: this can be a very slow operation
10119 
10120 		History:
10121 			Actually implemented on March 14, 2021
10122 	+/
10123 	TrueColorImage takeScreenshot() {
10124 		return trueColorImageFromNativeHandle(handle, width, height);
10125 	}
10126 
10127 	void delegate() paintingFinishedDg() { return null; }
10128 	bool closed() { return false; }
10129 	ScreenPainterImplementation* activeScreenPainter_;
10130 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
10131 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
10132 
10133 	version(Windows)
10134 		private ubyte* rawData;
10135 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
10136 	// ditto on the XPicture stuff
10137 
10138 	version(X11) {
10139 		private static XRenderPictFormat* RGB24;
10140 		private static XRenderPictFormat* ARGB32;
10141 
10142 		private Picture xrenderPicture;
10143 	}
10144 
10145 	version(X11)
10146 	private static void requireXRender() {
10147 		if(!XRenderLibrary.loadAttempted) {
10148 			XRenderLibrary.loadDynamicLibrary();
10149 		}
10150 
10151 		if(!XRenderLibrary.loadSuccessful)
10152 			throw new Exception("XRender library load failure");
10153 
10154 		auto display = XDisplayConnection.get;
10155 
10156 		// FIXME: if we migrate X displays, these need to be changed
10157 		if(RGB24 is null)
10158 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
10159 		if(ARGB32 is null)
10160 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
10161 	}
10162 
10163 	protected this() {}
10164 
10165 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
10166 		this._width = width;
10167 		this._height = height;
10168 		this.enableAlpha = enableAlpha;
10169 
10170 		version(X11) {
10171 			auto display = XDisplayConnection.get();
10172 
10173 			if(enableAlpha) {
10174 				requireXRender();
10175 			}
10176 
10177 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
10178 
10179 			if(enableAlpha) {
10180 				XRenderPictureAttributes attrs;
10181 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
10182 			}
10183 		} else version(Windows) {
10184 			version(CRuntime_DigitalMars) {
10185 				//if(enableAlpha)
10186 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
10187 			}
10188 
10189 			BITMAPINFO infoheader;
10190 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10191 			infoheader.bmiHeader.biWidth = width;
10192 			infoheader.bmiHeader.biHeight = height;
10193 			infoheader.bmiHeader.biPlanes = 1;
10194 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
10195 			infoheader.bmiHeader.biCompression = BI_RGB;
10196 
10197 			// FIXME: this should prolly be a device dependent bitmap...
10198 			handle = CreateDIBSection(
10199 				null,
10200 				&infoheader,
10201 				DIB_RGB_COLORS,
10202 				cast(void**) &rawData,
10203 				null,
10204 				0);
10205 
10206 			if(handle is null)
10207 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
10208 		}
10209 	}
10210 
10211 	/// Makes a sprite based on the image with the initial contents from the Image
10212 	this(SimpleWindow win, Image i) {
10213 		this(win, i.width, i.height, i.enableAlpha);
10214 
10215 		version(X11) {
10216 			auto display = XDisplayConnection.get();
10217 			auto gc = XCreateGC(display, this.handle, 0, null);
10218 			scope(exit) XFreeGC(display, gc);
10219 			if(i.usingXshm)
10220 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
10221 			else
10222 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
10223 		} else version(Windows) {
10224 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
10225 			auto arrLength = itemsPerLine * height;
10226 			rawData[0..arrLength] = i.rawData[0..arrLength];
10227 		} else version(OSXCocoa) {
10228 			// FIXME: I have no idea if this is even any good
10229 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
10230 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
10231 				colorSpace,
10232 				kCGImageAlphaPremultipliedLast
10233 				|kCGBitmapByteOrder32Big);
10234 			CGColorSpaceRelease(colorSpace);
10235 			auto rawData = CGBitmapContextGetData(handle);
10236 
10237 			auto rdl = (width * height * 4);
10238 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
10239 		} else static assert(0);
10240 	}
10241 
10242 	/++
10243 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
10244 
10245 		Params:
10246 			where = point on the window where the upper left corner of the image will be drawn
10247 			imageUpperLeft = point on the image to start the slice to draw
10248 			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.
10249 		History:
10250 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
10251 	+/
10252 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
10253 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
10254 	}
10255 
10256 	/// Call this when you're ready to get rid of it
10257 	void dispose() {
10258 		version(X11) {
10259 			staticDispose(xrenderPicture, handle);
10260 			xrenderPicture = None;
10261 			handle = None;
10262 		} else version(Windows) {
10263 			staticDispose(handle);
10264 			handle = null;
10265 		} else version(OSXCocoa) {
10266 			staticDispose(handle);
10267 			handle = null;
10268 		} else static assert(0);
10269 
10270 	}
10271 
10272 	version(X11)
10273 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
10274 		if(xrenderPicture)
10275 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
10276 		if(handle)
10277 			XFreePixmap(XDisplayConnection.get(), handle);
10278 	}
10279 	else version(Windows)
10280 	static void staticDispose(HBITMAP handle) {
10281 		if(handle)
10282 			DeleteObject(handle);
10283 	}
10284 	else version(OSXCocoa)
10285 	static void staticDispose(CGContextRef context) {
10286 		if(context)
10287 			CGContextRelease(context);
10288 	}
10289 
10290 	~this() {
10291 		version(X11) { if(xrenderPicture || handle)
10292 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
10293 		} else version(Windows) { if(handle)
10294 			cleanupQueue.queue!staticDispose(handle);
10295 		} else version(OSXCocoa) { if(handle)
10296 			cleanupQueue.queue!staticDispose(handle);
10297 		} else static assert(0);
10298 	}
10299 
10300 	///
10301 	final @property int width() { return _width; }
10302 
10303 	///
10304 	final @property int height() { return _height; }
10305 
10306 	///
10307 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
10308 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
10309 	}
10310 
10311 	auto nativeHandle() {
10312 		return handle;
10313 	}
10314 
10315 	private:
10316 
10317 	int _width;
10318 	int _height;
10319 	bool enableAlpha;
10320 	version(X11)
10321 		Pixmap handle;
10322 	else version(Windows)
10323 		HBITMAP handle;
10324 	else version(OSXCocoa)
10325 		CGContextRef handle;
10326 	else static assert(0);
10327 }
10328 
10329 /++
10330 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
10331 
10332 	History:
10333 		Added November 20, 2021 (dub v10.4)
10334 +/
10335 version(OSXCocoa) {} else // NotYetImplementedException
10336 abstract class Gradient : Sprite {
10337 	protected this(int w, int h) {
10338 		version(X11) {
10339 			Sprite.requireXRender();
10340 
10341 			super();
10342 			enableAlpha = true;
10343 			_width = w;
10344 			_height = h;
10345 		} else version(Windows) {
10346 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
10347 		}
10348 	}
10349 
10350 	version(Windows)
10351 	final void forEachPixel(scope Color delegate(int x, int y) dg) @system {
10352 		auto ptr = rawData;
10353 		foreach(j; 0 .. _height)
10354 		foreach(i; 0 .. _width) {
10355 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
10356 			*rawData = (color.a * color.b) / 255; rawData++;
10357 			*rawData = (color.a * color.g) / 255; rawData++;
10358 			*rawData = (color.a * color.r) / 255; rawData++;
10359 			*rawData = color.a; rawData++;
10360 		}
10361 	}
10362 
10363 	version(X11)
10364 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
10365 		assert(stops.length > 0);
10366 		assert(stops.length <= 16, "I got lazy with buffers");
10367 
10368 		XFixed[16] stopsPositions = void;
10369 		XRenderColor[16] colors = void;
10370 
10371 		foreach(idx, stop; stops) {
10372 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
10373 			auto c = stop.c;
10374 			colors[idx] = XRenderColor(
10375 				cast(ushort)(c.r * ushort.max / 255),
10376 				cast(ushort)(c.g * ushort.max / 255),
10377 				cast(ushort)(c.b * ushort.max / 255),
10378 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
10379 			);
10380 		}
10381 
10382 		xrenderPicture = dg(stopsPositions, colors);
10383 	}
10384 
10385 	///
10386 	static struct Stop {
10387 		float percentage; /// between 0 and 1.0
10388 		Color c;
10389 	}
10390 }
10391 
10392 /++
10393 	Creates a linear gradient between p1 and p2.
10394 
10395 	X ONLY RIGHT NOW
10396 
10397 	History:
10398 		Added November 20, 2021 (dub v10.4)
10399 
10400 	Bugs:
10401 		Not yet implemented on Windows.
10402 +/
10403 version(OSXCocoa) {} else // NotYetImplementedException
10404 class LinearGradient : Gradient {
10405 	/++
10406 
10407 	+/
10408 	this(Point p1, Point p2, Stop[] stops...) {
10409 		super(p2.x, p2.y);
10410 
10411 		version(X11) {
10412 			XLinearGradient gradient;
10413 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
10414 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
10415 
10416 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10417 				return XRenderCreateLinearGradient(
10418 					XDisplayConnection.get,
10419 					&gradient,
10420 					stopsPositions.ptr,
10421 					colors.ptr,
10422 					cast(int) stops.length);
10423 			});
10424 		} else version(Windows) {
10425 			// FIXME
10426 			forEachPixel((int x, int y) {
10427 				import core.stdc.math;
10428 
10429 				//sqrtf(
10430 
10431 				return Color.transparent;
10432 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10433 			});
10434 		}
10435 	}
10436 }
10437 
10438 /++
10439 	A conical gradient goes from color to color around a circumference from a center point.
10440 
10441 	X ONLY RIGHT NOW
10442 
10443 	History:
10444 		Added November 20, 2021 (dub v10.4)
10445 
10446 	Bugs:
10447 		Not yet implemented on Windows.
10448 +/
10449 version(OSXCocoa) {} else // NotYetImplementedException
10450 class ConicalGradient : Gradient {
10451 	/++
10452 
10453 	+/
10454 	this(Point center, float angleInDegrees, Stop[] stops...) {
10455 		super(center.x * 2, center.y * 2);
10456 
10457 		version(X11) {
10458 			XConicalGradient gradient;
10459 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
10460 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
10461 
10462 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10463 				return XRenderCreateConicalGradient(
10464 					XDisplayConnection.get,
10465 					&gradient,
10466 					stopsPositions.ptr,
10467 					colors.ptr,
10468 					cast(int) stops.length);
10469 			});
10470 		} else version(Windows) {
10471 			// FIXME
10472 			forEachPixel((int x, int y) {
10473 				import core.stdc.math;
10474 
10475 				//sqrtf(
10476 
10477 				return Color.transparent;
10478 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10479 			});
10480 
10481 		}
10482 	}
10483 }
10484 
10485 /++
10486 	A radial gradient goes from color to color based on distance from the center.
10487 	It is like rings of color.
10488 
10489 	X ONLY RIGHT NOW
10490 
10491 
10492 	More specifically, you create two circles: an inner circle and an outer circle.
10493 	The gradient is only drawn in the area outside the inner circle but inside the outer
10494 	circle. The closest line between those two circles forms the line for the gradient
10495 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10496 
10497 	History:
10498 		Added November 20, 2021 (dub v10.4)
10499 
10500 	Bugs:
10501 		Not yet implemented on Windows.
10502 +/
10503 version(OSXCocoa) {} else // NotYetImplementedException
10504 class RadialGradient : Gradient {
10505 	/++
10506 
10507 	+/
10508 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10509 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10510 
10511 		version(X11) {
10512 			XRadialGradient gradient;
10513 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10514 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10515 
10516 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10517 				return XRenderCreateRadialGradient(
10518 					XDisplayConnection.get,
10519 					&gradient,
10520 					stopsPositions.ptr,
10521 					colors.ptr,
10522 					cast(int) stops.length);
10523 			});
10524 		} else version(Windows) {
10525 			// FIXME
10526 			forEachPixel((int x, int y) {
10527 				import core.stdc.math;
10528 
10529 				//sqrtf(
10530 
10531 				return Color.transparent;
10532 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10533 			});
10534 		}
10535 	}
10536 }
10537 
10538 
10539 
10540 /+
10541 	NOT IMPLEMENTED
10542 
10543 	A display-stored image optimized for relatively quick drawing, like
10544 	[Sprite], but this one supports alpha channel blending and does NOT
10545 	support direct drawing upon it with a [ScreenPainter].
10546 
10547 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10548 	plain [ScreenPainter]... sort of.
10549 
10550 	On X11, it requires the Xrender extension and library. This is available
10551 	almost everywhere though.
10552 
10553 	History:
10554 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10555 +/
10556 version(none)
10557 class AlphaSprite {
10558 	/++
10559 		Copies the given image into it.
10560 	+/
10561 	this(MemoryImage img) {
10562 
10563 		if(!XRenderLibrary.loadAttempted) {
10564 			XRenderLibrary.loadDynamicLibrary();
10565 
10566 			// FIXME: this needs to be reconstructed when the X server changes
10567 			repopulateX();
10568 		}
10569 		if(!XRenderLibrary.loadSuccessful)
10570 			throw new Exception("XRender library load failure");
10571 
10572 		// I probably need to put the alpha mask in a separate Picture
10573 		// ugh
10574 		// maybe the Sprite itself can have an alpha bitmask anyway
10575 
10576 
10577 		auto display = XDisplayConnection.get();
10578 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10579 
10580 
10581 		XRenderPictureAttributes attrs;
10582 
10583 		handle = XRenderCreatePicture(
10584 			XDisplayConnection.get,
10585 			pixmap,
10586 			RGBA,
10587 			0,
10588 			&attrs
10589 		);
10590 
10591 	}
10592 
10593 	// maybe i'll use the create gradient functions too with static factories..
10594 
10595 	void drawAt(ScreenPainter painter, Point where) {
10596 		//painter.drawPixmap(this, where);
10597 
10598 		XRenderPictureAttributes attrs;
10599 
10600 		auto pic = XRenderCreatePicture(
10601 			XDisplayConnection.get,
10602 			painter.impl.d,
10603 			RGB,
10604 			0,
10605 			&attrs
10606 		);
10607 
10608 		XRenderComposite(
10609 			XDisplayConnection.get,
10610 			3, // PictOpOver
10611 			handle,
10612 			None,
10613 			pic,
10614 			0, // src
10615 			0,
10616 			0, // mask
10617 			0,
10618 			10, // dest
10619 			10,
10620 			100, // width
10621 			100
10622 		);
10623 
10624 		/+
10625 		XRenderFreePicture(
10626 			XDisplayConnection.get,
10627 			pic
10628 		);
10629 
10630 		XRenderFreePicture(
10631 			XDisplayConnection.get,
10632 			fill
10633 		);
10634 		+/
10635 		// on Windows you can stretch but Xrender still can't :(
10636 	}
10637 
10638 	static XRenderPictFormat* RGB;
10639 	static XRenderPictFormat* RGBA;
10640 	static void repopulateX() {
10641 		auto display = XDisplayConnection.get;
10642 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10643 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10644 	}
10645 
10646 	XPixmap pixmap;
10647 	Picture handle;
10648 }
10649 
10650 ///
10651 interface CapableOfBeingDrawnUpon {
10652 	///
10653 	ScreenPainter draw();
10654 	///
10655 	int width();
10656 	///
10657 	int height();
10658 	protected ScreenPainterImplementation* activeScreenPainter();
10659 	protected void activeScreenPainter(ScreenPainterImplementation*);
10660 	bool closed();
10661 
10662 	void delegate() paintingFinishedDg();
10663 
10664 	/// Be warned: this can be a very slow operation
10665 	TrueColorImage takeScreenshot();
10666 }
10667 
10668 /// 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].
10669 void flushGui() {
10670 	version(X11) {
10671 		auto dpy = XDisplayConnection.get();
10672 		XLockDisplay(dpy);
10673 		scope(exit) XUnlockDisplay(dpy);
10674 		XFlush(dpy);
10675 	}
10676 }
10677 
10678 /++
10679 	Runs the given code in the GUI thread when its event loop
10680 	is available, blocking until it completes. This allows you
10681 	to create and manipulate windows from another thread without
10682 	invoking undefined behavior.
10683 
10684 	If this is the gui thread, it runs the code immediately.
10685 
10686 	If no gui thread exists yet, the current thread is assumed
10687 	to be it. Attempting to create windows or run the event loop
10688 	in any other thread will cause an assertion failure.
10689 
10690 
10691 	$(TIP
10692 		Did you know you can use UFCS on delegate literals?
10693 
10694 		() {
10695 			// code here
10696 		}.runInGuiThread;
10697 	)
10698 
10699 	Returns:
10700 		`true` if the function was called, `false` if it was not.
10701 		The function may not be called because the gui thread had
10702 		already terminated by the time you called this.
10703 
10704 	History:
10705 		Added April 10, 2020 (v7.2.0)
10706 
10707 		Return value added and implementation tweaked to avoid locking
10708 		at program termination on February 24, 2021 (v9.2.1).
10709 +/
10710 bool runInGuiThread(scope void delegate() dg) @trusted {
10711 	claimGuiThread();
10712 
10713 	if(thisIsGuiThread) {
10714 		dg();
10715 		return true;
10716 	}
10717 
10718 	if(guiThreadTerminating)
10719 		return false;
10720 
10721 	import core.sync.semaphore;
10722 	static Semaphore sc;
10723 	if(sc is null)
10724 		sc = new Semaphore();
10725 
10726 	static RunQueueMember* rqm;
10727 	if(rqm is null)
10728 		rqm = new RunQueueMember;
10729 	rqm.dg = cast(typeof(rqm.dg)) dg;
10730 	rqm.signal = sc;
10731 	rqm.thrown = null;
10732 
10733 	synchronized(runInGuiThreadLock) {
10734 		runInGuiThreadQueue ~= rqm;
10735 	}
10736 
10737 	if(!SimpleWindow.eventWakeUp())
10738 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10739 
10740 	rqm.signal.wait();
10741 	auto t = rqm.thrown;
10742 
10743 	if(t)
10744 		throw t;
10745 
10746 	return true;
10747 }
10748 
10749 // note it runs sync if this is the gui thread....
10750 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10751 	claimGuiThread();
10752 
10753 	try {
10754 
10755 		if(thisIsGuiThread) {
10756 			dg();
10757 			return;
10758 		}
10759 
10760 		if(guiThreadTerminating)
10761 			return;
10762 
10763 		RunQueueMember* rqm = new RunQueueMember;
10764 		rqm.dg = cast(typeof(rqm.dg)) dg;
10765 		rqm.signal = null;
10766 		rqm.thrown = null;
10767 
10768 		synchronized(runInGuiThreadLock) {
10769 			runInGuiThreadQueue ~= rqm;
10770 		}
10771 
10772 		if(!SimpleWindow.eventWakeUp())
10773 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10774 	} catch(Exception e) {
10775 		if(handleError)
10776 			handleError(e);
10777 	}
10778 }
10779 
10780 private void runPendingRunInGuiThreadDelegates() {
10781 	more:
10782 	RunQueueMember* next;
10783 	synchronized(runInGuiThreadLock) {
10784 		if(runInGuiThreadQueue.length) {
10785 			next = runInGuiThreadQueue[0];
10786 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10787 		} else {
10788 			next = null;
10789 		}
10790 	}
10791 
10792 	if(next) {
10793 		try {
10794 			next.dg();
10795 			next.thrown = null;
10796 		} catch(Throwable t) {
10797 			next.thrown = t;
10798 		}
10799 
10800 		if(next.signal)
10801 			next.signal.notify();
10802 
10803 		goto more;
10804 	}
10805 }
10806 
10807 private void claimGuiThread() nothrow {
10808 	import core.atomic;
10809 	if(cas(&guiThreadExists_, false, true))
10810 		thisIsGuiThread = true;
10811 }
10812 
10813 private struct RunQueueMember {
10814 	void delegate() dg;
10815 	import core.sync.semaphore;
10816 	Semaphore signal;
10817 	Throwable thrown;
10818 }
10819 
10820 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10821 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10822 private bool thisIsGuiThread = false;
10823 private shared bool guiThreadExists_ = false;
10824 private shared bool guiThreadTerminating = false;
10825 
10826 /++
10827 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10828 	event loop. All windows must be exclusively created and managed by a single thread.
10829 
10830 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10831 	when you call one of its constructors.
10832 
10833 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10834 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10835 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10836 
10837 	The reason this function is available is in case you want to message pass between a gui
10838 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10839 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10840 
10841 	History:
10842 		Added December 3, 2021 (dub v10.5)
10843 +/
10844 public bool guiThreadExists() {
10845 	return guiThreadExists_;
10846 }
10847 
10848 /++
10849 	Returns `true` if this thread is either running or set to be running the
10850 	simpledisplay.d gui core event loop because it owns windows.
10851 
10852 	It is important to keep gui-related functionality in the right thread, so you will
10853 	want to `runInGuiThread` when you call them (with some specific exceptions called
10854 	out in those specific functions' documentation). Notably, all windows must be
10855 	created and managed only from the gui thread.
10856 
10857 	Will return false if simpledisplay's other functions haven't been called
10858 	yet; check [guiThreadExists] in addition to this.
10859 
10860 	History:
10861 		Added December 3, 2021 (dub v10.5)
10862 +/
10863 public bool thisThreadRunningGui() {
10864 	return thisIsGuiThread;
10865 }
10866 
10867 /++
10868 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10869 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10870 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10871 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10872 	file instead if you are in one of those situations).
10873 
10874 	It does not support outputting very many types; just strings and ints are likely to actually work.
10875 
10876 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10877 	is unspecified meaning I can change it at any time. The only point of this function is to help
10878 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10879 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10880 	in those contexts.
10881 
10882 	$(WARNING
10883 		I reserve the right to change this function at any time. You can use it if it helps you
10884 		but do not rely on it for anything permanent.
10885 	)
10886 
10887 	History:
10888 		Added December 3, 2021. Not formally supported under any stable tag.
10889 +/
10890 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10891 	try {
10892 		version(Windows) {
10893 			import core.sys.windows.wincon;
10894 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10895 				AllocConsole();
10896 			const(char)* fn = "CONOUT$";
10897 		} else version(Posix) {
10898 			const(char)* fn = "/dev/tty";
10899 		} else static assert(0, "Function not implemented for your system");
10900 
10901 		if(fileOverride.length)
10902 			fn = fileOverride.ptr;
10903 
10904 		import core.stdc.stdio;
10905 		auto fp = fopen(fn, "wt");
10906 		if(fp is null) return;
10907 		scope(exit) fclose(fp);
10908 
10909 		string str;
10910 		foreach(item; t) {
10911 			static if(is(typeof(item) : const(char)[]))
10912 				str ~= item;
10913 			else
10914 				str ~= toInternal!string(item);
10915 			str ~= " ";
10916 		}
10917 		str ~= "\n";
10918 
10919 		fwrite(str.ptr, 1, str.length, fp);
10920 		fflush(fp);
10921 	} catch(Exception e) {
10922 		// sorry no hope
10923 	}
10924 }
10925 
10926 private void guiThreadFinalize() {
10927 	assert(thisIsGuiThread);
10928 
10929 	guiThreadTerminating = true; // don't add any more from this point on
10930 	runPendingRunInGuiThreadDelegates();
10931 }
10932 
10933 /+
10934 interface IPromise {
10935 	void reportProgress(int current, int max, string message);
10936 
10937 	/+ // not formally in cuz of templates but still
10938 	IPromise Then();
10939 	IPromise Catch();
10940 	IPromise Finally();
10941 	+/
10942 }
10943 
10944 /+
10945 	auto promise = async({ ... });
10946 	promise.Then(whatever).
10947 		Then(whateverelse).
10948 		Catch((exception) { });
10949 
10950 
10951 	A promise is run inside a fiber and it looks something like:
10952 
10953 	try {
10954 		auto res = whatever();
10955 		auto res2 = whateverelse(res);
10956 	} catch(Exception e) {
10957 		{ }(e);
10958 	}
10959 
10960 	When a thing succeeds, it is passed as an arg to the next
10961 +/
10962 class Promise(T) : IPromise {
10963 	auto Then() { return null; }
10964 	auto Catch() { return null; }
10965 	auto Finally() { return null; }
10966 
10967 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10968 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10969 	T await();
10970 }
10971 
10972 interface Task {
10973 }
10974 
10975 interface Resolvable(T) : Task {
10976 	void run();
10977 
10978 	void resolve(T);
10979 
10980 	Resolvable!T then(void delegate(T)); // returns a new promise
10981 	Resolvable!T error(Throwable); // js catch
10982 	Resolvable!T completed(); // js finally
10983 
10984 }
10985 
10986 /++
10987 	Runs `work` in a helper thread and sends its return value back to the main gui
10988 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10989 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10990 	kill the program.
10991 
10992 	You can call reportProgress(position, max, message) to update your parent window
10993 	on your progress.
10994 
10995 	I should also use `shared` methods. FIXME
10996 
10997 	History:
10998 		Added March 6, 2021 (dub version 9.3).
10999 +/
11000 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
11001 	uponCompletion(work(null));
11002 }
11003 
11004 +/
11005 
11006 /// Used internal to dispatch events to various classes.
11007 interface CapableOfHandlingNativeEvent {
11008 	NativeEventHandler getNativeEventHandler();
11009 
11010 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
11011 
11012 	version(X11) {
11013 		// if this is impossible, you are allowed to just throw from it
11014 		// Note: if you call it from another object, set a flag cuz the manger will call you again
11015 		void recreateAfterDisconnect();
11016 		// discard any *connection specific* state, but keep enough that you
11017 		// can be recreated if possible. discardConnectionState() is always called immediately
11018 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
11019 		// you need initialization order
11020 		void discardConnectionState();
11021 	}
11022 }
11023 
11024 version(X11)
11025 /++
11026 	State of keys on mouse events, especially motion.
11027 
11028 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
11029 +/
11030 enum ModifierState : uint {
11031 	shift = 1, ///
11032 	capsLock = 2, ///
11033 	ctrl = 4, ///
11034 	alt = 8, /// Not always available on Windows
11035 	windows = 64, /// ditto
11036 	numLock = 16, ///
11037 
11038 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11039 	middleButtonDown = 512, /// ditto
11040 	rightButtonDown = 1024, /// ditto
11041 }
11042 else version(Windows)
11043 /// ditto
11044 enum ModifierState : uint {
11045 	shift = 4, ///
11046 	ctrl = 8, ///
11047 
11048 	// i'm not sure if the next two are available
11049 	alt = 256, /// not always available on Windows
11050 	windows = 512, /// ditto
11051 
11052 	capsLock = 1024, ///
11053 	numLock = 2048, ///
11054 
11055 	leftButtonDown = 1, /// not available on key events
11056 	middleButtonDown = 16, /// ditto
11057 	rightButtonDown = 2, /// ditto
11058 
11059 	backButtonDown = 0x20, /// not available on X
11060 	forwardButtonDown = 0x40, /// ditto
11061 }
11062 else version(OSXCocoa)
11063 // FIXME FIXME NotYetImplementedException
11064 enum ModifierState : uint {
11065 	shift = 1, ///
11066 	capsLock = 2, ///
11067 	ctrl = 4, ///
11068 	alt = 8, /// Not always available on Windows
11069 	windows = 64, /// ditto
11070 	numLock = 16, ///
11071 
11072 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11073 	middleButtonDown = 512, /// ditto
11074 	rightButtonDown = 1024, /// ditto
11075 }
11076 
11077 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
11078 enum MouseButton : int {
11079 	none = 0,
11080 	left = 1, ///
11081 	right = 2, ///
11082 	middle = 4, ///
11083 	wheelUp = 8, ///
11084 	wheelDown = 16, ///
11085 	backButton = 32, /// often found on the thumb and used for back in browsers
11086 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
11087 }
11088 
11089 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
11090 enum MouseButtonLinear : ubyte {
11091 	left = 1, ///
11092 	right, ///
11093 	middle, ///
11094 	wheelUp, ///
11095 	wheelDown, ///
11096 	backButton, /// often found on the thumb and used for back in browsers
11097 	forwardButton, /// often found on the thumb and used for forward in browsers
11098 }
11099 
11100 version(X11) {
11101 	// FIXME: match ASCII whenever we can. Most of it is already there,
11102 	// but there's a few exceptions and mismatches with Windows
11103 
11104 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11105 	enum Key {
11106 		Escape = 0xff1b, ///
11107 		F1 = 0xffbe, ///
11108 		F2 = 0xffbf, ///
11109 		F3 = 0xffc0, ///
11110 		F4 = 0xffc1, ///
11111 		F5 = 0xffc2, ///
11112 		F6 = 0xffc3, ///
11113 		F7 = 0xffc4, ///
11114 		F8 = 0xffc5, ///
11115 		F9 = 0xffc6, ///
11116 		F10 = 0xffc7, ///
11117 		F11 = 0xffc8, ///
11118 		F12 = 0xffc9, ///
11119 		PrintScreen = 0xff61, ///
11120 		ScrollLock = 0xff14, ///
11121 		Pause = 0xff13, ///
11122 		Grave = 0x60, /// The $(BACKTICK) ~ key
11123 		// number keys across the top of the keyboard
11124 		N1 = 0x31, /// Number key atop the keyboard
11125 		N2 = 0x32, ///
11126 		N3 = 0x33, ///
11127 		N4 = 0x34, ///
11128 		N5 = 0x35, ///
11129 		N6 = 0x36, ///
11130 		N7 = 0x37, ///
11131 		N8 = 0x38, ///
11132 		N9 = 0x39, ///
11133 		N0 = 0x30, ///
11134 		Dash = 0x2d, ///
11135 		Equals = 0x3d, ///
11136 		Backslash = 0x5c, /// The \ | key
11137 		Backspace = 0xff08, ///
11138 		Insert = 0xff63, ///
11139 		Home = 0xff50, ///
11140 		PageUp = 0xff55, ///
11141 		Delete = 0xffff, ///
11142 		End = 0xff57, ///
11143 		PageDown = 0xff56, ///
11144 		Up = 0xff52, ///
11145 		Down = 0xff54, ///
11146 		Left = 0xff51, ///
11147 		Right = 0xff53, ///
11148 
11149 		Tab = 0xff09, ///
11150 		Q = 0x71, ///
11151 		W = 0x77, ///
11152 		E = 0x65, ///
11153 		R = 0x72, ///
11154 		T = 0x74, ///
11155 		Y = 0x79, ///
11156 		U = 0x75, ///
11157 		I = 0x69, ///
11158 		O = 0x6f, ///
11159 		P = 0x70, ///
11160 		LeftBracket = 0x5b, /// the [ { key
11161 		RightBracket = 0x5d, /// the ] } key
11162 		CapsLock = 0xffe5, ///
11163 		A = 0x61, ///
11164 		S = 0x73, ///
11165 		D = 0x64, ///
11166 		F = 0x66, ///
11167 		G = 0x67, ///
11168 		H = 0x68, ///
11169 		J = 0x6a, ///
11170 		K = 0x6b, ///
11171 		L = 0x6c, ///
11172 		Semicolon = 0x3b, ///
11173 		Apostrophe = 0x27, ///
11174 		Enter = 0xff0d, ///
11175 		Shift = 0xffe1, ///
11176 		Z = 0x7a, ///
11177 		X = 0x78, ///
11178 		C = 0x63, ///
11179 		V = 0x76, ///
11180 		B = 0x62, ///
11181 		N = 0x6e, ///
11182 		M = 0x6d, ///
11183 		Comma = 0x2c, ///
11184 		Period = 0x2e, ///
11185 		Slash = 0x2f, /// the / ? key
11186 		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
11187 		Ctrl = 0xffe3, ///
11188 		Windows = 0xffeb, ///
11189 		Alt = 0xffe9, ///
11190 		Space = 0x20, ///
11191 		Alt_r = 0xffea, /// ditto of shift_r
11192 		Windows_r = 0xffec, ///
11193 		Menu = 0xff67, ///
11194 		Ctrl_r = 0xffe4, ///
11195 
11196 		NumLock = 0xff7f, ///
11197 		Divide = 0xffaf, /// The / key on the number pad
11198 		Multiply = 0xffaa, /// The * key on the number pad
11199 		Minus = 0xffad, /// The - key on the number pad
11200 		Plus = 0xffab, /// The + key on the number pad
11201 		PadEnter = 0xff8d, /// Numberpad enter key
11202 		Pad1 = 0xff9c, /// Numberpad keys
11203 		Pad2 = 0xff99, ///
11204 		Pad3 = 0xff9b, ///
11205 		Pad4 = 0xff96, ///
11206 		Pad5 = 0xff9d, ///
11207 		Pad6 = 0xff98, ///
11208 		Pad7 = 0xff95, ///
11209 		Pad8 = 0xff97, ///
11210 		Pad9 = 0xff9a, ///
11211 		Pad0 = 0xff9e, ///
11212 		PadDot = 0xff9f, ///
11213 	}
11214 } else version(Windows) {
11215 	// the character here is for en-us layouts and for illustration only
11216 	// if you actually want to get characters, wait for character events
11217 	// (the argument to your event handler is simply a dchar)
11218 	// those will be converted by the OS for the right locale.
11219 
11220 	enum Key {
11221 		Escape = 0x1b,
11222 		F1 = 0x70,
11223 		F2 = 0x71,
11224 		F3 = 0x72,
11225 		F4 = 0x73,
11226 		F5 = 0x74,
11227 		F6 = 0x75,
11228 		F7 = 0x76,
11229 		F8 = 0x77,
11230 		F9 = 0x78,
11231 		F10 = 0x79,
11232 		F11 = 0x7a,
11233 		F12 = 0x7b,
11234 		PrintScreen = 0x2c,
11235 		ScrollLock = 0x91,
11236 		Pause = 0x13,
11237 		Grave = 0xc0,
11238 		// number keys across the top of the keyboard
11239 		N1 = 0x31,
11240 		N2 = 0x32,
11241 		N3 = 0x33,
11242 		N4 = 0x34,
11243 		N5 = 0x35,
11244 		N6 = 0x36,
11245 		N7 = 0x37,
11246 		N8 = 0x38,
11247 		N9 = 0x39,
11248 		N0 = 0x30,
11249 		Dash = 0xbd,
11250 		Equals = 0xbb,
11251 		Backslash = 0xdc,
11252 		Backspace = 0x08,
11253 		Insert = 0x2d,
11254 		Home = 0x24,
11255 		PageUp = 0x21,
11256 		Delete = 0x2e,
11257 		End = 0x23,
11258 		PageDown = 0x22,
11259 		Up = 0x26,
11260 		Down = 0x28,
11261 		Left = 0x25,
11262 		Right = 0x27,
11263 
11264 		Tab = 0x09,
11265 		Q = 0x51,
11266 		W = 0x57,
11267 		E = 0x45,
11268 		R = 0x52,
11269 		T = 0x54,
11270 		Y = 0x59,
11271 		U = 0x55,
11272 		I = 0x49,
11273 		O = 0x4f,
11274 		P = 0x50,
11275 		LeftBracket = 0xdb,
11276 		RightBracket = 0xdd,
11277 		CapsLock = 0x14,
11278 		A = 0x41,
11279 		S = 0x53,
11280 		D = 0x44,
11281 		F = 0x46,
11282 		G = 0x47,
11283 		H = 0x48,
11284 		J = 0x4a,
11285 		K = 0x4b,
11286 		L = 0x4c,
11287 		Semicolon = 0xba,
11288 		Apostrophe = 0xde,
11289 		Enter = 0x0d,
11290 		Shift = 0x10,
11291 		Z = 0x5a,
11292 		X = 0x58,
11293 		C = 0x43,
11294 		V = 0x56,
11295 		B = 0x42,
11296 		N = 0x4e,
11297 		M = 0x4d,
11298 		Comma = 0xbc,
11299 		Period = 0xbe,
11300 		Slash = 0xbf,
11301 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11302 		Ctrl = 0x11,
11303 		Windows = 0x5b,
11304 		Alt = -5, // FIXME
11305 		Space = 0x20,
11306 		Alt_r = 0xffea, // ditto of shift_r
11307 		Windows_r = 0x5c, // ditto of shift_r
11308 		Menu = 0x5d,
11309 		Ctrl_r = 0xa3, // ditto of shift_r
11310 
11311 		NumLock = 0x90,
11312 		Divide = 0x6f,
11313 		Multiply = 0x6a,
11314 		Minus = 0x6d,
11315 		Plus = 0x6b,
11316 		PadEnter = -8, // FIXME
11317 		Pad1 = 0x61,
11318 		Pad2 = 0x62,
11319 		Pad3 = 0x63,
11320 		Pad4 = 0x64,
11321 		Pad5 = 0x65,
11322 		Pad6 = 0x66,
11323 		Pad7 = 0x67,
11324 		Pad8 = 0x68,
11325 		Pad9 = 0x69,
11326 		Pad0 = 0x60,
11327 		PadDot = 0x6e,
11328 	}
11329 
11330 	// I'm keeping this around for reference purposes
11331 	// ideally all these buttons will be listed for all platforms,
11332 	// but now now I'm just focusing on my US keyboard
11333 	version(none)
11334 	enum Key {
11335 		LBUTTON = 0x01,
11336 		RBUTTON = 0x02,
11337 		CANCEL = 0x03,
11338 		MBUTTON = 0x04,
11339 		//static if (_WIN32_WINNT > =  0x500) {
11340 		XBUTTON1 = 0x05,
11341 		XBUTTON2 = 0x06,
11342 		//}
11343 		BACK = 0x08,
11344 		TAB = 0x09,
11345 		CLEAR = 0x0C,
11346 		RETURN = 0x0D,
11347 		SHIFT = 0x10,
11348 		CONTROL = 0x11,
11349 		MENU = 0x12,
11350 		PAUSE = 0x13,
11351 		CAPITAL = 0x14,
11352 		KANA = 0x15,
11353 		HANGEUL = 0x15,
11354 		HANGUL = 0x15,
11355 		JUNJA = 0x17,
11356 		FINAL = 0x18,
11357 		HANJA = 0x19,
11358 		KANJI = 0x19,
11359 		ESCAPE = 0x1B,
11360 		CONVERT = 0x1C,
11361 		NONCONVERT = 0x1D,
11362 		ACCEPT = 0x1E,
11363 		MODECHANGE = 0x1F,
11364 		SPACE = 0x20,
11365 		PRIOR = 0x21,
11366 		NEXT = 0x22,
11367 		END = 0x23,
11368 		HOME = 0x24,
11369 		LEFT = 0x25,
11370 		UP = 0x26,
11371 		RIGHT = 0x27,
11372 		DOWN = 0x28,
11373 		SELECT = 0x29,
11374 		PRINT = 0x2A,
11375 		EXECUTE = 0x2B,
11376 		SNAPSHOT = 0x2C,
11377 		INSERT = 0x2D,
11378 		DELETE = 0x2E,
11379 		HELP = 0x2F,
11380 		LWIN = 0x5B,
11381 		RWIN = 0x5C,
11382 		APPS = 0x5D,
11383 		SLEEP = 0x5F,
11384 		NUMPAD0 = 0x60,
11385 		NUMPAD1 = 0x61,
11386 		NUMPAD2 = 0x62,
11387 		NUMPAD3 = 0x63,
11388 		NUMPAD4 = 0x64,
11389 		NUMPAD5 = 0x65,
11390 		NUMPAD6 = 0x66,
11391 		NUMPAD7 = 0x67,
11392 		NUMPAD8 = 0x68,
11393 		NUMPAD9 = 0x69,
11394 		MULTIPLY = 0x6A,
11395 		ADD = 0x6B,
11396 		SEPARATOR = 0x6C,
11397 		SUBTRACT = 0x6D,
11398 		DECIMAL = 0x6E,
11399 		DIVIDE = 0x6F,
11400 		F1 = 0x70,
11401 		F2 = 0x71,
11402 		F3 = 0x72,
11403 		F4 = 0x73,
11404 		F5 = 0x74,
11405 		F6 = 0x75,
11406 		F7 = 0x76,
11407 		F8 = 0x77,
11408 		F9 = 0x78,
11409 		F10 = 0x79,
11410 		F11 = 0x7A,
11411 		F12 = 0x7B,
11412 		F13 = 0x7C,
11413 		F14 = 0x7D,
11414 		F15 = 0x7E,
11415 		F16 = 0x7F,
11416 		F17 = 0x80,
11417 		F18 = 0x81,
11418 		F19 = 0x82,
11419 		F20 = 0x83,
11420 		F21 = 0x84,
11421 		F22 = 0x85,
11422 		F23 = 0x86,
11423 		F24 = 0x87,
11424 		NUMLOCK = 0x90,
11425 		SCROLL = 0x91,
11426 		LSHIFT = 0xA0,
11427 		RSHIFT = 0xA1,
11428 		LCONTROL = 0xA2,
11429 		RCONTROL = 0xA3,
11430 		LMENU = 0xA4,
11431 		RMENU = 0xA5,
11432 		//static if (_WIN32_WINNT > =  0x500) {
11433 		BROWSER_BACK = 0xA6,
11434 		BROWSER_FORWARD = 0xA7,
11435 		BROWSER_REFRESH = 0xA8,
11436 		BROWSER_STOP = 0xA9,
11437 		BROWSER_SEARCH = 0xAA,
11438 		BROWSER_FAVORITES = 0xAB,
11439 		BROWSER_HOME = 0xAC,
11440 		VOLUME_MUTE = 0xAD,
11441 		VOLUME_DOWN = 0xAE,
11442 		VOLUME_UP = 0xAF,
11443 		MEDIA_NEXT_TRACK = 0xB0,
11444 		MEDIA_PREV_TRACK = 0xB1,
11445 		MEDIA_STOP = 0xB2,
11446 		MEDIA_PLAY_PAUSE = 0xB3,
11447 		LAUNCH_MAIL = 0xB4,
11448 		LAUNCH_MEDIA_SELECT = 0xB5,
11449 		LAUNCH_APP1 = 0xB6,
11450 		LAUNCH_APP2 = 0xB7,
11451 		//}
11452 		OEM_1 = 0xBA,
11453 		//static if (_WIN32_WINNT > =  0x500) {
11454 		OEM_PLUS = 0xBB,
11455 		OEM_COMMA = 0xBC,
11456 		OEM_MINUS = 0xBD,
11457 		OEM_PERIOD = 0xBE,
11458 		//}
11459 		OEM_2 = 0xBF,
11460 		OEM_3 = 0xC0,
11461 		OEM_4 = 0xDB,
11462 		OEM_5 = 0xDC,
11463 		OEM_6 = 0xDD,
11464 		OEM_7 = 0xDE,
11465 		OEM_8 = 0xDF,
11466 		//static if (_WIN32_WINNT > =  0x500) {
11467 		OEM_102 = 0xE2,
11468 		//}
11469 		PROCESSKEY = 0xE5,
11470 		//static if (_WIN32_WINNT > =  0x500) {
11471 		PACKET = 0xE7,
11472 		//}
11473 		ATTN = 0xF6,
11474 		CRSEL = 0xF7,
11475 		EXSEL = 0xF8,
11476 		EREOF = 0xF9,
11477 		PLAY = 0xFA,
11478 		ZOOM = 0xFB,
11479 		NONAME = 0xFC,
11480 		PA1 = 0xFD,
11481 		OEM_CLEAR = 0xFE,
11482 	}
11483 
11484 } else version(OSXCocoa) {
11485 	enum Key {
11486 		Escape = 53,
11487 		F1 = 122,
11488 		F2 = 120,
11489 		F3 = 99,
11490 		F4 = 118,
11491 		F5 = 96,
11492 		F6 = 97,
11493 		F7 = 98,
11494 		F8 = 100,
11495 		F9 = 101,
11496 		F10 = 109,
11497 		F11 = 103,
11498 		F12 = 111,
11499 		PrintScreen = 105,
11500 		ScrollLock = 107,
11501 		Pause = 113,
11502 		Grave = 50,
11503 		// number keys across the top of the keyboard
11504 		N1 = 18,
11505 		N2 = 19,
11506 		N3 = 20,
11507 		N4 = 21,
11508 		N5 = 23,
11509 		N6 = 22,
11510 		N7 = 26,
11511 		N8 = 28,
11512 		N9 = 25,
11513 		N0 = 29,
11514 		Dash = 27,
11515 		Equals = 24,
11516 		Backslash = 42,
11517 		Backspace = 51,
11518 		Insert = 114,
11519 		Home = 115,
11520 		PageUp = 116,
11521 		Delete = 117,
11522 		End = 119,
11523 		PageDown = 121,
11524 		Up = 126,
11525 		Down = 125,
11526 		Left = 123,
11527 		Right = 124,
11528 
11529 		Tab = 48,
11530 		Q = 12,
11531 		W = 13,
11532 		E = 14,
11533 		R = 15,
11534 		T = 17,
11535 		Y = 16,
11536 		U = 32,
11537 		I = 34,
11538 		O = 31,
11539 		P = 35,
11540 		LeftBracket = 33,
11541 		RightBracket = 30,
11542 		CapsLock = 57,
11543 		A = 0,
11544 		S = 1,
11545 		D = 2,
11546 		F = 3,
11547 		G = 5,
11548 		H = 4,
11549 		J = 38,
11550 		K = 40,
11551 		L = 37,
11552 		Semicolon = 41,
11553 		Apostrophe = 39,
11554 		Enter = 36,
11555 		Shift = 56,
11556 		Z = 6,
11557 		X = 7,
11558 		C = 8,
11559 		V = 9,
11560 		B = 11,
11561 		N = 45,
11562 		M = 46,
11563 		Comma = 43,
11564 		Period = 47,
11565 		Slash = 44,
11566 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11567 		Ctrl = 59,
11568 		Windows = 55,
11569 		Alt = 58,
11570 		Space = 49,
11571 		Alt_r = -3, // ditto of shift_r
11572 		Windows_r = -2,
11573 		Menu = 110,
11574 		Ctrl_r = -1,
11575 
11576 		NumLock = 1,
11577 		Divide = 75,
11578 		Multiply = 67,
11579 		Minus = 78,
11580 		Plus = 69,
11581 		PadEnter = 76,
11582 		Pad1 = 83,
11583 		Pad2 = 84,
11584 		Pad3 = 85,
11585 		Pad4 = 86,
11586 		Pad5 = 87,
11587 		Pad6 = 88,
11588 		Pad7 = 89,
11589 		Pad8 = 91,
11590 		Pad9 = 92,
11591 		Pad0 = 82,
11592 		PadDot = 65,
11593 	}
11594 
11595 }
11596 
11597 /* Additional utilities */
11598 
11599 
11600 Color fromHsl(real h, real s, real l) {
11601 	return arsd.color.fromHsl([h,s,l]);
11602 }
11603 
11604 
11605 
11606 /* ********** What follows is the system-specific implementations *********/
11607 version(Windows) {
11608 
11609 
11610 	// helpers for making HICONs from MemoryImages
11611 	class WindowsIcon {
11612 		struct Win32Icon {
11613 			align(1):
11614 			uint biSize;
11615 			int biWidth;
11616 			int biHeight;
11617 			ushort biPlanes;
11618 			ushort biBitCount;
11619 			uint biCompression;
11620 			uint biSizeImage;
11621 			int biXPelsPerMeter;
11622 			int biYPelsPerMeter;
11623 			uint biClrUsed;
11624 			uint biClrImportant;
11625 			// RGBQUAD[colorCount] biColors;
11626 			/* Pixels:
11627 			Uint8 pixels[]
11628 			*/
11629 			/* Mask:
11630 			Uint8 mask[]
11631 			*/
11632 		}
11633 
11634 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11635 
11636 			assert(mi.width <= 256, "image too wide");
11637 			assert(mi.height <= 256, "image too tall");
11638 			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
11639 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11640 
11641 			int icon_plen = mi.width * mi.height * 4;
11642 			int icon_mlen = mi.width * mi.height / 8;
11643 
11644 			int colorCount = 0;
11645 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11646 
11647 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11648 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11649 
11650 			auto data = memory[Win32Icon.sizeof .. $];
11651 
11652 			width = mi.width;
11653 			height = mi.height;
11654 
11655 			auto trueColorImage = mi.getAsTrueColorImage();
11656 
11657 			icon_win32.biSize = 40;
11658 			icon_win32.biWidth = mi.width;
11659 			icon_win32.biHeight = mi.height*2;
11660 			icon_win32.biPlanes = 1;
11661 			icon_win32.biBitCount = 32;
11662 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11663 
11664 			int offset = 0;
11665 			int andOff = icon_plen * 8; // the and offset is in bits
11666 
11667 			// leaving the and mask as the default 0 so the rgba alpha blend
11668 			// does its thing instead
11669 			for(int y = height - 1; y >= 0; y--) {
11670 				int off2 = y * width * 4;
11671 				foreach(x; 0 .. width) {
11672 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11673 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11674 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11675 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11676 
11677 					offset += 4;
11678 					off2 += 4;
11679 				}
11680 			}
11681 
11682 			return memory;
11683 		}
11684 
11685 		this(MemoryImage mi) {
11686 			int icon_len, width, height;
11687 
11688 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11689 
11690 			/*
11691 			PNG* png = readPnpngData);
11692 			PNGHeader pngh = getHeader(png);
11693 			void* icon_win32;
11694 			if(pngh.depth == 4) {
11695 				auto i = new Win32Icon!(16);
11696 				i.fromPNG(png, pngh, icon_len, width, height);
11697 				icon_win32 = i;
11698 			}
11699 			else if(pngh.depth == 8) {
11700 				auto i = new Win32Icon!(256);
11701 				i.fromPNG(png, pngh, icon_len, width, height);
11702 				icon_win32 = i;
11703 			} else assert(0);
11704 			*/
11705 
11706 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11707 
11708 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11709 		}
11710 
11711 		~this() {
11712 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11713 			DestroyIcon(hIcon);
11714 		}
11715 
11716 		HICON hIcon;
11717 	}
11718 
11719 
11720 
11721 
11722 
11723 
11724 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11725 	alias HWND NativeWindowHandle;
11726 
11727 	extern(Windows)
11728 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11729 		try {
11730 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11731 				// it returns zero if the message is handled, so we won't do anything more there
11732 				// do I like that though?
11733 				int mustReturn;
11734 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11735 				if(mustReturn)
11736 					return ret;
11737 			}
11738 
11739 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11740 				if(window.getNativeEventHandler !is null) {
11741 					int mustReturn;
11742 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11743 					if(mustReturn)
11744 						return ret;
11745 				}
11746 				if(auto w = cast(SimpleWindow) (*window))
11747 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11748 				else
11749 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11750 			} else {
11751 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11752 			}
11753 		} catch (Exception e) {
11754 			try {
11755 				sdpy_abort(e);
11756 				return 0;
11757 			} catch(Exception e) { assert(0); }
11758 		}
11759 	}
11760 
11761 	void sdpy_abort(Throwable e) nothrow {
11762 		try
11763 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11764 		catch(Exception e)
11765 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11766 		ExitProcess(1);
11767 	}
11768 
11769 	mixin template NativeScreenPainterImplementation() {
11770 		HDC hdc;
11771 		HWND hwnd;
11772 		//HDC windowHdc;
11773 		HBITMAP oldBmp;
11774 
11775 		void create(PaintingHandle window) {
11776 			hwnd = window;
11777 
11778 			if(auto sw = cast(SimpleWindow) this.window) {
11779 				// drawing on a window, double buffer
11780 				auto windowHdc = GetDC(hwnd);
11781 
11782 				auto buffer = sw.impl.buffer;
11783 				if(buffer is null) {
11784 					hdc = windowHdc;
11785 					windowDc = true;
11786 				} else {
11787 					hdc = CreateCompatibleDC(windowHdc);
11788 
11789 					ReleaseDC(hwnd, windowHdc);
11790 
11791 					oldBmp = SelectObject(hdc, buffer);
11792 				}
11793 			} else {
11794 				// drawing on something else, draw directly
11795 				hdc = CreateCompatibleDC(null);
11796 				SelectObject(hdc, window);
11797 			}
11798 
11799 			// X doesn't draw a text background, so neither should we
11800 			SetBkMode(hdc, TRANSPARENT);
11801 
11802 			ensureDefaultFontLoaded();
11803 
11804 			if(defaultGuiFont) {
11805 				SelectObject(hdc, defaultGuiFont);
11806 				// DeleteObject(defaultGuiFont);
11807 			}
11808 		}
11809 
11810 		static HFONT defaultGuiFont;
11811 		static void ensureDefaultFontLoaded() {
11812 			static bool triedDefaultGuiFont = false;
11813 			if(!triedDefaultGuiFont) {
11814 				NONCLIENTMETRICS params;
11815 				params.cbSize = params.sizeof;
11816 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11817 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11818 				}
11819 				triedDefaultGuiFont = true;
11820 			}
11821 		}
11822 
11823 		private OperatingSystemFont _activeFont;
11824 
11825 		void setFont(OperatingSystemFont font) {
11826 			_activeFont = font;
11827 			if(font && font.font) {
11828 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11829 					// error... how to handle tho?
11830 				} else {
11831 
11832 				}
11833 			}
11834 			else if(defaultGuiFont)
11835 				SelectObject(hdc, defaultGuiFont);
11836 		}
11837 
11838 		arsd.color.Rectangle _clipRectangle;
11839 
11840 		void setClipRectangle(int x, int y, int width, int height) {
11841 			auto old = _clipRectangle;
11842 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11843 			if(old == _clipRectangle)
11844 				return;
11845 
11846 			if(width == 0 || height == 0) {
11847 				SelectClipRgn(hdc, null);
11848 			} else {
11849 				auto region = CreateRectRgn(x, y, x + width, y + height);
11850 				SelectClipRgn(hdc, region);
11851 				DeleteObject(region);
11852 			}
11853 		}
11854 
11855 
11856 		// just because we can on Windows...
11857 		//void create(Image image);
11858 
11859 		void invalidateRect(Rectangle invalidRect) {
11860 			RECT rect;
11861 			rect.left = invalidRect.left;
11862 			rect.right = invalidRect.right;
11863 			rect.top = invalidRect.top;
11864 			rect.bottom = invalidRect.bottom;
11865 			InvalidateRect(hwnd, &rect, false);
11866 		}
11867 		bool manualInvalidations;
11868 
11869 		void dispose() {
11870 			// FIXME: this.window.width/height is probably wrong
11871 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11872 			// ReleaseDC(hwnd, windowHdc);
11873 
11874 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11875 			if(cast(SimpleWindow) this.window) {
11876 				if(!manualInvalidations)
11877 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11878 			}
11879 
11880 			if(originalPen !is null)
11881 				SelectObject(hdc, originalPen);
11882 			if(currentPen !is null)
11883 				DeleteObject(currentPen);
11884 			if(originalBrush !is null)
11885 				SelectObject(hdc, originalBrush);
11886 			if(currentBrush !is null)
11887 				DeleteObject(currentBrush);
11888 
11889 			SelectObject(hdc, oldBmp);
11890 
11891 			if(windowDc)
11892 				ReleaseDC(hwnd, hdc);
11893 			else
11894 				DeleteDC(hdc);
11895 
11896 			if(window.paintingFinishedDg !is null)
11897 				window.paintingFinishedDg()();
11898 		}
11899 
11900 		bool windowDc;
11901 		HPEN originalPen;
11902 		HPEN currentPen;
11903 
11904 		Pen _activePen;
11905 
11906 		Color _outlineColor;
11907 
11908 		@property void pen(Pen p) {
11909 			_activePen = p;
11910 			_outlineColor = p.color;
11911 
11912 			HPEN pen;
11913 			if(p.color.a == 0) {
11914 				pen = GetStockObject(NULL_PEN);
11915 			} else {
11916 				int style = PS_SOLID;
11917 				final switch(p.style) {
11918 					case Pen.Style.Solid:
11919 						style = PS_SOLID;
11920 					break;
11921 					case Pen.Style.Dashed:
11922 						style = PS_DASH;
11923 					break;
11924 					case Pen.Style.Dotted:
11925 						style = PS_DOT;
11926 					break;
11927 				}
11928 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11929 			}
11930 			auto orig = SelectObject(hdc, pen);
11931 			if(originalPen is null)
11932 				originalPen = orig;
11933 
11934 			if(currentPen !is null)
11935 				DeleteObject(currentPen);
11936 
11937 			currentPen = pen;
11938 
11939 			// the outline is like a foreground since it's done that way on X
11940 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11941 
11942 		}
11943 
11944 		@property void rasterOp(RasterOp op) {
11945 			int mode;
11946 			final switch(op) {
11947 				case RasterOp.normal:
11948 					mode = R2_COPYPEN;
11949 				break;
11950 				case RasterOp.xor:
11951 					mode = R2_XORPEN;
11952 				break;
11953 			}
11954 			SetROP2(hdc, mode);
11955 		}
11956 
11957 		HBRUSH originalBrush;
11958 		HBRUSH currentBrush;
11959 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11960 		@property void fillColor(Color c) {
11961 			if(c == _fillColor)
11962 				return;
11963 			_fillColor = c;
11964 			HBRUSH brush;
11965 			if(c.a == 0) {
11966 				brush = GetStockObject(HOLLOW_BRUSH);
11967 			} else {
11968 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11969 			}
11970 			auto orig = SelectObject(hdc, brush);
11971 			if(originalBrush is null)
11972 				originalBrush = orig;
11973 
11974 			if(currentBrush !is null)
11975 				DeleteObject(currentBrush);
11976 
11977 			currentBrush = brush;
11978 
11979 			// background color is NOT set because X doesn't draw text backgrounds
11980 			//   SetBkColor(hdc, RGB(255, 255, 255));
11981 		}
11982 
11983 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11984 			BITMAP bm;
11985 
11986 			HDC hdcMem = CreateCompatibleDC(hdc);
11987 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11988 
11989 			GetObject(i.handle, bm.sizeof, &bm);
11990 
11991 			// or should I AlphaBlend!??!?!
11992 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11993 
11994 			SelectObject(hdcMem, hbmOld);
11995 			DeleteDC(hdcMem);
11996 		}
11997 
11998 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11999 			BITMAP bm;
12000 
12001 			HDC hdcMem = CreateCompatibleDC(hdc);
12002 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
12003 
12004 			GetObject(s.handle, bm.sizeof, &bm);
12005 
12006 			version(CRuntime_DigitalMars) goto noalpha;
12007 
12008 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
12009 			if(s.enableAlpha) {
12010 				auto dw = w ? w : bm.bmWidth;
12011 				auto dh = h ? h : bm.bmHeight;
12012 				BLENDFUNCTION bf;
12013 				bf.BlendOp = AC_SRC_OVER;
12014 				bf.SourceConstantAlpha = 255;
12015 				bf.AlphaFormat = AC_SRC_ALPHA;
12016 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
12017 			} else {
12018 				noalpha:
12019 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
12020 			}
12021 
12022 			SelectObject(hdcMem, hbmOld);
12023 			DeleteDC(hdcMem);
12024 		}
12025 
12026 		Size textSize(scope const(char)[] text) {
12027 			bool dummyX;
12028 			if(text.length == 0) {
12029 				text = " ";
12030 				dummyX = true;
12031 			}
12032 			RECT rect;
12033 			WCharzBuffer buffer = WCharzBuffer(text);
12034 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
12035 			return Size(dummyX ? 0 : rect.right, rect.bottom);
12036 		}
12037 
12038 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
12039 			if(text.length && text[$-1] == '\n')
12040 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
12041 			if(text.length && text[$-1] == '\r')
12042 				text = text[0 .. $-1];
12043 
12044 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
12045 			if(x2 == 0 && y2 == 0) {
12046 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
12047 			} else {
12048 				RECT rect;
12049 				rect.left = x;
12050 				rect.top = y;
12051 				rect.right = x2;
12052 				rect.bottom = y2;
12053 
12054 				uint mode = DT_LEFT;
12055 				if(alignment & TextAlignment.Right)
12056 					mode = DT_RIGHT;
12057 				else if(alignment & TextAlignment.Center)
12058 					mode = DT_CENTER;
12059 
12060 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
12061 				if(alignment & TextAlignment.VerticalCenter)
12062 					mode |= DT_VCENTER | DT_SINGLELINE;
12063 
12064 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
12065 			}
12066 
12067 			/*
12068 			uint mode;
12069 
12070 			if(alignment & TextAlignment.Center)
12071 				mode = TA_CENTER;
12072 
12073 			SetTextAlign(hdc, mode);
12074 			*/
12075 		}
12076 
12077 		int fontHeight() {
12078 			TEXTMETRIC metric;
12079 			if(GetTextMetricsW(hdc, &metric)) {
12080 				return metric.tmHeight;
12081 			}
12082 
12083 			return 16; // idk just guessing here, maybe we should throw
12084 		}
12085 
12086 		void drawPixel(int x, int y) {
12087 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
12088 		}
12089 
12090 		// The basic shapes, outlined
12091 
12092 		void drawLine(int x1, int y1, int x2, int y2) {
12093 			MoveToEx(hdc, x1, y1, null);
12094 			LineTo(hdc, x2, y2);
12095 		}
12096 
12097 		void drawRectangle(int x, int y, int width, int height) {
12098 			// FIXME: with a wider pen this might not draw quite right. im not sure.
12099 			gdi.Rectangle(hdc, x, y, x + width, y + height);
12100 		}
12101 
12102 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
12103 			RoundRect(
12104 				hdc,
12105 				upperLeft.x, upperLeft.y,
12106 				lowerRight.x, lowerRight.y,
12107 				borderRadius, borderRadius
12108 			);
12109 		}
12110 
12111 		/// Arguments are the points of the bounding rectangle
12112 		void drawEllipse(int x1, int y1, int x2, int y2) {
12113 			Ellipse(hdc, x1, y1, x2, y2);
12114 		}
12115 
12116 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
12117 			//if(length > 360*64)
12118 				//length = 360*64;
12119 
12120 			if((start == 0 && length == 360*64)) {
12121 				drawEllipse(x1, y1, x1 + width, y1 + height);
12122 			} else {
12123 				import core.stdc.math;
12124 
12125 				bool clockwise = false;
12126 				if(length < 0) {
12127 					clockwise = true;
12128 					length = -length;
12129 				}
12130 
12131 				double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323;
12132 				double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323;
12133 
12134 				auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12135 				auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12136 				auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12137 				auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12138 
12139 				if(clockwise) {
12140 					auto t1 = c1;
12141 					auto t2 = c2;
12142 					c1 = c3;
12143 					c2 = c4;
12144 					c3 = t1;
12145 					c4 = t2;
12146 				}
12147 
12148 				//if(_activePen.color.a)
12149 					//Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12150 				//if(_fillColor.a)
12151 
12152 				Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12153 			}
12154 		}
12155 
12156 		void drawPolygon(Point[] vertexes) {
12157 			POINT[] points;
12158 			points.length = vertexes.length;
12159 
12160 			foreach(i, p; vertexes) {
12161 				points[i].x = p.x;
12162 				points[i].y = p.y;
12163 			}
12164 
12165 			Polygon(hdc, points.ptr, cast(int) points.length);
12166 		}
12167 	}
12168 
12169 
12170 	// Mix this into the SimpleWindow class
12171 	mixin template NativeSimpleWindowImplementation() {
12172 		int curHidden = 0; // counter
12173 		__gshared static bool[string] knownWinClasses;
12174 		static bool altPressed = false;
12175 
12176 		HANDLE oldCursor;
12177 
12178 		void hideCursor () {
12179 			if(curHidden == 0)
12180 				oldCursor = SetCursor(null);
12181 			++curHidden;
12182 		}
12183 
12184 		void showCursor () {
12185 			--curHidden;
12186 			if(curHidden == 0) {
12187 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
12188 			}
12189 		}
12190 
12191 
12192 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
12193 
12194 		void setMinSize (int minwidth, int minheight) {
12195 			minWidth = minwidth;
12196 			minHeight = minheight;
12197 		}
12198 		void setMaxSize (int maxwidth, int maxheight) {
12199 			maxWidth = maxwidth;
12200 			maxHeight = maxheight;
12201 		}
12202 
12203 		// FIXME i'm not sure that Windows has this functionality
12204 		// though it is nonessential anyway.
12205 		void setResizeGranularity (int granx, int grany) {}
12206 
12207 		ScreenPainter getPainter(bool manualInvalidations) {
12208 			return ScreenPainter(this, hwnd, manualInvalidations);
12209 		}
12210 
12211 		HBITMAP buffer;
12212 
12213 		void setTitle(string title) {
12214 			WCharzBuffer bfr = WCharzBuffer(title);
12215 			SetWindowTextW(hwnd, bfr.ptr);
12216 		}
12217 
12218 		string getTitle() {
12219 			auto len = GetWindowTextLengthW(hwnd);
12220 			if (!len)
12221 				return null;
12222 			wchar[256] tmpBuffer;
12223 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
12224 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12225 			auto str = buffer[0 .. len2];
12226 			return makeUtf8StringFromWindowsString(str);
12227 		}
12228 
12229 		void move(int x, int y) {
12230 			RECT rect;
12231 			GetWindowRect(hwnd, &rect);
12232 			// move it while maintaining the same size...
12233 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
12234 		}
12235 
12236 		void resize(int w, int h) {
12237 			RECT rect;
12238 			GetWindowRect(hwnd, &rect);
12239 
12240 			RECT client;
12241 			GetClientRect(hwnd, &client);
12242 
12243 			rect.right = rect.right - client.right + w;
12244 			rect.bottom = rect.bottom - client.bottom + h;
12245 
12246 			// same position, new size for the client rectangle
12247 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
12248 
12249 			updateOpenglViewportIfNeeded(w, h);
12250 		}
12251 
12252 		void moveResize (int x, int y, int w, int h) {
12253 			// what's given is the client rectangle, we need to adjust
12254 
12255 			RECT rect;
12256 			rect.left = x;
12257 			rect.top = y;
12258 			rect.right = w + x;
12259 			rect.bottom = h + y;
12260 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
12261 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
12262 
12263 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
12264 			updateOpenglViewportIfNeeded(w, h);
12265 			if (windowResized !is null) windowResized(w, h);
12266 		}
12267 
12268 		version(without_opengl) {} else {
12269 			HGLRC ghRC;
12270 			HDC ghDC;
12271 		}
12272 
12273 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
12274 			string cnamec;
12275 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
12276 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
12277 				cnamec = "DSimpleWindow";
12278 			} else {
12279 				cnamec = sdpyWindowClass;
12280 			}
12281 
12282 			WCharzBuffer cn = WCharzBuffer(cnamec);
12283 
12284 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
12285 
12286 			if(cnamec !in knownWinClasses) {
12287 				WNDCLASSEX wc;
12288 
12289 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
12290 				// to the object. Maybe.
12291 				wc.cbSize = wc.sizeof;
12292 				wc.cbClsExtra = 0;
12293 				wc.cbWndExtra = 0;
12294 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
12295 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
12296 				wc.hIcon = LoadIcon(hInstance, null);
12297 				wc.hInstance = hInstance;
12298 				wc.lpfnWndProc = &WndProc;
12299 				wc.lpszClassName = cn.ptr;
12300 				wc.hIconSm = null;
12301 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
12302 				if(!RegisterClassExW(&wc))
12303 					throw new WindowsApiException("RegisterClassExW", GetLastError());
12304 				knownWinClasses[cnamec] = true;
12305 			}
12306 
12307 			int style;
12308 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
12309 
12310 			// FIXME: windowType and customizationFlags
12311 			final switch(windowType) {
12312 				case WindowTypes.normal:
12313 					if(resizability == Resizability.fixedSize) {
12314 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
12315 					} else {
12316 						style = WS_OVERLAPPEDWINDOW;
12317 					}
12318 				break;
12319 				case WindowTypes.undecorated:
12320 					style = WS_POPUP | WS_SYSMENU;
12321 				break;
12322 				case WindowTypes.eventOnly:
12323 					_hidden = true;
12324 				break;
12325 				case WindowTypes.dropdownMenu:
12326 				case WindowTypes.popupMenu:
12327 				case WindowTypes.notification:
12328 					style = WS_POPUP;
12329 					flags |= WS_EX_NOACTIVATE;
12330 				break;
12331 				case WindowTypes.dialog:
12332 					style = WS_OVERLAPPEDWINDOW;
12333 				break;
12334 				case WindowTypes.nestedChild:
12335 					style = WS_CHILD;
12336 				break;
12337 				case WindowTypes.minimallyWrapped:
12338 					assert(0, "construct minimally wrapped through the other ctor overlad");
12339 			}
12340 
12341 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12342 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
12343 
12344 			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
12345 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
12346 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
12347 
12348 			if(!hwnd)
12349 				throw new WindowsApiException("CreateWindowEx", GetLastError());
12350 
12351 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12352 				setOpacity(255);
12353 
12354 			SimpleWindow.nativeMapping[hwnd] = this;
12355 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
12356 
12357 			if(windowType == WindowTypes.eventOnly)
12358 				return;
12359 
12360 			HDC hdc = GetDC(hwnd);
12361 
12362 			if(!hdc)
12363 				throw new WindowsApiException("GetDC", GetLastError());
12364 
12365 			version(without_opengl) {}
12366 			else {
12367 				if(opengl == OpenGlOptions.yes) {
12368 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12369 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12370 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
12371 					ghDC = hdc;
12372 					PIXELFORMATDESCRIPTOR pfd;
12373 
12374 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
12375 					pfd.nVersion = 1;
12376 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
12377 					pfd.dwLayerMask = PFD_MAIN_PLANE;
12378 					pfd.iPixelType = PFD_TYPE_RGBA;
12379 					pfd.cColorBits = 24;
12380 					pfd.cDepthBits = 24;
12381 					pfd.cAccumBits = 0;
12382 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
12383 
12384 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
12385 
12386 					if (pixelformat == 0)
12387 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
12388 
12389 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
12390 						throw new WindowsApiException("SetPixelFormat", GetLastError());
12391 
12392 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
12393 						// windoze is idiotic: we have to have OpenGL context to get function addresses
12394 						// so we will create fake context to get that stupid address
12395 						auto tmpcc = wglCreateContext(ghDC);
12396 						if (tmpcc !is null) {
12397 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
12398 							wglMakeCurrent(ghDC, tmpcc);
12399 							wglInitOtherFunctions();
12400 						}
12401 					}
12402 
12403 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
12404 						int[9] contextAttribs = [
12405 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
12406 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
12407 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
12408 							// for modern context, set "forward compatibility" flag too
12409 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
12410 							0/*None*/,
12411 						];
12412 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
12413 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
12414 							// activate fallback mode
12415 							// 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;
12416 							ghRC = wglCreateContext(ghDC);
12417 						}
12418 						if (ghRC is null)
12419 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
12420 					} else {
12421 						// try to do at least something
12422 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
12423 							sdpyOpenGLContextVersion = 0;
12424 							ghRC = wglCreateContext(ghDC);
12425 						}
12426 						if (ghRC is null)
12427 							throw new WindowsApiException("wglCreateContext", GetLastError());
12428 					}
12429 				}
12430 			}
12431 
12432 			if(opengl == OpenGlOptions.no) {
12433 				buffer = CreateCompatibleBitmap(hdc, width, height);
12434 
12435 				auto hdcBmp = CreateCompatibleDC(hdc);
12436 				// make sure it's filled with a blank slate
12437 				auto oldBmp = SelectObject(hdcBmp, buffer);
12438 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
12439 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
12440 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
12441 				SelectObject(hdcBmp, oldBmp);
12442 				SelectObject(hdcBmp, oldBrush);
12443 				SelectObject(hdcBmp, oldPen);
12444 				DeleteDC(hdcBmp);
12445 
12446 				bmpWidth = width;
12447 				bmpHeight = height;
12448 
12449 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
12450 			}
12451 
12452 			// We want the window's client area to match the image size
12453 			RECT rcClient, rcWindow;
12454 			POINT ptDiff;
12455 			GetClientRect(hwnd, &rcClient);
12456 			GetWindowRect(hwnd, &rcWindow);
12457 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
12458 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
12459 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
12460 
12461 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
12462 				ShowWindow(hwnd, SW_SHOWNORMAL);
12463 			} else {
12464 				_hidden = true;
12465 			}
12466 			this._visibleForTheFirstTimeCalled = false; // hack!
12467 		}
12468 
12469 
12470 		void dispose() {
12471 			if(buffer)
12472 				DeleteObject(buffer);
12473 		}
12474 
12475 		void closeWindow() {
12476 			if(ghRC) {
12477 				wglDeleteContext(ghRC);
12478 				ghRC = null;
12479 			}
12480 			DestroyWindow(hwnd);
12481 		}
12482 
12483 		bool setOpacity(ubyte alpha) {
12484 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
12485 		}
12486 
12487 		HANDLE currentCursor;
12488 
12489 		// returns zero if it recognized the event
12490 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
12491 			MouseEvent mouse;
12492 
12493 			void mouseEvent(bool isScreen, ulong mods) {
12494 				auto x = LOWORD(lParam);
12495 				auto y = HIWORD(lParam);
12496 				if(isScreen) {
12497 					POINT p;
12498 					p.x = x;
12499 					p.y = y;
12500 					ScreenToClient(hwnd, &p);
12501 					x = cast(ushort) p.x;
12502 					y = cast(ushort) p.y;
12503 				}
12504 
12505 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
12506 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
12507 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
12508 				}
12509 
12510 				mouse.x = x + offsetX;
12511 				mouse.y = y + offsetY;
12512 
12513 				wind.mdx(mouse);
12514 				mouse.modifierState = cast(int) mods;
12515 				mouse.window = wind;
12516 
12517 				if(wind.handleMouseEvent)
12518 					wind.handleMouseEvent(mouse);
12519 			}
12520 
12521 			switch(msg) {
12522 				case WM_GETMINMAXINFO:
12523 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12524 
12525 					if(wind.minWidth > 0) {
12526 						RECT rect;
12527 						rect.left = 100;
12528 						rect.top = 100;
12529 						rect.right = wind.minWidth + 100;
12530 						rect.bottom = wind.minHeight + 100;
12531 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12532 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12533 
12534 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12535 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12536 					}
12537 
12538 					if(wind.maxWidth < int.max) {
12539 						RECT rect;
12540 						rect.left = 100;
12541 						rect.top = 100;
12542 						rect.right = wind.maxWidth + 100;
12543 						rect.bottom = wind.maxHeight + 100;
12544 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12545 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12546 
12547 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12548 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12549 					}
12550 				break;
12551 				case WM_CHAR:
12552 					wchar c = cast(wchar) wParam;
12553 					if(wind.handleCharEvent)
12554 						wind.handleCharEvent(cast(dchar) c);
12555 				break;
12556 				  case WM_SETFOCUS:
12557 				  case WM_KILLFOCUS:
12558 					wind._focused = (msg == WM_SETFOCUS);
12559 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12560 					if(wind.onFocusChange)
12561 						wind.onFocusChange(msg == WM_SETFOCUS);
12562 				  break;
12563 
12564 				case WM_SYSKEYDOWN:
12565 					goto case;
12566 				case WM_SYSKEYUP:
12567 					if(lParam & (1 << 29)) {
12568 						goto case;
12569 					} else {
12570 						// no window has keyboard focus
12571 						goto default;
12572 					}
12573 				case WM_KEYDOWN:
12574 				case WM_KEYUP:
12575 					KeyEvent ev;
12576 					ev.key = cast(Key) wParam;
12577 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12578 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12579 
12580 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12581 
12582 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12583 						ev.modifierState |= ModifierState.shift;
12584 					//k8: this doesn't work; thanks for nothing, windows
12585 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12586 						ev.modifierState |= ModifierState.alt;*/
12587 					// this never seems to actually be set
12588 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12589 
12590 					if (wParam == 0x12) {
12591 						altPressed = (msg == WM_SYSKEYDOWN);
12592 					}
12593 
12594 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12595 						altPressed = false;
12596 					}
12597 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12598 
12599 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12600 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12601 						ev.modifierState |= ModifierState.ctrl;
12602 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12603 						ev.modifierState |= ModifierState.windows;
12604 					if(GetKeyState(Key.NumLock))
12605 						ev.modifierState |= ModifierState.numLock;
12606 					if(GetKeyState(Key.CapsLock))
12607 						ev.modifierState |= ModifierState.capsLock;
12608 
12609 					/+
12610 					// we always want to send the character too, so let's convert it
12611 					ubyte[256] state;
12612 					wchar[16] buffer;
12613 					GetKeyboardState(state.ptr);
12614 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12615 
12616 					foreach(dchar d; buffer) {
12617 						ev.character = d;
12618 						break;
12619 					}
12620 					+/
12621 
12622 					ev.window = wind;
12623 					if(wind.handleKeyEvent)
12624 						wind.handleKeyEvent(ev);
12625 				break;
12626 				case 0x020a /*WM_MOUSEWHEEL*/:
12627 					// send click
12628 					mouse.type = cast(MouseEventType) 1;
12629 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12630 					mouseEvent(true, LOWORD(wParam));
12631 
12632 					// also send release
12633 					mouse.type = cast(MouseEventType) 2;
12634 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12635 					mouseEvent(true, LOWORD(wParam));
12636 				break;
12637 				case WM_MOUSEMOVE:
12638 					mouse.type = cast(MouseEventType) 0;
12639 					mouseEvent(false, wParam);
12640 				break;
12641 				case WM_LBUTTONDOWN:
12642 				case WM_LBUTTONDBLCLK:
12643 					mouse.type = cast(MouseEventType) 1;
12644 					mouse.button = MouseButton.left;
12645 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12646 					mouseEvent(false, wParam);
12647 				break;
12648 				case WM_LBUTTONUP:
12649 					mouse.type = cast(MouseEventType) 2;
12650 					mouse.button = MouseButton.left;
12651 					mouseEvent(false, wParam);
12652 				break;
12653 				case WM_RBUTTONDOWN:
12654 				case WM_RBUTTONDBLCLK:
12655 					mouse.type = cast(MouseEventType) 1;
12656 					mouse.button = MouseButton.right;
12657 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12658 					mouseEvent(false, wParam);
12659 				break;
12660 				case WM_RBUTTONUP:
12661 					mouse.type = cast(MouseEventType) 2;
12662 					mouse.button = MouseButton.right;
12663 					mouseEvent(false, wParam);
12664 				break;
12665 				case WM_MBUTTONDOWN:
12666 				case WM_MBUTTONDBLCLK:
12667 					mouse.type = cast(MouseEventType) 1;
12668 					mouse.button = MouseButton.middle;
12669 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12670 					mouseEvent(false, wParam);
12671 				break;
12672 				case WM_MBUTTONUP:
12673 					mouse.type = cast(MouseEventType) 2;
12674 					mouse.button = MouseButton.middle;
12675 					mouseEvent(false, wParam);
12676 				break;
12677 				case WM_XBUTTONDOWN:
12678 				case WM_XBUTTONDBLCLK:
12679 					mouse.type = cast(MouseEventType) 1;
12680 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12681 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12682 					mouseEvent(false, wParam);
12683 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12684 				case WM_XBUTTONUP:
12685 					mouse.type = cast(MouseEventType) 2;
12686 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12687 					mouseEvent(false, wParam);
12688 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12689 
12690 				default: return 1;
12691 			}
12692 			return 0;
12693 		}
12694 
12695 		HWND hwnd;
12696 		private int oldWidth;
12697 		private int oldHeight;
12698 		private bool inSizeMove;
12699 
12700 		/++
12701 			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.
12702 
12703 			History:
12704 				Added November 23, 2021
12705 
12706 				Not fully stable, may be moved out of the impl struct.
12707 
12708 				Default value changed to `true` on February 15, 2021
12709 		+/
12710 		bool doLiveResizing = true;
12711 
12712 		package int bmpWidth;
12713 		package int bmpHeight;
12714 
12715 		// the extern(Windows) wndproc should just forward to this
12716 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12717 		try {
12718 			assert(hwnd is this.hwnd);
12719 
12720 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12721 			switch(msg) {
12722 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12723 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12724 					// The main things we can do are select, execute, close, or ignore
12725 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12726 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12727 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12728 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12729 
12730 					// returns the value in the *high order word* of the return value
12731 					// hence the << 16
12732 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12733 				case WM_SETCURSOR:
12734 					if(cast(HWND) wParam !is hwnd)
12735 						return 0; // further processing elsewhere
12736 
12737 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12738 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12739 						return 1;
12740 					} else {
12741 						return DefWindowProc(hwnd, msg, wParam, lParam);
12742 					}
12743 				//break;
12744 
12745 				case WM_CLOSE:
12746 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12747 				break;
12748 				case WM_DESTROY:
12749 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12750 					SimpleWindow.nativeMapping.remove(hwnd);
12751 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12752 
12753 					bool anyImportant = false;
12754 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12755 						if(w.beingOpenKeepsAppOpen) {
12756 							anyImportant = true;
12757 							break;
12758 						}
12759 					if(!anyImportant) {
12760 						PostQuitMessage(0);
12761 					}
12762 				break;
12763 				case 0x02E0 /*WM_DPICHANGED*/:
12764 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12765 
12766 					RECT* prcNewWindow = cast(RECT*)lParam;
12767 					// docs say this is the recommended position and we should honor it
12768 					SetWindowPos(hwnd,
12769 							null,
12770 							prcNewWindow.left,
12771 							prcNewWindow.top,
12772 							prcNewWindow.right - prcNewWindow.left,
12773 							prcNewWindow.bottom - prcNewWindow.top,
12774 							SWP_NOZORDER | SWP_NOACTIVATE);
12775 
12776 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12777 					// im not sure it is completely correct
12778 					// but without it the tabs and such do look weird as things change.
12779 					if(SystemParametersInfoForDpi) {
12780 						LOGFONT lfText;
12781 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12782 						HFONT hFontNew = CreateFontIndirect(&lfText);
12783 						if (hFontNew)
12784 						{
12785 							//DeleteObject(hFontOld);
12786 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12787 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12788 								return TRUE;
12789 							}
12790 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12791 						}
12792 					}
12793 
12794 					if(this.onDpiChanged)
12795 						this.onDpiChanged();
12796 				break;
12797 				case WM_ENTERIDLE:
12798 					// when a menu is up, it stops normal event processing (modal message loop)
12799 					// but this at least gives us a chance to SOMETIMES catch up
12800 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12801 					SimpleWindow.processAllCustomEvents;
12802 					SimpleWindow.processAllCustomEvents;
12803 					SleepEx(0, true);
12804 					break;
12805 				case WM_SIZE:
12806 					if(wParam == 1 /* SIZE_MINIMIZED */)
12807 						break;
12808 					_width = LOWORD(lParam);
12809 					_height = HIWORD(lParam);
12810 
12811 					// I want to avoid tearing in the windows (my code is inefficient
12812 					// so this is a hack around that) so while sizing, we don't trigger,
12813 					// but we do want to trigger on events like mazimize.
12814 					if(!inSizeMove || doLiveResizing)
12815 						goto size_changed;
12816 				break;
12817 				/+
12818 				case WM_SIZING:
12819 					writeln("size");
12820 				break;
12821 				+/
12822 				// I don't like the tearing I get when redrawing on WM_SIZE
12823 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12824 				// so instead it is going to redraw only at the end of a size.
12825 				case 0x0231: /* WM_ENTERSIZEMOVE */
12826 					inSizeMove = true;
12827 				break;
12828 				case 0x0232: /* WM_EXITSIZEMOVE */
12829 					inSizeMove = false;
12830 
12831 					size_changed:
12832 
12833 					// nothing relevant changed, don't bother redrawing
12834 					if(oldWidth == _width && oldHeight == _height) {
12835 						if(msg == 0x0232)
12836 							goto finalize_resize;
12837 						break;
12838 					}
12839 
12840 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12841 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12842 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12843 						// gotta get the double buffer bmp to match the window
12844 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12845 
12846 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12847 						if(resizability != Resizability.automaticallyScaleIfPossible)
12848 						if(_width > bmpWidth || _height > bmpHeight) {
12849 							auto hdc = GetDC(hwnd);
12850 							auto oldBuffer = buffer;
12851 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12852 
12853 							auto hdcBmp = CreateCompatibleDC(hdc);
12854 							auto oldBmp = SelectObject(hdcBmp, buffer);
12855 
12856 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12857 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12858 
12859 							/+
12860 							RECT r;
12861 							r.left = 0;
12862 							r.top = 0;
12863 							r.right = width;
12864 							r.bottom = height;
12865 							auto c = Color.green;
12866 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12867 							FillRect(hdcBmp, &r, brush);
12868 							DeleteObject(brush);
12869 							+/
12870 
12871 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12872 
12873 							bmpWidth = _width;
12874 							bmpHeight = _height;
12875 
12876 							SelectObject(hdcOldBmp, oldOldBmp);
12877 							DeleteDC(hdcOldBmp);
12878 
12879 							SelectObject(hdcBmp, oldBmp);
12880 							DeleteDC(hdcBmp);
12881 
12882 							ReleaseDC(hwnd, hdc);
12883 
12884 							DeleteObject(oldBuffer);
12885 						}
12886 					}
12887 
12888 					updateOpenglViewportIfNeeded(_width, _height);
12889 
12890 					if(resizability != Resizability.automaticallyScaleIfPossible)
12891 					if(windowResized !is null)
12892 						windowResized(_width, _height);
12893 
12894 					/+
12895 					if(inSizeMove) {
12896 						// SimpleWindow.processAllCustomEvents();
12897 						// SimpleWindow.processAllCustomEvents();
12898 
12899 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12900 						//sdpyPrintDebugString("redraw b");
12901 					} else {
12902 					+/ {
12903 						finalize_resize:
12904 						// when it is all done, make sure everything is freshly drawn or there might be
12905 						// weird bugs left.
12906 						SimpleWindow.processAllCustomEvents();
12907 						SimpleWindow.processAllCustomEvents();
12908 
12909 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12910 						// sdpyPrintDebugString("redraw");
12911 					}
12912 
12913 					oldWidth = this._width;
12914 					oldHeight = this._height;
12915 				break;
12916 				case WM_ERASEBKGND:
12917 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12918 					if (!this._visibleForTheFirstTimeCalled) {
12919 						this._visibleForTheFirstTimeCalled = true;
12920 						if (this.visibleForTheFirstTime !is null) {
12921 							this.visibleForTheFirstTime();
12922 						}
12923 					}
12924 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12925 					version(without_opengl) {} else {
12926 						if (openglMode == OpenGlOptions.yes) return 1;
12927 					}
12928 					// call windows default handler, so it can paint standard controls
12929 					goto default;
12930 				case WM_CTLCOLORBTN:
12931 				case WM_CTLCOLORSTATIC:
12932 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12933 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12934 					GetSysColorBrush(COLOR_3DFACE);
12935 				//break;
12936 				case WM_SHOWWINDOW:
12937 					this._visible = (wParam != 0);
12938 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12939 						this._visibleForTheFirstTimeCalled = true;
12940 						if (this.visibleForTheFirstTime !is null) {
12941 							this.visibleForTheFirstTime();
12942 						}
12943 					}
12944 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12945 					break;
12946 				case WM_PAINT: {
12947 					if (!this._visibleForTheFirstTimeCalled) {
12948 						this._visibleForTheFirstTimeCalled = true;
12949 						if (this.visibleForTheFirstTime !is null) {
12950 							this.visibleForTheFirstTime();
12951 						}
12952 					}
12953 
12954 					BITMAP bm;
12955 					PAINTSTRUCT ps;
12956 
12957 					HDC hdc = BeginPaint(hwnd, &ps);
12958 
12959 					if(openglMode == OpenGlOptions.no) {
12960 
12961 						HDC hdcMem = CreateCompatibleDC(hdc);
12962 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12963 
12964 						GetObject(buffer, bm.sizeof, &bm);
12965 
12966 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12967 						if(resizability == Resizability.automaticallyScaleIfPossible)
12968 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12969 						else
12970 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12971 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12972 
12973 						SelectObject(hdcMem, hbmOld);
12974 						DeleteDC(hdcMem);
12975 						EndPaint(hwnd, &ps);
12976 					} else {
12977 						EndPaint(hwnd, &ps);
12978 						version(without_opengl) {} else
12979 							redrawOpenGlSceneSoon();
12980 					}
12981 				} break;
12982 				  default:
12983 					return DefWindowProc(hwnd, msg, wParam, lParam);
12984 			}
12985 			 return 0;
12986 
12987 		}
12988 		catch(Throwable t) {
12989 			sdpyPrintDebugString(t.toString);
12990 			return 0;
12991 		}
12992 		}
12993 	}
12994 
12995 	mixin template NativeImageImplementation() {
12996 		HBITMAP handle;
12997 		ubyte* rawData;
12998 
12999 	final:
13000 
13001 		Color getPixel(int x, int y) @system {
13002 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13003 			// remember, bmps are upside down
13004 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13005 
13006 			Color c;
13007 			if(enableAlpha)
13008 				c.a = rawData[offset + 3];
13009 			else
13010 				c.a = 255;
13011 			c.b = rawData[offset + 0];
13012 			c.g = rawData[offset + 1];
13013 			c.r = rawData[offset + 2];
13014 			c.unPremultiply();
13015 			return c;
13016 		}
13017 
13018 		void setPixel(int x, int y, Color c) @system {
13019 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13020 			// remember, bmps are upside down
13021 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13022 
13023 			if(enableAlpha)
13024 				c.premultiply();
13025 
13026 			rawData[offset + 0] = c.b;
13027 			rawData[offset + 1] = c.g;
13028 			rawData[offset + 2] = c.r;
13029 			if(enableAlpha)
13030 				rawData[offset + 3] = c.a;
13031 		}
13032 
13033 		void convertToRgbaBytes(ubyte[] where) @system {
13034 			assert(where.length == this.width * this.height * 4);
13035 
13036 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13037 			int idx = 0;
13038 			int offset = itemsPerLine * (height - 1);
13039 			// remember, bmps are upside down
13040 			for(int y = height - 1; y >= 0; y--) {
13041 				auto offsetStart = offset;
13042 				for(int x = 0; x < width; x++) {
13043 					where[idx + 0] = rawData[offset + 2]; // r
13044 					where[idx + 1] = rawData[offset + 1]; // g
13045 					where[idx + 2] = rawData[offset + 0]; // b
13046 					if(enableAlpha) {
13047 						where[idx + 3] = rawData[offset + 3]; // a
13048 						unPremultiplyRgba(where[idx .. idx + 4]);
13049 						offset++;
13050 					} else
13051 						where[idx + 3] = 255; // a
13052 					idx += 4;
13053 					offset += 3;
13054 				}
13055 
13056 				offset = offsetStart - itemsPerLine;
13057 			}
13058 		}
13059 
13060 		void setFromRgbaBytes(in ubyte[] what) @system {
13061 			assert(what.length == this.width * this.height * 4);
13062 
13063 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
13064 			int idx = 0;
13065 			int offset = itemsPerLine * (height - 1);
13066 			// remember, bmps are upside down
13067 			for(int y = height - 1; y >= 0; y--) {
13068 				auto offsetStart = offset;
13069 				for(int x = 0; x < width; x++) {
13070 					if(enableAlpha) {
13071 						auto a = what[idx + 3];
13072 
13073 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
13074 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
13075 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
13076 						rawData[offset + 3] = a; // a
13077 						//premultiplyBgra(rawData[offset .. offset + 4]);
13078 						offset++;
13079 					} else {
13080 						rawData[offset + 2] = what[idx + 0]; // r
13081 						rawData[offset + 1] = what[idx + 1]; // g
13082 						rawData[offset + 0] = what[idx + 2]; // b
13083 					}
13084 					idx += 4;
13085 					offset += 3;
13086 				}
13087 
13088 				offset = offsetStart - itemsPerLine;
13089 			}
13090 		}
13091 
13092 
13093 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
13094 			BITMAPINFO infoheader;
13095 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
13096 			infoheader.bmiHeader.biWidth = width;
13097 			infoheader.bmiHeader.biHeight = height;
13098 			infoheader.bmiHeader.biPlanes = 1;
13099 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
13100 			infoheader.bmiHeader.biCompression = BI_RGB;
13101 
13102 			handle = CreateDIBSection(
13103 				null,
13104 				&infoheader,
13105 				DIB_RGB_COLORS,
13106 				cast(void**) &rawData,
13107 				null,
13108 				0);
13109 			if(handle is null)
13110 				throw new WindowsApiException("create image failed", GetLastError());
13111 
13112 		}
13113 
13114 		void dispose() {
13115 			DeleteObject(handle);
13116 		}
13117 	}
13118 
13119 	enum KEY_ESCAPE = 27;
13120 }
13121 version(X11) {
13122 	/// This is the default font used. You might change this before doing anything else with
13123 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
13124 	/// for cross-platform compatibility.
13125 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13126 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13127 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
13128 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
13129 
13130 	alias int delegate(XEvent) NativeEventHandler;
13131 	alias Window NativeWindowHandle;
13132 
13133 	enum KEY_ESCAPE = 9;
13134 
13135 	mixin template NativeScreenPainterImplementation() {
13136 		Display* display;
13137 		Drawable d;
13138 		Drawable destiny;
13139 
13140 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
13141 		GC gc;
13142 
13143 		__gshared bool fontAttempted;
13144 
13145 		__gshared XFontStruct* defaultfont;
13146 		__gshared XFontSet defaultfontset;
13147 
13148 		XFontStruct* font;
13149 		XFontSet fontset;
13150 
13151 		void create(PaintingHandle window) {
13152 			this.display = XDisplayConnection.get();
13153 
13154 			Drawable buffer = None;
13155 			if(auto sw = cast(SimpleWindow) this.window) {
13156 				buffer = sw.impl.buffer;
13157 				this.destiny = cast(Drawable) window;
13158 			} else {
13159 				buffer = cast(Drawable) window;
13160 				this.destiny = None;
13161 			}
13162 
13163 			this.d = cast(Drawable) buffer;
13164 
13165 			auto dgc = DefaultGC(display, DefaultScreen(display));
13166 
13167 			this.gc = XCreateGC(display, d, 0, null);
13168 
13169 			XCopyGC(display, dgc, 0xffffffff, this.gc);
13170 
13171 			ensureDefaultFontLoaded();
13172 
13173 			font = defaultfont;
13174 			fontset = defaultfontset;
13175 
13176 			if(font) {
13177 				XSetFont(display, gc, font.fid);
13178 			}
13179 		}
13180 
13181 		static void ensureDefaultFontLoaded() {
13182 			if(!fontAttempted) {
13183 				auto display = XDisplayConnection.get;
13184 				auto font = XLoadQueryFont(display, xfontstr.ptr);
13185 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
13186 				if(font is null) {
13187 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
13188 					font = XLoadQueryFont(display, xfontstr.ptr);
13189 				}
13190 
13191 				char** lol;
13192 				int lol2;
13193 				char* lol3;
13194 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
13195 
13196 				fontAttempted = true;
13197 
13198 				defaultfont = font;
13199 				defaultfontset = fontset;
13200 			}
13201 		}
13202 
13203 		arsd.color.Rectangle _clipRectangle;
13204 		void setClipRectangle(int x, int y, int width, int height) {
13205 			auto old = _clipRectangle;
13206 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
13207 			if(old == _clipRectangle)
13208 				return;
13209 
13210 			if(width == 0 || height == 0) {
13211 				XSetClipMask(display, gc, None);
13212 
13213 				if(xrenderPicturePainter) {
13214 
13215 					XRectangle[1] rects;
13216 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
13217 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13218 				}
13219 
13220 				version(with_xft) {
13221 					if(xftFont is null || xftDraw is null)
13222 						return;
13223 					XftDrawSetClip(xftDraw, null);
13224 				}
13225 			} else {
13226 				XRectangle[1] rects;
13227 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
13228 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
13229 
13230 				if(xrenderPicturePainter)
13231 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13232 
13233 				version(with_xft) {
13234 					if(xftFont is null || xftDraw is null)
13235 						return;
13236 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
13237 				}
13238 			}
13239 		}
13240 
13241 		version(with_xft) {
13242 			XftFont* xftFont;
13243 			XftDraw* xftDraw;
13244 
13245 			XftColor xftColor;
13246 
13247 			void updateXftColor() {
13248 				if(xftFont is null)
13249 					return;
13250 
13251 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
13252 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
13253 
13254 				XftColorAllocValue(
13255 					display,
13256 					DefaultVisual(display, DefaultScreen(display)),
13257 					DefaultColormap(display, 0),
13258 					&colorIn,
13259 					&xftColor
13260 				);
13261 			}
13262 		}
13263 
13264 		private OperatingSystemFont _activeFont;
13265 		void setFont(OperatingSystemFont font) {
13266 			_activeFont = font;
13267 			version(with_xft) {
13268 				if(font && font.isXft && font.xftFont)
13269 					this.xftFont = font.xftFont;
13270 				else
13271 					this.xftFont = null;
13272 
13273 				if(this.xftFont) {
13274 					if(xftDraw is null) {
13275 						xftDraw = XftDrawCreate(
13276 							display,
13277 							d,
13278 							DefaultVisual(display, DefaultScreen(display)),
13279 							DefaultColormap(display, 0)
13280 						);
13281 
13282 						updateXftColor();
13283 					}
13284 
13285 					return;
13286 				}
13287 			}
13288 
13289 			if(font && font.font) {
13290 				this.font = font.font;
13291 				this.fontset = font.fontset;
13292 				XSetFont(display, gc, font.font.fid);
13293 			} else {
13294 				this.font = defaultfont;
13295 				this.fontset = defaultfontset;
13296 			}
13297 
13298 		}
13299 
13300 		private Picture xrenderPicturePainter;
13301 
13302 		bool manualInvalidations;
13303 		void invalidateRect(Rectangle invalidRect) {
13304 			// FIXME if manualInvalidations
13305 		}
13306 
13307 		void dispose() {
13308 			this.rasterOp = RasterOp.normal;
13309 
13310 			if(xrenderPicturePainter) {
13311 				XRenderFreePicture(display, xrenderPicturePainter);
13312 				xrenderPicturePainter = None;
13313 			}
13314 
13315 			// FIXME: this.window.width/height is probably wrong
13316 
13317 			// src x,y     then dest x, y
13318 			if(destiny != None) {
13319 				// FIXME: if manual invalidations we can actually only copy some of the area.
13320 				// if(manualInvalidations)
13321 				XSetClipMask(display, gc, None);
13322 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
13323 			}
13324 
13325 			XFreeGC(display, gc);
13326 
13327 			version(with_xft)
13328 			if(xftDraw) {
13329 				XftDrawDestroy(xftDraw);
13330 				xftDraw = null;
13331 			}
13332 
13333 			/+
13334 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
13335 			if(font && font !is defaultfont) {
13336 				XFreeFont(display, font);
13337 				font = null;
13338 			}
13339 			if(fontset && fontset !is defaultfontset) {
13340 				XFreeFontSet(display, fontset);
13341 				fontset = null;
13342 			}
13343 			+/
13344 			XFlush(display);
13345 
13346 			if(window.paintingFinishedDg !is null)
13347 				window.paintingFinishedDg()();
13348 		}
13349 
13350 		bool backgroundIsNotTransparent = true;
13351 		bool foregroundIsNotTransparent = true;
13352 
13353 		bool _penInitialized = false;
13354 		Pen _activePen;
13355 
13356 		Color _outlineColor;
13357 		Color _fillColor;
13358 
13359 		@property void pen(Pen p) {
13360 			if(_penInitialized && p == _activePen) {
13361 				return;
13362 			}
13363 			_penInitialized = true;
13364 			_activePen = p;
13365 			_outlineColor = p.color;
13366 
13367 			int style;
13368 
13369 			byte dashLength;
13370 
13371 			final switch(p.style) {
13372 				case Pen.Style.Solid:
13373 					style = 0 /*LineSolid*/;
13374 				break;
13375 				case Pen.Style.Dashed:
13376 					style = 1 /*LineOnOffDash*/;
13377 					dashLength = 4;
13378 				break;
13379 				case Pen.Style.Dotted:
13380 					style = 1 /*LineOnOffDash*/;
13381 					dashLength = 1;
13382 				break;
13383 			}
13384 
13385 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
13386 			if(dashLength)
13387 				XSetDashes(display, gc, 0, &dashLength, 1);
13388 
13389 			if(p.color.a == 0) {
13390 				foregroundIsNotTransparent = false;
13391 				return;
13392 			}
13393 
13394 			foregroundIsNotTransparent = true;
13395 
13396 			XSetForeground(display, gc, colorToX(p.color, display));
13397 
13398 			version(with_xft)
13399 				updateXftColor();
13400 		}
13401 
13402 		RasterOp _currentRasterOp;
13403 		bool _currentRasterOpInitialized = false;
13404 		@property void rasterOp(RasterOp op) {
13405 			if(_currentRasterOpInitialized && _currentRasterOp == op)
13406 				return;
13407 			_currentRasterOp = op;
13408 			_currentRasterOpInitialized = true;
13409 			int mode;
13410 			final switch(op) {
13411 				case RasterOp.normal:
13412 					mode = GXcopy;
13413 				break;
13414 				case RasterOp.xor:
13415 					mode = GXxor;
13416 				break;
13417 			}
13418 			XSetFunction(display, gc, mode);
13419 		}
13420 
13421 
13422 		bool _fillColorInitialized = false;
13423 
13424 		@property void fillColor(Color c) {
13425 			if(_fillColorInitialized && _fillColor == c)
13426 				return; // already good, no need to waste time calling it
13427 			_fillColor = c;
13428 			_fillColorInitialized = true;
13429 			if(c.a == 0) {
13430 				backgroundIsNotTransparent = false;
13431 				return;
13432 			}
13433 
13434 			backgroundIsNotTransparent = true;
13435 
13436 			XSetBackground(display, gc, colorToX(c, display));
13437 
13438 		}
13439 
13440 		void swapColors() {
13441 			auto tmp = _fillColor;
13442 			fillColor = _outlineColor;
13443 			auto newPen = _activePen;
13444 			newPen.color = tmp;
13445 			pen(newPen);
13446 		}
13447 
13448 		uint colorToX(Color c, Display* display) {
13449 			auto visual = DefaultVisual(display, DefaultScreen(display));
13450 			import core.bitop;
13451 			uint color = 0;
13452 			{
13453 			auto startBit = bsf(visual.red_mask);
13454 			auto lastBit = bsr(visual.red_mask);
13455 			auto r = cast(uint) c.r;
13456 			r >>= 7 - (lastBit - startBit);
13457 			r <<= startBit;
13458 			color |= r;
13459 			}
13460 			{
13461 			auto startBit = bsf(visual.green_mask);
13462 			auto lastBit = bsr(visual.green_mask);
13463 			auto g = cast(uint) c.g;
13464 			g >>= 7 - (lastBit - startBit);
13465 			g <<= startBit;
13466 			color |= g;
13467 			}
13468 			{
13469 			auto startBit = bsf(visual.blue_mask);
13470 			auto lastBit = bsr(visual.blue_mask);
13471 			auto b = cast(uint) c.b;
13472 			b >>= 7 - (lastBit - startBit);
13473 			b <<= startBit;
13474 			color |= b;
13475 			}
13476 
13477 
13478 
13479 			return color;
13480 		}
13481 
13482 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
13483 			// source x, source y
13484 			if(ix >= i.width) return;
13485 			if(iy >= i.height) return;
13486 			if(ix + w > i.width) w = i.width - ix;
13487 			if(iy + h > i.height) h = i.height - iy;
13488 			if(i.usingXshm)
13489 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
13490 			else
13491 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
13492 		}
13493 
13494 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
13495 			if(s.enableAlpha) {
13496 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
13497 				if(this.xrenderPicturePainter == None) {
13498 					XRenderPictureAttributes attrs;
13499 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
13500 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
13501 
13502 					// need to initialize the clip
13503 					XRectangle[1] rects;
13504 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
13505 
13506 					if(_clipRectangle != Rectangle.init)
13507 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13508 				}
13509 
13510 				XRenderComposite(
13511 					display,
13512 					3, // PicOpOver
13513 					s.xrenderPicture,
13514 					None,
13515 					this.xrenderPicturePainter,
13516 					ix,
13517 					iy,
13518 					0,
13519 					0,
13520 					x,
13521 					y,
13522 					w ? w : s.width,
13523 					h ? h : s.height
13524 				);
13525 			} else {
13526 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13527 			}
13528 		}
13529 
13530 		int fontHeight() {
13531 			version(with_xft)
13532 				if(xftFont !is null)
13533 					return xftFont.height;
13534 			if(font)
13535 				return font.max_bounds.ascent + font.max_bounds.descent;
13536 			return 12; // pretty common default...
13537 		}
13538 
13539 		int textWidth(in char[] line) {
13540 			version(with_xft)
13541 			if(xftFont) {
13542 				if(line.length == 0)
13543 					return 0;
13544 				XGlyphInfo extents;
13545 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13546 				return extents.width;
13547 			}
13548 
13549 			if(fontset) {
13550 				if(line.length == 0)
13551 					return 0;
13552 				XRectangle rect;
13553 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13554 
13555 				return rect.width;
13556 			}
13557 
13558 			if(font)
13559 				// FIXME: unicode
13560 				return XTextWidth( font, line.ptr, cast(int) line.length);
13561 			else
13562 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13563 		}
13564 
13565 		Size textSize(in char[] text) {
13566 			auto maxWidth = 0;
13567 			auto lineHeight = fontHeight;
13568 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13569 			foreach(line; text.split('\n')) {
13570 				int textWidth = this.textWidth(line);
13571 				if(textWidth > maxWidth)
13572 					maxWidth = textWidth;
13573 				h += lineHeight + 4;
13574 			}
13575 			return Size(maxWidth, h);
13576 		}
13577 
13578 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13579 			const(char)[] text;
13580 			version(with_xft)
13581 			if(xftFont) {
13582 				text = originalText;
13583 				goto loaded;
13584 			}
13585 
13586 			if(fontset)
13587 				text = originalText;
13588 			else {
13589 				text.reserve(originalText.length);
13590 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13591 				// then strip the rest so there isn't garbage
13592 				foreach(dchar ch; originalText)
13593 					if(ch < 256)
13594 						text ~= cast(ubyte) ch;
13595 					else
13596 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13597 			}
13598 			loaded:
13599 			if(text.length == 0)
13600 				return;
13601 
13602 			// FIXME: should we clip it to the bounding box?
13603 			int textHeight = fontHeight;
13604 
13605 			auto lines = text.split('\n');
13606 
13607 			const lineHeight = textHeight;
13608 			textHeight *= lines.length;
13609 
13610 			int cy = y;
13611 
13612 			if(alignment & TextAlignment.VerticalBottom) {
13613 				if(y2 <= 0)
13614 					return;
13615 				auto h = y2 - y;
13616 				if(h > textHeight) {
13617 					cy += h - textHeight;
13618 					cy -= lineHeight / 2;
13619 				}
13620 			} else if(alignment & TextAlignment.VerticalCenter) {
13621 				if(y2 <= 0)
13622 					return;
13623 				auto h = y2 - y;
13624 				if(textHeight < h) {
13625 					cy += (h - textHeight) / 2;
13626 					//cy -= lineHeight / 4;
13627 				}
13628 			}
13629 
13630 			foreach(line; text.split('\n')) {
13631 				int textWidth = this.textWidth(line);
13632 
13633 				int px = x, py = cy;
13634 
13635 				if(alignment & TextAlignment.Center) {
13636 					if(x2 <= 0)
13637 						return;
13638 					auto w = x2 - x;
13639 					if(w > textWidth)
13640 						px += (w - textWidth) / 2;
13641 				} else if(alignment & TextAlignment.Right) {
13642 					if(x2 <= 0)
13643 						return;
13644 					auto pos = x2 - textWidth;
13645 					if(pos > x)
13646 						px = pos;
13647 				}
13648 
13649 				version(with_xft)
13650 				if(xftFont) {
13651 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13652 
13653 					goto carry_on;
13654 				}
13655 
13656 				if(fontset)
13657 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13658 				else
13659 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13660 				carry_on:
13661 				cy += lineHeight + 4;
13662 			}
13663 		}
13664 
13665 		void drawPixel(int x, int y) {
13666 			XDrawPoint(display, d, gc, x, y);
13667 		}
13668 
13669 		// The basic shapes, outlined
13670 
13671 		void drawLine(int x1, int y1, int x2, int y2) {
13672 			if(foregroundIsNotTransparent)
13673 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13674 		}
13675 
13676 		void drawRectangle(int x, int y, int width, int height) {
13677 			if(backgroundIsNotTransparent) {
13678 				swapColors();
13679 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13680 				swapColors();
13681 			}
13682 			// 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
13683 			if(foregroundIsNotTransparent)
13684 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13685 		}
13686 
13687 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
13688 			int[4] radii = borderRadius;
13689 			auto r = Rectangle(upperLeft, lowerRight);
13690 
13691 			if(backgroundIsNotTransparent) {
13692 				swapColors();
13693 				// FIXME these overlap and thus draw the pixels multiple times
13694 				XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius);
13695 				XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13696 				swapColors();
13697 			}
13698 
13699 			drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top);
13700 			drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1);
13701 			drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2);
13702 			drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2);
13703 
13704 			//drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13705 
13706 			drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64);
13707 			drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64);
13708 			drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64);
13709 			drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64);
13710 		}
13711 
13712 
13713 		/// Arguments are the points of the bounding rectangle
13714 		void drawEllipse(int x1, int y1, int x2, int y2) {
13715 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13716 		}
13717 
13718 		// NOTE: start and finish are in units of degrees * 64
13719 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
13720 			if(backgroundIsNotTransparent) {
13721 				swapColors();
13722 				XFillArc(display, d, gc, x1, y1, width, height, start, length);
13723 				swapColors();
13724 			}
13725 			if(foregroundIsNotTransparent) {
13726 				XDrawArc(display, d, gc, x1, y1, width, height, start, length);
13727 
13728 				// Windows draws the straight lines on the edges too so FIXME sort of
13729 			}
13730 		}
13731 
13732 		void drawPolygon(Point[] vertexes) {
13733 			XPoint[16] pointsBuffer;
13734 			XPoint[] points;
13735 			if(vertexes.length <= pointsBuffer.length)
13736 				points = pointsBuffer[0 .. vertexes.length];
13737 			else
13738 				points.length = vertexes.length;
13739 
13740 			foreach(i, p; vertexes) {
13741 				points[i].x = cast(short) p.x;
13742 				points[i].y = cast(short) p.y;
13743 			}
13744 
13745 			if(backgroundIsNotTransparent) {
13746 				swapColors();
13747 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13748 				swapColors();
13749 			}
13750 			if(foregroundIsNotTransparent) {
13751 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13752 			}
13753 		}
13754 	}
13755 
13756 	/* XRender { */
13757 
13758 	struct XRenderColor {
13759 		ushort red;
13760 		ushort green;
13761 		ushort blue;
13762 		ushort alpha;
13763 	}
13764 
13765 	alias Picture = XID;
13766 	alias PictFormat = XID;
13767 
13768 	struct XGlyphInfo {
13769 		ushort width;
13770 		ushort height;
13771 		short x;
13772 		short y;
13773 		short xOff;
13774 		short yOff;
13775 	}
13776 
13777 struct XRenderDirectFormat {
13778     short   red;
13779     short   redMask;
13780     short   green;
13781     short   greenMask;
13782     short   blue;
13783     short   blueMask;
13784     short   alpha;
13785     short   alphaMask;
13786 }
13787 
13788 struct XRenderPictFormat {
13789     PictFormat		id;
13790     int			type;
13791     int			depth;
13792     XRenderDirectFormat	direct;
13793     Colormap		colormap;
13794 }
13795 
13796 enum PictFormatID	=   (1 << 0);
13797 enum PictFormatType	=   (1 << 1);
13798 enum PictFormatDepth	=   (1 << 2);
13799 enum PictFormatRed	=   (1 << 3);
13800 enum PictFormatRedMask  =(1 << 4);
13801 enum PictFormatGreen	=   (1 << 5);
13802 enum PictFormatGreenMask=(1 << 6);
13803 enum PictFormatBlue	=   (1 << 7);
13804 enum PictFormatBlueMask =(1 << 8);
13805 enum PictFormatAlpha	=   (1 << 9);
13806 enum PictFormatAlphaMask=(1 << 10);
13807 enum PictFormatColormap =(1 << 11);
13808 
13809 struct XRenderPictureAttributes {
13810 	int 		repeat;
13811 	Picture		alpha_map;
13812 	int			alpha_x_origin;
13813 	int			alpha_y_origin;
13814 	int			clip_x_origin;
13815 	int			clip_y_origin;
13816 	Pixmap		clip_mask;
13817 	Bool		graphics_exposures;
13818 	int			subwindow_mode;
13819 	int			poly_edge;
13820 	int			poly_mode;
13821 	Atom		dither;
13822 	Bool		component_alpha;
13823 }
13824 
13825 alias int XFixed;
13826 
13827 struct XPointFixed {
13828     XFixed  x, y;
13829 }
13830 
13831 struct XCircle {
13832     XFixed x;
13833     XFixed y;
13834     XFixed radius;
13835 }
13836 
13837 struct XTransform {
13838     XFixed[3][3]  matrix;
13839 }
13840 
13841 struct XFilters {
13842     int	    nfilter;
13843     char    **filter;
13844     int	    nalias;
13845     short   *alias_;
13846 }
13847 
13848 struct XIndexValue {
13849     c_ulong    pixel;
13850     ushort   red, green, blue, alpha;
13851 }
13852 
13853 struct XAnimCursor {
13854     Cursor	    cursor;
13855     c_ulong   delay;
13856 }
13857 
13858 struct XLinearGradient {
13859     XPointFixed p1;
13860     XPointFixed p2;
13861 }
13862 
13863 struct XRadialGradient {
13864     XCircle inner;
13865     XCircle outer;
13866 }
13867 
13868 struct XConicalGradient {
13869     XPointFixed center;
13870     XFixed angle; /* in degrees */
13871 }
13872 
13873 enum PictStandardARGB32  = 0;
13874 enum PictStandardRGB24   = 1;
13875 enum PictStandardA8	 =  2;
13876 enum PictStandardA4	 =  3;
13877 enum PictStandardA1	 =  4;
13878 enum PictStandardNUM	 =  5;
13879 
13880 interface XRender {
13881 extern(C) @nogc:
13882 
13883 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13884 
13885 	Status XRenderQueryVersion (Display *dpy,
13886 			int     *major_versionp,
13887 			int     *minor_versionp);
13888 
13889 	Status XRenderQueryFormats (Display *dpy);
13890 
13891 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13892 
13893 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13894 
13895 	XRenderPictFormat *
13896 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13897 
13898 	XRenderPictFormat *
13899 		XRenderFindFormat (Display			*dpy,
13900 				c_ulong		mask,
13901 				const XRenderPictFormat	*templ,
13902 				int				count);
13903 	XRenderPictFormat *
13904 		XRenderFindStandardFormat (Display		*dpy,
13905 				int			format);
13906 
13907 	XIndexValue *
13908 		XRenderQueryPictIndexValues(Display			*dpy,
13909 				const XRenderPictFormat	*format,
13910 				int				*num);
13911 
13912 	Picture XRenderCreatePicture(
13913 		Display *dpy,
13914 		Drawable drawable,
13915 		const XRenderPictFormat *format,
13916 		c_ulong valuemask,
13917 		const XRenderPictureAttributes *attributes);
13918 
13919 	void XRenderChangePicture (Display				*dpy,
13920 				Picture				picture,
13921 				c_ulong			valuemask,
13922 				const XRenderPictureAttributes  *attributes);
13923 
13924 	void
13925 		XRenderSetPictureClipRectangles (Display	    *dpy,
13926 				Picture	    picture,
13927 				int		    xOrigin,
13928 				int		    yOrigin,
13929 				const XRectangle *rects,
13930 				int		    n);
13931 
13932 	void
13933 		XRenderSetPictureClipRegion (Display	    *dpy,
13934 				Picture	    picture,
13935 				Region	    r);
13936 
13937 	void
13938 		XRenderSetPictureTransform (Display	    *dpy,
13939 				Picture	    picture,
13940 				XTransform	    *transform);
13941 
13942 	void
13943 		XRenderFreePicture (Display                   *dpy,
13944 				Picture                   picture);
13945 
13946 	void
13947 		XRenderComposite (Display   *dpy,
13948 				int	    op,
13949 				Picture   src,
13950 				Picture   mask,
13951 				Picture   dst,
13952 				int	    src_x,
13953 				int	    src_y,
13954 				int	    mask_x,
13955 				int	    mask_y,
13956 				int	    dst_x,
13957 				int	    dst_y,
13958 				uint	width,
13959 				uint	height);
13960 
13961 
13962 	Picture XRenderCreateSolidFill (Display *dpy,
13963 			const XRenderColor *color);
13964 
13965 	Picture XRenderCreateLinearGradient (Display *dpy,
13966 			const XLinearGradient *gradient,
13967 			const XFixed *stops,
13968 			const XRenderColor *colors,
13969 			int nstops);
13970 
13971 	Picture XRenderCreateRadialGradient (Display *dpy,
13972 			const XRadialGradient *gradient,
13973 			const XFixed *stops,
13974 			const XRenderColor *colors,
13975 			int nstops);
13976 
13977 	Picture XRenderCreateConicalGradient (Display *dpy,
13978 			const XConicalGradient *gradient,
13979 			const XFixed *stops,
13980 			const XRenderColor *colors,
13981 			int nstops);
13982 
13983 
13984 
13985 	Cursor
13986 		XRenderCreateCursor (Display	    *dpy,
13987 				Picture	    source,
13988 				uint   x,
13989 				uint   y);
13990 
13991 	XFilters *
13992 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13993 
13994 	void
13995 		XRenderSetPictureFilter (Display    *dpy,
13996 				Picture    picture,
13997 				const char *filter,
13998 				XFixed	    *params,
13999 				int	    nparams);
14000 
14001 	Cursor
14002 		XRenderCreateAnimCursor (Display	*dpy,
14003 				int		ncursor,
14004 				XAnimCursor	*cursors);
14005 }
14006 
14007 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
14008 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
14009 
14010 	/* XRender } */
14011 
14012 	/* Xrandr { */
14013 
14014 struct XRRMonitorInfo {
14015     Atom name;
14016     Bool primary;
14017     Bool automatic;
14018     int noutput;
14019     int x;
14020     int y;
14021     int width;
14022     int height;
14023     int mwidth;
14024     int mheight;
14025     /*RROutput*/ void *outputs;
14026 }
14027 
14028 struct XRRScreenChangeNotifyEvent {
14029     int type;                   /* event base */
14030     c_ulong serial;       /* # of last request processed by server */
14031     Bool send_event;            /* true if this came from a SendEvent request */
14032     Display *display;           /* Display the event was read from */
14033     Window window;              /* window which selected for this event */
14034     Window root;                /* Root window for changed screen */
14035     Time timestamp;             /* when the screen change occurred */
14036     Time config_timestamp;      /* when the last configuration change */
14037     ushort/*SizeID*/ size_index;
14038     ushort/*SubpixelOrder*/ subpixel_order;
14039     ushort/*Rotation*/ rotation;
14040     int width;
14041     int height;
14042     int mwidth;
14043     int mheight;
14044 }
14045 
14046 enum RRScreenChangeNotify = 0;
14047 
14048 enum RRScreenChangeNotifyMask = 1;
14049 
14050 __gshared int xrrEventBase = -1;
14051 
14052 
14053 interface XRandr {
14054 extern(C) @nogc:
14055 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
14056 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
14057 
14058 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
14059 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
14060 
14061 	void XRRSelectInput(Display *dpy, Window window, int mask);
14062 }
14063 
14064 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
14065 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
14066 	/* Xrandr } */
14067 
14068 	/* Xft { */
14069 
14070 	// actually freetype
14071 	alias void FT_Face;
14072 
14073 	// actually fontconfig
14074 	private alias FcBool = int;
14075 	alias void FcCharSet;
14076 	alias void FcPattern;
14077 	alias void FcResult;
14078 	enum FcEndian { FcEndianBig, FcEndianLittle }
14079 	struct FcFontSet {
14080 		int nfont;
14081 		int sfont;
14082 		FcPattern** fonts;
14083 	}
14084 
14085 	// actually XRegion
14086 	struct BOX {
14087 		short x1, x2, y1, y2;
14088 	}
14089 	struct _XRegion {
14090 		c_long size;
14091 		c_long numRects;
14092 		BOX* rects;
14093 		BOX extents;
14094 	}
14095 
14096 	alias Region = _XRegion*;
14097 
14098 	// ok actually Xft
14099 
14100 	struct XftFontInfo;
14101 
14102 	struct XftFont {
14103 		int         ascent;
14104 		int         descent;
14105 		int         height;
14106 		int         max_advance_width;
14107 		FcCharSet*  charset;
14108 		FcPattern*  pattern;
14109 	}
14110 
14111 	struct XftDraw;
14112 
14113 	struct XftColor {
14114 		c_ulong pixel;
14115 		XRenderColor color;
14116 	}
14117 
14118 	struct XftCharSpec {
14119 		dchar           ucs4;
14120 		short           x;
14121 		short           y;
14122 	}
14123 
14124 	struct XftCharFontSpec {
14125 		XftFont         *font;
14126 		dchar           ucs4;
14127 		short           x;
14128 		short           y;
14129 	}
14130 
14131 	struct XftGlyphSpec {
14132 		uint            glyph;
14133 		short           x;
14134 		short           y;
14135 	}
14136 
14137 	struct XftGlyphFontSpec {
14138 		XftFont         *font;
14139 		uint            glyph;
14140 		short           x;
14141 		short           y;
14142 	}
14143 
14144 	interface Xft {
14145 	extern(C) @nogc pure:
14146 
14147 	Bool XftColorAllocName (Display  *dpy,
14148 				const Visual   *visual,
14149 				Colormap cmap,
14150 				const char     *name,
14151 				XftColor *result);
14152 
14153 	Bool XftColorAllocValue (Display         *dpy,
14154 				Visual          *visual,
14155 				Colormap        cmap,
14156 				const XRenderColor    *color,
14157 				XftColor        *result);
14158 
14159 	void XftColorFree (Display   *dpy,
14160 				Visual    *visual,
14161 				Colormap  cmap,
14162 				XftColor  *color);
14163 
14164 	Bool XftDefaultHasRender (Display *dpy);
14165 
14166 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
14167 
14168 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
14169 
14170 	XftDraw * XftDrawCreate (Display   *dpy,
14171 		       Drawable  drawable,
14172 		       Visual    *visual,
14173 		       Colormap  colormap);
14174 
14175 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
14176 			     Pixmap   bitmap);
14177 
14178 	XftDraw * XftDrawCreateAlpha (Display *dpy,
14179 			    Pixmap  pixmap,
14180 			    int     depth);
14181 
14182 	void XftDrawChange (XftDraw  *draw,
14183 		       Drawable drawable);
14184 
14185 	Display * XftDrawDisplay (XftDraw *draw);
14186 
14187 	Drawable XftDrawDrawable (XftDraw *draw);
14188 
14189 	Colormap XftDrawColormap (XftDraw *draw);
14190 
14191 	Visual * XftDrawVisual (XftDraw *draw);
14192 
14193 	void XftDrawDestroy (XftDraw *draw);
14194 
14195 	Picture XftDrawPicture (XftDraw *draw);
14196 
14197 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
14198 
14199 	void XftDrawGlyphs (XftDraw          *draw,
14200 				const XftColor *color,
14201 				XftFont          *pub,
14202 				int              x,
14203 				int              y,
14204 				const uint  *glyphs,
14205 				int              nglyphs);
14206 
14207 	void XftDrawString8 (XftDraw             *draw,
14208 				const XftColor    *color,
14209 				XftFont             *pub,
14210 				int                 x,
14211 				int                 y,
14212 				const char     *string,
14213 				int                 len);
14214 
14215 	void XftDrawString16 (XftDraw            *draw,
14216 				const XftColor   *color,
14217 				XftFont            *pub,
14218 				int                x,
14219 				int                y,
14220 				const wchar   *string,
14221 				int                len);
14222 
14223 	void XftDrawString32 (XftDraw            *draw,
14224 				const XftColor   *color,
14225 				XftFont            *pub,
14226 				int                x,
14227 				int                y,
14228 				const dchar   *string,
14229 				int                len);
14230 
14231 	void XftDrawStringUtf8 (XftDraw          *draw,
14232 				const XftColor *color,
14233 				XftFont          *pub,
14234 				int              x,
14235 				int              y,
14236 				const char  *string,
14237 				int              len);
14238 	void XftDrawStringUtf16 (XftDraw             *draw,
14239 				const XftColor    *color,
14240 				XftFont             *pub,
14241 				int                 x,
14242 				int                 y,
14243 				const char     *string,
14244 				FcEndian            endian,
14245 				int                 len);
14246 
14247 	void XftDrawCharSpec (XftDraw                *draw,
14248 				const XftColor       *color,
14249 				XftFont                *pub,
14250 				const XftCharSpec    *chars,
14251 				int                    len);
14252 
14253 	void XftDrawCharFontSpec (XftDraw                    *draw,
14254 				const XftColor           *color,
14255 				const XftCharFontSpec    *chars,
14256 				int                        len);
14257 
14258 	void XftDrawGlyphSpec (XftDraw               *draw,
14259 				const XftColor      *color,
14260 				XftFont               *pub,
14261 				const XftGlyphSpec  *glyphs,
14262 				int                   len);
14263 
14264 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
14265 				const XftColor          *color,
14266 				const XftGlyphFontSpec  *glyphs,
14267 				int                       len);
14268 
14269 	void XftDrawRect (XftDraw            *draw,
14270 				const XftColor   *color,
14271 				int                x,
14272 				int                y,
14273 				uint       width,
14274 				uint       height);
14275 
14276 	Bool XftDrawSetClip (XftDraw     *draw,
14277 				Region      r);
14278 
14279 
14280 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
14281 				int                   xOrigin,
14282 				int                   yOrigin,
14283 				const XRectangle    *rects,
14284 				int                   n);
14285 
14286 	void XftDrawSetSubwindowMode (XftDraw    *draw,
14287 				int        mode);
14288 
14289 	void XftGlyphExtents (Display            *dpy,
14290 				XftFont            *pub,
14291 				const uint    *glyphs,
14292 				int                nglyphs,
14293 				XGlyphInfo         *extents);
14294 
14295 	void XftTextExtents8 (Display            *dpy,
14296 				XftFont            *pub,
14297 				const char    *string,
14298 				int                len,
14299 				XGlyphInfo         *extents);
14300 
14301 	void XftTextExtents16 (Display           *dpy,
14302 				XftFont           *pub,
14303 				const wchar  *string,
14304 				int               len,
14305 				XGlyphInfo        *extents);
14306 
14307 	void XftTextExtents32 (Display           *dpy,
14308 				XftFont           *pub,
14309 				const dchar  *string,
14310 				int               len,
14311 				XGlyphInfo        *extents);
14312 
14313 	void XftTextExtentsUtf8 (Display         *dpy,
14314 				XftFont         *pub,
14315 				const char *string,
14316 				int             len,
14317 				XGlyphInfo      *extents);
14318 
14319 	void XftTextExtentsUtf16 (Display            *dpy,
14320 				XftFont            *pub,
14321 				const char    *string,
14322 				FcEndian           endian,
14323 				int                len,
14324 				XGlyphInfo         *extents);
14325 
14326 	FcPattern * XftFontMatch (Display           *dpy,
14327 				int               screen,
14328 				const FcPattern *pattern,
14329 				FcResult          *result);
14330 
14331 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
14332 
14333 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
14334 
14335 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
14336 
14337 	FT_Face XftLockFace (XftFont *pub);
14338 
14339 	void XftUnlockFace (XftFont *pub);
14340 
14341 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
14342 
14343 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
14344 
14345 	dchar XftFontInfoHash (const XftFontInfo *fi);
14346 
14347 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
14348 
14349 	XftFont * XftFontOpenInfo (Display        *dpy,
14350 				FcPattern      *pattern,
14351 				XftFontInfo    *fi);
14352 
14353 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
14354 
14355 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
14356 
14357 	void XftFontClose (Display *dpy, XftFont *pub);
14358 
14359 	FcBool XftInitFtLibrary();
14360 	void XftFontLoadGlyphs (Display          *dpy,
14361 				XftFont          *pub,
14362 				FcBool           need_bitmaps,
14363 				const uint  *glyphs,
14364 				int              nglyph);
14365 
14366 	void XftFontUnloadGlyphs (Display            *dpy,
14367 				XftFont            *pub,
14368 				const uint    *glyphs,
14369 				int                nglyph);
14370 
14371 	FcBool XftFontCheckGlyph (Display  *dpy,
14372 				XftFont  *pub,
14373 				FcBool   need_bitmaps,
14374 				uint  glyph,
14375 				uint  *missing,
14376 				int      *nmissing);
14377 
14378 	FcBool XftCharExists (Display      *dpy,
14379 				XftFont      *pub,
14380 				dchar    ucs4);
14381 
14382 	uint XftCharIndex (Display       *dpy,
14383 				XftFont       *pub,
14384 				dchar      ucs4);
14385 	FcBool XftInit (const char *config);
14386 
14387 	int XftGetVersion ();
14388 
14389 	FcFontSet * XftListFonts (Display   *dpy,
14390 				int       screen,
14391 				...);
14392 
14393 	FcPattern *XftNameParse (const char *name);
14394 
14395 	void XftGlyphRender (Display         *dpy,
14396 				int             op,
14397 				Picture         src,
14398 				XftFont         *pub,
14399 				Picture         dst,
14400 				int             srcx,
14401 				int             srcy,
14402 				int             x,
14403 				int             y,
14404 				const uint *glyphs,
14405 				int             nglyphs);
14406 
14407 	void XftGlyphSpecRender (Display                 *dpy,
14408 				int                     op,
14409 				Picture                 src,
14410 				XftFont                 *pub,
14411 				Picture                 dst,
14412 				int                     srcx,
14413 				int                     srcy,
14414 				const XftGlyphSpec    *glyphs,
14415 				int                     nglyphs);
14416 
14417 	void XftCharSpecRender (Display              *dpy,
14418 				int                  op,
14419 				Picture              src,
14420 				XftFont              *pub,
14421 				Picture              dst,
14422 				int                  srcx,
14423 				int                  srcy,
14424 				const XftCharSpec  *chars,
14425 				int                  len);
14426 	void XftGlyphFontSpecRender (Display                     *dpy,
14427 				int                         op,
14428 				Picture                     src,
14429 				Picture                     dst,
14430 				int                         srcx,
14431 				int                         srcy,
14432 				const XftGlyphFontSpec    *glyphs,
14433 				int                         nglyphs);
14434 
14435 	void XftCharFontSpecRender (Display                  *dpy,
14436 				int                      op,
14437 				Picture                  src,
14438 				Picture                  dst,
14439 				int                      srcx,
14440 				int                      srcy,
14441 				const XftCharFontSpec  *chars,
14442 				int                      len);
14443 
14444 	void XftTextRender8 (Display         *dpy,
14445 				int             op,
14446 				Picture         src,
14447 				XftFont         *pub,
14448 				Picture         dst,
14449 				int             srcx,
14450 				int             srcy,
14451 				int             x,
14452 				int             y,
14453 				const char *string,
14454 				int             len);
14455 	void XftTextRender16 (Display            *dpy,
14456 				int                op,
14457 				Picture            src,
14458 				XftFont            *pub,
14459 				Picture            dst,
14460 				int                srcx,
14461 				int                srcy,
14462 				int                x,
14463 				int                y,
14464 				const wchar   *string,
14465 				int                len);
14466 
14467 	void XftTextRender16BE (Display          *dpy,
14468 				int              op,
14469 				Picture          src,
14470 				XftFont          *pub,
14471 				Picture          dst,
14472 				int              srcx,
14473 				int              srcy,
14474 				int              x,
14475 				int              y,
14476 				const char  *string,
14477 				int              len);
14478 
14479 	void XftTextRender16LE (Display          *dpy,
14480 				int              op,
14481 				Picture          src,
14482 				XftFont          *pub,
14483 				Picture          dst,
14484 				int              srcx,
14485 				int              srcy,
14486 				int              x,
14487 				int              y,
14488 				const char  *string,
14489 				int              len);
14490 
14491 	void XftTextRender32 (Display            *dpy,
14492 				int                op,
14493 				Picture            src,
14494 				XftFont            *pub,
14495 				Picture            dst,
14496 				int                srcx,
14497 				int                srcy,
14498 				int                x,
14499 				int                y,
14500 				const dchar   *string,
14501 				int                len);
14502 
14503 	void XftTextRender32BE (Display          *dpy,
14504 				int              op,
14505 				Picture          src,
14506 				XftFont          *pub,
14507 				Picture          dst,
14508 				int              srcx,
14509 				int              srcy,
14510 				int              x,
14511 				int              y,
14512 				const char  *string,
14513 				int              len);
14514 
14515 	void XftTextRender32LE (Display          *dpy,
14516 				int              op,
14517 				Picture          src,
14518 				XftFont          *pub,
14519 				Picture          dst,
14520 				int              srcx,
14521 				int              srcy,
14522 				int              x,
14523 				int              y,
14524 				const char  *string,
14525 				int              len);
14526 
14527 	void XftTextRenderUtf8 (Display          *dpy,
14528 				int              op,
14529 				Picture          src,
14530 				XftFont          *pub,
14531 				Picture          dst,
14532 				int              srcx,
14533 				int              srcy,
14534 				int              x,
14535 				int              y,
14536 				const char  *string,
14537 				int              len);
14538 
14539 	void XftTextRenderUtf16 (Display         *dpy,
14540 				int             op,
14541 				Picture         src,
14542 				XftFont         *pub,
14543 				Picture         dst,
14544 				int             srcx,
14545 				int             srcy,
14546 				int             x,
14547 				int             y,
14548 				const char *string,
14549 				FcEndian        endian,
14550 				int             len);
14551 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14552 
14553 	}
14554 
14555 	interface FontConfig {
14556 	extern(C) @nogc pure:
14557 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14558 		void FcFontSetDestroy(FcFontSet*);
14559 		char* FcNameUnparse(const FcPattern *);
14560 	}
14561 
14562 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14563 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14564 
14565 
14566 	/* Xft } */
14567 
14568 	class XDisconnectException : Exception {
14569 		bool userRequested;
14570 		this(bool userRequested = true) {
14571 			this.userRequested = userRequested;
14572 			super("X disconnected");
14573 		}
14574 	}
14575 
14576 	/++
14577 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14578 
14579 		Please note that it returns
14580 	+/
14581 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14582 
14583 		static XErrorEvent[] errorBuffer;
14584 
14585 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14586 			errorBuffer ~= *evt;
14587 			return 0;
14588 		}
14589 
14590 		auto savedErrorHandler = XSetErrorHandler(&handler);
14591 
14592 		try {
14593 			dg();
14594 		} finally {
14595 			XSync(XDisplayConnection.get, 0/*False*/);
14596 			XSetErrorHandler(savedErrorHandler);
14597 		}
14598 
14599 		auto bfr = errorBuffer;
14600 		errorBuffer = null;
14601 
14602 		return bfr;
14603 	}
14604 
14605 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14606 	class XDisplayConnection {
14607 		private __gshared Display* display;
14608 		private __gshared XIM xim;
14609 		private __gshared char* displayName;
14610 
14611 		private __gshared int connectionSequence_;
14612 		private __gshared bool isLocal_;
14613 
14614 		/// use this for lazy caching when reconnection
14615 		static int connectionSequenceNumber() { return connectionSequence_; }
14616 
14617 		/++
14618 			Guesses if the connection appears to be local.
14619 
14620 			History:
14621 				Added June 3, 2021
14622 		+/
14623 		static @property bool isLocal() nothrow @trusted @nogc {
14624 			return isLocal_;
14625 		}
14626 
14627 		/// Attempts recreation of state, may require application assistance
14628 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14629 		/// then call this, and if successful, reenter the loop.
14630 		static void discardAndRecreate(string newDisplayString = null) {
14631 			if(insideXEventLoop)
14632 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14633 
14634 			// 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
14635 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14636 
14637 			foreach(handle; chnenhm) {
14638 				handle.discardConnectionState();
14639 			}
14640 
14641 			discardState();
14642 
14643 			if(newDisplayString !is null)
14644 				setDisplayName(newDisplayString);
14645 
14646 			auto display = get();
14647 
14648 			foreach(handle; chnenhm) {
14649 				handle.recreateAfterDisconnect();
14650 			}
14651 		}
14652 
14653 		private __gshared EventMask rootEventMask;
14654 
14655 		/++
14656 			Requests the specified input from the root window on the connection, in addition to any other request.
14657 
14658 
14659 			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.
14660 
14661 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14662 		+/
14663 		static void addRootInput(EventMask mask) {
14664 			auto old = rootEventMask;
14665 			rootEventMask |= mask;
14666 			get(); // to ensure display connected
14667 			if(display !is null && rootEventMask != old)
14668 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14669 		}
14670 
14671 		static void discardState() {
14672 			freeImages();
14673 
14674 			foreach(atomPtr; interredAtoms)
14675 				*atomPtr = 0;
14676 			interredAtoms = null;
14677 			interredAtoms.assumeSafeAppend();
14678 
14679 			ScreenPainterImplementation.fontAttempted = false;
14680 			ScreenPainterImplementation.defaultfont = null;
14681 			ScreenPainterImplementation.defaultfontset = null;
14682 
14683 			Image.impl.xshmQueryCompleted = false;
14684 			Image.impl._xshmAvailable = false;
14685 
14686 			SimpleWindow.nativeMapping = null;
14687 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14688 			// GlobalHotkeyManager
14689 
14690 			display = null;
14691 			xim = null;
14692 		}
14693 
14694 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14695 		private static void createXIM () {
14696 			import core.stdc.locale : setlocale, LC_ALL;
14697 			import core.stdc.stdio : stderr, fprintf;
14698 			import core.stdc.stdlib : free;
14699 			import core.stdc.string : strdup;
14700 
14701 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14702 
14703 			auto olocale = strdup(setlocale(LC_ALL, null));
14704 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14705 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14706 
14707 			//fprintf(stderr, "opening IM...\n");
14708 			foreach (string s; mtry) {
14709 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14710 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14711 			}
14712 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14713 		}
14714 
14715 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14716 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14717 		static struct ImgList {
14718 			size_t img; // class; hide it from GC
14719 			ImgList* next;
14720 		}
14721 
14722 		static __gshared ImgList* imglist = null;
14723 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14724 
14725 		static void registerImage (Image img) {
14726 			if (!imglistLocked && img !is null) {
14727 				import core.stdc.stdlib : malloc;
14728 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14729 				assert(it !is null); // do proper checks
14730 				it.img = cast(size_t)cast(void*)img;
14731 				it.next = imglist;
14732 				imglist = it;
14733 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14734 			}
14735 		}
14736 
14737 		static void unregisterImage (Image img) {
14738 			if (!imglistLocked && img !is null) {
14739 				import core.stdc.stdlib : free;
14740 				ImgList* prev = null;
14741 				ImgList* cur = imglist;
14742 				while (cur !is null) {
14743 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14744 					prev = cur;
14745 					cur = cur.next;
14746 				}
14747 				if (cur !is null) {
14748 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14749 					free(cur);
14750 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14751 				} else {
14752 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14753 				}
14754 			}
14755 		}
14756 
14757 		static void freeImages () { // needed for discardAndRecreate
14758 			imglistLocked = true;
14759 			scope(exit) imglistLocked = false;
14760 			ImgList* cur = imglist;
14761 			ImgList* next = null;
14762 			while (cur !is null) {
14763 				import core.stdc.stdlib : free;
14764 				next = cur.next;
14765 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14766 				(cast(Image)cast(void*)cur.img).dispose();
14767 				free(cur);
14768 				cur = next;
14769 			}
14770 			imglist = null;
14771 		}
14772 
14773 		/// can be used to override normal handling of display name
14774 		/// from environment and/or command line
14775 		static setDisplayName(string newDisplayName) {
14776 			displayName = cast(char*) (newDisplayName ~ '\0');
14777 		}
14778 
14779 		/// resets to the default display string
14780 		static resetDisplayName() {
14781 			displayName = null;
14782 		}
14783 
14784 		///
14785 		static Display* get() {
14786 			if(display is null) {
14787 				if(!librariesSuccessfullyLoaded)
14788 					throw new Exception("Unable to load X11 client libraries");
14789 				display = XOpenDisplay(displayName);
14790 
14791 				isLocal_ = false;
14792 
14793 				connectionSequence_++;
14794 				if(display is null)
14795 					throw new Exception("Unable to open X display");
14796 
14797 				auto str = display.display_name;
14798 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14799 				// and otherwise it probably isn't
14800 				if(str is null || (str[0] != ':' && str[0] != '/'))
14801 					isLocal_ = false;
14802 				else
14803 					isLocal_ = true;
14804 
14805 				debug(sdpy_x_errors) {
14806 					XSetErrorHandler(&adrlogger);
14807 					XSynchronize(display, true);
14808 
14809 					extern(C) int wtf() {
14810 						if(errorHappened) {
14811 							asm { int 3; }
14812 							errorHappened = false;
14813 						}
14814 						return 0;
14815 					}
14816 					XSetAfterFunction(display, &wtf);
14817 				}
14818 
14819 
14820 				XSetIOErrorHandler(&x11ioerrCB);
14821 				Bool sup;
14822 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14823 				createXIM();
14824 				version(with_eventloop) {
14825 					import arsd.eventloop;
14826 					addFileEventListeners(display.fd, &eventListener, null, null);
14827 				}
14828 			}
14829 
14830 			return display;
14831 		}
14832 
14833 		extern(C)
14834 		static int x11ioerrCB(Display* dpy) {
14835 			throw new XDisconnectException(false);
14836 		}
14837 
14838 		version(with_eventloop) {
14839 			import arsd.eventloop;
14840 			static void eventListener(OsFileHandle fd) {
14841 				//this.mtLock();
14842 				//scope(exit) this.mtUnlock();
14843 				while(XPending(display))
14844 					doXNextEvent(display);
14845 			}
14846 		}
14847 
14848 		// close connection on program exit -- we need this to properly free all images
14849 		static ~this () {
14850 			// the gui thread must clean up after itself or else Xlib might deadlock
14851 			// using this flag on any thread destruction is the easiest way i know of
14852 			// (shared static this is run by the LAST thread to exit, which may not be
14853 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14854 			if(thisIsGuiThread)
14855 				close();
14856 		}
14857 
14858 		///
14859 		static void close() {
14860 			if(display is null)
14861 				return;
14862 
14863 			version(with_eventloop) {
14864 				import arsd.eventloop;
14865 				removeFileEventListeners(display.fd);
14866 			}
14867 
14868 			// now remove all registered images to prevent shared memory leaks
14869 			freeImages();
14870 
14871 			// tbh I don't know why it is doing this but like if this happens to run
14872 			// from the other thread there's frequent hanging inside here.
14873 			if(thisIsGuiThread)
14874 				XCloseDisplay(display);
14875 			display = null;
14876 		}
14877 	}
14878 
14879 	mixin template NativeImageImplementation() {
14880 		XImage* handle;
14881 		ubyte* rawData;
14882 
14883 		XShmSegmentInfo shminfo;
14884 		bool premultiply = true;
14885 
14886 		__gshared bool xshmQueryCompleted;
14887 		__gshared bool _xshmAvailable;
14888 		public static @property bool xshmAvailable() {
14889 			if(!xshmQueryCompleted) {
14890 				int i1, i2, i3;
14891 				xshmQueryCompleted = true;
14892 
14893 				if(!XDisplayConnection.isLocal)
14894 					_xshmAvailable = false;
14895 				else
14896 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14897 			}
14898 			return _xshmAvailable;
14899 		}
14900 
14901 		bool usingXshm;
14902 	final:
14903 
14904 		private __gshared bool xshmfailed;
14905 
14906 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14907 			auto display = XDisplayConnection.get();
14908 			assert(display !is null);
14909 			auto screen = DefaultScreen(display);
14910 
14911 			// it will only use shared memory for somewhat largish images,
14912 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14913 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14914 
14915 
14916 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14917 				// the actual use still fails. For example, if the program is in a container and permission denied
14918 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14919 				//
14920 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14921 
14922 
14923 				// synchronize so preexisting buffers are clear
14924 				XSync(display, false);
14925 				xshmfailed = false;
14926 
14927 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14928 
14929 
14930 				usingXshm = true;
14931 				handle = XShmCreateImage(
14932 					display,
14933 					DefaultVisual(display, screen),
14934 					enableAlpha ? 32: 24,
14935 					ImageFormat.ZPixmap,
14936 					null,
14937 					&shminfo,
14938 					width, height);
14939 				if(handle is null)
14940 					goto abortXshm1;
14941 
14942 				if(handle.bytes_per_line != 4 * width)
14943 					goto abortXshm2;
14944 
14945 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14946 				if(shminfo.shmid < 0)
14947 					goto abortXshm3;
14948 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14949 				if(rawData == cast(ubyte*) -1)
14950 					goto abortXshm4;
14951 				shminfo.readOnly = 0;
14952 				XShmAttach(display, &shminfo);
14953 
14954 				// and now to the final error check to ensure it actually worked.
14955 				XSync(display, false);
14956 				if(xshmfailed)
14957 					goto abortXshm5;
14958 
14959 				XSetErrorHandler(oldErrorHandler);
14960 
14961 				XDisplayConnection.registerImage(this);
14962 				// if I don't flush here there's a chance the dtor will run before the
14963 				// ctor and lead to a bad value X error. While this hurts the efficiency
14964 				// it is local anyway so prolly better to keep it simple
14965 				XFlush(display);
14966 
14967 				return;
14968 
14969 				abortXshm5:
14970 					shmdt(shminfo.shmaddr);
14971 					rawData = null;
14972 
14973 				abortXshm4:
14974 					shmctl(shminfo.shmid, IPC_RMID, null);
14975 
14976 				abortXshm3:
14977 					// nothing needed, the shmget failed so there's nothing to free
14978 
14979 				abortXshm2:
14980 					XDestroyImage(handle);
14981 					handle = null;
14982 
14983 				abortXshm1:
14984 					XSetErrorHandler(oldErrorHandler);
14985 					usingXshm = false;
14986 					handle = null;
14987 
14988 					shminfo = typeof(shminfo).init;
14989 
14990 					_xshmAvailable = false; // don't try again in the future
14991 
14992 					// writeln("fallingback");
14993 
14994 					goto fallback;
14995 
14996 			} else {
14997 				fallback:
14998 
14999 				if (forcexshm) throw new Exception("can't create XShm Image");
15000 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
15001 				import core.stdc.stdlib : malloc;
15002 				rawData = cast(ubyte*) malloc(width * height * 4);
15003 
15004 				handle = XCreateImage(
15005 					display,
15006 					DefaultVisual(display, screen),
15007 					enableAlpha ? 32 : 24, // bpp
15008 					ImageFormat.ZPixmap,
15009 					0, // offset
15010 					rawData,
15011 					width, height,
15012 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
15013 			}
15014 		}
15015 
15016 		void dispose() {
15017 			// note: this calls free(rawData) for us
15018 			if(handle) {
15019 				if (usingXshm) {
15020 					XDisplayConnection.unregisterImage(this);
15021 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
15022 				}
15023 				XDestroyImage(handle);
15024 				if(usingXshm) {
15025 					shmdt(shminfo.shmaddr);
15026 					shmctl(shminfo.shmid, IPC_RMID, null);
15027 				}
15028 				handle = null;
15029 			}
15030 		}
15031 
15032 		Color getPixel(int x, int y) @system {
15033 			auto offset = (y * width + x) * 4;
15034 			Color c;
15035 			c.a = enableAlpha ? rawData[offset + 3] : 255;
15036 			c.b = rawData[offset + 0];
15037 			c.g = rawData[offset + 1];
15038 			c.r = rawData[offset + 2];
15039 			if(enableAlpha && premultiply)
15040 				c.unPremultiply;
15041 			return c;
15042 		}
15043 
15044 		void setPixel(int x, int y, Color c) @system {
15045 			if(enableAlpha && premultiply)
15046 				c.premultiply();
15047 			auto offset = (y * width + x) * 4;
15048 			rawData[offset + 0] = c.b;
15049 			rawData[offset + 1] = c.g;
15050 			rawData[offset + 2] = c.r;
15051 			if(enableAlpha)
15052 				rawData[offset + 3] = c.a;
15053 		}
15054 
15055 		void convertToRgbaBytes(ubyte[] where) @system {
15056 			assert(where.length == this.width * this.height * 4);
15057 
15058 			// if rawData had a length....
15059 			//assert(rawData.length == where.length);
15060 			for(int idx = 0; idx < where.length; idx += 4) {
15061 				where[idx + 0] = rawData[idx + 2]; // r
15062 				where[idx + 1] = rawData[idx + 1]; // g
15063 				where[idx + 2] = rawData[idx + 0]; // b
15064 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
15065 
15066 				if(enableAlpha && premultiply)
15067 					unPremultiplyRgba(where[idx .. idx + 4]);
15068 			}
15069 		}
15070 
15071 		void setFromRgbaBytes(in ubyte[] where) @system {
15072 			assert(where.length == this.width * this.height * 4);
15073 
15074 			// if rawData had a length....
15075 			//assert(rawData.length == where.length);
15076 			for(int idx = 0; idx < where.length; idx += 4) {
15077 				rawData[idx + 2] = where[idx + 0]; // r
15078 				rawData[idx + 1] = where[idx + 1]; // g
15079 				rawData[idx + 0] = where[idx + 2]; // b
15080 				if(enableAlpha) {
15081 					rawData[idx + 3] = where[idx + 3]; // a
15082 					if(premultiply)
15083 						premultiplyBgra(rawData[idx .. idx + 4]);
15084 				}
15085 			}
15086 		}
15087 
15088 	}
15089 
15090 	mixin template NativeSimpleWindowImplementation() {
15091 		GC gc;
15092 		Window window;
15093 		Display* display;
15094 
15095 		Pixmap buffer;
15096 		int bufferw, bufferh; // size of the buffer; can be bigger than window
15097 		XIC xic; // input context
15098 		int curHidden = 0; // counter
15099 		Cursor blankCurPtr = 0;
15100 		int cursorSequenceNumber = 0;
15101 		int warpEventCount = 0; // number of mouse movement events to eat
15102 
15103 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS
15104 		X11GetSelectionHandler[Atom] getSelectionHandlers;
15105 
15106 		version(without_opengl) {} else
15107 		GLXContext glc;
15108 
15109 		private void fixFixedSize(bool forced=false) (int width, int height) {
15110 			if (forced || this.resizability == Resizability.fixedSize) {
15111 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
15112 				XSizeHints sh;
15113 				static if (!forced) {
15114 					c_long spr;
15115 					XGetWMNormalHints(display, window, &sh, &spr);
15116 					sh.flags |= PMaxSize | PMinSize;
15117 				} else {
15118 					sh.flags = PMaxSize | PMinSize;
15119 				}
15120 				sh.min_width = width;
15121 				sh.min_height = height;
15122 				sh.max_width = width;
15123 				sh.max_height = height;
15124 				XSetWMNormalHints(display, window, &sh);
15125 				//XFlush(display);
15126 			}
15127 		}
15128 
15129 		ScreenPainter getPainter(bool manualInvalidations) {
15130 			return ScreenPainter(this, window, manualInvalidations);
15131 		}
15132 
15133 		void move(int x, int y) {
15134 			XMoveWindow(display, window, x, y);
15135 		}
15136 
15137 		void resize(int w, int h) {
15138 			if (w < 1) w = 1;
15139 			if (h < 1) h = 1;
15140 			XResizeWindow(display, window, w, h);
15141 
15142 			// calling this now to avoid waiting for the server to
15143 			// acknowledge the resize; draws without returning to the
15144 			// event loop will thus actually work. the server's event
15145 			// btw might overrule this and resize it again
15146 			recordX11Resize(display, this, w, h);
15147 
15148 			updateOpenglViewportIfNeeded(w, h);
15149 		}
15150 
15151 		void moveResize (int x, int y, int w, int h) {
15152 			if (w < 1) w = 1;
15153 			if (h < 1) h = 1;
15154 			XMoveResizeWindow(display, window, x, y, w, h);
15155 			updateOpenglViewportIfNeeded(w, h);
15156 		}
15157 
15158 		void hideCursor () {
15159 			if (curHidden++ == 0) {
15160 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
15161 					static const(char)[1] cmbmp = 0;
15162 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
15163 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
15164 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
15165 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
15166 					XFreePixmap(display, pm);
15167 				}
15168 				XDefineCursor(display, window, blankCurPtr);
15169 			}
15170 		}
15171 
15172 		void showCursor () {
15173 			if (--curHidden == 0) XUndefineCursor(display, window);
15174 		}
15175 
15176 		void warpMouse (int x, int y) {
15177 			// here i will send dummy "ignore next mouse motion" event,
15178 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
15179 			// and we don't need to report it to the user (as warping is
15180 			// used when the user needs movement deltas).
15181 			//XClientMessageEvent xclient;
15182 			XEvent e;
15183 			e.xclient.type = EventType.ClientMessage;
15184 			e.xclient.window = window;
15185 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15186 			e.xclient.format = 32;
15187 			e.xclient.data.l[0] = 0;
15188 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
15189 			//{ 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]); }
15190 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15191 			// now warp pointer...
15192 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
15193 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
15194 			// ...and flush
15195 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
15196 			XFlush(display);
15197 		}
15198 
15199 		void sendDummyEvent () {
15200 			// here i will send dummy event to ping event queue
15201 			XEvent e;
15202 			e.xclient.type = EventType.ClientMessage;
15203 			e.xclient.window = window;
15204 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15205 			e.xclient.format = 32;
15206 			e.xclient.data.l[0] = 0;
15207 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15208 			XFlush(display);
15209 		}
15210 
15211 		void setTitle(string title) {
15212 			if (title.ptr is null) title = "";
15213 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15214 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15215 			XTextProperty windowName;
15216 			windowName.value = title.ptr;
15217 			windowName.encoding = XA_UTF8; //XA_STRING;
15218 			windowName.format = 8;
15219 			windowName.nitems = cast(uint)title.length;
15220 			XSetWMName(display, window, &windowName);
15221 			char[1024] namebuf = 0;
15222 			auto maxlen = namebuf.length-1;
15223 			if (maxlen > title.length) maxlen = title.length;
15224 			namebuf[0..maxlen] = title[0..maxlen];
15225 			XStoreName(display, window, namebuf.ptr);
15226 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
15227 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
15228 		}
15229 
15230 		string[] getTitles() {
15231 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15232 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15233 			XTextProperty textProp;
15234 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
15235 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
15236 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
15237 				} else
15238 					return [];
15239 			} else
15240 				return null;
15241 		}
15242 
15243 		string getTitle() {
15244 			auto titles = getTitles();
15245 			return titles.length ? titles[0] : null;
15246 		}
15247 
15248 		void setMinSize (int minwidth, int minheight) {
15249 			import core.stdc.config : c_long;
15250 			if (minwidth < 1) minwidth = 1;
15251 			if (minheight < 1) minheight = 1;
15252 			XSizeHints sh;
15253 			c_long spr;
15254 			XGetWMNormalHints(display, window, &sh, &spr);
15255 			sh.min_width = minwidth;
15256 			sh.min_height = minheight;
15257 			sh.flags |= PMinSize;
15258 			XSetWMNormalHints(display, window, &sh);
15259 			flushGui();
15260 		}
15261 
15262 		void setMaxSize (int maxwidth, int maxheight) {
15263 			import core.stdc.config : c_long;
15264 			if (maxwidth < 1) maxwidth = 1;
15265 			if (maxheight < 1) maxheight = 1;
15266 			XSizeHints sh;
15267 			c_long spr;
15268 			XGetWMNormalHints(display, window, &sh, &spr);
15269 			sh.max_width = maxwidth;
15270 			sh.max_height = maxheight;
15271 			sh.flags |= PMaxSize;
15272 			XSetWMNormalHints(display, window, &sh);
15273 			flushGui();
15274 		}
15275 
15276 		void setResizeGranularity (int granx, int grany) {
15277 			import core.stdc.config : c_long;
15278 			if (granx < 1) granx = 1;
15279 			if (grany < 1) grany = 1;
15280 			XSizeHints sh;
15281 			c_long spr;
15282 			XGetWMNormalHints(display, window, &sh, &spr);
15283 			sh.width_inc = granx;
15284 			sh.height_inc = grany;
15285 			sh.flags |= PResizeInc;
15286 			XSetWMNormalHints(display, window, &sh);
15287 			flushGui();
15288 		}
15289 
15290 		void setOpacity (uint opacity) {
15291 			arch_ulong o = opacity;
15292 			if (opacity == uint.max)
15293 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
15294 			else
15295 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
15296 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
15297 		}
15298 
15299 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted {
15300 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
15301 			display = XDisplayConnection.get();
15302 			auto screen = DefaultScreen(display);
15303 
15304 			bool overrideRedirect = false;
15305 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
15306 				overrideRedirect = true;
15307 
15308 			version(without_opengl) {}
15309 			else {
15310 				if(opengl == OpenGlOptions.yes) {
15311 					GLXFBConfig fbconf = null;
15312 					XVisualInfo* vi = null;
15313 					bool useLegacy = false;
15314 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
15315 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
15316 						int[23] visualAttribs = [
15317 							GLX_X_RENDERABLE , 1/*True*/,
15318 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
15319 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
15320 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
15321 							GLX_RED_SIZE     , 8,
15322 							GLX_GREEN_SIZE   , 8,
15323 							GLX_BLUE_SIZE    , 8,
15324 							GLX_ALPHA_SIZE   , 8,
15325 							GLX_DEPTH_SIZE   , 24,
15326 							GLX_STENCIL_SIZE , 8,
15327 							GLX_DOUBLEBUFFER , 1/*True*/,
15328 							0/*None*/,
15329 						];
15330 						int fbcount;
15331 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
15332 						if (fbcount == 0) {
15333 							useLegacy = true; // try to do at least something
15334 						} else {
15335 							// pick the FB config/visual with the most samples per pixel
15336 							int bestidx = -1, bestns = -1;
15337 							foreach (int fbi; 0..fbcount) {
15338 								int sb, samples;
15339 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
15340 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
15341 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
15342 							}
15343 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
15344 							fbconf = fbc[bestidx];
15345 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
15346 							XFree(fbc);
15347 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
15348 						}
15349 					}
15350 					if (vi is null || useLegacy) {
15351 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
15352 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
15353 						useLegacy = true;
15354 					}
15355 					if (vi is null) throw new Exception("no open gl visual found");
15356 
15357 					XSetWindowAttributes swa;
15358 					auto root = RootWindow(display, screen);
15359 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
15360 
15361 					swa.override_redirect = overrideRedirect;
15362 
15363 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15364 						0, 0, width, height,
15365 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
15366 
15367 					// now try to use `glXCreateContextAttribsARB()` if it's here
15368 					if (!useLegacy) {
15369 						// request fairly advanced context, even with stencil buffer!
15370 						int[9] contextAttribs = [
15371 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
15372 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
15373 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
15374 							// for modern context, set "forward compatibility" flag too
15375 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
15376 							0/*None*/,
15377 						];
15378 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
15379 						if (glc is null && sdpyOpenGLContextAllowFallback) {
15380 							sdpyOpenGLContextVersion = 0;
15381 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15382 						}
15383 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
15384 					} else {
15385 						// fallback to old GLX call
15386 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
15387 							sdpyOpenGLContextVersion = 0;
15388 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15389 						}
15390 					}
15391 					// sync to ensure any errors generated are processed
15392 					XSync(display, 0/*False*/);
15393 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
15394 					if(glc is null)
15395 						throw new Exception("glc");
15396 				}
15397 			}
15398 
15399 			if(opengl == OpenGlOptions.no) {
15400 
15401 				XSetWindowAttributes swa;
15402 				swa.background_pixel = WhitePixel(display, screen);
15403 				swa.border_pixel = BlackPixel(display, screen);
15404 				swa.override_redirect = overrideRedirect;
15405 				auto root = RootWindow(display, screen);
15406 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
15407 
15408 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15409 					0, 0, width, height,
15410 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
15411 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
15412 
15413 
15414 
15415 				/*
15416 				window = XCreateSimpleWindow(
15417 					display,
15418 					parent is null ? RootWindow(display, screen) : parent.impl.window,
15419 					0, 0, // x, y
15420 					width, height,
15421 					1, // border width
15422 					BlackPixel(display, screen), // border
15423 					WhitePixel(display, screen)); // background
15424 				*/
15425 
15426 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
15427 				bufferw = width;
15428 				bufferh = height;
15429 
15430 				gc = DefaultGC(display, screen);
15431 
15432 				// clear out the buffer to get us started...
15433 				XSetForeground(display, gc, WhitePixel(display, screen));
15434 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
15435 				XSetForeground(display, gc, BlackPixel(display, screen));
15436 			}
15437 
15438 			// input context
15439 			//TODO: create this only for top-level windows, and reuse that?
15440 			populateXic();
15441 
15442 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
15443 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
15444 			// window class
15445 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
15446 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
15447 				XClassHint klass;
15448 				XWMHints wh;
15449 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15450 					wh.input = true;
15451 					wh.flags |= InputHint;
15452 				}
15453 				XSizeHints size;
15454 				klass.res_name = sdpyWindowClassStr;
15455 				klass.res_class = sdpyWindowClassStr;
15456 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
15457 			}
15458 
15459 			setTitle(title);
15460 			SimpleWindow.nativeMapping[window] = this;
15461 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
15462 
15463 			// This gives our window a close button
15464 			if (windowType != WindowTypes.eventOnly) {
15465 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
15466 				int useAtoms;
15467 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15468 					useAtoms = 2;
15469 				} else {
15470 					useAtoms = 1;
15471 				}
15472 				assert(useAtoms <= atoms.length);
15473 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
15474 			}
15475 
15476 			// FIXME: windowType and customizationFlags
15477 			Atom[8] wsatoms; // here, due to goto
15478 			int wmsacount = 0; // here, due to goto
15479 
15480 			try
15481 			final switch(windowType) {
15482 				case WindowTypes.normal:
15483 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15484 				break;
15485 				case WindowTypes.undecorated:
15486 					motifHideDecorations();
15487 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15488 				break;
15489 				case WindowTypes.eventOnly:
15490 					_hidden = true;
15491 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
15492 					goto hiddenWindow;
15493 				//break;
15494 				case WindowTypes.nestedChild:
15495 					// handled in XCreateWindow calls
15496 				break;
15497 
15498 				case WindowTypes.dropdownMenu:
15499 					motifHideDecorations();
15500 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
15501 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15502 				break;
15503 				case WindowTypes.popupMenu:
15504 					motifHideDecorations();
15505 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
15506 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15507 				break;
15508 				case WindowTypes.notification:
15509 					motifHideDecorations();
15510 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
15511 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15512 				break;
15513 				case WindowTypes.minimallyWrapped:
15514 					assert(0, "don't create a minimallyWrapped thing explicitly!");
15515 
15516 				case WindowTypes.dialog:
15517 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display));
15518 				break;
15519 				/+
15520 				case WindowTypes.menu:
15521 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15522 					motifHideDecorations();
15523 				break;
15524 				case WindowTypes.desktop:
15525 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
15526 				break;
15527 				case WindowTypes.dock:
15528 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
15529 				break;
15530 				case WindowTypes.toolbar:
15531 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
15532 				break;
15533 				case WindowTypes.menu:
15534 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15535 				break;
15536 				case WindowTypes.utility:
15537 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
15538 				break;
15539 				case WindowTypes.splash:
15540 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
15541 				break;
15542 				case WindowTypes.tooltip:
15543 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
15544 				break;
15545 				case WindowTypes.notification:
15546 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
15547 				break;
15548 				case WindowTypes.combo:
15549 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
15550 				break;
15551 				case WindowTypes.dnd:
15552 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
15553 				break;
15554 				+/
15555 			}
15556 			catch(Exception e) {
15557 				// XInternAtom failed, prolly a WM
15558 				// that doesn't support these things
15559 			}
15560 
15561 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15562 			// the two following flags may be ignored by WM
15563 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15564 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15565 
15566 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15567 
15568 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15569 
15570 			// What would be ideal here is if they only were
15571 			// selected if there was actually an event handler
15572 			// for them...
15573 
15574 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15575 
15576 			hiddenWindow:
15577 
15578 			// set the pid property for lookup later by window managers
15579 			// a standard convenience
15580 			import core.sys.posix.unistd;
15581 			arch_ulong pid = getpid();
15582 
15583 			XChangeProperty(
15584 				display,
15585 				impl.window,
15586 				GetAtom!("_NET_WM_PID", true)(display),
15587 				XA_CARDINAL,
15588 				32 /* bits */,
15589 				0 /*PropModeReplace*/,
15590 				&pid,
15591 				1);
15592 
15593 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15594 				if(parent is null) assert(0);
15595 				// sdpyPrintDebugString("transient");
15596 				XChangeProperty(
15597 					display,
15598 					impl.window,
15599 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15600 					XA_WINDOW,
15601 					32 /* bits */,
15602 					0 /*PropModeReplace*/,
15603 					&parent.impl.window,
15604 					1);
15605 
15606 			}
15607 
15608 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15609 				XMapWindow(display, window);
15610 			} else {
15611 				_hidden = true;
15612 			}
15613 		}
15614 
15615 		void populateXic() {
15616 			if (XDisplayConnection.xim !is null) {
15617 				xic = XCreateIC(XDisplayConnection.xim,
15618 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15619 						/*XNClientWindow*/"clientWindow".ptr, window,
15620 						/*XNFocusWindow*/"focusWindow".ptr, window,
15621 						null);
15622 				if (xic is null) {
15623 					import core.stdc.stdio : stderr, fprintf;
15624 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15625 				}
15626 			}
15627 		}
15628 
15629 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15630 			auto mask = EventMask.ExposureMask |
15631 				EventMask.KeyPressMask |
15632 				EventMask.KeyReleaseMask |
15633 				EventMask.PropertyChangeMask |
15634 				EventMask.FocusChangeMask |
15635 				EventMask.StructureNotifyMask |
15636 				EventMask.SubstructureNotifyMask |
15637 				EventMask.VisibilityChangeMask
15638 				| EventMask.ButtonPressMask
15639 				| EventMask.ButtonReleaseMask
15640 			;
15641 
15642 			// xshm is our shortcut for local connections
15643 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15644 				mask |= EventMask.PointerMotionMask;
15645 			else
15646 				mask |= EventMask.ButtonMotionMask;
15647 
15648 			XSelectInput(display, window, mask);
15649 		}
15650 
15651 
15652 		void setNetWMWindowType(Atom type) {
15653 			Atom[2] atoms;
15654 
15655 			atoms[0] = type;
15656 			// generic fallback
15657 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15658 
15659 			XChangeProperty(
15660 				display,
15661 				impl.window,
15662 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15663 				XA_ATOM,
15664 				32 /* bits */,
15665 				0 /*PropModeReplace*/,
15666 				atoms.ptr,
15667 				cast(int) atoms.length);
15668 		}
15669 
15670 		void motifHideDecorations(bool hide = true) {
15671 			MwmHints hints;
15672 			hints.flags = MWM_HINTS_DECORATIONS;
15673 			hints.decorations = hide ? 0 : 1;
15674 
15675 			XChangeProperty(
15676 				display,
15677 				impl.window,
15678 				GetAtom!"_MOTIF_WM_HINTS"(display),
15679 				GetAtom!"_MOTIF_WM_HINTS"(display),
15680 				32 /* bits */,
15681 				0 /*PropModeReplace*/,
15682 				&hints,
15683 				hints.sizeof / 4);
15684 		}
15685 
15686 		/*k8: unused
15687 		void createOpenGlContext() {
15688 
15689 		}
15690 		*/
15691 
15692 		void closeWindow() {
15693 			// I can't close this or a child window closing will
15694 			// break events for everyone. So I'm just leaking it right
15695 			// now and that is probably perfectly fine...
15696 			version(none)
15697 			if (customEventFDRead != -1) {
15698 				import core.sys.posix.unistd : close;
15699 				auto same = customEventFDRead == customEventFDWrite;
15700 
15701 				close(customEventFDRead);
15702 				if(!same)
15703 					close(customEventFDWrite);
15704 				customEventFDRead = -1;
15705 				customEventFDWrite = -1;
15706 			}
15707 
15708 			version(without_opengl) {} else
15709 			if(glc !is null) {
15710 				glXDestroyContext(display, glc);
15711 				glc = null;
15712 			}
15713 
15714 			if(buffer)
15715 				XFreePixmap(display, buffer);
15716 			bufferw = bufferh = 0;
15717 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15718 			XDestroyWindow(display, window);
15719 			XFlush(display);
15720 		}
15721 
15722 		void dispose() {
15723 		}
15724 
15725 		bool destroyed = false;
15726 	}
15727 
15728 	bool insideXEventLoop;
15729 }
15730 
15731 version(X11) {
15732 
15733 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15734 
15735 	private class ResizeEvent {
15736 		int width, height;
15737 	}
15738 
15739 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15740 		if(win.windowType == WindowTypes.minimallyWrapped)
15741 			return;
15742 
15743 		if(win.pendingResizeEvent is null) {
15744 			win.pendingResizeEvent = new ResizeEvent();
15745 			win.addEventListener((ResizeEvent re) {
15746 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15747 			});
15748 		}
15749 		win.pendingResizeEvent.width = width;
15750 		win.pendingResizeEvent.height = height;
15751 		if(!win.eventQueued!ResizeEvent) {
15752 			win.postEvent(win.pendingResizeEvent);
15753 		}
15754 	}
15755 
15756 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15757 		if(win.windowType == WindowTypes.minimallyWrapped)
15758 			return;
15759 		if(win.closed)
15760 			return;
15761 
15762 		if(width != win.width || height != win.height) {
15763 
15764 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15765 			win._width = width;
15766 			win._height = height;
15767 
15768 			if(win.openglMode == OpenGlOptions.no) {
15769 				// FIXME: could this be more efficient?
15770 
15771 				if (win.bufferw < width || win.bufferh < height) {
15772 					//{ 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); }
15773 					// grow the internal buffer to match the window...
15774 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15775 					{
15776 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15777 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15778 						scope(exit) XFreeGC(win.display, xgc);
15779 						XSetClipMask(win.display, xgc, None);
15780 						XSetForeground(win.display, xgc, 0);
15781 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15782 					}
15783 					XCopyArea(display,
15784 						cast(Drawable) win.buffer,
15785 						cast(Drawable) newPixmap,
15786 						win.gc, 0, 0,
15787 						win.bufferw < width ? win.bufferw : win.width,
15788 						win.bufferh < height ? win.bufferh : win.height,
15789 						0, 0);
15790 
15791 					XFreePixmap(display, win.buffer);
15792 					win.buffer = newPixmap;
15793 					win.bufferw = width;
15794 					win.bufferh = height;
15795 				}
15796 
15797 				// clear unused parts of the buffer
15798 				if (win.bufferw > width || win.bufferh > height) {
15799 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15800 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15801 					scope(exit) XFreeGC(win.display, xgc);
15802 					XSetClipMask(win.display, xgc, None);
15803 					XSetForeground(win.display, xgc, 0);
15804 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15805 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15806 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15807 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15808 				}
15809 
15810 			}
15811 
15812 			win.updateOpenglViewportIfNeeded(width, height);
15813 
15814 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15815 
15816 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15817 			if(win.windowResized !is null) {
15818 				XUnlockDisplay(display);
15819 				scope(exit) XLockDisplay(display);
15820 				win.windowResized(width, height);
15821 			}
15822 		}
15823 	}
15824 
15825 
15826 	/// Platform-specific, you might use it when doing a custom event loop.
15827 	bool doXNextEvent(Display* display) {
15828 		bool done;
15829 		XEvent e;
15830 		XNextEvent(display, &e);
15831 		version(sddddd) {
15832 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15833 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15834 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15835 			}
15836 		}
15837 
15838 		// filter out compose events
15839 		if (XFilterEvent(&e, None)) {
15840 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15841 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15842 			return false;
15843 		}
15844 		// process keyboard mapping changes
15845 		if (e.type == EventType.KeymapNotify) {
15846 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15847 			XRefreshKeyboardMapping(&e.xmapping);
15848 			return false;
15849 		}
15850 
15851 		version(with_eventloop)
15852 			import arsd.eventloop;
15853 
15854 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15855 			// see windows impl's comments
15856 			XUnlockDisplay(display);
15857 			scope(exit) XLockDisplay(display);
15858 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15859 			if(ret == 0)
15860 				return done;
15861 		}
15862 
15863 
15864 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15865 			if(win.getNativeEventHandler !is null) {
15866 				XUnlockDisplay(display);
15867 				scope(exit) XLockDisplay(display);
15868 				auto ret = win.getNativeEventHandler()(e);
15869 				if(ret == 0)
15870 					return done;
15871 			}
15872 		}
15873 
15874 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15875 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15876 				// we get this because of the RRScreenChangeNotifyMask
15877 
15878 				// this isn't actually an ideal way to do it since it wastes time
15879 				// but meh it is simple and it works.
15880 				win.actualDpiLoadAttempted = false;
15881 				SimpleWindow.xRandrInfoLoadAttemped = false;
15882 				win.updateActualDpi(); // trigger a reload
15883 			}
15884 		}
15885 
15886 		switch(e.type) {
15887 		  case EventType.SelectionClear:
15888 		  	// writeln("SelectionClear");
15889 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15890 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15891 				// writeln("SelectionClear");
15892 			}
15893 			SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15894 			mightShortCircuitClipboard = false;
15895 		  break;
15896 		  case EventType.SelectionRequest:
15897 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15898 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15899 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15900 				XUnlockDisplay(display);
15901 				scope(exit) XLockDisplay(display);
15902 				(*ssh).handleRequest(e);
15903 			}
15904 		  break;
15905 		  case EventType.PropertyNotify:
15906 			// import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15907 
15908 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15909 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15910 					ssh.sendMoreIncr(&e.xproperty);
15911 			}
15912 
15913 
15914 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15915 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15916 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15917 					Atom target;
15918 					int format;
15919 					arch_ulong bytesafter, length;
15920 					void* value;
15921 
15922 					ubyte[] s;
15923 					Atom targetToKeep;
15924 
15925 					XGetWindowProperty(
15926 						e.xproperty.display,
15927 						e.xproperty.window,
15928 						e.xproperty.atom,
15929 						0,
15930 						100000 /* length */,
15931 						true, /* erase it to signal we got it and want more */
15932 						0 /*AnyPropertyType*/,
15933 						&target, &format, &length, &bytesafter, &value);
15934 
15935 					if(!targetToKeep)
15936 						targetToKeep = target;
15937 
15938 					auto id = (cast(ubyte*) value)[0 .. length];
15939 
15940 					handler.handleIncrData(targetToKeep, id);
15941 					if(length == 0) {
15942 						win.getSelectionHandlers.remove(e.xproperty.atom);
15943 					}
15944 
15945 					XFree(value);
15946 				}
15947 			}
15948 		  break;
15949 		  case EventType.SelectionNotify:
15950 		  	// import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom);
15951 			if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15952 			if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15953 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15954 					XUnlockDisplay(display);
15955 					scope(exit) XLockDisplay(display);
15956 					handler.handleData(None, null);
15957 					win.getSelectionHandlers.remove(e.xproperty.atom);
15958 				} else {
15959 					Atom target;
15960 					int format;
15961 					arch_ulong bytesafter, length;
15962 					void* value;
15963 					XGetWindowProperty(
15964 						e.xselection.display,
15965 						e.xselection.requestor,
15966 						e.xselection.property,
15967 						0,
15968 						100000 /* length */,
15969 						//false, /* don't erase it */
15970 						true, /* do erase it lol */
15971 						0 /*AnyPropertyType*/,
15972 						&target, &format, &length, &bytesafter, &value);
15973 
15974 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15975 
15976 					{
15977 						XUnlockDisplay(display);
15978 						scope(exit) XLockDisplay(display);
15979 
15980 						if(target == XA_ATOM) {
15981 							// initial request, see what they are able to work with and request the best one
15982 							// we can handle, if available
15983 
15984 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15985 							Atom best = handler.findBestFormat(answer);
15986 
15987 							/+
15988 							writeln("got ", answer);
15989 							foreach(a; answer)
15990 								writeln(XGetAtomName(display, a).stringz);
15991 							writeln("best ", best);
15992 							+/
15993 
15994 							if(best != None) {
15995 								// actually request the best format
15996 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15997 							}
15998 						} else if(target == GetAtom!"INCR"(display)) {
15999 							// incremental
16000 
16001 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
16002 
16003 							// signal the sending program that we see
16004 							// the incr and are ready to receive more.
16005 							XDeleteProperty(
16006 								e.xselection.display,
16007 								e.xselection.requestor,
16008 								e.xselection.property);
16009 						} else {
16010 							// unsupported type... maybe, forward, then we done with it
16011 							if(target != None) {
16012 								handler.handleData(target, cast(ubyte[]) value[0 .. length]);
16013 								win.getSelectionHandlers.remove(e.xproperty.atom);
16014 							}
16015 						}
16016 					}
16017 					XFree(value);
16018 					/*
16019 					XDeleteProperty(
16020 						e.xselection.display,
16021 						e.xselection.requestor,
16022 						e.xselection.property);
16023 					*/
16024 				}
16025 			}
16026 			break;
16027 		  case EventType.ConfigureNotify:
16028 			auto event = e.xconfigure;
16029 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
16030 				if(win.windowType == WindowTypes.minimallyWrapped)
16031 					break;
16032 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
16033 
16034 				/+
16035 					The ICCCM says window managers must send a synthetic event when the window
16036 					is moved but NOT when it is resized. In the resize case, an event is sent
16037 					with position (0, 0) which can be wrong and break the dpi calculations.
16038 
16039 					So we only consider the synthetic events from the WM and otherwise
16040 					need to wait for some other event to get the position which... sucks.
16041 
16042 					I'd rather not have windows changing their layout on mouse motion after
16043 					switching monitors... might be forced to but for now just ignoring it.
16044 
16045 					Easiest way to switch monitors without sending a size position is by
16046 					maximize or fullscreen in a setup like mine, but on most setups those
16047 					work on the monitor it is already living on, so it should be ok most the
16048 					time.
16049 				+/
16050 				if(event.send_event) {
16051 					win.screenPositionKnown = true;
16052 					win.screenPositionX = event.x;
16053 					win.screenPositionY = event.y;
16054 					win.updateActualDpi();
16055 				}
16056 
16057 				win.updateIMEPopupLocation();
16058 				recordX11ResizeAsync(display, *win, event.width, event.height);
16059 			}
16060 		  break;
16061 		  case EventType.Expose:
16062 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
16063 				if(win.windowType == WindowTypes.minimallyWrapped)
16064 					break;
16065 				// if it is closing from a popup menu, it can get
16066 				// an Expose event right by the end and trigger a
16067 				// BadDrawable error ... we'll just check
16068 				// closed to handle that.
16069 				if((*win).closed) break;
16070 				if((*win).openglMode == OpenGlOptions.no) {
16071 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
16072 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
16073 					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);
16074 				} else {
16075 					// need to redraw the scene somehow
16076 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
16077 						XUnlockDisplay(display);
16078 						scope(exit) XLockDisplay(display);
16079 						version(without_opengl) {} else
16080 						win.redrawOpenGlSceneSoon();
16081 					}
16082 				}
16083 			}
16084 		  break;
16085 		  case EventType.FocusIn:
16086 		  case EventType.FocusOut:
16087 			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
16088 
16089 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16090 				/+
16091 
16092 				void info(string detail) {
16093 					string s;
16094 					// import std.conv;
16095 					// import std.datetime;
16096 					s ~= to!string(Clock.currTime);
16097 					s ~= " ";
16098 					s ~= e.type == EventType.FocusIn ? "in " : "out";
16099 					s ~= " ";
16100 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
16101 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
16102 					s ~= detail;
16103 					s ~= " ";
16104 
16105 					sdpyPrintDebugString(s);
16106 
16107 				}
16108 
16109 				switch(e.xfocus.detail) {
16110 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
16111 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
16112 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
16113 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
16114 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
16115 					case NotifyDetail.NotifyPointer: info("pointer"); break;
16116 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
16117 					case NotifyDetail.NotifyDetailNone: info("none"); break;
16118 					default:
16119 
16120 				}
16121 				+/
16122 
16123 
16124 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
16125 					break; // just ignore these they seem irrelevant
16126 
16127 				auto old = win._focused;
16128 				win._focused = e.type == EventType.FocusIn;
16129 
16130 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
16131 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
16132 					win._focused = true;
16133 
16134 				if(win.demandingAttention)
16135 					demandAttention(*win, false);
16136 
16137 				win.updateIMEFocused();
16138 
16139 				if(old != win._focused && win.onFocusChange) {
16140 					XUnlockDisplay(display);
16141 					scope(exit) XLockDisplay(display);
16142 					win.onFocusChange(win._focused);
16143 				}
16144 			}
16145 		  break;
16146 		  case EventType.VisibilityNotify:
16147 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16148 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
16149 						if (win.visibilityChanged !is null) {
16150 								XUnlockDisplay(display);
16151 								scope(exit) XLockDisplay(display);
16152 								win.visibilityChanged(false);
16153 							}
16154 					} else {
16155 						if (win.visibilityChanged !is null) {
16156 							XUnlockDisplay(display);
16157 							scope(exit) XLockDisplay(display);
16158 							win.visibilityChanged(true);
16159 						}
16160 					}
16161 				}
16162 				break;
16163 		  case EventType.ClientMessage:
16164 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
16165 					// "ignore next mouse motion" event, increment ignore counter for teh window
16166 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16167 						++(*win).warpEventCount;
16168 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
16169 					} else {
16170 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
16171 					}
16172 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
16173 					// user clicked the close button on the window manager
16174 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16175 						XUnlockDisplay(display);
16176 						scope(exit) XLockDisplay(display);
16177 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
16178 					}
16179 
16180 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
16181 					// writeln("HAPPENED");
16182 					// user clicked the close button on the window manager
16183 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16184 						XUnlockDisplay(display);
16185 						scope(exit) XLockDisplay(display);
16186 
16187 						auto setTo = *win;
16188 
16189 						if(win.setRequestedInputFocus !is null) {
16190 							auto s = win.setRequestedInputFocus();
16191 							if(s !is null) {
16192 								setTo = s;
16193 							}
16194 						}
16195 
16196 						assert(setTo !is null);
16197 
16198 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
16199 
16200 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
16201 					}
16202 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
16203 					foreach(nai; NotificationAreaIcon.activeIcons)
16204 						nai.newManager();
16205 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16206 
16207 					bool xDragWindow = true;
16208 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
16209 						//XDefineCursor(display, xDragWindow.impl.window,
16210 							//writeln("XdndStatus ", e.xclient.data.l);
16211 					}
16212 					if(auto dh = win.dropHandler) {
16213 
16214 						static Atom[3] xFormatsBuffer;
16215 						static Atom[] xFormats;
16216 
16217 						void resetXFormats() {
16218 							xFormatsBuffer[] = 0;
16219 							xFormats = xFormatsBuffer[];
16220 						}
16221 
16222 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
16223 							// on Windows it is supposed to return the effect you actually do FIXME
16224 
16225 							auto sourceWindow =  e.xclient.data.l[0];
16226 
16227 							xFormatsBuffer[0] = e.xclient.data.l[2];
16228 							xFormatsBuffer[1] = e.xclient.data.l[3];
16229 							xFormatsBuffer[2] = e.xclient.data.l[4];
16230 
16231 							if(e.xclient.data.l[1] & 1) {
16232 								// can just grab it all but like we don't necessarily need them...
16233 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
16234 							} else {
16235 								int len;
16236 								foreach(fmt; xFormatsBuffer)
16237 									if(fmt) len++;
16238 								xFormats = xFormatsBuffer[0 .. len];
16239 							}
16240 
16241 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
16242 
16243 							dh.dragEnter(&pkg);
16244 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
16245 
16246 							auto pack = e.xclient.data.l[2];
16247 
16248 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
16249 
16250 
16251 							XClientMessageEvent xclient;
16252 
16253 							xclient.type = EventType.ClientMessage;
16254 							xclient.window = e.xclient.data.l[0];
16255 							xclient.message_type = GetAtom!"XdndStatus"(display);
16256 							xclient.format = 32;
16257 							xclient.data.l[0] = win.impl.window;
16258 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
16259 							auto r = result.consistentWithin;
16260 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
16261 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
16262 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
16263 
16264 							XSendEvent(
16265 								display,
16266 								e.xclient.data.l[0],
16267 								false,
16268 								EventMask.NoEventMask,
16269 								cast(XEvent*) &xclient
16270 							);
16271 
16272 
16273 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
16274 							//writeln("XdndLeave");
16275 							// drop cancelled.
16276 							// data.l[0] is the source window
16277 							dh.dragLeave();
16278 
16279 							resetXFormats();
16280 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
16281 							// drop happening, should fetch data, then send finished
16282 							// writeln("XdndDrop");
16283 
16284 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
16285 
16286 							dh.drop(&pkg);
16287 
16288 							resetXFormats();
16289 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
16290 							// writeln("XdndFinished");
16291 
16292 							dh.finish();
16293 						}
16294 
16295 					}
16296 				}
16297 		  break;
16298 		  case EventType.MapNotify:
16299 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
16300 					(*win)._visible = true;
16301 					if (!(*win)._visibleForTheFirstTimeCalled) {
16302 						(*win)._visibleForTheFirstTimeCalled = true;
16303 						if ((*win).visibleForTheFirstTime !is null) {
16304 							XUnlockDisplay(display);
16305 							scope(exit) XLockDisplay(display);
16306 							(*win).visibleForTheFirstTime();
16307 						}
16308 					}
16309 					if ((*win).visibilityChanged !is null) {
16310 						XUnlockDisplay(display);
16311 						scope(exit) XLockDisplay(display);
16312 						(*win).visibilityChanged(true);
16313 					}
16314 				}
16315 		  break;
16316 		  case EventType.UnmapNotify:
16317 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
16318 					win._visible = false;
16319 					if (win.visibilityChanged !is null) {
16320 						XUnlockDisplay(display);
16321 						scope(exit) XLockDisplay(display);
16322 						win.visibilityChanged(false);
16323 					}
16324 			}
16325 		  break;
16326 		  case EventType.DestroyNotify:
16327 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
16328 				if(win.destroyed)
16329 					break; // might get a notification both for itself and from its parent
16330 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
16331 				win._closed = true; // just in case
16332 				win.destroyed = true;
16333 				if (win.xic !is null) {
16334 					XDestroyIC(win.xic);
16335 					win.xic = null; // just in case
16336 				}
16337 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
16338 				bool anyImportant = false;
16339 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
16340 					if(w.beingOpenKeepsAppOpen) {
16341 						anyImportant = true;
16342 						break;
16343 					}
16344 				if(!anyImportant) {
16345 					EventLoop.quitApplication();
16346 					done = true;
16347 				}
16348 			}
16349 			auto window = e.xdestroywindow.window;
16350 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
16351 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
16352 
16353 			version(with_eventloop) {
16354 				if(done) exit();
16355 			}
16356 		  break;
16357 
16358 		  case EventType.MotionNotify:
16359 			MouseEvent mouse;
16360 			auto event = e.xmotion;
16361 
16362 			mouse.type = MouseEventType.motion;
16363 			mouse.x = event.x;
16364 			mouse.y = event.y;
16365 			mouse.modifierState = event.state;
16366 
16367 			mouse.timestamp = event.time;
16368 
16369 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
16370 				mouse.window = *win;
16371 				if (win.warpEventCount > 0) {
16372 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
16373 					--(*win).warpEventCount;
16374 					(*win).mdx(mouse); // so deltas will be correctly updated
16375 				} else {
16376 					win.warpEventCount = 0; // just in case
16377 					(*win).mdx(mouse);
16378 					if((*win).handleMouseEvent) {
16379 						XUnlockDisplay(display);
16380 						scope(exit) XLockDisplay(display);
16381 						(*win).handleMouseEvent(mouse);
16382 					}
16383 				}
16384 			}
16385 
16386 		  	version(with_eventloop)
16387 				send(mouse);
16388 		  break;
16389 		  case EventType.ButtonPress:
16390 		  case EventType.ButtonRelease:
16391 			MouseEvent mouse;
16392 			auto event = e.xbutton;
16393 
16394 			mouse.timestamp = event.time;
16395 
16396 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
16397 			mouse.x = event.x;
16398 			mouse.y = event.y;
16399 
16400 			static Time lastMouseDownTime = 0;
16401 			static int lastMouseDownButton = -1;
16402 
16403 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
16404 			if(e.type == EventType.ButtonPress) {
16405 				lastMouseDownTime = event.time;
16406 				lastMouseDownButton = event.button;
16407 			}
16408 
16409 			switch(event.button) {
16410 				case 1: mouse.button = MouseButton.left; break; // left
16411 				case 2: mouse.button = MouseButton.middle; break; // middle
16412 				case 3: mouse.button = MouseButton.right; break; // right
16413 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
16414 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
16415 				case 6: break; // idk
16416 				case 7: break; // idk
16417 				case 8: mouse.button = MouseButton.backButton; break;
16418 				case 9: mouse.button = MouseButton.forwardButton; break;
16419 				default:
16420 			}
16421 
16422 			// FIXME: double check this
16423 			mouse.modifierState = event.state;
16424 
16425 			//mouse.modifierState = event.detail;
16426 
16427 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
16428 				mouse.window = *win;
16429 				(*win).mdx(mouse);
16430 				if((*win).handleMouseEvent) {
16431 					XUnlockDisplay(display);
16432 					scope(exit) XLockDisplay(display);
16433 					(*win).handleMouseEvent(mouse);
16434 				}
16435 			}
16436 			version(with_eventloop)
16437 				send(mouse);
16438 		  break;
16439 
16440 		  case EventType.KeyPress:
16441 		  case EventType.KeyRelease:
16442 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
16443 			KeyEvent ke;
16444 			ke.pressed = e.type == EventType.KeyPress;
16445 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
16446 
16447 			auto sym = XKeycodeToKeysym(
16448 				XDisplayConnection.get(),
16449 				e.xkey.keycode,
16450 				0);
16451 
16452 			ke.key = cast(Key) sym;//e.xkey.keycode;
16453 
16454 			ke.modifierState = e.xkey.state;
16455 
16456 			// writefln("%x", sym);
16457 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
16458 			int charbuflen = 0; // return value of XwcLookupString
16459 			if (ke.pressed) {
16460 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
16461 				if (win !is null && win.xic !is null) {
16462 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
16463 					Status status;
16464 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
16465 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
16466 				} else {
16467 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
16468 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
16469 					char[16] buffer;
16470 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
16471 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
16472 				}
16473 			}
16474 
16475 			// if there's no char, subst one
16476 			if (charbuflen == 0) {
16477 				switch (sym) {
16478 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
16479 					case 0xff8d: // keypad enter
16480 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
16481 					default : // ignore
16482 				}
16483 			}
16484 
16485 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
16486 				ke.window = *win;
16487 
16488 
16489 				if(win.inputProxy)
16490 					win = &win.inputProxy;
16491 
16492 				// char events are separate since they are on Windows too
16493 				// also, xcompose can generate long char sequences
16494 				// don't send char events if Meta and/or Hyper is pressed
16495 				// TODO: ctrl+char should only send control chars; not yet
16496 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
16497 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
16498 				}
16499 
16500 				dchar[32] charsComingBuffer;
16501 				int charsComingPosition;
16502 				dchar[] charsComing = charsComingBuffer[];
16503 
16504 				if (ke.pressed && charbuflen > 0) {
16505 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
16506 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
16507 						if(charsComingPosition >= charsComing.length)
16508 							charsComing.length = charsComingPosition + 8;
16509 
16510 						charsComing[charsComingPosition++] = ch;
16511 					}
16512 
16513 					charsComing = charsComing[0 .. charsComingPosition];
16514 				} else {
16515 					charsComing = null;
16516 				}
16517 
16518 				ke.charsPossible = charsComing;
16519 
16520 				if (win.handleKeyEvent) {
16521 					XUnlockDisplay(display);
16522 					scope(exit) XLockDisplay(display);
16523 					win.handleKeyEvent(ke);
16524 				}
16525 
16526 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
16527 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
16528 					XUnlockDisplay(display);
16529 					scope(exit) XLockDisplay(display);
16530 					foreach(ch; charsComing)
16531 						win.handleCharEvent(ch);
16532 				}
16533 			}
16534 
16535 			version(with_eventloop)
16536 				send(ke);
16537 		  break;
16538 		  default:
16539 		}
16540 
16541 		return done;
16542 	}
16543 }
16544 
16545 /* *************************************** */
16546 /*      Done with simpledisplay stuff      */
16547 /* *************************************** */
16548 
16549 // Necessary C library bindings follow
16550 version(Windows) {} else
16551 version(X11) {
16552 
16553 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
16554 
16555 // X11 bindings needed here
16556 /*
16557 	A little of this is from the bindings project on
16558 	D Source and some of it is copy/paste from the C
16559 	header.
16560 
16561 	The DSource listing consistently used D's long
16562 	where C used long. That's wrong - C long is 32 bit, so
16563 	it should be int in D. I changed that here.
16564 
16565 	Note:
16566 	This isn't complete, just took what I needed for myself.
16567 */
16568 
16569 import core.stdc.stddef : wchar_t;
16570 
16571 interface XLib {
16572 extern(C) nothrow @nogc {
16573 	char* XResourceManagerString(Display*);
16574 	void XrmInitialize();
16575 	XrmDatabase XrmGetStringDatabase(char* data);
16576 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16577 
16578 	Cursor XCreateFontCursor(Display*, uint shape);
16579 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16580 	int XUndefineCursor(Display* display, Window w);
16581 
16582 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16583 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16584 	int XFreeCursor(Display* display, Cursor cursor);
16585 
16586 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16587 
16588 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16589 
16590 	XVaNestedList XVaCreateNestedList(int unused, ...);
16591 
16592 	char *XKeysymToString(KeySym keysym);
16593 	KeySym XKeycodeToKeysym(
16594 		Display*		/* display */,
16595 		KeyCode		/* keycode */,
16596 		int			/* index */
16597 	);
16598 
16599 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16600 
16601 	int XFree(void*);
16602 	int XDeleteProperty(Display *display, Window w, Atom property);
16603 
16604 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16605 
16606 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16607 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16608 		*actual_type_return, int *actual_format_return, arch_ulong
16609 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16610 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16611 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16612 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16613 
16614 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16615 
16616 	Window XGetSelectionOwner(Display *display, Atom selection);
16617 
16618 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16619 
16620 	char** XListFonts(Display*, const char*, int, int*);
16621 	void XFreeFontNames(char**);
16622 
16623 	Display* XOpenDisplay(const char*);
16624 	int XCloseDisplay(Display*);
16625 
16626 	int function() XSynchronize(Display*, bool);
16627 	int function() XSetAfterFunction(Display*, int function() proc);
16628 
16629 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16630 
16631 	Bool XSupportsLocale();
16632 	char* XSetLocaleModifiers(const(char)* modifier_list);
16633 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16634 	Status XCloseOM(XOM om);
16635 
16636 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16637 	Status XCloseIM(XIM im);
16638 
16639 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16640 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16641 	Display* XDisplayOfIM(XIM im);
16642 	char* XLocaleOfIM(XIM im);
16643 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16644 	void XDestroyIC(XIC ic);
16645 	void XSetICFocus(XIC ic);
16646 	void XUnsetICFocus(XIC ic);
16647 	//wchar_t* XwcResetIC(XIC ic);
16648 	char* XmbResetIC(XIC ic);
16649 	char* Xutf8ResetIC(XIC ic);
16650 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16651 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16652 	XIM XIMOfIC(XIC ic);
16653 
16654 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16655 
16656 
16657 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16658 	int XFreeFont(Display *display, XFontStruct *font_struct);
16659 	int XSetFont(Display* display, GC gc, Font font);
16660 	int XTextWidth(XFontStruct*, scope const char*, int);
16661 
16662 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16663 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16664 
16665 	Window XCreateSimpleWindow(
16666 		Display*	/* display */,
16667 		Window		/* parent */,
16668 		int			/* x */,
16669 		int			/* y */,
16670 		uint		/* width */,
16671 		uint		/* height */,
16672 		uint		/* border_width */,
16673 		uint		/* border */,
16674 		uint		/* background */
16675 	);
16676 	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);
16677 
16678 	int XReparentWindow(Display*, Window, Window, int, int);
16679 	int XClearWindow(Display*, Window);
16680 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16681 	int XMoveWindow(Display*, Window, int, int);
16682 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16683 
16684 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16685 
16686 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16687 
16688 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16689 
16690 	XImage *XCreateImage(
16691 		Display*		/* display */,
16692 		Visual*		/* visual */,
16693 		uint	/* depth */,
16694 		int			/* format */,
16695 		int			/* offset */,
16696 		ubyte*		/* data */,
16697 		uint	/* width */,
16698 		uint	/* height */,
16699 		int			/* bitmap_pad */,
16700 		int			/* bytes_per_line */
16701 	);
16702 
16703 	Status XInitImage (XImage* image);
16704 
16705 	Atom XInternAtom(
16706 		Display*		/* display */,
16707 		const char*	/* atom_name */,
16708 		Bool		/* only_if_exists */
16709 	);
16710 
16711 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16712 	char* XGetAtomName(Display*, Atom);
16713 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16714 
16715 	int XPutImage(
16716 		Display*	/* display */,
16717 		Drawable	/* d */,
16718 		GC			/* gc */,
16719 		XImage*	/* image */,
16720 		int			/* src_x */,
16721 		int			/* src_y */,
16722 		int			/* dest_x */,
16723 		int			/* dest_y */,
16724 		uint		/* width */,
16725 		uint		/* height */
16726 	);
16727 
16728 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16729 
16730 
16731 	int XDestroyWindow(
16732 		Display*	/* display */,
16733 		Window		/* w */
16734 	);
16735 
16736 	int XDestroyImage(XImage*);
16737 
16738 	int XSelectInput(
16739 		Display*	/* display */,
16740 		Window		/* w */,
16741 		EventMask	/* event_mask */
16742 	);
16743 
16744 	int XMapWindow(
16745 		Display*	/* display */,
16746 		Window		/* w */
16747 	);
16748 
16749 	Status XIconifyWindow(Display*, Window, int);
16750 	int XMapRaised(Display*, Window);
16751 	int XMapSubwindows(Display*, Window);
16752 
16753 	int XNextEvent(
16754 		Display*	/* display */,
16755 		XEvent*		/* event_return */
16756 	);
16757 
16758 	int XMaskEvent(Display*, arch_long, XEvent*);
16759 
16760 	Bool XFilterEvent(XEvent *event, Window window);
16761 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16762 
16763 	Status XSetWMProtocols(
16764 		Display*	/* display */,
16765 		Window		/* w */,
16766 		Atom*		/* protocols */,
16767 		int			/* count */
16768 	);
16769 
16770 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16771 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16772 
16773 
16774 	Status XInitThreads();
16775 	void XLockDisplay (Display* display);
16776 	void XUnlockDisplay (Display* display);
16777 
16778 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16779 
16780 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16781 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16782 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16783 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16784 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16785 
16786 
16787 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16788 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16789 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16790 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16791 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16792 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16793 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16794 	int XDrawPoint(Display*, Drawable, GC, int, int);
16795 	int XSetForeground(Display*, GC, uint);
16796 	int XSetBackground(Display*, GC, uint);
16797 
16798 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16799 	void XFreeFontSet(Display*, XFontSet);
16800 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16801 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16802 
16803 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16804 
16805 
16806 //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);
16807 
16808 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16809 	int XSetFunction(Display*, GC, int);
16810 
16811 	GC XCreateGC(Display*, Drawable, uint, void*);
16812 	int XCopyGC(Display*, GC, uint, GC);
16813 	int XFreeGC(Display*, GC);
16814 
16815 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16816 	bool XCheckMaskEvent(Display*, int, XEvent*);
16817 
16818 	int XPending(Display*);
16819 	int XEventsQueued(Display* display, int mode);
16820 
16821 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16822 	int XFreePixmap(Display*, Pixmap);
16823 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16824 	int XFlush(Display*);
16825 	int XBell(Display*, int);
16826 	int XSync(Display*, bool);
16827 
16828 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16829 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16830 
16831 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16832 	int XUngrabKeyboard(Display*, Time);
16833 
16834 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16835 
16836 	KeySym XStringToKeysym(const char *string);
16837 
16838 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16839 
16840 	Window XDefaultRootWindow(Display*);
16841 
16842 	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);
16843 
16844 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16845 
16846 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16847 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16848 
16849 	Status XAllocColor(Display*, Colormap, XColor*);
16850 
16851 	int XWithdrawWindow(Display*, Window, int);
16852 	int XUnmapWindow(Display*, Window);
16853 	int XLowerWindow(Display*, Window);
16854 	int XRaiseWindow(Display*, Window);
16855 
16856 	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);
16857 	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);
16858 
16859 	int XGetInputFocus(Display*, Window*, int*);
16860 	int XSetInputFocus(Display*, Window, int, Time);
16861 
16862 	XErrorHandler XSetErrorHandler(XErrorHandler);
16863 
16864 	int XGetErrorText(Display*, int, char*, int);
16865 
16866 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16867 
16868 
16869 	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);
16870 	int XUngrabPointer(Display *display, Time time);
16871 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16872 
16873 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16874 
16875 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16876 	int XSetClipMask(Display*, GC, Pixmap);
16877 	int XSetClipOrigin(Display*, GC, int, int);
16878 
16879 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16880 
16881 	void XSetWMName(Display*, Window, XTextProperty*);
16882 	Status XGetWMName(Display*, Window, XTextProperty*);
16883 	int XStoreName(Display* display, Window w, const(char)* window_name);
16884 
16885 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16886 
16887 }
16888 }
16889 
16890 interface Xext {
16891 extern(C) nothrow @nogc {
16892 	Status XShmAttach(Display*, XShmSegmentInfo*);
16893 	Status XShmDetach(Display*, XShmSegmentInfo*);
16894 	Status XShmPutImage(
16895 		Display*            /* dpy */,
16896 		Drawable            /* d */,
16897 		GC                  /* gc */,
16898 		XImage*             /* image */,
16899 		int                 /* src_x */,
16900 		int                 /* src_y */,
16901 		int                 /* dst_x */,
16902 		int                 /* dst_y */,
16903 		uint        /* src_width */,
16904 		uint        /* src_height */,
16905 		Bool                /* send_event */
16906 	);
16907 
16908 	Status XShmQueryExtension(Display*);
16909 
16910 	XImage *XShmCreateImage(
16911 		Display*            /* dpy */,
16912 		Visual*             /* visual */,
16913 		uint        /* depth */,
16914 		int                 /* format */,
16915 		char*               /* data */,
16916 		XShmSegmentInfo*    /* shminfo */,
16917 		uint        /* width */,
16918 		uint        /* height */
16919 	);
16920 
16921 	Pixmap XShmCreatePixmap(
16922 		Display*            /* dpy */,
16923 		Drawable            /* d */,
16924 		char*               /* data */,
16925 		XShmSegmentInfo*    /* shminfo */,
16926 		uint        /* width */,
16927 		uint        /* height */,
16928 		uint        /* depth */
16929 	);
16930 
16931 }
16932 }
16933 
16934 	// this requires -lXpm
16935 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16936 
16937 
16938 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16939 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16940 shared static this() {
16941 	xlib.loadDynamicLibrary();
16942 	xext.loadDynamicLibrary();
16943 }
16944 
16945 
16946 extern(C) nothrow @nogc {
16947 
16948 alias XrmDatabase = void*;
16949 struct XrmValue {
16950 	uint size;
16951 	void* addr;
16952 }
16953 
16954 struct XVisualInfo {
16955 	Visual* visual;
16956 	VisualID visualid;
16957 	int screen;
16958 	uint depth;
16959 	int c_class;
16960 	c_ulong red_mask;
16961 	c_ulong green_mask;
16962 	c_ulong blue_mask;
16963 	int colormap_size;
16964 	int bits_per_rgb;
16965 }
16966 
16967 enum VisualNoMask=	0x0;
16968 enum VisualIDMask=	0x1;
16969 enum VisualScreenMask=0x2;
16970 enum VisualDepthMask=	0x4;
16971 enum VisualClassMask=	0x8;
16972 enum VisualRedMaskMask=0x10;
16973 enum VisualGreenMaskMask=0x20;
16974 enum VisualBlueMaskMask=0x40;
16975 enum VisualColormapSizeMask=0x80;
16976 enum VisualBitsPerRGBMask=0x100;
16977 enum VisualAllMask=	0x1FF;
16978 
16979 enum AnyKey = 0;
16980 enum AnyModifier = 1 << 15;
16981 
16982 // XIM and other crap
16983 struct _XOM {}
16984 struct _XIM {}
16985 struct _XIC {}
16986 alias XOM = _XOM*;
16987 alias XIM = _XIM*;
16988 alias XIC = _XIC*;
16989 
16990 alias XVaNestedList = void*;
16991 
16992 alias XIMStyle = arch_ulong;
16993 enum : arch_ulong {
16994 	XIMPreeditArea      = 0x0001,
16995 	XIMPreeditCallbacks = 0x0002,
16996 	XIMPreeditPosition  = 0x0004,
16997 	XIMPreeditNothing   = 0x0008,
16998 	XIMPreeditNone      = 0x0010,
16999 	XIMStatusArea       = 0x0100,
17000 	XIMStatusCallbacks  = 0x0200,
17001 	XIMStatusNothing    = 0x0400,
17002 	XIMStatusNone       = 0x0800,
17003 }
17004 
17005 
17006 /* X Shared Memory Extension functions */
17007 	//pragma(lib, "Xshm");
17008 	alias arch_ulong ShmSeg;
17009 	struct XShmSegmentInfo {
17010 		ShmSeg shmseg;
17011 		int shmid;
17012 		ubyte* shmaddr;
17013 		Bool readOnly;
17014 	}
17015 
17016 	// and the necessary OS functions
17017 	int shmget(int, size_t, int);
17018 	void* shmat(int, scope const void*, int);
17019 	int shmdt(scope const void*);
17020 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
17021 
17022 	enum IPC_PRIVATE = 0;
17023 	enum IPC_CREAT = 512;
17024 	enum IPC_RMID = 0;
17025 
17026 /* MIT-SHM end */
17027 
17028 
17029 enum MappingType:int {
17030 	MappingModifier		=0,
17031 	MappingKeyboard		=1,
17032 	MappingPointer		=2
17033 }
17034 
17035 /* ImageFormat -- PutImage, GetImage */
17036 enum ImageFormat:int {
17037 	XYBitmap	=0,	/* depth 1, XYFormat */
17038 	XYPixmap	=1,	/* depth == drawable depth */
17039 	ZPixmap	=2	/* depth == drawable depth */
17040 }
17041 
17042 enum ModifierName:int {
17043 	ShiftMapIndex	=0,
17044 	LockMapIndex	=1,
17045 	ControlMapIndex	=2,
17046 	Mod1MapIndex	=3,
17047 	Mod2MapIndex	=4,
17048 	Mod3MapIndex	=5,
17049 	Mod4MapIndex	=6,
17050 	Mod5MapIndex	=7
17051 }
17052 
17053 enum ButtonMask:int {
17054 	Button1Mask	=1<<8,
17055 	Button2Mask	=1<<9,
17056 	Button3Mask	=1<<10,
17057 	Button4Mask	=1<<11,
17058 	Button5Mask	=1<<12,
17059 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17060 }
17061 
17062 enum KeyOrButtonMask:uint {
17063 	ShiftMask	=1<<0,
17064 	LockMask	=1<<1,
17065 	ControlMask	=1<<2,
17066 	Mod1Mask	=1<<3,
17067 	Mod2Mask	=1<<4,
17068 	Mod3Mask	=1<<5,
17069 	Mod4Mask	=1<<6,
17070 	Mod5Mask	=1<<7,
17071 	Button1Mask	=1<<8,
17072 	Button2Mask	=1<<9,
17073 	Button3Mask	=1<<10,
17074 	Button4Mask	=1<<11,
17075 	Button5Mask	=1<<12,
17076 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17077 }
17078 
17079 enum ButtonName:int {
17080 	Button1	=1,
17081 	Button2	=2,
17082 	Button3	=3,
17083 	Button4	=4,
17084 	Button5	=5
17085 }
17086 
17087 /* Notify modes */
17088 enum NotifyModes:int
17089 {
17090 	NotifyNormal		=0,
17091 	NotifyGrab			=1,
17092 	NotifyUngrab		=2,
17093 	NotifyWhileGrabbed	=3
17094 }
17095 enum NotifyHint = 1;	/* for MotionNotify events */
17096 
17097 /* Notify detail */
17098 enum NotifyDetail:int
17099 {
17100 	NotifyAncestor			=0,
17101 	NotifyVirtual			=1,
17102 	NotifyInferior			=2,
17103 	NotifyNonlinear			=3,
17104 	NotifyNonlinearVirtual	=4,
17105 	NotifyPointer			=5,
17106 	NotifyPointerRoot		=6,
17107 	NotifyDetailNone		=7
17108 }
17109 
17110 /* Visibility notify */
17111 
17112 enum VisibilityNotify:int
17113 {
17114 VisibilityUnobscured		=0,
17115 VisibilityPartiallyObscured	=1,
17116 VisibilityFullyObscured		=2
17117 }
17118 
17119 
17120 enum WindowStackingMethod:int
17121 {
17122 	Above		=0,
17123 	Below		=1,
17124 	TopIf		=2,
17125 	BottomIf	=3,
17126 	Opposite	=4
17127 }
17128 
17129 /* Circulation request */
17130 enum CirculationRequest:int
17131 {
17132 	PlaceOnTop		=0,
17133 	PlaceOnBottom	=1
17134 }
17135 
17136 enum PropertyNotification:int
17137 {
17138 	PropertyNewValue	=0,
17139 	PropertyDelete		=1
17140 }
17141 
17142 enum ColorMapNotification:int
17143 {
17144 	ColormapUninstalled	=0,
17145 	ColormapInstalled		=1
17146 }
17147 
17148 
17149 	struct _XPrivate {}
17150 	struct _XrmHashBucketRec {}
17151 
17152 	alias void* XPointer;
17153 	alias void* XExtData;
17154 
17155 	version( X86_64 ) {
17156 		alias ulong XID;
17157 		alias ulong arch_ulong;
17158 		alias long arch_long;
17159 	} else version (AArch64) {
17160 		alias ulong XID;
17161 		alias ulong arch_ulong;
17162 		alias long arch_long;
17163 	} else {
17164 		alias uint XID;
17165 		alias uint arch_ulong;
17166 		alias int arch_long;
17167 	}
17168 
17169 	alias XID Window;
17170 	alias XID Drawable;
17171 	alias XID Pixmap;
17172 
17173 	alias arch_ulong Atom;
17174 	alias int Bool;
17175 	alias Display XDisplay;
17176 
17177 	alias int ByteOrder;
17178 	alias arch_ulong Time;
17179 	alias void ScreenFormat;
17180 
17181 	struct XImage {
17182 		int width, height;			/* size of image */
17183 		int xoffset;				/* number of pixels offset in X direction */
17184 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
17185 		void *data;					/* pointer to image data */
17186 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
17187 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
17188 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
17189 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
17190 		int depth;					/* depth of image */
17191 		int bytes_per_line;			/* accelarator to next line */
17192 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
17193 		arch_ulong red_mask;	/* bits in z arrangment */
17194 		arch_ulong green_mask;
17195 		arch_ulong blue_mask;
17196 		XPointer obdata;			/* hook for the object routines to hang on */
17197 		static struct F {				/* image manipulation routines */
17198 			XImage* function(
17199 				XDisplay* 			/* display */,
17200 				Visual*				/* visual */,
17201 				uint				/* depth */,
17202 				int					/* format */,
17203 				int					/* offset */,
17204 				ubyte*				/* data */,
17205 				uint				/* width */,
17206 				uint				/* height */,
17207 				int					/* bitmap_pad */,
17208 				int					/* bytes_per_line */) create_image;
17209 			int function(XImage *) destroy_image;
17210 			arch_ulong function(XImage *, int, int) get_pixel;
17211 			int function(XImage *, int, int, arch_ulong) put_pixel;
17212 			XImage* function(XImage *, int, int, uint, uint) sub_image;
17213 			int function(XImage *, arch_long) add_pixel;
17214 		}
17215 		F f;
17216 	}
17217 	version(X86_64) static assert(XImage.sizeof == 136);
17218 	else version(X86) static assert(XImage.sizeof == 88);
17219 
17220 struct XCharStruct {
17221 	short       lbearing;       /* origin to left edge of raster */
17222 	short       rbearing;       /* origin to right edge of raster */
17223 	short       width;          /* advance to next char's origin */
17224 	short       ascent;         /* baseline to top edge of raster */
17225 	short       descent;        /* baseline to bottom edge of raster */
17226 	ushort attributes;  /* per char flags (not predefined) */
17227 }
17228 
17229 /*
17230  * To allow arbitrary information with fonts, there are additional properties
17231  * returned.
17232  */
17233 struct XFontProp {
17234 	Atom name;
17235 	arch_ulong card32;
17236 }
17237 
17238 alias Atom Font;
17239 
17240 struct XFontStruct {
17241 	XExtData *ext_data;           /* Hook for extension to hang data */
17242 	Font fid;                     /* Font ID for this font */
17243 	uint direction;           /* Direction the font is painted */
17244 	uint min_char_or_byte2;   /* First character */
17245 	uint max_char_or_byte2;   /* Last character */
17246 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
17247 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
17248 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
17249 	uint default_char;        /* Char to print for undefined character */
17250 	int n_properties;             /* How many properties there are */
17251 	XFontProp *properties;        /* Pointer to array of additional properties*/
17252 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
17253 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
17254 	XCharStruct *per_char;        /* first_char to last_char information */
17255 	int ascent;                   /* Max extent above baseline for spacing */
17256 	int descent;                  /* Max descent below baseline for spacing */
17257 }
17258 
17259 
17260 /*
17261  * Definitions of specific events.
17262  */
17263 struct XKeyEvent
17264 {
17265 	int type;			/* of event */
17266 	arch_ulong serial;		/* # of last request processed by server */
17267 	Bool send_event;	/* true if this came from a SendEvent request */
17268 	Display *display;	/* Display the event was read from */
17269 	Window window;	        /* "event" window it is reported relative to */
17270 	Window root;	        /* root window that the event occurred on */
17271 	Window subwindow;	/* child window */
17272 	Time time;		/* milliseconds */
17273 	int x, y;		/* pointer x, y coordinates in event window */
17274 	int x_root, y_root;	/* coordinates relative to root */
17275 	KeyOrButtonMask state;	/* key or button mask */
17276 	uint keycode;	/* detail */
17277 	Bool same_screen;	/* same screen flag */
17278 }
17279 version(X86_64) static assert(XKeyEvent.sizeof == 96);
17280 alias XKeyEvent XKeyPressedEvent;
17281 alias XKeyEvent XKeyReleasedEvent;
17282 
17283 struct XButtonEvent
17284 {
17285 	int type;		/* of event */
17286 	arch_ulong serial;	/* # of last request processed by server */
17287 	Bool send_event;	/* true if this came from a SendEvent request */
17288 	Display *display;	/* Display the event was read from */
17289 	Window window;	        /* "event" window it is reported relative to */
17290 	Window root;	        /* root window that the event occurred on */
17291 	Window subwindow;	/* child window */
17292 	Time time;		/* milliseconds */
17293 	int x, y;		/* pointer x, y coordinates in event window */
17294 	int x_root, y_root;	/* coordinates relative to root */
17295 	KeyOrButtonMask state;	/* key or button mask */
17296 	uint button;	/* detail */
17297 	Bool same_screen;	/* same screen flag */
17298 }
17299 alias XButtonEvent XButtonPressedEvent;
17300 alias XButtonEvent XButtonReleasedEvent;
17301 
17302 struct XMotionEvent{
17303 	int type;		/* of event */
17304 	arch_ulong serial;	/* # of last request processed by server */
17305 	Bool send_event;	/* true if this came from a SendEvent request */
17306 	Display *display;	/* Display the event was read from */
17307 	Window window;	        /* "event" window reported relative to */
17308 	Window root;	        /* root window that the event occurred on */
17309 	Window subwindow;	/* child window */
17310 	Time time;		/* milliseconds */
17311 	int x, y;		/* pointer x, y coordinates in event window */
17312 	int x_root, y_root;	/* coordinates relative to root */
17313 	KeyOrButtonMask state;	/* key or button mask */
17314 	byte is_hint;		/* detail */
17315 	Bool same_screen;	/* same screen flag */
17316 }
17317 alias XMotionEvent XPointerMovedEvent;
17318 
17319 struct XCrossingEvent{
17320 	int type;		/* of event */
17321 	arch_ulong serial;	/* # of last request processed by server */
17322 	Bool send_event;	/* true if this came from a SendEvent request */
17323 	Display *display;	/* Display the event was read from */
17324 	Window window;	        /* "event" window reported relative to */
17325 	Window root;	        /* root window that the event occurred on */
17326 	Window subwindow;	/* child window */
17327 	Time time;		/* milliseconds */
17328 	int x, y;		/* pointer x, y coordinates in event window */
17329 	int x_root, y_root;	/* coordinates relative to root */
17330 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
17331 	NotifyDetail detail;
17332 	/*
17333 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17334 	 * NotifyNonlinear,NotifyNonlinearVirtual
17335 	 */
17336 	Bool same_screen;	/* same screen flag */
17337 	Bool focus;		/* Boolean focus */
17338 	KeyOrButtonMask state;	/* key or button mask */
17339 }
17340 alias XCrossingEvent XEnterWindowEvent;
17341 alias XCrossingEvent XLeaveWindowEvent;
17342 
17343 struct XFocusChangeEvent{
17344 	int type;		/* FocusIn or FocusOut */
17345 	arch_ulong serial;	/* # of last request processed by server */
17346 	Bool send_event;	/* true if this came from a SendEvent request */
17347 	Display *display;	/* Display the event was read from */
17348 	Window window;		/* window of event */
17349 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
17350 				   NotifyGrab, NotifyUngrab */
17351 	NotifyDetail detail;
17352 	/*
17353 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17354 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
17355 	 * NotifyPointerRoot, NotifyDetailNone
17356 	 */
17357 }
17358 alias XFocusChangeEvent XFocusInEvent;
17359 alias XFocusChangeEvent XFocusOutEvent;
17360 
17361 enum CWBackPixmap              = (1L<<0);
17362 enum CWBackPixel               = (1L<<1);
17363 enum CWBorderPixmap            = (1L<<2);
17364 enum CWBorderPixel             = (1L<<3);
17365 enum CWBitGravity              = (1L<<4);
17366 enum CWWinGravity              = (1L<<5);
17367 enum CWBackingStore            = (1L<<6);
17368 enum CWBackingPlanes           = (1L<<7);
17369 enum CWBackingPixel            = (1L<<8);
17370 enum CWOverrideRedirect        = (1L<<9);
17371 enum CWSaveUnder               = (1L<<10);
17372 enum CWEventMask               = (1L<<11);
17373 enum CWDontPropagate           = (1L<<12);
17374 enum CWColormap                = (1L<<13);
17375 enum CWCursor                  = (1L<<14);
17376 
17377 struct XWindowAttributes {
17378 	int x, y;			/* location of window */
17379 	int width, height;		/* width and height of window */
17380 	int border_width;		/* border width of window */
17381 	int depth;			/* depth of window */
17382 	Visual *visual;			/* the associated visual structure */
17383 	Window root;			/* root of screen containing window */
17384 	int class_;			/* InputOutput, InputOnly*/
17385 	int bit_gravity;		/* one of the bit gravity values */
17386 	int win_gravity;		/* one of the window gravity values */
17387 	int backing_store;		/* NotUseful, WhenMapped, Always */
17388 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
17389 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
17390 	Bool save_under;		/* boolean, should bits under be saved? */
17391 	Colormap colormap;		/* color map to be associated with window */
17392 	Bool map_installed;		/* boolean, is color map currently installed*/
17393 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
17394 	arch_long all_event_masks;		/* set of events all people have interest in*/
17395 	arch_long your_event_mask;		/* my event mask */
17396 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
17397 	Bool override_redirect;		/* boolean value for override-redirect */
17398 	Screen *screen;			/* back pointer to correct screen */
17399 }
17400 
17401 enum IsUnmapped = 0;
17402 enum IsUnviewable = 1;
17403 enum IsViewable = 2;
17404 
17405 struct XSetWindowAttributes {
17406 	Pixmap background_pixmap;/* background, None, or ParentRelative */
17407 	arch_ulong background_pixel;/* background pixel */
17408 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
17409 	arch_ulong border_pixel;/* border pixel value */
17410 	int bit_gravity;         /* one of bit gravity values */
17411 	int win_gravity;         /* one of the window gravity values */
17412 	int backing_store;       /* NotUseful, WhenMapped, Always */
17413 	arch_ulong backing_planes;/* planes to be preserved if possible */
17414 	arch_ulong backing_pixel;/* value to use in restoring planes */
17415 	Bool save_under;         /* should bits under be saved? (popups) */
17416 	arch_long event_mask;         /* set of events that should be saved */
17417 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
17418 	Bool override_redirect;  /* boolean value for override_redirect */
17419 	Colormap colormap;       /* color map to be associated with window */
17420 	Cursor cursor;           /* cursor to be displayed (or None) */
17421 }
17422 
17423 
17424 alias int Status;
17425 
17426 
17427 enum EventMask:int
17428 {
17429 	NoEventMask				=0,
17430 	KeyPressMask			=1<<0,
17431 	KeyReleaseMask			=1<<1,
17432 	ButtonPressMask			=1<<2,
17433 	ButtonReleaseMask		=1<<3,
17434 	EnterWindowMask			=1<<4,
17435 	LeaveWindowMask			=1<<5,
17436 	PointerMotionMask		=1<<6,
17437 	PointerMotionHintMask	=1<<7,
17438 	Button1MotionMask		=1<<8,
17439 	Button2MotionMask		=1<<9,
17440 	Button3MotionMask		=1<<10,
17441 	Button4MotionMask		=1<<11,
17442 	Button5MotionMask		=1<<12,
17443 	ButtonMotionMask		=1<<13,
17444 	KeymapStateMask		=1<<14,
17445 	ExposureMask			=1<<15,
17446 	VisibilityChangeMask	=1<<16,
17447 	StructureNotifyMask		=1<<17,
17448 	ResizeRedirectMask		=1<<18,
17449 	SubstructureNotifyMask	=1<<19,
17450 	SubstructureRedirectMask=1<<20,
17451 	FocusChangeMask			=1<<21,
17452 	PropertyChangeMask		=1<<22,
17453 	ColormapChangeMask		=1<<23,
17454 	OwnerGrabButtonMask		=1<<24
17455 }
17456 
17457 struct MwmHints {
17458 	c_ulong flags;
17459 	c_ulong functions;
17460 	c_ulong decorations;
17461 	c_long input_mode;
17462 	c_ulong status;
17463 }
17464 
17465 enum {
17466 	MWM_HINTS_FUNCTIONS = (1L << 0),
17467 	MWM_HINTS_DECORATIONS =  (1L << 1),
17468 
17469 	MWM_FUNC_ALL = (1L << 0),
17470 	MWM_FUNC_RESIZE = (1L << 1),
17471 	MWM_FUNC_MOVE = (1L << 2),
17472 	MWM_FUNC_MINIMIZE = (1L << 3),
17473 	MWM_FUNC_MAXIMIZE = (1L << 4),
17474 	MWM_FUNC_CLOSE = (1L << 5),
17475 
17476 	MWM_DECOR_ALL = (1L << 0),
17477 	MWM_DECOR_BORDER = (1L << 1),
17478 	MWM_DECOR_RESIZEH = (1L << 2),
17479 	MWM_DECOR_TITLE = (1L << 3),
17480 	MWM_DECOR_MENU = (1L << 4),
17481 	MWM_DECOR_MINIMIZE = (1L << 5),
17482 	MWM_DECOR_MAXIMIZE = (1L << 6),
17483 }
17484 
17485 import core.stdc.config : c_long, c_ulong;
17486 
17487 	/* Size hints mask bits */
17488 
17489 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
17490 	enum   USSize      = (1L << 1)          /* user specified width, height */;
17491 	enum   PPosition   = (1L << 2)          /* program specified position */;
17492 	enum   PSize       = (1L << 3)          /* program specified size */;
17493 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
17494 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
17495 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
17496 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
17497 	enum   PBaseSize   = (1L << 8);
17498 	enum   PWinGravity = (1L << 9);
17499 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
17500 	struct XSizeHints {
17501 		arch_long flags;         /* marks which fields in this structure are defined */
17502 		int x, y;           /* Obsolete */
17503 		int width, height;  /* Obsolete */
17504 		int min_width, min_height;
17505 		int max_width, max_height;
17506 		int width_inc, height_inc;
17507 		struct Aspect {
17508 			int x;       /* numerator */
17509 			int y;       /* denominator */
17510 		}
17511 
17512 		Aspect min_aspect;
17513 		Aspect max_aspect;
17514 		int base_width, base_height;
17515 		int win_gravity;
17516 		/* this structure may be extended in the future */
17517 	}
17518 
17519 
17520 
17521 enum EventType:int
17522 {
17523 	KeyPress			=2,
17524 	KeyRelease			=3,
17525 	ButtonPress			=4,
17526 	ButtonRelease		=5,
17527 	MotionNotify		=6,
17528 	EnterNotify			=7,
17529 	LeaveNotify			=8,
17530 	FocusIn				=9,
17531 	FocusOut			=10,
17532 	KeymapNotify		=11,
17533 	Expose				=12,
17534 	GraphicsExpose		=13,
17535 	NoExpose			=14,
17536 	VisibilityNotify	=15,
17537 	CreateNotify		=16,
17538 	DestroyNotify		=17,
17539 	UnmapNotify		=18,
17540 	MapNotify			=19,
17541 	MapRequest			=20,
17542 	ReparentNotify		=21,
17543 	ConfigureNotify		=22,
17544 	ConfigureRequest	=23,
17545 	GravityNotify		=24,
17546 	ResizeRequest		=25,
17547 	CirculateNotify		=26,
17548 	CirculateRequest	=27,
17549 	PropertyNotify		=28,
17550 	SelectionClear		=29,
17551 	SelectionRequest	=30,
17552 	SelectionNotify		=31,
17553 	ColormapNotify		=32,
17554 	ClientMessage		=33,
17555 	MappingNotify		=34,
17556 	LASTEvent			=35	/* must be bigger than any event # */
17557 }
17558 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
17559 struct XKeymapEvent
17560 {
17561 	int type;
17562 	arch_ulong serial;	/* # of last request processed by server */
17563 	Bool send_event;	/* true if this came from a SendEvent request */
17564 	Display *display;	/* Display the event was read from */
17565 	Window window;
17566 	byte[32] key_vector;
17567 }
17568 
17569 struct XExposeEvent
17570 {
17571 	int type;
17572 	arch_ulong serial;	/* # of last request processed by server */
17573 	Bool send_event;	/* true if this came from a SendEvent request */
17574 	Display *display;	/* Display the event was read from */
17575 	Window window;
17576 	int x, y;
17577 	int width, height;
17578 	int count;		/* if non-zero, at least this many more */
17579 }
17580 
17581 struct XGraphicsExposeEvent{
17582 	int type;
17583 	arch_ulong serial;	/* # of last request processed by server */
17584 	Bool send_event;	/* true if this came from a SendEvent request */
17585 	Display *display;	/* Display the event was read from */
17586 	Drawable drawable;
17587 	int x, y;
17588 	int width, height;
17589 	int count;		/* if non-zero, at least this many more */
17590 	int major_code;		/* core is CopyArea or CopyPlane */
17591 	int minor_code;		/* not defined in the core */
17592 }
17593 
17594 struct XNoExposeEvent{
17595 	int type;
17596 	arch_ulong serial;	/* # of last request processed by server */
17597 	Bool send_event;	/* true if this came from a SendEvent request */
17598 	Display *display;	/* Display the event was read from */
17599 	Drawable drawable;
17600 	int major_code;		/* core is CopyArea or CopyPlane */
17601 	int minor_code;		/* not defined in the core */
17602 }
17603 
17604 struct XVisibilityEvent{
17605 	int type;
17606 	arch_ulong serial;	/* # of last request processed by server */
17607 	Bool send_event;	/* true if this came from a SendEvent request */
17608 	Display *display;	/* Display the event was read from */
17609 	Window window;
17610 	VisibilityNotify state;		/* Visibility state */
17611 }
17612 
17613 struct XCreateWindowEvent{
17614 	int type;
17615 	arch_ulong serial;	/* # of last request processed by server */
17616 	Bool send_event;	/* true if this came from a SendEvent request */
17617 	Display *display;	/* Display the event was read from */
17618 	Window parent;		/* parent of the window */
17619 	Window window;		/* window id of window created */
17620 	int x, y;		/* window location */
17621 	int width, height;	/* size of window */
17622 	int border_width;	/* border width */
17623 	Bool override_redirect;	/* creation should be overridden */
17624 }
17625 
17626 struct XDestroyWindowEvent
17627 {
17628 	int type;
17629 	arch_ulong serial;		/* # of last request processed by server */
17630 	Bool send_event;	/* true if this came from a SendEvent request */
17631 	Display *display;	/* Display the event was read from */
17632 	Window event;
17633 	Window window;
17634 }
17635 
17636 struct XUnmapEvent
17637 {
17638 	int type;
17639 	arch_ulong serial;		/* # of last request processed by server */
17640 	Bool send_event;	/* true if this came from a SendEvent request */
17641 	Display *display;	/* Display the event was read from */
17642 	Window event;
17643 	Window window;
17644 	Bool from_configure;
17645 }
17646 
17647 struct XMapEvent
17648 {
17649 	int type;
17650 	arch_ulong serial;		/* # of last request processed by server */
17651 	Bool send_event;	/* true if this came from a SendEvent request */
17652 	Display *display;	/* Display the event was read from */
17653 	Window event;
17654 	Window window;
17655 	Bool override_redirect;	/* Boolean, is override set... */
17656 }
17657 
17658 struct XMapRequestEvent
17659 {
17660 	int type;
17661 	arch_ulong serial;	/* # of last request processed by server */
17662 	Bool send_event;	/* true if this came from a SendEvent request */
17663 	Display *display;	/* Display the event was read from */
17664 	Window parent;
17665 	Window window;
17666 }
17667 
17668 struct XReparentEvent
17669 {
17670 	int type;
17671 	arch_ulong serial;	/* # of last request processed by server */
17672 	Bool send_event;	/* true if this came from a SendEvent request */
17673 	Display *display;	/* Display the event was read from */
17674 	Window event;
17675 	Window window;
17676 	Window parent;
17677 	int x, y;
17678 	Bool override_redirect;
17679 }
17680 
17681 struct XConfigureEvent
17682 {
17683 	int type;
17684 	arch_ulong serial;	/* # of last request processed by server */
17685 	Bool send_event;	/* true if this came from a SendEvent request */
17686 	Display *display;	/* Display the event was read from */
17687 	Window event;
17688 	Window window;
17689 	int x, y;
17690 	int width, height;
17691 	int border_width;
17692 	Window above;
17693 	Bool override_redirect;
17694 }
17695 
17696 struct XGravityEvent
17697 {
17698 	int type;
17699 	arch_ulong serial;	/* # of last request processed by server */
17700 	Bool send_event;	/* true if this came from a SendEvent request */
17701 	Display *display;	/* Display the event was read from */
17702 	Window event;
17703 	Window window;
17704 	int x, y;
17705 }
17706 
17707 struct XResizeRequestEvent
17708 {
17709 	int type;
17710 	arch_ulong serial;	/* # of last request processed by server */
17711 	Bool send_event;	/* true if this came from a SendEvent request */
17712 	Display *display;	/* Display the event was read from */
17713 	Window window;
17714 	int width, height;
17715 }
17716 
17717 struct  XConfigureRequestEvent
17718 {
17719 	int type;
17720 	arch_ulong serial;	/* # of last request processed by server */
17721 	Bool send_event;	/* true if this came from a SendEvent request */
17722 	Display *display;	/* Display the event was read from */
17723 	Window parent;
17724 	Window window;
17725 	int x, y;
17726 	int width, height;
17727 	int border_width;
17728 	Window above;
17729 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17730 	arch_ulong value_mask;
17731 }
17732 
17733 struct XCirculateEvent
17734 {
17735 	int type;
17736 	arch_ulong serial;	/* # of last request processed by server */
17737 	Bool send_event;	/* true if this came from a SendEvent request */
17738 	Display *display;	/* Display the event was read from */
17739 	Window event;
17740 	Window window;
17741 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17742 }
17743 
17744 struct XCirculateRequestEvent
17745 {
17746 	int type;
17747 	arch_ulong serial;	/* # of last request processed by server */
17748 	Bool send_event;	/* true if this came from a SendEvent request */
17749 	Display *display;	/* Display the event was read from */
17750 	Window parent;
17751 	Window window;
17752 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17753 }
17754 
17755 struct XPropertyEvent
17756 {
17757 	int type;
17758 	arch_ulong serial;	/* # of last request processed by server */
17759 	Bool send_event;	/* true if this came from a SendEvent request */
17760 	Display *display;	/* Display the event was read from */
17761 	Window window;
17762 	Atom atom;
17763 	Time time;
17764 	PropertyNotification state;		/* NewValue, Deleted */
17765 }
17766 
17767 struct XSelectionClearEvent
17768 {
17769 	int type;
17770 	arch_ulong serial;	/* # of last request processed by server */
17771 	Bool send_event;	/* true if this came from a SendEvent request */
17772 	Display *display;	/* Display the event was read from */
17773 	Window window;
17774 	Atom selection;
17775 	Time time;
17776 }
17777 
17778 struct XSelectionRequestEvent
17779 {
17780 	int type;
17781 	arch_ulong serial;	/* # of last request processed by server */
17782 	Bool send_event;	/* true if this came from a SendEvent request */
17783 	Display *display;	/* Display the event was read from */
17784 	Window owner;
17785 	Window requestor;
17786 	Atom selection;
17787 	Atom target;
17788 	Atom property;
17789 	Time time;
17790 }
17791 
17792 struct XSelectionEvent
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 requestor;
17799 	Atom selection;
17800 	Atom target;
17801 	Atom property;		/* ATOM or None */
17802 	Time time;
17803 }
17804 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17805 
17806 struct XColormapEvent
17807 {
17808 	int type;
17809 	arch_ulong serial;	/* # of last request processed by server */
17810 	Bool send_event;	/* true if this came from a SendEvent request */
17811 	Display *display;	/* Display the event was read from */
17812 	Window window;
17813 	Colormap colormap;	/* COLORMAP or None */
17814 	Bool new_;		/* C++ */
17815 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17816 }
17817 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17818 
17819 struct XClientMessageEvent
17820 {
17821 	int type;
17822 	arch_ulong serial;	/* # of last request processed by server */
17823 	Bool send_event;	/* true if this came from a SendEvent request */
17824 	Display *display;	/* Display the event was read from */
17825 	Window window;
17826 	Atom message_type;
17827 	int format;
17828 	union Data{
17829 		byte[20] b;
17830 		short[10] s;
17831 		arch_ulong[5] l;
17832 	}
17833 	Data data;
17834 
17835 }
17836 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17837 
17838 struct XMappingEvent
17839 {
17840 	int type;
17841 	arch_ulong serial;	/* # of last request processed by server */
17842 	Bool send_event;	/* true if this came from a SendEvent request */
17843 	Display *display;	/* Display the event was read from */
17844 	Window window;		/* unused */
17845 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17846 				   MappingPointer */
17847 	int first_keycode;	/* first keycode */
17848 	int count;		/* defines range of change w. first_keycode*/
17849 }
17850 
17851 struct XErrorEvent
17852 {
17853 	int type;
17854 	Display *display;	/* Display the event was read from */
17855 	XID resourceid;		/* resource id */
17856 	arch_ulong serial;	/* serial number of failed request */
17857 	ubyte error_code;	/* error code of failed request */
17858 	ubyte request_code;	/* Major op-code of failed request */
17859 	ubyte minor_code;	/* Minor op-code of failed request */
17860 }
17861 
17862 struct XAnyEvent
17863 {
17864 	int type;
17865 	arch_ulong serial;	/* # of last request processed by server */
17866 	Bool send_event;	/* true if this came from a SendEvent request */
17867 	Display *display;/* Display the event was read from */
17868 	Window window;	/* window on which event was requested in event mask */
17869 }
17870 
17871 union XEvent{
17872 	int type;		/* must not be changed; first element */
17873 	XAnyEvent xany;
17874 	XKeyEvent xkey;
17875 	XButtonEvent xbutton;
17876 	XMotionEvent xmotion;
17877 	XCrossingEvent xcrossing;
17878 	XFocusChangeEvent xfocus;
17879 	XExposeEvent xexpose;
17880 	XGraphicsExposeEvent xgraphicsexpose;
17881 	XNoExposeEvent xnoexpose;
17882 	XVisibilityEvent xvisibility;
17883 	XCreateWindowEvent xcreatewindow;
17884 	XDestroyWindowEvent xdestroywindow;
17885 	XUnmapEvent xunmap;
17886 	XMapEvent xmap;
17887 	XMapRequestEvent xmaprequest;
17888 	XReparentEvent xreparent;
17889 	XConfigureEvent xconfigure;
17890 	XGravityEvent xgravity;
17891 	XResizeRequestEvent xresizerequest;
17892 	XConfigureRequestEvent xconfigurerequest;
17893 	XCirculateEvent xcirculate;
17894 	XCirculateRequestEvent xcirculaterequest;
17895 	XPropertyEvent xproperty;
17896 	XSelectionClearEvent xselectionclear;
17897 	XSelectionRequestEvent xselectionrequest;
17898 	XSelectionEvent xselection;
17899 	XColormapEvent xcolormap;
17900 	XClientMessageEvent xclient;
17901 	XMappingEvent xmapping;
17902 	XErrorEvent xerror;
17903 	XKeymapEvent xkeymap;
17904 	arch_ulong[24] pad;
17905 }
17906 
17907 
17908 	struct Display {
17909 		XExtData *ext_data;	/* hook for extension to hang data */
17910 		_XPrivate *private1;
17911 		int fd;			/* Network socket. */
17912 		int private2;
17913 		int proto_major_version;/* major version of server's X protocol */
17914 		int proto_minor_version;/* minor version of servers X protocol */
17915 		char *vendor;		/* vendor of the server hardware */
17916 	    	XID private3;
17917 		XID private4;
17918 		XID private5;
17919 		int private6;
17920 		XID function(Display*)resource_alloc;/* allocator function */
17921 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17922 		int bitmap_unit;	/* padding and data requirements */
17923 		int bitmap_pad;		/* padding requirements on bitmaps */
17924 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17925 		int nformats;		/* number of pixmap formats in list */
17926 		ScreenFormat *pixmap_format;	/* pixmap format list */
17927 		int private8;
17928 		int release;		/* release of the server */
17929 		_XPrivate *private9;
17930 		_XPrivate *private10;
17931 		int qlen;		/* Length of input event queue */
17932 		arch_ulong last_request_read; /* seq number of last event read */
17933 		arch_ulong request;	/* sequence number of last request. */
17934 		XPointer private11;
17935 		XPointer private12;
17936 		XPointer private13;
17937 		XPointer private14;
17938 		uint max_request_size; /* maximum number 32 bit words in request*/
17939 		_XrmHashBucketRec *db;
17940 		int function  (Display*)private15;
17941 		char *display_name;	/* "host:display" string used on this connect*/
17942 		int default_screen;	/* default screen for operations */
17943 		int nscreens;		/* number of screens on this server*/
17944 		Screen *screens;	/* pointer to list of screens */
17945 		arch_ulong motion_buffer;	/* size of motion buffer */
17946 		arch_ulong private16;
17947 		int min_keycode;	/* minimum defined keycode */
17948 		int max_keycode;	/* maximum defined keycode */
17949 		XPointer private17;
17950 		XPointer private18;
17951 		int private19;
17952 		byte *xdefaults;	/* contents of defaults from server */
17953 		/* there is more to this structure, but it is private to Xlib */
17954 	}
17955 
17956 	// I got these numbers from a C program as a sanity test
17957 	version(X86_64) {
17958 		static assert(Display.sizeof == 296);
17959 		static assert(XPointer.sizeof == 8);
17960 		static assert(XErrorEvent.sizeof == 40);
17961 		static assert(XAnyEvent.sizeof == 40);
17962 		static assert(XMappingEvent.sizeof == 56);
17963 		static assert(XEvent.sizeof == 192);
17964     	} else version (AArch64) {
17965 		// omit check for aarch64
17966 	} else {
17967 		static assert(Display.sizeof == 176);
17968 		static assert(XPointer.sizeof == 4);
17969 		static assert(XEvent.sizeof == 96);
17970 	}
17971 
17972 struct Depth
17973 {
17974 	int depth;		/* this depth (Z) of the depth */
17975 	int nvisuals;		/* number of Visual types at this depth */
17976 	Visual *visuals;	/* list of visuals possible at this depth */
17977 }
17978 
17979 alias void* GC;
17980 alias c_ulong VisualID;
17981 alias XID Colormap;
17982 alias XID Cursor;
17983 alias XID KeySym;
17984 alias uint KeyCode;
17985 enum None = 0;
17986 }
17987 
17988 version(without_opengl) {}
17989 else {
17990 extern(C) nothrow @nogc {
17991 
17992 
17993 static if(!SdpyIsUsingIVGLBinds) {
17994 enum GLX_USE_GL=            1;       /* support GLX rendering */
17995 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17996 enum GLX_LEVEL=             3;       /* level in plane stacking */
17997 enum GLX_RGBA=              4;       /* true if RGBA mode */
17998 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17999 enum GLX_STEREO=            6;       /* stereo buffering supported */
18000 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
18001 enum GLX_RED_SIZE=          8;       /* number of red component bits */
18002 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
18003 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
18004 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
18005 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
18006 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
18007 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
18008 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
18009 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
18010 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
18011 
18012 
18013 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
18014 
18015 
18016 
18017 enum GL_TRUE = 1;
18018 enum GL_FALSE = 0;
18019 }
18020 
18021 alias XID GLXContextID;
18022 alias XID GLXPixmap;
18023 alias XID GLXDrawable;
18024 alias XID GLXPbuffer;
18025 alias XID GLXWindow;
18026 alias XID GLXFBConfigID;
18027 alias void* GLXContext;
18028 
18029 }
18030 }
18031 
18032 enum AllocNone = 0;
18033 
18034 extern(C) {
18035 	/* WARNING, this type not in Xlib spec */
18036 	extern(C) alias XIOErrorHandler = int function (Display* display);
18037 }
18038 
18039 extern(C) nothrow
18040 alias XErrorHandler = int function(Display*, XErrorEvent*);
18041 
18042 extern(C) nothrow @nogc {
18043 struct Screen{
18044 	XExtData *ext_data;		/* hook for extension to hang data */
18045 	Display *display;		/* back pointer to display structure */
18046 	Window root;			/* Root window id. */
18047 	int width, height;		/* width and height of screen */
18048 	int mwidth, mheight;	/* width and height of  in millimeters */
18049 	int ndepths;			/* number of depths possible */
18050 	Depth *depths;			/* list of allowable depths on the screen */
18051 	int root_depth;			/* bits per pixel */
18052 	Visual *root_visual;	/* root visual */
18053 	GC default_gc;			/* GC for the root root visual */
18054 	Colormap cmap;			/* default color map */
18055 	uint white_pixel;
18056 	uint black_pixel;		/* White and Black pixel values */
18057 	int max_maps, min_maps;	/* max and min color maps */
18058 	int backing_store;		/* Never, WhenMapped, Always */
18059 	bool save_unders;
18060 	int root_input_mask;	/* initial root input mask */
18061 }
18062 
18063 struct Visual
18064 {
18065 	XExtData *ext_data;	/* hook for extension to hang data */
18066 	VisualID visualid;	/* visual id of this visual */
18067 	int class_;			/* class of screen (monochrome, etc.) */
18068 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
18069 	int bits_per_rgb;	/* log base 2 of distinct color values */
18070 	int map_entries;	/* color map entries */
18071 }
18072 
18073 	alias Display* _XPrivDisplay;
18074 
18075 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system {
18076 		assert(dpy !is null);
18077 		return &dpy.screens[scr];
18078 	}
18079 
18080 	extern(D) Window RootWindow(Display *dpy,int scr) {
18081 		return ScreenOfDisplay(dpy,scr).root;
18082 	}
18083 
18084 	struct XWMHints {
18085 		arch_long flags;
18086 		Bool input;
18087 		int initial_state;
18088 		Pixmap icon_pixmap;
18089 		Window icon_window;
18090 		int icon_x, icon_y;
18091 		Pixmap icon_mask;
18092 		XID window_group;
18093 	}
18094 
18095 	struct XClassHint {
18096 		char* res_name;
18097 		char* res_class;
18098 	}
18099 
18100 	extern(D) int DefaultScreen(Display *dpy) {
18101 		return dpy.default_screen;
18102 	}
18103 
18104 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
18105 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
18106 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
18107 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
18108 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
18109 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
18110 
18111 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
18112 
18113 	enum int AnyPropertyType = 0;
18114 	enum int Success = 0;
18115 
18116 	enum int RevertToNone = None;
18117 	enum int PointerRoot = 1;
18118 	enum Time CurrentTime = 0;
18119 	enum int RevertToPointerRoot = PointerRoot;
18120 	enum int RevertToParent = 2;
18121 
18122 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
18123 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
18124 	}
18125 
18126 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
18127 		return ScreenOfDisplay(dpy,scr).root_visual;
18128 	}
18129 
18130 	extern(D) GC DefaultGC(Display *dpy,int scr) {
18131 		return ScreenOfDisplay(dpy,scr).default_gc;
18132 	}
18133 
18134 	extern(D) uint BlackPixel(Display *dpy,int scr) {
18135 		return ScreenOfDisplay(dpy,scr).black_pixel;
18136 	}
18137 
18138 	extern(D) uint WhitePixel(Display *dpy,int scr) {
18139 		return ScreenOfDisplay(dpy,scr).white_pixel;
18140 	}
18141 
18142 	alias void* XFontSet; // i think
18143 	struct XmbTextItem {
18144 		char* chars;
18145 		int nchars;
18146 		int delta;
18147 		XFontSet font_set;
18148 	}
18149 
18150 	struct XTextItem {
18151 		char* chars;
18152 		int nchars;
18153 		int delta;
18154 		Font font;
18155 	}
18156 
18157 	enum {
18158 		GXclear        = 0x0, /* 0 */
18159 		GXand          = 0x1, /* src AND dst */
18160 		GXandReverse   = 0x2, /* src AND NOT dst */
18161 		GXcopy         = 0x3, /* src */
18162 		GXandInverted  = 0x4, /* NOT src AND dst */
18163 		GXnoop         = 0x5, /* dst */
18164 		GXxor          = 0x6, /* src XOR dst */
18165 		GXor           = 0x7, /* src OR dst */
18166 		GXnor          = 0x8, /* NOT src AND NOT dst */
18167 		GXequiv        = 0x9, /* NOT src XOR dst */
18168 		GXinvert       = 0xa, /* NOT dst */
18169 		GXorReverse    = 0xb, /* src OR NOT dst */
18170 		GXcopyInverted = 0xc, /* NOT src */
18171 		GXorInverted   = 0xd, /* NOT src OR dst */
18172 		GXnand         = 0xe, /* NOT src OR NOT dst */
18173 		GXset          = 0xf, /* 1 */
18174 	}
18175 	enum QueueMode : int {
18176 		QueuedAlready,
18177 		QueuedAfterReading,
18178 		QueuedAfterFlush
18179 	}
18180 
18181 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
18182 
18183 	struct XPoint {
18184 		short x;
18185 		short y;
18186 	}
18187 
18188 	enum CoordMode:int {
18189 		CoordModeOrigin = 0,
18190 		CoordModePrevious = 1
18191 	}
18192 
18193 	enum PolygonShape:int {
18194 		Complex = 0,
18195 		Nonconvex = 1,
18196 		Convex = 2
18197 	}
18198 
18199 	struct XTextProperty {
18200 		const(char)* value;		/* same as Property routines */
18201 		Atom encoding;			/* prop type */
18202 		int format;				/* prop data format: 8, 16, or 32 */
18203 		arch_ulong nitems;		/* number of data items in value */
18204 	}
18205 
18206 	version( X86_64 ) {
18207 		static assert(XTextProperty.sizeof == 32);
18208 	}
18209 
18210 
18211 	struct XGCValues {
18212 		int function_;           /* logical operation */
18213 		arch_ulong plane_mask;/* plane mask */
18214 		arch_ulong foreground;/* foreground pixel */
18215 		arch_ulong background;/* background pixel */
18216 		int line_width;         /* line width */
18217 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
18218 		int cap_style;          /* CapNotLast, CapButt,
18219 					   CapRound, CapProjecting */
18220 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
18221 		int fill_style;         /* FillSolid, FillTiled,
18222 					   FillStippled, FillOpaeueStippled */
18223 		int fill_rule;          /* EvenOddRule, WindingRule */
18224 		int arc_mode;           /* ArcChord, ArcPieSlice */
18225 		Pixmap tile;            /* tile pixmap for tiling operations */
18226 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
18227 		int ts_x_origin;        /* offset for tile or stipple operations */
18228 		int ts_y_origin;
18229 		Font font;              /* default text font for text operations */
18230 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
18231 		Bool graphics_exposures;/* boolean, should exposures be generated */
18232 		int clip_x_origin;      /* origin for clipping */
18233 		int clip_y_origin;
18234 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
18235 		int dash_offset;        /* patterned/dashed line information */
18236 		char dashes;
18237 	}
18238 
18239 	struct XColor {
18240 		arch_ulong pixel;
18241 		ushort red, green, blue;
18242 		byte flags;
18243 		byte pad;
18244 	}
18245 
18246 	struct XRectangle {
18247 		short x;
18248 		short y;
18249 		ushort width;
18250 		ushort height;
18251 	}
18252 
18253 	enum ClipByChildren = 0;
18254 	enum IncludeInferiors = 1;
18255 
18256 	enum Atom XA_PRIMARY = 1;
18257 	enum Atom XA_SECONDARY = 2;
18258 	enum Atom XA_STRING = 31;
18259 	enum Atom XA_CARDINAL = 6;
18260 	enum Atom XA_WM_NAME = 39;
18261 	enum Atom XA_ATOM = 4;
18262 	enum Atom XA_WINDOW = 33;
18263 	enum Atom XA_WM_HINTS = 35;
18264 	enum int PropModeAppend = 2;
18265 	enum int PropModeReplace = 0;
18266 	enum int PropModePrepend = 1;
18267 
18268 	enum int CopyFromParent = 0;
18269 	enum int InputOutput = 1;
18270 
18271 	// XWMHints
18272 	enum InputHint = 1 << 0;
18273 	enum StateHint = 1 << 1;
18274 	enum IconPixmapHint = (1L << 2);
18275 	enum IconWindowHint = (1L << 3);
18276 	enum IconPositionHint = (1L << 4);
18277 	enum IconMaskHint = (1L << 5);
18278 	enum WindowGroupHint = (1L << 6);
18279 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
18280 	enum XUrgencyHint = (1L << 8);
18281 
18282 	// GC Components
18283 	enum GCFunction           =   (1L<<0);
18284 	enum GCPlaneMask         =    (1L<<1);
18285 	enum GCForeground       =     (1L<<2);
18286 	enum GCBackground      =      (1L<<3);
18287 	enum GCLineWidth      =       (1L<<4);
18288 	enum GCLineStyle     =        (1L<<5);
18289 	enum GCCapStyle     =         (1L<<6);
18290 	enum GCJoinStyle   =          (1L<<7);
18291 	enum GCFillStyle  =           (1L<<8);
18292 	enum GCFillRule  =            (1L<<9);
18293 	enum GCTile     =             (1L<<10);
18294 	enum GCStipple           =    (1L<<11);
18295 	enum GCTileStipXOrigin  =     (1L<<12);
18296 	enum GCTileStipYOrigin =      (1L<<13);
18297 	enum GCFont               =   (1L<<14);
18298 	enum GCSubwindowMode     =    (1L<<15);
18299 	enum GCGraphicsExposures=     (1L<<16);
18300 	enum GCClipXOrigin     =      (1L<<17);
18301 	enum GCClipYOrigin    =       (1L<<18);
18302 	enum GCClipMask      =        (1L<<19);
18303 	enum GCDashOffset   =         (1L<<20);
18304 	enum GCDashList    =          (1L<<21);
18305 	enum GCArcMode    =           (1L<<22);
18306 	enum GCLastBit   =            22;
18307 
18308 
18309 	enum int WithdrawnState = 0;
18310 	enum int NormalState = 1;
18311 	enum int IconicState = 3;
18312 
18313 }
18314 } else version (OSXCocoa) {
18315 
18316 /+
18317 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
18318 +/
18319 
18320 	private __gshared AppDelegate globalAppDelegate;
18321 
18322 	extern(Objective-C)
18323 	class AppDelegate : NSObject, NSApplicationDelegate {
18324 		override static AppDelegate alloc() @selector("alloc");
18325 
18326 
18327 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
18328 			SimpleWindow.processAllCustomEvents();
18329 		}
18330 
18331 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
18332 			immutable style = NSWindowStyleMask.resizable |
18333 				NSWindowStyleMask.closable |
18334 				NSWindowStyleMask.miniaturizable |
18335 				NSWindowStyleMask.titled;
18336 
18337 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
18338 
18339 			{
18340 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
18341 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
18342 				mainMenu.setSubmenu(menu, item);
18343 
18344 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
18345 				newItem.target = NSApp;
18346 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
18347 				newItem2.target = NSApp;
18348 			}
18349 
18350 			{
18351 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
18352 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
18353 				mainMenu.setSubmenu(menu, item);
18354 
18355 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
18356 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
18357 			}
18358 
18359 
18360 			NSApp.menu = mainMenu;
18361 
18362 
18363 			// auto controller = ViewController.alloc.init;
18364 
18365 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
18366 
18367 			/+
18368 			this.window = window;
18369 			this.controller = controller;
18370 			+/
18371 		}
18372 
18373 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
18374 			NSApplication.shared_.activateIgnoringOtherApps(true);
18375 		}
18376 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
18377 			return true;
18378 		}
18379 	}
18380 
18381 	extern(Objective-C)
18382 	class SDWindowDelegate : NSObject, NSWindowDelegate {
18383 		override static SDWindowDelegate alloc() @selector("alloc");
18384 		override SDWindowDelegate init() @selector("init");
18385 
18386 		SimpleWindow simpleWindow;
18387 
18388 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
18389 			auto window = cast(void*) notification.object;
18390 
18391 			// FIXME: do i need to release it?
18392 			SimpleWindow.nativeMapping.remove(window);
18393 		}
18394 
18395 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
18396 			if(simpleWindow.windowResized) {
18397 				// FIXME: automaticallyScaleIfPossible behaviors
18398 
18399 				simpleWindow._width = cast(int) frameSize.width;
18400 				simpleWindow._height = cast(int) frameSize.height;
18401 
18402 				simpleWindow.view.setFrameSize(frameSize);
18403 
18404 				/+
18405 				auto size = simpleWindow.view.frame.size;
18406 				writeln(cast(int) size.width, "x", cast(int) size.height);
18407 				+/
18408 
18409 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
18410 
18411 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
18412 
18413 				// simpleWindow.view.setNeedsDisplay(true);
18414 			}
18415 
18416 			return frameSize;
18417 		}
18418 
18419 		/+
18420 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
18421 			if(simpleWindow.windowResized) {
18422 				auto window = simpleWindow.window;
18423 				auto rect = window.contentRectForFrameRect(window.frame);
18424 				import std.stdio; writeln(window.frame.size);
18425 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
18426 			}
18427 		}
18428 		+/
18429 	}
18430 
18431 	extern(Objective-C)
18432 	class SDGraphicsView : NSView {
18433 		SimpleWindow simpleWindow;
18434 
18435 		override static SDGraphicsView alloc() @selector("alloc");
18436 		override SDGraphicsView init() @selector("init") {
18437 			super.init();
18438 			return this;
18439 		}
18440 
18441 		override void drawRect(NSRect rect) @selector("drawRect:") {
18442 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
18443 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18444 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
18445 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18446 			CGImageRelease(cgImage);
18447 		}
18448 
18449 		extern(D)
18450 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
18451 			MouseEvent me;
18452 			me.type = type;
18453 
18454 			auto pos = event.locationInWindow;
18455 
18456 			me.x = cast(int) pos.x;
18457 			me.y = cast(int) (simpleWindow.height - pos.y);
18458 
18459 			me.dx = 0; // FIXME
18460 			me.dy = 0; // FIXME
18461 
18462 			me.button = button;
18463 			me.modifierState = cast(uint) event.modifierFlags;
18464 			me.window = simpleWindow;
18465 
18466 			me.doubleClick = false;
18467 
18468 			if(simpleWindow && simpleWindow.handleMouseEvent)
18469 				simpleWindow.handleMouseEvent(me);
18470 		}
18471 
18472 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
18473 			// writeln(event.pressedMouseButtons);
18474 
18475 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18476 		}
18477 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
18478 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
18479 		}
18480 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
18481 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
18482 		}
18483 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
18484 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
18485 		}
18486 		/+
18487 			// FIXME
18488 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
18489 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18490 		}
18491 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
18492 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18493 		}
18494 		+/
18495 
18496 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
18497 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
18498 		}
18499 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
18500 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
18501 		}
18502 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
18503 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
18504 		}
18505 
18506 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
18507 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
18508 		}
18509 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
18510 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
18511 		}
18512 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
18513 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
18514 		}
18515 
18516 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
18517 			// import std.stdio; writeln(event.deltaY);
18518 		}
18519 
18520 		override void keyDown(NSEvent event) @selector("keyDown:") {
18521 			// the event may have multiple characters, and we send them all at once.
18522 			if (simpleWindow.handleCharEvent) {
18523 				auto chars = DeifiedNSString(event.characters);
18524 				foreach (dchar dc; chars.str)
18525 					simpleWindow.handleCharEvent(dc);
18526 			}
18527 
18528 			keyHelper(event, true);
18529 		}
18530 
18531 		override void keyUp(NSEvent event) @selector("keyUp:") {
18532 			keyHelper(event, false);
18533 		}
18534 
18535 		extern(D)
18536 		private void keyHelper(NSEvent event, bool pressed) {
18537 			if(simpleWindow.handleKeyEvent) {
18538 				KeyEvent ev;
18539 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
18540 				ev.pressed = pressed;
18541 				ev.hardwareCode = cast(ubyte) event.keyCode;
18542 				ev.modifierState = cast(uint) event.modifierFlags;
18543 				ev.window = simpleWindow;
18544 
18545 				simpleWindow.handleKeyEvent(ev);
18546 			}
18547 		}
18548 
18549 		override bool isFlipped() @selector("isFlipped") {
18550 			return true;
18551 		}
18552 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
18553 			return true;
18554 		}
18555 
18556 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
18557 			if(simpleWindow && simpleWindow.handlePulse)
18558 				simpleWindow.handlePulse();
18559 			/+
18560 			setNeedsDisplay = true;
18561 			+/
18562 		}
18563 	}
18564 
18565 private:
18566 	alias const(void)* CFStringRef;
18567 	alias const(void)* CFAllocatorRef;
18568 	alias const(void)* CFTypeRef;
18569 	alias const(void)* CGColorSpaceRef;
18570 	alias const(void)* CGImageRef;
18571 	alias ulong CGBitmapInfo;
18572 	alias NSGraphicsContext CGContextRef;
18573 
18574 	alias NSPoint CGPoint;
18575 	alias NSSize CGSize;
18576 	alias NSRect CGRect;
18577 
18578 	struct CGAffineTransform {
18579 		double a, b, c, d, tx, ty;
18580 	}
18581 
18582 	enum NSApplicationActivationPolicyRegular = 0;
18583 	enum NSBackingStoreBuffered = 2;
18584 	enum kCFStringEncodingUTF8 = 0x08000100;
18585 
18586 	enum : size_t {
18587 		NSBorderlessWindowMask = 0,
18588 		NSTitledWindowMask = 1 << 0,
18589 		NSClosableWindowMask = 1 << 1,
18590 		NSMiniaturizableWindowMask = 1 << 2,
18591 		NSResizableWindowMask = 1 << 3,
18592 		NSTexturedBackgroundWindowMask = 1 << 8
18593 	}
18594 
18595 	enum : ulong {
18596 		kCGImageAlphaNone,
18597 		kCGImageAlphaPremultipliedLast,
18598 		kCGImageAlphaPremultipliedFirst,
18599 		kCGImageAlphaLast,
18600 		kCGImageAlphaFirst,
18601 		kCGImageAlphaNoneSkipLast,
18602 		kCGImageAlphaNoneSkipFirst
18603 	}
18604 	enum : ulong {
18605 		kCGBitmapAlphaInfoMask = 0x1F,
18606 		kCGBitmapFloatComponents = (1 << 8),
18607 		kCGBitmapByteOrderMask = 0x7000,
18608 		kCGBitmapByteOrderDefault = (0 << 12),
18609 		kCGBitmapByteOrder16Little = (1 << 12),
18610 		kCGBitmapByteOrder32Little = (2 << 12),
18611 		kCGBitmapByteOrder16Big = (3 << 12),
18612 		kCGBitmapByteOrder32Big = (4 << 12)
18613 	}
18614 	enum CGPathDrawingMode {
18615 		kCGPathFill,
18616 		kCGPathEOFill,
18617 		kCGPathStroke,
18618 		kCGPathFillStroke,
18619 		kCGPathEOFillStroke
18620 	}
18621 	enum objc_AssociationPolicy : size_t {
18622 		OBJC_ASSOCIATION_ASSIGN = 0,
18623 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18624 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18625 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18626 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18627 	}
18628 
18629 	extern(C) {
18630 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18631 		void CGContextRelease(CGContextRef c);
18632 		ubyte* CGBitmapContextGetData(CGContextRef c);
18633 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18634 		size_t CGBitmapContextGetWidth(CGContextRef c);
18635 		size_t CGBitmapContextGetHeight(CGContextRef c);
18636 
18637 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18638 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18639 
18640 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18641 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18642 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18643 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18644 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18645 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
18646 
18647 		void CGContextBeginPath(CGContextRef c);
18648 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18649 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18650 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18651 		void CGContextAddRect(CGContextRef c, CGRect rect);
18652 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18653 		void CGContextSaveGState(CGContextRef c);
18654 		void CGContextRestoreGState(CGContextRef c);
18655 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18656 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18657 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18658 
18659 		void CGImageRelease(CGImageRef image);
18660 	}
18661 } else static assert(0, "Unsupported operating system");
18662 
18663 
18664 version(OSXCocoa) {
18665 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18666 	//
18667 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18668 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18669 	//
18670 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18671 	// Probably won't even fully compile right now
18672 
18673 	private enum double PI = 3.14159265358979323;
18674 
18675 	alias NSWindow NativeWindowHandle;
18676 	alias void delegate(NSid) NativeEventHandler;
18677 
18678 	enum KEY_ESCAPE = 27;
18679 
18680 	mixin template NativeImageImplementation() {
18681 		CGContextRef context;
18682 		ubyte* rawData;
18683 
18684 		final:
18685 
18686 		void convertToRgbaBytes(ubyte[] where) @system {
18687 			assert(where.length == this.width * this.height * 4);
18688 
18689 			// if rawData had a length....
18690 			//assert(rawData.length == where.length);
18691 			for(long idx = 0; idx < where.length; idx += 4) {
18692 				auto alpha = rawData[idx + 3];
18693 				if(alpha == 255) {
18694 					where[idx + 0] = rawData[idx + 0]; // r
18695 					where[idx + 1] = rawData[idx + 1]; // g
18696 					where[idx + 2] = rawData[idx + 2]; // b
18697 					where[idx + 3] = rawData[idx + 3]; // a
18698 				} else {
18699 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18700 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18701 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18702 					where[idx + 3] = rawData[idx + 3]; // a
18703 
18704 				}
18705 			}
18706 		}
18707 
18708 		void setFromRgbaBytes(in ubyte[] where) @system {
18709 			// FIXME: this is probably wrong
18710 			assert(where.length == this.width * this.height * 4);
18711 
18712 			// if rawData had a length....
18713 			//assert(rawData.length == where.length);
18714 			for(long idx = 0; idx < where.length; idx += 4) {
18715 				auto alpha = where[idx + 3];
18716 				if(alpha == 255) {
18717 					rawData[idx + 0] = where[idx + 0]; // r
18718 					rawData[idx + 1] = where[idx + 1]; // g
18719 					rawData[idx + 2] = where[idx + 2]; // b
18720 					rawData[idx + 3] = where[idx + 3]; // a
18721 				} else if(alpha == 0) {
18722 					rawData[idx + 0] = 0;
18723 					rawData[idx + 1] = 0;
18724 					rawData[idx + 2] = 0;
18725 					rawData[idx + 3] = 0;
18726 				} else {
18727 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18728 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18729 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18730 					rawData[idx + 3] = where[idx + 3]; // a
18731 				}
18732 			}
18733 		}
18734 
18735 
18736 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18737 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18738 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18739 			CGColorSpaceRelease(colorSpace);
18740 			rawData = CGBitmapContextGetData(context);
18741 		}
18742 		void dispose() {
18743 			CGContextRelease(context);
18744 		}
18745 
18746 		void setPixel(int x, int y, Color c) @system {
18747 			auto offset = (y * width + x) * 4;
18748 			if (c.a == 255) {
18749 				rawData[offset + 0] = c.r;
18750 				rawData[offset + 1] = c.g;
18751 				rawData[offset + 2] = c.b;
18752 				rawData[offset + 3] = c.a;
18753 			} else {
18754 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18755 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18756 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18757 				rawData[offset + 3] = c.a;
18758 			}
18759 		}
18760 	}
18761 
18762 	mixin template NativeScreenPainterImplementation() {
18763 		CGContextRef context;
18764 		ubyte[4] _outlineComponents;
18765 		NSView view;
18766 
18767 		Pen _activePen;
18768 		Color _fillColor;
18769 		Rectangle _clipRectangle;
18770 		OperatingSystemFont _font;
18771 
18772 		OperatingSystemFont getFont() {
18773 			if(_font is null) {
18774 				static OperatingSystemFont _defaultFont;
18775 				if(_defaultFont is null) {
18776 					_defaultFont = new OperatingSystemFont();
18777 					_defaultFont.loadDefault();
18778 				}
18779 				_font = _defaultFont;
18780 			}
18781 
18782 			return _font;
18783 		}
18784 
18785 		void create(PaintingHandle window) {
18786 			// this.destiny = window;
18787 			if(auto sw = cast(SimpleWindow) this.window) {
18788 				context = sw.drawingContext;
18789 				view = sw.view;
18790 			} else {
18791 				throw new NotYetImplementedException();
18792 			}
18793 		}
18794 
18795 		void dispose() {
18796 			view.setNeedsDisplay(true);
18797 		}
18798 
18799 		bool manualInvalidations;
18800 		void invalidateRect(Rectangle invalidRect) { }
18801 
18802 		// NotYetImplementedException
18803 		void rasterOp(RasterOp op) {
18804 		}
18805 		void setClipRectangle(int, int, int, int) {
18806 		}
18807 		Size textSize(in char[] txt) {
18808 			auto font = getFont();
18809 			return Size(font.stringWidth(txt), font.height());
18810 		}
18811 
18812 		void setFont(OperatingSystemFont font) {
18813 			_font = font;
18814 			//font.font.setInContext(context);
18815 		}
18816 		int fontHeight() {
18817 			auto font = getFont();
18818 			return font.height;
18819 		}
18820 
18821 		// end
18822 
18823 		void pen(Pen pen) {
18824 			_activePen = pen;
18825 			auto color = pen.color; // FIXME
18826 			double alphaComponent = color.a/255.0f;
18827 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
18828 
18829 			double[2] patternBuffer;
18830 			double[] pattern;
18831 			final switch(pen.style) {
18832 				case Pen.Style.Solid:
18833 					pattern = null;
18834 				break;
18835 				case Pen.Style.Dashed:
18836 					patternBuffer[0] = 4;
18837 					patternBuffer[1] = 1;
18838 					pattern = patternBuffer[];
18839 				break;
18840 				case Pen.Style.Dotted:
18841 					patternBuffer[0] = 1;
18842 					patternBuffer[1] = 1;
18843 					pattern = patternBuffer[];
18844 				break;
18845 			}
18846 
18847 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
18848 
18849 			if (color.a != 255) {
18850 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
18851 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
18852 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18853 				_outlineComponents[3] = color.a;
18854 			} else {
18855 				_outlineComponents[0] = color.r;
18856 				_outlineComponents[1] = color.g;
18857 				_outlineComponents[2] = color.b;
18858 				_outlineComponents[3] = color.a;
18859 			}
18860 		}
18861 
18862 		@property void fillColor(Color color) {
18863 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18864 		}
18865 
18866 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18867 		// NotYetImplementedException for upper left/width/height
18868 			auto cgImage = CGBitmapContextCreateImage(image.context);
18869 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
18870 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18871 			CGImageRelease(cgImage);
18872 		}
18873 
18874 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
18875 		// FIXME: is this efficient?
18876 			auto cgImage = CGBitmapContextCreateImage(s.handle);
18877 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
18878 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18879 			CGImageRelease(cgImage);
18880 		}
18881 
18882 
18883 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18884 		// FIXME: alignment
18885 			if (_outlineComponents[3] != 0) {
18886 				CGContextSaveGState(context);
18887 				auto invAlpha = 1.0f/_outlineComponents[3];
18888 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18889 												  _outlineComponents[1]*invAlpha,
18890 												  _outlineComponents[2]*invAlpha,
18891 												  _outlineComponents[3]/255.0f);
18892 
18893 
18894 
18895 				// FIXME: should we clip it to the bounding box?
18896 				int textHeight = fontHeight;
18897 
18898 				auto lines = text.split('\n');
18899 
18900 				const lineHeight = textHeight;
18901 				textHeight *= lines.length;
18902 
18903 				int cy = y;
18904 
18905 				if(alignment & TextAlignment.VerticalBottom) {
18906 					if(y2 <= 0)
18907 						return;
18908 					auto h = y2 - y;
18909 					if(h > textHeight) {
18910 						cy += h - textHeight;
18911 						cy -= lineHeight / 2;
18912 					}
18913 				} else if(alignment & TextAlignment.VerticalCenter) {
18914 					if(y2 <= 0)
18915 						return;
18916 					auto h = y2 - y;
18917 					if(textHeight < h) {
18918 						cy += (h - textHeight) / 2;
18919 						//cy -= lineHeight / 4;
18920 					}
18921 				}
18922 
18923 				foreach(line; text.split('\n')) {
18924 					int textWidth = this.textSize(line).width;
18925 
18926 					int px = x, py = cy;
18927 
18928 					if(alignment & TextAlignment.Center) {
18929 						if(x2 <= 0)
18930 							return;
18931 						auto w = x2 - x;
18932 						if(w > textWidth)
18933 							px += (w - textWidth) / 2;
18934 					} else if(alignment & TextAlignment.Right) {
18935 						if(x2 <= 0)
18936 							return;
18937 						auto pos = x2 - textWidth;
18938 						if(pos > x)
18939 							px = pos;
18940 					}
18941 
18942 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
18943 
18944 					carry_on:
18945 					cy += lineHeight + 4;
18946 				}
18947 
18948 // auto cfstr = cast(NSid)createCFString(text);
18949 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18950 // NSPoint(x, y), null);
18951 // CFRelease(cfstr);
18952 				CGContextRestoreGState(context);
18953 			}
18954 		}
18955 
18956 		void drawPixel(int x, int y) {
18957 			auto rawData = CGBitmapContextGetData(context);
18958 			auto width = CGBitmapContextGetWidth(context);
18959 			auto height = CGBitmapContextGetHeight(context);
18960 			auto offset = ((height - y - 1) * width + x) * 4;
18961 			rawData[offset .. offset+4] = _outlineComponents;
18962 		}
18963 
18964 		void drawLine(int x1, int y1, int x2, int y2) {
18965 			CGPoint[2] linePoints;
18966 			linePoints[0] = CGPoint(x1, y1);
18967 			linePoints[1] = CGPoint(x2, y2);
18968 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18969 		}
18970 
18971 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
18972 			drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded
18973 		}
18974 
18975 		void drawRectangle(int x, int y, int width, int height) {
18976 			CGContextBeginPath(context);
18977 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18978 			CGContextAddRect(context, rect);
18979 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18980 		}
18981 
18982 		void drawEllipse(int x1, int y1, int x2, int y2) {
18983 			CGContextBeginPath(context);
18984 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18985 			CGContextAddEllipseInRect(context, rect);
18986 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18987 		}
18988 
18989 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
18990 			// @@@BUG@@@ Does not support elliptic arc (width != height).
18991 			CGContextBeginPath(context);
18992 			int clockwise = 0;
18993 			if(length < 0) {
18994 				clockwise = 1;
18995 				length = -length;
18996 			}
18997 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18998 							start*PI/(180*64), (start+length)*PI/(180*64), clockwise);
18999 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19000 		}
19001 
19002 		void drawPolygon(Point[] intPoints) {
19003 			CGContextBeginPath(context);
19004 			CGPoint[16] pointsBuffer;
19005 			CGPoint[] points;
19006 			if(intPoints.length <= pointsBuffer.length)
19007 				points = pointsBuffer[0 .. intPoints.length];
19008 			else
19009 				points = new CGPoint[](intPoints.length);
19010 
19011 			foreach(idx, pt; intPoints)
19012 				points[idx] = CGPoint(pt.x, pt.y);
19013 
19014 			CGContextAddLines(context, points.ptr, points.length);
19015 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19016 		}
19017 	}
19018 
19019 	private bool appInitialized = false;
19020 	void initializeApp() {
19021 		if(appInitialized)
19022 			return;
19023 		synchronized {
19024 			if(appInitialized)
19025 				return;
19026 
19027 			auto app = NSApp(); // ensure the is initialized
19028 
19029 			auto dg = AppDelegate.alloc;
19030 			globalAppDelegate = dg;
19031 			NSApp.delegate_ = dg;
19032 
19033 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
19034 
19035 			appInitialized = true;
19036 		}
19037 	}
19038 
19039 	mixin template NativeSimpleWindowImplementation() {
19040 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
19041 			initializeApp();
19042 
19043 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
19044 
19045 			auto window = NSWindow.alloc.initWithContentRect(
19046 				contentRect,
19047 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
19048 				NSBackingStoreType.buffered,
19049 				true
19050 			);
19051 
19052 			SimpleWindow.nativeMapping[cast(void*) window] = this;
19053 
19054 			window.title = MacString(title).borrow;
19055 
19056 			auto dg = SDWindowDelegate.alloc.init;
19057 			dg.simpleWindow = this;
19058 			window.delegate_ = dg;
19059 
19060 			auto view = SDGraphicsView.alloc.init;
19061 			assert(view !is null);
19062 			window.contentView = view;
19063 			this.view = view;
19064 			view.simpleWindow = this;
19065 
19066 			window.center();
19067 
19068 			window.makeKeyAndOrderFront(null);
19069 
19070 			// no need to make a bitmap on mac since everything is double buffered already
19071 
19072 			// create area to draw on.
19073 			createNewDrawingContext(width, height);
19074 
19075 			window.setBackgroundColor(NSColor.whiteColor);
19076 		}
19077 
19078 		void createNewDrawingContext(int width, int height) {
19079 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
19080 			if(this.drawingContext)
19081 				CGContextRelease(this.drawingContext);
19082 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
19083 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
19084 			CGColorSpaceRelease(colorSpace);
19085 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
19086 			auto matrix = CGContextGetTextMatrix(drawingContext);
19087 			matrix.c = -matrix.c;
19088 			matrix.d = -matrix.d;
19089 			CGContextSetTextMatrix(drawingContext, matrix);
19090 
19091 		}
19092 
19093 		void dispose() {
19094 			closeWindow();
19095 			// window.release(); // closing the window does this automatically i think
19096 		}
19097 		void closeWindow() {
19098 			if(timer)
19099 				timer.invalidate();
19100 			window.close();
19101 		}
19102 
19103 		ScreenPainter getPainter(bool manualInvalidations) {
19104 			return ScreenPainter(this, this.window, manualInvalidations);
19105 		}
19106 
19107 		NSWindow window;
19108 		NSTimer timer;
19109 		NSView view;
19110 		CGContextRef drawingContext;
19111 	}
19112 }
19113 
19114 version(without_opengl) {} else
19115 extern(System) nothrow @nogc {
19116 	//enum uint GL_VERSION = 0x1F02;
19117 	//const(char)* glGetString (/*GLenum*/uint);
19118 	version(X11) {
19119 	static if (!SdpyIsUsingIVGLBinds) {
19120 
19121 		enum GLX_X_RENDERABLE = 0x8012;
19122 		enum GLX_DRAWABLE_TYPE = 0x8010;
19123 		enum GLX_RENDER_TYPE = 0x8011;
19124 		enum GLX_X_VISUAL_TYPE = 0x22;
19125 		enum GLX_TRUE_COLOR = 0x8002;
19126 		enum GLX_WINDOW_BIT = 0x00000001;
19127 		enum GLX_RGBA_BIT = 0x00000001;
19128 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
19129 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
19130 		enum GLX_SAMPLES = 0x186a1;
19131 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19132 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19133 	}
19134 
19135 		// GLX_EXT_swap_control
19136 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
19137 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
19138 
19139 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
19140 		extern(System) {
19141 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
19142 		}
19143 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
19144 
19145 		// this made public so we don't have to get it again and again
19146 		public bool glXCreateContextAttribsARB_present () @system {
19147 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
19148 				// get it
19149 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
19150 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
19151 			}
19152 			return (glXCreateContextAttribsARBFn !is null);
19153 		}
19154 
19155 		// this made public so we don't have to get it again and again
19156 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system {
19157 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
19158 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
19159 		}
19160 
19161 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
19162 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
19163 
19164 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
19165 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
19166 			if (_glx_swapInterval_fn is null) {
19167 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
19168 				if (_glx_swapInterval_fn is null) {
19169 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
19170 					return;
19171 				}
19172 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
19173 			}
19174 
19175 			if(glXSwapIntervalMESA is null) {
19176 				// it seems to require both to actually take effect on many computers
19177 				// idk why
19178 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
19179 				if(glXSwapIntervalMESA is null)
19180 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
19181 			}
19182 
19183 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
19184 				glXSwapIntervalMESA(wait ? 1 : 0);
19185 
19186 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
19187 		}
19188 	} else version(Windows) {
19189 	static if (!SdpyIsUsingIVGLBinds) {
19190 	enum GL_TRUE = 1;
19191 	enum GL_FALSE = 0;
19192 
19193 	public void* glbindGetProcAddress (const(char)* name) {
19194 		void* res = wglGetProcAddress(name);
19195 		if (res is null) {
19196 			/+
19197 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
19198 			import core.sys.windows.windef, core.sys.windows.winbase;
19199 			__gshared HINSTANCE dll = null;
19200 			if (dll is null) {
19201 				dll = LoadLibraryA("opengl32.dll");
19202 				if (dll is null) return null; // <32, but idc
19203 			}
19204 			res = GetProcAddress(dll, name);
19205 			+/
19206 			res = GetProcAddress(gl.libHandle, name);
19207 		}
19208 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
19209 		return res;
19210 	}
19211 	}
19212 
19213 
19214  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
19215 	void wglSetVSync(bool wait) {
19216 		if(wglSwapIntervalEXT is null) {
19217 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
19218 			if(wglSwapIntervalEXT is null)
19219 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
19220 		}
19221 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
19222 			return;
19223 
19224 		wglSwapIntervalEXT(wait ? 1 : 0);
19225 	}
19226 
19227 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19228 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19229 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
19230 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
19231 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
19232 
19233 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
19234 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
19235 
19236 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
19237 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
19238 
19239 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
19240 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
19241 
19242 		void wglInitOtherFunctions () {
19243 			if (wglCreateContextAttribsARB is null) {
19244 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
19245 			}
19246 		}
19247 	}
19248 
19249 	static if (!SdpyIsUsingIVGLBinds) {
19250 
19251 	interface GL {
19252 		extern(System) @nogc nothrow:
19253 
19254 		void glGetIntegerv(int, void*);
19255 		void glMatrixMode(int);
19256 		void glPushMatrix();
19257 		void glLoadIdentity();
19258 		void glOrtho(double, double, double, double, double, double);
19259 		void glFrustum(double, double, double, double, double, double);
19260 
19261 		void glPopMatrix();
19262 		void glEnable(int);
19263 		void glDisable(int);
19264 		void glClear(int);
19265 		void glBegin(int);
19266 		void glVertex2f(float, float);
19267 		void glVertex3f(float, float, float);
19268 		void glEnd();
19269 		void glColor3b(byte, byte, byte);
19270 		void glColor3ub(ubyte, ubyte, ubyte);
19271 		void glColor4b(byte, byte, byte, byte);
19272 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
19273 		void glColor3i(int, int, int);
19274 		void glColor3ui(uint, uint, uint);
19275 		void glColor4i(int, int, int, int);
19276 		void glColor4ui(uint, uint, uint, uint);
19277 		void glColor3f(float, float, float);
19278 		void glColor4f(float, float, float, float);
19279 		void glTranslatef(float, float, float);
19280 		void glScalef(float, float, float);
19281 		version(X11) {
19282 			void glSecondaryColor3b(byte, byte, byte);
19283 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
19284 			void glSecondaryColor3i(int, int, int);
19285 			void glSecondaryColor3ui(uint, uint, uint);
19286 			void glSecondaryColor3f(float, float, float);
19287 		}
19288 
19289 		void glDrawElements(int, int, int, void*);
19290 
19291 		void glRotatef(float, float, float, float);
19292 
19293 		uint glGetError();
19294 
19295 		void glDeleteTextures(int, uint*);
19296 
19297 
19298 		void glRasterPos2i(int, int);
19299 		void glDrawPixels(int, int, uint, uint, void*);
19300 		void glClearColor(float, float, float, float);
19301 
19302 
19303 		void glPixelStorei(uint, int);
19304 
19305 		void glGenTextures(uint, uint*);
19306 		void glBindTexture(int, int);
19307 		void glTexParameteri(uint, uint, int);
19308 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19309 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
19310 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
19311 			/*GLsizei*/int width, /*GLsizei*/int height,
19312 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19313 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19314 
19315 		void glLineWidth(int);
19316 
19317 
19318 		void glTexCoord2f(float, float);
19319 		void glVertex2i(int, int);
19320 		void glBlendFunc (int, int);
19321 		void glDepthFunc (int);
19322 		void glViewport(int, int, int, int);
19323 
19324 		void glClearDepth(double);
19325 
19326 		void glReadBuffer(uint);
19327 		void glReadPixels(int, int, int, int, int, int, void*);
19328 
19329 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
19330 
19331 		void glFlush();
19332 		void glFinish();
19333 
19334 		version(Windows) {
19335 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
19336 			HGLRC wglCreateContext(HDC);
19337 			HGLRC wglCreateLayerContext(HDC, int);
19338 			BOOL wglDeleteContext(HGLRC);
19339 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
19340 			HGLRC wglGetCurrentContext();
19341 			HDC wglGetCurrentDC();
19342 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
19343 			PROC wglGetProcAddress(LPCSTR);
19344 			BOOL wglMakeCurrent(HDC, HGLRC);
19345 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
19346 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
19347 			BOOL wglShareLists(HGLRC, HGLRC);
19348 			BOOL wglSwapLayerBuffers(HDC, UINT);
19349 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
19350 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
19351 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19352 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19353 		}
19354 
19355 	}
19356 
19357 	interface GL3 {
19358 		extern(System) @nogc nothrow:
19359 
19360 		void glGenVertexArrays(GLsizei, GLuint*);
19361 		void glBindVertexArray(GLuint);
19362 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
19363 		void glGenerateMipmap(GLenum);
19364 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
19365 		void glStencilMask(GLuint);
19366 		void glStencilFunc(GLenum, GLint, GLuint);
19367 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19368 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19369 		GLuint glCreateProgram();
19370 		GLuint glCreateShader(GLenum);
19371 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
19372 		void glCompileShader(GLuint);
19373 		void glGetShaderiv(GLuint, GLenum, GLint*);
19374 		void glAttachShader(GLuint, GLuint);
19375 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
19376 		void glLinkProgram(GLuint);
19377 		void glGetProgramiv(GLuint, GLenum, GLint*);
19378 		void glDeleteProgram(GLuint);
19379 		void glDeleteShader(GLuint);
19380 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
19381 		void glGenBuffers(GLsizei, GLuint*);
19382 
19383 		void glUniform1f(GLint location, GLfloat v0);
19384 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
19385 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
19386 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
19387 		void glUniform1i(GLint location, GLint v0);
19388 		void glUniform2i(GLint location, GLint v0, GLint v1);
19389 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
19390 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
19391 		void glUniform1ui(GLint location, GLuint v0);
19392 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
19393 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
19394 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
19395 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
19396 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
19397 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
19398 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
19399 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
19400 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
19401 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
19402 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
19403 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
19404 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
19405 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
19406 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
19407 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19408 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19409 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19410 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19411 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19412 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19413 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19414 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19415 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19416 
19417 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
19418 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
19419 		void glDrawArrays(GLenum, GLint, GLsizei);
19420 		void glStencilOp(GLenum, GLenum, GLenum);
19421 		void glUseProgram(GLuint);
19422 		void glCullFace(GLenum);
19423 		void glFrontFace(GLenum);
19424 		void glActiveTexture(GLenum);
19425 		void glBindBuffer(GLenum, GLuint);
19426 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
19427 		void glEnableVertexAttribArray(GLuint);
19428 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
19429 		void glUniform1i(GLint, GLint);
19430 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
19431 		void glDisableVertexAttribArray(GLuint);
19432 		void glDeleteBuffers(GLsizei, const(GLuint)*);
19433 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
19434 		void glLogicOp (GLenum opcode);
19435 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
19436 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
19437 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
19438 		GLenum glCheckFramebufferStatus (GLenum target);
19439 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
19440 	}
19441 
19442 	interface GL4 {
19443 		extern(System) @nogc nothrow:
19444 
19445 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
19446 			/*GLsizei*/int width, /*GLsizei*/int height,
19447 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19448 	}
19449 
19450 	interface GLU {
19451 		extern(System) @nogc nothrow:
19452 
19453 		void gluLookAt(double, double, double, double, double, double, double, double, double);
19454 		void gluPerspective(double, double, double, double);
19455 
19456 		char* gluErrorString(uint);
19457 	}
19458 
19459 
19460 	enum GL_RED = 0x1903;
19461 	enum GL_ALPHA = 0x1906;
19462 
19463 	enum uint GL_FRONT = 0x0404;
19464 
19465 	enum uint GL_BLEND = 0x0be2;
19466 	enum uint GL_LEQUAL = 0x0203;
19467 
19468 
19469 	enum uint GL_RGB = 0x1907;
19470 	enum uint GL_BGRA = 0x80e1;
19471 	enum uint GL_RGBA = 0x1908;
19472 	enum uint GL_RGBA8 = 0x8058;
19473 	enum uint GL_TEXTURE_2D =   0x0DE1;
19474 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
19475 	enum uint GL_NEAREST = 0x2600;
19476 	enum uint GL_LINEAR = 0x2601;
19477 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
19478 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
19479 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
19480 	enum uint GL_REPEAT = 0x2901;
19481 	enum uint GL_CLAMP = 0x2900;
19482 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
19483 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
19484 	enum uint GL_DECAL = 0x2101;
19485 	enum uint GL_MODULATE = 0x2100;
19486 	enum uint GL_TEXTURE_ENV = 0x2300;
19487 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
19488 	enum uint GL_REPLACE = 0x1E01;
19489 	enum uint GL_LIGHTING = 0x0B50;
19490 	enum uint GL_DITHER = 0x0BD0;
19491 
19492 	enum uint GL_NO_ERROR = 0;
19493 
19494 
19495 
19496 	enum int GL_VIEWPORT = 0x0BA2;
19497 	enum int GL_MODELVIEW = 0x1700;
19498 	enum int GL_TEXTURE = 0x1702;
19499 	enum int GL_PROJECTION = 0x1701;
19500 	enum int GL_DEPTH_TEST = 0x0B71;
19501 
19502 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
19503 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
19504 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
19505 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
19506 
19507 	enum int GL_POINTS = 0x0000;
19508 	enum int GL_LINES =  0x0001;
19509 	enum int GL_LINE_LOOP = 0x0002;
19510 	enum int GL_LINE_STRIP = 0x0003;
19511 	enum int GL_TRIANGLES = 0x0004;
19512 	enum int GL_TRIANGLE_STRIP = 5;
19513 	enum int GL_TRIANGLE_FAN = 6;
19514 	enum int GL_QUADS = 7;
19515 	enum int GL_QUAD_STRIP = 8;
19516 	enum int GL_POLYGON = 9;
19517 
19518 	alias GLvoid = void;
19519 	alias GLboolean = ubyte;
19520 	alias GLint = int;
19521 	alias GLuint = uint;
19522 	alias GLenum = uint;
19523 	alias GLchar = char;
19524 	alias GLsizei = int;
19525 	alias GLfloat = float;
19526 	alias GLintptr = size_t;
19527 	alias GLsizeiptr = ptrdiff_t;
19528 
19529 
19530 	enum uint GL_INVALID_ENUM = 0x0500;
19531 
19532 	enum uint GL_ZERO = 0;
19533 	enum uint GL_ONE = 1;
19534 
19535 	enum uint GL_BYTE = 0x1400;
19536 	enum uint GL_UNSIGNED_BYTE = 0x1401;
19537 	enum uint GL_SHORT = 0x1402;
19538 	enum uint GL_UNSIGNED_SHORT = 0x1403;
19539 	enum uint GL_INT = 0x1404;
19540 	enum uint GL_UNSIGNED_INT = 0x1405;
19541 	enum uint GL_FLOAT = 0x1406;
19542 	enum uint GL_2_BYTES = 0x1407;
19543 	enum uint GL_3_BYTES = 0x1408;
19544 	enum uint GL_4_BYTES = 0x1409;
19545 	enum uint GL_DOUBLE = 0x140A;
19546 
19547 	enum uint GL_STREAM_DRAW = 0x88E0;
19548 
19549 	enum uint GL_CCW = 0x0901;
19550 
19551 	enum uint GL_STENCIL_TEST = 0x0B90;
19552 	enum uint GL_SCISSOR_TEST = 0x0C11;
19553 
19554 	enum uint GL_EQUAL = 0x0202;
19555 	enum uint GL_NOTEQUAL = 0x0205;
19556 
19557 	enum uint GL_ALWAYS = 0x0207;
19558 	enum uint GL_KEEP = 0x1E00;
19559 
19560 	enum uint GL_INCR = 0x1E02;
19561 
19562 	enum uint GL_INCR_WRAP = 0x8507;
19563 	enum uint GL_DECR_WRAP = 0x8508;
19564 
19565 	enum uint GL_CULL_FACE = 0x0B44;
19566 	enum uint GL_BACK = 0x0405;
19567 
19568 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
19569 	enum uint GL_VERTEX_SHADER = 0x8B31;
19570 
19571 	enum uint GL_COMPILE_STATUS = 0x8B81;
19572 	enum uint GL_LINK_STATUS = 0x8B82;
19573 
19574 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
19575 
19576 	enum uint GL_STATIC_DRAW = 0x88E4;
19577 
19578 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
19579 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
19580 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
19581 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
19582 
19583 	enum uint GL_GENERATE_MIPMAP = 0x8191;
19584 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
19585 
19586 	enum uint GL_TEXTURE0 = 0x84C0U;
19587 	enum uint GL_TEXTURE1 = 0x84C1U;
19588 
19589 	enum uint GL_ARRAY_BUFFER = 0x8892;
19590 
19591 	enum uint GL_SRC_COLOR = 0x0300;
19592 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
19593 	enum uint GL_SRC_ALPHA = 0x0302;
19594 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
19595 	enum uint GL_DST_ALPHA = 0x0304;
19596 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
19597 	enum uint GL_DST_COLOR = 0x0306;
19598 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
19599 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
19600 
19601 	enum uint GL_INVERT = 0x150AU;
19602 
19603 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
19604 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
19605 
19606 	enum uint GL_FRAMEBUFFER = 0x8D40U;
19607 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
19608 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
19609 
19610 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
19611 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
19612 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
19613 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
19614 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
19615 
19616 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
19617 	enum uint GL_CLEAR = 0x1500U;
19618 	enum uint GL_COPY = 0x1503U;
19619 	enum uint GL_XOR = 0x1506U;
19620 
19621 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
19622 
19623 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
19624 
19625 	}
19626 }
19627 
19628 /++
19629 	History:
19630 		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.
19631 +/
19632 __gshared bool gluSuccessfullyLoaded = true;
19633 
19634 version(without_opengl) {} else {
19635 static if(!SdpyIsUsingIVGLBinds) {
19636 	version(Windows) {
19637 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
19638 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
19639 	} else {
19640 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
19641 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
19642 	}
19643 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
19644 
19645 
19646 	shared static this() {
19647 		gl.loadDynamicLibrary();
19648 
19649 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
19650 		// unless those functions are actually used
19651 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
19652 		glu.loadDynamicLibrary();
19653 	}
19654 }
19655 }
19656 
19657 /++
19658 	Convenience method for converting D arrays to opengl buffer data
19659 
19660 	I would LOVE to overload it with the original glBufferData, but D won't
19661 	let me since glBufferData is a function pointer :(
19662 
19663 	Added: August 25, 2020 (version 8.5)
19664 +/
19665 version(without_opengl) {} else
19666 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
19667 	glBufferData(target, data.length, data.ptr, usage);
19668 }
19669 
19670 /++
19671 	History:
19672 		Added September 1, 2024
19673 +/
19674 version(without_opengl) {} else
19675 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) {
19676 	glBufferSubData(target, offset, data.length, data.ptr);
19677 }
19678 
19679 /++
19680 	Convenience class for using opengl shaders.
19681 
19682 	Ensure that you've loaded opengl 3+ and set your active
19683 	context before trying to use this.
19684 
19685 	Added: August 25, 2020 (version 8.5)
19686 +/
19687 version(without_opengl) {} else
19688 final class OpenGlShader {
19689 	private int shaderProgram_;
19690 	private @property void shaderProgram(int a) {
19691 		shaderProgram_ = a;
19692 	}
19693 	/// Get the program ID for use in OpenGL functions.
19694 	public @property int shaderProgram() {
19695 		return shaderProgram_;
19696 	}
19697 
19698 	/++
19699 
19700 	+/
19701 	static struct Source {
19702 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19703 		string code; ///
19704 	}
19705 
19706 	/++
19707 		Helper method to just compile some shader code and check for errors
19708 		while you do glCreateShader, etc. on the outside yourself.
19709 
19710 		This just does `glShaderSource` and `glCompileShader` for the given code.
19711 
19712 		If you the OpenGlShader class constructor, you never need to call this yourself.
19713 	+/
19714 	static void compile(int sid, Source code) {
19715 		const(char)*[1] buffer;
19716 		int[1] lengthBuffer;
19717 
19718 		buffer[0] = code.code.ptr;
19719 		lengthBuffer[0] = cast(int) code.code.length;
19720 
19721 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19722 		glCompileShader(sid);
19723 
19724 		int success;
19725 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19726 		if(!success) {
19727 			char[512] info;
19728 			int len;
19729 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19730 
19731 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19732 		}
19733 	}
19734 
19735 	/++
19736 		Calls `glLinkProgram` and throws if error a occurs.
19737 
19738 		If you the OpenGlShader class constructor, you never need to call this yourself.
19739 	+/
19740 	static void link(int shaderProgram) {
19741 		glLinkProgram(shaderProgram);
19742 		int success;
19743 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19744 		if(!success) {
19745 			char[512] info;
19746 			int len;
19747 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19748 
19749 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19750 		}
19751 	}
19752 
19753 	/++
19754 		Constructs the shader object by calling `glCreateProgram`, then
19755 		compiling each given [Source], and finally, linking them together.
19756 
19757 		Throws: on compile or link failure.
19758 	+/
19759 	this(Source[] codes...) {
19760 		shaderProgram = glCreateProgram();
19761 
19762 		int[16] shadersBufferStack;
19763 
19764 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19765 			shadersBufferStack[0 .. codes.length] :
19766 			new int[](codes.length);
19767 
19768 		foreach(idx, code; codes) {
19769 			shadersBuffer[idx] = glCreateShader(code.type);
19770 
19771 			compile(shadersBuffer[idx], code);
19772 
19773 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19774 		}
19775 
19776 		link(shaderProgram);
19777 
19778 		foreach(s; shadersBuffer)
19779 			glDeleteShader(s);
19780 	}
19781 
19782 	/// Calls `glUseProgram(this.shaderProgram)`
19783 	void use() {
19784 		glUseProgram(this.shaderProgram);
19785 	}
19786 
19787 	/// Deletes the program.
19788 	void delete_() {
19789 		glDeleteProgram(shaderProgram);
19790 		shaderProgram = 0;
19791 	}
19792 
19793 	/++
19794 		[OpenGlShader.uniforms].name gives you one of these.
19795 
19796 		You can get the id out of it or just assign
19797 	+/
19798 	static struct Uniform {
19799 		/// the id passed to glUniform*
19800 		int id;
19801 
19802 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19803 		void opAssign(float x, float y, float z, float w) {
19804 			if(id != -1)
19805 			glUniform4f(id, x, y, z, w);
19806 		}
19807 
19808 		void opAssign(float x) {
19809 			if(id != -1)
19810 			glUniform1f(id, x);
19811 		}
19812 
19813 		void opAssign(float x, float y) {
19814 			if(id != -1)
19815 			glUniform2f(id, x, y);
19816 		}
19817 
19818 		void opAssign(T)(T t) {
19819 			t.glUniform(id);
19820 		}
19821 	}
19822 
19823 	static struct UniformsHelper {
19824 		OpenGlShader _shader;
19825 
19826 		@property Uniform opDispatch(string name)() {
19827 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19828 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19829 			//if(i == -1)
19830 				//throw new Exception("Could not find uniform " ~ name);
19831 			return Uniform(i);
19832 		}
19833 
19834 		@property void opDispatch(string name, T)(T t) {
19835 			Uniform f = this.opDispatch!name;
19836 			t.glUniform(f);
19837 		}
19838 	}
19839 
19840 	/++
19841 		Gives access to the uniforms through dot access.
19842 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19843 	+/
19844 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19845 }
19846 
19847 version(without_opengl) {} else {
19848 /++
19849 	A static container of experimental types and value constructors for opengl 3+ shaders.
19850 
19851 
19852 	You can declare variables like:
19853 
19854 	```
19855 	OGL.vec3f something;
19856 	```
19857 
19858 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19859 
19860 	```
19861 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19862 	```
19863 
19864 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19865 
19866 
19867 	History:
19868 		Added December 7, 2021. Not yet stable.
19869 +/
19870 final class OGL {
19871 	static:
19872 
19873 	private template typeFromSpecifier(string specifier) {
19874 		static if(specifier == "f")
19875 			alias typeFromSpecifier = GLfloat;
19876 		else static if(specifier == "i")
19877 			alias typeFromSpecifier = GLint;
19878 		else static if(specifier == "ui")
19879 			alias typeFromSpecifier = GLuint;
19880 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19881 	}
19882 
19883 	private template CommonType(T...) {
19884 		static if(T.length == 1)
19885 			alias CommonType = T[0];
19886 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19887 			alias CommonType = CommonType!(C, T[2 .. $]);
19888 	}
19889 
19890 	private template typesToSpecifier(T...) {
19891 		static if(is(CommonType!T == float))
19892 			enum typesToSpecifier = "f";
19893 		else static if(is(CommonType!T == int))
19894 			enum typesToSpecifier = "i";
19895 		else static if(is(CommonType!T == uint))
19896 			enum typesToSpecifier = "ui";
19897 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19898 	}
19899 
19900 	private template genNames(size_t dim, size_t dim2 = 0) {
19901 		string helper() {
19902 			string s;
19903 			if(dim2) {
19904 				static if(__VERSION__ < 2102)
19905 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug
19906 				else
19907 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;";
19908 			} else {
19909 				if(dim > 0) s ~= "type x = 0;";
19910 				if(dim > 1) s ~= "type y = 0;";
19911 				if(dim > 2) s ~= "type z = 0;";
19912 				if(dim > 3) s ~= "type w = 0;";
19913 			}
19914 
19915 			s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }";
19916 			if(dim2)
19917 				s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }";
19918 
19919 			return s;
19920 		}
19921 
19922 		enum genNames = helper();
19923 	}
19924 
19925 	// there's vec, arrays of vec, mat, and arrays of mat
19926 	template opDispatch(string name)
19927 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19928 	{
19929 		static if(name[4] == 'x') {
19930 			enum dimX = cast(int) (name[3] - '0');
19931 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19932 
19933 			enum dimY = cast(int) (name[5] - '0');
19934 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19935 
19936 			enum isArray = name[$ - 1] == 'v';
19937 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19938 			alias type = typeFromSpecifier!typeSpecifier;
19939 		} else {
19940 			enum dim = cast(int) (name[3] - '0');
19941 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19942 			enum isArray = name[$ - 1] == 'v';
19943 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19944 			alias type = typeFromSpecifier!typeSpecifier;
19945 		}
19946 
19947 		align(1)
19948 		struct opDispatch {
19949 			align(1):
19950 			static if(name[4] == 'x')
19951 				mixin(genNames!(dimX, dimY));
19952 			else
19953 				mixin(genNames!dim);
19954 
19955 			private void glUniform(OpenGlShader.Uniform assignTo) {
19956 				glUniform(assignTo.id);
19957 			}
19958 			private void glUniform(int assignTo) {
19959 				static if(name[4] == 'x') {
19960 					static if(name[3] == name[5]) {
19961 						// import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY);
19962 						mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]);
19963 					} else {
19964 						mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr);
19965 					}
19966 				} else
19967 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19968 			}
19969 		}
19970 	}
19971 
19972 	auto vec(T...)(T members) {
19973 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19974 	}
19975 }
19976 
19977 void checkGlError() {
19978 	auto error = glGetError();
19979 	int[] errors;
19980 	string[] errorStrings;
19981 	while(error != GL_NO_ERROR) {
19982 		errors ~= error;
19983 		switch(error) {
19984 			case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break;
19985 			case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break;
19986 			case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break;
19987 			case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break;
19988 			case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break;
19989 			case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break;
19990 			default: errorStrings ~= "idk";
19991 		}
19992 		error = glGetError();
19993 	}
19994 	if(errors.length)
19995 		throw ArsdException!"glGetError"(errors, errorStrings);
19996 }
19997 
19998 /++
19999 	A matrix for simple uses that easily integrates with [OpenGlShader].
20000 
20001 	Might not be useful to you since it only as some simple functions and
20002 	probably isn't that fast.
20003 
20004 	Note it uses an inline static array for its storage, so copying it
20005 	may be expensive.
20006 +/
20007 struct BasicMatrix(int columns, int rows, T = float) {
20008 	static import core.stdc.math;
20009 	static if(is(T == float)) {
20010 		alias cos = core.stdc.math.cosf;
20011 		alias sin = core.stdc.math.sinf;
20012 	} else {
20013 		alias cos = core.stdc.math.cos;
20014 		alias sin = core.stdc.math.sin;
20015 	}
20016 
20017 	T[columns * rows] data = 0.0;
20018 
20019 	/++
20020 
20021 	+/
20022 	this(T[columns * rows] data) {
20023 		this.data = data;
20024 	}
20025 
20026 	/++
20027 		Basic operations that operate *in place*.
20028 	+/
20029 	static if(columns == 4 && rows == 4)
20030 	void translate(T x, T y, T z) {
20031 		BasicMatrix m = [
20032 			1, 0, 0, x,
20033 			0, 1, 0, y,
20034 			0, 0, 1, z,
20035 			0, 0, 0, 1
20036 		];
20037 
20038 		this *= m;
20039 	}
20040 
20041 	/// ditto
20042 	static if(columns == 4 && rows == 4)
20043 	void scale(T x, T y, T z) {
20044 		BasicMatrix m = [
20045 			x, 0, 0, 0,
20046 			0, y, 0, 0,
20047 			0, 0, z, 0,
20048 			0, 0, 0, 1
20049 		];
20050 
20051 		this *= m;
20052 	}
20053 
20054 	/// ditto
20055 	static if(columns == 4 && rows == 4)
20056 	void rotateX(T theta) {
20057 		BasicMatrix m = [
20058 			1,          0,           0, 0,
20059 			0, cos(theta), -sin(theta), 0,
20060 			0, sin(theta),  cos(theta), 0,
20061 			0,          0,           0, 1
20062 		];
20063 
20064 		this *= m;
20065 	}
20066 
20067 	/// ditto
20068 	static if(columns == 4 && rows == 4)
20069 	void rotateY(T theta) {
20070 		BasicMatrix m = [
20071 			 cos(theta), 0,  sin(theta), 0,
20072 			          0, 1,           0, 0,
20073 			-sin(theta), 0,  cos(theta), 0,
20074 			          0, 0,           0, 1
20075 		];
20076 
20077 		this *= m;
20078 	}
20079 
20080 	/// ditto
20081 	static if(columns == 4 && rows == 4)
20082 	void rotateZ(T theta) {
20083 		BasicMatrix m = [
20084 			cos(theta), -sin(theta), 0, 0,
20085 			sin(theta),  cos(theta), 0, 0,
20086 			         0,           0, 1, 0,
20087 				 0,           0, 0, 1
20088 		];
20089 
20090 		this *= m;
20091 	}
20092 
20093 	/++
20094 
20095 	+/
20096 	static if(columns == rows)
20097 	static BasicMatrix identity() {
20098 		BasicMatrix m;
20099 		foreach(i; 0 .. columns)
20100 			m.data[0 + i + i * columns] = 1.0;
20101 		return m;
20102 	}
20103 
20104 	static if(columns == rows)
20105 	void loadIdentity() {
20106 		this = identity();
20107 	}
20108 
20109 	static if(columns == 4 && rows == 4)
20110 	static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) {
20111 		return BasicMatrix([
20112 			2/(r-l),       0,        0, -(r+l)/(r-l),
20113 			      0, 2/(t-b),        0, -(t+b)/(t-b),
20114 			      0,       0, -2/(f-n), -(f+n)/(f-n),
20115 			      0,       0,        0,            1
20116 		]);
20117 	}
20118 
20119 	static if(columns == 4 && rows == 4)
20120 	void loadOrtho(T l, T r, T b, T t, T n, T f) {
20121 		this = ortho(l, r, b, t, n, f);
20122 	}
20123 
20124 	void opOpAssign(string op : "+")(const BasicMatrix rhs) {
20125 		this.data[] += rhs.data;
20126 	}
20127 	void opOpAssign(string op : "-")(const BasicMatrix rhs) {
20128 		this.data[] -= rhs.data;
20129 	}
20130 	void opOpAssign(string op : "*")(const T rhs) {
20131 		this.data[] *= rhs;
20132 	}
20133 	void opOpAssign(string op : "/")(const T rhs) {
20134 		this.data[] /= rhs;
20135 	}
20136 	void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) {
20137 		static assert(columns == rhsRows);
20138 		auto multiplySize = columns;
20139 
20140 		auto tmp = this.data; // copy cuz it is a value type
20141 
20142 		int idx = 0;
20143 		foreach(r; 0 .. rows)
20144 		foreach(c; 0 .. columns) {
20145 			T sum = 0.0;
20146 
20147 			foreach(i; 0 .. multiplySize)
20148 				sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c];
20149 
20150 			tmp[idx++] = sum;
20151 		}
20152 
20153 		this.data = tmp;
20154 	}
20155 }
20156 
20157 unittest {
20158 	auto m = BasicMatrix!(2, 2)([
20159 		1, 2,
20160 		3, 4
20161 	]);
20162 
20163 	auto m2 = BasicMatrix!(2, 2)([
20164 		5, 6,
20165 		7, 8
20166 	]);
20167 
20168 	//import std.conv;
20169 	m *= m2;
20170 	assert(m.data == [
20171 		19, 22,
20172 		43, 50
20173 	]);//, to!string(m.data));
20174 }
20175 
20176 
20177 
20178 class GlObjectBase {
20179 	protected uint _vao;
20180 	protected uint _elementsCount;
20181 
20182 	protected uint element_buffer;
20183 
20184 	void gen() {
20185 		glGenVertexArrays(1, &_vao);
20186 	}
20187 
20188 	void bind() {
20189 		glBindVertexArray(_vao);
20190 	}
20191 
20192 	void dispose() {
20193 		glDeleteVertexArrays(1, &_vao);
20194 	}
20195 
20196 	void draw() {
20197 		bind();
20198 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20199 		glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null);
20200 	}
20201 }
20202 
20203 /++
20204 
20205 +/
20206 class GlObject(T) : GlObjectBase {
20207 	protected uint VBO;
20208 
20209 	this(T[] arr, uint[] indices) {
20210 		gen();
20211 		bind();
20212 
20213 		glGenBuffers(1, &VBO);
20214 		glGenBuffers(1, &element_buffer);
20215 
20216 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
20217 		glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW);
20218 
20219 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20220 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
20221 		_elementsCount = cast(int) indices.length;
20222 
20223 		foreach(int idx, memberName; __traits(allMembers, T)) {
20224 			static if(memberName != "__ctor") {
20225 			static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) {
20226 				glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof);
20227 				glEnableVertexAttribArray(idx);
20228 			} else static assert(0); }
20229 		}
20230 	}
20231 
20232 	static string generateShaderDefinitions() {
20233 		string code;
20234 
20235 		foreach(idx, memberName; __traits(allMembers, T)) {
20236 			// never use stringof ladies and gents it has a LU thing at the end of it
20237 			static if(memberName != "__ctor")
20238 			code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n";
20239 		}
20240 
20241 		return code;
20242 	}
20243 }
20244 
20245 private string typeToGl(T)() {
20246 	static if(is(T == float[4]))
20247 		return "vec4";
20248 	else static if(is(T == float[3]))
20249 		return "vec3";
20250 	else static if(is(T == float[2]))
20251 		return "vec2";
20252 	else static assert(0, T.stringof);
20253 }
20254 
20255 
20256 }
20257 
20258 version(linux) {
20259 	version(with_eventloop) {} else {
20260 		private int epollFd = -1;
20261 		void prepareEventLoop() {
20262 			if(epollFd != -1)
20263 				return; // already initialized, no need to do it again
20264 			import ep = core.sys.linux.epoll;
20265 
20266 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
20267 			if(epollFd == -1)
20268 				throw new Exception("epoll create failure");
20269 		}
20270 	}
20271 } else version(Posix) {
20272 	void prepareEventLoop() {}
20273 }
20274 
20275 version(X11) {
20276 	import core.stdc.locale : LC_ALL; // rdmd fix
20277 	__gshared bool sdx_isUTF8Locale;
20278 
20279 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
20280 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
20281 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
20282 	// anal magic is here. I (Ketmar) hope you like it.
20283 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
20284 	// always return correct unicode symbols. The detection is here 'cause user can change locale
20285 	// later.
20286 
20287 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
20288 	shared static this () @system {
20289 		if(!librariesSuccessfullyLoaded)
20290 			return;
20291 
20292 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
20293 
20294 		// this doesn't hurt; it may add some locking, but the speed is still
20295 		// allows doing 60 FPS videogames; also, ignore the result, as most
20296 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
20297 		// never seen this failing).
20298 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
20299 
20300 		setlocale(LC_ALL, "");
20301 		// check if out locale is UTF-8
20302 		auto lct = setlocale(LC_CTYPE, null);
20303 		if (lct is null) {
20304 			sdx_isUTF8Locale = false;
20305 		} else {
20306 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
20307 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
20308 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
20309 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
20310 				{
20311 					sdx_isUTF8Locale = true;
20312 					break;
20313 				}
20314 			}
20315 		}
20316 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
20317 	}
20318 }
20319 
20320 class ExperimentalTextComponent2 {
20321 	/+
20322 		Stage 1: get it working monospace
20323 		Stage 2: use proportional font
20324 		Stage 3: allow changes in inline style
20325 		Stage 4: allow new fonts and sizes in the middle
20326 		Stage 5: optimize gap buffer
20327 		Stage 6: optimize layout
20328 		Stage 7: word wrap
20329 		Stage 8: justification
20330 		Stage 9: editing, selection, etc.
20331 
20332 			Operations:
20333 				insert text
20334 				overstrike text
20335 				select
20336 				cut
20337 				modify
20338 	+/
20339 
20340 	/++
20341 		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.
20342 	+/
20343 	this(SimpleWindow window) {
20344 		this.window = window;
20345 	}
20346 
20347 	private SimpleWindow window;
20348 
20349 
20350 	/++
20351 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
20352 		representing the internal parts. The first pass is focused on the x parameter, then the
20353 		renderer is responsible for going back to the parts in the current line and calling
20354 		adjustDownForAscent to change the y params.
20355 	+/
20356 	static interface ComponentRenderHelper {
20357 
20358 		/+
20359 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
20360 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
20361 			to move (adjust y to make room for new line) until you get back to the same position,
20362 			then you can stop - if one thing is unchanged, nothing after it is changed too.
20363 
20364 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
20365 			once you reach something that is unchanged, you can stop.
20366 		+/
20367 
20368 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
20369 
20370 		int ascent() const;
20371 		int descent() const;
20372 
20373 		int advance() const;
20374 
20375 		bool endsWithExplititLineBreak() const;
20376 	}
20377 
20378 	static interface RenderResult {
20379 		/++
20380 			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.
20381 		+/
20382 		void popFront();
20383 		@property bool empty() const;
20384 		@property ComponentRenderHelper front() const;
20385 
20386 		void repositionForNextLine(Point baseline, int availableWidth);
20387 	}
20388 
20389 	static interface ComponentInFlow {
20390 		void draw(ScreenPainter painter);
20391 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
20392 
20393 		bool startsWithExplicitLineBreak() const;
20394 	}
20395 
20396 	static class TextFlowComponent : ComponentInFlow {
20397 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
20398 
20399 		Color foreground;
20400 		Color background;
20401 
20402 		OperatingSystemFont font; // should NEVER be null
20403 
20404 		ubyte attributes; // underline, strike through, display on new block
20405 
20406 		version(Windows)
20407 			const(wchar)[] content;
20408 		else
20409 			const(char)[] content; // this should NEVER have a newline, except at the end
20410 
20411 		RenderedComponent[] rendered; // entirely controlled by [rerender]
20412 
20413 		// could prolly put some spacing around it too like margin / padding
20414 
20415 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
20416 			in { assert(font !is null);
20417 			     assert(!font.isNull); }
20418 			do
20419 		{
20420 			this.foreground = f;
20421 			this.background = b;
20422 			this.font = font;
20423 
20424 			this.attributes = attr;
20425 			version(Windows) {
20426 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
20427 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
20428 				auto buffer = new wchar[](sz);
20429 				this.content = makeWindowsString(c, buffer, conversionFlags);
20430 			} else {
20431 				this.content = c.dup;
20432 			}
20433 		}
20434 
20435 		void draw(ScreenPainter painter) {
20436 			painter.setFont(this.font);
20437 			painter.outlineColor = this.foreground;
20438 			painter.fillColor = Color.transparent;
20439 			foreach(rendered; this.rendered) {
20440 				// the component works in term of baseline,
20441 				// but the painter works in term of upper left bounding box
20442 				// so need to translate that
20443 
20444 				if(this.background.a) {
20445 					painter.fillColor = this.background;
20446 					painter.outlineColor = this.background;
20447 
20448 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
20449 
20450 					painter.outlineColor = this.foreground;
20451 					painter.fillColor = Color.transparent;
20452 				}
20453 
20454 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
20455 
20456 				// FIXME: strike through, underline, highlight selection, etc.
20457 			}
20458 		}
20459 	}
20460 
20461 	// I could split the parts into words on render
20462 	// for easier word-wrap, each one being an unbreakable "inline-block"
20463 	private TextFlowComponent[] parts;
20464 	private int needsRerenderFrom;
20465 
20466 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
20467 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
20468 		parts ~= new TextFlowComponent(f, b, font, attr, c);
20469 	}
20470 
20471 	static struct RenderedComponent {
20472 		int startX;
20473 		int startY;
20474 		short width;
20475 		// 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!
20476 		// for individual chars in here you've gotta process on demand
20477 		version(Windows)
20478 			const(wchar)[] slice;
20479 		else
20480 			const(char)[] slice;
20481 	}
20482 
20483 
20484 	void rerender(Rectangle boundingBox) {
20485 		Point baseline = boundingBox.upperLeft;
20486 
20487 		this.boundingBox.left = boundingBox.left;
20488 		this.boundingBox.top = boundingBox.top;
20489 
20490 		auto remainingParts = parts;
20491 
20492 		int largestX;
20493 
20494 
20495 		foreach(part; parts)
20496 			part.font.prepareContext(window);
20497 		scope(exit)
20498 		foreach(part; parts)
20499 			part.font.releaseContext();
20500 
20501 		calculateNextLine:
20502 
20503 		int nextLineHeight = 0;
20504 		int nextBiggestDescent = 0;
20505 
20506 		foreach(part; remainingParts) {
20507 			auto height = part.font.ascent;
20508 			if(height > nextLineHeight)
20509 				nextLineHeight = height;
20510 			if(part.font.descent > nextBiggestDescent)
20511 				nextBiggestDescent = part.font.descent;
20512 			if(part.content.length && part.content[$-1] == '\n')
20513 				break;
20514 		}
20515 
20516 		baseline.y += nextLineHeight;
20517 		auto lineStart = baseline;
20518 
20519 		while(remainingParts.length) {
20520 			remainingParts[0].rendered = null;
20521 
20522 			bool eol;
20523 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
20524 				eol = true;
20525 
20526 			// FIXME: word wrap
20527 			auto font = remainingParts[0].font;
20528 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
20529 			auto width = font.stringWidth(slice, window);
20530 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
20531 
20532 			remainingParts = remainingParts[1 .. $];
20533 			baseline.x += width;
20534 
20535 			if(eol) {
20536 				baseline.y += nextBiggestDescent;
20537 				if(baseline.x > largestX)
20538 					largestX = baseline.x;
20539 				baseline.x = lineStart.x;
20540 				goto calculateNextLine;
20541 			}
20542 		}
20543 
20544 		if(baseline.x > largestX)
20545 			largestX = baseline.x;
20546 
20547 		this.boundingBox.right = largestX;
20548 		this.boundingBox.bottom = baseline.y;
20549 	}
20550 
20551 	// you must call rerender first!
20552 	void draw(ScreenPainter painter) {
20553 		foreach(part; parts) {
20554 			part.draw(painter);
20555 		}
20556 	}
20557 
20558 	struct IdentifyResult {
20559 		TextFlowComponent part;
20560 		int charIndexInPart;
20561 		int totalCharIndex = -1; // if this is -1, it just means the end
20562 
20563 		Rectangle boundingBox;
20564 	}
20565 
20566 	IdentifyResult identify(Point pt, bool exact = false) {
20567 		if(parts.length == 0)
20568 			return IdentifyResult(null, 0);
20569 
20570 		if(pt.y < boundingBox.top) {
20571 			if(exact)
20572 				return IdentifyResult(null, 1);
20573 			return IdentifyResult(parts[0], 0);
20574 		}
20575 		if(pt.y > boundingBox.bottom) {
20576 			if(exact)
20577 				return IdentifyResult(null, 2);
20578 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
20579 		}
20580 
20581 		int tci = 0;
20582 
20583 		// I should probably like binary search this or something...
20584 		foreach(ref part; parts) {
20585 			foreach(rendered; part.rendered) {
20586 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
20587 				if(rect.contains(pt)) {
20588 					auto x = pt.x - rendered.startX;
20589 					auto estimatedIdx = x / part.font.averageWidth;
20590 
20591 					if(estimatedIdx < 0)
20592 						estimatedIdx = 0;
20593 
20594 					if(estimatedIdx > rendered.slice.length)
20595 						estimatedIdx = cast(int) rendered.slice.length;
20596 
20597 					int idx;
20598 					int x1, x2;
20599 					if(part.font.isMonospace) {
20600 						auto w = part.font.averageWidth;
20601 						if(!exact && x > (estimatedIdx + 1) * w)
20602 							return IdentifyResult(null, 4);
20603 						idx = estimatedIdx;
20604 						x1 = idx * w;
20605 						x2 = (idx + 1) * w;
20606 					} else {
20607 						idx = estimatedIdx;
20608 
20609 						part.font.prepareContext(window);
20610 						scope(exit) part.font.releaseContext();
20611 
20612 						// int iterations;
20613 
20614 						while(true) {
20615 							// iterations++;
20616 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
20617 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
20618 
20619 							x1 += rendered.startX;
20620 							x2 += rendered.startX;
20621 
20622 							if(pt.x < x1) {
20623 								if(idx == 0) {
20624 									if(exact)
20625 										return IdentifyResult(null, 6);
20626 									else
20627 										break;
20628 								}
20629 								idx--;
20630 							} else if(pt.x > x2) {
20631 								idx++;
20632 								if(idx > rendered.slice.length) {
20633 									if(exact)
20634 										return IdentifyResult(null, 5);
20635 									else
20636 										break;
20637 								}
20638 							} else if(pt.x >= x1 && pt.x <= x2) {
20639 								if(idx)
20640 									idx--; // point it at the original index
20641 								break; // we fit
20642 							}
20643 						}
20644 
20645 						// writeln(iterations)
20646 					}
20647 
20648 
20649 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
20650 				}
20651 			}
20652 			tci += cast(int) part.content.length; // FIXME: utf-8?
20653 		}
20654 		return IdentifyResult(null, 3);
20655 	}
20656 
20657 	Rectangle boundingBox; // only set after [rerender]
20658 
20659 	// text will be positioned around the exclusion zone
20660 	static struct ExclusionZone {
20661 
20662 	}
20663 
20664 	ExclusionZone[] exclusionZones;
20665 }
20666 
20667 
20668 // Don't use this yet. When I'm happy with it, I will move it to the
20669 // regular module namespace.
20670 mixin template ExperimentalTextComponent() {
20671 
20672 static:
20673 
20674 	alias Rectangle = arsd.color.Rectangle;
20675 
20676 	struct ForegroundColor {
20677 		Color color;
20678 		alias color this;
20679 
20680 		this(Color c) {
20681 			color = c;
20682 		}
20683 
20684 		this(int r, int g, int b, int a = 255) {
20685 			color = Color(r, g, b, a);
20686 		}
20687 
20688 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
20689 			return ForegroundColor(mixin("Color." ~ s));
20690 		}
20691 	}
20692 
20693 	struct BackgroundColor {
20694 		Color color;
20695 		alias color this;
20696 
20697 		this(Color c) {
20698 			color = c;
20699 		}
20700 
20701 		this(int r, int g, int b, int a = 255) {
20702 			color = Color(r, g, b, a);
20703 		}
20704 
20705 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
20706 			return BackgroundColor(mixin("Color." ~ s));
20707 		}
20708 	}
20709 
20710 	static class InlineElement {
20711 		string text;
20712 
20713 		BlockElement containingBlock;
20714 
20715 		Color color = Color.black;
20716 		Color backgroundColor = Color.transparent;
20717 		ushort styles;
20718 
20719 		string font;
20720 		int fontSize;
20721 
20722 		int lineHeight;
20723 
20724 		void* identifier;
20725 
20726 		Rectangle boundingBox;
20727 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
20728 
20729 		bool isMergeCompatible(InlineElement other) {
20730 			return
20731 				containingBlock is other.containingBlock &&
20732 				color == other.color &&
20733 				backgroundColor == other.backgroundColor &&
20734 				styles == other.styles &&
20735 				font == other.font &&
20736 				fontSize == other.fontSize &&
20737 				lineHeight == other.lineHeight &&
20738 				true;
20739 		}
20740 
20741 		int xOfIndex(size_t index) {
20742 			if(index < letterXs.length)
20743 				return letterXs[index];
20744 			else
20745 				return boundingBox.right;
20746 		}
20747 
20748 		InlineElement clone() {
20749 			auto ie = new InlineElement();
20750 			ie.tupleof = this.tupleof;
20751 			return ie;
20752 		}
20753 
20754 		InlineElement getPreviousInlineElement() {
20755 			InlineElement prev = null;
20756 			foreach(ie; this.containingBlock.parts) {
20757 				if(ie is this)
20758 					break;
20759 				prev = ie;
20760 			}
20761 			if(prev is null) {
20762 				BlockElement pb;
20763 				BlockElement cb = this.containingBlock;
20764 				moar:
20765 				foreach(ie; this.containingBlock.containingLayout.blocks) {
20766 					if(ie is cb)
20767 						break;
20768 					pb = ie;
20769 				}
20770 				if(pb is null)
20771 					return null;
20772 				if(pb.parts.length == 0) {
20773 					cb = pb;
20774 					goto moar;
20775 				}
20776 
20777 				prev = pb.parts[$-1];
20778 
20779 			}
20780 			return prev;
20781 		}
20782 
20783 		InlineElement getNextInlineElement() {
20784 			InlineElement next = null;
20785 			foreach(idx, ie; this.containingBlock.parts) {
20786 				if(ie is this) {
20787 					if(idx + 1 < this.containingBlock.parts.length)
20788 						next = this.containingBlock.parts[idx + 1];
20789 					break;
20790 				}
20791 			}
20792 			if(next is null) {
20793 				BlockElement n;
20794 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
20795 					if(ie is this.containingBlock) {
20796 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
20797 							n = this.containingBlock.containingLayout.blocks[idx + 1];
20798 						break;
20799 					}
20800 				}
20801 				if(n is null)
20802 					return null;
20803 
20804 				if(n.parts.length)
20805 					next = n.parts[0];
20806 				else {} // FIXME
20807 
20808 			}
20809 			return next;
20810 		}
20811 
20812 	}
20813 
20814 	// Block elements are used entirely for positioning inline elements,
20815 	// which are the things that are actually drawn.
20816 	class BlockElement {
20817 		InlineElement[] parts;
20818 		uint alignment;
20819 
20820 		int whiteSpace; // pre, pre-wrap, wrap
20821 
20822 		TextLayout containingLayout;
20823 
20824 		// inputs
20825 		Point where;
20826 		Size minimumSize;
20827 		Size maximumSize;
20828 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
20829 		void* identifier;
20830 
20831 		Rectangle margin;
20832 		Rectangle padding;
20833 
20834 		// outputs
20835 		Rectangle[] boundingBoxes;
20836 	}
20837 
20838 	struct TextIdentifyResult {
20839 		InlineElement element;
20840 		int offset;
20841 
20842 		private TextIdentifyResult fixupNewline() {
20843 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
20844 				offset--;
20845 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
20846 				offset--;
20847 			}
20848 			return this;
20849 		}
20850 	}
20851 
20852 	class TextLayout {
20853 		BlockElement[] blocks;
20854 		Rectangle boundingBox_;
20855 		Rectangle boundingBox() { return boundingBox_; }
20856 		void boundingBox(Rectangle r) {
20857 			if(r != boundingBox_) {
20858 				boundingBox_ = r;
20859 				layoutInvalidated = true;
20860 			}
20861 		}
20862 
20863 		Rectangle contentBoundingBox() {
20864 			Rectangle r;
20865 			foreach(block; blocks)
20866 			foreach(ie; block.parts) {
20867 				if(ie.boundingBox.right > r.right)
20868 					r.right = ie.boundingBox.right;
20869 				if(ie.boundingBox.bottom > r.bottom)
20870 					r.bottom = ie.boundingBox.bottom;
20871 			}
20872 			return r;
20873 		}
20874 
20875 		BlockElement[] getBlocks() {
20876 			return blocks;
20877 		}
20878 
20879 		InlineElement[] getTexts() {
20880 			InlineElement[] elements;
20881 			foreach(block; blocks)
20882 				elements ~= block.parts;
20883 			return elements;
20884 		}
20885 
20886 		string getPlainText() {
20887 			string text;
20888 			foreach(block; blocks)
20889 				foreach(part; block.parts)
20890 					text ~= part.text;
20891 			return text;
20892 		}
20893 
20894 		string getHtml() {
20895 			return null; // FIXME
20896 		}
20897 
20898 		this(Rectangle boundingBox) {
20899 			this.boundingBox = boundingBox;
20900 		}
20901 
20902 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
20903 			auto be = new BlockElement();
20904 			be.containingLayout = this;
20905 			if(after is null)
20906 				blocks ~= be;
20907 			else {
20908 				foreach(idx, b; blocks) {
20909 					if(b is after.containingBlock) {
20910 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
20911 						break;
20912 					}
20913 				}
20914 			}
20915 			return be;
20916 		}
20917 
20918 		void clear() {
20919 			blocks = null;
20920 			selectionStart = selectionEnd = caret = Caret.init;
20921 		}
20922 
20923 		void addText(Args...)(Args args) {
20924 			if(blocks.length == 0)
20925 				addBlock();
20926 
20927 			InlineElement ie = new InlineElement();
20928 			foreach(idx, arg; args) {
20929 				static if(is(typeof(arg) == ForegroundColor))
20930 					ie.color = arg;
20931 				else static if(is(typeof(arg) == TextFormat)) {
20932 					if(arg & 0x8000) // ~TextFormat.something turns it off
20933 						ie.styles &= arg;
20934 					else
20935 						ie.styles |= arg;
20936 				} else static if(is(typeof(arg) == string)) {
20937 					static if(idx == 0 && args.length > 1)
20938 						static assert(0, "Put styles before the string.");
20939 					size_t lastLineIndex;
20940 					foreach(cidx, char a; arg) {
20941 						if(a == '\n') {
20942 							ie.text = arg[lastLineIndex .. cidx + 1];
20943 							lastLineIndex = cidx + 1;
20944 							ie.containingBlock = blocks[$-1];
20945 							blocks[$-1].parts ~= ie.clone;
20946 							ie.text = null;
20947 						} else {
20948 
20949 						}
20950 					}
20951 
20952 					ie.text = arg[lastLineIndex .. $];
20953 					ie.containingBlock = blocks[$-1];
20954 					blocks[$-1].parts ~= ie.clone;
20955 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
20956 				}
20957 			}
20958 
20959 			invalidateLayout();
20960 		}
20961 
20962 		void tryMerge(InlineElement into, InlineElement what) {
20963 			if(!into.isMergeCompatible(what)) {
20964 				return; // cannot merge, different configs
20965 			}
20966 
20967 			// cool, can merge, bring text together...
20968 			into.text ~= what.text;
20969 
20970 			// and remove what
20971 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
20972 				if(what.containingBlock.parts[a] is what) {
20973 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
20974 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
20975 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
20976 
20977 				}
20978 			}
20979 
20980 			// FIXME: ensure no other carets have a reference to it
20981 		}
20982 
20983 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
20984 		TextIdentifyResult identify(int x, int y, bool exact = false) {
20985 			TextIdentifyResult inexactMatch;
20986 			foreach(block; blocks) {
20987 				foreach(part; block.parts) {
20988 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
20989 
20990 						// FIXME binary search
20991 						int tidx;
20992 						int lastX;
20993 						foreach_reverse(idxo, lx; part.letterXs) {
20994 							int idx = cast(int) idxo;
20995 							if(lx <= x) {
20996 								if(lastX && lastX - x < x - lx)
20997 									tidx = idx + 1;
20998 								else
20999 									tidx = idx;
21000 								break;
21001 							}
21002 							lastX = lx;
21003 						}
21004 
21005 						return TextIdentifyResult(part, tidx).fixupNewline;
21006 					} else if(!exact) {
21007 						// we're not in the box, but are we on the same line?
21008 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
21009 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
21010 					}
21011 				}
21012 			}
21013 
21014 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
21015 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
21016 
21017 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
21018 		}
21019 
21020 		void moveCaretToPixelCoordinates(int x, int y) {
21021 			auto result = identify(x, y);
21022 			caret.inlineElement = result.element;
21023 			caret.offset = result.offset;
21024 		}
21025 
21026 		void selectToPixelCoordinates(int x, int y) {
21027 			auto result = identify(x, y);
21028 
21029 			if(y < caretLastDrawnY1) {
21030 				// on a previous line, carat is selectionEnd
21031 				selectionEnd = caret;
21032 
21033 				selectionStart = Caret(this, result.element, result.offset);
21034 			} else if(y > caretLastDrawnY2) {
21035 				// on a later line
21036 				selectionStart = caret;
21037 
21038 				selectionEnd = Caret(this, result.element, result.offset);
21039 			} else {
21040 				// on the same line...
21041 				if(x <= caretLastDrawnX) {
21042 					selectionEnd = caret;
21043 					selectionStart = Caret(this, result.element, result.offset);
21044 				} else {
21045 					selectionStart = caret;
21046 					selectionEnd = Caret(this, result.element, result.offset);
21047 				}
21048 
21049 			}
21050 		}
21051 
21052 
21053 		/// Call this if the inputs change. It will reflow everything
21054 		void redoLayout(ScreenPainter painter) {
21055 			//painter.setClipRectangle(boundingBox);
21056 			auto pos = Point(boundingBox.left, boundingBox.top);
21057 
21058 			int lastHeight;
21059 			void nl() {
21060 				pos.x = boundingBox.left;
21061 				pos.y += lastHeight;
21062 			}
21063 			foreach(block; blocks) {
21064 				nl();
21065 				foreach(part; block.parts) {
21066 					part.letterXs = null;
21067 
21068 					auto size = painter.textSize(part.text);
21069 					version(Windows)
21070 						if(part.text.length && part.text[$-1] == '\n')
21071 							size.height /= 2; // windows counts the new line at the end, but we don't want that
21072 
21073 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
21074 
21075 					foreach(idx, char c; part.text) {
21076 							// FIXME: unicode
21077 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
21078 					}
21079 
21080 					pos.x += size.width;
21081 					if(pos.x >= boundingBox.right) {
21082 						pos.y += size.height;
21083 						pos.x = boundingBox.left;
21084 						lastHeight = 0;
21085 					} else {
21086 						lastHeight = size.height;
21087 					}
21088 
21089 					if(part.text.length && part.text[$-1] == '\n')
21090 						nl();
21091 				}
21092 			}
21093 
21094 			layoutInvalidated = false;
21095 		}
21096 
21097 		bool layoutInvalidated = true;
21098 		void invalidateLayout() {
21099 			layoutInvalidated = true;
21100 		}
21101 
21102 // FIXME: caret can remain sometimes when inserting
21103 // FIXME: inserting at the beginning once you already have something can eff it up.
21104 		void drawInto(ScreenPainter painter, bool focused = false) {
21105 			if(layoutInvalidated)
21106 				redoLayout(painter);
21107 			foreach(block; blocks) {
21108 				foreach(part; block.parts) {
21109 					painter.outlineColor = part.color;
21110 					painter.fillColor = part.backgroundColor;
21111 
21112 					auto pos = part.boundingBox.upperLeft;
21113 					auto size = part.boundingBox.size;
21114 
21115 					painter.drawText(pos, part.text);
21116 					if(part.styles & TextFormat.underline)
21117 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
21118 					if(part.styles & TextFormat.strikethrough)
21119 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
21120 				}
21121 			}
21122 
21123 			// on every redraw, I will force the caret to be
21124 			// redrawn too, in order to eliminate perceived lag
21125 			// when moving around with the mouse.
21126 			eraseCaret(painter);
21127 
21128 			if(focused) {
21129 				highlightSelection(painter);
21130 				drawCaret(painter);
21131 			}
21132 		}
21133 
21134 		Color selectionXorColor = Color(255, 255, 127);
21135 
21136 		void highlightSelection(ScreenPainter painter) {
21137 			if(selectionStart is selectionEnd)
21138 				return; // no selection
21139 
21140 			if(selectionStart.inlineElement is null) return;
21141 			if(selectionEnd.inlineElement is null) return;
21142 
21143 			assert(selectionStart.inlineElement !is null);
21144 			assert(selectionEnd.inlineElement !is null);
21145 
21146 			painter.rasterOp = RasterOp.xor;
21147 			painter.outlineColor = Color.transparent;
21148 			painter.fillColor = selectionXorColor;
21149 
21150 			auto at = selectionStart.inlineElement;
21151 			auto atOffset = selectionStart.offset;
21152 			bool done;
21153 			while(at) {
21154 				auto box = at.boundingBox;
21155 				if(atOffset < at.letterXs.length)
21156 					box.left = at.letterXs[atOffset];
21157 
21158 				if(at is selectionEnd.inlineElement) {
21159 					if(selectionEnd.offset < at.letterXs.length)
21160 						box.right = at.letterXs[selectionEnd.offset];
21161 					done = true;
21162 				}
21163 
21164 				painter.drawRectangle(box.upperLeft, box.width, box.height);
21165 
21166 				if(done)
21167 					break;
21168 
21169 				at = at.getNextInlineElement();
21170 				atOffset = 0;
21171 			}
21172 		}
21173 
21174 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
21175 		bool caretShowingOnScreen = false;
21176 		void drawCaret(ScreenPainter painter) {
21177 			//painter.setClipRectangle(boundingBox);
21178 			int x, y1, y2;
21179 			if(caret.inlineElement is null) {
21180 				x = boundingBox.left;
21181 				y1 = boundingBox.top + 2;
21182 				y2 = boundingBox.top + painter.fontHeight;
21183 			} else {
21184 				x = caret.inlineElement.xOfIndex(caret.offset);
21185 				y1 = caret.inlineElement.boundingBox.top + 2;
21186 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21187 			}
21188 
21189 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
21190 				eraseCaret(painter);
21191 
21192 			painter.pen = Pen(Color.white, 1);
21193 			painter.rasterOp = RasterOp.xor;
21194 			painter.drawLine(
21195 				Point(x, y1),
21196 				Point(x, y2)
21197 			);
21198 			painter.rasterOp = RasterOp.normal;
21199 			caretShowingOnScreen = !caretShowingOnScreen;
21200 
21201 			if(caretShowingOnScreen) {
21202 				caretLastDrawnX = x;
21203 				caretLastDrawnY1 = y1;
21204 				caretLastDrawnY2 = y2;
21205 			}
21206 		}
21207 
21208 		Rectangle caretBoundingBox() {
21209 			int x, y1, y2;
21210 			if(caret.inlineElement is null) {
21211 				x = boundingBox.left;
21212 				y1 = boundingBox.top + 2;
21213 				y2 = boundingBox.top + 16;
21214 			} else {
21215 				x = caret.inlineElement.xOfIndex(caret.offset);
21216 				y1 = caret.inlineElement.boundingBox.top + 2;
21217 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21218 			}
21219 
21220 			return Rectangle(x, y1, x + 1, y2);
21221 		}
21222 
21223 		void eraseCaret(ScreenPainter painter) {
21224 			//painter.setClipRectangle(boundingBox);
21225 			if(!caretShowingOnScreen) return;
21226 			painter.pen = Pen(Color.white, 1);
21227 			painter.rasterOp = RasterOp.xor;
21228 			painter.drawLine(
21229 				Point(caretLastDrawnX, caretLastDrawnY1),
21230 				Point(caretLastDrawnX, caretLastDrawnY2)
21231 			);
21232 
21233 			caretShowingOnScreen = false;
21234 			painter.rasterOp = RasterOp.normal;
21235 		}
21236 
21237 		/// Caret movement api
21238 		/// These should give the user a logical result based on what they see on screen...
21239 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
21240 		void moveUp() {
21241 			if(caret.inlineElement is null) return;
21242 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21243 			auto y = caret.inlineElement.boundingBox.top + 2;
21244 
21245 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21246 			if(y < 0)
21247 				return;
21248 
21249 			auto i = identify(x, y);
21250 
21251 			if(i.element) {
21252 				caret.inlineElement = i.element;
21253 				caret.offset = i.offset;
21254 			}
21255 		}
21256 		void moveDown() {
21257 			if(caret.inlineElement is null) return;
21258 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21259 			auto y = caret.inlineElement.boundingBox.bottom - 2;
21260 
21261 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21262 
21263 			auto i = identify(x, y);
21264 			if(i.element) {
21265 				caret.inlineElement = i.element;
21266 				caret.offset = i.offset;
21267 			}
21268 		}
21269 		void moveLeft() {
21270 			if(caret.inlineElement is null) return;
21271 			if(caret.offset)
21272 				caret.offset--;
21273 			else {
21274 				auto p = caret.inlineElement.getPreviousInlineElement();
21275 				if(p) {
21276 					caret.inlineElement = p;
21277 					if(p.text.length && p.text[$-1] == '\n')
21278 						caret.offset = cast(int) p.text.length - 1;
21279 					else
21280 						caret.offset = cast(int) p.text.length;
21281 				}
21282 			}
21283 		}
21284 		void moveRight() {
21285 			if(caret.inlineElement is null) return;
21286 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
21287 				caret.offset++;
21288 			} else {
21289 				auto p = caret.inlineElement.getNextInlineElement();
21290 				if(p) {
21291 					caret.inlineElement = p;
21292 					caret.offset = 0;
21293 				}
21294 			}
21295 		}
21296 		void moveHome() {
21297 			if(caret.inlineElement is null) return;
21298 			auto x = 0;
21299 			auto y = caret.inlineElement.boundingBox.top + 2;
21300 
21301 			auto i = identify(x, y);
21302 
21303 			if(i.element) {
21304 				caret.inlineElement = i.element;
21305 				caret.offset = i.offset;
21306 			}
21307 		}
21308 		void moveEnd() {
21309 			if(caret.inlineElement is null) return;
21310 			auto x = int.max;
21311 			auto y = caret.inlineElement.boundingBox.top + 2;
21312 
21313 			auto i = identify(x, y);
21314 
21315 			if(i.element) {
21316 				caret.inlineElement = i.element;
21317 				caret.offset = i.offset;
21318 			}
21319 
21320 		}
21321 		void movePageUp(ref Caret caret) {}
21322 		void movePageDown(ref Caret caret) {}
21323 
21324 		void moveDocumentStart(ref Caret caret) {
21325 			if(blocks.length && blocks[0].parts.length)
21326 				caret = Caret(this, blocks[0].parts[0], 0);
21327 			else
21328 				caret = Caret.init;
21329 		}
21330 
21331 		void moveDocumentEnd(ref Caret caret) {
21332 			if(blocks.length) {
21333 				auto parts = blocks[$-1].parts;
21334 				if(parts.length) {
21335 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
21336 				} else {
21337 					caret = Caret.init;
21338 				}
21339 			} else
21340 				caret = Caret.init;
21341 		}
21342 
21343 		void deleteSelection() {
21344 			if(selectionStart is selectionEnd)
21345 				return;
21346 
21347 			if(selectionStart.inlineElement is null) return;
21348 			if(selectionEnd.inlineElement is null) return;
21349 
21350 			assert(selectionStart.inlineElement !is null);
21351 			assert(selectionEnd.inlineElement !is null);
21352 
21353 			auto at = selectionStart.inlineElement;
21354 
21355 			if(selectionEnd.inlineElement is at) {
21356 				// same element, need to chop out
21357 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
21358 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
21359 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
21360 			} else {
21361 				// different elements, we can do it with slicing
21362 				at.text = at.text[0 .. selectionStart.offset];
21363 				if(selectionStart.offset < at.letterXs.length)
21364 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
21365 
21366 				at = at.getNextInlineElement();
21367 
21368 				while(at) {
21369 					if(at is selectionEnd.inlineElement) {
21370 						at.text = at.text[selectionEnd.offset .. $];
21371 						if(selectionEnd.offset < at.letterXs.length)
21372 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
21373 						selectionEnd.offset = 0;
21374 						break;
21375 					} else {
21376 						auto cfd = at;
21377 						cfd.text = null; // delete the whole thing
21378 
21379 						at = at.getNextInlineElement();
21380 
21381 						if(cfd.text.length == 0) {
21382 							// and remove cfd
21383 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
21384 								if(cfd.containingBlock.parts[a] is cfd) {
21385 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
21386 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
21387 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
21388 
21389 								}
21390 							}
21391 						}
21392 					}
21393 				}
21394 			}
21395 
21396 			caret = selectionEnd;
21397 			selectNone();
21398 
21399 			invalidateLayout();
21400 
21401 		}
21402 
21403 		/// Plain text editing api. These work at the current caret inside the selected inline element.
21404 		void insert(in char[] text) {
21405 			foreach(dchar ch; text)
21406 				insert(ch);
21407 		}
21408 		/// ditto
21409 		void insert(dchar ch) {
21410 
21411 			bool selectionDeleted = false;
21412 			if(selectionStart !is selectionEnd) {
21413 				deleteSelection();
21414 				selectionDeleted = true;
21415 			}
21416 
21417 			if(ch == 127) {
21418 				delete_();
21419 				return;
21420 			}
21421 			if(ch == 8) {
21422 				if(!selectionDeleted)
21423 					backspace();
21424 				return;
21425 			}
21426 
21427 			invalidateLayout();
21428 
21429 			if(ch == 13) ch = 10;
21430 			auto e = caret.inlineElement;
21431 			if(e is null) {
21432 				addText("" ~ cast(char) ch) ; // FIXME
21433 				return;
21434 			}
21435 
21436 			if(caret.offset == e.text.length) {
21437 				e.text ~= cast(char) ch; // FIXME
21438 				caret.offset++;
21439 				if(ch == 10) {
21440 					auto c = caret.inlineElement.clone;
21441 					c.text = null;
21442 					c.letterXs = null;
21443 					insertPartAfter(c,e);
21444 					caret = Caret(this, c, 0);
21445 				}
21446 			} else {
21447 				// FIXME cast char sucks
21448 				if(ch == 10) {
21449 					auto c = caret.inlineElement.clone;
21450 					c.text = e.text[caret.offset .. $];
21451 					if(caret.offset < c.letterXs.length)
21452 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
21453 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
21454 					if(caret.offset <= e.letterXs.length) {
21455 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
21456 					}
21457 					insertPartAfter(c,e);
21458 					caret = Caret(this, c, 0);
21459 				} else {
21460 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
21461 					caret.offset++;
21462 				}
21463 			}
21464 		}
21465 
21466 		void insertPartAfter(InlineElement what, InlineElement where) {
21467 			foreach(idx, p; where.containingBlock.parts) {
21468 				if(p is where) {
21469 					if(idx + 1 == where.containingBlock.parts.length)
21470 						where.containingBlock.parts ~= what;
21471 					else
21472 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
21473 					return;
21474 				}
21475 			}
21476 		}
21477 
21478 		void cleanupStructures() {
21479 			for(size_t i = 0; i < blocks.length; i++) {
21480 				auto block = blocks[i];
21481 				for(size_t a = 0; a < block.parts.length; a++) {
21482 					auto part = block.parts[a];
21483 					if(part.text.length == 0) {
21484 						for(size_t b = a; b < block.parts.length - 1; b++)
21485 							block.parts[b] = block.parts[b+1];
21486 						block.parts = block.parts[0 .. $-1];
21487 					}
21488 				}
21489 				if(block.parts.length == 0) {
21490 					for(size_t a = i; a < blocks.length - 1; a++)
21491 						blocks[a] = blocks[a+1];
21492 					blocks = blocks[0 .. $-1];
21493 				}
21494 			}
21495 		}
21496 
21497 		void backspace() {
21498 			try_again:
21499 			auto e = caret.inlineElement;
21500 			if(e is null)
21501 				return;
21502 			if(caret.offset == 0) {
21503 				auto prev = e.getPreviousInlineElement();
21504 				if(prev is null)
21505 					return;
21506 				auto newOffset = cast(int) prev.text.length;
21507 				tryMerge(prev, e);
21508 				caret.inlineElement = prev;
21509 				caret.offset = prev is null ? 0 : newOffset;
21510 
21511 				goto try_again;
21512 			} else if(caret.offset == e.text.length) {
21513 				e.text = e.text[0 .. $-1];
21514 				caret.offset--;
21515 			} else {
21516 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
21517 				caret.offset--;
21518 			}
21519 			//cleanupStructures();
21520 
21521 			invalidateLayout();
21522 		}
21523 		void delete_() {
21524 			if(selectionStart !is selectionEnd)
21525 				deleteSelection();
21526 			else {
21527 				auto before = caret;
21528 				moveRight();
21529 				if(caret != before) {
21530 					backspace();
21531 				}
21532 			}
21533 
21534 			invalidateLayout();
21535 		}
21536 		void overstrike() {}
21537 
21538 		/// Selection API. See also: caret movement.
21539 		void selectAll() {
21540 			moveDocumentStart(selectionStart);
21541 			moveDocumentEnd(selectionEnd);
21542 		}
21543 		bool selectNone() {
21544 			if(selectionStart != selectionEnd) {
21545 				selectionStart = selectionEnd = Caret.init;
21546 				return true;
21547 			}
21548 			return false;
21549 		}
21550 
21551 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
21552 		/// They will modify the current selection if there is one and will splice one in if needed.
21553 		void changeAttributes() {}
21554 
21555 
21556 		/// Text search api. They manipulate the selection and/or caret.
21557 		void findText(string text) {}
21558 		void findIndex(size_t textIndex) {}
21559 
21560 		// sample event handlers
21561 
21562 		void handleEvent(KeyEvent event) {
21563 			//if(event.type == KeyEvent.Type.KeyPressed) {
21564 
21565 			//}
21566 		}
21567 
21568 		void handleEvent(dchar ch) {
21569 
21570 		}
21571 
21572 		void handleEvent(MouseEvent event) {
21573 
21574 		}
21575 
21576 		bool contentEditable; // can it be edited?
21577 		bool contentCaretable; // is there a caret/cursor that moves around in there?
21578 		bool contentSelectable; // selectable?
21579 
21580 		Caret caret;
21581 		Caret selectionStart;
21582 		Caret selectionEnd;
21583 
21584 		bool insertMode;
21585 	}
21586 
21587 	struct Caret {
21588 		TextLayout layout;
21589 		InlineElement inlineElement;
21590 		int offset;
21591 	}
21592 
21593 	enum TextFormat : ushort {
21594 		// decorations
21595 		underline = 1,
21596 		strikethrough = 2,
21597 
21598 		// font selectors
21599 
21600 		bold = 0x4000 | 1, // weight 700
21601 		light = 0x4000 | 2, // weight 300
21602 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
21603 		// bold | light is really invalid but should give weight 500
21604 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
21605 
21606 		italic = 0x4000 | 8,
21607 		smallcaps = 0x4000 | 16,
21608 	}
21609 
21610 	void* findFont(string family, int weight, TextFormat formats) {
21611 		return null;
21612 	}
21613 
21614 }
21615 
21616 /++
21617 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21618 
21619 	History:
21620 		Added February 19, 2021
21621 +/
21622 /// Group: drag_and_drop
21623 interface DropHandler {
21624 	/++
21625 		Called when the drag enters the handler's area.
21626 	+/
21627 	DragAndDropAction dragEnter(DropPackage*);
21628 	/++
21629 		Called when the drag leaves the handler's area or is
21630 		cancelled. You should free your resources when this is called.
21631 	+/
21632 	void dragLeave();
21633 	/++
21634 		Called continually as the drag moves over the handler's area.
21635 
21636 		Returns: feedback to the dragger
21637 	+/
21638 	DropParameters dragOver(Point pt);
21639 	/++
21640 		The user dropped the data and you should process it now. You can
21641 		access the data through the given [DropPackage].
21642 	+/
21643 	void drop(scope DropPackage*);
21644 	/++
21645 		Called when the drop is complete. You should free whatever temporary
21646 		resources you were using. It is often reasonable to simply forward
21647 		this call to [dragLeave].
21648 	+/
21649 	void finish();
21650 
21651 	/++
21652 		Parameters returned by [DropHandler.drop].
21653 	+/
21654 	static struct DropParameters {
21655 		/++
21656 			Acceptable action over this area.
21657 		+/
21658 		DragAndDropAction action;
21659 		/++
21660 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
21661 
21662 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
21663 		+/
21664 		Rectangle consistentWithin;
21665 	}
21666 }
21667 
21668 /++
21669 	History:
21670 		Added February 19, 2021
21671 +/
21672 /// Group: drag_and_drop
21673 enum DragAndDropAction {
21674 	none = 0,
21675 	copy,
21676 	move,
21677 	link,
21678 	ask,
21679 	custom
21680 }
21681 
21682 /++
21683 	An opaque structure representing dropped data. It contains
21684 	private, platform-specific data that your `drop` function
21685 	should simply forward.
21686 
21687 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21688 
21689 	History:
21690 		Added February 19, 2021
21691 +/
21692 /// Group: drag_and_drop
21693 struct DropPackage {
21694 	/++
21695 		Lists the available formats as magic numbers. You should compare these
21696 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
21697 		understand the passed data.
21698 	+/
21699 	DraggableData.FormatId[] availableFormats() {
21700 		version(X11) {
21701 			return xFormats;
21702 		} else version(Windows) {
21703 			if(pDataObj is null)
21704 				return null;
21705 
21706 			typeof(return) ret;
21707 
21708 			IEnumFORMATETC ef;
21709 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
21710 				FORMATETC fmt;
21711 				ULONG fetched;
21712 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
21713 					if(fetched == 0)
21714 						break;
21715 
21716 					if(fmt.lindex != -1)
21717 						continue;
21718 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
21719 						continue;
21720 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
21721 						continue;
21722 
21723 					ret ~= fmt.cfFormat;
21724 				}
21725 			}
21726 
21727 			return ret;
21728 		} else throw new NotYetImplementedException();
21729 	}
21730 
21731 	/++
21732 		Gets data from the drop and optionally accepts it.
21733 
21734 		Returns:
21735 			void because the data is fed asynchronously through the `dg` parameter.
21736 
21737 		Params:
21738 			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.
21739 
21740 			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.
21741 
21742 			Calling `getData` again after accepting a drop is not permitted.
21743 
21744 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
21745 
21746 			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.
21747 
21748 		Throws:
21749 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
21750 
21751 		History:
21752 			Included in first release of [DropPackage].
21753 	+/
21754 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
21755 		version(X11) {
21756 
21757 			auto display = XDisplayConnection.get();
21758 			auto selectionAtom = GetAtom!"XdndSelection"(display);
21759 			auto best = format;
21760 
21761 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
21762 
21763 				XDisplay* display;
21764 				Atom selectionAtom;
21765 				DraggableData.FormatId best;
21766 				DraggableData.FormatId format;
21767 				void delegate(scope ubyte[] data) dg;
21768 				DragAndDropAction acceptedAction;
21769 				Window sourceWindow;
21770 				SimpleWindow win;
21771 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
21772 					this.display = display;
21773 					this.win = win;
21774 					this.sourceWindow = sourceWindow;
21775 					this.format = format;
21776 					this.selectionAtom = selectionAtom;
21777 					this.best = best;
21778 					this.dg = dg;
21779 					this.acceptedAction = acceptedAction;
21780 				}
21781 
21782 
21783 				mixin X11GetSelectionHandler_Basics;
21784 
21785 				void handleData(Atom target, in ubyte[] data) {
21786 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
21787 
21788 					dg(cast(ubyte[]) data);
21789 
21790 					if(acceptedAction != DragAndDropAction.none) {
21791 						auto display = XDisplayConnection.get;
21792 
21793 						XClientMessageEvent xclient;
21794 
21795 						xclient.type = EventType.ClientMessage;
21796 						xclient.window = sourceWindow;
21797 						xclient.message_type = GetAtom!"XdndFinished"(display);
21798 						xclient.format = 32;
21799 						xclient.data.l[0] = win.impl.window;
21800 						xclient.data.l[1] = 1; // drop successful
21801 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
21802 
21803 						XSendEvent(
21804 							display,
21805 							sourceWindow,
21806 							false,
21807 							EventMask.NoEventMask,
21808 							cast(XEvent*) &xclient
21809 						);
21810 
21811 						XFlush(display);
21812 					}
21813 				}
21814 
21815 				Atom findBestFormat(Atom[] answer) {
21816 					Atom best = None;
21817 					foreach(option; answer) {
21818 						if(option == format) {
21819 							best = option;
21820 							break;
21821 						}
21822 						/*
21823 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
21824 							best = option;
21825 							break;
21826 						} else if(option == XA_STRING) {
21827 							best = option;
21828 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
21829 							best = option;
21830 						}
21831 						*/
21832 					}
21833 					return best;
21834 				}
21835 			}
21836 
21837 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
21838 
21839 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
21840 
21841 		} else version(Windows) {
21842 
21843 			// clean up like DragLeave
21844 			// pass effect back up
21845 
21846 			FORMATETC t;
21847 			assert(format >= 0 && format <= ushort.max);
21848 			t.cfFormat = cast(ushort) format;
21849 			t.lindex = -1;
21850 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21851 			t.tymed = TYMED.TYMED_HGLOBAL;
21852 
21853 			STGMEDIUM m;
21854 
21855 			if(pDataObj.GetData(&t, &m) != S_OK) {
21856 				// fail
21857 			} else {
21858 				// succeed, take the data and clean up
21859 
21860 				// FIXME: ensure it is legit HGLOBAL
21861 				auto handle = m.hGlobal;
21862 
21863 				if(handle) {
21864 					auto sz = GlobalSize(handle);
21865 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
21866 						scope(exit) GlobalUnlock(handle);
21867 						scope(exit) GlobalFree(handle);
21868 
21869 						auto data = ptr[0 .. sz];
21870 
21871 						dg(data);
21872 					}
21873 				}
21874 			}
21875 		}
21876 	}
21877 
21878 	private:
21879 
21880 	version(X11) {
21881 		SimpleWindow win;
21882 		Window sourceWindow;
21883 		Time dataTimestamp;
21884 
21885 		Atom[] xFormats;
21886 	}
21887 	version(Windows) {
21888 		IDataObject pDataObj;
21889 	}
21890 }
21891 
21892 /++
21893 	A generic helper base class for making a drop handler with a preference list of custom types.
21894 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
21895 	droppers too.
21896 
21897 	It assumes the whole window it used, but you can subclass to change that.
21898 
21899 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21900 
21901 	History:
21902 		Added February 19, 2021
21903 +/
21904 /// Group: drag_and_drop
21905 class GenericDropHandlerBase : DropHandler {
21906 	// no fancy state here so no need to do anything here
21907 	void finish() { }
21908 	void dragLeave() { }
21909 
21910 	private DragAndDropAction acceptedAction;
21911 	private DraggableData.FormatId acceptedFormat;
21912 	private void delegate(scope ubyte[]) acceptedHandler;
21913 
21914 	struct FormatHandler {
21915 		DraggableData.FormatId format;
21916 		void delegate(scope ubyte[]) handler;
21917 	}
21918 
21919 	protected abstract FormatHandler[] formatHandlers();
21920 
21921 	DragAndDropAction dragEnter(DropPackage* pkg) {
21922 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
21923 		foreach(fmt; formatHandlers())
21924 		foreach(f; pkg.availableFormats())
21925 			if(f == fmt.format) {
21926 				acceptedFormat = f;
21927 				acceptedHandler = fmt.handler;
21928 				return acceptedAction = DragAndDropAction.copy;
21929 			}
21930 		return acceptedAction = DragAndDropAction.none;
21931 	}
21932 	DropParameters dragOver(Point pt) {
21933 		return DropParameters(acceptedAction);
21934 	}
21935 
21936 	void drop(scope DropPackage* dropPackage) {
21937 		if(!acceptedFormat || acceptedHandler is null) {
21938 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
21939 			return; // prolly shouldn't happen anyway...
21940 		}
21941 
21942 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
21943 	}
21944 }
21945 
21946 /++
21947 	A simple handler for making your window accept drops of plain text.
21948 
21949 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21950 
21951 	History:
21952 		Added February 22, 2021
21953 +/
21954 /// Group: drag_and_drop
21955 class TextDropHandler : GenericDropHandlerBase {
21956 	private void delegate(in char[] text) dg;
21957 
21958 	/++
21959 
21960 	+/
21961 	this(void delegate(in char[] text) dg) {
21962 		this.dg = dg;
21963 	}
21964 
21965 	protected override FormatHandler[] formatHandlers() {
21966 		version(X11)
21967 			return [
21968 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21969 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21970 			];
21971 		else version(Windows)
21972 			return [
21973 				FormatHandler(CF_UNICODETEXT, &translator),
21974 			];
21975 		else throw new NotYetImplementedException();
21976 	}
21977 
21978 	private void translator(scope ubyte[] data) {
21979 		version(X11)
21980 			dg(cast(char[]) data);
21981 		else version(Windows)
21982 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21983 	}
21984 }
21985 
21986 /++
21987 	A simple handler for making your window accept drops of files, issued to you as file names.
21988 
21989 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21990 
21991 	History:
21992 		Added February 22, 2021
21993 +/
21994 /// Group: drag_and_drop
21995 
21996 class FilesDropHandler : GenericDropHandlerBase {
21997 	private void delegate(in char[][]) dg;
21998 
21999 	/++
22000 
22001 	+/
22002 	this(void delegate(in char[][] fileNames) dg) {
22003 		this.dg = dg;
22004 	}
22005 
22006 	protected override FormatHandler[] formatHandlers() {
22007 		version(X11)
22008 			return [
22009 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
22010 			];
22011 		else version(Windows)
22012 			return [
22013 				FormatHandler(CF_HDROP, &translator),
22014 			];
22015 		else throw new NotYetImplementedException();
22016 	}
22017 
22018 	private void translator(scope ubyte[] data) @system {
22019 		version(X11) {
22020 			char[] listString = cast(char[]) data;
22021 			char[][16] buffer;
22022 			int count;
22023 			char[][] result = buffer[];
22024 
22025 			void commit(char[] s) {
22026 				if(count == result.length)
22027 					result.length += 16;
22028 				if(s.length > 7 && s[0 ..7] == "file://")
22029 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
22030 				result[count++] = s;
22031 			}
22032 
22033 			size_t last;
22034 			foreach(idx, char c; listString) {
22035 				if(c == '\n') {
22036 					commit(listString[last .. idx - 1]); // a \r
22037 					last = idx + 1; // a \n
22038 				}
22039 			}
22040 
22041 			if(last < listString.length) {
22042 				commit(listString[last .. $]);
22043 			}
22044 
22045 			// FIXME: they are uris now, should I translate it to local file names?
22046 			// of course the host name is supposed to be there cuz of X rokking...
22047 
22048 			dg(result[0 .. count]);
22049 		} else version(Windows) {
22050 
22051 			static struct DROPFILES {
22052 				DWORD pFiles;
22053 				POINT pt;
22054 				BOOL  fNC;
22055 				BOOL  fWide;
22056 			}
22057 
22058 
22059 			const(char)[][16] buffer;
22060 			int count;
22061 			const(char)[][] result = buffer[];
22062 			size_t last;
22063 
22064 			void commitA(in char[] stuff) {
22065 				if(count == result.length)
22066 					result.length += 16;
22067 				result[count++] = stuff;
22068 			}
22069 
22070 			void commitW(in wchar[] stuff) {
22071 				commitA(makeUtf8StringFromWindowsString(stuff));
22072 			}
22073 
22074 			void magic(T)(T chars) {
22075 				size_t idx;
22076 				while(chars[idx]) {
22077 					last = idx;
22078 					while(chars[idx]) {
22079 						idx++;
22080 					}
22081 					static if(is(T == char*))
22082 						commitA(chars[last .. idx]);
22083 					else
22084 						commitW(chars[last .. idx]);
22085 					idx++;
22086 				}
22087 			}
22088 
22089 			auto df = cast(DROPFILES*) data.ptr;
22090 			if(df.fWide) {
22091 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
22092 				magic(chars);
22093 			} else {
22094 				char* chars = cast(char*) (data.ptr + df.pFiles);
22095 				magic(chars);
22096 			}
22097 			dg(result[0 .. count]);
22098 		}
22099 		else throw new NotYetImplementedException();
22100 	}
22101 }
22102 
22103 /++
22104 	Interface to describe data being dragged. See also [draggable] helper function.
22105 
22106 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22107 
22108 	History:
22109 		Added February 19, 2021
22110 +/
22111 interface DraggableData {
22112 	version(X11)
22113 		alias FormatId = Atom;
22114 	else
22115 		alias FormatId = uint;
22116 	/++
22117 		Gets the platform-specific FormatId associated with the given named format.
22118 
22119 		This may be a MIME type, but may also be other various strings defined by the
22120 		programs you want to interoperate with.
22121 
22122 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
22123 		and convert it to some particular type for you.
22124 	+/
22125 	static FormatId getFormatId(string name)() {
22126 		version(X11)
22127 			return GetAtom!name(XDisplayConnection.get);
22128 		else version(Windows) {
22129 			static UINT cache;
22130 			if(!cache)
22131 				cache = RegisterClipboardFormatA(name);
22132 			return cache;
22133 		} else
22134 			throw new NotYetImplementedException();
22135 	}
22136 
22137 	/++
22138 		Looks up a string to represent the name for the given format, if there is one.
22139 
22140 		You should avoid using this function because it is slow. It is provided more for
22141 		debugging than for primary use.
22142 	+/
22143 	static string getFormatName(FormatId format) {
22144 		version(X11) {
22145 			if(format == 0)
22146 				return "None";
22147 			else
22148 				return getAtomName(format, XDisplayConnection.get);
22149 		} else version(Windows) {
22150 			switch(format) {
22151 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
22152 				case CF_DIBV5: return "CF_DIBV5";
22153 				case CF_RIFF: return "CF_RIFF";
22154 				case CF_WAVE: return "CF_WAVE";
22155 				case CF_HDROP: return "CF_HDROP";
22156 				default:
22157 					char[1024] name;
22158 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
22159 					return name[0 .. count].idup;
22160 			}
22161 		} else throw new NotYetImplementedException();
22162 	}
22163 
22164 	FormatId[] availableFormats();
22165 	// Return the slice of data you filled, empty slice if done.
22166 	// this is to support the incremental thing
22167 	ubyte[] getData(FormatId format, return scope ubyte[] data);
22168 
22169 	size_t dataLength(FormatId format);
22170 }
22171 
22172 /++
22173 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22174 
22175 	History:
22176 		Added February 19, 2021
22177 +/
22178 DraggableData draggable(string s) {
22179 	version(X11)
22180 	return new class X11SetSelectionHandler_Text, DraggableData {
22181 		this() {
22182 			super(s);
22183 		}
22184 
22185 		override FormatId[] availableFormats() {
22186 			return X11SetSelectionHandler_Text.availableFormats();
22187 		}
22188 
22189 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
22190 			return X11SetSelectionHandler_Text.getData(format, data);
22191 		}
22192 
22193 		size_t dataLength(FormatId format) {
22194 			return s.length;
22195 		}
22196 	};
22197 	else version(Windows)
22198 	return new class DraggableData {
22199 		FormatId[] availableFormats() {
22200 			return [CF_UNICODETEXT];
22201 		}
22202 
22203 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
22204 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
22205 		}
22206 
22207 		size_t dataLength(FormatId format) {
22208 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
22209 		}
22210 	};
22211 	else
22212 	throw new NotYetImplementedException();
22213 }
22214 
22215 /++
22216 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22217 
22218 	History:
22219 		Added February 19, 2021
22220 +/
22221 /// Group: drag_and_drop
22222 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
22223 in {
22224 	assert(window !is null);
22225 	assert(handler !is null);
22226 }
22227 do
22228 {
22229 	version(X11) {
22230 		auto sh = cast(X11SetSelectionHandler) handler;
22231 		if(sh is null) {
22232 			// gotta make my own adapter.
22233 			sh = new class X11SetSelectionHandler {
22234 				mixin X11SetSelectionHandler_Basics;
22235 
22236 				Atom[] availableFormats() { return handler.availableFormats(); }
22237 				ubyte[] getData(Atom format, return scope ubyte[] data) {
22238 					return handler.getData(format, data);
22239 				}
22240 
22241 				// since the drop selection is only ever used once it isn't important
22242 				// to reset it.
22243 				void done() {}
22244 			};
22245 		}
22246 		return doDragDropX11(window, sh, action);
22247 	} else version(Windows) {
22248 		return doDragDropWindows(window, handler, action);
22249 	} else throw new NotYetImplementedException();
22250 }
22251 
22252 version(Windows)
22253 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
22254 	IDataObject obj = new class IDataObject {
22255 		ULONG refCount;
22256 		ULONG AddRef() {
22257 			return ++refCount;
22258 		}
22259 		ULONG Release() {
22260 			return --refCount;
22261 		}
22262 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22263 			if (IID_IUnknown == *riid) {
22264 				*ppv = cast(void*) cast(IUnknown) this;
22265 			}
22266 			else if (IID_IDataObject == *riid) {
22267 				*ppv = cast(void*) cast(IDataObject) this;
22268 			}
22269 			else {
22270 				*ppv = null;
22271 				return E_NOINTERFACE;
22272 			}
22273 
22274 			AddRef();
22275 			return NOERROR;
22276 		}
22277 
22278 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
22279 			//  writeln("Advise");
22280 			return E_NOTIMPL;
22281 		}
22282 		HRESULT DUnadvise(DWORD dwConnection) {
22283 			return E_NOTIMPL;
22284 		}
22285 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
22286 			//  writeln("EnumDAdvise");
22287 			return OLE_E_ADVISENOTSUPPORTED;
22288 		}
22289 		// tell what formats it supports
22290 
22291 		FORMATETC[] types;
22292 		this() {
22293 			FORMATETC t;
22294 			foreach(ty; handler.availableFormats()) {
22295 				assert(ty <= ushort.max && ty >= 0);
22296 				t.cfFormat = cast(ushort) ty;
22297 				t.lindex = -1;
22298 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22299 				t.tymed = TYMED.TYMED_HGLOBAL;
22300 			}
22301 			types ~= t;
22302 		}
22303 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
22304 			if(dwDirection == DATADIR.DATADIR_GET) {
22305 				*ppenumFormatEtc = new class IEnumFORMATETC {
22306 					ULONG refCount;
22307 					ULONG AddRef() {
22308 						return ++refCount;
22309 					}
22310 					ULONG Release() {
22311 						return --refCount;
22312 					}
22313 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22314 						if (IID_IUnknown == *riid) {
22315 							*ppv = cast(void*) cast(IUnknown) this;
22316 						}
22317 						else if (IID_IEnumFORMATETC == *riid) {
22318 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
22319 						}
22320 						else {
22321 							*ppv = null;
22322 							return E_NOINTERFACE;
22323 						}
22324 
22325 						AddRef();
22326 						return NOERROR;
22327 					}
22328 
22329 
22330 					int pos;
22331 					this() {
22332 						pos = 0;
22333 					}
22334 
22335 					HRESULT Clone(IEnumFORMATETC* ppenum) {
22336 						// writeln("clone");
22337 						return E_NOTIMPL; // FIXME
22338 					}
22339 
22340 					// Caller is responsible for freeing memory
22341 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
22342 						// fetched may be null if celt is one
22343 						if(celt != 1)
22344 							return E_NOTIMPL; // FIXME
22345 
22346 						if(celt + pos > types.length)
22347 							return S_FALSE;
22348 
22349 						*rgelt = types[pos++];
22350 
22351 						if(pceltFetched !is null)
22352 							*pceltFetched = 1;
22353 
22354 						// writeln("ok celt ", celt);
22355 						return S_OK;
22356 					}
22357 
22358 					HRESULT Reset() {
22359 						pos = 0;
22360 						return S_OK;
22361 					}
22362 
22363 					HRESULT Skip(ULONG celt) {
22364 						if(celt + pos <= types.length) {
22365 							pos += celt;
22366 							return S_OK;
22367 						}
22368 						return S_FALSE;
22369 					}
22370 				};
22371 
22372 				return S_OK;
22373 			} else
22374 				return E_NOTIMPL;
22375 		}
22376 		// given a format, return the format you'd prefer to use cuz it is identical
22377 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
22378 			// FIXME: prolly could be better but meh
22379 			// writeln("gcf: ", *pformatectIn);
22380 			*pformatetcOut = *pformatectIn;
22381 			return S_OK;
22382 		}
22383 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22384 			foreach(ty; types) {
22385 				if(ty == *pformatetcIn) {
22386 					auto format = ty.cfFormat;
22387 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
22388 					STGMEDIUM medium;
22389 					medium.tymed = TYMED.TYMED_HGLOBAL;
22390 
22391 					auto sz = handler.dataLength(format);
22392 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
22393 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
22394 					if(auto data = cast(wchar*) GlobalLock(handle)) {
22395 						auto slice = data[0 .. sz];
22396 						scope(exit)
22397 							GlobalUnlock(handle);
22398 
22399 						handler.getData(format, cast(ubyte[]) slice[]);
22400 					}
22401 
22402 
22403 					medium.hGlobal = handle; // FIXME
22404 					*pmedium = medium;
22405 					return S_OK;
22406 				}
22407 			}
22408 			return DV_E_FORMATETC;
22409 		}
22410 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22411 			// writeln("GDH: ", *pformatetcIn);
22412 			return E_NOTIMPL; // FIXME
22413 		}
22414 		HRESULT QueryGetData(FORMATETC* pformatetc) {
22415 			auto search = *pformatetc;
22416 			search.tymed &= TYMED.TYMED_HGLOBAL;
22417 			foreach(ty; types)
22418 				if(ty == search) {
22419 					// writeln("QueryGetData ", search, " ", types[0]);
22420 					return S_OK;
22421 				}
22422 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
22423 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
22424 			}
22425 			return S_FALSE;
22426 		}
22427 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
22428 			//  writeln("SetData: ");
22429 			return E_NOTIMPL;
22430 		}
22431 	};
22432 
22433 
22434 	IDropSource src = new class IDropSource {
22435 		ULONG refCount;
22436 		ULONG AddRef() {
22437 			return ++refCount;
22438 		}
22439 		ULONG Release() {
22440 			return --refCount;
22441 		}
22442 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22443 			if (IID_IUnknown == *riid) {
22444 				*ppv = cast(void*) cast(IUnknown) this;
22445 			}
22446 			else if (IID_IDropSource == *riid) {
22447 				*ppv = cast(void*) cast(IDropSource) this;
22448 			}
22449 			else {
22450 				*ppv = null;
22451 				return E_NOINTERFACE;
22452 			}
22453 
22454 			AddRef();
22455 			return NOERROR;
22456 		}
22457 
22458 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
22459 			if(fEscapePressed)
22460 				return DRAGDROP_S_CANCEL;
22461 			if(!(grfKeyState & MK_LBUTTON))
22462 				return DRAGDROP_S_DROP;
22463 			return S_OK;
22464 		}
22465 
22466 		int GiveFeedback(uint dwEffect) {
22467 			return DRAGDROP_S_USEDEFAULTCURSORS;
22468 		}
22469 	};
22470 
22471 	DWORD effect;
22472 
22473 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
22474 
22475 	DROPEFFECT de = win32DragAndDropAction(action);
22476 
22477 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
22478 	// but still prolly a FIXME
22479 
22480 	auto ret = DoDragDrop(obj, src, de, &effect);
22481 	/+
22482 	if(ret == DRAGDROP_S_DROP)
22483 		writeln("drop ", effect);
22484 	else if(ret == DRAGDROP_S_CANCEL)
22485 		writeln("cancel");
22486 	else if(ret == S_OK)
22487 		writeln("ok");
22488 	else writeln(ret);
22489 	+/
22490 
22491 	return ret;
22492 }
22493 
22494 version(Windows)
22495 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
22496 	DROPEFFECT de;
22497 
22498 	with(DragAndDropAction)
22499 	with(DROPEFFECT)
22500 	final switch(action) {
22501 		case none: de = DROPEFFECT_NONE; break;
22502 		case copy: de = DROPEFFECT_COPY; break;
22503 		case move: de = DROPEFFECT_MOVE; break;
22504 		case link: de = DROPEFFECT_LINK; break;
22505 		case ask: throw new Exception("ask not implemented yet");
22506 		case custom: throw new Exception("custom not implemented yet");
22507 	}
22508 
22509 	return de;
22510 }
22511 
22512 
22513 /++
22514 	History:
22515 		Added February 19, 2021
22516 +/
22517 /// Group: drag_and_drop
22518 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
22519 	version(X11) {
22520 		auto display = XDisplayConnection.get;
22521 
22522 		Atom atom = 5; // right???
22523 
22524 		XChangeProperty(
22525 			display,
22526 			window.impl.window,
22527 			GetAtom!"XdndAware"(display),
22528 			XA_ATOM,
22529 			32 /* bits */,
22530 			PropModeReplace,
22531 			&atom,
22532 			1);
22533 
22534 		window.dropHandler = handler;
22535 	} else version(Windows) {
22536 
22537 		initDnd();
22538 
22539 		auto dropTarget = new class (handler) IDropTarget {
22540 			DropHandler handler;
22541 			this(DropHandler handler) {
22542 				this.handler = handler;
22543 			}
22544 			ULONG refCount;
22545 			ULONG AddRef() {
22546 				return ++refCount;
22547 			}
22548 			ULONG Release() {
22549 				return --refCount;
22550 			}
22551 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22552 				if (IID_IUnknown == *riid) {
22553 					*ppv = cast(void*) cast(IUnknown) this;
22554 				}
22555 				else if (IID_IDropTarget == *riid) {
22556 					*ppv = cast(void*) cast(IDropTarget) this;
22557 				}
22558 				else {
22559 					*ppv = null;
22560 					return E_NOINTERFACE;
22561 				}
22562 
22563 				AddRef();
22564 				return NOERROR;
22565 			}
22566 
22567 
22568 			// ///////////////////
22569 
22570 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22571 				DropPackage dropPackage = DropPackage(pDataObj);
22572 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
22573 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
22574 			}
22575 
22576 			HRESULT DragLeave() {
22577 				handler.dragLeave();
22578 				// release the IDataObject if needed
22579 				return S_OK;
22580 			}
22581 
22582 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22583 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
22584 
22585 				*pdwEffect = win32DragAndDropAction(res.action);
22586 				// same as DragEnter basically
22587 				return S_OK;
22588 			}
22589 
22590 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22591 				DropPackage pkg = DropPackage(pDataObj);
22592 				handler.drop(&pkg);
22593 
22594 				return S_OK;
22595 			}
22596 		};
22597 		// Windows can hold on to the handler and try to call it
22598 		// during which time the GC can't see it. so important to
22599 		// manually manage this. At some point i'll FIXME and make
22600 		// all my com instances manually managed since they supposed
22601 		// to respect the refcount.
22602 		import core.memory;
22603 		GC.addRoot(cast(void*) dropTarget);
22604 
22605 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
22606 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
22607 
22608 		window.dropHandler = handler;
22609 	} else throw new NotYetImplementedException();
22610 }
22611 
22612 
22613 
22614 static if(UsingSimpledisplayX11) {
22615 
22616 enum _NET_WM_STATE_ADD = 1;
22617 enum _NET_WM_STATE_REMOVE = 0;
22618 enum _NET_WM_STATE_TOGGLE = 2;
22619 
22620 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
22621 void demandAttention(SimpleWindow window, bool needs = true) {
22622 	demandAttention(window.impl.window, needs);
22623 }
22624 
22625 /// ditto
22626 void demandAttention(Window window, bool needs = true) {
22627 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
22628 }
22629 
22630 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
22631 	auto display = XDisplayConnection.get();
22632 	if(atom == None)
22633 		return; // non-failure error
22634 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
22635 
22636 	XClientMessageEvent xclient;
22637 
22638 	xclient.type = EventType.ClientMessage;
22639 	xclient.window = window;
22640 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
22641 	xclient.format = 32;
22642 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
22643 	xclient.data.l[1] = atom;
22644 	xclient.data.l[2] = atom2;
22645 	xclient.data.l[3] = 1;
22646 	// [3] == source. 0 == unknown, 1 == app, 2 == else
22647 
22648 	XSendEvent(
22649 		display,
22650 		RootWindow(display, DefaultScreen(display)),
22651 		false,
22652 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
22653 		cast(XEvent*) &xclient
22654 	);
22655 
22656 	/+
22657 	XChangeProperty(
22658 		display,
22659 		window.impl.window,
22660 		GetAtom!"_NET_WM_STATE"(display),
22661 		XA_ATOM,
22662 		32 /* bits */,
22663 		PropModeAppend,
22664 		&atom,
22665 		1);
22666 	+/
22667 }
22668 
22669 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
22670 	Atom actionAtom;
22671 	with(DragAndDropAction)
22672 	final switch(action) {
22673 		case none: actionAtom = None; break;
22674 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
22675 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
22676 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
22677 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
22678 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
22679 	}
22680 
22681 	return actionAtom;
22682 }
22683 
22684 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
22685 	// FIXME: I need to show user feedback somehow.
22686 	auto display = XDisplayConnection.get;
22687 
22688 	auto actionAtom = dndActionAtom(display, action);
22689 	assert(actionAtom, "Don't use action none to accept a drop");
22690 
22691 	setX11Selection!"XdndSelection"(window, handler, null);
22692 
22693 	auto oldKeyHandler = window.handleKeyEvent;
22694 	scope(exit) window.handleKeyEvent = oldKeyHandler;
22695 
22696 	auto oldCharHandler = window.handleCharEvent;
22697 	scope(exit) window.handleCharEvent = oldCharHandler;
22698 
22699 	auto oldMouseHandler = window.handleMouseEvent;
22700 	scope(exit) window.handleMouseEvent = oldMouseHandler;
22701 
22702 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
22703 
22704 	import core.sys.posix.sys.time;
22705 	timeval tv;
22706 	gettimeofday(&tv, null);
22707 
22708 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
22709 
22710 	Time lastMouseTimestamp;
22711 
22712 	bool dnding = true;
22713 	Window lastIn = None;
22714 
22715 	void leave() {
22716 		if(lastIn == None)
22717 			return;
22718 
22719 		XEvent ev;
22720 		ev.xclient.type = EventType.ClientMessage;
22721 		ev.xclient.window = lastIn;
22722 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
22723 		ev.xclient.format = 32;
22724 		ev.xclient.data.l[0] = window.impl.window;
22725 
22726 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22727 		XFlush(display);
22728 
22729 		lastIn = None;
22730 	}
22731 
22732 	void enter(Window w) {
22733 		assert(lastIn == None);
22734 
22735 		lastIn = w;
22736 
22737 		XEvent ev;
22738 		ev.xclient.type = EventType.ClientMessage;
22739 		ev.xclient.window = lastIn;
22740 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
22741 		ev.xclient.format = 32;
22742 		ev.xclient.data.l[0] = window.impl.window;
22743 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
22744 
22745 		auto types = handler.availableFormats();
22746 		assert(types.length > 0);
22747 
22748 		ev.xclient.data.l[2] = types[0];
22749 		if(types.length > 1)
22750 			ev.xclient.data.l[3] = types[1];
22751 		if(types.length > 2)
22752 			ev.xclient.data.l[4] = types[2];
22753 
22754 		// FIXME: other types?!?!? and make sure we skip TARGETS
22755 
22756 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22757 		XFlush(display);
22758 	}
22759 
22760 	void position(int rootX, int rootY) {
22761 		assert(lastIn != None);
22762 
22763 		XEvent ev;
22764 		ev.xclient.type = EventType.ClientMessage;
22765 		ev.xclient.window = lastIn;
22766 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
22767 		ev.xclient.format = 32;
22768 		ev.xclient.data.l[0] = window.impl.window;
22769 		ev.xclient.data.l[1] = 0; // reserved
22770 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
22771 		ev.xclient.data.l[3] = dataTimestamp;
22772 		ev.xclient.data.l[4] = actionAtom;
22773 
22774 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22775 		XFlush(display);
22776 
22777 	}
22778 
22779 	void drop() {
22780 		XEvent ev;
22781 		ev.xclient.type = EventType.ClientMessage;
22782 		ev.xclient.window = lastIn;
22783 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
22784 		ev.xclient.format = 32;
22785 		ev.xclient.data.l[0] = window.impl.window;
22786 		ev.xclient.data.l[1] = 0; // reserved
22787 		ev.xclient.data.l[2] = dataTimestamp;
22788 
22789 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22790 		XFlush(display);
22791 
22792 		lastIn = None;
22793 		dnding = false;
22794 	}
22795 
22796 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
22797 	// but idk if i should...
22798 
22799 	window.setEventHandlers(
22800 		delegate(KeyEvent ev) {
22801 			if(ev.pressed == true && ev.key == Key.Escape) {
22802 				// cancel
22803 				dnding = false;
22804 			}
22805 		},
22806 		delegate(MouseEvent ev) {
22807 			if(ev.timestamp < lastMouseTimestamp)
22808 				return;
22809 
22810 			lastMouseTimestamp = ev.timestamp;
22811 
22812 			if(ev.type == MouseEventType.motion) {
22813 				auto display = XDisplayConnection.get;
22814 				auto root = RootWindow(display, DefaultScreen(display));
22815 
22816 				Window topWindow;
22817 				int rootX, rootY;
22818 
22819 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
22820 
22821 				if(topWindow == None)
22822 					return;
22823 
22824 				top:
22825 				if(auto result = topWindow in eligibility) {
22826 					auto dropWindow = *result;
22827 					if(dropWindow == None) {
22828 						leave();
22829 						return;
22830 					}
22831 
22832 					if(dropWindow != lastIn) {
22833 						leave();
22834 						enter(dropWindow);
22835 						position(rootX, rootY);
22836 					} else {
22837 						position(rootX, rootY);
22838 					}
22839 				} else {
22840 					// determine eligibility
22841 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
22842 					if(data.length == 1) {
22843 						// in case there is no WM or it isn't reparenting
22844 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
22845 					} else {
22846 
22847 						Window tryScanChildren(Window search, int maxRecurse) {
22848 							// could be reparenting window manager, so gotta check the next few children too
22849 							Window child;
22850 							int x;
22851 							int y;
22852 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
22853 
22854 							if(child == None)
22855 								return None;
22856 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
22857 							if(data.length == 1) {
22858 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
22859 							} else {
22860 								if(maxRecurse)
22861 									return tryScanChildren(child, maxRecurse - 1);
22862 								else
22863 									return None;
22864 							}
22865 
22866 						}
22867 
22868 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
22869 						auto topResult = tryScanChildren(topWindow, 3);
22870 						// it is easy to have a false negative due to the mouse going over a WM
22871 						// child window like the close button if separate from the frame... so I
22872 						// can't really cache negatives, :(
22873 						if(topResult != None) {
22874 							eligibility[topWindow] = topResult;
22875 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
22876 						}
22877 					}
22878 
22879 				}
22880 
22881 			} else if(ev.type == MouseEventType.buttonReleased) {
22882 				drop();
22883 				dnding = false;
22884 			}
22885 		}
22886 	);
22887 
22888 	window.grabInput();
22889 	scope(exit)
22890 		window.releaseInputGrab();
22891 
22892 
22893 	EventLoop.get.run(() => dnding);
22894 
22895 	return 0;
22896 }
22897 
22898 /// X-specific
22899 TrueColorImage getWindowNetWmIcon(Window window) {
22900 	try {
22901 		auto display = XDisplayConnection.get;
22902 
22903 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
22904 
22905 		if (data.length > arch_ulong.sizeof * 2) {
22906 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
22907 			// these are an array of rgba images that we have to convert into pixmaps ourself
22908 
22909 			int width = cast(int) meta[0];
22910 			int height = cast(int) meta[1];
22911 
22912 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
22913 
22914 			static if(arch_ulong.sizeof == 4) {
22915 				bytes = bytes[0 .. width * height * 4];
22916 				alias imageData = bytes;
22917 			} else static if(arch_ulong.sizeof == 8) {
22918 				bytes = bytes[0 .. width * height * 8];
22919 				auto imageData = new ubyte[](4 * width * height);
22920 			} else static assert(0);
22921 
22922 
22923 
22924 			// this returns ARGB. Remember it is little-endian so
22925 			//                                         we have BGRA
22926 			// our thing uses RGBA, which in little endian, is ABGR
22927 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
22928 				auto r = bytes[idx + 2];
22929 				auto g = bytes[idx + 1];
22930 				auto b = bytes[idx + 0];
22931 				auto a = bytes[idx + 3];
22932 
22933 				imageData[idx2 + 0] = r;
22934 				imageData[idx2 + 1] = g;
22935 				imageData[idx2 + 2] = b;
22936 				imageData[idx2 + 3] = a;
22937 			}
22938 
22939 			return new TrueColorImage(width, height, imageData);
22940 		}
22941 
22942 		return null;
22943 	} catch(Exception e) {
22944 		return null;
22945 	}
22946 }
22947 
22948 } /* UsingSimpledisplayX11 */
22949 
22950 
22951 void loadBinNameToWindowClassName () {
22952 	import core.stdc.stdlib : realloc;
22953 	version(linux) {
22954 		// args[0] MAY be empty, so we'll just use this
22955 		import core.sys.posix.unistd : readlink;
22956 		char[1024] ebuf = void; // 1KB should be enough for everyone!
22957 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22958 		if (len < 1) return;
22959 	} else /*version(Windows)*/ {
22960 		import core.runtime : Runtime;
22961 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22962 		auto ebuf = Runtime.args[0];
22963 		auto len = ebuf.length;
22964 	}
22965 	auto pos = len;
22966 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22967 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22968 	if (sdpyWindowClassStr is null) return; // oops
22969 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22970 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22971 }
22972 
22973 /++
22974 	An interface representing a font that is drawn with custom facilities.
22975 
22976 	You might want [OperatingSystemFont] instead, which represents
22977 	a font loaded and drawn by functions native to the operating system.
22978 
22979 	WARNING: I might still change this.
22980 +/
22981 interface DrawableFont : MeasurableFont {
22982 	/++
22983 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22984 
22985 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22986 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22987 		fill color, but that's up to the implementation.
22988 	+/
22989 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22990 
22991 	/++
22992 		Requests that the given string is added to the image cache. You should only do this rarely, but
22993 		if you have a string that you know will be used over and over again, adding it to a cache can
22994 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22995 		to implement this as a do-nothing method).
22996 	+/
22997 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22998 }
22999 
23000 /++
23001 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
23002 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
23003 
23004 	You should also consider [OperatingSystemFont], which loads and draws a font with
23005 	facilities native to the user's operating system. You might also consider
23006 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
23007 	of game, as they have their own ways to draw text too.
23008 
23009 	Be warned: this can be slow, especially on remote connections to the X server, since
23010 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
23011 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
23012 	experiment in your specific case.
23013 
23014 	Please note that the return type of [DrawableFont] also includes an implementation of
23015 	[MeasurableFont].
23016 +/
23017 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
23018 	import arsd.ttf;
23019 	static class ArsdTtfFont : DrawableFont {
23020 		TtfFont font;
23021 		int size;
23022 		this(in ubyte[] data, int size) {
23023 			font = TtfFont(data);
23024 			this.size = size;
23025 
23026 
23027 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
23028 			int ascent_, descent_, line_gap;
23029 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
23030 
23031 			int advance, lsb;
23032 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
23033 			xWidth = cast(int) (advance * scale);
23034 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
23035 			MWidth = cast(int) (advance * scale);
23036 		}
23037 
23038 		private int ascent_;
23039 		private int descent_;
23040 		private int xWidth;
23041 		private int MWidth;
23042 
23043 		bool isMonospace() {
23044 			return xWidth == MWidth;
23045 		}
23046 		int averageWidth() {
23047 			return xWidth;
23048 		}
23049 		int height() {
23050 			return size;
23051 		}
23052 		int ascent() {
23053 			return ascent_;
23054 		}
23055 		int descent() {
23056 			return descent_;
23057 		}
23058 
23059 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
23060 			int width, height;
23061 			font.getStringSize(s, size, width, height);
23062 			return width;
23063 		}
23064 
23065 
23066 
23067 		Sprite[string] cache;
23068 
23069 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
23070 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
23071 			cache[text] = sprite;
23072 		}
23073 
23074 		Image stringToImage(Color fg, Color bg, in char[] text) {
23075 			int width, height;
23076 			auto data = font.renderString(text, size, width, height);
23077 			auto image = new TrueColorImage(width, height);
23078 			int pos = 0;
23079 			foreach(y; 0 .. height)
23080 			foreach(x; 0 .. width) {
23081 				fg.a = data[0];
23082 				bg.a = 255;
23083 				auto color = alphaBlend(fg, bg);
23084 				image.imageData.bytes[pos++] = color.r;
23085 				image.imageData.bytes[pos++] = color.g;
23086 				image.imageData.bytes[pos++] = color.b;
23087 				image.imageData.bytes[pos++] = data[0];
23088 				data = data[1 .. $];
23089 			}
23090 			assert(data.length == 0);
23091 
23092 			return Image.fromMemoryImage(image);
23093 		}
23094 
23095 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
23096 			Sprite sprite = (text in cache) ? *(text in cache) : null;
23097 
23098 			auto fg = painter.impl._outlineColor;
23099 			auto bg = painter.impl._fillColor;
23100 
23101 			if(sprite !is null) {
23102 				auto w = cast(SimpleWindow) painter.window;
23103 				assert(w !is null);
23104 
23105 				sprite.drawAt(painter, upperLeft);
23106 			} else {
23107 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
23108 			}
23109 		}
23110 	}
23111 
23112 	return new ArsdTtfFont(data, size);
23113 }
23114 
23115 class NotYetImplementedException : Exception {
23116 	this(string file = __FILE__, size_t line = __LINE__) {
23117 		super("Not yet implemented", file, line);
23118 	}
23119 }
23120 
23121 ///
23122 __gshared bool librariesSuccessfullyLoaded = true;
23123 ///
23124 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
23125 
23126 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
23127 	// mixin(staticForeachReplacement!Iface);
23128 	static foreach(name; __traits(derivedMembers, Iface))
23129 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23130 
23131 	void loadDynamicLibrary() @nogc {
23132 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23133 	}
23134 
23135 	void loadDynamicLibraryForReal() {
23136 		foreach(name; __traits(derivedMembers, Iface)) {
23137 			mixin("alias tmp = " ~ name ~ ";");
23138 			tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
23139 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
23140 		}
23141 	}
23142 }
23143 
23144 /+
23145 private const(char)[] staticForeachReplacement(Iface)() pure {
23146 /*
23147 	// just this for gdc 9....
23148 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
23149 
23150 	static foreach(name; __traits(derivedMembers, Iface))
23151 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23152 */
23153 
23154 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
23155 	size_t pos;
23156 
23157 	void append(in char[] what) {
23158 		if(pos + what.length > code.length)
23159 			code.length = (code.length * 3) / 2;
23160 		code[pos .. pos + what.length] = what[];
23161 		pos += what.length;
23162 	}
23163 
23164 	foreach(name; __traits(derivedMembers, Iface)) {
23165 		append(`__gshared typeof(&__traits(getMember, Iface, "`);
23166 		append(name);
23167 		append(`")) `);
23168 		append(name);
23169 		append(";");
23170 	}
23171 
23172 	return code[0 .. pos];
23173 }
23174 +/
23175 
23176 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
23177 	//mixin(staticForeachReplacement!Iface);
23178 	static foreach(name; __traits(derivedMembers, Iface))
23179 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23180 
23181 	private __gshared void* libHandle;
23182 	private __gshared bool attempted;
23183 
23184 	void loadDynamicLibrary() @nogc {
23185 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23186 	}
23187 
23188 	bool loadAttempted() {
23189 		return attempted;
23190 	}
23191 	bool loadSuccessful() {
23192 		return libHandle !is null;
23193 	}
23194 
23195 	void loadDynamicLibraryForReal() {
23196 		attempted = true;
23197 		version(Posix) {
23198 			import core.sys.posix.dlfcn;
23199 			version(OSX) {
23200 				version(X11)
23201 					libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
23202 				else
23203 					libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
23204 			} else {
23205 				version(apitrace) {
23206 					if(library == "GL" || library == "GLX") {
23207 						libHandle = dlopen("glxtrace.so", RTLD_NOW);
23208 						if(libHandle is null) {
23209 							assert(false, "Failed to load `glxtrace.so`.");
23210 						}
23211 					}
23212 					else {
23213 						libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23214 					}
23215 				}
23216 				else {
23217 					libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23218 				}
23219 				if(libHandle is null) {
23220 					libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
23221 				}
23222 			}
23223 
23224 			static void* loadsym(void* l, const char* name) {
23225 				import core.stdc.stdlib;
23226 				if(l is null)
23227 					return &abort;
23228 				return dlsym(l, name);
23229 			}
23230 		} else version(Windows) {
23231 			import core.sys.windows.winbase;
23232 			libHandle = LoadLibrary(library ~ ".dll");
23233 			static void* loadsym(void* l, const char* name) {
23234 				import core.stdc.stdlib;
23235 				if(l is null)
23236 					return &abort;
23237 				return GetProcAddress(l, name);
23238 			}
23239 		}
23240 		if(libHandle is null) {
23241 			success = false;
23242 			//throw new Exception("load failure of library " ~ library);
23243 		}
23244 		foreach(name; __traits(derivedMembers, Iface)) {
23245 			mixin("alias tmp = " ~ name ~ ";");
23246 			tmp = cast(typeof(tmp)) loadsym(libHandle, name);
23247 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
23248 		}
23249 	}
23250 
23251 	void unloadDynamicLibrary() {
23252 		version(Posix) {
23253 			import core.sys.posix.dlfcn;
23254 			dlclose(libHandle);
23255 		} else version(Windows) {
23256 			import core.sys.windows.winbase;
23257 			FreeLibrary(libHandle);
23258 		}
23259 		foreach(name; __traits(derivedMembers, Iface))
23260 			mixin(name ~ " = null;");
23261 	}
23262 }
23263 
23264 // version(X11)
23265 /++
23266 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
23267 
23268 	$(WARNING
23269 		This function is exempted from stability guarantees.
23270 	)
23271 +/
23272 float customScalingFactorForMonitor(int monitorNumber) @system {
23273 	import core.stdc.stdlib;
23274 	auto val = getenv("ARSD_SCALING_FACTOR");
23275 
23276 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
23277 	if(val is null)
23278 		return 1.0;
23279 
23280 	char[16] buffer = 0;
23281 	int pos;
23282 
23283 	const(char)* at = val;
23284 
23285 	foreach(item; 0 .. monitorNumber + 1) {
23286 		if(*at == 0)
23287 			break; // reuse the last number when we at the end of the string
23288 		pos = 0;
23289 		while(pos + 1 < buffer.length && *at && *at != ';') {
23290 			buffer[pos++] = *at;
23291 			at++;
23292 		}
23293 		if(*at)
23294 			at++; // skip the semicolon
23295 		buffer[pos] = 0;
23296 	}
23297 
23298 	//sdpyPrintDebugString(buffer[0 .. pos]);
23299 
23300 	import core.stdc.math;
23301 	auto f = atof(buffer.ptr);
23302 
23303 	if(f <= 0.0 || isnan(f) || isinf(f))
23304 		return 1.0;
23305 
23306 	return f;
23307 }
23308 
23309 void guiAbortProcess(string msg) {
23310 	import core.stdc.stdlib;
23311 	version(Windows) {
23312 		WCharzBuffer t = WCharzBuffer(msg);
23313 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
23314 	} else {
23315 		import core.stdc.stdio;
23316 		fwrite(msg.ptr, 1, msg.length, stderr);
23317 		msg = "\n";
23318 		fwrite(msg.ptr, 1, msg.length, stderr);
23319 		fflush(stderr);
23320 	}
23321 
23322 	abort();
23323 }
23324 
23325 private int minInternal(int a, int b) {
23326 	return (a < b) ? a : b;
23327 }
23328 
23329 private alias scriptable = arsd_jsvar_compatible;