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 		module opengl3test;
371 		import arsd.simpledisplay;
372 
373 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
374 
375 		void main() {
376 			// First thing we do, before creating the window, is declare what version we want.
377 			setOpenGLContextVersion(3, 3);
378 			// turning off legacy compat is required to use version 3.3 and newer
379 			openGLContextCompatible = false;
380 
381 			uint VAO;
382 			OpenGlShader shader;
383 
384 			// then we can create the window.
385 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
386 
387 			// additional setup needs to be done when it is visible, simpledisplay offers a property
388 			// for exactly that:
389 			window.visibleForTheFirstTime = delegate() {
390 				// now with the window loaded, we can start loading the modern opengl functions.
391 
392 				// you MUST set the context first.
393 				window.setAsCurrentOpenGlContext;
394 				// then load the remainder of the library
395 				gl3.loadDynamicLibrary();
396 
397 				// now you can create the shaders, etc.
398 				shader = new OpenGlShader(
399 					OpenGlShader.Source(GL_VERTEX_SHADER, `
400 						#version 330 core
401 						layout (location = 0) in vec3 aPos;
402 						void main() {
403 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
404 						}
405 					`),
406 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
407 						#version 330 core
408 						out vec4 FragColor;
409 						uniform vec4 mycolor;
410 						void main() {
411 							FragColor = mycolor;
412 						}
413 					`),
414 				);
415 
416 				// and do whatever other setup you want.
417 
418 				float[] vertices = [
419 					0.5f,  0.5f, 0.0f,  // top right
420 					0.5f, -0.5f, 0.0f,  // bottom right
421 					-0.5f, -0.5f, 0.0f,  // bottom left
422 					-0.5f,  0.5f, 0.0f   // top left
423 				];
424 				uint[] indices = [  // note that we start from 0!
425 					0, 1, 3,  // first Triangle
426 					1, 2, 3   // second Triangle
427 				];
428 				uint VBO, EBO;
429 				glGenVertexArrays(1, &VAO);
430 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
431 				glBindVertexArray(VAO);
432 
433 				glGenBuffers(1, &VBO);
434 				glGenBuffers(1, &EBO);
435 
436 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
437 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
438 
439 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
440 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
441 
442 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
443 				glEnableVertexAttribArray(0);
444 
445 				// the library will set the initial viewport and trigger our first draw,
446 				// so these next two lines are NOT needed. they are just here as comments
447 				// to show what would happen next.
448 
449 				// glViewport(0, 0, window.width, window.height);
450 				// window.redrawOpenGlSceneNow();
451 			};
452 
453 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
454 			// it is our render method.
455 			window.redrawOpenGlScene = delegate() {
456 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
457 				glClear(GL_COLOR_BUFFER_BIT);
458 
459 				glUseProgram(shader.shaderProgram);
460 
461 				// the shader helper class has methods to set uniforms too
462 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
463 
464 				glBindVertexArray(VAO);
465 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
466 			};
467 
468 			window.eventLoop(0);
469 		}
470 		---
471 
472 	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.
473 
474 	$(H3 $(ID vulkan) Vulkan)
475 
476 	See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
477 
478 	https://github.com/adamdruppe/VulkanSdpyDemo
479 
480 	https://github.com/Cy-Tek/VulkanizeD/compare/main...adamdruppe:VulkanizeDSdpy:main
481 
482 	$(H3 $(ID topic-images) Displaying images)
483 		You can also load PNG images using [arsd.png].
484 
485 		---
486 		// dmd example.d simpledisplay.d color.d png.d
487 		import arsd.simpledisplay;
488 		import arsd.png;
489 
490 		void main() {
491 			auto image = Image.fromMemoryImage(readPng("image.png"));
492 			displayImage(image);
493 		}
494 		---
495 
496 		Compile with `dmd example.d simpledisplay.d png.d`.
497 
498 		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.
499 
500 	$(H3 $(ID topic-sprites) Sprites)
501 		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.
502 
503 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
504 
505 	$(H3 $(ID topic-clipboard) Clipboard)
506 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
507 
508 		It also has helpers for handling X-specific events.
509 
510 	$(H3 $(ID topic-dnd) Drag and Drop)
511 		See [enableDragAndDrop] and [draggable].
512 
513 	$(H3 $(ID topic-timers) Timers)
514 		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].
515 
516 		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.
517 
518 		---
519 			import arsd.simpledisplay;
520 
521 			void main() {
522 				auto window = new SimpleWindow(400, 400);
523 				// every 100 ms, it will draw a random line
524 				// on the window.
525 				window.eventLoop(100, {
526 					auto painter = window.draw();
527 
528 					import std.random;
529 					// random color
530 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
531 					// random line
532 					painter.drawLine(
533 						Point(uniform(0, window.width), uniform(0, window.height)),
534 						Point(uniform(0, window.width), uniform(0, window.height)));
535 
536 				});
537 			}
538 		---
539 
540 		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.
541 
542 		The pulse timer and instances of the [Timer] class may be combined at will.
543 
544 		---
545 			import arsd.simpledisplay;
546 
547 			void main() {
548 				auto window = new SimpleWindow(400, 400);
549 				auto timer = new Timer(1000, delegate {
550 					auto painter = window.draw();
551 					painter.clear();
552 				});
553 
554 				window.eventLoop(0);
555 			}
556 		---
557 
558 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
559 
560 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
561 		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.
562 
563 		See also: `xwindows.d` from my github.
564 
565 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
566 		`handleNativeEvent` and `handleNativeGlobalEvent`.
567 
568 	$(H3 $(ID topic-integration) Integration with other libraries)
569 		Integration with a third-party event loop is possible.
570 
571 		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.
572 
573 	$(H3 $(ID topic-guis) GUI widgets)
574 		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!
575 
576 		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.
577 
578 		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.)
579 
580 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
581 
582 	$(H2 Platform-specific tips and tricks)
583 
584 	X_tips:
585 
586 	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.
587 
588 	Windows_tips:
589 
590 	You can add icons or manifest files to your exe using a resource file.
591 
592 	To create a Windows .ico file, use the gimp or something. I'll write a helper
593 	program later.
594 
595 	Create `yourapp.rc`:
596 
597 	```rc
598 		1 ICON filename.ico
599 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
600 	```
601 
602 	And `yourapp.exe.manifest`:
603 
604 	```xml
605 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
606 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
607 		<assemblyIdentity
608 		    version="1.0.0.0"
609 		    processorArchitecture="*"
610 		    name="CompanyName.ProductName.YourApplication"
611 		    type="win32"
612 		/>
613 		<description>Your application description here.</description>
614 		<dependency>
615 		    <dependentAssembly>
616 			<assemblyIdentity
617 			    type="win32"
618 			    name="Microsoft.Windows.Common-Controls"
619 			    version="6.0.0.0"
620 			    processorArchitecture="*"
621 			    publicKeyToken="6595b64144ccf1df"
622 			    language="*"
623 			/>
624 		    </dependentAssembly>
625 		</dependency>
626 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
627 			<windowsSettings>
628 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
629 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
630 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
631 				<!-- to render crisply in DPI-unaware contexts -->
632 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
633 			</windowsSettings>
634 		</application>
635 		</assembly>
636 	```
637 
638 	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`.
639 
640 	Doing this lets you opt into various new things since Windows XP.
641 
642 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
643 
644 	$(H2 Tips)
645 
646 	$(H3 Name conflicts)
647 
648 	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:
649 
650 	---
651 	static import sdpy = arsd.simpledisplay;
652 	import arsd.simpledisplay : SimpleWindow;
653 
654 	void main() {
655 		auto window = new SimpleWindow();
656 		sdpy.EventLoop.get.run();
657 	}
658 	---
659 
660 	$(H2 $(ID developer-notes) Developer notes)
661 
662 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
663 	implementation though.
664 
665 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
666 	suck. If I was rewriting it, I wouldn't do it that way again.
667 
668 	This file must not have any more required dependencies. If you need bindings, add
669 	them right to this file. Once it gets into druntime and is there for a while, remove
670 	bindings from here to avoid conflicts (or put them in an appropriate version block
671 	so it continues to just work on old dmd), but wait a couple releases before making the
672 	transition so this module remains usable with older versions of dmd.
673 
674 	You may have optional dependencies if needed by putting them in version blocks or
675 	template functions. You may also extend the module with other modules with UFCS without
676 	actually editing this - that is nice to do if you can.
677 
678 	Try to make functions work the same way across operating systems. I typically make
679 	it thinly wrap Windows, then emulate that on Linux.
680 
681 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
682 	Phobos! So try to avoid it.
683 
684 	See more comments throughout the source.
685 
686 	I realize this file is fairly large, but over half that is just bindings at the bottom
687 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
688 	to understand. I suggest you jump around the source by looking for a particular
689 	declaration you're interested in, like `class SimpleWindow` using your editor's search
690 	function, then look at one piece at a time.
691 
692 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
693 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
694 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
695 
696 	I live in the eastern United States, so I will most likely not be around at night in
697 	that US east timezone.
698 
699 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
700 
701 	Building documentation: use my adrdox generator, `dub run adrdox`.
702 
703 	Examples:
704 
705 	$(DIV $(ID Event-example))
706 	$(H3 $(ID event-example) Event example)
707 	This program creates a window and draws events inside them as they
708 	happen, scrolling the text in the window as needed. Run this program
709 	and experiment to get a feel for where basic input events take place
710 	in the library.
711 
712 	---
713 	// dmd example.d simpledisplay.d color.d
714 	import arsd.simpledisplay;
715 	import std.conv;
716 
717 	void main() {
718 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
719 
720 		int y = 0;
721 
722 		void addLine(string text) {
723 			auto painter = window.draw();
724 
725 			if(y + painter.fontHeight >= window.height) {
726 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
727 				y -= painter.fontHeight;
728 			}
729 
730 			painter.outlineColor = Color.red;
731 			painter.fillColor = Color.black;
732 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
733 
734 			painter.outlineColor = Color.white;
735 
736 			painter.drawText(Point(10, y), text);
737 
738 			y += painter.fontHeight;
739 		}
740 
741 		window.eventLoop(1000,
742 		  () {
743 			addLine("Timer went off!");
744 		  },
745 		  (KeyEvent event) {
746 			addLine(to!string(event));
747 		  },
748 		  (MouseEvent event) {
749 			addLine(to!string(event));
750 		  },
751 		  (dchar ch) {
752 			addLine(to!string(ch));
753 		  }
754 		);
755 	}
756 	---
757 
758 	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.
759 
760 	$(COMMENT
761 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
762 
763 	---
764 
765 	---
766 	)
767 
768 	History:
769 		Initial release in April 2011.
770 
771 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
772 
773 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
774 +/
775 module arsd.simpledisplay;
776 
777 import arsd.core;
778 
779 // FIXME: tetris demo
780 // FIXME: space invaders demo
781 // FIXME: asteroids demo
782 
783 /++ $(ID Pong-example)
784 	$(H3 Pong)
785 
786 	This program creates a little Pong-like game. Player one is controlled
787 	with the keyboard.  Player two is controlled with the mouse. It demos
788 	the pulse timer, event handling, and some basic drawing.
789 +/
790 version(demos)
791 unittest {
792 	// dmd example.d simpledisplay.d color.d
793 	import arsd.simpledisplay;
794 
795 	enum paddleMovementSpeed = 8;
796 	enum paddleHeight = 48;
797 
798 	void main() {
799 		auto window = new SimpleWindow(600, 400, "Pong game!");
800 
801 		int playerOnePosition, playerTwoPosition;
802 		int playerOneMovement, playerTwoMovement;
803 		int playerOneScore, playerTwoScore;
804 
805 		int ballX, ballY;
806 		int ballDx, ballDy;
807 
808 		void serve() {
809 			import std.random;
810 
811 			ballX = window.width / 2;
812 			ballY = window.height / 2;
813 			ballDx = uniform(-4, 4) * 3;
814 			ballDy = uniform(-4, 4) * 3;
815 			if(ballDx == 0)
816 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
817 		}
818 
819 		serve();
820 
821 		window.eventLoop(50, // set a 50 ms timer pulls
822 			// This runs once per timer pulse
823 			delegate () {
824 				auto painter = window.draw();
825 
826 				painter.clear();
827 
828 				// Update everyone's motion
829 				playerOnePosition += playerOneMovement;
830 				playerTwoPosition += playerTwoMovement;
831 
832 				ballX += ballDx;
833 				ballY += ballDy;
834 
835 				// Bounce off the top and bottom edges of the window
836 				if(ballY + 7 >= window.height)
837 					ballDy = -ballDy;
838 				if(ballY - 8 <= 0)
839 					ballDy = -ballDy;
840 
841 				// Bounce off the paddle, if it is in position
842 				if(ballX - 8 <= 16) {
843 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
844 						ballDx = -ballDx + 1; // add some speed to keep it interesting
845 						ballDy += playerOneMovement; // and y movement based on your controls too
846 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
847 					} else {
848 						// Missed it
849 						playerTwoScore ++;
850 						serve();
851 					}
852 				}
853 
854 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
855 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
856 						ballDx = -ballDx - 1;
857 						ballDy += playerTwoMovement;
858 						ballX = window.width - 24;
859 					} else {
860 						// Missed it
861 						playerOneScore ++;
862 						serve();
863 					}
864 				}
865 
866 				// Draw the paddles
867 				painter.outlineColor = Color.black;
868 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
869 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
870 
871 				// Draw the ball
872 				painter.fillColor = Color.red;
873 				painter.outlineColor = Color.yellow;
874 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
875 
876 				// Draw the score
877 				painter.outlineColor = Color.blue;
878 				import std.conv;
879 				painter.drawText(Point(64, 4), to!string(playerOneScore));
880 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
881 
882 			},
883 			delegate (KeyEvent event) {
884 				// Player 1's controls are the arrow keys on the keyboard
885 				if(event.key == Key.Down)
886 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
887 				if(event.key == Key.Up)
888 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
889 
890 			},
891 			delegate (MouseEvent event) {
892 				// Player 2's controls are mouse movement while the left button is held down
893 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
894 					if(event.dy > 0)
895 						playerTwoMovement = paddleMovementSpeed;
896 					else if(event.dy < 0)
897 						playerTwoMovement = -paddleMovementSpeed;
898 				} else {
899 					playerTwoMovement = 0;
900 				}
901 			}
902 		);
903 	}
904 }
905 
906 /++ $(H3 $(ID example-minesweeper) Minesweeper)
907 
908 	This minesweeper demo shows how we can implement another classic
909 	game with simpledisplay and shows some mouse input and basic output
910 	code.
911 +/
912 version(demos)
913 unittest {
914 	import arsd.simpledisplay;
915 
916 	enum GameSquare {
917 		mine = 0,
918 		clear,
919 		m1, m2, m3, m4, m5, m6, m7, m8
920 	}
921 
922 	enum UserSquare {
923 		unknown,
924 		revealed,
925 		flagged,
926 		questioned
927 	}
928 
929 	enum GameState {
930 		inProgress,
931 		lose,
932 		win
933 	}
934 
935 	GameSquare[] board;
936 	UserSquare[] userState;
937 	GameState gameState;
938 	int boardWidth;
939 	int boardHeight;
940 
941 	bool isMine(int x, int y) {
942 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
943 			return false;
944 		return board[y * boardWidth + x] == GameSquare.mine;
945 	}
946 
947 	GameState reveal(int x, int y) {
948 		if(board[y * boardWidth + x] == GameSquare.clear) {
949 			floodFill(userState, boardWidth, boardHeight,
950 				UserSquare.unknown, UserSquare.revealed,
951 				x, y,
952 				(x, y) {
953 					if(board[y * boardWidth + x] == GameSquare.clear)
954 						return true;
955 					else {
956 						userState[y * boardWidth + x] = UserSquare.revealed;
957 						return false;
958 					}
959 				});
960 		} else {
961 			userState[y * boardWidth + x] = UserSquare.revealed;
962 			if(isMine(x, y))
963 				return GameState.lose;
964 		}
965 
966 		foreach(state; userState) {
967 			if(state == UserSquare.unknown || state == UserSquare.questioned)
968 				return GameState.inProgress;
969 		}
970 
971 		return GameState.win;
972 	}
973 
974 	void initializeBoard(int width, int height, int numberOfMines) {
975 		boardWidth = width;
976 		boardHeight = height;
977 		board.length = width * height;
978 
979 		userState.length = width * height;
980 		userState[] = UserSquare.unknown;
981 
982 		import std.algorithm, std.random, std.range;
983 
984 		board[] = GameSquare.clear;
985 
986 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
987 			board[minePosition] = GameSquare.mine;
988 
989 		int x;
990 		int y;
991 		foreach(idx, ref square; board) {
992 			if(square == GameSquare.clear) {
993 				int danger = 0;
994 				danger += isMine(x-1, y-1)?1:0;
995 				danger += isMine(x-1, y)?1:0;
996 				danger += isMine(x-1, y+1)?1:0;
997 				danger += isMine(x, y-1)?1:0;
998 				danger += isMine(x, y+1)?1:0;
999 				danger += isMine(x+1, y-1)?1:0;
1000 				danger += isMine(x+1, y)?1:0;
1001 				danger += isMine(x+1, y+1)?1:0;
1002 
1003 				square = cast(GameSquare) (danger + 1);
1004 			}
1005 
1006 			x++;
1007 			if(x == width) {
1008 				x = 0;
1009 				y++;
1010 			}
1011 		}
1012 	}
1013 
1014 	void redraw(SimpleWindow window) {
1015 		import std.conv;
1016 
1017 		auto painter = window.draw();
1018 
1019 		painter.clear();
1020 
1021 		final switch(gameState) with(GameState) {
1022 			case inProgress:
1023 				break;
1024 			case win:
1025 				painter.fillColor = Color.green;
1026 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1027 				return;
1028 			case lose:
1029 				painter.fillColor = Color.red;
1030 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1031 				return;
1032 		}
1033 
1034 		int x = 0;
1035 		int y = 0;
1036 
1037 		foreach(idx, square; board) {
1038 			auto state = userState[idx];
1039 
1040 			final switch(state) with(UserSquare) {
1041 				case unknown:
1042 					painter.outlineColor = Color.black;
1043 					painter.fillColor = Color(128,128,128);
1044 
1045 					painter.drawRectangle(
1046 						Point(x * 20, y * 20),
1047 						20, 20
1048 					);
1049 				break;
1050 				case revealed:
1051 					if(square == GameSquare.clear) {
1052 						painter.outlineColor = Color.white;
1053 						painter.fillColor = Color.white;
1054 
1055 						painter.drawRectangle(
1056 							Point(x * 20, y * 20),
1057 							20, 20
1058 						);
1059 					} else {
1060 						painter.outlineColor = Color.black;
1061 						painter.fillColor = Color.white;
1062 
1063 						painter.drawText(
1064 							Point(x * 20, y * 20),
1065 							to!string(square)[1..2],
1066 							Point(x * 20 + 20, y * 20 + 20),
1067 							TextAlignment.Center | TextAlignment.VerticalCenter);
1068 					}
1069 				break;
1070 				case flagged:
1071 					painter.outlineColor = Color.black;
1072 					painter.fillColor = Color.red;
1073 					painter.drawRectangle(
1074 						Point(x * 20, y * 20),
1075 						20, 20
1076 					);
1077 				break;
1078 				case questioned:
1079 					painter.outlineColor = Color.black;
1080 					painter.fillColor = Color.yellow;
1081 					painter.drawRectangle(
1082 						Point(x * 20, y * 20),
1083 						20, 20
1084 					);
1085 				break;
1086 			}
1087 
1088 			x++;
1089 			if(x == boardWidth) {
1090 				x = 0;
1091 				y++;
1092 			}
1093 		}
1094 
1095 	}
1096 
1097 	void main() {
1098 		auto window = new SimpleWindow(200, 200);
1099 
1100 		initializeBoard(10, 10, 10);
1101 
1102 		redraw(window);
1103 		window.eventLoop(0,
1104 			delegate (MouseEvent me) {
1105 				if(me.type != MouseEventType.buttonPressed)
1106 					return;
1107 				auto x = me.x / 20;
1108 				auto y = me.y / 20;
1109 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1110 					if(me.button == MouseButton.left) {
1111 						gameState = reveal(x, y);
1112 					} else {
1113 						userState[y*boardWidth+x] = UserSquare.flagged;
1114 					}
1115 					redraw(window);
1116 				}
1117 			}
1118 		);
1119 	}
1120 }
1121 
1122 import arsd.core;
1123 
1124 // FIXME: tetris demo
1125 // FIXME: space invaders demo
1126 // FIXME: asteroids demo
1127 
1128 version(OSX) version(DigitalMars) version=OSXCocoa;
1129 
1130 
1131 version(OSXCocoa) {
1132 	version=without_opengl;
1133 	version=allow_unimplemented_features;
1134 	// version=OSXCocoa;
1135 	// pragma(linkerDirective, "-framework Cocoa");
1136 }
1137 
1138 version(without_opengl) {
1139 	enum SdpyIsUsingIVGLBinds = false;
1140 } else /*version(Posix)*/ {
1141 	static if (__traits(compiles, (){import iv.glbinds;})) {
1142 		enum SdpyIsUsingIVGLBinds = true;
1143 		public import iv.glbinds;
1144 		//pragma(msg, "SDPY: using iv.glbinds");
1145 	} else {
1146 		enum SdpyIsUsingIVGLBinds = false;
1147 	}
1148 //} else {
1149 //	enum SdpyIsUsingIVGLBinds = false;
1150 }
1151 
1152 
1153 version(Windows) {
1154 	//import core.sys.windows.windows;
1155 	import core.sys.windows.winnls;
1156 	import core.sys.windows.windef;
1157 	import core.sys.windows.basetyps;
1158 	import core.sys.windows.winbase;
1159 	import core.sys.windows.winuser;
1160 	import core.sys.windows.shellapi;
1161 	import core.sys.windows.wingdi;
1162 	static import gdi = core.sys.windows.wingdi; // so i
1163 
1164 	pragma(lib, "gdi32");
1165 	pragma(lib, "user32");
1166 
1167 	// for AlphaBlend... a breaking change....
1168 	version(CRuntime_DigitalMars) { } else
1169 		pragma(lib, "msimg32");
1170 } else version (linux) {
1171 	//k8: this is hack for rdmd. sorry.
1172 	static import core.sys.linux.epoll;
1173 	static import core.sys.linux.timerfd;
1174 }
1175 
1176 
1177 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1178 
1179 // http://wiki.dlang.org/Simpledisplay.d
1180 
1181 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1182 
1183 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1184 // but can i control the scroll lock led
1185 
1186 
1187 // Note: if you are using Image on X, you might want to do:
1188 /*
1189 	static if(UsingSimpledisplayX11) {
1190 		if(!Image.impl.xshmAvailable) {
1191 			// the images will use the slower XPutImage, you might
1192 			// want to consider an alternative method to get better speed
1193 		}
1194 	}
1195 
1196 	If the shared memory extension is available though, simpledisplay uses it
1197 	for a significant speed boost whenever you draw large Images.
1198 */
1199 
1200 // 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.
1201 
1202 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1203 
1204 /*
1205 	Biggest FIXME:
1206 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1207 
1208 		clean up opengl contexts when their windows close
1209 
1210 		fix resizing the bitmaps/pixmaps
1211 */
1212 
1213 // BTW on Windows:
1214 // -L/SUBSYSTEM:WINDOWS:5.0
1215 // to dmd will make a nice windows binary w/o a console if you want that.
1216 
1217 /*
1218 	Stuff to add:
1219 
1220 	use multibyte functions everywhere we can
1221 
1222 	OpenGL windows
1223 	more event stuff
1224 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1225 
1226 
1227 	resizeEvent
1228 		and make the windows non-resizable by default,
1229 		or perhaps stretched (if I can find something in X like StretchBlt)
1230 
1231 	take a screenshot function!
1232 
1233 	Pens and brushes?
1234 	Maybe a global event loop?
1235 
1236 	Mouse deltas
1237 	Key items
1238 */
1239 
1240 /*
1241 From MSDN:
1242 
1243 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1244 
1245 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.
1246 
1247 */
1248 
1249 version(linux) {
1250 	version = X11;
1251 	version(without_libnotify) {
1252 		// we cool
1253 	}
1254 	else
1255 		version = libnotify;
1256 }
1257 
1258 version(libnotify) {
1259 	pragma(lib, "dl");
1260 	import core.sys.posix.dlfcn;
1261 
1262 	void delegate()[int] libnotify_action_delegates;
1263 	int libnotify_action_delegates_count;
1264 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1265 		auto idx = cast(int) user_data;
1266 		if(auto dgptr = idx in libnotify_action_delegates) {
1267 			(*dgptr)();
1268 			libnotify_action_delegates.remove(idx);
1269 		}
1270 	}
1271 
1272 	struct C_DynamicLibrary {
1273 		void* handle;
1274 		this(string name) {
1275 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1276 			if(handle is null)
1277 				throw new Exception("dlopen");
1278 		}
1279 
1280 		void close() {
1281 			dlclose(handle);
1282 		}
1283 
1284 		~this() {
1285 			// close
1286 		}
1287 
1288 		// FIXME: this looks up by name every time....
1289 		template call(string func, Ret, Args...) {
1290 			extern(C) Ret function(Args) fptr;
1291 			typeof(fptr) call() {
1292 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1293 				return fptr;
1294 			}
1295 		}
1296 	}
1297 
1298 	C_DynamicLibrary* libnotify;
1299 }
1300 
1301 version(OSX) {
1302 	version(OSXCocoa) {}
1303 	else { version = X11; }
1304 }
1305 	//version = OSXCocoa; // this was written by KennyTM
1306 version(FreeBSD)
1307 	version = X11;
1308 version(Solaris)
1309 	version = X11;
1310 
1311 version(X11) {
1312 	version(without_xft) {}
1313 	else version=with_xft;
1314 }
1315 
1316 void featureNotImplemented()() {
1317 	version(allow_unimplemented_features)
1318 		throw new NotYetImplementedException();
1319 	else
1320 		static assert(0);
1321 }
1322 
1323 // these are so the static asserts don't trigger unless you want to
1324 // add support to it for an OS
1325 version(Windows)
1326 	version = with_timer;
1327 version(linux)
1328 	version = with_timer;
1329 version(OSXCocoa)
1330 	version = with_timer;
1331 
1332 version(with_timer)
1333 	enum bool SimpledisplayTimerAvailable = true;
1334 else
1335 	enum bool SimpledisplayTimerAvailable = false;
1336 
1337 /// 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.
1338 version(Windows)
1339 	enum bool UsingSimpledisplayWindows = true;
1340 else
1341 	enum bool UsingSimpledisplayWindows = false;
1342 
1343 /// 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.
1344 version(X11)
1345 	enum bool UsingSimpledisplayX11 = true;
1346 else
1347 	enum bool UsingSimpledisplayX11 = false;
1348 
1349 /// 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.
1350 version(OSXCocoa)
1351 	enum bool UsingSimpledisplayCocoa = true;
1352 else
1353 	enum bool UsingSimpledisplayCocoa = false;
1354 
1355 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1356 version(Windows)
1357 	enum multipleWindowsSupported = true;
1358 else version(X11)
1359 	enum multipleWindowsSupported = true;
1360 else version(OSXCocoa)
1361 	enum multipleWindowsSupported = true;
1362 else
1363 	static assert(0);
1364 
1365 version(without_opengl)
1366 	enum bool OpenGlEnabled = false;
1367 else
1368 	enum bool OpenGlEnabled = true;
1369 
1370 /++
1371 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1372 	If you mix this in above your `main` function, you no longer need to use the linker
1373 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1374 
1375 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1376 
1377 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1378 	stderr writeln. It will fail and throw an exception.
1379 
1380 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1381 
1382 	History:
1383 		Added November 24, 2021 (dub v10.4)
1384 +/
1385 mixin template EnableWindowsSubsystem() {
1386 	version(Windows)
1387 	version(CRuntime_Microsoft) {
1388 		pragma(linkerDirective, "/subsystem:windows");
1389 		version(LDC)
1390 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1391 		else
1392 			pragma(linkerDirective, "/entry:mainCRTStartup");
1393 	}
1394 }
1395 
1396 
1397 /++
1398 	After selecting a type from [WindowTypes], you may further customize
1399 	its behavior by setting one or more of these flags.
1400 
1401 
1402 	The different window types have different meanings of `normal`. If the
1403 	window type already is a good match for what you want to do, you should
1404 	just use [WindowFlags.normal], the default, which will do the right thing
1405 	for your users.
1406 
1407 	The window flags will not always be honored by the operating system
1408 	and window managers; they are hints, not commands.
1409 +/
1410 enum WindowFlags : int {
1411 	normal = 0, ///
1412 	skipTaskbar = 1, ///
1413 	alwaysOnTop = 2, ///
1414 	alwaysOnBottom = 4, ///
1415 	cannotBeActivated = 8, ///
1416 	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.
1417 	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.
1418 	/++
1419 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1420 		it is still a top-level window. This should NOT be set separately for most window types.
1421 
1422 		A transient window will not keep the application open if its main window closes.
1423 
1424 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1425 
1426 
1427 		From the ICCM:
1428 
1429 		$(BLOCKQUOTE
1430 			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.
1431 
1432 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1433 		)
1434 
1435 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1436 
1437 		History:
1438 			Added February 23, 2021 but not yet stabilized.
1439 	+/
1440 	transient = 64,
1441 	/++
1442 		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.
1443 
1444 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1445 
1446 		History:
1447 			Added April 1, 2022
1448 	+/
1449 	managesChildWindowFocus = 128,
1450 
1451 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1452 }
1453 
1454 /++
1455 	When creating a window, you can pass a type to SimpleWindow's constructor,
1456 	then further customize the window by changing `WindowFlags`.
1457 
1458 
1459 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1460 	use. The others are there to build a foundation for a higher level GUI toolkit,
1461 	but are themselves not as high level as you might think from their names.
1462 
1463 	This list is based on the EMWH spec for X11.
1464 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1465 +/
1466 enum WindowTypes : int {
1467 	/// An ordinary application window.
1468 	normal,
1469 	/// 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.
1470 	undecorated,
1471 	/// 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.
1472 	eventOnly,
1473 	/// A drop down menu, such as from a menu bar
1474 	dropdownMenu,
1475 	/// A popup menu, such as from a right click
1476 	popupMenu,
1477 	/// A popup bubble notification
1478 	notification,
1479 	/*
1480 	menu, /// a tearable menu bar
1481 	splashScreen, /// a loading splash screen for your application
1482 	tooltip, /// A tiny window showing temporary help text or something.
1483 	comboBoxDropdown,
1484 	dialog,
1485 	toolbar
1486 	*/
1487 	/// a child nested inside the parent. You must pass a parent window to the ctor
1488 	nestedChild,
1489 
1490 	/++
1491 		The type you get when you pass in an existing browser handle, which means most
1492 		of simpledisplay's fancy things will not be done since they were never set up.
1493 
1494 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1495 		failure; you should use the existing handle constructor.
1496 
1497 		History:
1498 			Added November 17, 2022 (previously it would have type `normal`)
1499 	+/
1500 	minimallyWrapped
1501 }
1502 
1503 
1504 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1505 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1506 private __gshared char* sdpyWindowClassStr = null;
1507 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1508 
1509 /**
1510 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1511 	You may want to change context version if you want to use advanced shaders or
1512 	other modern OpenGL techinques. This setting doesn't affect already created
1513 	windows. You may use version 2.1 as your default, which should be supported
1514 	by any box since 2006, so seems to be a reasonable choice.
1515 
1516 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1517 	old context creation code without any version specified. This is the safest
1518 	way to init OpenGL, but it may not give you access to advanced features.
1519 
1520 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1521 */
1522 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1523 
1524 /**
1525 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1526 	pipeline functions, and without "compatible" mode you won't be able to use
1527 	your old non-shader-based code with such contexts. By default SimpleDisplay
1528 	creates compatible context, so you can gradually upgrade your OpenGL code if
1529 	you want to (or leave it as is, as it should "just work").
1530 */
1531 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1532 
1533 /**
1534 	Set to `true` to allow creating OpenGL context with lower version than requested
1535 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1536 	`openGLContextFallbackActivated()` will return `true`.
1537 	*/
1538 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1539 
1540 /**
1541 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1542 	*/
1543 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1544 
1545 /++
1546 	History:
1547 		Added April 24, 2023  (dub v11.0)
1548 +/
1549 version(without_opengl) {} else
1550 auto openGLCurrentContext() {
1551 	version(Windows)
1552 		return wglGetCurrentContext();
1553 	else
1554 		return glXGetCurrentContext();
1555 }
1556 
1557 
1558 /**
1559 	Set window class name for all following `new SimpleWindow()` calls.
1560 
1561 	WARNING! For Windows, you should set your class name before creating any
1562 	window, and NEVER change it after that!
1563 */
1564 void sdpyWindowClass (const(char)[] v) {
1565 	import core.stdc.stdlib : realloc;
1566 	if (v.length == 0) v = "SimpleDisplayWindow";
1567 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1568 	if (sdpyWindowClassStr is null) return; // oops
1569 	sdpyWindowClassStr[0..v.length+1] = 0;
1570 	sdpyWindowClassStr[0..v.length] = v[];
1571 }
1572 
1573 /**
1574 	Get current window class name.
1575 */
1576 string sdpyWindowClass () {
1577 	if (sdpyWindowClassStr is null) return null;
1578 	foreach (immutable idx; 0..size_t.max-1) {
1579 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1580 	}
1581 	return null;
1582 }
1583 
1584 /++
1585 	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.
1586 
1587 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1588 +/
1589 float[2] getDpi() {
1590 	float[2] dpi;
1591 	version(Windows) {
1592 		HDC screen = GetDC(null);
1593 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1594 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1595 	} else version(X11) {
1596 		auto display = XDisplayConnection.get;
1597 		auto screen = DefaultScreen(display);
1598 
1599 		void fallback() {
1600 			/+
1601 			// 25.4 millimeters in an inch...
1602 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1603 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1604 			+/
1605 
1606 			// the physical size isn't actually as important as the logical size since this is
1607 			// all about scaling really
1608 			dpi[0] = 96;
1609 			dpi[1] = 96;
1610 		}
1611 
1612 		auto xft = getXftDpi();
1613 		if(xft is float.init)
1614 			fallback();
1615 		else {
1616 			dpi[0] = xft;
1617 			dpi[1] = xft;
1618 		}
1619 	}
1620 
1621 	return dpi;
1622 }
1623 
1624 version(X11)
1625 float getXftDpi() {
1626 	auto display = XDisplayConnection.get;
1627 
1628 	char* resourceString = XResourceManagerString(display);
1629 	XrmInitialize();
1630 
1631 	if (resourceString) {
1632 		auto db = XrmGetStringDatabase(resourceString);
1633 		XrmValue value;
1634 		char* type;
1635 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1636 			if (value.addr) {
1637 				import core.stdc.stdlib;
1638 				return atof(cast(char*) value.addr);
1639 			}
1640 		}
1641 	}
1642 
1643 	return float.init;
1644 }
1645 
1646 /++
1647 	Implementation used by [SimpleWindow.takeScreenshot].
1648 
1649 	Params:
1650 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1651 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1652 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1653 		x = the x-offset of the image to capture, from the left.
1654 		y = the y-offset of the image to capture, from the top.
1655 
1656 	History:
1657 		Added on March 14, 2021
1658 
1659 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1660 
1661 +/
1662 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1663 	TrueColorImage got;
1664 	version(X11) {
1665 		auto display = XDisplayConnection.get;
1666 		if(handle == 0)
1667 			handle = RootWindow(display, DefaultScreen(display));
1668 
1669 		if(width == 0 || height == 0) {
1670 			Window root;
1671 			int xpos, ypos;
1672 			uint widthret, heightret, borderret, depthret;
1673 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1674 
1675 			if(width == 0)
1676 				width = widthret;
1677 			if(height == 0)
1678 				height = heightret;
1679 		}
1680 
1681 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1682 
1683 		// https://github.com/adamdruppe/arsd/issues/98
1684 
1685 		auto i = new Image(image);
1686 		got = i.toTrueColorImage();
1687 
1688 		XDestroyImage(image);
1689 	} else version(Windows) {
1690 		auto hdc = GetDC(handle);
1691 		scope(exit) ReleaseDC(handle, hdc);
1692 
1693 		if(width == 0 || height == 0) {
1694 			BITMAP bmHeader;
1695 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1696 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1697 			if(width == 0)
1698 				width = bmHeader.bmWidth;
1699 			if(height == 0)
1700 				height = bmHeader.bmHeight;
1701 		}
1702 
1703 		auto i = new Image(width, height);
1704 		HDC hdcMem = CreateCompatibleDC(hdc);
1705 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1706 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1707 		SelectObject(hdcMem, hbmOld);
1708 		DeleteDC(hdcMem);
1709 
1710 		got = i.toTrueColorImage();
1711 	} else featureNotImplemented();
1712 
1713 	return got;
1714 }
1715 
1716 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1717 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1718 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1719 
1720 version(Windows)
1721 shared static this() {
1722 	auto lib = LoadLibrary("User32.dll");
1723 	if(lib is null)
1724 		return;
1725 	//scope(exit)
1726 		//FreeLibrary(lib);
1727 
1728 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1729 
1730 	if(SetProcessDpiAwarenessContext is null)
1731 		return;
1732 
1733 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1734 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1735 		//writeln(GetLastError());
1736 	}
1737 
1738 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1739 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1740 }
1741 
1742 /++
1743 	Blocking mode for event loop calls associated with a window instance.
1744 
1745 	History:
1746 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1747 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1748 		is, all would block until the application quit.
1749 
1750 		That behavior can still be achieved here with `untilApplicationQuits`,
1751 		or explicitly calling the top-level `EventLoop.get.run` function.
1752 +/
1753 enum BlockingMode {
1754 	/++
1755 		The event loop call will block until the whole application is ready
1756 		to quit if it is the only one running, but if it is nested inside
1757 		another one, it will only block until the window you're calling it on
1758 		closes.
1759 	+/
1760 	automatic             = 0x00,
1761 	/++
1762 		The event loop call will only return when the whole application
1763 		is ready to quit. This usually means all windows have been closed.
1764 
1765 		This is appropriate for your main application event loop.
1766 	+/
1767 	untilApplicationQuits = 0x01,
1768 	/++
1769 		The event loop will return when the window you're calling it on
1770 		closes. If there are other windows still open, they may be destroyed
1771 		unless you have another event loop running later.
1772 
1773 		This might be appropriate for a modal dialog box loop. Remember that
1774 		other windows are still processing input though, so you can end up
1775 		with a lengthy call stack if this happens in a loop, similar to a
1776 		recursive function (well, it literally is a recursive function, just
1777 		not an obvious looking one).
1778 	+/
1779 	untilWindowCloses     = 0x02,
1780 	/++
1781 		If an event loop is already running, this call will immediately
1782 		return, allowing the existing loop to handle it. If not, this call
1783 		will block until the condition you bitwise-or into the flag.
1784 
1785 		The default is to block until the application quits, same as with
1786 		the `automatic` setting (since if it were nested, which triggers until
1787 		window closes in automatic, this flag would instead not block at all),
1788 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1789 		it will only nest until the window closes. You might want that if you are
1790 		going to open two windows simultaneously and want closing just one of them
1791 		to trigger the event loop return.
1792 	+/
1793 	onlyIfNotNested       = 0x10,
1794 }
1795 
1796 /++
1797 	The flagship window class.
1798 
1799 
1800 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1801 	out of more advanced or complex features of the underlying windowing system.
1802 
1803 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1804 	and get a suitable window to work with.
1805 
1806 	From there, you can opt into additional features, like custom resizability and OpenGL support
1807 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1808 	and customization flags with the final two constructor arguments.
1809 
1810 	If none of that works for you, you can also create a window using native function calls, then
1811 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1812 	though, if you do this, managing the window is still your own responsibility! Notably, you
1813 	will need to destroy it yourself.
1814 +/
1815 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1816 
1817 	/++
1818 		Copies the window's current state into a [TrueColorImage].
1819 
1820 		Be warned: this can be a very slow operation
1821 
1822 		History:
1823 			Actually implemented on March 14, 2021
1824 	+/
1825 	TrueColorImage takeScreenshot() {
1826 		version(Windows)
1827 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1828 		else version(OSXCocoa)
1829 			throw new NotYetImplementedException();
1830 		else
1831 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1832 	}
1833 
1834 	/++
1835 		Returns the actual logical DPI for the window on its current display monitor. If the window
1836 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1837 
1838 		Please note this function may return zero if it doesn't know the answer!
1839 
1840 
1841 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1842 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1843 
1844 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1845 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1846 		window primarily resides on by checking the center point of the window against the monitor map.
1847 
1848 		Returns:
1849 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1850 			assumes the X and Y dpi are the same.
1851 
1852 		History:
1853 			Added November 26, 2021 (dub v10.4)
1854 
1855 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1856 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1857 			that.
1858 
1859 		Bugs:
1860 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1861 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1862 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1863 			and 1.5 on the secondary monitor.
1864 
1865 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1866 			is a historical misnomer - the real thing of interest is the scale factor and due to
1867 			compatibility concerns the scale would modify dpi values to trick applications. But since
1868 			that's the terminology common out there, I used it too.
1869 
1870 		See_Also:
1871 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1872 			as this since the window many be on a different monitor, but it is a reasonable fallback
1873 			to use if `actualDpi` returns 0.
1874 
1875 			[onDpiChanged] is changed when `actualDpi` has changed.
1876 	+/
1877 	int actualDpi() {
1878 		version(X11) bool useFallbackDpi = false;
1879 		if(!actualDpiLoadAttempted) {
1880 			// FIXME: do the actual monitor we are on
1881 			// and on X this is a good chance to load the monitor map.
1882 			version(Windows) {
1883 				if(GetDpiForWindow)
1884 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1885 			} else version(X11) {
1886 				if(!xRandrInfoLoadAttemped) {
1887 					xRandrInfoLoadAttemped = true;
1888 					if(!XRandrLibrary.attempted) {
1889 						XRandrLibrary.loadDynamicLibrary();
1890 					}
1891 
1892 					if(XRandrLibrary.loadSuccessful) {
1893 						auto display = XDisplayConnection.get;
1894 						int scratch;
1895 						int major, minor;
1896 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1897 							goto fallback;
1898 
1899 						XRRQueryVersion(display, &major, &minor);
1900 						if(major <= 1 && minor < 5)
1901 							goto fallback;
1902 
1903 						int count;
1904 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1905 						if(monitors is null)
1906 							goto fallback;
1907 						scope(exit) XRRFreeMonitors(monitors);
1908 
1909 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1910 						MonitorInfo.info.assumeSafeAppend();
1911 						foreach(idx, monitor; monitors[0 .. count]) {
1912 							MonitorInfo.info ~= MonitorInfo(
1913 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1914 								Size(monitor.mwidth, monitor.mheight),
1915 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1916 							);
1917 
1918 							/+
1919 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1920 							// unknown physical size, just guess 96 to avoid divide by zero
1921 							MonitorInfo.info ~= MonitorInfo(
1922 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1923 								Size(monitor.mwidth, monitor.mheight),
1924 								96
1925 							);
1926 							else
1927 							// and actual thing
1928 							MonitorInfo.info ~= MonitorInfo(
1929 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1930 								Size(monitor.mwidth, monitor.mheight),
1931 								minInternal(
1932 									// millimeter to int then rounding up.
1933 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1934 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1935 								)
1936 							);
1937 							+/
1938 						}
1939 					// writeln("Here", MonitorInfo.info);
1940 					}
1941 				}
1942 
1943 				if(XRandrLibrary.loadSuccessful) {
1944 					updateActualDpi(true);
1945 					// writeln("updated");
1946 
1947 					if(!requestedInput) {
1948 						// this is what requests live updates should the configuration change
1949 						// each time you select input, it sends an initial event, so very important
1950 						// to not get into a loop of selecting input, getting event, updating data,
1951 						// and reselecting input...
1952 						requestedInput = true;
1953 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1954 						// writeln("requested input");
1955 					}
1956 				} else {
1957 					fallback:
1958 					// make sure we disable events that aren't coming
1959 					xrrEventBase = -1;
1960 					// best guess... respect the custom scaling user command to some extent at least though
1961 					useFallbackDpi = true;
1962 				}
1963 			}
1964 			actualDpiLoadAttempted = true;
1965 		} else version(X11) if(MonitorInfo.info.length == 0) {
1966 			useFallbackDpi = true;
1967 		}
1968 
1969 		version(X11)
1970 		if(useFallbackDpi)
1971 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1972 
1973 		return actualDpi_;
1974 	}
1975 
1976 	private int actualDpi_;
1977 	private bool actualDpiLoadAttempted;
1978 
1979 	version(X11) private {
1980 		bool requestedInput;
1981 		static bool xRandrInfoLoadAttemped;
1982 		struct MonitorInfo {
1983 			Rectangle position;
1984 			Size size;
1985 			int dpi;
1986 
1987 			static MonitorInfo[] info;
1988 		}
1989 		bool screenPositionKnown;
1990 		int screenPositionX;
1991 		int screenPositionY;
1992 		void updateActualDpi(bool loadingNow = false) {
1993 			if(!loadingNow && !actualDpiLoadAttempted)
1994 				actualDpi(); // just to make it do the load
1995 			foreach(idx, m; MonitorInfo.info) {
1996 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1997 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1998 					actualDpi_ = m.dpi;
1999 					// writeln("monitor ", idx);
2000 					if(changed && onDpiChanged)
2001 						onDpiChanged();
2002 					break;
2003 				}
2004 			}
2005 		}
2006 	}
2007 
2008 	/++
2009 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2010 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2011 
2012 		History:
2013 			Added November 26, 2021 (dub v10.4)
2014 
2015 		See_Also:
2016 			[actualDpi]
2017 	+/
2018 	void delegate() onDpiChanged;
2019 
2020 	version(X11) {
2021 		void recreateAfterDisconnect() {
2022 			if(!stateDiscarded) return;
2023 
2024 			if(_parent !is null && _parent.stateDiscarded)
2025 				_parent.recreateAfterDisconnect();
2026 
2027 			bool wasHidden = hidden;
2028 
2029 			activeScreenPainter = null; // should already be done but just to confirm
2030 
2031 			actualDpi_ = 0;
2032 			actualDpiLoadAttempted = false;
2033 			xRandrInfoLoadAttemped = false;
2034 
2035 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2036 
2037 			if(auto dh = dropHandler) {
2038 				dropHandler = null;
2039 				enableDragAndDrop(this, dh);
2040 			}
2041 
2042 			if(recreateAdditionalConnectionState)
2043 				recreateAdditionalConnectionState();
2044 
2045 			hidden = wasHidden;
2046 			stateDiscarded = false;
2047 		}
2048 
2049 		bool stateDiscarded;
2050 		void discardConnectionState() {
2051 			if(XDisplayConnection.display)
2052 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2053 			if(discardAdditionalConnectionState)
2054 				discardAdditionalConnectionState();
2055 			stateDiscarded = true;
2056 		}
2057 
2058 		void delegate() discardAdditionalConnectionState;
2059 		void delegate() recreateAdditionalConnectionState;
2060 
2061 	}
2062 
2063 	private DropHandler dropHandler;
2064 
2065 	SimpleWindow _parent;
2066 	bool beingOpenKeepsAppOpen = true;
2067 	/++
2068 		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.
2069 
2070 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2071 
2072 		Params:
2073 
2074 		width = the width of the window's client area, in pixels
2075 		height = the height of the window's client area, in pixels
2076 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2077 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2078 		resizable = [Resizability] has three options:
2079 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2080 			$(P `fixedSize` will not allow the user to resize the window.)
2081 			$(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.)
2082 		windowType = The type of window you want to make.
2083 		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.
2084 		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".
2085 	+/
2086 	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) {
2087 		claimGuiThread();
2088 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2089 		this._width = this._virtualWidth = width;
2090 		this._height = this._virtualHeight = height;
2091 		this.openglMode = opengl;
2092 		version(X11) {
2093 			// auto scale not implemented except with opengl and even there it is kinda weird
2094 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2095 				resizable = Resizability.fixedSize;
2096 		}
2097 		this.resizability = resizable;
2098 		this.windowType = windowType;
2099 		this.customizationFlags = customizationFlags;
2100 		this._title = (title is null ? "D Application" : title);
2101 		this._parent = parent;
2102 		impl.createWindow(width, height, this._title, opengl, parent);
2103 
2104 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2105 			beingOpenKeepsAppOpen = false;
2106 	}
2107 
2108 	/// ditto
2109 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2110 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2111 	}
2112 
2113 	/// Same as above, except using the `Size` struct instead of separate width and height.
2114 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2115 		this(size.width, size.height, title, opengl, resizable);
2116 	}
2117 
2118 	/// ditto
2119 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2120 		this(size, title, opengl, resizable);
2121 	}
2122 
2123 
2124 	/++
2125 		Creates a window based on the given [Image]. It's client area
2126 		width and height is equal to the image. (A window's client area
2127 		is the drawable space inside; it excludes the title bar, etc.)
2128 
2129 		Windows based on images will not be resizable and do not use OpenGL.
2130 
2131 		It will draw the image in upon creation, but this will be overwritten
2132 		upon any draws, including the initial window visible event.
2133 
2134 		You probably do not want to use this and it may be removed from
2135 		the library eventually, or I might change it to be a "permanent"
2136 		background image; one that is automatically drawn on it before any
2137 		other drawing event. idk.
2138 	+/
2139 	this(Image image, string title = null) {
2140 		this(image.width, image.height, title);
2141 		this.image = image;
2142 	}
2143 
2144 	/++
2145 		Wraps a native window handle with very little additional processing - notably no destruction
2146 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2147 		windows created through the low level API (so you can use platform-specific options and
2148 		other details SimpleWindow does not expose) available to the event loop wrappers.
2149 	+/
2150 	this(NativeWindowHandle nativeWindow) {
2151 		windowType = WindowTypes.minimallyWrapped;
2152 		version(Windows)
2153 			impl.hwnd = nativeWindow;
2154 		else version(X11) {
2155 			impl.window = nativeWindow;
2156 			if(nativeWindow)
2157 				display = XDisplayConnection.get(); // get initial display to not segfault
2158 		} else version(OSXCocoa) {
2159 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2160 		} else featureNotImplemented();
2161 		// FIXME: set the size correctly
2162 		_width = 1;
2163 		_height = 1;
2164 		if(nativeWindow)
2165 			nativeMapping[cast(void*) nativeWindow] = this;
2166 
2167 		beingOpenKeepsAppOpen = false;
2168 
2169 		if(nativeWindow)
2170 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2171 		_suppressDestruction = true; // so it doesn't try to close
2172 	}
2173 
2174 	/++
2175 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2176 		The delegate will be called when the window manager asks you to take focus.
2177 
2178 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2179 
2180 		History:
2181 			Added April 1, 2022 (dub v10.8)
2182 	+/
2183 	SimpleWindow delegate() setRequestedInputFocus;
2184 
2185 	/// Experimental, do not use yet
2186 	/++
2187 		Grabs exclusive input from the user until you release it with
2188 		[releaseInputGrab].
2189 
2190 
2191 		Note: it is extremely rude to do this without good reason.
2192 		Reasons may include doing some kind of mouse drag operation
2193 		or popping up a temporary menu that should get events and will
2194 		be dismissed at ease by the user clicking away.
2195 
2196 		Params:
2197 			keyboard = do you want to grab keyboard input?
2198 			mouse = grab mouse input?
2199 			confine = confine the mouse cursor to inside this window?
2200 
2201 		History:
2202 			Prior to March 11, 2021, grabbing the keyboard would always also
2203 			set the X input focus. Now, it only focuses if it is a non-transient
2204 			window and otherwise manages the input direction internally.
2205 
2206 			This means spurious focus/blur events will no longer be sent and the
2207 			application will not steal focus from other applications (which the
2208 			window manager may have rejected anyway).
2209 	+/
2210 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2211 		static if(UsingSimpledisplayX11) {
2212 			XSync(XDisplayConnection.get, 0);
2213 			if(keyboard) {
2214 				if(isTransient && _parent) {
2215 					/*
2216 					FIXME:
2217 						setting the keyboard focus is not actually that helpful, what I more likely want
2218 						is the events from the parent window to be sent over here if we're transient.
2219 					*/
2220 
2221 					_parent.inputProxy = this;
2222 				} else {
2223 
2224 					SimpleWindow setTo;
2225 					if(setRequestedInputFocus !is null)
2226 						setTo = setRequestedInputFocus();
2227 					if(setTo is null)
2228 						setTo = this;
2229 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2230 				}
2231 			}
2232 			if(mouse) {
2233 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2234 				EventMask.PointerMotionMask // FIXME: not efficient
2235 				| EventMask.ButtonPressMask
2236 				| EventMask.ButtonReleaseMask
2237 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2238 				)
2239 			{
2240 				XSync(XDisplayConnection.get, 0);
2241 				import core.stdc.stdio;
2242 				printf("Grab input failed %d\n", res);
2243 				//throw new Exception("Grab input failed");
2244 			} else {
2245 				// cool
2246 			}
2247 			}
2248 
2249 		} else version(Windows) {
2250 			// FIXME: keyboard?
2251 			SetCapture(impl.hwnd);
2252 			if(confine) {
2253 				RECT rcClip;
2254 				//RECT rcOldClip;
2255 				//GetClipCursor(&rcOldClip);
2256 				GetWindowRect(hwnd, &rcClip);
2257 				ClipCursor(&rcClip);
2258 			}
2259 		} else version(OSXCocoa) {
2260 			// throw new NotYetImplementedException();
2261 		} else static assert(0);
2262 	}
2263 
2264 	private Point imePopupLocation = Point(0, 0);
2265 
2266 	/++
2267 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2268 
2269 		Bugs:
2270 			Not implemented outside X11.
2271 	+/
2272 	void setIMEPopupLocation(Point location) {
2273 		static if(UsingSimpledisplayX11) {
2274 			imePopupLocation = location;
2275 			updateIMEPopupLocation();
2276 		} else {
2277 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2278 			// throw new NotYetImplementedException();
2279 		}
2280 	}
2281 
2282 	/// ditto
2283 	void setIMEPopupLocation(int x, int y) {
2284 		return setIMEPopupLocation(Point(x, y));
2285 	}
2286 
2287 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2288 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2289 	// receives a ConfigureNotify event
2290 	private void updateIMEPopupLocation() {
2291 		static if(UsingSimpledisplayX11) {
2292 			if (xic is null) {
2293 				return;
2294 			}
2295 
2296 			XPoint nspot;
2297 			nspot.x = cast(short) imePopupLocation.x;
2298 			nspot.y = cast(short) imePopupLocation.y;
2299 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2300 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2301 			XFree(preeditAttr);
2302 		}
2303 	}
2304 
2305 	private bool imeFocused = true;
2306 
2307 	/++
2308 		Tells the IME whether or not an input field is currently focused in the window.
2309 
2310 		Bugs:
2311 			Not implemented outside X11.
2312 	+/
2313 	void setIMEFocused(bool value) {
2314 		imeFocused = value;
2315 		updateIMEFocused();
2316 	}
2317 
2318 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2319 	private void updateIMEFocused() {
2320 		static if(UsingSimpledisplayX11) {
2321 			if (xic is null) {
2322 				return;
2323 			}
2324 
2325 			if (focused && imeFocused) {
2326 				XSetICFocus(xic);
2327 			} else {
2328 				XUnsetICFocus(xic);
2329 			}
2330 		}
2331 	}
2332 
2333 	/++
2334 		Returns the native window.
2335 
2336 		History:
2337 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2338 			to access it through the `impl` member (which is semi-supported
2339 			but platform specific and here it is simple enough to offer an accessor).
2340 
2341 		Bugs:
2342 			Not implemented outside Windows or X11.
2343 	+/
2344 	NativeWindowHandle nativeWindowHandle() {
2345 		version(X11)
2346 			return impl.window;
2347 		else version(Windows)
2348 			return impl.hwnd;
2349 		else
2350 			throw new NotYetImplementedException();
2351 	}
2352 
2353 	private bool isTransient() {
2354 		with(WindowTypes)
2355 		final switch(windowType) {
2356 			case normal, undecorated, eventOnly:
2357 			case nestedChild, minimallyWrapped:
2358 				return (customizationFlags & WindowFlags.transient) ? true : false;
2359 			case dropdownMenu, popupMenu, notification:
2360 				return true;
2361 		}
2362 	}
2363 
2364 	private SimpleWindow inputProxy;
2365 
2366 	/++
2367 		Releases the grab acquired by [grabInput].
2368 	+/
2369 	void releaseInputGrab() {
2370 		static if(UsingSimpledisplayX11) {
2371 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2372 			if(_parent)
2373 				_parent.inputProxy = null;
2374 		} else version(Windows) {
2375 			ReleaseCapture();
2376 			ClipCursor(null);
2377 		} else version(OSXCocoa) {
2378 			// throw new NotYetImplementedException();
2379 		} else static assert(0);
2380 	}
2381 
2382 	/++
2383 		Sets the input focus to this window.
2384 
2385 		You shouldn't call this very often - please let the user control the input focus.
2386 	+/
2387 	void focus() {
2388 		static if(UsingSimpledisplayX11) {
2389 			SimpleWindow setTo;
2390 			if(setRequestedInputFocus !is null)
2391 				setTo = setRequestedInputFocus();
2392 			if(setTo is null)
2393 				setTo = this;
2394 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2395 		} else version(Windows) {
2396 			SetFocus(this.impl.hwnd);
2397 		} else version(OSXCocoa) {
2398 			throw new NotYetImplementedException();
2399 		} else static assert(0);
2400 	}
2401 
2402 	/++
2403 		Requests attention from the user for this window.
2404 
2405 
2406 		The typical result of this function is to change the color
2407 		of the taskbar icon, though it may be tweaked on specific
2408 		platforms.
2409 
2410 		It is meant to unobtrusively tell the user that something
2411 		relevant to them happened in the background and they should
2412 		check the window when they get a chance. Upon receiving the
2413 		keyboard focus, the window will automatically return to its
2414 		natural state.
2415 
2416 		If the window already has the keyboard focus, this function
2417 		may do nothing, because the user is presumed to already be
2418 		giving the window attention.
2419 
2420 		Implementation_note:
2421 
2422 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2423 		atom on X11 and the FlashWindow function on Windows.
2424 	+/
2425 	void requestAttention() {
2426 		if(_focused)
2427 			return;
2428 
2429 		version(Windows) {
2430 			FLASHWINFO info;
2431 			info.cbSize = info.sizeof;
2432 			info.hwnd = impl.hwnd;
2433 			info.dwFlags = FLASHW_TRAY;
2434 			info.uCount = 1;
2435 
2436 			FlashWindowEx(&info);
2437 
2438 		} else version(X11) {
2439 			demandingAttention = true;
2440 			demandAttention(this, true);
2441 		} else version(OSXCocoa) {
2442 			throw new NotYetImplementedException();
2443 		} else static assert(0);
2444 	}
2445 
2446 	private bool _focused;
2447 
2448 	version(X11) private bool demandingAttention;
2449 
2450 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2451 	/// You'll have to call `close()` manually if you set this delegate.
2452 	void delegate () closeQuery;
2453 
2454 	/// This will be called when window visibility was changed.
2455 	void delegate (bool becomesVisible) visibilityChanged;
2456 
2457 	/// This will be called when window becomes visible for the first time.
2458 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2459 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2460 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2461 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2462 	private bool _visibleForTheFirstTimeCalled;
2463 	void delegate () visibleForTheFirstTime;
2464 
2465 	/// Returns true if the window has been closed.
2466 	final @property bool closed() { return _closed; }
2467 
2468 	private final @property bool notClosed() { return !_closed; }
2469 
2470 	/// Returns true if the window is focused.
2471 	final @property bool focused() { return _focused; }
2472 
2473 	private bool _visible;
2474 	/// Returns true if the window is visible (mapped).
2475 	final @property bool visible() { return _visible; }
2476 
2477 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2478 	void close() {
2479 		if (!_closed) {
2480 			runInGuiThread( {
2481 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2482 				if (onClosing !is null) onClosing();
2483 				impl.closeWindow();
2484 				_closed = true;
2485 			} );
2486 		}
2487 	}
2488 
2489 	/++
2490 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2491 
2492 		History:
2493 			Overload added on March 7, 2021.
2494 	+/
2495 	void close() shared {
2496 		(cast() this).close();
2497 	}
2498 
2499 	/++
2500 
2501 	+/
2502 	void maximize() {
2503 		version(Windows)
2504 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2505 		else version(X11) {
2506 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2507 
2508 			// also note _NET_WM_STATE_FULLSCREEN
2509 		}
2510 
2511 	}
2512 
2513 	private bool _fullscreen;
2514 	version(Windows)
2515 	private WINDOWPLACEMENT g_wpPrev;
2516 
2517 	/// not fully implemented but planned for a future release
2518 	void fullscreen(bool yes) {
2519 		version(Windows) {
2520 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2521 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2522 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2523 				MONITORINFO mi;
2524 				mi.cbSize = MONITORINFO.sizeof;
2525 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2526 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2527 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2528 					SetWindowLong(hwnd, GWL_STYLE,
2529 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2530 					SetWindowPos(hwnd, HWND_TOP,
2531 						     mi.rcMonitor.left, mi.rcMonitor.top,
2532 						     mi.rcMonitor.right - mi.rcMonitor.left,
2533 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2534 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2535 				}
2536 			} else {
2537 				SetWindowLong(hwnd, GWL_STYLE,
2538 					      dwStyle | WS_OVERLAPPEDWINDOW);
2539 				SetWindowPlacement(hwnd, &g_wpPrev);
2540 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2541 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2542 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2543 			}
2544 
2545 		} else version(X11) {
2546 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2547 		}
2548 
2549 		_fullscreen = yes;
2550 
2551 	}
2552 
2553 	bool fullscreen() {
2554 		return _fullscreen;
2555 	}
2556 
2557 	/++
2558 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2559 
2560 	+/
2561 	void minimize() {
2562 		version(Windows)
2563 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2564 		//else version(X11)
2565 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2566 	}
2567 
2568 	/// Alias for `hidden = false`
2569 	void show() {
2570 		hidden = false;
2571 	}
2572 
2573 	/// Alias for `hidden = true`
2574 	void hide() {
2575 		hidden = true;
2576 	}
2577 
2578 	/// Hide cursor when it enters the window.
2579 	void hideCursor() {
2580 		version(OSXCocoa) throw new NotYetImplementedException(); else
2581 		if (!_closed) impl.hideCursor();
2582 	}
2583 
2584 	/// Don't hide cursor when it enters the window.
2585 	void showCursor() {
2586 		version(OSXCocoa) throw new NotYetImplementedException(); else
2587 		if (!_closed) impl.showCursor();
2588 	}
2589 
2590 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2591 	 *
2592 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2593 	 * control. Try to think for other approaches before using this function.
2594 	 *
2595 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2596 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2597 	 *       receive "mouse moved here" event.
2598 	 */
2599 	bool warpMouse (int x, int y) {
2600 		version(X11) {
2601 			if (!_closed) { impl.warpMouse(x, y); return true; }
2602 		} else version(Windows) {
2603 			if (!_closed) {
2604 				POINT point;
2605 				point.x = x;
2606 				point.y = y;
2607 				if(ClientToScreen(impl.hwnd, &point)) {
2608 					SetCursorPos(point.x, point.y);
2609 					return true;
2610 				}
2611 			}
2612 		}
2613 		return false;
2614 	}
2615 
2616 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2617 	void sendDummyEvent () {
2618 		version(X11) {
2619 			if (!_closed) { impl.sendDummyEvent(); }
2620 		}
2621 	}
2622 
2623 	/// Set window minimal size.
2624 	void setMinSize (int minwidth, int minheight) {
2625 		version(OSXCocoa) throw new NotYetImplementedException(); else
2626 		if (!_closed) impl.setMinSize(minwidth, minheight);
2627 	}
2628 
2629 	/// Set window maximal size.
2630 	void setMaxSize (int maxwidth, int maxheight) {
2631 		version(OSXCocoa) throw new NotYetImplementedException(); else
2632 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2633 	}
2634 
2635 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2636 	/// Currently only supported on X11.
2637 	void setResizeGranularity (int granx, int grany) {
2638 		version(OSXCocoa) throw new NotYetImplementedException(); else
2639 		if (!_closed) impl.setResizeGranularity(granx, grany);
2640 	}
2641 
2642 	/// Move window.
2643 	void move(int x, int y) {
2644 		version(OSXCocoa) throw new NotYetImplementedException(); else
2645 		if (!_closed) impl.move(x, y);
2646 	}
2647 
2648 	/// ditto
2649 	void move(Point p) {
2650 		version(OSXCocoa) throw new NotYetImplementedException(); else
2651 		if (!_closed) impl.move(p.x, p.y);
2652 	}
2653 
2654 	/++
2655 		Resize window.
2656 
2657 		Note that the width and height of the window are NOT instantly
2658 		updated - it waits for the window manager to approve the resize
2659 		request, which means you must return to the event loop before the
2660 		width and height are actually changed.
2661 	+/
2662 	void resize(int w, int h) {
2663 		if(!_closed && _fullscreen) fullscreen = false;
2664 		version(OSXCocoa) throw new NotYetImplementedException(); else
2665 		if (!_closed) impl.resize(w, h);
2666 	}
2667 
2668 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2669 	void moveResize (int x, int y, int w, int h) {
2670 		if(!_closed && _fullscreen) fullscreen = false;
2671 		version(OSXCocoa) throw new NotYetImplementedException(); else
2672 		if (!_closed) impl.moveResize(x, y, w, h);
2673 	}
2674 
2675 	private bool _hidden;
2676 
2677 	/// Returns true if the window is hidden.
2678 	final @property bool hidden() {
2679 		return _hidden;
2680 	}
2681 
2682 	/// Shows or hides the window based on the bool argument.
2683 	final @property void hidden(bool b) {
2684 		_hidden = b;
2685 		version(Windows) {
2686 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2687 		} else version(X11) {
2688 			if(b)
2689 				//XUnmapWindow(impl.display, impl.window);
2690 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2691 			else
2692 				XMapWindow(impl.display, impl.window);
2693 		} else version(OSXCocoa) {
2694 			// throw new NotYetImplementedException();
2695 		} else static assert(0);
2696 	}
2697 
2698 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2699 	void opacity(double opacity) @property
2700 	in {
2701 		assert(opacity >= 0 && opacity <= 1);
2702 	} do {
2703 		version (Windows) {
2704 			impl.setOpacity(cast(ubyte)(255 * opacity));
2705 		} else version (X11) {
2706 			impl.setOpacity(cast(uint)(uint.max * opacity));
2707 		} else throw new NotYetImplementedException();
2708 	}
2709 
2710 	/++
2711 		Sets your event handlers, without entering the event loop. Useful if you
2712 		have multiple windows - set the handlers on each window, then only do
2713 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2714 
2715 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2716 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2717 		delegate signatures.
2718 	+/
2719 	void setEventHandlers(T...)(T eventHandlers) {
2720 		// FIXME: add more events
2721 		foreach(handler; eventHandlers) {
2722 			static if(__traits(compiles, handleKeyEvent = handler)) {
2723 				handleKeyEvent = handler;
2724 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2725 				handleCharEvent = handler;
2726 			} else static if(__traits(compiles, handlePulse = handler)) {
2727 				handlePulse = handler;
2728 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2729 				handleMouseEvent = handler;
2730 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2731 		}
2732 	}
2733 
2734 	/++
2735 		The event loop automatically returns when the window is closed
2736 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2737 		pulse timer is created. The event loop will block until an event
2738 		arrives or the pulse timer goes off.
2739 
2740 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2741 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2742 		[handleMouseEvent], based on the signature of delegates you provide.
2743 
2744 		Give one with no parameters to set a timer pulse handler. Give one that
2745 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2746 		and one that takes `dchar` for a char event handler. You can use as many
2747 		or as few handlers as you need for your application.
2748 
2749 		Bugs:
2750 
2751 		$(PITFALL
2752 			You should always have one event loop live for your application.
2753 			If you make two windows in sequence, the second call to eventLoop
2754 			might fail:
2755 
2756 			---
2757 			// don't do this!
2758 			auto window = new SimpleWindow();
2759 			window.eventLoop(0);
2760 
2761 			auto window2 = new SimpleWindow();
2762 			window2.eventLoop(0); // problematic! might crash
2763 			---
2764 
2765 			simpledisplay's current implementation assumes that final cleanup is
2766 			done when the event loop refcount reaches zero. So after the first
2767 			eventLoop returns, when there isn't already another one active, it assumes
2768 			the program will exit soon and cleans up.
2769 
2770 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2771 			it eventually, but in the mean time, there's an easy solution:
2772 
2773 			---
2774 			// do this
2775 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2776 
2777 			auto window = new SimpleWindow();
2778 			window.eventLoop(0);
2779 
2780 			auto window2 = new SimpleWindow();
2781 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2782 			---
2783 
2784 			By adding a top-level reference to the event loop, it ensures the final cleanup
2785 			is not performed until it goes out of scope too, letting the individual window loops
2786 			work without trouble despite the bug.
2787 		)
2788 
2789 		History:
2790 			The overload without `pulseTimeout` was added on December 8, 2021.
2791 
2792 			On December 9, 2021, the default blocking mode (which is now configurable
2793 			because [eventLoopWithBlockingMode] was added) switched from
2794 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2795 			should almost never be noticeable to you since the typical simpledisplay
2796 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2797 
2798 		See_Also:
2799 			[eventLoopWithBlockingMode]
2800 	+/
2801 	final int eventLoop(T...)(
2802 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2803 		T eventHandlers) /// delegate list like std.concurrency.receive
2804 	{
2805 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2806 	}
2807 
2808 	/// ditto
2809 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2810 	{
2811 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2812 	}
2813 
2814 	/++
2815 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2816 
2817 		History:
2818 			Added December 8, 2021 (dub v10.5)
2819 
2820 			Previously, this implementation was right inside [eventLoop], but when I wanted
2821 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2822 			just renamed it instead of adding as an overload. Besides, the new name makes it
2823 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2824 
2825 		See_Also:
2826 			[SimpleWindow.eventLoop], [EventLoop]
2827 
2828 		Bugs:
2829 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2830 	+/
2831 	final int eventLoopWithBlockingMode(T...)(
2832 		BlockingMode blockingMode, /// when you want this function to block until
2833 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2834 		T eventHandlers) /// delegate list like std.concurrency.receive
2835 	{
2836 		setEventHandlers(eventHandlers);
2837 
2838 		version(with_eventloop) {
2839 			// delegates event loop to my other module
2840 			version(X11)
2841 				XFlush(display);
2842 
2843 			import arsd.eventloop;
2844 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2845 			scope(exit) clearInterval(handle);
2846 
2847 			loop();
2848 			return 0;
2849 		} else version(OSXCocoa) {
2850 			// FIXME
2851 			if (handlePulse !is null && pulseTimeout != 0) {
2852 				timer = NSTimer.schedule(pulseTimeout*1e-3,
2853 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
2854 					null, true);
2855 			}
2856 
2857 			view.setNeedsDisplay(true);
2858 
2859 			NSApp.run();
2860             		return 0;
2861         	} else {
2862 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2863 
2864 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2865 				return 0;
2866 
2867 			return el.run(
2868 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2869 					null :
2870 					&this.notClosed
2871 			);
2872 		}
2873 	}
2874 
2875 	/++
2876 		This lets you draw on the window (or its backing buffer) using basic
2877 		2D primitives.
2878 
2879 		Be sure to call this in a limited scope because your changes will not
2880 		actually appear on the window until ScreenPainter's destructor runs.
2881 
2882 		Returns: an instance of [ScreenPainter], which has the drawing methods
2883 		on it to draw on this window.
2884 
2885 		Params:
2886 			manualInvalidations = if you set this to true, you will need to
2887 			set the invalid rectangle on the painter yourself. If false, it
2888 			assumes the whole window has been redrawn each time you draw.
2889 
2890 			Only invalidated rectangles are blitted back to the window when
2891 			the destructor runs. Doing this yourself can reduce flickering
2892 			of child windows.
2893 
2894 		History:
2895 			The `manualInvalidations` parameter overload was added on
2896 			December 30, 2021 (dub v10.5)
2897 	+/
2898 	ScreenPainter draw() {
2899 		return draw(false);
2900 	}
2901 	/// ditto
2902 	ScreenPainter draw(bool manualInvalidations) {
2903 		return impl.getPainter(manualInvalidations);
2904 	}
2905 
2906 	// This is here to implement the interface we use for various native handlers.
2907 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2908 
2909 	// maps native window handles to SimpleWindow instances, if there are any
2910 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2911 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
2912 	version(OSXCocoa)
2913 	public __gshared SimpleWindow[void*] nativeMapping;
2914 	else
2915 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2916 
2917 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
2918 	private int _virtualWidth;
2919 	private int _virtualHeight;
2920 
2921 	/// Width of the window's drawable client area, in pixels.
2922 	@scriptable
2923 	final @property int width() const pure nothrow @safe @nogc {
2924 		if(resizability == Resizability.automaticallyScaleIfPossible)
2925 			return _virtualWidth;
2926 		else
2927 			return _width;
2928 	}
2929 
2930 	/// Height of the window's drawable client area, in pixels.
2931 	@scriptable
2932 	final @property int height() const pure nothrow @safe @nogc {
2933 		if(resizability == Resizability.automaticallyScaleIfPossible)
2934 			return _virtualHeight;
2935 		else
2936 			return _height;
2937 	}
2938 
2939 	/++
2940 		Returns the actual size of the window, bypassing the logical
2941 		illusions of [Resizability.automaticallyScaleIfPossible].
2942 
2943 		History:
2944 			Added November 11, 2022 (dub v10.10)
2945 	+/
2946 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
2947 		return Size(_width, _height);
2948 	}
2949 
2950 
2951 	private int _width;
2952 	private int _height;
2953 
2954 	// HACK: making the best of some copy constructor woes with refcounting
2955 	private ScreenPainterImplementation* activeScreenPainter_;
2956 
2957 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2958 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2959 
2960 	private OpenGlOptions openglMode;
2961 	private Resizability resizability;
2962 	private WindowTypes windowType;
2963 	private int customizationFlags;
2964 
2965 	/// `true` if OpenGL was initialized for this window.
2966 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2967 		version(without_opengl)
2968 			return false;
2969 		else
2970 			return (openglMode == OpenGlOptions.yes);
2971 	}
2972 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2973 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2974 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2975 
2976 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2977 	/// to call this, as it's not recommended to share window between threads.
2978 	void mtLock () {
2979 		version(X11) {
2980 			XLockDisplay(this.display);
2981 		}
2982 	}
2983 
2984 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2985 	/// to call this, as it's not recommended to share window between threads.
2986 	void mtUnlock () {
2987 		version(X11) {
2988 			XUnlockDisplay(this.display);
2989 		}
2990 	}
2991 
2992 	/// Emit a beep to get user's attention.
2993 	void beep () {
2994 		version(X11) {
2995 			XBell(this.display, 100);
2996 		} else version(Windows) {
2997 			MessageBeep(0xFFFFFFFF);
2998 		}
2999 	}
3000 
3001 
3002 
3003 	version(without_opengl) {} else {
3004 
3005 		/// 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`.
3006 		void delegate() redrawOpenGlScene;
3007 
3008 		/// This will allow you to change OpenGL vsync state.
3009 		final @property void vsync (bool wait) {
3010 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3011 		  version(X11) {
3012 		    setAsCurrentOpenGlContext();
3013 		    glxSetVSync(display, impl.window, wait);
3014 		  } else version(Windows) {
3015 		    setAsCurrentOpenGlContext();
3016                     wglSetVSync(wait);
3017 		  }
3018 		}
3019 
3020 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3021 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3022 		/// enough without waiting 'em to finish their frame business.
3023 		bool useGLFinish = true;
3024 
3025 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3026 		/// 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.
3027 		void redrawOpenGlSceneNow() {
3028 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3029 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3030 			if(redrawOpenGlScene is null)
3031 				return;
3032 
3033 			this.mtLock();
3034 			scope(exit) this.mtUnlock();
3035 
3036 			this.setAsCurrentOpenGlContext();
3037 
3038 			redrawOpenGlScene();
3039 
3040 			this.swapOpenGlBuffers();
3041 			// 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.
3042 			if (useGLFinish) glFinish();
3043 		}
3044 
3045 		private bool redrawOpenGlSceneSoonSet = false;
3046 		private static class RedrawOpenGlSceneEvent {
3047 			SimpleWindow w;
3048 			this(SimpleWindow w) { this.w = w; }
3049 		}
3050 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3051 		/++
3052 			Queues an opengl redraw as soon as the other pending events are cleared.
3053 		+/
3054 		void redrawOpenGlSceneSoon() {
3055 			if(redrawOpenGlScene is null)
3056 				return;
3057 
3058 			if(!redrawOpenGlSceneSoonSet) {
3059 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3060 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3061 				redrawOpenGlSceneSoonSet = true;
3062 			}
3063 			this.postEvent(redrawOpenGlSceneEvent, true);
3064 		}
3065 
3066 
3067 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3068 		void setAsCurrentOpenGlContext() {
3069 			assert(openglMode == OpenGlOptions.yes);
3070 			version(X11) {
3071 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3072 					throw new Exception("glXMakeCurrent");
3073 			} else version(Windows) {
3074 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3075 				if (!wglMakeCurrent(ghDC, ghRC))
3076 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3077 			}
3078 		}
3079 
3080 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3081 		/// This doesn't throw, returning success flag instead.
3082 		bool setAsCurrentOpenGlContextNT() nothrow {
3083 			assert(openglMode == OpenGlOptions.yes);
3084 			version(X11) {
3085 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3086 			} else version(Windows) {
3087 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3088 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3089 			}
3090 		}
3091 
3092 		/// 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.
3093 		/// This doesn't throw, returning success flag instead.
3094 		bool releaseCurrentOpenGlContext() nothrow {
3095 			assert(openglMode == OpenGlOptions.yes);
3096 			version(X11) {
3097 				return (glXMakeCurrent(display, 0, null) != 0);
3098 			} else version(Windows) {
3099 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3100 				return wglMakeCurrent(ghDC, null) ? true : false;
3101 			}
3102 		}
3103 
3104 		/++
3105 			simpledisplay always uses double buffering, usually automatically. This
3106 			manually swaps the OpenGL buffers.
3107 
3108 
3109 			You should not need to call this yourself because simpledisplay will do it
3110 			for you after calling your `redrawOpenGlScene`.
3111 
3112 			Remember that this may throw an exception, which you can catch in a multithreaded
3113 			application to keep your thread from dying from an unhandled exception.
3114 		+/
3115 		void swapOpenGlBuffers() {
3116 			assert(openglMode == OpenGlOptions.yes);
3117 			version(X11) {
3118 				if (!this._visible) return; // no need to do this if window is invisible
3119 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3120 				glXSwapBuffers(display, impl.window);
3121 			} else version(Windows) {
3122 				SwapBuffers(ghDC);
3123 			}
3124 		}
3125 	}
3126 
3127 	/++
3128 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3129 
3130 
3131 		---
3132 			auto window = new SimpleWindow(100, 100, "First title");
3133 			window.title = "A new title";
3134 		---
3135 
3136 		You may call this function at any time.
3137 	+/
3138 	@property void title(string title) {
3139 		_title = title;
3140 		version(OSXCocoa) throw new NotYetImplementedException(); else
3141 		impl.setTitle(title);
3142 	}
3143 
3144 	private string _title;
3145 
3146 	/// Gets the title
3147 	@property string title() {
3148 		if(_title is null)
3149 			_title = getRealTitle();
3150 		return _title;
3151 	}
3152 
3153 	/++
3154 		Get the title as set by the window manager.
3155 		May not match what you attempted to set.
3156 	+/
3157 	string getRealTitle() {
3158 		static if(is(typeof(impl.getTitle())))
3159 			return impl.getTitle();
3160 		else
3161 			return null;
3162 	}
3163 
3164 	// don't use this generally it is not yet really released
3165 	version(X11)
3166 	@property Image secret_icon() {
3167 		return secret_icon_inner;
3168 	}
3169 	private Image secret_icon_inner;
3170 
3171 
3172 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3173 	@property void icon(MemoryImage icon) {
3174 		if(icon is null)
3175 			return;
3176 		auto tci = icon.getAsTrueColorImage();
3177 		version(Windows) {
3178 			winIcon = new WindowsIcon(icon);
3179 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3180 		} else version(X11) {
3181 			secret_icon_inner = Image.fromMemoryImage(icon);
3182 			// FIXME: ensure this is correct
3183 			auto display = XDisplayConnection.get;
3184 			arch_ulong[] buffer;
3185 			buffer ~= icon.width;
3186 			buffer ~= icon.height;
3187 			foreach(c; tci.imageData.colors) {
3188 				arch_ulong b;
3189 				b |= c.a << 24;
3190 				b |= c.r << 16;
3191 				b |= c.g << 8;
3192 				b |= c.b;
3193 				buffer ~= b;
3194 			}
3195 
3196 			XChangeProperty(
3197 				display,
3198 				impl.window,
3199 				GetAtom!("_NET_WM_ICON", true)(display),
3200 				GetAtom!"CARDINAL"(display),
3201 				32 /* bits */,
3202 				0 /*PropModeReplace*/,
3203 				buffer.ptr,
3204 				cast(int) buffer.length);
3205 		} else version(OSXCocoa) {
3206 			throw new NotYetImplementedException();
3207 		} else static assert(0);
3208 	}
3209 
3210 	version(Windows)
3211 		private WindowsIcon winIcon;
3212 
3213 	bool _suppressDestruction;
3214 
3215 	~this() {
3216 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3217 		if(_suppressDestruction)
3218 			return;
3219 		impl.dispose();
3220 	}
3221 
3222 	private bool _closed;
3223 
3224 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3225 	/*
3226 	ScreenPainter drawTransiently() {
3227 		return impl.getPainter();
3228 	}
3229 	*/
3230 
3231 	/// Draws an image on the window. This is meant to provide quick look
3232 	/// of a static image generated elsewhere.
3233 	@property void image(Image i) {
3234 	/+
3235 		version(Windows) {
3236 			BITMAP bm;
3237 			HDC hdc = GetDC(hwnd);
3238 			HDC hdcMem = CreateCompatibleDC(hdc);
3239 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3240 
3241 			GetObject(i.handle, bm.sizeof, &bm);
3242 
3243 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3244 
3245 			SelectObject(hdcMem, hbmOld);
3246 			DeleteDC(hdcMem);
3247 			ReleaseDC(hwnd, hdc);
3248 
3249 			/*
3250 			RECT r;
3251 			r.right = i.width;
3252 			r.bottom = i.height;
3253 			InvalidateRect(hwnd, &r, false);
3254 			*/
3255 		} else
3256 		version(X11) {
3257 			if(!destroyed) {
3258 				if(i.usingXshm)
3259 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3260 				else
3261 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3262 			}
3263 		} else
3264 		version(OSXCocoa) {
3265 			draw().drawImage(Point(0, 0), i);
3266 			setNeedsDisplay(view, true);
3267 		} else static assert(0);
3268 	+/
3269 		auto painter = this.draw;
3270 		painter.drawImage(Point(0, 0), i);
3271 	}
3272 
3273 	/++
3274 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3275 
3276 		---
3277 		window.cursor = GenericCursor.Help;
3278 		// now the window mouse cursor is set to a generic help
3279 		---
3280 
3281 	+/
3282 	@property void cursor(MouseCursor cursor) {
3283 		version(OSXCocoa)
3284 			{} // featureNotImplemented();
3285 		else
3286 		if(this.impl.curHidden <= 0) {
3287 			static if(UsingSimpledisplayX11) {
3288 				auto ch = cursor.cursorHandle;
3289 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3290 			} else version(Windows) {
3291 				auto ch = cursor.cursorHandle;
3292 				impl.currentCursor = ch;
3293 				SetCursor(ch); // redraw without waiting for mouse movement to update
3294 			} else featureNotImplemented();
3295 		}
3296 
3297 	}
3298 
3299 	/// What follows are the event handlers. These are set automatically
3300 	/// by the eventLoop function, but are still public so you can change
3301 	/// them later. wasPressed == true means key down. false == key up.
3302 
3303 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3304 	void delegate(KeyEvent ke) handleKeyEvent;
3305 
3306 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3307 	void delegate(dchar c) handleCharEvent;
3308 
3309 	/// Handles a timer pulse. Settable through setEventHandlers.
3310 	void delegate() handlePulse;
3311 
3312 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3313 	void delegate(bool) onFocusChange;
3314 
3315 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3316 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3317 	void delegate() onClosing;
3318 
3319 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3320 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3321 	 * last minute cleanup. */
3322 	void delegate() onDestroyed;
3323 
3324 	static if (UsingSimpledisplayX11)
3325 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3326 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3327 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3328 	 *
3329 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3330 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3331 
3332 	//version(Windows)
3333 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3334 
3335 	private {
3336 		int lastMouseX = int.min;
3337 		int lastMouseY = int.min;
3338 		void mdx(ref MouseEvent ev) {
3339 			if(lastMouseX == int.min || lastMouseY == int.min) {
3340 				ev.dx = 0;
3341 				ev.dy = 0;
3342 			} else {
3343 				ev.dx = ev.x - lastMouseX;
3344 				ev.dy = ev.y - lastMouseY;
3345 			}
3346 
3347 			lastMouseX = ev.x;
3348 			lastMouseY = ev.y;
3349 		}
3350 	}
3351 
3352 	/// Mouse event handler. Settable through setEventHandlers.
3353 	void delegate(MouseEvent) handleMouseEvent;
3354 
3355 	/// use to redraw child widgets if you use system apis to add stuff
3356 	void delegate() paintingFinished;
3357 
3358 	void delegate() paintingFinishedDg() {
3359 		return paintingFinished;
3360 	}
3361 
3362 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3363 	/// for this to ever happen.
3364 	void delegate(int width, int height) windowResized;
3365 
3366 	/++
3367 		Platform specific - handle any native message this window gets.
3368 
3369 		Note: this is called *in addition to* other event handlers, unless you either:
3370 
3371 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3372 
3373 		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.
3374 
3375 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3376 
3377 		On X, it takes the form of `int delegate(XEvent)`.
3378 
3379 		History:
3380 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3381 
3382 			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.
3383 	+/
3384 	NativeEventHandler handleNativeEvent_;
3385 
3386 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3387 		return handleNativeEvent_;
3388 	}
3389 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3390 		handleNativeEvent_ = neh;
3391 	}
3392 
3393 	version(Windows)
3394 	// compatibility shim with the old deprecated way
3395 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3396 	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) {
3397 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3398 			auto ret = dg(h, m, w, l);
3399 			if(ret == 0)
3400 				r = 1;
3401 			return ret;
3402 		};
3403 	}
3404 
3405 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3406 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3407 	/// this instead and it will work the same way.
3408 	__gshared NativeEventHandler handleNativeGlobalEvent;
3409 
3410 //  private:
3411 	/// The native implementation is available, but you shouldn't use it unless you are
3412 	/// familiar with the underlying operating system, don't mind depending on it, and
3413 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3414 	/// do what you need to do with handleNativeEvent instead.
3415 	///
3416 	/// This is likely to eventually change to be just a struct holding platform-specific
3417 	/// handles instead of a template mixin at some point because I'm not happy with the
3418 	/// code duplication here (ironically).
3419 	mixin NativeSimpleWindowImplementation!() impl;
3420 
3421 	/**
3422 		This is in-process one-way (from anything to window) event sending mechanics.
3423 		It is thread-safe, so it can be used in multi-threaded applications to send,
3424 		for example, "wake up and repaint" events when thread completed some operation.
3425 		This will allow to avoid using timer pulse to check events with synchronization,
3426 		'cause event handler will be called in UI thread. You can stop guessing which
3427 		pulse frequency will be enough for your app.
3428 		Note that events handlers may be called in arbitrary order, i.e. last registered
3429 		handler can be called first, and vice versa.
3430 	*/
3431 public:
3432 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3433 	 * "spamming" window with events it can't cope with.
3434 	 * It is safe to call this from non-UI threads.
3435 	 */
3436 	@property bool eventQueueEmpty() () {
3437 		synchronized(this) {
3438 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3439 		}
3440 		return true;
3441 	}
3442 
3443 	/** Does our custom event queue contains at least one with the given type?
3444 	 * Can be used in simple cases to prevent "spamming" window with events
3445 	 * it can't cope with.
3446 	 * It is safe to call this from non-UI threads.
3447 	 */
3448 	@property bool eventQueued(ET:Object) () {
3449 		synchronized(this) {
3450 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3451 				if (!o.doProcess) {
3452 					if (cast(ET)(o.evt)) return true;
3453 				}
3454 			}
3455 		}
3456 		return false;
3457 	}
3458 
3459 	/++
3460 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3461 
3462 		History:
3463 			Added May 12, 2021
3464 	+/
3465 	void delegate(Exception e) nothrow eventUncaughtException;
3466 
3467 	/** Add listener for custom event. Can be used like this:
3468 	 *
3469 	 * ---------------------
3470 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3471 	 *   ...
3472 	 *   win.removeEventListener(eid);
3473 	 * ---------------------
3474 	 *
3475 	 * Returns: 0 on failure (should never happen, so ignore it)
3476 	 *
3477 	 * $(WARNING Don't use this method in object destructors!)
3478 	 *
3479 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3480 	 *           'cause if event handler id counter will overflow, you won't be able
3481 	 *           to register any more events.)
3482 	 */
3483 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3484 		if (dg is null) return 0; // ignore empty handlers
3485 		synchronized(this) {
3486 			//FIXME: abort on overflow?
3487 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3488 			EventHandlerEntry e;
3489 			e.dg = delegate (Object o) {
3490 				if (auto co = cast(ET)o) {
3491 					try {
3492 						dg(co);
3493 					} catch (Exception e) {
3494 						// sorry!
3495 						if(eventUncaughtException)
3496 							eventUncaughtException(e);
3497 					}
3498 					return true;
3499 				}
3500 				return false;
3501 			};
3502 			e.id = lastUsedHandlerId;
3503 			auto optr = eventHandlers.ptr;
3504 			eventHandlers ~= e;
3505 			if (eventHandlers.ptr !is optr) {
3506 				import core.memory : GC;
3507 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3508 			}
3509 			return lastUsedHandlerId;
3510 		}
3511 	}
3512 
3513 	/// Remove event listener. It is safe to pass invalid event id here.
3514 	/// $(WARNING Don't use this method in object destructors!)
3515 	void removeEventListener() (uint id) {
3516 		if (id == 0 || id > lastUsedHandlerId) return;
3517 		synchronized(this) {
3518 			foreach (immutable idx; 0..eventHandlers.length) {
3519 				if (eventHandlers[idx].id == id) {
3520 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3521 					eventHandlers[$-1].dg = null;
3522 					eventHandlers.length -= 1;
3523 					eventHandlers.assumeSafeAppend;
3524 					return;
3525 				}
3526 			}
3527 		}
3528 	}
3529 
3530 	/// Post event to queue. It is safe to call this from non-UI threads.
3531 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3532 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3533 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3534 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3535 		if (this.closed) return false; // closed windows can't handle events
3536 
3537 		// remove all events of type `ET`
3538 		void removeAllET () {
3539 			uint eidx = 0, ec = eventQueueUsed;
3540 			auto eptr = eventQueue.ptr;
3541 			while (eidx < ec) {
3542 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3543 				if (cast(ET)eptr.evt !is null) {
3544 					// i found her!
3545 					if (inCustomEventProcessor) {
3546 						// if we're in custom event processing loop, processor will clear it for us
3547 						eptr.evt = null;
3548 						++eidx;
3549 						++eptr;
3550 					} else {
3551 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3552 						ec = --eventQueueUsed;
3553 						// clear last event (it is already copied)
3554 						eventQueue.ptr[ec].evt = null;
3555 					}
3556 				} else {
3557 					++eidx;
3558 					++eptr;
3559 				}
3560 			}
3561 		}
3562 
3563 		if (evt is null) {
3564 			if (replace) { synchronized(this) removeAllET(); }
3565 			// ignore empty events, they can't be handled anyway
3566 			return false;
3567 		}
3568 
3569 		// add events even if no event FD/event object created yet
3570 		synchronized(this) {
3571 			if (replace) removeAllET();
3572 			if (eventQueueUsed == uint.max) return false; // just in case
3573 			if (eventQueueUsed < eventQueue.length) {
3574 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3575 			} else {
3576 				if (eventQueue.capacity == eventQueue.length) {
3577 					// need to reallocate; do a trick to ensure that old array is cleared
3578 					auto oarr = eventQueue;
3579 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3580 					// just in case, do yet another check
3581 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3582 					import core.memory : GC;
3583 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3584 				} else {
3585 					auto optr = eventQueue.ptr;
3586 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3587 					assert(eventQueue.ptr is optr);
3588 				}
3589 				++eventQueueUsed;
3590 				assert(eventQueueUsed == eventQueue.length);
3591 			}
3592 			if (!eventWakeUp()) {
3593 				// can't wake up event processor, so there is no reason to keep the event
3594 				assert(eventQueueUsed > 0);
3595 				eventQueue[--eventQueueUsed].evt = null;
3596 				return false;
3597 			}
3598 			return true;
3599 		}
3600 	}
3601 
3602 	/// Post event to queue. It is safe to call this from non-UI threads.
3603 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3604 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3605 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3606 		return postTimeout!ET(evt, 0, replace);
3607 	}
3608 
3609 private:
3610 	private import core.time : MonoTime;
3611 
3612 	version(Posix) {
3613 		__gshared int customEventFDRead = -1;
3614 		__gshared int customEventFDWrite = -1;
3615 		__gshared int customSignalFD = -1;
3616 	} else version(Windows) {
3617 		__gshared HANDLE customEventH = null;
3618 	}
3619 
3620 	// wake up event processor
3621 	static bool eventWakeUp () {
3622 		version(X11) {
3623 			import core.sys.posix.unistd : write;
3624 			ulong n = 1;
3625 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3626 			return true;
3627 		} else version(Windows) {
3628 			if (customEventH !is null) SetEvent(customEventH);
3629 			return true;
3630 		} else version(OSXCocoa) {
3631 			if(globalAppDelegate)
3632 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3633 			return true;
3634 		} else {
3635 			// not implemented for other OSes
3636 			return false;
3637 		}
3638 	}
3639 
3640 	static struct QueuedEvent {
3641 		Object evt;
3642 		bool timed = false;
3643 		MonoTime hittime = MonoTime.zero;
3644 		bool doProcess = false; // process event at the current iteration (internal flag)
3645 
3646 		this (Object aevt, uint toutmsecs) {
3647 			evt = aevt;
3648 			if (toutmsecs > 0) {
3649 				import core.time : msecs;
3650 				timed = true;
3651 				hittime = MonoTime.currTime+toutmsecs.msecs;
3652 			}
3653 		}
3654 	}
3655 
3656 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3657 	static struct EventHandlerEntry {
3658 		CustomEventHandler dg;
3659 		uint id;
3660 	}
3661 
3662 	uint lastUsedHandlerId;
3663 	EventHandlerEntry[] eventHandlers;
3664 	QueuedEvent[] eventQueue = null;
3665 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3666 	bool inCustomEventProcessor = false; // required to properly remove events
3667 
3668 	// process queued events and call custom event handlers
3669 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3670 	void processCustomEvents () {
3671 		bool hasSomethingToDo = false;
3672 		uint ecount;
3673 		bool ocep;
3674 		synchronized(this) {
3675 			ocep = inCustomEventProcessor;
3676 			inCustomEventProcessor = true;
3677 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3678 			auto ctt = MonoTime.currTime;
3679 			bool hasEmpty = false;
3680 			// mark events to process (this is required for `eventQueued()`)
3681 			foreach (ref qe; eventQueue[0..ecount]) {
3682 				if (qe.evt is null) { hasEmpty = true; continue; }
3683 				if (qe.timed) {
3684 					qe.doProcess = (qe.hittime <= ctt);
3685 				} else {
3686 					qe.doProcess = true;
3687 				}
3688 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3689 			}
3690 			if (!hasSomethingToDo) {
3691 				// remove empty events
3692 				if (hasEmpty) {
3693 					uint eidx = 0, ec = eventQueueUsed;
3694 					auto eptr = eventQueue.ptr;
3695 					while (eidx < ec) {
3696 						if (eptr.evt is null) {
3697 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3698 							ec = --eventQueueUsed;
3699 							eventQueue.ptr[ec].evt = null; // make GC life easier
3700 						} else {
3701 							++eidx;
3702 							++eptr;
3703 						}
3704 					}
3705 				}
3706 				inCustomEventProcessor = ocep;
3707 				return;
3708 			}
3709 		}
3710 		// process marked events
3711 		uint efree = 0; // non-processed events will be put at this index
3712 		EventHandlerEntry[] eh;
3713 		Object evt;
3714 		foreach (immutable eidx; 0..ecount) {
3715 			synchronized(this) {
3716 				if (!eventQueue[eidx].doProcess) {
3717 					// skip this event
3718 					assert(efree <= eidx);
3719 					if (efree != eidx) {
3720 						// copy this event to queue start
3721 						eventQueue[efree] = eventQueue[eidx];
3722 						eventQueue[eidx].evt = null; // just in case
3723 					}
3724 					++efree;
3725 					continue;
3726 				}
3727 				evt = eventQueue[eidx].evt;
3728 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3729 				if (evt is null) continue; // just in case
3730 				// try all handlers; this can be slow, but meh...
3731 				eh = eventHandlers;
3732 			}
3733 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3734 			evt = null;
3735 			eh = null;
3736 		}
3737 		synchronized(this) {
3738 			// move all unprocessed events to queue top; efree holds first "free index"
3739 			foreach (immutable eidx; ecount..eventQueueUsed) {
3740 				assert(efree <= eidx);
3741 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3742 				++efree;
3743 			}
3744 			eventQueueUsed = efree;
3745 			// wake up event processor on next event loop iteration if we have more queued events
3746 			// also, remove empty events
3747 			bool awaken = false;
3748 			uint eidx = 0, ec = eventQueueUsed;
3749 			auto eptr = eventQueue.ptr;
3750 			while (eidx < ec) {
3751 				if (eptr.evt is null) {
3752 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3753 					ec = --eventQueueUsed;
3754 					eventQueue.ptr[ec].evt = null; // make GC life easier
3755 				} else {
3756 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3757 					++eidx;
3758 					++eptr;
3759 				}
3760 			}
3761 			inCustomEventProcessor = ocep;
3762 		}
3763 	}
3764 
3765 	// for all windows in nativeMapping
3766 	package static void processAllCustomEvents () {
3767 
3768 		cleanupQueue.process();
3769 
3770 		justCommunication.processCustomEvents();
3771 
3772 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3773 			if (sw is null || sw.closed) continue;
3774 			sw.processCustomEvents();
3775 		}
3776 
3777 		runPendingRunInGuiThreadDelegates();
3778 	}
3779 
3780 	// 0: infinite (i.e. no scheduled events in queue)
3781 	uint eventQueueTimeoutMSecs () {
3782 		synchronized(this) {
3783 			if (eventQueueUsed == 0) return 0;
3784 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3785 			uint res = int.max;
3786 			auto ctt = MonoTime.currTime;
3787 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3788 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3789 				if (qe.doProcess) continue; // just in case
3790 				if (!qe.timed) return 1; // minimal
3791 				if (qe.hittime <= ctt) return 1; // minimal
3792 				auto tms = (qe.hittime-ctt).total!"msecs";
3793 				if (tms < 1) tms = 1; // safety net
3794 				if (tms >= int.max) tms = int.max-1; // and another safety net
3795 				if (res > tms) res = cast(uint)tms;
3796 			}
3797 			return (res >= int.max ? 0 : res);
3798 		}
3799 	}
3800 
3801 	// for all windows in nativeMapping
3802 	static uint eventAllQueueTimeoutMSecs () {
3803 		uint res = uint.max;
3804 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3805 			if (sw is null || sw.closed) continue;
3806 			uint to = sw.eventQueueTimeoutMSecs();
3807 			if (to && to < res) {
3808 				res = to;
3809 				if (to == 1) break; // can't have less than this
3810 			}
3811 		}
3812 		return (res >= int.max ? 0 : res);
3813 	}
3814 
3815 	version(X11) {
3816 		ResizeEvent pendingResizeEvent;
3817 	}
3818 
3819 	/++
3820 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3821 
3822 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3823 		worth so you can disable it by setting this to `true`.
3824 
3825 		History:
3826 			Added November 13, 2022.
3827 	+/
3828 	public bool suppressAutoOpenglViewport = false;
3829 	private void updateOpenglViewportIfNeeded(int width, int height) {
3830 		if(suppressAutoOpenglViewport) return;
3831 
3832 		version(without_opengl) {} else
3833 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3834 		// writeln(width, " ", height);
3835 			setAsCurrentOpenGlContextNT();
3836 			glViewport(0, 0, width, height);
3837 		}
3838 	}
3839 }
3840 
3841 version(OSXCocoa)
3842 	enum NSWindow NullWindow = null;
3843 else
3844 	enum NullWindow = NativeWindowHandle.init;
3845 
3846 /++
3847 	Magic pseudo-window for just posting events to a global queue.
3848 
3849 	Not entirely supported, I might delete it at any time.
3850 
3851 	Added Nov 5, 2021.
3852 +/
3853 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
3854 
3855 /* Drag and drop support { */
3856 version(X11) {
3857 
3858 } else version(Windows) {
3859 	import core.sys.windows.uuid;
3860 	import core.sys.windows.ole2;
3861 	import core.sys.windows.oleidl;
3862 	import core.sys.windows.objidl;
3863 	import core.sys.windows.wtypes;
3864 
3865 	pragma(lib, "ole32");
3866 	void initDnd() {
3867 		auto err = OleInitialize(null);
3868 		if(err != S_OK && err != S_FALSE)
3869 			throw new Exception("init");//err);
3870 	}
3871 }
3872 /* } End drag and drop support */
3873 
3874 
3875 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3876 /// See [GenericCursor].
3877 class MouseCursor {
3878 	int osId;
3879 	bool isStockCursor;
3880 	private this(int osId) {
3881 		this.osId = osId;
3882 		this.isStockCursor = true;
3883 	}
3884 
3885 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3886 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3887 
3888 	version(Windows) {
3889 		HCURSOR cursor_;
3890 		HCURSOR cursorHandle() {
3891 			if(cursor_ is null)
3892 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3893 			return cursor_;
3894 		}
3895 
3896 	} else static if(UsingSimpledisplayX11) {
3897 		Cursor cursor_ = None;
3898 		int xDisplaySequence;
3899 
3900 		Cursor cursorHandle() {
3901 			if(this.osId == None)
3902 				return None;
3903 
3904 			// we need to reload if we on a new X connection
3905 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3906 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3907 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3908 			}
3909 			return cursor_;
3910 		}
3911 	}
3912 }
3913 
3914 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3915 // https://tronche.com/gui/x/xlib/appendix/b/
3916 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3917 /// 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.
3918 enum GenericCursorType {
3919 	Default, /// The default arrow pointer.
3920 	Wait, /// A cursor indicating something is loading and the user must wait.
3921 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3922 	Help, /// A cursor indicating the user can get help about the pointer location.
3923 	Cross, /// A crosshair.
3924 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3925 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3926 	UpArrow, /// An arrow pointing straight up.
3927 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3928 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3929 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3930 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3931 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3932 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3933 
3934 }
3935 
3936 /*
3937 	X_plus == css cell == Windows ?
3938 */
3939 
3940 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3941 static struct GenericCursor {
3942 	static:
3943 	///
3944 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3945 		static MouseCursor mc;
3946 
3947 		auto type = __traits(getMember, GenericCursorType, str);
3948 
3949 		if(mc is null) {
3950 
3951 			version(Windows) {
3952 				int osId;
3953 				final switch(type) {
3954 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3955 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3956 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3957 					case GenericCursorType.Help: osId = IDC_HELP; break;
3958 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3959 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3960 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3961 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3962 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3963 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3964 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3965 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3966 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3967 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3968 				}
3969 			} else static if(UsingSimpledisplayX11) {
3970 				int osId;
3971 				final switch(type) {
3972 					case GenericCursorType.Default: osId = None; break;
3973 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3974 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3975 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3976 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3977 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3978 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3979 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3980 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3981 
3982 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3983 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3984 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3985 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3986 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3987 				}
3988 
3989 			} else {
3990 				int osId;
3991 				// featureNotImplemented();
3992 			}
3993 
3994 			mc = new MouseCursor(osId);
3995 		}
3996 		return mc;
3997 	}
3998 }
3999 
4000 
4001 /++
4002 	If you want to get more control over the event loop, you can use this.
4003 
4004 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4005 	to `EventLoop.get.run`.
4006 +/
4007 struct EventLoop {
4008 	@disable this();
4009 
4010 	/// Gets a reference to an existing event loop
4011 	static EventLoop get() {
4012 		return EventLoop(0, null);
4013 	}
4014 
4015 	static void quitApplication() {
4016 		EventLoop.get().exit();
4017 	}
4018 
4019 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4020 
4021 	/// Construct an application-global event loop for yourself
4022 	/// See_Also: [SimpleWindow.setEventHandlers]
4023 	this(long pulseTimeout, void delegate() handlePulse) {
4024 		synchronized(monitor) {
4025 			if(impl is null) {
4026 				claimGuiThread();
4027 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4028 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4029 			} else {
4030 				if(pulseTimeout) {
4031 					impl.pulseTimeout = pulseTimeout;
4032 					impl.handlePulse = handlePulse;
4033 				}
4034 			}
4035 			impl.refcount++;
4036 		}
4037 	}
4038 
4039 	~this() {
4040 		if(impl is null)
4041 			return;
4042 		impl.refcount--;
4043 		if(impl.refcount == 0) {
4044 			impl.dispose();
4045 			if(thisIsGuiThread)
4046 				guiThreadFinalize();
4047 		}
4048 
4049 	}
4050 
4051 	this(this) {
4052 		if(impl is null)
4053 			return;
4054 		impl.refcount++;
4055 	}
4056 
4057 	/// Runs the event loop until the whileCondition, if present, returns false
4058 	int run(bool delegate() whileCondition = null) {
4059 		assert(impl !is null);
4060 		impl.notExited = true;
4061 		return impl.run(whileCondition);
4062 	}
4063 
4064 	/// Exits the event loop
4065 	void exit() {
4066 		assert(impl !is null);
4067 		impl.notExited = false;
4068 	}
4069 
4070 	version(linux)
4071 	ref void delegate(int) signalHandler() {
4072 		assert(impl !is null);
4073 		return impl.signalHandler;
4074 	}
4075 
4076 	__gshared static EventLoopImpl* impl;
4077 }
4078 
4079 version(linux)
4080 	void delegate(int, int) globalHupHandler;
4081 
4082 version(Posix)
4083 	void makeNonBlocking(int fd) {
4084 		import fcntl = core.sys.posix.fcntl;
4085 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4086 		if(flags == -1)
4087 			throw new Exception("fcntl get");
4088 		flags |= fcntl.O_NONBLOCK;
4089 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4090 		if(s == -1)
4091 			throw new Exception("fcntl set");
4092 	}
4093 
4094 struct EventLoopImpl {
4095 	int refcount;
4096 
4097 	bool notExited = true;
4098 
4099 	version(linux) {
4100 		static import ep = core.sys.linux.epoll;
4101 		static import unix = core.sys.posix.unistd;
4102 		static import err = core.stdc.errno;
4103 		import core.sys.linux.timerfd;
4104 
4105 		void delegate(int) signalHandler;
4106 	}
4107 
4108 	version(X11) {
4109 		int pulseFd = -1;
4110 		version(linux) ep.epoll_event[16] events = void;
4111 	} else version(Windows) {
4112 		Timer pulser;
4113 		HANDLE[] handles;
4114 	}
4115 
4116 
4117 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4118 	/// to call this, as it's not recommended to share window between threads.
4119 	void mtLock () {
4120 		version(X11) {
4121 			XLockDisplay(this.display);
4122 		}
4123 	}
4124 
4125 	version(X11)
4126 	auto display() { return XDisplayConnection.get; }
4127 
4128 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4129 	/// to call this, as it's not recommended to share window between threads.
4130 	void mtUnlock () {
4131 		version(X11) {
4132 			XUnlockDisplay(this.display);
4133 		}
4134 	}
4135 
4136 	version(with_eventloop)
4137 	void initialize(long pulseTimeout) {}
4138 	else
4139 	void initialize(long pulseTimeout) {
4140 		version(Windows) {
4141 			if(pulseTimeout && handlePulse !is null)
4142 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4143 
4144 			if (customEventH is null) {
4145 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4146 				if (customEventH !is null) {
4147 					handles ~= customEventH;
4148 				} else {
4149 					// this is something that should not be; better be safe than sorry
4150 					throw new Exception("can't create eventfd for custom event processing");
4151 				}
4152 			}
4153 
4154 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4155 		}
4156 
4157 		version(linux) {
4158 			prepareEventLoop();
4159 			{
4160 				auto display = XDisplayConnection.get;
4161 				// adding Xlib file
4162 				ep.epoll_event ev = void;
4163 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4164 				ev.events = ep.EPOLLIN;
4165 				ev.data.fd = display.fd;
4166 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4167 					throw new Exception("add x fd");// ~ to!string(epollFd));
4168 				displayFd = display.fd;
4169 			}
4170 
4171 			if(pulseTimeout && handlePulse !is null) {
4172 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4173 				if(pulseFd == -1)
4174 					throw new Exception("pulse timer create failed");
4175 
4176 				itimerspec value;
4177 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4178 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4179 
4180 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4181 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4182 
4183 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4184 					throw new Exception("couldn't make pulse timer");
4185 
4186 				ep.epoll_event ev = void;
4187 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4188 				ev.events = ep.EPOLLIN;
4189 				ev.data.fd = pulseFd;
4190 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4191 			}
4192 
4193 			// eventfd for custom events
4194 			if (customEventFDWrite == -1) {
4195 				customEventFDWrite = eventfd(0, 0);
4196 				customEventFDRead = customEventFDWrite;
4197 				if (customEventFDRead >= 0) {
4198 					ep.epoll_event ev = void;
4199 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4200 					ev.events = ep.EPOLLIN;
4201 					ev.data.fd = customEventFDRead;
4202 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4203 				} else {
4204 					// this is something that should not be; better be safe than sorry
4205 					throw new Exception("can't create eventfd for custom event processing");
4206 				}
4207 			}
4208 
4209 			if (customSignalFD == -1) {
4210 				import core.sys.linux.sys.signalfd;
4211 
4212 				sigset_t sigset;
4213 				auto err = sigemptyset(&sigset);
4214 				assert(!err);
4215 				err = sigaddset(&sigset, SIGINT);
4216 				assert(!err);
4217 				err = sigaddset(&sigset, SIGHUP);
4218 				assert(!err);
4219 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4220 				assert(!err);
4221 
4222 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4223 				assert(customSignalFD != -1);
4224 
4225 				ep.epoll_event ev = void;
4226 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4227 				ev.events = ep.EPOLLIN;
4228 				ev.data.fd = customSignalFD;
4229 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4230 			}
4231 		} else version(Posix) {
4232 			prepareEventLoop();
4233 			if (customEventFDRead == -1) {
4234 				int[2] bfr;
4235 				import core.sys.posix.unistd;
4236 				auto ret = pipe(bfr);
4237 				if(ret == -1) throw new Exception("pipe");
4238 				customEventFDRead = bfr[0];
4239 				customEventFDWrite = bfr[1];
4240 			}
4241 
4242 		}
4243 
4244 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4245 
4246 		version(linux) {
4247 			this.mtLock();
4248 			scope(exit) this.mtUnlock();
4249 			XPending(display); // no, really
4250 		}
4251 
4252 		disposed = false;
4253 	}
4254 
4255 	bool disposed = true;
4256 	version(X11)
4257 		int displayFd = -1;
4258 
4259 	version(with_eventloop)
4260 	void dispose() {}
4261 	else
4262 	void dispose() {
4263 		disposed = true;
4264 		version(X11) {
4265 			if(pulseFd != -1) {
4266 				import unix = core.sys.posix.unistd;
4267 				unix.close(pulseFd);
4268 				pulseFd = -1;
4269 			}
4270 
4271 				version(linux)
4272 				if(displayFd != -1) {
4273 					// 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
4274 					ep.epoll_event ev = void;
4275 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4276 					ev.events = ep.EPOLLIN;
4277 					ev.data.fd = displayFd;
4278 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4279 					displayFd = -1;
4280 				}
4281 
4282 		} else version(Windows) {
4283 			if(pulser !is null) {
4284 				pulser.destroy();
4285 				pulser = null;
4286 			}
4287 			if (customEventH !is null) {
4288 				CloseHandle(customEventH);
4289 				customEventH = null;
4290 			}
4291 		}
4292 	}
4293 
4294 	this(long pulseTimeout, void delegate() handlePulse) {
4295 		this.pulseTimeout = pulseTimeout;
4296 		this.handlePulse = handlePulse;
4297 		initialize(pulseTimeout);
4298 	}
4299 
4300 	private long pulseTimeout;
4301 	void delegate() handlePulse;
4302 
4303 	~this() {
4304 		dispose();
4305 	}
4306 
4307 	version(Posix)
4308 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4309 	version(Posix)
4310 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4311 	version(linux)
4312 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4313 	version(Windows)
4314 	ref auto customEventH() { return SimpleWindow.customEventH; }
4315 
4316 	version(with_eventloop) {
4317 		int loopHelper(bool delegate() whileCondition) {
4318 			// FIXME: whileCondition
4319 			import arsd.eventloop;
4320 			loop();
4321 			return 0;
4322 		}
4323 	} else
4324 	int loopHelper(bool delegate() whileCondition) {
4325 		version(X11) {
4326 			bool done = false;
4327 
4328 			XFlush(display);
4329 			insideXEventLoop = true;
4330 			scope(exit) insideXEventLoop = false;
4331 
4332 			version(linux) {
4333 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4334 					bool forceXPending = false;
4335 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4336 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4337 					{
4338 						this.mtLock();
4339 						scope(exit) this.mtUnlock();
4340 						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
4341 					}
4342 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4343 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4344 					if(nfds == -1) {
4345 						if(err.errno == err.EINTR) {
4346 							//if(forceXPending) goto xpending;
4347 							continue; // interrupted by signal, just try again
4348 						}
4349 						throw new Exception("epoll wait failure");
4350 					}
4351 					// writeln(nfds, " ", events[0].data.fd);
4352 
4353 					SimpleWindow.processAllCustomEvents(); // anyway
4354 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4355 					foreach(idx; 0 .. nfds) {
4356 						if(done) break;
4357 						auto fd = events[idx].data.fd;
4358 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4359 						auto flags = events[idx].events;
4360 						if(flags & ep.EPOLLIN) {
4361 							if (fd == customSignalFD) {
4362 								version(linux) {
4363 									import core.sys.linux.sys.signalfd;
4364 									import core.sys.posix.unistd : read;
4365 									signalfd_siginfo info;
4366 									read(customSignalFD, &info, info.sizeof);
4367 
4368 									auto sig = info.ssi_signo;
4369 
4370 									if(EventLoop.get.signalHandler !is null) {
4371 										EventLoop.get.signalHandler()(sig);
4372 									} else {
4373 										EventLoop.get.exit();
4374 									}
4375 								}
4376 							} else if(fd == display.fd) {
4377 								version(sdddd) { writeln("X EVENT PENDING!"); }
4378 								this.mtLock();
4379 								scope(exit) this.mtUnlock();
4380 								while(!done && XPending(display)) {
4381 									done = doXNextEvent(this.display);
4382 								}
4383 								forceXPending = false;
4384 							} else if(fd == pulseFd) {
4385 								long expirationCount;
4386 								// 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...
4387 
4388 								handlePulse();
4389 
4390 								// read just to clear the buffer so poll doesn't trigger again
4391 								// BTW I read AFTER the pulse because if the pulse handler takes
4392 								// a lot of time to execute, we don't want the app to get stuck
4393 								// in a loop of timer hits without a chance to do anything else
4394 								//
4395 								// IOW handlePulse happens at most once per pulse interval.
4396 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4397 								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
4398 							} else if (fd == customEventFDRead) {
4399 								// we have some custom events; process 'em
4400 								import core.sys.posix.unistd : read;
4401 								ulong n;
4402 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4403 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4404 								//SimpleWindow.processAllCustomEvents();
4405 
4406 								forceXPending = true;
4407 							} else {
4408 								// some other timer
4409 								version(sdddd) { writeln("unknown fd: ", fd); }
4410 
4411 								if(Timer* t = fd in Timer.mapping)
4412 									(*t).trigger();
4413 
4414 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4415 									(*pfr).ready(flags);
4416 
4417 								// or i might add support for other FDs too
4418 								// but for now it is just timer
4419 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4420 							}
4421 						}
4422 						if(flags & ep.EPOLLHUP) {
4423 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4424 								(*pfr).hup(flags);
4425 							if(globalHupHandler)
4426 								globalHupHandler(fd, flags);
4427 						}
4428 						/+
4429 						} else {
4430 							// not interested in OUT, we are just reading here.
4431 							//
4432 							// error or hup might also be reported
4433 							// but it shouldn't here since we are only
4434 							// using a few types of FD and Xlib will report
4435 							// if it dies.
4436 							// so instead of thoughtfully handling it, I'll
4437 							// just throw. for now at least
4438 
4439 							throw new Exception("epoll did something else");
4440 						}
4441 						+/
4442 					}
4443 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4444 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4445 					xpending:
4446 					if (!done && forceXPending) {
4447 						this.mtLock();
4448 						scope(exit) this.mtUnlock();
4449 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4450 						while(!done && XPending(display)) {
4451 							done = doXNextEvent(this.display);
4452 						}
4453 					}
4454 				}
4455 			} else {
4456 				// Generic fallback: yes to simple pulse support,
4457 				// but NO timer support!
4458 
4459 				// FIXME: we could probably support the POSIX timer_create
4460 				// signal-based option, but I'm in no rush to write it since
4461 				// I prefer the fd-based functions.
4462 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4463 
4464 					import core.sys.posix.poll;
4465 
4466 					pollfd[] pfds;
4467 					pollfd[32] pfdsBuffer;
4468 					auto len = PosixFdReader.mapping.length + 2;
4469 					// FIXME: i should just reuse the buffer
4470 					if(len < pfdsBuffer.length)
4471 						pfds = pfdsBuffer[0 .. len];
4472 					else
4473 						pfds = new pollfd[](len);
4474 
4475 					pfds[0].fd = display.fd;
4476 					pfds[0].events = POLLIN;
4477 					pfds[0].revents = 0;
4478 
4479 					int slot = 1;
4480 
4481 					if(customEventFDRead != -1) {
4482 						pfds[slot].fd = customEventFDRead;
4483 						pfds[slot].events = POLLIN;
4484 						pfds[slot].revents = 0;
4485 
4486 						slot++;
4487 					}
4488 
4489 					foreach(fd, obj; PosixFdReader.mapping) {
4490 						if(!obj.enabled) continue;
4491 						pfds[slot].fd = fd;
4492 						pfds[slot].events = POLLIN;
4493 						pfds[slot].revents = 0;
4494 
4495 						slot++;
4496 					}
4497 
4498 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4499 					if(ret == -1) throw new Exception("poll");
4500 
4501 					if(ret == 0) {
4502 						// FIXME it may not necessarily time out if events keep coming
4503 						if(handlePulse !is null)
4504 							handlePulse();
4505 					} else {
4506 						foreach(s; 0 .. slot) {
4507 							if(pfds[s].revents == 0) continue;
4508 
4509 							if(pfds[s].fd == display.fd) {
4510 								while(!done && XPending(display)) {
4511 									this.mtLock();
4512 									scope(exit) this.mtUnlock();
4513 									done = doXNextEvent(this.display);
4514 								}
4515 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4516 
4517 								import core.sys.posix.unistd : read;
4518 								ulong n;
4519 								read(customEventFDRead, &n, n.sizeof);
4520 								SimpleWindow.processAllCustomEvents();
4521 							} else {
4522 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4523 								if(pfds[s].revents & POLLNVAL) {
4524 									obj.dispose();
4525 								} else {
4526 									obj.ready(pfds[s].revents);
4527 								}
4528 							}
4529 
4530 							ret--;
4531 							if(ret == 0) break;
4532 						}
4533 					}
4534 				}
4535 			}
4536 		}
4537 
4538 		version(Windows) {
4539 			int ret = -1;
4540 			MSG message;
4541 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4542 				eventLoopRound++;
4543 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4544 				auto waitResult = MsgWaitForMultipleObjectsEx(
4545 					cast(int) handles.length, handles.ptr,
4546 					(wto == 0 ? INFINITE : wto), /* timeout */
4547 					0x04FF, /* QS_ALLINPUT */
4548 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4549 
4550 				SimpleWindow.processAllCustomEvents(); // anyway
4551 				enum WAIT_OBJECT_0 = 0;
4552 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4553 					auto h = handles[waitResult - WAIT_OBJECT_0];
4554 					if(auto e = h in WindowsHandleReader.mapping) {
4555 						(*e).ready();
4556 					}
4557 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4558 					// message ready
4559 					int count;
4560 					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
4561 						ret = GetMessage(&message, null, 0, 0);
4562 						if(ret == -1)
4563 							throw new WindowsApiException("GetMessage", GetLastError());
4564 						TranslateMessage(&message);
4565 						DispatchMessage(&message);
4566 
4567 						count++;
4568 						if(count > 10)
4569 							break; // take the opportunity to catch up on other events
4570 
4571 						if(ret == 0) { // WM_QUIT
4572 							EventLoop.quitApplication();
4573 							break;
4574 						}
4575 					}
4576 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4577 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4578 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4579 					// timeout, should never happen since we aren't using it
4580 				} else if(waitResult == 0xFFFFFFFF) {
4581 						// failed
4582 						throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4583 				} else {
4584 					// idk....
4585 				}
4586 			}
4587 
4588 			// return message.wParam;
4589 			return 0;
4590 		} else {
4591 			return 0;
4592 		}
4593 	}
4594 
4595 	int run(bool delegate() whileCondition = null) {
4596 		if(disposed)
4597 			initialize(this.pulseTimeout);
4598 
4599 		version(X11) {
4600 			try {
4601 				return loopHelper(whileCondition);
4602 			} catch(XDisconnectException e) {
4603 				if(e.userRequested) {
4604 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4605 						item.discardConnectionState();
4606 					XCloseDisplay(XDisplayConnection.display);
4607 				}
4608 
4609 				XDisplayConnection.display = null;
4610 
4611 				this.dispose();
4612 
4613 				throw e;
4614 			}
4615 		} else {
4616 			return loopHelper(whileCondition);
4617 		}
4618 	}
4619 }
4620 
4621 
4622 /++
4623 	Provides an icon on the system notification area (also known as the system tray).
4624 
4625 
4626 	If a notification area is not available with the NotificationIcon object is created,
4627 	it will silently succeed and simply attempt to create one when an area becomes available.
4628 
4629 
4630 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
4631 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
4632 	with true color was added at that time. I was just too lazy to write the fallback.
4633 
4634 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
4635 	you use arsd 10.x when targeting Windows XP.
4636 +/
4637 version(OSXCocoa) {} else // NotYetImplementedException
4638 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4639 
4640 	version(X11) {
4641 		void recreateAfterDisconnect() {
4642 			stateDiscarded = false;
4643 			clippixmap = None;
4644 			throw new Exception("NOT IMPLEMENTED");
4645 		}
4646 
4647 		bool stateDiscarded;
4648 		void discardConnectionState() {
4649 			stateDiscarded = true;
4650 		}
4651 	}
4652 
4653 
4654 	version(X11) {
4655 		Image img;
4656 
4657 		NativeEventHandler getNativeEventHandler() {
4658 			return delegate int(XEvent e) {
4659 				switch(e.type) {
4660 					case EventType.Expose:
4661 					//case EventType.VisibilityNotify:
4662 						redraw();
4663 					break;
4664 					case EventType.ClientMessage:
4665 						version(sddddd) {
4666 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4667 						writeln("\t", e.xclient.format);
4668 						writeln("\t", e.xclient.data.l);
4669 						}
4670 					break;
4671 					case EventType.ButtonPress:
4672 						auto event = e.xbutton;
4673 						if (onClick !is null || onClickEx !is null) {
4674 							MouseButton mb = cast(MouseButton)0;
4675 							switch (event.button) {
4676 								case 1: mb = MouseButton.left; break; // left
4677 								case 2: mb = MouseButton.middle; break; // middle
4678 								case 3: mb = MouseButton.right; break; // right
4679 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4680 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4681 								case 6: break; // scroll left...
4682 								case 7: break; // scroll right...
4683 								case 8: mb = MouseButton.backButton; break;
4684 								case 9: mb = MouseButton.forwardButton; break;
4685 								default:
4686 							}
4687 							if (mb) {
4688 								try { onClick()(mb); } catch (Exception) {}
4689 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4690 							}
4691 						}
4692 					break;
4693 					case EventType.EnterNotify:
4694 						if (onEnter !is null) {
4695 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4696 						}
4697 						break;
4698 					case EventType.LeaveNotify:
4699 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4700 						break;
4701 					case EventType.DestroyNotify:
4702 						active = false;
4703 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4704 					break;
4705 					case EventType.ConfigureNotify:
4706 						auto event = e.xconfigure;
4707 						this.width = event.width;
4708 						this.height = event.height;
4709 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4710 						redraw();
4711 					break;
4712 					default: return 1;
4713 				}
4714 				return 1;
4715 			};
4716 		}
4717 
4718 		/* private */ void hideBalloon() {
4719 			balloon.close();
4720 			version(with_timer)
4721 				timer.destroy();
4722 			balloon = null;
4723 			version(with_timer)
4724 				timer = null;
4725 		}
4726 
4727 		void redraw() {
4728 			if (!active) return;
4729 
4730 			auto display = XDisplayConnection.get;
4731 			GC gc;
4732 
4733 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
4734 
4735 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
4736 				Visual *visual;
4737 				XVisualInfo vis_info;
4738 				XSetWindowAttributes win_attr;
4739 				c_ulong win_mask;
4740 
4741 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
4742 					assert(0);
4743 					// return 1;
4744 				}
4745 
4746 				visual = vis_info.visual;
4747 
4748 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
4749 				win_attr.background_pixel = 0;
4750 				win_attr.border_pixel = 0;
4751 
4752 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
4753 
4754 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
4755 
4756 				return 0;
4757 			}
4758 
4759 			if(useAlpha)
4760 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
4761 			else
4762 				gc = DefaultGC(display, DefaultScreen(display));
4763 
4764 			XClearWindow(display, nativeHandle);
4765 
4766 			if(!useAlpha && img !is null)
4767 				XSetClipMask(display, gc, clippixmap);
4768 
4769 			/+
4770 			XSetForeground(display, gc,
4771 				cast(uint) 0 << 16 |
4772 				cast(uint) 0 << 8 |
4773 				cast(uint) 0);
4774 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4775 			+/
4776 
4777 			if (img is null) {
4778 				XSetForeground(display, gc,
4779 					cast(uint) 0 << 16 |
4780 					cast(uint) 127 << 8 |
4781 					cast(uint) 0);
4782 				XFillArc(display, nativeHandle,
4783 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4784 			} else {
4785 				int dx = 0;
4786 				int dy = 0;
4787 				if(width > img.width)
4788 					dx = (width - img.width) / 2;
4789 				if(height > img.height)
4790 					dy = (height - img.height) / 2;
4791 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
4792 				XSetClipOrigin(display, gc, dx, dy);
4793 
4794 				int max(int a, int b) {
4795 					if(a > b) return a; else return b;
4796 				}
4797 
4798 				if (img.usingXshm)
4799 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
4800 				else
4801 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
4802 			}
4803 			XSetClipMask(display, gc, None);
4804 			flushGui();
4805 		}
4806 
4807 		static Window getTrayOwner() {
4808 			auto display = XDisplayConnection.get;
4809 			auto i = cast(int) DefaultScreen(display);
4810 			if(i < 10 && i >= 0) {
4811 				static Atom atom;
4812 				if(atom == None)
4813 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4814 				return XGetSelectionOwner(display, atom);
4815 			}
4816 			return None;
4817 		}
4818 
4819 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4820 			auto to = getTrayOwner();
4821 			auto display = XDisplayConnection.get;
4822 			XEvent ev;
4823 			ev.xclient.type = EventType.ClientMessage;
4824 			ev.xclient.window = to;
4825 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4826 			ev.xclient.format = 32;
4827 			ev.xclient.data.l[0] = CurrentTime;
4828 			ev.xclient.data.l[1] = message;
4829 			ev.xclient.data.l[2] = d1;
4830 			ev.xclient.data.l[3] = d2;
4831 			ev.xclient.data.l[4] = d3;
4832 
4833 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4834 		}
4835 
4836 		private static NotificationAreaIcon[] activeIcons;
4837 
4838 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4839 		private void newManager() {
4840 			close();
4841 			createXWin();
4842 
4843 			if(this.clippixmap)
4844 				XFreePixmap(XDisplayConnection.get, clippixmap);
4845 			if(this.originalMemoryImage)
4846 				this.icon = this.originalMemoryImage;
4847 			else if(this.img)
4848 				this.icon = this.img;
4849 		}
4850 
4851 		private bool useAlpha = false;
4852 
4853 		private void createXWin () {
4854 			// create window
4855 			auto display = XDisplayConnection.get;
4856 
4857 			// to check for MANAGER on root window to catch new/changed tray owners
4858 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4859 			// so if a thing does appear, we can handle it
4860 			foreach(ai; activeIcons)
4861 				if(ai is this)
4862 					goto alreadythere;
4863 			activeIcons ~= this;
4864 			alreadythere:
4865 
4866 			// and check for an existing tray
4867 			auto trayOwner = getTrayOwner();
4868 			if(trayOwner == None)
4869 				return;
4870 				//throw new Exception("No notification area found");
4871 
4872 			Visual* v = cast(Visual*) CopyFromParent;
4873 
4874 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
4875 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
4876 			// a resize event later.
4877 			width = 22;
4878 			height = 22;
4879 
4880 			// if they system gave us a 32 bit visual we need to switch to it too
4881 			int depth = 24;
4882 
4883 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4884 			if(visualProp !is null) {
4885 				c_ulong[] info = cast(c_ulong[]) visualProp;
4886 				if(info.length == 1) {
4887 					auto vid = info[0];
4888 					int returned;
4889 					XVisualInfo t;
4890 					t.visualid = vid;
4891 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4892 					if(got !is null) {
4893 						if(returned == 1) {
4894 							v = got.visual;
4895 							depth = got.depth;
4896 							// writeln("using special visual ", got.depth);
4897 							// writeln(depth);
4898 						}
4899 						XFree(got);
4900 					}
4901 				}
4902 			}
4903 
4904 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
4905 			XSetWindowAttributes attr;
4906 			attr.background_pixel = 0;
4907 			attr.border_pixel = 0;
4908 			attr.override_redirect = 0;
4909 			if(v !is cast(Visual*) CopyFromParent) {
4910 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
4911 				CWFlags |= CWColormap;
4912 				if(depth == 32)
4913 					useAlpha = true;
4914 				else
4915 					goto plain;
4916 			} else {
4917 				plain:
4918 				attr.background_pixmap = 1 /* ParentRelative */;
4919 				CWFlags |= CWBackPixmap;
4920 			}
4921 
4922 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
4923 
4924 			assert(nativeWindow);
4925 
4926 			if(!useAlpha)
4927 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4928 
4929 			nativeHandle = nativeWindow;
4930 
4931 			///+
4932 			arch_ulong[2] info;
4933 			info[0] = 0;
4934 			info[1] = 1;
4935 
4936 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4937 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4938 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4939 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4940 
4941 			XChangeProperty(
4942 				display,
4943 				nativeWindow,
4944 				GetAtom!("_XEMBED_INFO", true)(display),
4945 				GetAtom!("_XEMBED_INFO", true)(display),
4946 				32 /* bits */,
4947 				0 /*PropModeReplace*/,
4948 				info.ptr,
4949 				2);
4950 
4951 			import core.sys.posix.unistd;
4952 			arch_ulong pid = getpid();
4953 
4954 			XChangeProperty(
4955 				display,
4956 				nativeWindow,
4957 				GetAtom!("_NET_WM_PID", true)(display),
4958 				XA_CARDINAL,
4959 				32 /* bits */,
4960 				0 /*PropModeReplace*/,
4961 				&pid,
4962 				1);
4963 
4964 			updateNetWmIcon();
4965 
4966 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4967 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4968 				XClassHint klass;
4969 				XWMHints wh;
4970 				XSizeHints size;
4971 				klass.res_name = sdpyWindowClassStr;
4972 				klass.res_class = sdpyWindowClassStr;
4973 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4974 			}
4975 
4976 				// believe it or not, THIS is what xfce needed for the 9999 issue
4977 				XSizeHints sh;
4978 				c_long spr;
4979 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4980 				sh.flags |= PMaxSize | PMinSize;
4981 				// FIXME maybe nicer resizing
4982 				sh.min_width = 16;
4983 				sh.min_height = 16;
4984 				sh.max_width = 22;
4985 				sh.max_height = 22;
4986 				XSetWMNormalHints(display, nativeWindow, &sh);
4987 
4988 
4989 			//+/
4990 
4991 
4992 			XSelectInput(display, nativeWindow,
4993 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4994 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4995 
4996 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4997 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
4998 
4999 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5000 			active = true;
5001 		}
5002 
5003 		void updateNetWmIcon() {
5004 			if(img is null) return;
5005 			auto display = XDisplayConnection.get;
5006 			// FIXME: ensure this is correct
5007 			arch_ulong[] buffer;
5008 			auto imgMi = img.toTrueColorImage;
5009 			buffer ~= imgMi.width;
5010 			buffer ~= imgMi.height;
5011 			foreach(c; imgMi.imageData.colors) {
5012 				arch_ulong b;
5013 				b |= c.a << 24;
5014 				b |= c.r << 16;
5015 				b |= c.g << 8;
5016 				b |= c.b;
5017 				buffer ~= b;
5018 			}
5019 
5020 			XChangeProperty(
5021 				display,
5022 				nativeHandle,
5023 				GetAtom!"_NET_WM_ICON"(display),
5024 				GetAtom!"CARDINAL"(display),
5025 				32 /* bits */,
5026 				0 /*PropModeReplace*/,
5027 				buffer.ptr,
5028 				cast(int) buffer.length);
5029 		}
5030 
5031 
5032 
5033 		private SimpleWindow balloon;
5034 		version(with_timer)
5035 		private Timer timer;
5036 
5037 		private Window nativeHandle;
5038 		private Pixmap clippixmap = None;
5039 		private int width = 16;
5040 		private int height = 16;
5041 		private bool active = false;
5042 
5043 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5044 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5045 		void delegate () onLeave; /// X11 only.
5046 
5047 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5048 
5049 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5050 		void getWindowRect (out int x, out int y, out int width, out int height) {
5051 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5052 			Window dummyw;
5053 			auto dpy = XDisplayConnection.get;
5054 			//XWindowAttributes xwa;
5055 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5056 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5057 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5058 			width = this.width;
5059 			height = this.height;
5060 		}
5061 	}
5062 
5063 	/+
5064 		What I actually want from this:
5065 
5066 		* set / change: icon, tooltip
5067 		* handle: mouse click, right click
5068 		* show: notification bubble.
5069 	+/
5070 
5071 	version(Windows) {
5072 		WindowsIcon win32Icon;
5073 		HWND hwnd;
5074 
5075 		NOTIFYICONDATAW data;
5076 
5077 		NativeEventHandler getNativeEventHandler() {
5078 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5079 				if(msg == WM_USER) {
5080 					auto event = LOWORD(lParam);
5081 					auto iconId = HIWORD(lParam);
5082 					//auto x = GET_X_LPARAM(wParam);
5083 					//auto y = GET_Y_LPARAM(wParam);
5084 					switch(event) {
5085 						case WM_LBUTTONDOWN:
5086 							onClick()(MouseButton.left);
5087 						break;
5088 						case WM_RBUTTONDOWN:
5089 							onClick()(MouseButton.right);
5090 						break;
5091 						case WM_MBUTTONDOWN:
5092 							onClick()(MouseButton.middle);
5093 						break;
5094 						case WM_MOUSEMOVE:
5095 							// sent, we could use it.
5096 						break;
5097 						case WM_MOUSEWHEEL:
5098 							// NOT SENT
5099 						break;
5100 						//case NIN_KEYSELECT:
5101 						//case NIN_SELECT:
5102 						//break;
5103 						default: {}
5104 					}
5105 				}
5106 				return 0;
5107 			};
5108 		}
5109 
5110 		enum NIF_SHOWTIP = 0x00000080;
5111 
5112 		private static struct NOTIFYICONDATAW {
5113 			DWORD cbSize;
5114 			HWND  hWnd;
5115 			UINT  uID;
5116 			UINT  uFlags;
5117 			UINT  uCallbackMessage;
5118 			HICON hIcon;
5119 			WCHAR[128] szTip;
5120 			DWORD dwState;
5121 			DWORD dwStateMask;
5122 			WCHAR[256] szInfo;
5123 			union {
5124 				UINT uTimeout;
5125 				UINT uVersion;
5126 			}
5127 			WCHAR[64] szInfoTitle;
5128 			DWORD dwInfoFlags;
5129 			GUID  guidItem;
5130 			HICON hBalloonIcon;
5131 		}
5132 
5133 	}
5134 
5135 	/++
5136 		Note that on Windows, only left, right, and middle buttons are sent.
5137 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5138 		program is meant to be used on Windows too.
5139 	+/
5140 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5141 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5142 		// but on X, we need an Image, so its canonical ctor is there. They should
5143 		// forward to each other though.
5144 		version(X11) {
5145 			this.name = name;
5146 			this.onClick = onClick;
5147 			createXWin();
5148 			this.icon = icon;
5149 		} else version(Windows) {
5150 			this.onClick = onClick;
5151 			this.win32Icon = new WindowsIcon(icon);
5152 
5153 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5154 
5155 			static bool registered = false;
5156 			if(!registered) {
5157 				WNDCLASSEX wc;
5158 				wc.cbSize = wc.sizeof;
5159 				wc.hInstance = hInstance;
5160 				wc.lpfnWndProc = &WndProc;
5161 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5162 				if(!RegisterClassExW(&wc))
5163 					throw new WindowsApiException("RegisterClass", GetLastError());
5164 				registered = true;
5165 			}
5166 
5167 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5168 			if(hwnd is null)
5169 				throw new WindowsApiException("CreateWindow", GetLastError());
5170 
5171 			data.cbSize = data.sizeof;
5172 			data.hWnd = hwnd;
5173 			data.uID = cast(uint) cast(void*) this;
5174 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5175 				// NIF_INFO means show balloon
5176 			data.uCallbackMessage = WM_USER;
5177 			data.hIcon = this.win32Icon.hIcon;
5178 			data.szTip = ""; // FIXME
5179 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5180 			data.dwStateMask = NIS_HIDDEN; // windows vista
5181 
5182 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5183 
5184 
5185 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5186 
5187 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5188 		} else version(OSXCocoa) {
5189 			throw new NotYetImplementedException();
5190 		} else static assert(0);
5191 	}
5192 
5193 	/// ditto
5194 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5195 		version(X11) {
5196 			this.onClick = onClick;
5197 			this.name = name;
5198 			createXWin();
5199 			this.icon = icon;
5200 		} else version(Windows) {
5201 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5202 		} else version(OSXCocoa) {
5203 			throw new NotYetImplementedException();
5204 		} else static assert(0);
5205 	}
5206 
5207 	version(X11) {
5208 		/++
5209 			X-specific extension (for now at least)
5210 		+/
5211 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5212 			this.onClickEx = onClickEx;
5213 			createXWin();
5214 			if (icon !is null) this.icon = icon;
5215 		}
5216 
5217 		/// ditto
5218 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5219 			this.onClickEx = onClickEx;
5220 			createXWin();
5221 			this.icon = icon;
5222 		}
5223 	}
5224 
5225 	private void delegate (MouseButton button) onClick_;
5226 
5227 	///
5228 	@property final void delegate(MouseButton) onClick() {
5229 		if(onClick_ is null)
5230 			onClick_ = delegate void(MouseButton) {};
5231 		return onClick_;
5232 	}
5233 
5234 	/// ditto
5235 	@property final void onClick(void delegate(MouseButton) handler) {
5236 		// I made this a property setter so we can wrap smaller arg
5237 		// delegates and just forward all to onClickEx or something.
5238 		onClick_ = handler;
5239 	}
5240 
5241 
5242 	string name_;
5243 	@property void name(string n) {
5244 		name_ = n;
5245 	}
5246 
5247 	@property string name() {
5248 		return name_;
5249 	}
5250 
5251 	private MemoryImage originalMemoryImage;
5252 
5253 	///
5254 	@property void icon(MemoryImage i) {
5255 		version(X11) {
5256 			this.originalMemoryImage = i;
5257 			if (!active) return;
5258 			if (i !is null) {
5259 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5260 				if(!useAlpha)
5261 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5262 				// writeln("using pixmap ", clippixmap);
5263 				updateNetWmIcon();
5264 				redraw();
5265 			} else {
5266 				if (this.img !is null) {
5267 					this.img = null;
5268 					redraw();
5269 				}
5270 			}
5271 		} else version(Windows) {
5272 			this.win32Icon = new WindowsIcon(i);
5273 
5274 			data.uFlags = NIF_ICON;
5275 			data.hIcon = this.win32Icon.hIcon;
5276 
5277 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5278 		} else version(OSXCocoa) {
5279 			throw new NotYetImplementedException();
5280 		} else static assert(0);
5281 	}
5282 
5283 	/// ditto
5284 	@property void icon (Image i) {
5285 		version(X11) {
5286 			if (!active) return;
5287 			if (i !is img) {
5288 				originalMemoryImage = null;
5289 				img = i;
5290 				redraw();
5291 			}
5292 		} else version(Windows) {
5293 			this.icon(i is null ? null : i.toTrueColorImage());
5294 		} else version(OSXCocoa) {
5295 			throw new NotYetImplementedException();
5296 		} else static assert(0);
5297 	}
5298 
5299 	/++
5300 		Shows a balloon notification. You can only show one balloon at a time, if you call
5301 		it twice while one is already up, the first balloon will be replaced.
5302 
5303 
5304 		The user is free to block notifications and they will automatically disappear after
5305 		a timeout period.
5306 
5307 		Params:
5308 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5309 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5310 			icon = the icon to display with the notification. If null, it uses your existing icon.
5311 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5312 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5313 	+/
5314 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5315 		bool useCustom = true;
5316 		version(libnotify) {
5317 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5318 			try {
5319 				if(!active) return;
5320 
5321 				if(libnotify is null) {
5322 					libnotify = new C_DynamicLibrary("libnotify.so");
5323 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5324 				}
5325 
5326 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5327 
5328 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5329 
5330 				if(onclick) {
5331 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5332 					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);
5333 					libnotify_action_delegates_count++;
5334 				}
5335 
5336 				// FIXME icon
5337 
5338 				// set hint image-data
5339 				// set default action for onclick
5340 
5341 				void* error;
5342 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5343 
5344 				useCustom = false;
5345 			} catch(Exception e) {
5346 
5347 			}
5348 		}
5349 
5350 		version(X11) {
5351 		if(useCustom) {
5352 			if(!active) return;
5353 			if(balloon) {
5354 				hideBalloon();
5355 			}
5356 			// I know there are two specs for this, but one is never
5357 			// implemented by any window manager I have ever seen, and
5358 			// the other is a bloated mess and too complicated for simpledisplay...
5359 			// so doing my own little window instead.
5360 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5361 
5362 			int x, y, width, height;
5363 			getWindowRect(x, y, width, height);
5364 
5365 			int bx = x - balloon.width;
5366 			int by = y - balloon.height;
5367 			if(bx < 0)
5368 				bx = x + width + balloon.width;
5369 			if(by < 0)
5370 				by = y + height;
5371 
5372 			// just in case, make sure it is actually on scren
5373 			if(bx < 0)
5374 				bx = 0;
5375 			if(by < 0)
5376 				by = 0;
5377 
5378 			balloon.move(bx, by);
5379 			auto painter = balloon.draw();
5380 			painter.fillColor = Color(220, 220, 220);
5381 			painter.outlineColor = Color.black;
5382 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5383 			auto iconWidth = icon is null ? 0 : icon.width;
5384 			if(icon)
5385 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5386 			iconWidth += 6; // margin around the icon
5387 
5388 			// draw a close button
5389 			painter.outlineColor = Color(44, 44, 44);
5390 			painter.fillColor = Color(255, 255, 255);
5391 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5392 			painter.pen = Pen(Color.black, 3);
5393 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5394 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5395 			painter.pen = Pen(Color.black, 1);
5396 			painter.fillColor = Color(220, 220, 220);
5397 
5398 			// Draw the title and message
5399 			painter.drawText(Point(4 + iconWidth, 4), title);
5400 			painter.drawLine(
5401 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5402 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5403 			);
5404 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5405 
5406 			balloon.setEventHandlers(
5407 				(MouseEvent ev) {
5408 					if(ev.type == MouseEventType.buttonPressed) {
5409 						if(ev.x > balloon.width - 16 && ev.y < 16)
5410 							hideBalloon();
5411 						else if(onclick)
5412 							onclick();
5413 					}
5414 				}
5415 			);
5416 			balloon.show();
5417 
5418 			version(with_timer)
5419 			timer = new Timer(timeout, &hideBalloon);
5420 			else {} // FIXME
5421 		}
5422 		} else version(Windows) {
5423 			enum NIF_INFO = 0x00000010;
5424 
5425 			data.uFlags = NIF_INFO;
5426 
5427 			// FIXME: go back to the last valid unicode code point
5428 			if(title.length > 40)
5429 				title = title[0 .. 40];
5430 			if(message.length > 220)
5431 				message = message[0 .. 220];
5432 
5433 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5434 			enum NIIF_LARGE_ICON  = 0x00000020;
5435 			enum NIIF_NOSOUND = 0x00000010;
5436 			enum NIIF_USER = 0x00000004;
5437 			enum NIIF_ERROR = 0x00000003;
5438 			enum NIIF_WARNING = 0x00000002;
5439 			enum NIIF_INFO = 0x00000001;
5440 			enum NIIF_NONE = 0;
5441 
5442 			WCharzBuffer t = WCharzBuffer(title);
5443 			WCharzBuffer m = WCharzBuffer(message);
5444 
5445 			t.copyInto(data.szInfoTitle);
5446 			m.copyInto(data.szInfo);
5447 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5448 
5449 			if(icon !is null) {
5450 				auto i = new WindowsIcon(icon);
5451 				data.hBalloonIcon = i.hIcon;
5452 				data.dwInfoFlags |= NIIF_USER;
5453 			}
5454 
5455 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5456 		} else version(OSXCocoa) {
5457 			throw new NotYetImplementedException();
5458 		} else static assert(0);
5459 	}
5460 
5461 	///
5462 	//version(Windows)
5463 	void show() {
5464 		version(X11) {
5465 			if(!hidden)
5466 				return;
5467 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5468 			hidden = false;
5469 		} else version(Windows) {
5470 			data.uFlags = NIF_STATE;
5471 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5472 			data.dwStateMask = NIS_HIDDEN; // windows vista
5473 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5474 		} else version(OSXCocoa) {
5475 			throw new NotYetImplementedException();
5476 		} else static assert(0);
5477 	}
5478 
5479 	version(X11)
5480 		bool hidden = false;
5481 
5482 	///
5483 	//version(Windows)
5484 	void hide() {
5485 		version(X11) {
5486 			if(hidden)
5487 				return;
5488 			hidden = true;
5489 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5490 		} else version(Windows) {
5491 			data.uFlags = NIF_STATE;
5492 			data.dwState = NIS_HIDDEN; // windows vista
5493 			data.dwStateMask = NIS_HIDDEN; // windows vista
5494 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5495 		} else version(OSXCocoa) {
5496 			throw new NotYetImplementedException();
5497 		} else static assert(0);
5498 	}
5499 
5500 	///
5501 	void close () {
5502 		version(X11) {
5503 			if (active) {
5504 				active = false; // event handler will set this too, but meh
5505 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5506 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5507 				flushGui();
5508 			}
5509 		} else version(Windows) {
5510 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5511 		} else version(OSXCocoa) {
5512 			throw new NotYetImplementedException();
5513 		} else static assert(0);
5514 	}
5515 
5516 	~this() {
5517 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5518 		version(X11)
5519 			if(clippixmap != None)
5520 				XFreePixmap(XDisplayConnection.get, clippixmap);
5521 		close();
5522 	}
5523 }
5524 
5525 version(X11)
5526 /// Call `XFreePixmap` on the return value.
5527 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5528 	char[] data = new char[](i.width * i.height / 8 + 2);
5529 	data[] = 0;
5530 
5531 	int bitOffset = 0;
5532 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5533 		ubyte v = c.a > 128 ? 1 : 0;
5534 		data[bitOffset / 8] |= v << (bitOffset%8);
5535 		bitOffset++;
5536 	}
5537 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5538 	return handle;
5539 }
5540 
5541 
5542 // basic functions to make timers
5543 /**
5544 	A timer that will trigger your function on a given interval.
5545 
5546 
5547 	You create a timer with an interval and a callback. It will continue
5548 	to fire on the interval until it is destroyed.
5549 
5550 	There are currently no one-off timers (instead, just create one and
5551 	destroy it when it is triggered) nor are there pause/resume functions -
5552 	the timer must again be destroyed and recreated if you want to pause it.
5553 
5554 	---
5555 	auto timer = new Timer(50, { it happened!; });
5556 	timer.destroy();
5557 	---
5558 
5559 	Timers can only be expected to fire when the event loop is running and only
5560 	once per iteration through the event loop.
5561 
5562 	History:
5563 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5564 		slow could lock up the event loop. It now guarantees other things will
5565 		get a chance to run between timer calls, even if that means not keeping up
5566 		with the requested interval.
5567 */
5568 version(with_timer) {
5569 class Timer {
5570 // FIXME: needs pause and unpause
5571 	// FIXME: I might add overloads for ones that take a count of
5572 	// how many elapsed since last time (on Windows, it will divide
5573 	// the ticks thing given, on Linux it is just available) and
5574 	// maybe one that takes an instance of the Timer itself too
5575 	/// Create a timer with a callback when it triggers.
5576 	this(int intervalInMilliseconds, void delegate() onPulse) {
5577 		assert(onPulse !is null);
5578 
5579 		this.intervalInMilliseconds = intervalInMilliseconds;
5580 		this.onPulse = onPulse;
5581 
5582 		version(Windows) {
5583 			/*
5584 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5585 			if(handle == 0)
5586 				throw new WindowsApiException("SetTimer", GetLastError());
5587 			*/
5588 
5589 			// thanks to Archival 998 for the WaitableTimer blocks
5590 			handle = CreateWaitableTimer(null, false, null);
5591 			long initialTime = -intervalInMilliseconds;
5592 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5593 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5594 
5595 			mapping[handle] = this;
5596 
5597 		} else version(linux) {
5598 			static import ep = core.sys.linux.epoll;
5599 
5600 			import core.sys.linux.timerfd;
5601 
5602 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5603 			if(fd == -1)
5604 				throw new Exception("timer create failed");
5605 
5606 			mapping[fd] = this;
5607 
5608 			itimerspec value;
5609 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5610 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5611 
5612 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5613 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5614 
5615 			if(timerfd_settime(fd, 0, &value, null) == -1)
5616 				throw new Exception("couldn't make pulse timer");
5617 
5618 			version(with_eventloop) {
5619 				import arsd.eventloop;
5620 				addFileEventListeners(fd, &trigger, null, null);
5621 			} else {
5622 				prepareEventLoop();
5623 
5624 				ep.epoll_event ev = void;
5625 				ev.events = ep.EPOLLIN;
5626 				ev.data.fd = fd;
5627 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5628 			}
5629 		} else featureNotImplemented();
5630 	}
5631 
5632 	private int intervalInMilliseconds;
5633 
5634 	// just cuz I sometimes call it this.
5635 	alias dispose = destroy;
5636 
5637 	/// Stop and destroy the timer object.
5638 	void destroy() {
5639 		version(Windows) {
5640 			staticDestroy(handle);
5641 			handle = null;
5642 		} else version(linux) {
5643 			staticDestroy(fd);
5644 			fd = -1;
5645 		} else featureNotImplemented();
5646 	}
5647 
5648 	version(Windows)
5649 	static void staticDestroy(HANDLE handle) {
5650 		if(handle) {
5651 			// KillTimer(null, handle);
5652 			CancelWaitableTimer(cast(void*)handle);
5653 			mapping.remove(handle);
5654 			CloseHandle(handle);
5655 		}
5656 	}
5657 	else version(linux)
5658 	static void staticDestroy(int fd) {
5659 		if(fd != -1) {
5660 			import unix = core.sys.posix.unistd;
5661 			static import ep = core.sys.linux.epoll;
5662 
5663 			version(with_eventloop) {
5664 				import arsd.eventloop;
5665 				removeFileEventListeners(fd);
5666 			} else {
5667 				ep.epoll_event ev = void;
5668 				ev.events = ep.EPOLLIN;
5669 				ev.data.fd = fd;
5670 
5671 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5672 			}
5673 			unix.close(fd);
5674 			mapping.remove(fd);
5675 		}
5676 	}
5677 
5678 	~this() {
5679 		version(Windows) { if(handle)
5680 			cleanupQueue.queue!staticDestroy(handle);
5681 		} else version(linux) { if(fd != -1)
5682 			cleanupQueue.queue!staticDestroy(fd);
5683 		}
5684 	}
5685 
5686 
5687 	void changeTime(int intervalInMilliseconds)
5688 	{
5689 		this.intervalInMilliseconds = intervalInMilliseconds;
5690 		version(Windows)
5691 		{
5692 			if(handle)
5693 			{
5694 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5695 				long initialTime = -intervalInMilliseconds;
5696 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5697 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
5698 			}
5699 		}
5700 	}
5701 
5702 
5703 	private:
5704 
5705 	void delegate() onPulse;
5706 
5707 	int lastEventLoopRoundTriggered;
5708 
5709 	void trigger() {
5710 		version(linux) {
5711 			import unix = core.sys.posix.unistd;
5712 			long val;
5713 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5714 		} else version(Windows) {
5715 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5716 				return; // never try to actually run faster than the event loop
5717 			lastEventLoopRoundTriggered = eventLoopRound;
5718 		} else featureNotImplemented();
5719 
5720 		onPulse();
5721 	}
5722 
5723 	version(Windows)
5724 	void rearm() {
5725 
5726 	}
5727 
5728 	version(Windows)
5729 		extern(Windows)
5730 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5731 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5732 			if(Timer* t = timer in mapping) {
5733 				try
5734 				(*t).trigger();
5735 				catch(Exception e) { sdpy_abort(e); assert(0); }
5736 			}
5737 		}
5738 
5739 	version(Windows) {
5740 		//UINT_PTR handle;
5741 		//static Timer[UINT_PTR] mapping;
5742 		HANDLE handle;
5743 		__gshared Timer[HANDLE] mapping;
5744 	} else version(linux) {
5745 		int fd = -1;
5746 		__gshared Timer[int] mapping;
5747 	} else version(OSXCocoa) {
5748 	} else static assert(0, "timer not supported");
5749 }
5750 }
5751 
5752 version(Windows)
5753 private int eventLoopRound;
5754 
5755 version(Windows)
5756 /// 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
5757 class WindowsHandleReader {
5758 	///
5759 	this(void delegate() onReady, HANDLE handle) {
5760 		this.onReady = onReady;
5761 		this.handle = handle;
5762 
5763 		mapping[handle] = this;
5764 
5765 		enable();
5766 	}
5767 
5768 	///
5769 	void enable() {
5770 		auto el = EventLoop.get().impl;
5771 		el.handles ~= handle;
5772 	}
5773 
5774 	///
5775 	void disable() {
5776 		auto el = EventLoop.get().impl;
5777 		for(int i = 0; i < el.handles.length; i++) {
5778 			if(el.handles[i] is handle) {
5779 				el.handles[i] = el.handles[$-1];
5780 				el.handles = el.handles[0 .. $-1];
5781 				return;
5782 			}
5783 		}
5784 	}
5785 
5786 	void dispose() {
5787 		disable();
5788 		if(handle)
5789 			mapping.remove(handle);
5790 		handle = null;
5791 	}
5792 
5793 	void ready() {
5794 		if(onReady)
5795 			onReady();
5796 	}
5797 
5798 	HANDLE handle;
5799 	void delegate() onReady;
5800 
5801 	__gshared WindowsHandleReader[HANDLE] mapping;
5802 }
5803 
5804 version(Posix)
5805 /// Lets you add files to the event loop for reading. Use at your own risk.
5806 class PosixFdReader {
5807 	///
5808 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5809 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5810 	}
5811 
5812 	///
5813 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5814 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5815 	}
5816 
5817 	///
5818 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5819 		this.onReady = onReady;
5820 		this.fd = fd;
5821 		this.captureWrites = captureWrites;
5822 		this.captureReads = captureReads;
5823 
5824 		mapping[fd] = this;
5825 
5826 		version(with_eventloop) {
5827 			import arsd.eventloop;
5828 			addFileEventListeners(fd, &readyel);
5829 		} else {
5830 			enable();
5831 		}
5832 	}
5833 
5834 	bool captureReads;
5835 	bool captureWrites;
5836 
5837 	version(with_eventloop) {} else
5838 	///
5839 	void enable() {
5840 		prepareEventLoop();
5841 
5842 		enabled = true;
5843 
5844 		version(linux) {
5845 			static import ep = core.sys.linux.epoll;
5846 			ep.epoll_event ev = void;
5847 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5848 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5849 			ev.data.fd = fd;
5850 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5851 		} else {
5852 
5853 		}
5854 	}
5855 
5856 	version(with_eventloop) {} else
5857 	///
5858 	void disable() {
5859 		prepareEventLoop();
5860 
5861 		enabled = false;
5862 
5863 		version(linux) {
5864 			static import ep = core.sys.linux.epoll;
5865 			ep.epoll_event ev = void;
5866 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5867 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5868 			ev.data.fd = fd;
5869 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5870 		}
5871 	}
5872 
5873 	version(with_eventloop) {} else
5874 	///
5875 	void dispose() {
5876 		if(enabled)
5877 			disable();
5878 		if(fd != -1)
5879 			mapping.remove(fd);
5880 		fd = -1;
5881 	}
5882 
5883 	void delegate(int, bool, bool) onReady;
5884 
5885 	version(with_eventloop)
5886 	void readyel() {
5887 		onReady(fd, true, true);
5888 	}
5889 
5890 	void ready(uint flags) {
5891 		version(linux) {
5892 			static import ep = core.sys.linux.epoll;
5893 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5894 		} else {
5895 			import core.sys.posix.poll;
5896 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5897 		}
5898 	}
5899 
5900 	void hup(uint flags) {
5901 		if(onHup)
5902 			onHup();
5903 	}
5904 
5905 	void delegate() onHup;
5906 
5907 	int fd = -1;
5908 	private bool enabled;
5909 	__gshared PosixFdReader[int] mapping;
5910 }
5911 
5912 // basic functions to access the clipboard
5913 /+
5914 
5915 
5916 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5917 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5918 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5919 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5920 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5921 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5922 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5923 
5924 +/
5925 
5926 /++
5927 	this does a delegate because it is actually an async call on X...
5928 	the receiver may never be called if the clipboard is empty or unavailable
5929 	gets plain text from the clipboard.
5930 +/
5931 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5932 	version(Windows) {
5933 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5934 		if(OpenClipboard(hwndOwner) == 0)
5935 			throw new WindowsApiException("OpenClipboard", GetLastError());
5936 		scope(exit)
5937 			CloseClipboard();
5938 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5939 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5940 
5941 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5942 				scope(exit)
5943 					GlobalUnlock(dataHandle);
5944 
5945 				// FIXME: CR/LF conversions
5946 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5947 				int len = 0;
5948 				auto d = data;
5949 				while(*d) {
5950 					d++;
5951 					len++;
5952 				}
5953 				string s;
5954 				s.reserve(len);
5955 				foreach(dchar ch; data[0 .. len]) {
5956 					s ~= ch;
5957 				}
5958 				receiver(s);
5959 			}
5960 		}
5961 	} else version(X11) {
5962 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5963 	} else version(OSXCocoa) {
5964 		throw new NotYetImplementedException();
5965 	} else static assert(0);
5966 }
5967 
5968 // FIXME: a clipboard listener might be cool btw
5969 
5970 /++
5971 	this does a delegate because it is actually an async call on X...
5972 	the receiver may never be called if the clipboard is empty or unavailable
5973 	gets image from the clipboard.
5974 
5975 	templated because it introduces an optional dependency on arsd.bmp
5976 +/
5977 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5978 	version(Windows) {
5979 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5980 		if(OpenClipboard(hwndOwner) == 0)
5981 			throw new WindowsApiException("OpenClipboard", GetLastError());
5982 		scope(exit)
5983 			CloseClipboard();
5984 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5985 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5986 				scope(exit)
5987 					GlobalUnlock(dataHandle);
5988 
5989 				auto len = GlobalSize(dataHandle);
5990 
5991 				import arsd.bmp;
5992 				auto img = readBmp(data[0 .. len], false);
5993 				receiver(img);
5994 			}
5995 		}
5996 	} else version(X11) {
5997 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5998 	} else version(OSXCocoa) {
5999 		throw new NotYetImplementedException();
6000 	} else static assert(0);
6001 }
6002 
6003 /// Copies some text to the clipboard.
6004 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6005 	assert(clipboardOwner !is null);
6006 	version(Windows) {
6007 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6008 			throw new WindowsApiException("OpenClipboard", GetLastError());
6009 		scope(exit)
6010 			CloseClipboard();
6011 		EmptyClipboard();
6012 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6013 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6014 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6015 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6016 			auto slice = data[0 .. sz];
6017 			scope(failure)
6018 				GlobalUnlock(handle);
6019 
6020 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6021 
6022 			GlobalUnlock(handle);
6023 			SetClipboardData(CF_UNICODETEXT, handle);
6024 		}
6025 	} else version(X11) {
6026 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6027 	} else version(OSXCocoa) {
6028 		throw new NotYetImplementedException();
6029 	} else static assert(0);
6030 }
6031 
6032 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6033 	assert(clipboardOwner !is null);
6034 	version(Windows) {
6035 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6036 			throw new WindowsApiException("OpenClipboard", GetLastError());
6037 		scope(exit)
6038 			CloseClipboard();
6039 		EmptyClipboard();
6040 
6041 
6042 		import arsd.bmp;
6043 		ubyte[] mdata;
6044 		mdata.reserve(img.width * img.height);
6045 		void sink(ubyte b) {
6046 			mdata ~= b;
6047 		}
6048 		writeBmpIndirect(img, &sink, false);
6049 
6050 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6051 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6052 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6053 			auto slice = data[0 .. mdata.length];
6054 			scope(failure)
6055 				GlobalUnlock(handle);
6056 
6057 			slice[] = mdata[];
6058 
6059 			GlobalUnlock(handle);
6060 			SetClipboardData(CF_DIB, handle);
6061 		}
6062 	} else version(X11) {
6063 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6064 			mixin X11SetSelectionHandler_Basics;
6065 			private const(ubyte)[] mdata;
6066 			private const(ubyte)[] mdata_original;
6067 			this(MemoryImage img) {
6068 				import arsd.bmp;
6069 
6070 				mdata.reserve(img.width * img.height);
6071 				void sink(ubyte b) {
6072 					mdata ~= b;
6073 				}
6074 				writeBmpIndirect(img, &sink, true);
6075 
6076 				mdata_original = mdata;
6077 			}
6078 
6079 			Atom[] availableFormats() {
6080 				auto display = XDisplayConnection.get;
6081 				return [
6082 					GetAtom!"image/bmp"(display),
6083 					GetAtom!"TARGETS"(display)
6084 				];
6085 			}
6086 
6087 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6088 				if(mdata.length < data.length) {
6089 					data[0 .. mdata.length] = mdata[];
6090 					auto ret = data[0 .. mdata.length];
6091 					mdata = mdata[$..$];
6092 					return ret;
6093 				} else {
6094 					data[] = mdata[0 .. data.length];
6095 					mdata = mdata[data.length .. $];
6096 					return data[];
6097 				}
6098 			}
6099 
6100 			void done() {
6101 				mdata = mdata_original;
6102 			}
6103 		}
6104 
6105 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6106 	} else version(OSXCocoa) {
6107 		throw new NotYetImplementedException();
6108 	} else static assert(0);
6109 }
6110 
6111 
6112 version(X11) {
6113 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6114 
6115 	private Atom*[] interredAtoms; // for discardAndRecreate
6116 
6117 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6118 	/// Platform-specific for X11.
6119 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6120 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6121 		static Atom a;
6122 		if(!a) {
6123 			a = XInternAtom(display, name, !create);
6124 			interredAtoms ~= &a;
6125 		}
6126 		if(a == None)
6127 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6128 		return a;
6129 	}
6130 
6131 	/// Platform-specific for X11 - gets atom names as a string.
6132 	string getAtomName(Atom atom, Display* display) {
6133 		auto got = XGetAtomName(display, atom);
6134 		scope(exit) XFree(got);
6135 		import core.stdc.string;
6136 		string s = got[0 .. strlen(got)].idup;
6137 		return s;
6138 	}
6139 
6140 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6141 	void setPrimarySelection(SimpleWindow window, string text) {
6142 		setX11Selection!"PRIMARY"(window, text);
6143 	}
6144 
6145 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6146 	void setSecondarySelection(SimpleWindow window, string text) {
6147 		setX11Selection!"SECONDARY"(window, text);
6148 	}
6149 
6150 	interface X11SetSelectionHandler {
6151 		// should include TARGETS right now
6152 		Atom[] availableFormats();
6153 		// Return the slice of data you filled, empty slice if done.
6154 		// this is to support the incremental thing
6155 		ubyte[] getData(Atom format, return scope ubyte[] data);
6156 
6157 		void done();
6158 
6159 		void handleRequest(XEvent);
6160 
6161 		bool matchesIncr(Window, Atom);
6162 		void sendMoreIncr(XPropertyEvent*);
6163 	}
6164 
6165 	mixin template X11SetSelectionHandler_Basics() {
6166 		Window incrWindow;
6167 		Atom incrAtom;
6168 		Atom selectionAtom;
6169 		Atom formatAtom;
6170 		ubyte[] toSend;
6171 		bool matchesIncr(Window w, Atom a) {
6172 			return incrAtom && incrAtom == a && w == incrWindow;
6173 		}
6174 		void sendMoreIncr(XPropertyEvent* event) {
6175 			auto display = XDisplayConnection.get;
6176 
6177 			XChangeProperty (display,
6178 				incrWindow,
6179 				incrAtom,
6180 				formatAtom,
6181 				8 /* bits */, PropModeReplace,
6182 				toSend.ptr, cast(int) toSend.length);
6183 
6184 			if(toSend.length != 0) {
6185 				toSend = this.getData(formatAtom, toSend[]);
6186 			} else {
6187 				this.done();
6188 				incrWindow = None;
6189 				incrAtom = None;
6190 				selectionAtom = None;
6191 				formatAtom = None;
6192 				toSend = null;
6193 			}
6194 		}
6195 		void handleRequest(XEvent ev) {
6196 
6197 			auto display = XDisplayConnection.get;
6198 
6199 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6200 			XSelectionEvent selectionEvent;
6201 			selectionEvent.type = EventType.SelectionNotify;
6202 			selectionEvent.display = event.display;
6203 			selectionEvent.requestor = event.requestor;
6204 			selectionEvent.selection = event.selection;
6205 			selectionEvent.time = event.time;
6206 			selectionEvent.target = event.target;
6207 
6208 			bool supportedType() {
6209 				foreach(t; this.availableFormats())
6210 					if(t == event.target)
6211 						return true;
6212 				return false;
6213 			}
6214 
6215 			if(event.property == None) {
6216 				selectionEvent.property = event.target;
6217 
6218 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6219 				XFlush(display);
6220 			} if(event.target == GetAtom!"TARGETS"(display)) {
6221 				/* respond with the supported types */
6222 				auto tlist = this.availableFormats();
6223 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6224 				selectionEvent.property = event.property;
6225 
6226 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6227 				XFlush(display);
6228 			} else if(supportedType()) {
6229 				auto buffer = new ubyte[](1024 * 64);
6230 				auto toSend = this.getData(event.target, buffer[]);
6231 
6232 				if(toSend.length < 32 * 1024) {
6233 					// small enough to send directly...
6234 					selectionEvent.property = event.property;
6235 					XChangeProperty (display,
6236 						selectionEvent.requestor,
6237 						selectionEvent.property,
6238 						event.target,
6239 						8 /* bits */, 0 /* PropModeReplace */,
6240 						toSend.ptr, cast(int) toSend.length);
6241 
6242 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6243 					XFlush(display);
6244 				} else {
6245 					// large, let's send incrementally
6246 					arch_ulong l = toSend.length;
6247 
6248 					// if I wanted other events from this window don't want to clear that out....
6249 					XWindowAttributes xwa;
6250 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6251 
6252 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6253 
6254 					incrWindow = event.requestor;
6255 					incrAtom = event.property;
6256 					formatAtom = event.target;
6257 					selectionAtom = event.selection;
6258 					this.toSend = toSend;
6259 
6260 					selectionEvent.property = event.property;
6261 					XChangeProperty (display,
6262 						selectionEvent.requestor,
6263 						selectionEvent.property,
6264 						GetAtom!"INCR"(display),
6265 						32 /* bits */, PropModeReplace,
6266 						&l, 1);
6267 
6268 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6269 					XFlush(display);
6270 				}
6271 				//if(after)
6272 					//after();
6273 			} else {
6274 				debug(sdpy_clip) {
6275 					writeln("Unsupported data ", getAtomName(event.target, display));
6276 				}
6277 				selectionEvent.property = None; // I don't know how to handle this type...
6278 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6279 				XFlush(display);
6280 			}
6281 		}
6282 	}
6283 
6284 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6285 		mixin X11SetSelectionHandler_Basics;
6286 		private const(ubyte)[] text;
6287 		private const(ubyte)[] text_original;
6288 		this(string text) {
6289 			this.text = cast(const ubyte[]) text;
6290 			this.text_original = this.text;
6291 		}
6292 		Atom[] availableFormats() {
6293 			auto display = XDisplayConnection.get;
6294 			return [
6295 				GetAtom!"UTF8_STRING"(display),
6296 				GetAtom!"text/plain"(display),
6297 				XA_STRING,
6298 				GetAtom!"TARGETS"(display)
6299 			];
6300 		}
6301 
6302 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6303 			if(text.length < data.length) {
6304 				data[0 .. text.length] = text[];
6305 				return data[0 .. text.length];
6306 			} else {
6307 				data[] = text[0 .. data.length];
6308 				text = text[data.length .. $];
6309 				return data[];
6310 			}
6311 		}
6312 
6313 		void done() {
6314 			text = text_original;
6315 		}
6316 	}
6317 
6318 	/// 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?!)
6319 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6320 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6321 	}
6322 
6323 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6324 		assert(window !is null);
6325 
6326 		auto display = XDisplayConnection.get();
6327 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6328 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6329 		else Atom a = GetAtom!atomName(display);
6330 
6331 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6332 
6333 		window.impl.setSelectionHandlers[a] = data;
6334 	}
6335 
6336 	///
6337 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6338 		getX11Selection!"PRIMARY"(window, handler);
6339 	}
6340 
6341 	// added July 28, 2020
6342 	// undocumented as experimental tho
6343 	interface X11GetSelectionHandler {
6344 		void handleData(Atom target, in ubyte[] data);
6345 		Atom findBestFormat(Atom[] answer);
6346 
6347 		void prepareIncremental(Window, Atom);
6348 		bool matchesIncr(Window, Atom);
6349 		void handleIncrData(Atom, in ubyte[] data);
6350 	}
6351 
6352 	mixin template X11GetSelectionHandler_Basics() {
6353 		Window incrWindow;
6354 		Atom incrAtom;
6355 
6356 		void prepareIncremental(Window w, Atom a) {
6357 			incrWindow = w;
6358 			incrAtom = a;
6359 		}
6360 		bool matchesIncr(Window w, Atom a) {
6361 			return incrWindow == w && incrAtom == a;
6362 		}
6363 
6364 		Atom incrFormatAtom;
6365 		ubyte[] incrData;
6366 		void handleIncrData(Atom format, in ubyte[] data) {
6367 			incrFormatAtom = format;
6368 
6369 			if(data.length)
6370 				incrData ~= data;
6371 			else
6372 				handleData(incrFormatAtom, incrData);
6373 
6374 		}
6375 	}
6376 
6377 	///
6378 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6379 		assert(window !is null);
6380 
6381 		auto display = XDisplayConnection.get();
6382 		auto atom = GetAtom!atomName(display);
6383 
6384 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6385 			this(void delegate(in char[]) handler) {
6386 				this.handler = handler;
6387 			}
6388 
6389 			mixin X11GetSelectionHandler_Basics;
6390 
6391 			void delegate(in char[]) handler;
6392 
6393 			void handleData(Atom target, in ubyte[] data) {
6394 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6395 					handler(cast(const char[]) data);
6396 			}
6397 
6398 			Atom findBestFormat(Atom[] answer) {
6399 				Atom best = None;
6400 				foreach(option; answer) {
6401 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6402 						best = option;
6403 						break;
6404 					} else if(option == XA_STRING) {
6405 						best = option;
6406 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6407 						best = option;
6408 					}
6409 				}
6410 				return best;
6411 			}
6412 		}
6413 
6414 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6415 
6416 		auto target = GetAtom!"TARGETS"(display);
6417 
6418 		// SDD_DATA is "simpledisplay.d data"
6419 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6420 	}
6421 
6422 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6423 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6424 		assert(window !is null);
6425 
6426 		auto display = XDisplayConnection.get();
6427 		auto atom = GetAtom!atomName(display);
6428 
6429 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6430 			this(void delegate(MemoryImage) handler) {
6431 				this.handler = handler;
6432 			}
6433 
6434 			mixin X11GetSelectionHandler_Basics;
6435 
6436 			void delegate(MemoryImage) handler;
6437 
6438 			void handleData(Atom target, in ubyte[] data) {
6439 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6440 					import arsd.bmp;
6441 					handler(readBmp(data));
6442 				}
6443 			}
6444 
6445 			Atom findBestFormat(Atom[] answer) {
6446 				Atom best = None;
6447 				foreach(option; answer) {
6448 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6449 						best = option;
6450 					}
6451 				}
6452 				return best;
6453 			}
6454 
6455 		}
6456 
6457 
6458 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6459 
6460 		auto target = GetAtom!"TARGETS"(display);
6461 
6462 		// SDD_DATA is "simpledisplay.d data"
6463 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6464 	}
6465 
6466 
6467 	///
6468 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6469 		Atom actualType;
6470 		int actualFormat;
6471 		arch_ulong actualItems;
6472 		arch_ulong bytesRemaining;
6473 		void* data;
6474 
6475 		auto display = XDisplayConnection.get();
6476 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6477 			if(actualFormat == 0)
6478 				return null;
6479 			else {
6480 				int byteLength;
6481 				if(actualFormat == 32) {
6482 					// 32 means it is a C long... which is variable length
6483 					actualFormat = cast(int) arch_long.sizeof * 8;
6484 				}
6485 
6486 				// then it is just a bit count
6487 				byteLength = cast(int) (actualItems * actualFormat / 8);
6488 
6489 				auto d = new ubyte[](byteLength);
6490 				d[] = cast(ubyte[]) data[0 .. byteLength];
6491 				XFree(data);
6492 				return d;
6493 			}
6494 		}
6495 		return null;
6496 	}
6497 
6498 	/* defined in the systray spec */
6499 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6500 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6501 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6502 
6503 
6504 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6505 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6506 	public class GlobalHotkey {
6507 		KeyEvent key;
6508 		void delegate () handler;
6509 
6510 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6511 
6512 		/// Create from initialzed KeyEvent object
6513 		this (KeyEvent akey, void delegate () ahandler=null) {
6514 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6515 			key = akey;
6516 			handler = ahandler;
6517 		}
6518 
6519 		/// Create from emacs-like key name ("C-M-Y", etc.)
6520 		this (const(char)[] akey, void delegate () ahandler=null) {
6521 			key = KeyEvent.parse(akey);
6522 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6523 			handler = ahandler;
6524 		}
6525 
6526 	}
6527 
6528 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6529 		//conwriteln("failed to grab key");
6530 		GlobalHotkeyManager.ghfailed = true;
6531 		return 0;
6532 	}
6533 
6534 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6535 		Image.impl.xshmfailed = true;
6536 		return 0;
6537 	}
6538 
6539 	private __gshared int errorHappened;
6540 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6541 		import core.stdc.stdio;
6542 		char[265] buffer;
6543 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6544 		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);
6545 		errorHappened = true;
6546 		return 0;
6547 	}
6548 
6549 	/++
6550 		Global hotkey manager. It contains static methods to manage global hotkeys.
6551 
6552 		---
6553 		 try {
6554 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6555 		} catch (Exception e) {
6556 			conwriteln("ERROR registering hotkey!");
6557 		}
6558 		EventLoop.get.run();
6559 		---
6560 
6561 		The key strings are based on Emacs. In practical terms,
6562 		`M` means `alt` and `H` means the Windows logo key. `C`
6563 		is `ctrl`.
6564 
6565 		$(WARNING
6566 			This is X-specific right now. If you are on
6567 			Windows, try [registerHotKey] instead.
6568 
6569 			We will probably merge these into a single
6570 			interface later.
6571 		)
6572 	+/
6573 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6574 		version(X11) {
6575 			void recreateAfterDisconnect() {
6576 				throw new Exception("NOT IMPLEMENTED");
6577 			}
6578 			void discardConnectionState() {
6579 				throw new Exception("NOT IMPLEMENTED");
6580 			}
6581 		}
6582 
6583 		private static immutable uint[8] masklist = [ 0,
6584 			KeyOrButtonMask.LockMask,
6585 			KeyOrButtonMask.Mod2Mask,
6586 			KeyOrButtonMask.Mod3Mask,
6587 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6588 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6589 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6590 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6591 		];
6592 		private __gshared GlobalHotkeyManager ghmanager;
6593 		private __gshared bool ghfailed = false;
6594 
6595 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6596 			if (modmask == 0) return false;
6597 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6598 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6599 			return true;
6600 		}
6601 
6602 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6603 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6604 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6605 			return modmask;
6606 		}
6607 
6608 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6609 			uint keycode = cast(uint)ke.key;
6610 			auto dpy = XDisplayConnection.get;
6611 			return XKeysymToKeycode(dpy, keycode);
6612 		}
6613 
6614 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6615 
6616 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6617 
6618 		NativeEventHandler getNativeEventHandler () {
6619 			return delegate int (XEvent e) {
6620 				if (e.type != EventType.KeyPress) return 1;
6621 				auto kev = cast(const(XKeyEvent)*)&e;
6622 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6623 				if (auto ghkp = hash in globalHotkeyList) {
6624 					try {
6625 						ghkp.doHandle();
6626 					} catch (Exception e) {
6627 						import core.stdc.stdio : stderr, fprintf;
6628 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6629 					}
6630 				}
6631 				return 1;
6632 			};
6633 		}
6634 
6635 		private this () {
6636 			auto dpy = XDisplayConnection.get;
6637 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6638 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6639 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6640 		}
6641 
6642 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6643 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6644 		static void register (GlobalHotkey gh) {
6645 			if (gh is null) return;
6646 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6647 
6648 			auto dpy = XDisplayConnection.get;
6649 			immutable keycode = keyEvent2KeyCode(gh.key);
6650 
6651 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6652 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6653 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6654 			XSync(dpy, 0/*False*/);
6655 
6656 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6657 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6658 			ghfailed = false;
6659 			foreach (immutable uint ormask; masklist[]) {
6660 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6661 			}
6662 			XSync(dpy, 0/*False*/);
6663 			XSetErrorHandler(savedErrorHandler);
6664 
6665 			if (ghfailed) {
6666 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6667 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6668 				XSync(dpy, 0/*False*/);
6669 				XSetErrorHandler(savedErrorHandler);
6670 				throw new Exception("cannot register global hotkey");
6671 			}
6672 
6673 			globalHotkeyList[hash] = gh;
6674 		}
6675 
6676 		/// Ditto
6677 		static void register (const(char)[] akey, void delegate () ahandler) {
6678 			register(new GlobalHotkey(akey, ahandler));
6679 		}
6680 
6681 		private static void removeByHash (ulong hash) {
6682 			if (auto ghp = hash in globalHotkeyList) {
6683 				auto dpy = XDisplayConnection.get;
6684 				immutable keycode = keyEvent2KeyCode(ghp.key);
6685 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6686 				XSync(dpy, 0/*False*/);
6687 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6688 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6689 				XSync(dpy, 0/*False*/);
6690 				XSetErrorHandler(savedErrorHandler);
6691 				globalHotkeyList.remove(hash);
6692 			}
6693 		}
6694 
6695 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6696 		/// It is safe to unregister unknown or invalid hotkey.
6697 		static void unregister (GlobalHotkey gh) {
6698 			//TODO: add second AA for faster search? prolly doesn't worth it.
6699 			if (gh is null) return;
6700 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6701 				if (kv.value is gh) {
6702 					removeByHash(kv.key);
6703 					return;
6704 				}
6705 			}
6706 		}
6707 
6708 		/// Ditto.
6709 		static void unregister (const(char)[] key) {
6710 			auto kev = KeyEvent.parse(key);
6711 			immutable keycode = keyEvent2KeyCode(kev);
6712 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6713 		}
6714 	}
6715 }
6716 
6717 version(Windows) {
6718 	/++
6719 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6720 
6721 		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).
6722 	+/
6723 	void sendSyntheticInput(wstring s) {
6724 			INPUT[] inputs;
6725 			inputs.reserve(s.length * 2);
6726 
6727 			foreach(wchar c; s) {
6728 				INPUT input;
6729 				input.type = INPUT_KEYBOARD;
6730 				input.ki.wScan = c;
6731 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6732 				inputs ~= input;
6733 
6734 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6735 				inputs ~= input;
6736 			}
6737 
6738 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6739 				throw new WindowsApiException("SendInput", GetLastError());
6740 			}
6741 
6742 	}
6743 
6744 
6745 	// global hotkey helper function
6746 
6747 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6748 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6749 		__gshared int hotkeyId = 0;
6750 		int id = ++hotkeyId;
6751 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6752 			throw new Exception("RegisterHotKey");
6753 
6754 		__gshared void delegate()[WPARAM][HWND] handlers;
6755 
6756 		handlers[window.impl.hwnd][id] = handler;
6757 
6758 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6759 
6760 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6761 			switch(msg) {
6762 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6763 				case WM_HOTKEY:
6764 					if(auto list = hwnd in handlers) {
6765 						if(auto h = wParam in *list) {
6766 							(*h)();
6767 							return 0;
6768 						}
6769 					}
6770 				goto default;
6771 				default:
6772 			}
6773 			if(oldHandler)
6774 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6775 			return 1; // pass it on
6776 		};
6777 
6778 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6779 			oldHandler = window.handleNativeEvent;
6780 			window.handleNativeEvent = nativeEventHandler;
6781 		}
6782 
6783 		return id;
6784 	}
6785 
6786 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6787 	void unregisterHotKey(SimpleWindow window, int id) {
6788 		if(!UnregisterHotKey(window.impl.hwnd, id))
6789 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6790 	}
6791 }
6792 
6793 version (X11) {
6794 	pragma(lib, "dl");
6795 	import core.sys.posix.dlfcn;
6796 }
6797 
6798 /++
6799 	Allows for sending synthetic input to the X server via the Xtst
6800 	extension or on Windows using SendInput.
6801 
6802 	Please remember user input is meant to be user - don't use this
6803 	if you have some other alternative!
6804 
6805 	History:
6806 		Added May 17, 2020 with the X implementation.
6807 
6808 		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.)
6809 	Bugs:
6810 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6811 +/
6812 struct SyntheticInput {
6813 	@disable this();
6814 
6815 	private int* refcount;
6816 
6817 	version(X11) {
6818 		private void* lib;
6819 
6820 		private extern(C) {
6821 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6822 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6823 		}
6824 	}
6825 
6826 	/// The dummy param must be 0.
6827 	this(int dummy) {
6828 		version(X11) {
6829 			lib = dlopen("libXtst.so", RTLD_NOW);
6830 			if(lib is null)
6831 				throw new Exception("cannot load xtest lib extension");
6832 			scope(failure)
6833 				dlclose(lib);
6834 
6835 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6836 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6837 
6838 			if(XTestFakeKeyEvent is null)
6839 				throw new Exception("No XTestFakeKeyEvent");
6840 			if(XTestFakeButtonEvent is null)
6841 				throw new Exception("No XTestFakeButtonEvent");
6842 		}
6843 
6844 		refcount = new int;
6845 		*refcount = 1;
6846 	}
6847 
6848 	this(this) {
6849 		if(refcount)
6850 			*refcount += 1;
6851 	}
6852 
6853 	~this() {
6854 		if(refcount) {
6855 			*refcount -= 1;
6856 			if(*refcount == 0)
6857 				// I commented this because if I close the lib before
6858 				// XCloseDisplay, it is liable to segfault... so just
6859 				// gonna keep it loaded if it is loaded, no big deal
6860 				// anyway.
6861 				{} // dlclose(lib);
6862 		}
6863 	}
6864 
6865 	/++
6866 		Simulates typing a string into the keyboard.
6867 
6868 		Bugs:
6869 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6870 
6871 			Not implemented except on Windows and X11.
6872 	+/
6873 	void sendSyntheticInput(string s) {
6874 		version(Windows) {
6875 			INPUT[] inputs;
6876 			inputs.reserve(s.length * 2);
6877 
6878 			auto ei = GetMessageExtraInfo();
6879 
6880 			foreach(wchar c; s) {
6881 				INPUT input;
6882 				input.type = INPUT_KEYBOARD;
6883 				input.ki.wScan = c;
6884 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6885 				input.ki.dwExtraInfo = ei;
6886 				inputs ~= input;
6887 
6888 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6889 				inputs ~= input;
6890 			}
6891 
6892 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6893 				throw new WindowsApiException("SendInput", GetLastError());
6894 			}
6895 		} else version(X11) {
6896 			int delay = 0;
6897 			foreach(ch; s) {
6898 				pressKey(cast(Key) ch, true, delay);
6899 				pressKey(cast(Key) ch, false, delay);
6900 				delay += 5;
6901 			}
6902 		} else throw new NotYetImplementedException();
6903 	}
6904 
6905 	/++
6906 		Sends a fake press or release key event.
6907 
6908 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6909 
6910 		Bugs:
6911 			The `delay` parameter is not implemented yet on Windows.
6912 
6913 			Not implemented except on Windows and X11.
6914 	+/
6915 	void pressKey(Key key, bool pressed, int delay = 0) {
6916 		version(Windows) {
6917 			INPUT input;
6918 			input.type = INPUT_KEYBOARD;
6919 			input.ki.wVk = cast(ushort) key;
6920 
6921 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6922 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6923 
6924 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6925 				throw new WindowsApiException("SendInput", GetLastError());
6926 			}
6927 		} else version(X11) {
6928 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6929 		} else throw new NotYetImplementedException();
6930 	}
6931 
6932 	/++
6933 		Sends a fake mouse button press or release event.
6934 
6935 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6936 
6937 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6938 
6939 		Bugs:
6940 			The `delay` parameter is not implemented yet on Windows.
6941 
6942 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6943 
6944 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6945 	+/
6946 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6947 		version(Windows) {
6948 			INPUT input;
6949 			input.type = INPUT_MOUSE;
6950 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6951 
6952 			// input.mi.mouseData for a wheel event
6953 
6954 			switch(button) {
6955 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6956 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6957 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6958 				case MouseButton.wheelUp:
6959 				case MouseButton.wheelDown:
6960 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6961 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6962 				break;
6963 				case MouseButton.backButton: throw new NotYetImplementedException();
6964 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6965 				default:
6966 			}
6967 
6968 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6969 				throw new WindowsApiException("SendInput", GetLastError());
6970 			}
6971 		} else version(X11) {
6972 			int btn;
6973 
6974 			switch(button) {
6975 				case MouseButton.left: btn = 1; break;
6976 				case MouseButton.middle: btn = 2; break;
6977 				case MouseButton.right: btn = 3; break;
6978 				case MouseButton.wheelUp: btn = 4; break;
6979 				case MouseButton.wheelDown: btn = 5; break;
6980 				case MouseButton.backButton: btn = 8; break;
6981 				case MouseButton.forwardButton: btn = 9; break;
6982 				default:
6983 			}
6984 
6985 			assert(btn);
6986 
6987 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6988 		} else throw new NotYetImplementedException();
6989 	}
6990 
6991 	///
6992 	static void moveMouseArrowBy(int dx, int dy) {
6993 		version(Windows) {
6994 			INPUT input;
6995 			input.type = INPUT_MOUSE;
6996 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6997 			input.mi.dx = dx;
6998 			input.mi.dy = dy;
6999 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7000 
7001 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7002 				throw new WindowsApiException("SendInput", GetLastError());
7003 			}
7004 		} else version(X11) {
7005 			auto disp = XDisplayConnection.get();
7006 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7007 			XFlush(disp);
7008 		} else throw new NotYetImplementedException();
7009 	}
7010 
7011 	///
7012 	static void moveMouseArrowTo(int x, int y) {
7013 		version(Windows) {
7014 			INPUT input;
7015 			input.type = INPUT_MOUSE;
7016 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7017 			input.mi.dx = x;
7018 			input.mi.dy = y;
7019 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7020 
7021 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7022 				throw new WindowsApiException("SendInput", GetLastError());
7023 			}
7024 		} else version(X11) {
7025 			auto disp = XDisplayConnection.get();
7026 			auto root = RootWindow(disp, DefaultScreen(disp));
7027 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7028 			XFlush(disp);
7029 		} else throw new NotYetImplementedException();
7030 	}
7031 }
7032 
7033 
7034 
7035 /++
7036 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7037 
7038 	See_Also:
7039 	$(LIST
7040 		*[ScreenPainter]
7041 		*[ScreenPainter.rasterOp]
7042 	)
7043 +/
7044 enum RasterOp {
7045 	normal, /// Replaces the pixel.
7046 	xor, /// Uses bitwise xor to draw.
7047 }
7048 
7049 // being phobos-free keeps the size WAY down
7050 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7051 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7052 package(arsd) const(wchar)* toWStringz(string s) {
7053 	wstring r;
7054 	foreach(dchar c; s)
7055 		r ~= c;
7056 	r ~= '\0';
7057 	return r.ptr;
7058 }
7059 private string[] split(in void[] a, char c) {
7060 		string[] ret;
7061 		size_t previous = 0;
7062 		foreach(i, char ch; cast(ubyte[]) a) {
7063 			if(ch == c) {
7064 				ret ~= cast(string) a[previous .. i];
7065 				previous = i + 1;
7066 			}
7067 		}
7068 		if(previous != a.length)
7069 			ret ~= cast(string) a[previous .. $];
7070 		return ret;
7071 	}
7072 
7073 version(without_opengl) {
7074 	enum OpenGlOptions {
7075 		no,
7076 	}
7077 } else {
7078 	/++
7079 		Determines if you want an OpenGL context created on the new window.
7080 
7081 
7082 		See more: [#topics-3d|in the 3d topic].
7083 
7084 		---
7085 		import arsd.simpledisplay;
7086 		void main() {
7087 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7088 
7089 			// Set up the matrix
7090 			window.setAsCurrentOpenGlContext(); // make this window active
7091 
7092 			// This is called on each frame, we will draw our scene
7093 			window.redrawOpenGlScene = delegate() {
7094 
7095 			};
7096 
7097 			window.eventLoop(0);
7098 		}
7099 		---
7100 	+/
7101 	enum OpenGlOptions {
7102 		no, /// No OpenGL context is created
7103 		yes, /// Yes, create an OpenGL context
7104 	}
7105 
7106 	version(X11) {
7107 		static if (!SdpyIsUsingIVGLBinds) {
7108 
7109 
7110 			struct __GLXFBConfigRec {}
7111 			alias GLXFBConfig = __GLXFBConfigRec*;
7112 
7113 			//pragma(lib, "GL");
7114 			//pragma(lib, "GLU");
7115 			interface GLX {
7116 			extern(C) nothrow @nogc {
7117 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7118 						const int *attrib_list);
7119 
7120 				 void glXCopyContext(Display *dpy, GLXContext src,
7121 						GLXContext dst, arch_ulong mask);
7122 
7123 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7124 						GLXContext share_list, Bool direct);
7125 
7126 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7127 						Pixmap pixmap);
7128 
7129 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7130 
7131 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7132 
7133 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7134 						int attrib, int *value);
7135 
7136 				 GLXContext glXGetCurrentContext();
7137 
7138 				 GLXDrawable glXGetCurrentDrawable();
7139 
7140 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7141 
7142 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7143 						GLXContext ctx);
7144 
7145 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7146 
7147 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7148 
7149 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7150 
7151 				 void glXUseXFont(Font font, int first, int count, int list_base);
7152 
7153 				 void glXWaitGL();
7154 
7155 				 void glXWaitX();
7156 
7157 
7158 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7159 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7160 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7161 
7162 				char* glXQueryExtensionsString (Display*, int);
7163 				void* glXGetProcAddress (const(char)*);
7164 
7165 			}
7166 			}
7167 
7168 			version(OSX)
7169 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7170 			else
7171 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7172 			shared static this() {
7173 				glx.loadDynamicLibrary();
7174 			}
7175 
7176 			alias glbindGetProcAddress = glXGetProcAddress;
7177 		}
7178 	} else version(Windows) {
7179 		/* it is done below by interface GL */
7180 	} else
7181 		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.");
7182 }
7183 
7184 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7185 alias Resizablity = Resizability;
7186 
7187 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7188 enum Resizability {
7189 	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.
7190 	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.
7191 	/++
7192 		$(PITFALL
7193 			Planned for the future but not implemented.
7194 		)
7195 
7196 		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.
7197 
7198 		History:
7199 			Added November 11, 2022, but not yet implemented and may not be for some time.
7200 	+/
7201 	/*@__future*/ allowResizingMaintainingAspectRatio,
7202 	/++
7203 		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.
7204 
7205 		History:
7206 			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.
7207 
7208 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7209 	+/
7210 	automaticallyScaleIfPossible,
7211 }
7212 /// ditto
7213 alias Resizeability = Resizability;
7214 
7215 
7216 /++
7217 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7218 +/
7219 enum TextAlignment : uint {
7220 	Left = 0, ///
7221 	Center = 1, ///
7222 	Right = 2, ///
7223 
7224 	VerticalTop = 0, ///
7225 	VerticalCenter = 4, ///
7226 	VerticalBottom = 8, ///
7227 }
7228 
7229 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7230 alias Rectangle = arsd.color.Rectangle;
7231 
7232 
7233 /++
7234 	Keyboard press and release events.
7235 +/
7236 struct KeyEvent {
7237 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7238 	Key key;
7239 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7240 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7241 
7242 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7243 
7244 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7245 
7246 	SimpleWindow window; /// associated Window
7247 
7248 	/++
7249 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7250 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7251 		to predict if char events are actually coming..
7252 
7253 		Only available on X systems since this information is not given ahead of time elsewhere.
7254 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7255 
7256 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7257 		and potential quirks I'd recommend avoiding it.
7258 
7259 		History:
7260 			Added April 26, 2021 (dub v9.5)
7261 	+/
7262 	version(X11)
7263 		dchar[] charsPossible;
7264 
7265 	// convert key event to simplified string representation a-la emacs
7266 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7267 		uint dpos = 0;
7268 		void put (const(char)[] s...) nothrow @trusted {
7269 			static if (growdest) {
7270 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7271 			} else {
7272 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7273 			}
7274 		}
7275 
7276 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7277 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7278 		}
7279 
7280 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7281 
7282 		// put modifiers
7283 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7284 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7285 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7286 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7287 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7288 
7289 		if (this.key) {
7290 			foreach (string kn; __traits(allMembers, Key)) {
7291 				if (this.key == __traits(getMember, Key, kn)) {
7292 					// HACK!
7293 					static if (kn == "N0") put("0");
7294 					else static if (kn == "N1") put("1");
7295 					else static if (kn == "N2") put("2");
7296 					else static if (kn == "N3") put("3");
7297 					else static if (kn == "N4") put("4");
7298 					else static if (kn == "N5") put("5");
7299 					else static if (kn == "N6") put("6");
7300 					else static if (kn == "N7") put("7");
7301 					else static if (kn == "N8") put("8");
7302 					else static if (kn == "N9") put("9");
7303 					else put(kn);
7304 					return dest[0..dpos];
7305 				}
7306 			}
7307 			put("Unknown");
7308 		} else {
7309 			if (dpos && dest[dpos-1] == '+') --dpos;
7310 		}
7311 		return dest[0..dpos];
7312 	}
7313 
7314 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7315 
7316 	/** Parse string into key name with modifiers. It accepts things like:
7317 	 *
7318 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7319 	 *
7320 	 * Ctrl+Win+1 -- windows style
7321 	 *
7322 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7323 	 *
7324 	 * Ctrl Win 1 -- and space
7325 	 *
7326 	 * and even "Win + 1 + Ctrl".
7327 	 */
7328 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7329 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7330 
7331 		// remove trailing spaces
7332 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7333 
7334 		// tokens delimited by blank, '+', or '-'
7335 		// null on eol
7336 		const(char)[] getToken () nothrow @trusted @nogc {
7337 			// remove leading spaces and delimiters
7338 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7339 			if (name.length == 0) return null; // oops, no more tokens
7340 			// get token
7341 			size_t epos = 0;
7342 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7343 			assert(epos > 0 && epos <= name.length);
7344 			auto res = name[0..epos];
7345 			name = name[epos..$];
7346 			return res;
7347 		}
7348 
7349 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7350 			if (s0.length != s1.length) return false;
7351 			foreach (immutable ci, char c0; s0) {
7352 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7353 				char c1 = s1[ci];
7354 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7355 				if (c0 != c1) return false;
7356 			}
7357 			return true;
7358 		}
7359 
7360 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7361 		if (updown !is null) *updown = -1;
7362 		KeyEvent res;
7363 		res.key = cast(Key)0; // just in case
7364 		const(char)[] tk, tkn; // last token
7365 		bool allowEmascStyle = true;
7366 		bool ignoreModifiers = false;
7367 		tokenloop: for (;;) {
7368 			tk = tkn;
7369 			tkn = getToken();
7370 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7371 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7372 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7373 			if (allowEmascStyle && tkn.length != 0) {
7374 				if (tk.length == 1) {
7375 					char mdc = tk[0];
7376 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7377 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7378 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7379 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7380 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7381 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7382 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7383 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7384 				}
7385 			}
7386 			allowEmascStyle = false;
7387 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7388 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7389 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7390 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7391 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7392 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7393 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7394 			if (tk.length == 0) continue;
7395 			// try key name
7396 			if (res.key == 0) {
7397 				// little hack
7398 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7399 					final switch (tk[0]) {
7400 						case '0': tk = "N0"; break;
7401 						case '1': tk = "N1"; break;
7402 						case '2': tk = "N2"; break;
7403 						case '3': tk = "N3"; break;
7404 						case '4': tk = "N4"; break;
7405 						case '5': tk = "N5"; break;
7406 						case '6': tk = "N6"; break;
7407 						case '7': tk = "N7"; break;
7408 						case '8': tk = "N8"; break;
7409 						case '9': tk = "N9"; break;
7410 					}
7411 				}
7412 				foreach (string kn; __traits(allMembers, Key)) {
7413 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7414 				}
7415 			}
7416 			// unknown or duplicate key name, get out of here
7417 			break;
7418 		}
7419 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7420 		return res; // something
7421 	}
7422 
7423 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7424 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7425 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7426 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7427 		}
7428 		bool ignoreMods;
7429 		int updown;
7430 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7431 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7432 		if (this.key != ke.key) {
7433 			// things like "ctrl+alt" are complicated
7434 			uint tkm = this.modifierState&modmask;
7435 			uint kkm = ke.modifierState&modmask;
7436 			Key tk = this.key;
7437 			// ke
7438 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7439 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7440 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7441 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7442 			// this
7443 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7444 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7445 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7446 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7447 			return (tk == ke.key && tkm == kkm);
7448 		}
7449 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7450 	}
7451 }
7452 
7453 /// Sets the application name.
7454 @property string ApplicationName(string name) {
7455 	return _applicationName = name;
7456 }
7457 
7458 string _applicationName;
7459 
7460 /// ditto
7461 @property string ApplicationName() {
7462 	if(_applicationName is null) {
7463 		import core.runtime;
7464 		return Runtime.args[0];
7465 	}
7466 	return _applicationName;
7467 }
7468 
7469 
7470 /// Type of a [MouseEvent].
7471 enum MouseEventType : int {
7472 	motion = 0, /// The mouse moved inside the window
7473 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7474 	buttonReleased = 2, /// A mouse button was released
7475 }
7476 
7477 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7478 /++
7479 	Listen for this on your event listeners if you are interested in mouse action.
7480 
7481 	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.
7482 
7483 	Examples:
7484 
7485 	This will draw boxes on the window with the mouse as you hold the left button.
7486 	---
7487 	import arsd.simpledisplay;
7488 
7489 	void main() {
7490 		auto window = new SimpleWindow();
7491 
7492 		window.eventLoop(0,
7493 			(MouseEvent ev) {
7494 				if(ev.modifierState & ModifierState.leftButtonDown) {
7495 					auto painter = window.draw();
7496 					painter.fillColor = Color.red;
7497 					painter.outlineColor = Color.black;
7498 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7499 				}
7500 			}
7501 		);
7502 	}
7503 	---
7504 +/
7505 struct MouseEvent {
7506 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7507 
7508 	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.
7509 	int y; /// Current Y position of the cursor when the event fired.
7510 
7511 	int dx; /// Change in X position since last report
7512 	int dy; /// Change in Y position since last report
7513 
7514 	MouseButton button; /// See [MouseButton]
7515 	int modifierState; /// See [ModifierState]
7516 
7517 	version(X11)
7518 		private Time timestamp;
7519 
7520 	/// Returns a linear representation of mouse button,
7521 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7522 	///
7523 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7524 	@property ubyte buttonLinear() const {
7525 		import core.bitop;
7526 		if(button == 0)
7527 			return 0;
7528 		return (bsf(button) + 1) & 0b1111;
7529 	}
7530 
7531 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7532 
7533 	SimpleWindow window; /// The window in which the event happened.
7534 
7535 	Point globalCoordinates() {
7536 		Point p;
7537 		if(window is null)
7538 			throw new Exception("wtf");
7539 		static if(UsingSimpledisplayX11) {
7540 			Window child;
7541 			XTranslateCoordinates(
7542 				XDisplayConnection.get,
7543 				window.impl.window,
7544 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7545 				x, y, &p.x, &p.y, &child);
7546 			return p;
7547 		} else version(Windows) {
7548 			POINT[1] points;
7549 			points[0].x = x;
7550 			points[0].y = y;
7551 			MapWindowPoints(
7552 				window.impl.hwnd,
7553 				null,
7554 				points.ptr,
7555 				points.length
7556 			);
7557 			p.x = points[0].x;
7558 			p.y = points[0].y;
7559 
7560 			return p;
7561 		} else version(OSXCocoa) {
7562 			throw new NotYetImplementedException();
7563 		} else static assert(0);
7564 	}
7565 
7566 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7567 
7568 	/**
7569 	can contain emacs-like modifier prefix
7570 	case-insensitive names:
7571 		lmbX/leftX
7572 		rmbX/rightX
7573 		mmbX/middleX
7574 		wheelX
7575 		motion (no prefix allowed)
7576 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7577 	*/
7578 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7579 		if (str.length == 0) return false; // just in case
7580 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7581 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7582 		auto anchor = str;
7583 		uint mods = 0; // uint.max == any
7584 		// interesting bits in kmod
7585 		uint kmodmask =
7586 			ModifierState.shift|
7587 			ModifierState.ctrl|
7588 			ModifierState.alt|
7589 			ModifierState.windows|
7590 			ModifierState.leftButtonDown|
7591 			ModifierState.middleButtonDown|
7592 			ModifierState.rightButtonDown|
7593 			0;
7594 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7595 		bool wasButtons = false;
7596 		while (str.length) {
7597 			if (str.ptr[0] <= ' ') {
7598 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7599 				continue;
7600 			}
7601 			// one-letter modifier?
7602 			if (str.length >= 2 && str.ptr[1] == '-') {
7603 				switch (str.ptr[0]) {
7604 					case '*': // "any" modifier (cannot be undone)
7605 						mods = mods.max;
7606 						break;
7607 					case 'C': case 'c': // emacs "ctrl"
7608 						if (mods != mods.max) mods |= ModifierState.ctrl;
7609 						break;
7610 					case 'M': case 'm': // emacs "meta"
7611 						if (mods != mods.max) mods |= ModifierState.alt;
7612 						break;
7613 					case 'S': case 's': // emacs "shift"
7614 						if (mods != mods.max) mods |= ModifierState.shift;
7615 						break;
7616 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7617 						if (mods != mods.max) mods |= ModifierState.windows;
7618 						break;
7619 					default:
7620 						return false; // unknown modifier
7621 				}
7622 				str = str[2..$];
7623 				continue;
7624 			}
7625 			// word
7626 			char[16] buf = void; // locased
7627 			auto wep = 0;
7628 			while (str.length) {
7629 				immutable char ch = str.ptr[0];
7630 				if (ch <= ' ' || ch == '-') break;
7631 				str = str[1..$];
7632 				if (wep > buf.length) return false; // too long
7633 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7634 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7635 				else return false; // invalid char
7636 			}
7637 			if (wep == 0) return false; // just in case
7638 			uint bnum;
7639 			enum UpDown { None = -1, Up, Down, Any }
7640 			auto updown = UpDown.None; // 0: up; 1: down
7641 			switch (buf[0..wep]) {
7642 				// left button
7643 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7644 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7645 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7646 				case "lmb": case "left": bnum = 0; break;
7647 				// middle button
7648 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7649 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7650 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7651 				case "mmb": case "middle": bnum = 1; break;
7652 				// right button
7653 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7654 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7655 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7656 				case "rmb": case "right": bnum = 2; break;
7657 				// wheel
7658 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7659 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7660 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7661 				case "wheel": bnum = 3; break;
7662 				// motion
7663 				case "motion": bnum = 7; break;
7664 				// unknown
7665 				default: return false;
7666 			}
7667 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7668 			// parse possible "-up" or "-down"
7669 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7670 				wep = 0;
7671 				foreach (immutable idx, immutable char ch; str[1..$]) {
7672 					if (ch <= ' ' || ch == '-') break;
7673 					assert(idx == wep); // for now; trick
7674 					if (wep > buf.length) { wep = 0; break; } // too long
7675 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7676 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7677 					else { wep = 0; break; } // invalid char
7678 				}
7679 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7680 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7681 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7682 				// remove parsed part
7683 				if (updown != UpDown.None) str = str[wep+1..$];
7684 			}
7685 			if (updown == UpDown.None) {
7686 				updown = UpDown.Down;
7687 			}
7688 			wasButtons = wasButtons || (bnum <= 2);
7689 			//assert(updown != UpDown.None);
7690 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7691 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7692 			if (lastButt != lastButt.max) {
7693 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7694 				if (mods != mods.max) {
7695 					uint butbit = 0;
7696 					final switch (lastButt&0x03) {
7697 						case 0: butbit = ModifierState.leftButtonDown; break;
7698 						case 1: butbit = ModifierState.middleButtonDown; break;
7699 						case 2: butbit = ModifierState.rightButtonDown; break;
7700 					}
7701 					     if (lastButt&Flag.Down) mods |= butbit;
7702 					else if (lastButt&Flag.Up) mods &= ~butbit;
7703 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7704 				}
7705 			}
7706 			// remember last button
7707 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7708 		}
7709 		// no button -- nothing to do
7710 		if (lastButt == lastButt.max) return false;
7711 		// done parsing, check if something's left
7712 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7713 		// remove action button from mask
7714 		if ((lastButt&0xff) < 3) {
7715 			final switch (lastButt&0x03) {
7716 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7717 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7718 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7719 			}
7720 		}
7721 		// special case: "Motion" means "ignore buttons"
7722 		if ((lastButt&0xff) == 7 && !wasButtons) {
7723 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7724 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7725 		}
7726 		uint kmod = event.modifierState&kmodmask;
7727 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7728 		// check modifier state
7729 		if (mods != mods.max) {
7730 			if (kmod != mods) return false;
7731 		}
7732 		// now check type
7733 		if ((lastButt&0xff) == 7) {
7734 			// motion
7735 			if (event.type != MouseEventType.motion) return false;
7736 		} else if ((lastButt&0xff) == 3) {
7737 			// wheel
7738 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7739 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7740 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7741 			return false;
7742 		} else {
7743 			// buttons
7744 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7745 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7746 			{
7747 				return false;
7748 			}
7749 			// button number
7750 			switch (lastButt&0x03) {
7751 				case 0: if (event.button != MouseButton.left) return false; break;
7752 				case 1: if (event.button != MouseButton.middle) return false; break;
7753 				case 2: if (event.button != MouseButton.right) return false; break;
7754 				default: return false;
7755 			}
7756 		}
7757 		return true;
7758 	}
7759 }
7760 
7761 version(arsd_mevent_strcmp_test) unittest {
7762 	MouseEvent event;
7763 	event.type = MouseEventType.buttonPressed;
7764 	event.button = MouseButton.left;
7765 	event.modifierState = ModifierState.ctrl;
7766 	assert(event == "C-LMB");
7767 	assert(event != "C-LMBUP");
7768 	assert(event != "C-LMB-UP");
7769 	assert(event != "C-S-LMB");
7770 	assert(event == "*-LMB");
7771 	assert(event != "*-LMB-UP");
7772 
7773 	event.type = MouseEventType.buttonReleased;
7774 	assert(event != "C-LMB");
7775 	assert(event == "C-LMBUP");
7776 	assert(event == "C-LMB-UP");
7777 	assert(event != "C-S-LMB");
7778 	assert(event != "*-LMB");
7779 	assert(event == "*-LMB-UP");
7780 
7781 	event.button = MouseButton.right;
7782 	event.modifierState |= ModifierState.shift;
7783 	event.type = MouseEventType.buttonPressed;
7784 	assert(event != "C-LMB");
7785 	assert(event != "C-LMBUP");
7786 	assert(event != "C-LMB-UP");
7787 	assert(event != "C-S-LMB");
7788 	assert(event != "*-LMB");
7789 	assert(event != "*-LMB-UP");
7790 
7791 	assert(event != "C-RMB");
7792 	assert(event != "C-RMBUP");
7793 	assert(event != "C-RMB-UP");
7794 	assert(event == "C-S-RMB");
7795 	assert(event == "*-RMB");
7796 	assert(event != "*-RMB-UP");
7797 }
7798 
7799 /// This gives a few more options to drawing lines and such
7800 struct Pen {
7801 	Color color; /// the foreground color
7802 	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.
7803 	Style style; /// See [Style]
7804 /+
7805 // From X.h
7806 
7807 #define LineSolid		0
7808 #define LineOnOffDash		1
7809 #define LineDoubleDash		2
7810        LineDou-        The full path of the line is drawn, but the
7811        bleDash         even dashes are filled differently from the
7812                        odd dashes (see fill-style) with CapButt
7813                        style used where even and odd dashes meet.
7814 
7815 
7816 
7817 /* capStyle */
7818 
7819 #define CapNotLast		0
7820 #define CapButt			1
7821 #define CapRound		2
7822 #define CapProjecting		3
7823 
7824 /* joinStyle */
7825 
7826 #define JoinMiter		0
7827 #define JoinRound		1
7828 #define JoinBevel		2
7829 
7830 /* fillStyle */
7831 
7832 #define FillSolid		0
7833 #define FillTiled		1
7834 #define FillStippled		2
7835 #define FillOpaqueStippled	3
7836 
7837 
7838 +/
7839 	/// Style of lines drawn
7840 	enum Style {
7841 		Solid, /// a solid line
7842 		Dashed, /// a dashed line
7843 		Dotted, /// a dotted line
7844 	}
7845 }
7846 
7847 
7848 /++
7849 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7850 
7851 
7852 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7853 
7854 	$(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.)
7855 
7856 	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.
7857 
7858 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7859 
7860 	$(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.
7861 
7862 	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!
7863 
7864 	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!)
7865 
7866 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7867 
7868 	---
7869 		auto image = new Image(256, 256);
7870 		scope(exit) destroy(image);
7871 	---
7872 
7873 	As long as you don't hold on to it outside the scope.
7874 
7875 	I might change it to be an owned pointer at some point in the future.
7876 
7877 	)
7878 
7879 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7880 	you can also often get a fair amount of speedup by getting the raw data format and
7881 	writing some custom code.
7882 
7883 	FIXME INSERT EXAMPLES HERE
7884 
7885 
7886 +/
7887 final class Image {
7888 	///
7889 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7890 		this.width = width;
7891 		this.height = height;
7892 		this.enableAlpha = enableAlpha;
7893 
7894 		impl.createImage(width, height, forcexshm, enableAlpha);
7895 	}
7896 
7897 	///
7898 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7899 		this(size.width, size.height, forcexshm, enableAlpha);
7900 	}
7901 
7902 	private bool suppressDestruction;
7903 
7904 	version(X11)
7905 	this(XImage* handle) {
7906 		this.handle = handle;
7907 		this.rawData = cast(ubyte*) handle.data;
7908 		this.width = handle.width;
7909 		this.height = handle.height;
7910 		this.enableAlpha = handle.depth == 32;
7911 		suppressDestruction = true;
7912 	}
7913 
7914 	~this() {
7915 		if(suppressDestruction) return;
7916 		impl.dispose();
7917 	}
7918 
7919 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7920 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7921 	pure const @system nothrow {
7922 		/*
7923 			To use these to draw a blue rectangle with size WxH at position X,Y...
7924 
7925 			// make certain that it will fit before we proceed
7926 			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!
7927 
7928 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7929 			// (though calculating them isn't really that expensive).
7930 			auto nextLineAdjustment = img.adjustmentForNextLine();
7931 			auto offR = img.redByteOffset();
7932 			auto offB = img.blueByteOffset();
7933 			auto offG = img.greenByteOffset();
7934 			auto bpp = img.bytesPerPixel();
7935 
7936 			auto data = img.getDataPointer();
7937 
7938 			// figure out the starting byte offset
7939 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7940 
7941 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7942 
7943 			// and now our drawing loop for the rectangle
7944 			foreach(y; 0 .. H) {
7945 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7946 				foreach(x; 0 .. W) {
7947 					// write our color
7948 					data[offR] = 0;
7949 					data[offG] = 0;
7950 					data[offB] = 255;
7951 
7952 					data += bpp; // moving to the next pixel is just an addition...
7953 				}
7954 				startOfLine += nextLineAdjustment;
7955 			}
7956 
7957 
7958 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7959 
7960 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7961 			can be made into a bitmask or something so we can write them as *uint...
7962 		*/
7963 
7964 		///
7965 		int offsetForTopLeftPixel() {
7966 			version(X11) {
7967 				return 0;
7968 			} else version(Windows) {
7969 				if(enableAlpha) {
7970 					return (width * 4) * (height - 1);
7971 				} else {
7972 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7973 				}
7974 			} else version(OSXCocoa) {
7975 				return 0 ; //throw new NotYetImplementedException();
7976 			} else static assert(0, "fill in this info for other OSes");
7977 		}
7978 
7979 		///
7980 		int offsetForPixel(int x, int y) {
7981 			version(X11) {
7982 				auto offset = (y * width + x) * 4;
7983 				return offset;
7984 			} else version(Windows) {
7985 				if(enableAlpha) {
7986 					auto itemsPerLine = width * 4;
7987 					// remember, bmps are upside down
7988 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7989 					return offset;
7990 				} else {
7991 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7992 					// remember, bmps are upside down
7993 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7994 					return offset;
7995 				}
7996 			} else version(OSXCocoa) {
7997 				return 0 ; //throw new NotYetImplementedException();
7998 			} else static assert(0, "fill in this info for other OSes");
7999 		}
8000 
8001 		///
8002 		int adjustmentForNextLine() {
8003 			version(X11) {
8004 				return width * 4;
8005 			} else version(Windows) {
8006 				// windows bmps are upside down, so the adjustment is actually negative
8007 				if(enableAlpha)
8008 					return - (cast(int) width * 4);
8009 				else
8010 					return -((cast(int) width * 3 + 3) / 4) * 4;
8011 			} else version(OSXCocoa) {
8012 				return 0 ; //throw new NotYetImplementedException();
8013 			} else static assert(0, "fill in this info for other OSes");
8014 		}
8015 
8016 		/// once you have the position of a pixel, use these to get to the proper color
8017 		int redByteOffset() {
8018 			version(X11) {
8019 				return 2;
8020 			} else version(Windows) {
8021 				return 2;
8022 			} else version(OSXCocoa) {
8023 				return 0 ; //throw new NotYetImplementedException();
8024 			} else static assert(0, "fill in this info for other OSes");
8025 		}
8026 
8027 		///
8028 		int greenByteOffset() {
8029 			version(X11) {
8030 				return 1;
8031 			} else version(Windows) {
8032 				return 1;
8033 			} else version(OSXCocoa) {
8034 				return 0 ; //throw new NotYetImplementedException();
8035 			} else static assert(0, "fill in this info for other OSes");
8036 		}
8037 
8038 		///
8039 		int blueByteOffset() {
8040 			version(X11) {
8041 				return 0;
8042 			} else version(Windows) {
8043 				return 0;
8044 			} else version(OSXCocoa) {
8045 				return 0 ; //throw new NotYetImplementedException();
8046 			} else static assert(0, "fill in this info for other OSes");
8047 		}
8048 
8049 		/// Only valid if [enableAlpha] is true
8050 		int alphaByteOffset() {
8051 			version(X11) {
8052 				return 3;
8053 			} else version(Windows) {
8054 				return 3;
8055 			} else version(OSXCocoa) {
8056 				return 3; //throw new NotYetImplementedException();
8057 			} else static assert(0, "fill in this info for other OSes");
8058 		}
8059 	}
8060 
8061 	///
8062 	final void putPixel(int x, int y, Color c) {
8063 		if(x < 0 || x >= width)
8064 			return;
8065 		if(y < 0 || y >= height)
8066 			return;
8067 
8068 		impl.setPixel(x, y, c);
8069 	}
8070 
8071 	///
8072 	final Color getPixel(int x, int y) {
8073 		if(x < 0 || x >= width)
8074 			return Color.transparent;
8075 		if(y < 0 || y >= height)
8076 			return Color.transparent;
8077 
8078 		version(OSXCocoa) throw new NotYetImplementedException(); else
8079 		return impl.getPixel(x, y);
8080 	}
8081 
8082 	///
8083 	final void opIndexAssign(Color c, int x, int y) {
8084 		putPixel(x, y, c);
8085 	}
8086 
8087 	///
8088 	TrueColorImage toTrueColorImage() {
8089 		auto tci = new TrueColorImage(width, height);
8090 		convertToRgbaBytes(tci.imageData.bytes);
8091 		return tci;
8092 	}
8093 
8094 	///
8095 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8096 		auto tci = i.getAsTrueColorImage();
8097 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8098 		static if(UsingSimpledisplayX11)
8099 			img.premultiply = premultiply;
8100 		img.setRgbaBytes(tci.imageData.bytes);
8101 		return img;
8102 	}
8103 
8104 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8105 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8106 	/// if you pass null, it will allocate a new one.
8107 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8108 		if(where is null)
8109 			where = new ubyte[this.width*this.height*4];
8110 		convertToRgbaBytes(where);
8111 		return where;
8112 	}
8113 
8114 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8115 	void setRgbaBytes(in ubyte[] from ) {
8116 		assert(from.length == this.width * this.height * 4);
8117 		setFromRgbaBytes(from);
8118 	}
8119 
8120 	// FIXME: make properly cross platform by getting rgba right
8121 
8122 	/// warning: this is not portable across platforms because the data format can change
8123 	ubyte* getDataPointer() {
8124 		return impl.rawData;
8125 	}
8126 
8127 	/// for use with getDataPointer
8128 	final int bytesPerLine() const pure @safe nothrow {
8129 		version(Windows)
8130 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8131 		else version(X11)
8132 			return 4 * width;
8133 		else version(OSXCocoa)
8134 			return 4 * width;
8135 		else static assert(0);
8136 	}
8137 
8138 	/// for use with getDataPointer
8139 	final int bytesPerPixel() const pure @safe nothrow {
8140 		version(Windows)
8141 			return enableAlpha ? 4 : 3;
8142 		else version(X11)
8143 			return 4;
8144 		else version(OSXCocoa)
8145 			return 4;
8146 		else static assert(0);
8147 	}
8148 
8149 	///
8150 	immutable int width;
8151 
8152 	///
8153 	immutable int height;
8154 
8155 	///
8156 	immutable bool enableAlpha;
8157     //private:
8158 	mixin NativeImageImplementation!() impl;
8159 }
8160 
8161 /++
8162 	A convenience function to pop up a window displaying the image.
8163 	If you pass a win, it will draw the image in it. Otherwise, it will
8164 	create a window with the size of the image and run its event loop, closing
8165 	when a key is pressed.
8166 
8167 	History:
8168 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8169 		always block until the application quit which could cause bizarre behavior
8170 		inside a more complex application. Now, the default is to block until
8171 		this window closes if it is the only event loop running, and otherwise,
8172 		not to block at all and just pop up the display window asynchronously.
8173 +/
8174 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8175 	if(win is null) {
8176 		win = new SimpleWindow(image);
8177 		{
8178 			auto p = win.draw;
8179 			p.drawImage(Point(0, 0), image);
8180 		}
8181 		win.eventLoopWithBlockingMode(
8182 			bm, 0,
8183 			(KeyEvent ev) {
8184 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8185 			} );
8186 	} else {
8187 		win.image = image;
8188 	}
8189 }
8190 
8191 enum FontWeight : int {
8192 	dontcare = 0,
8193 	thin = 100,
8194 	extralight = 200,
8195 	light = 300,
8196 	regular = 400,
8197 	medium = 500,
8198 	semibold = 600,
8199 	bold = 700,
8200 	extrabold = 800,
8201 	heavy = 900
8202 }
8203 
8204 /++
8205 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8206 
8207 	History:
8208 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8209 +/
8210 interface MeasurableFont {
8211 	/++
8212 		Returns true if it is a monospace font, meaning each of the
8213 		glyphs (at least the ascii characters) have matching width
8214 		and no kerning, so you can determine the display width of some
8215 		strings by simply multiplying the string width by [averageWidth].
8216 
8217 		(Please note that multiply doesn't $(I actually) work in general,
8218 		consider characters like tab and newline, but it does sometimes.)
8219 	+/
8220 	bool isMonospace();
8221 
8222 	/++
8223 		The average width of glyphs in the font, traditionally equal to the
8224 		width of the lowercase x. Can be used to estimate bounding boxes,
8225 		especially if the font [isMonospace].
8226 
8227 		Given in pixels.
8228 	+/
8229 	int averageWidth();
8230 	/++
8231 		The height of the bounding box of a line.
8232 	+/
8233 	int height();
8234 	/++
8235 		The maximum ascent of a glyph above the baseline.
8236 
8237 		Given in pixels.
8238 	+/
8239 	int ascent();
8240 	/++
8241 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8242 
8243 		Given in pixels.
8244 	+/
8245 	int descent();
8246 	/++
8247 		The display width of the given string, and if you provide a window, it will use it to
8248 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8249 
8250 		Given in pixels.
8251 	+/
8252 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8253 
8254 }
8255 
8256 // FIXME: i need a font cache and it needs to handle disconnects.
8257 
8258 /++
8259 	Represents a font loaded off the operating system or the X server.
8260 
8261 
8262 	While the api here is unified cross platform, the fonts are not necessarily
8263 	available, even across machines of the same platform, so be sure to always check
8264 	for null (using [isNull]) and have a fallback plan.
8265 
8266 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8267 
8268 	Worst case, a null font will automatically fall back to the default font loaded
8269 	for your system.
8270 +/
8271 class OperatingSystemFont : MeasurableFont {
8272 	// FIXME: when the X Connection is lost, these need to be invalidated!
8273 	// that means I need to store the original stuff again to reconstruct it too.
8274 
8275 	version(X11) {
8276 		XFontStruct* font;
8277 		XFontSet fontset;
8278 
8279 		version(with_xft) {
8280 			XftFont* xftFont;
8281 			bool isXft;
8282 		}
8283 	} else version(Windows) {
8284 		HFONT font;
8285 		int width_;
8286 		int height_;
8287 	} else version(OSXCocoa) {
8288 		NSFont font;
8289 	} else static assert(0);
8290 
8291 	/++
8292 		Constructs the class and immediately calls [load].
8293 	+/
8294 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8295 		load(name, size, weight, italic);
8296 	}
8297 
8298 	/++
8299 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8300 
8301 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8302 
8303 		History:
8304 			Added January 24, 2021.
8305 	+/
8306 	this() {
8307 		// this space intentionally left blank
8308 	}
8309 
8310 	/++
8311 		Constructs a copy of the given font object.
8312 
8313 		History:
8314 			Added January 7, 2023.
8315 	+/
8316 	this(OperatingSystemFont font) {
8317 		if(font is null || font.loadedInfo is LoadedInfo.init)
8318 			loadDefault();
8319 		else
8320 			load(font.loadedInfo.tupleof);
8321 	}
8322 
8323 	/++
8324 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8325 
8326 		History:
8327 			Added November 13, 2020.
8328 	+/
8329 	version(with_xft)
8330 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8331 		unload();
8332 
8333 		if(!XftLibrary.attempted) {
8334 			XftLibrary.loadDynamicLibrary();
8335 		}
8336 
8337 		if(!XftLibrary.loadSuccessful)
8338 			return false;
8339 
8340 		auto display = XDisplayConnection.get;
8341 
8342 		char[256] nameBuffer = void;
8343 		int nbp = 0;
8344 
8345 		void add(in char[] a) {
8346 			nameBuffer[nbp .. nbp + a.length] = a[];
8347 			nbp += a.length;
8348 		}
8349 		add(name);
8350 
8351 		if(size) {
8352 			add(":size=");
8353 			add(toInternal!string(size));
8354 		}
8355 		if(weight != FontWeight.dontcare) {
8356 			add(":weight=");
8357 			add(weightToString(weight));
8358 		}
8359 		if(italic)
8360 			add(":slant=100");
8361 
8362 		nameBuffer[nbp] = 0;
8363 
8364 		this.xftFont = XftFontOpenName(
8365 			display,
8366 			DefaultScreen(display),
8367 			nameBuffer.ptr
8368 		);
8369 
8370 		this.isXft = true;
8371 
8372 		if(xftFont !is null) {
8373 			isMonospace_ = stringWidth("x") == stringWidth("M");
8374 			ascent_ = xftFont.ascent;
8375 			descent_ = xftFont.descent;
8376 		}
8377 
8378 		return !isNull();
8379 	}
8380 
8381 	/++
8382 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8383 
8384 
8385 		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.
8386 
8387 		If `pattern` is null, it returns all available font families.
8388 
8389 		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.
8390 
8391 		The format of the pattern is platform-specific.
8392 
8393 		History:
8394 			Added May 1, 2021 (dub v9.5)
8395 	+/
8396 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8397 		version(Windows) {
8398 			auto hdc = GetDC(null);
8399 			scope(exit) ReleaseDC(null, hdc);
8400 			LOGFONT logfont;
8401 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8402 				auto localHandler = *(cast(typeof(handler)*) p);
8403 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8404 			}
8405 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8406 		} else version(X11) {
8407 			//import core.stdc.stdio;
8408 			bool done = false;
8409 			version(with_xft) {
8410 				if(!XftLibrary.attempted) {
8411 					XftLibrary.loadDynamicLibrary();
8412 				}
8413 
8414 				if(!XftLibrary.loadSuccessful)
8415 					goto skipXft;
8416 
8417 				if(!FontConfigLibrary.attempted)
8418 					FontConfigLibrary.loadDynamicLibrary();
8419 				if(!FontConfigLibrary.loadSuccessful)
8420 					goto skipXft;
8421 
8422 				{
8423 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8424 					if(got is null)
8425 						goto skipXft;
8426 					scope(exit) FcFontSetDestroy(got);
8427 
8428 					auto fontPatterns = got.fonts[0 .. got.nfont];
8429 					foreach(candidate; fontPatterns) {
8430 						char* where, whereStyle;
8431 
8432 						char* pmg = FcNameUnparse(candidate);
8433 
8434 						//FcPatternGetString(candidate, "family", 0, &where);
8435 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8436 						//if(where && whereStyle) {
8437 						if(pmg) {
8438 							if(!handler(pmg.sliceCString))
8439 								return;
8440 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8441 						}
8442 					}
8443 				}
8444 			}
8445 
8446 			skipXft:
8447 
8448 			if(pattern is null)
8449 				pattern = "*";
8450 
8451 			int count;
8452 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8453 			scope(exit) XFreeFontNames(coreFontsRaw);
8454 
8455 			auto coreFonts = coreFontsRaw[0 .. count];
8456 
8457 			foreach(font; coreFonts) {
8458 				char[128] tmp;
8459 				tmp[0 ..5] = "core:";
8460 				auto cf = font.sliceCString;
8461 				if(5 + cf.length > tmp.length)
8462 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8463 				tmp[5 .. 5 + cf.length] = cf;
8464 				if(!handler(tmp[0 .. 5 + cf.length]))
8465 					return;
8466 			}
8467 		}
8468 	}
8469 
8470 	/++
8471 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8472 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8473 
8474 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8475 		underlying system doesn't support returning the raw bytes.
8476 
8477 		History:
8478 			Added September 10, 2021 (dub v10.3)
8479 	+/
8480 	ubyte[] getTtfBytes() {
8481 		if(isNull)
8482 			return null;
8483 
8484 		version(Windows) {
8485 			auto dc = GetDC(null);
8486 			auto orig = SelectObject(dc, font);
8487 
8488 			scope(exit) {
8489 				SelectObject(dc, orig);
8490 				ReleaseDC(null, dc);
8491 			}
8492 
8493 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8494 			if(res == GDI_ERROR)
8495 				return null;
8496 
8497 			ubyte[] buffer = new ubyte[](res);
8498 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8499 			if(res == GDI_ERROR)
8500 				return null; // wtf really tbh
8501 
8502 			return buffer;
8503 		} else version(with_xft) {
8504 			if(isXft && xftFont) {
8505 				if(!FontConfigLibrary.attempted)
8506 					FontConfigLibrary.loadDynamicLibrary();
8507 				if(!FontConfigLibrary.loadSuccessful)
8508 					return null;
8509 
8510 				char* file;
8511 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8512 					if (file !is null && file[0]) {
8513 						import core.stdc.stdio;
8514 						auto fp = fopen(file, "rb");
8515 						if(fp is null)
8516 							return null;
8517 						scope(exit)
8518 							fclose(fp);
8519 						fseek(fp, 0, SEEK_END);
8520 						ubyte[] buffer = new ubyte[](ftell(fp));
8521 						fseek(fp, 0, SEEK_SET);
8522 
8523 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8524 						if(got != buffer.length)
8525 							return null;
8526 
8527 						return buffer;
8528 					}
8529 				}
8530 			}
8531 			return null;
8532 		} else throw new NotYetImplementedException();
8533 	}
8534 
8535 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8536 
8537 	private string weightToString(FontWeight weight) {
8538 		with(FontWeight)
8539 		final switch(weight) {
8540 			case dontcare: return "*";
8541 			case thin: return "extralight";
8542 			case extralight: return "extralight";
8543 			case light: return "light";
8544 			case regular: return "regular";
8545 			case medium: return "medium";
8546 			case semibold: return "demibold";
8547 			case bold: return "bold";
8548 			case extrabold: return "demibold";
8549 			case heavy: return "black";
8550 		}
8551 	}
8552 
8553 	/++
8554 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8555 
8556 		History:
8557 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8558 	+/
8559 	version(X11)
8560 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8561 		unload();
8562 
8563 		string xfontstr;
8564 
8565 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8566 			// this is kinda a disgusting hack but if the user sends an exact
8567 			// string I'd like to honor it...
8568 			xfontstr = name;
8569 		} else {
8570 			string weightstr = weightToString(weight);
8571 			string sizestr;
8572 			if(size == 0)
8573 				sizestr = "*";
8574 			else
8575 				sizestr = toInternal!string(size);
8576 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8577 		}
8578 
8579 		// writeln(xfontstr);
8580 
8581 		auto display = XDisplayConnection.get;
8582 
8583 		font = XLoadQueryFont(display, xfontstr.ptr);
8584 		if(font is null)
8585 			return false;
8586 
8587 		char** lol;
8588 		int lol2;
8589 		char* lol3;
8590 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8591 
8592 		prepareFontInfo();
8593 
8594 		return !isNull();
8595 	}
8596 
8597 	version(X11)
8598 	private void prepareFontInfo() {
8599 		if(font !is null) {
8600 			isMonospace_ = stringWidth("l") == stringWidth("M");
8601 			ascent_ = font.max_bounds.ascent;
8602 			descent_ = font.max_bounds.descent;
8603 		}
8604 	}
8605 
8606 	version(OSXCocoa)
8607 	private void prepareFontInfo() {
8608 		if(font !is null) {
8609 			isMonospace_ = font.isFixedPitch;
8610 			ascent_ = cast(int) font.ascender;
8611 			descent_ = cast(int) - font.descender;
8612 		}
8613 	}
8614 
8615 
8616 	/++
8617 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8618 
8619 		History:
8620 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8621 	+/
8622 	version(Windows)
8623 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8624 		unload();
8625 
8626 		WCharzBuffer buffer = WCharzBuffer(name);
8627 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8628 
8629 		prepareFontInfo(hdc);
8630 
8631 		return !isNull();
8632 	}
8633 
8634 	version(Windows)
8635 	void prepareFontInfo(HDC hdc = null) {
8636 		if(font is null)
8637 			return;
8638 
8639 		TEXTMETRIC tm;
8640 		auto dc = hdc ? hdc : GetDC(null);
8641 		auto orig = SelectObject(dc, font);
8642 		GetTextMetrics(dc, &tm);
8643 		SelectObject(dc, orig);
8644 		if(hdc is null)
8645 			ReleaseDC(null, dc);
8646 
8647 		width_ = tm.tmAveCharWidth;
8648 		height_ = tm.tmHeight;
8649 		ascent_ = tm.tmAscent;
8650 		descent_ = tm.tmDescent;
8651 		// 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.
8652 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8653 	}
8654 
8655 
8656 	/++
8657 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8658 
8659 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8660 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8661 
8662 		On Windows, it forwards directly to [loadWin32].
8663 
8664 		Params:
8665 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8666 			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.
8667 			weight = approximate boldness, results may vary.
8668 			italic = try to get a slanted version of the given font.
8669 
8670 		History:
8671 			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.
8672 	+/
8673 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8674 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8675 		version(X11) {
8676 			version(with_xft) {
8677 				if(name.length > 5 && name[0 .. 5] == "core:") {
8678 					goto core;
8679 				}
8680 
8681 				if(loadXft(name, size, weight, italic))
8682 					return true;
8683 				// if xft fails, fallback to core to avoid breaking
8684 				// code that already depended on this.
8685 			}
8686 
8687 			core:
8688 
8689 			if(name.length > 5 && name[0 .. 5] == "core:") {
8690 				name = name[5 .. $];
8691 			}
8692 
8693 			return loadCoreX(name, size, weight, italic);
8694 		} else version(Windows) {
8695 			return loadWin32(name, size, weight, italic);
8696 		} else version(OSXCocoa) {
8697 			return loadCocoa(name, size, weight, italic);
8698 		} else static assert(0);
8699 	}
8700 
8701 	version(OSXCocoa)
8702 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
8703 		unload();
8704 
8705 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
8706 		prepareFontInfo();
8707 
8708 		return !isNull();
8709 	}
8710 
8711 	private struct LoadedInfo {
8712 		string name;
8713 		int size;
8714 		FontWeight weight;
8715 		bool italic;
8716 	}
8717 	private LoadedInfo loadedInfo;
8718 
8719 	///
8720 	void unload() {
8721 		if(isNull())
8722 			return;
8723 
8724 		version(X11) {
8725 			auto display = XDisplayConnection.display;
8726 
8727 			if(display is null)
8728 				return;
8729 
8730 			version(with_xft) {
8731 				if(isXft) {
8732 					if(xftFont)
8733 						XftFontClose(display, xftFont);
8734 					isXft = false;
8735 					xftFont = null;
8736 					return;
8737 				}
8738 			}
8739 
8740 			if(font && font !is ScreenPainterImplementation.defaultfont)
8741 				XFreeFont(display, font);
8742 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8743 				XFreeFontSet(display, fontset);
8744 
8745 			font = null;
8746 			fontset = null;
8747 		} else version(Windows) {
8748 			DeleteObject(font);
8749 			font = null;
8750 		} else version(OSXCocoa) {
8751 			font.release();
8752 			font = null;
8753 		} else static assert(0);
8754 	}
8755 
8756 	private bool isMonospace_;
8757 
8758 	/++
8759 		History:
8760 			Added January 16, 2021
8761 	+/
8762 	bool isMonospace() {
8763 		return isMonospace_;
8764 	}
8765 
8766 	/++
8767 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8768 
8769 		History:
8770 			Added March 26, 2020
8771 			Documented January 16, 2021
8772 	+/
8773 	int averageWidth() {
8774 		version(X11) {
8775 			return stringWidth("x");
8776 		} version(OSXCocoa) {
8777 			return stringWidth("x");
8778 		} else version(Windows)
8779 			return width_;
8780 		else assert(0);
8781 	}
8782 
8783 	/++
8784 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8785 
8786 		History:
8787 			Added January 16, 2021
8788 	+/
8789 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8790 	// FIXME: what about tab?
8791 		if(isNull)
8792 			return 0;
8793 
8794 		version(X11) {
8795 			version(with_xft)
8796 				if(isXft && xftFont !is null) {
8797 					//return xftFont.max_advance_width;
8798 					XGlyphInfo extents;
8799 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8800 					// writeln(extents);
8801 					return extents.xOff;
8802 				}
8803 			if(font is null)
8804 				return 0;
8805 			else if(fontset) {
8806 				XRectangle rect;
8807 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8808 
8809 				return rect.width;
8810 			} else {
8811 				return XTextWidth(font, s.ptr, cast(int) s.length);
8812 			}
8813 		} else version(Windows) {
8814 			WCharzBuffer buffer = WCharzBuffer(s);
8815 
8816 			return stringWidth(buffer.slice, window);
8817 		} else version(OSXCocoa) {
8818 			/+
8819 			int charCount = [string length];
8820 			CGGlyph glyphs[charCount];
8821 			CGRect rects[charCount];
8822 
8823 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
8824 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
8825 
8826 			int totalwidth = 0, maxheight = 0;
8827 			for (int i=0; i < charCount; i++)
8828 			{
8829 				totalwidth += rects[i].size.width;
8830 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
8831 			}
8832 
8833 			dim = CGSizeMake(totalwidth, maxheight);
8834 			+/
8835 
8836 			return 16; // FIXME
8837 		}
8838 		else assert(0);
8839 	}
8840 
8841 	version(Windows)
8842 	/// ditto
8843 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8844 		if(isNull)
8845 			return 0;
8846 		version(Windows) {
8847 			SIZE size;
8848 
8849 			prepareContext(window);
8850 			scope(exit) releaseContext();
8851 
8852 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8853 
8854 			return size.cx;
8855 		} else {
8856 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8857 			static assert(0, "not implemented yet");
8858 			//return stringWidth(s, window);
8859 		}
8860 	}
8861 
8862 	private {
8863 		int prepRefcount;
8864 
8865 		version(Windows) {
8866 			HDC dc;
8867 			HANDLE orig;
8868 			HWND hwnd;
8869 		}
8870 	}
8871 	/++
8872 		[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.
8873 
8874 		History:
8875 			Added January 23, 2021
8876 	+/
8877 	void prepareContext(SimpleWindow window = null) {
8878 		prepRefcount++;
8879 		if(prepRefcount == 1) {
8880 			version(Windows) {
8881 				hwnd = window is null ? null : window.impl.hwnd;
8882 				dc = GetDC(hwnd);
8883 				orig = SelectObject(dc, font);
8884 			}
8885 		}
8886 	}
8887 	/// ditto
8888 	void releaseContext() {
8889 		prepRefcount--;
8890 		if(prepRefcount == 0) {
8891 			version(Windows) {
8892 				SelectObject(dc, orig);
8893 				ReleaseDC(hwnd, dc);
8894 				hwnd = null;
8895 				dc = null;
8896 				orig = null;
8897 			}
8898 		}
8899 	}
8900 
8901 	/+
8902 		FIXME: I think I need advance and kerning pair
8903 
8904 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8905 	+/
8906 
8907 	/++
8908 		Returns the height of the font.
8909 
8910 		History:
8911 			Added March 26, 2020
8912 			Documented January 16, 2021
8913 	+/
8914 	int height() {
8915 		version(X11) {
8916 			version(with_xft)
8917 				if(isXft && xftFont !is null) {
8918 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8919 				}
8920 			if(font is null)
8921 				return 0;
8922 			return font.max_bounds.ascent + font.max_bounds.descent;
8923 		} else version(Windows) {
8924 			return height_;
8925 		} else version(OSXCocoa) {
8926 			if(font is null)
8927 				return 0;
8928 			return cast(int) font.capHeight;
8929 		}
8930 		else assert(0);
8931 	}
8932 
8933 	private int ascent_;
8934 	private int descent_;
8935 
8936 	/++
8937 		Max ascent above the baseline.
8938 
8939 		History:
8940 			Added January 22, 2021
8941 	+/
8942 	int ascent() {
8943 		return ascent_;
8944 	}
8945 
8946 	/++
8947 		Max descent below the baseline.
8948 
8949 		History:
8950 			Added January 22, 2021
8951 	+/
8952 	int descent() {
8953 		return descent_;
8954 	}
8955 
8956 	/++
8957 		Loads the default font used by [ScreenPainter] if none others are loaded.
8958 
8959 		Returns:
8960 			This method mutates the `this` object, but then returns `this` for
8961 			easy chaining like:
8962 
8963 			---
8964 			auto font = foo.isNull ? foo : foo.loadDefault
8965 			---
8966 
8967 		History:
8968 			Added previously, but left unimplemented until January 24, 2021.
8969 	+/
8970 	OperatingSystemFont loadDefault() {
8971 		unload();
8972 
8973 		loadedInfo = LoadedInfo.init;
8974 
8975 		version(X11) {
8976 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8977 			// but meh since sdpy does its own thing, this should be ok too
8978 
8979 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8980 			this.font = ScreenPainterImplementation.defaultfont;
8981 			this.fontset = ScreenPainterImplementation.defaultfontset;
8982 
8983 			prepareFontInfo();
8984 			return this;
8985 		} else version(Windows) {
8986 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8987 			this.font = ScreenPainterImplementation.defaultGuiFont;
8988 
8989 			prepareFontInfo();
8990 			return this;
8991 		} else version(OSXCocoa) {
8992 			this.font = NSFont.systemFontOfSize(12);
8993 
8994 			prepareFontInfo();
8995 			return this;
8996 		} else throw new NotYetImplementedException();
8997 	}
8998 
8999 	///
9000 	bool isNull() {
9001 		version(with_xft)
9002 			if(isXft)
9003 				return xftFont is null;
9004 		return font is null;
9005 	}
9006 
9007 	/* Metrics */
9008 	/+
9009 		GetABCWidth
9010 		GetKerningPairs
9011 
9012 		if I do it right, I can size it all here, and match
9013 		what happens when I draw the full string with the OS functions.
9014 
9015 		subclasses might do the same thing while getting the glyphs on images
9016 	struct GlyphInfo {
9017 		int glyph;
9018 
9019 		size_t stringIdxStart;
9020 		size_t stringIdxEnd;
9021 
9022 		Rectangle boundingBox;
9023 	}
9024 	GlyphInfo[] getCharBoxes() {
9025 		// XftTextExtentsUtf8
9026 		return null;
9027 
9028 	}
9029 	+/
9030 
9031 	~this() {
9032 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9033 		unload();
9034 	}
9035 }
9036 
9037 version(Windows)
9038 private string sliceCString(const(wchar)[] w) {
9039 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9040 }
9041 
9042 private inout(char)[] sliceCString(inout(char)* s) {
9043 	import core.stdc.string;
9044 	auto len = strlen(s);
9045 	return s[0 .. len];
9046 }
9047 
9048 version(OSXCocoa)
9049 	alias PaintingHandle = NSObject;
9050 else
9051 	alias PaintingHandle = NativeWindowHandle;
9052 
9053 /**
9054 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9055 	than constructing it directly. Then, it is reference counted so you can pass it
9056 	at around and when the last ref goes out of scope, the buffered drawing activities
9057 	are all carried out.
9058 
9059 
9060 	Most functions use the outlineColor instead of taking a color themselves.
9061 	ScreenPainter is reference counted and draws its buffer to the screen when its
9062 	final reference goes out of scope.
9063 */
9064 struct ScreenPainter {
9065 	CapableOfBeingDrawnUpon window;
9066 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9067 		this.window = window;
9068 		if(window.closed)
9069 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9070 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9071 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9072 		if(window.activeScreenPainter !is null) {
9073 			impl = window.activeScreenPainter;
9074 			if(impl.referenceCount == 0) {
9075 				impl.window = window;
9076 				impl.create(handle);
9077 			}
9078 			impl.manualInvalidations = manualInvalidations;
9079 			impl.referenceCount++;
9080 		//	writeln("refcount ++ ", impl.referenceCount);
9081 		} else {
9082 			impl = new ScreenPainterImplementation;
9083 			impl.window = window;
9084 			impl.create(handle);
9085 			impl.referenceCount = 1;
9086 			impl.manualInvalidations = manualInvalidations;
9087 			window.activeScreenPainter = impl;
9088 			// writeln("constructed");
9089 		}
9090 
9091 		copyActiveOriginals();
9092 	}
9093 
9094 	/++
9095 		EXPERIMENTAL. subject to change.
9096 
9097 		When you draw a cursor, you can draw this to notify your window of where it is,
9098 		for IME systems to use.
9099 	+/
9100 	void notifyCursorPosition(int x, int y, int width, int height) {
9101 		if(auto w = cast(SimpleWindow) window) {
9102 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9103 		}
9104 	}
9105 
9106 	/++
9107 		If you are using manual invalidations, this informs the
9108 		window system that a section needs to be redrawn.
9109 
9110 		If you didn't opt into manual invalidation, you don't
9111 		have to call this.
9112 
9113 		History:
9114 			Added December 30, 2021 (dub v10.5)
9115 	+/
9116 	void invalidateRect(Rectangle rect) {
9117 		if(impl is null) return;
9118 
9119 		// transform(rect)
9120 		rect.left += _originX;
9121 		rect.right += _originX;
9122 		rect.top += _originY;
9123 		rect.bottom += _originY;
9124 
9125 		impl.invalidateRect(rect);
9126 	}
9127 
9128 	private Pen originalPen;
9129 	private Color originalFillColor;
9130 	private arsd.color.Rectangle originalClipRectangle;
9131 	private OperatingSystemFont originalFont;
9132 	void copyActiveOriginals() {
9133 		if(impl is null) return;
9134 		originalPen = impl._activePen;
9135 		originalFillColor = impl._fillColor;
9136 		originalClipRectangle = impl._clipRectangle;
9137 		version(OSXCocoa) {} else
9138 		originalFont = impl._activeFont;
9139 	}
9140 
9141 	~this() {
9142 		if(impl is null) return;
9143 		impl.referenceCount--;
9144 		//writeln("refcount -- ", impl.referenceCount);
9145 		if(impl.referenceCount == 0) {
9146 			// writeln("destructed");
9147 			impl.dispose();
9148 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9149 			// writeln("paint finished");
9150 		} else {
9151 			// there is still an active reference, reset stuff so the
9152 			// next user doesn't get weirdness via the reference
9153 			this.rasterOp = RasterOp.normal;
9154 			pen = originalPen;
9155 			fillColor = originalFillColor;
9156 			if(originalFont)
9157 				setFont(originalFont);
9158 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9159 		}
9160 	}
9161 
9162 	this(this) {
9163 		if(impl is null) return;
9164 		impl.referenceCount++;
9165 		//writeln("refcount ++ ", impl.referenceCount);
9166 
9167 		copyActiveOriginals();
9168 	}
9169 
9170 	private int _originX;
9171 	private int _originY;
9172 	@property int originX() { return _originX; }
9173 	@property int originY() { return _originY; }
9174 	@property int originX(int a) {
9175 		_originX = a;
9176 		return _originX;
9177 	}
9178 	@property int originY(int a) {
9179 		_originY = a;
9180 		return _originY;
9181 	}
9182 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9183 	private void transform(ref Point p) {
9184 		if(impl is null) return;
9185 		p.x += _originX;
9186 		p.y += _originY;
9187 	}
9188 
9189 	// this needs to be checked BEFORE the originX/Y transformation
9190 	private bool isClipped(Point p) {
9191 		return !currentClipRectangle.contains(p);
9192 	}
9193 	private bool isClipped(Point p, int width, int height) {
9194 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9195 	}
9196 	private bool isClipped(Point p, Size s) {
9197 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9198 	}
9199 	private bool isClipped(Point p, Point p2) {
9200 		// need to ensure the end points are actually included inside, so the +1 does that
9201 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9202 	}
9203 
9204 
9205 	/++
9206 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9207 
9208 		Returns:
9209 			The old clip rectangle.
9210 
9211 		History:
9212 			Return value was `void` prior to May 10, 2021.
9213 
9214 	+/
9215 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9216 		if(impl is null) return currentClipRectangle;
9217 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9218 			return currentClipRectangle; // no need to do anything
9219 		auto old = currentClipRectangle;
9220 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9221 		transform(pt);
9222 
9223 		impl.setClipRectangle(pt.x, pt.y, width, height);
9224 
9225 		return old;
9226 	}
9227 
9228 	/// ditto
9229 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9230 		if(impl is null) return currentClipRectangle;
9231 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9232 	}
9233 
9234 	///
9235 	void setFont(OperatingSystemFont font) {
9236 		if(impl is null) return;
9237 		impl.setFont(font);
9238 	}
9239 
9240 	///
9241 	int fontHeight() {
9242 		if(impl is null) return 0;
9243 		return impl.fontHeight();
9244 	}
9245 
9246 	private Pen activePen;
9247 
9248 	///
9249 	@property void pen(Pen p) {
9250 		if(impl is null) return;
9251 		activePen = p;
9252 		impl.pen(p);
9253 	}
9254 
9255 	///
9256 	@scriptable
9257 	@property void outlineColor(Color c) {
9258 		if(impl is null) return;
9259 		if(activePen.color == c)
9260 			return;
9261 		activePen.color = c;
9262 		impl.pen(activePen);
9263 	}
9264 
9265 	///
9266 	@scriptable
9267 	@property void fillColor(Color c) {
9268 		if(impl is null) return;
9269 		impl.fillColor(c);
9270 	}
9271 
9272 	///
9273 	@property void rasterOp(RasterOp op) {
9274 		if(impl is null) return;
9275 		impl.rasterOp(op);
9276 	}
9277 
9278 
9279 	void updateDisplay() {
9280 		// FIXME this should do what the dtor does
9281 	}
9282 
9283 	/// 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)
9284 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9285 		if(impl is null) return;
9286 		if(isClipped(upperLeft, width, height)) return;
9287 		transform(upperLeft);
9288 		version(Windows) {
9289 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9290 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9291 			RECT clip = scroll;
9292 			RECT uncovered;
9293 			HRGN hrgn;
9294 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9295 				throw new WindowsApiException("ScrollDC", GetLastError());
9296 
9297 		} else version(X11) {
9298 			// FIXME: clip stuff outside this rectangle
9299 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9300 		} else version(OSXCocoa) {
9301 			throw new NotYetImplementedException();
9302 		} else static assert(0);
9303 	}
9304 
9305 	///
9306 	void clear(Color color = Color.white()) {
9307 		if(impl is null) return;
9308 		fillColor = color;
9309 		outlineColor = color;
9310 		drawRectangle(Point(0, 0), window.width, window.height);
9311 	}
9312 
9313 	/++
9314 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9315 
9316 		Params:
9317 			upperLeft = point on the window where the upper left corner of the image will be drawn
9318 			imageUpperLeft = point on the image to start the slice to draw
9319 			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.
9320 		History:
9321 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9322 	+/
9323 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9324 		if(impl is null) return;
9325 		if(isClipped(upperLeft, s.width, s.height)) return;
9326 		transform(upperLeft);
9327 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9328 	}
9329 
9330 	///
9331 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9332 		if(impl is null) return;
9333 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9334 		transform(upperLeft);
9335 		if(w == 0 || w > i.width)
9336 			w = i.width;
9337 		if(h == 0 || h > i.height)
9338 			h = i.height;
9339 		if(upperLeftOfImage.x < 0)
9340 			upperLeftOfImage.x = 0;
9341 		if(upperLeftOfImage.y < 0)
9342 			upperLeftOfImage.y = 0;
9343 
9344 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9345 	}
9346 
9347 	///
9348 	Size textSize(in char[] text) {
9349 		if(impl is null) return Size(0, 0);
9350 		return impl.textSize(text);
9351 	}
9352 
9353 	/++
9354 		Draws a string in the window with the set font (see [setFont] to change it).
9355 
9356 		Params:
9357 			upperLeft = the upper left point of the bounding box of the text
9358 			text = the string to draw
9359 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9360 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9361 	+/
9362 	@scriptable
9363 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9364 		if(impl is null) return;
9365 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9366 			if(isClipped(upperLeft, lowerRight)) return;
9367 			transform(lowerRight);
9368 		} else {
9369 			if(isClipped(upperLeft, textSize(text))) return;
9370 		}
9371 		transform(upperLeft);
9372 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9373 	}
9374 
9375 	/++
9376 		Draws text using a custom font.
9377 
9378 		This is still MAJOR work in progress.
9379 
9380 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9381 	+/
9382 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9383 		if(impl is null) return;
9384 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9385 		transform(upperLeft);
9386 		font.drawString(this, upperLeft, text);
9387 	}
9388 
9389 	version(Windows)
9390 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9391 		if(impl is null) return;
9392 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9393 		transform(upperLeft);
9394 
9395 		if(text.length && text[$-1] == '\n')
9396 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9397 
9398 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9399 	}
9400 
9401 	static struct TextDrawingContext {
9402 		Point boundingBoxUpperLeft;
9403 		Point boundingBoxLowerRight;
9404 
9405 		Point currentLocation;
9406 
9407 		Point lastDrewUpperLeft;
9408 		Point lastDrewLowerRight;
9409 
9410 		// how do i do right aligned rich text?
9411 		// i kinda want to do a pre-made drawing then right align
9412 		// draw the whole block.
9413 		//
9414 		// That's exactly the diff: inline vs block stuff.
9415 
9416 		// I need to get coordinates of an inline section out too,
9417 		// not just a bounding box, but a series of bounding boxes
9418 		// should be ok. Consider what's needed to detect a click
9419 		// on a link in the middle of a paragraph breaking a line.
9420 		//
9421 		// Generally, we should be able to get the rectangles of
9422 		// any portion we draw.
9423 		//
9424 		// It also needs to tell what text is left if it overflows
9425 		// out of the box, so we can do stuff like float images around
9426 		// it. It should not attempt to draw a letter that would be
9427 		// clipped.
9428 		//
9429 		// I might also turn off word wrap stuff.
9430 	}
9431 
9432 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9433 		if(impl is null) return;
9434 		// FIXME
9435 	}
9436 
9437 	/// Drawing an individual pixel is slow. Avoid it if possible.
9438 	void drawPixel(Point where) {
9439 		if(impl is null) return;
9440 		if(isClipped(where)) return;
9441 		transform(where);
9442 		impl.drawPixel(where.x, where.y);
9443 	}
9444 
9445 
9446 	/// Draws a pen using the current pen / outlineColor
9447 	@scriptable
9448 	void drawLine(Point starting, Point ending) {
9449 		if(impl is null) return;
9450 		if(isClipped(starting, ending)) return;
9451 		transform(starting);
9452 		transform(ending);
9453 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9454 	}
9455 
9456 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9457 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9458 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9459 	@scriptable
9460 	void drawRectangle(Point upperLeft, int width, int height) {
9461 		if(impl is null) return;
9462 		if(isClipped(upperLeft, width, height)) return;
9463 		transform(upperLeft);
9464 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9465 	}
9466 
9467 	/// ditto
9468 	void drawRectangle(Point upperLeft, Size size) {
9469 		if(impl is null) return;
9470 		if(isClipped(upperLeft, size.width, size.height)) return;
9471 		transform(upperLeft);
9472 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9473 	}
9474 
9475 	/// ditto
9476 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9477 		if(impl is null) return;
9478 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9479 		transform(upperLeft);
9480 		transform(lowerRightInclusive);
9481 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9482 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9483 	}
9484 
9485 	// overload added on May 12, 2021
9486 	/// ditto
9487 	void drawRectangle(Rectangle rect) {
9488 		drawRectangle(rect.upperLeft, rect.size);
9489 	}
9490 
9491 	/// Arguments are the points of the bounding rectangle
9492 	void drawEllipse(Point upperLeft, Point lowerRight) {
9493 		if(impl is null) return;
9494 		if(isClipped(upperLeft, lowerRight)) return;
9495 		transform(upperLeft);
9496 		transform(lowerRight);
9497 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9498 	}
9499 
9500 	/++
9501 		start and finish are units of degrees * 64
9502 
9503 		History:
9504 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9505 
9506 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9507 	+/
9508 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9509 		if(impl is null) return;
9510 		// FIXME: not actually implemented
9511 		if(isClipped(upperLeft, width, height)) return;
9512 		transform(upperLeft);
9513 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9514 	}
9515 
9516 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9517 	void drawCircle(Point upperLeft, int diameter) {
9518 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9519 	}
9520 
9521 	/// .
9522 	void drawPolygon(Point[] vertexes) {
9523 		if(impl is null) return;
9524 		assert(vertexes.length);
9525 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9526 		foreach(ref vertex; vertexes) {
9527 			if(vertex.x < minX)
9528 				minX = vertex.x;
9529 			if(vertex.y < minY)
9530 				minY = vertex.y;
9531 			if(vertex.x > maxX)
9532 				maxX = vertex.x;
9533 			if(vertex.y > maxY)
9534 				maxY = vertex.y;
9535 			transform(vertex);
9536 		}
9537 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9538 		impl.drawPolygon(vertexes);
9539 	}
9540 
9541 	/// ditto
9542 	void drawPolygon(Point[] vertexes...) {
9543 		if(impl is null) return;
9544 		drawPolygon(vertexes);
9545 	}
9546 
9547 
9548 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9549 
9550 	//mixin NativeScreenPainterImplementation!() impl;
9551 
9552 
9553 	// HACK: if I mixin the impl directly, it won't let me override the copy
9554 	// constructor! The linker complains about there being multiple definitions.
9555 	// I'll make the best of it and reference count it though.
9556 	ScreenPainterImplementation* impl;
9557 }
9558 
9559 	// HACK: I need a pointer to the implementation so it's separate
9560 	struct ScreenPainterImplementation {
9561 		CapableOfBeingDrawnUpon window;
9562 		int referenceCount;
9563 		mixin NativeScreenPainterImplementation!();
9564 	}
9565 
9566 // FIXME: i haven't actually tested the sprite class on MS Windows
9567 
9568 /**
9569 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9570 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9571 
9572 
9573 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9574 	though I'm not sure that's ideal and the implementation might change.
9575 
9576 	You create one by giving a window and an image. It optimizes for that window,
9577 	and copies the image into it to use as the initial picture. Creating a sprite
9578 	can be quite slow (especially over a network connection) so you should do it
9579 	as little as possible and just hold on to your sprite handles after making them.
9580 	simpledisplay does try to do its best though, using the XSHM extension if available,
9581 	but you should still write your code as if it will always be slow.
9582 
9583 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9584 	a fast operation - much faster than drawing the Image itself every time.
9585 
9586 	`Sprite` represents a scarce resource which should be freed when you
9587 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9588 	after it has been disposed. If you are unsure about this, don't take chances,
9589 	just let the garbage collector do it for you. But ideally, you can manage its
9590 	lifetime more efficiently.
9591 
9592 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9593 	support alpha blending in its drawing at this time. That might change in the
9594 	future, but if you need alpha blending right now, use OpenGL instead. See
9595 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9596 
9597 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9598 	in by setting the enableAlpha = true in the constructor.
9599 */
9600 class Sprite : CapableOfBeingDrawnUpon {
9601 
9602 	///
9603 	ScreenPainter draw() {
9604 		return ScreenPainter(this, handle, false);
9605 	}
9606 
9607 	/++
9608 		Copies the sprite's current state into a [TrueColorImage].
9609 
9610 		Be warned: this can be a very slow operation
9611 
9612 		History:
9613 			Actually implemented on March 14, 2021
9614 	+/
9615 	TrueColorImage takeScreenshot() {
9616 		return trueColorImageFromNativeHandle(handle, width, height);
9617 	}
9618 
9619 	void delegate() paintingFinishedDg() { return null; }
9620 	bool closed() { return false; }
9621 	ScreenPainterImplementation* activeScreenPainter_;
9622 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9623 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9624 
9625 	version(Windows)
9626 		private ubyte* rawData;
9627 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9628 	// ditto on the XPicture stuff
9629 
9630 	version(X11) {
9631 		private static XRenderPictFormat* RGB24;
9632 		private static XRenderPictFormat* ARGB32;
9633 
9634 		private Picture xrenderPicture;
9635 	}
9636 
9637 	version(X11)
9638 	private static void requireXRender() {
9639 		if(!XRenderLibrary.loadAttempted) {
9640 			XRenderLibrary.loadDynamicLibrary();
9641 		}
9642 
9643 		if(!XRenderLibrary.loadSuccessful)
9644 			throw new Exception("XRender library load failure");
9645 
9646 		auto display = XDisplayConnection.get;
9647 
9648 		// FIXME: if we migrate X displays, these need to be changed
9649 		if(RGB24 is null)
9650 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9651 		if(ARGB32 is null)
9652 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9653 	}
9654 
9655 	protected this() {}
9656 
9657 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9658 		this._width = width;
9659 		this._height = height;
9660 		this.enableAlpha = enableAlpha;
9661 
9662 		version(X11) {
9663 			auto display = XDisplayConnection.get();
9664 
9665 			if(enableAlpha) {
9666 				requireXRender();
9667 			}
9668 
9669 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9670 
9671 			if(enableAlpha) {
9672 				XRenderPictureAttributes attrs;
9673 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9674 			}
9675 		} else version(Windows) {
9676 			version(CRuntime_DigitalMars) {
9677 				//if(enableAlpha)
9678 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9679 			}
9680 
9681 			BITMAPINFO infoheader;
9682 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9683 			infoheader.bmiHeader.biWidth = width;
9684 			infoheader.bmiHeader.biHeight = height;
9685 			infoheader.bmiHeader.biPlanes = 1;
9686 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9687 			infoheader.bmiHeader.biCompression = BI_RGB;
9688 
9689 			// FIXME: this should prolly be a device dependent bitmap...
9690 			handle = CreateDIBSection(
9691 				null,
9692 				&infoheader,
9693 				DIB_RGB_COLORS,
9694 				cast(void**) &rawData,
9695 				null,
9696 				0);
9697 
9698 			if(handle is null)
9699 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9700 		}
9701 	}
9702 
9703 	/// Makes a sprite based on the image with the initial contents from the Image
9704 	this(SimpleWindow win, Image i) {
9705 		this(win, i.width, i.height, i.enableAlpha);
9706 
9707 		version(X11) {
9708 			auto display = XDisplayConnection.get();
9709 			auto gc = XCreateGC(display, this.handle, 0, null);
9710 			scope(exit) XFreeGC(display, gc);
9711 			if(i.usingXshm)
9712 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9713 			else
9714 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9715 		} else version(Windows) {
9716 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9717 			auto arrLength = itemsPerLine * height;
9718 			rawData[0..arrLength] = i.rawData[0..arrLength];
9719 		} else version(OSXCocoa) {
9720 			// FIXME: I have no idea if this is even any good
9721 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9722 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
9723 				colorSpace,
9724 				kCGImageAlphaPremultipliedLast
9725 				|kCGBitmapByteOrder32Big);
9726 			CGColorSpaceRelease(colorSpace);
9727 			auto rawData = CGBitmapContextGetData(handle);
9728 
9729 			auto rdl = (width * height * 4);
9730 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9731 		} else static assert(0);
9732 	}
9733 
9734 	/++
9735 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9736 
9737 		Params:
9738 			where = point on the window where the upper left corner of the image will be drawn
9739 			imageUpperLeft = point on the image to start the slice to draw
9740 			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.
9741 		History:
9742 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9743 	+/
9744 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9745 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9746 	}
9747 
9748 	/// Call this when you're ready to get rid of it
9749 	void dispose() {
9750 		version(X11) {
9751 			staticDispose(xrenderPicture, handle);
9752 			xrenderPicture = None;
9753 			handle = None;
9754 		} else version(Windows) {
9755 			staticDispose(handle);
9756 			handle = null;
9757 		} else version(OSXCocoa) {
9758 			staticDispose(handle);
9759 			handle = null;
9760 		} else static assert(0);
9761 
9762 	}
9763 
9764 	version(X11)
9765 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9766 		if(xrenderPicture)
9767 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9768 		if(handle)
9769 			XFreePixmap(XDisplayConnection.get(), handle);
9770 	}
9771 	else version(Windows)
9772 	static void staticDispose(HBITMAP handle) {
9773 		if(handle)
9774 			DeleteObject(handle);
9775 	}
9776 	else version(OSXCocoa)
9777 	static void staticDispose(CGContextRef context) {
9778 		if(context)
9779 			CGContextRelease(context);
9780 	}
9781 
9782 	~this() {
9783 		version(X11) { if(xrenderPicture || handle)
9784 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9785 		} else version(Windows) { if(handle)
9786 			cleanupQueue.queue!staticDispose(handle);
9787 		} else version(OSXCocoa) { if(handle)
9788 			cleanupQueue.queue!staticDispose(handle);
9789 		} else static assert(0);
9790 	}
9791 
9792 	///
9793 	final @property int width() { return _width; }
9794 
9795 	///
9796 	final @property int height() { return _height; }
9797 
9798 	///
9799 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9800 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9801 	}
9802 
9803 	auto nativeHandle() {
9804 		return handle;
9805 	}
9806 
9807 	private:
9808 
9809 	int _width;
9810 	int _height;
9811 	bool enableAlpha;
9812 	version(X11)
9813 		Pixmap handle;
9814 	else version(Windows)
9815 		HBITMAP handle;
9816 	else version(OSXCocoa)
9817 		CGContextRef handle;
9818 	else static assert(0);
9819 }
9820 
9821 /++
9822 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9823 
9824 	History:
9825 		Added November 20, 2021 (dub v10.4)
9826 +/
9827 version(OSXCocoa) {} else // NotYetImplementedException
9828 abstract class Gradient : Sprite {
9829 	protected this(int w, int h) {
9830 		version(X11) {
9831 			Sprite.requireXRender();
9832 
9833 			super();
9834 			enableAlpha = true;
9835 			_width = w;
9836 			_height = h;
9837 		} else version(Windows) {
9838 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9839 		}
9840 	}
9841 
9842 	version(Windows)
9843 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9844 		auto ptr = rawData;
9845 		foreach(j; 0 .. _height)
9846 		foreach(i; 0 .. _width) {
9847 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9848 			*rawData = (color.a * color.b) / 255; rawData++;
9849 			*rawData = (color.a * color.g) / 255; rawData++;
9850 			*rawData = (color.a * color.r) / 255; rawData++;
9851 			*rawData = color.a; rawData++;
9852 		}
9853 	}
9854 
9855 	version(X11)
9856 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9857 		assert(stops.length > 0);
9858 		assert(stops.length <= 16, "I got lazy with buffers");
9859 
9860 		XFixed[16] stopsPositions = void;
9861 		XRenderColor[16] colors = void;
9862 
9863 		foreach(idx, stop; stops) {
9864 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9865 			auto c = stop.c;
9866 			colors[idx] = XRenderColor(
9867 				cast(ushort)(c.r * ushort.max / 255),
9868 				cast(ushort)(c.g * ushort.max / 255),
9869 				cast(ushort)(c.b * ushort.max / 255),
9870 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9871 			);
9872 		}
9873 
9874 		xrenderPicture = dg(stopsPositions, colors);
9875 	}
9876 
9877 	///
9878 	static struct Stop {
9879 		float percentage; /// between 0 and 1.0
9880 		Color c;
9881 	}
9882 }
9883 
9884 /++
9885 	Creates a linear gradient between p1 and p2.
9886 
9887 	X ONLY RIGHT NOW
9888 
9889 	History:
9890 		Added November 20, 2021 (dub v10.4)
9891 
9892 	Bugs:
9893 		Not yet implemented on Windows.
9894 +/
9895 version(OSXCocoa) {} else // NotYetImplementedException
9896 class LinearGradient : Gradient {
9897 	/++
9898 
9899 	+/
9900 	this(Point p1, Point p2, Stop[] stops...) {
9901 		super(p2.x, p2.y);
9902 
9903 		version(X11) {
9904 			XLinearGradient gradient;
9905 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9906 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9907 
9908 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9909 				return XRenderCreateLinearGradient(
9910 					XDisplayConnection.get,
9911 					&gradient,
9912 					stopsPositions.ptr,
9913 					colors.ptr,
9914 					cast(int) stops.length);
9915 			});
9916 		} else version(Windows) {
9917 			// FIXME
9918 			forEachPixel((int x, int y) {
9919 				import core.stdc.math;
9920 
9921 				//sqrtf(
9922 
9923 				return Color.transparent;
9924 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9925 			});
9926 		}
9927 	}
9928 }
9929 
9930 /++
9931 	A conical gradient goes from color to color around a circumference from a center point.
9932 
9933 	X ONLY RIGHT NOW
9934 
9935 	History:
9936 		Added November 20, 2021 (dub v10.4)
9937 
9938 	Bugs:
9939 		Not yet implemented on Windows.
9940 +/
9941 version(OSXCocoa) {} else // NotYetImplementedException
9942 class ConicalGradient : Gradient {
9943 	/++
9944 
9945 	+/
9946 	this(Point center, float angleInDegrees, Stop[] stops...) {
9947 		super(center.x * 2, center.y * 2);
9948 
9949 		version(X11) {
9950 			XConicalGradient gradient;
9951 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9952 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9953 
9954 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9955 				return XRenderCreateConicalGradient(
9956 					XDisplayConnection.get,
9957 					&gradient,
9958 					stopsPositions.ptr,
9959 					colors.ptr,
9960 					cast(int) stops.length);
9961 			});
9962 		} else version(Windows) {
9963 			// FIXME
9964 			forEachPixel((int x, int y) {
9965 				import core.stdc.math;
9966 
9967 				//sqrtf(
9968 
9969 				return Color.transparent;
9970 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9971 			});
9972 
9973 		}
9974 	}
9975 }
9976 
9977 /++
9978 	A radial gradient goes from color to color based on distance from the center.
9979 	It is like rings of color.
9980 
9981 	X ONLY RIGHT NOW
9982 
9983 
9984 	More specifically, you create two circles: an inner circle and an outer circle.
9985 	The gradient is only drawn in the area outside the inner circle but inside the outer
9986 	circle. The closest line between those two circles forms the line for the gradient
9987 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9988 
9989 	History:
9990 		Added November 20, 2021 (dub v10.4)
9991 
9992 	Bugs:
9993 		Not yet implemented on Windows.
9994 +/
9995 version(OSXCocoa) {} else // NotYetImplementedException
9996 class RadialGradient : Gradient {
9997 	/++
9998 
9999 	+/
10000 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10001 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10002 
10003 		version(X11) {
10004 			XRadialGradient gradient;
10005 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10006 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10007 
10008 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10009 				return XRenderCreateRadialGradient(
10010 					XDisplayConnection.get,
10011 					&gradient,
10012 					stopsPositions.ptr,
10013 					colors.ptr,
10014 					cast(int) stops.length);
10015 			});
10016 		} else version(Windows) {
10017 			// FIXME
10018 			forEachPixel((int x, int y) {
10019 				import core.stdc.math;
10020 
10021 				//sqrtf(
10022 
10023 				return Color.transparent;
10024 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10025 			});
10026 		}
10027 	}
10028 }
10029 
10030 
10031 
10032 /+
10033 	NOT IMPLEMENTED
10034 
10035 	A display-stored image optimized for relatively quick drawing, like
10036 	[Sprite], but this one supports alpha channel blending and does NOT
10037 	support direct drawing upon it with a [ScreenPainter].
10038 
10039 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10040 	plain [ScreenPainter]... sort of.
10041 
10042 	On X11, it requires the Xrender extension and library. This is available
10043 	almost everywhere though.
10044 
10045 	History:
10046 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10047 +/
10048 version(none)
10049 class AlphaSprite {
10050 	/++
10051 		Copies the given image into it.
10052 	+/
10053 	this(MemoryImage img) {
10054 
10055 		if(!XRenderLibrary.loadAttempted) {
10056 			XRenderLibrary.loadDynamicLibrary();
10057 
10058 			// FIXME: this needs to be reconstructed when the X server changes
10059 			repopulateX();
10060 		}
10061 		if(!XRenderLibrary.loadSuccessful)
10062 			throw new Exception("XRender library load failure");
10063 
10064 		// I probably need to put the alpha mask in a separate Picture
10065 		// ugh
10066 		// maybe the Sprite itself can have an alpha bitmask anyway
10067 
10068 
10069 		auto display = XDisplayConnection.get();
10070 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10071 
10072 
10073 		XRenderPictureAttributes attrs;
10074 
10075 		handle = XRenderCreatePicture(
10076 			XDisplayConnection.get,
10077 			pixmap,
10078 			RGBA,
10079 			0,
10080 			&attrs
10081 		);
10082 
10083 	}
10084 
10085 	// maybe i'll use the create gradient functions too with static factories..
10086 
10087 	void drawAt(ScreenPainter painter, Point where) {
10088 		//painter.drawPixmap(this, where);
10089 
10090 		XRenderPictureAttributes attrs;
10091 
10092 		auto pic = XRenderCreatePicture(
10093 			XDisplayConnection.get,
10094 			painter.impl.d,
10095 			RGB,
10096 			0,
10097 			&attrs
10098 		);
10099 
10100 		XRenderComposite(
10101 			XDisplayConnection.get,
10102 			3, // PictOpOver
10103 			handle,
10104 			None,
10105 			pic,
10106 			0, // src
10107 			0,
10108 			0, // mask
10109 			0,
10110 			10, // dest
10111 			10,
10112 			100, // width
10113 			100
10114 		);
10115 
10116 		/+
10117 		XRenderFreePicture(
10118 			XDisplayConnection.get,
10119 			pic
10120 		);
10121 
10122 		XRenderFreePicture(
10123 			XDisplayConnection.get,
10124 			fill
10125 		);
10126 		+/
10127 		// on Windows you can stretch but Xrender still can't :(
10128 	}
10129 
10130 	static XRenderPictFormat* RGB;
10131 	static XRenderPictFormat* RGBA;
10132 	static void repopulateX() {
10133 		auto display = XDisplayConnection.get;
10134 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10135 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10136 	}
10137 
10138 	XPixmap pixmap;
10139 	Picture handle;
10140 }
10141 
10142 ///
10143 interface CapableOfBeingDrawnUpon {
10144 	///
10145 	ScreenPainter draw();
10146 	///
10147 	int width();
10148 	///
10149 	int height();
10150 	protected ScreenPainterImplementation* activeScreenPainter();
10151 	protected void activeScreenPainter(ScreenPainterImplementation*);
10152 	bool closed();
10153 
10154 	void delegate() paintingFinishedDg();
10155 
10156 	/// Be warned: this can be a very slow operation
10157 	TrueColorImage takeScreenshot();
10158 }
10159 
10160 /// 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].
10161 void flushGui() {
10162 	version(X11) {
10163 		auto dpy = XDisplayConnection.get();
10164 		XLockDisplay(dpy);
10165 		scope(exit) XUnlockDisplay(dpy);
10166 		XFlush(dpy);
10167 	}
10168 }
10169 
10170 /++
10171 	Runs the given code in the GUI thread when its event loop
10172 	is available, blocking until it completes. This allows you
10173 	to create and manipulate windows from another thread without
10174 	invoking undefined behavior.
10175 
10176 	If this is the gui thread, it runs the code immediately.
10177 
10178 	If no gui thread exists yet, the current thread is assumed
10179 	to be it. Attempting to create windows or run the event loop
10180 	in any other thread will cause an assertion failure.
10181 
10182 
10183 	$(TIP
10184 		Did you know you can use UFCS on delegate literals?
10185 
10186 		() {
10187 			// code here
10188 		}.runInGuiThread;
10189 	)
10190 
10191 	Returns:
10192 		`true` if the function was called, `false` if it was not.
10193 		The function may not be called because the gui thread had
10194 		already terminated by the time you called this.
10195 
10196 	History:
10197 		Added April 10, 2020 (v7.2.0)
10198 
10199 		Return value added and implementation tweaked to avoid locking
10200 		at program termination on February 24, 2021 (v9.2.1).
10201 +/
10202 bool runInGuiThread(scope void delegate() dg) @trusted {
10203 	claimGuiThread();
10204 
10205 	if(thisIsGuiThread) {
10206 		dg();
10207 		return true;
10208 	}
10209 
10210 	if(guiThreadTerminating)
10211 		return false;
10212 
10213 	import core.sync.semaphore;
10214 	static Semaphore sc;
10215 	if(sc is null)
10216 		sc = new Semaphore();
10217 
10218 	static RunQueueMember* rqm;
10219 	if(rqm is null)
10220 		rqm = new RunQueueMember;
10221 	rqm.dg = cast(typeof(rqm.dg)) dg;
10222 	rqm.signal = sc;
10223 	rqm.thrown = null;
10224 
10225 	synchronized(runInGuiThreadLock) {
10226 		runInGuiThreadQueue ~= rqm;
10227 	}
10228 
10229 	if(!SimpleWindow.eventWakeUp())
10230 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10231 
10232 	rqm.signal.wait();
10233 	auto t = rqm.thrown;
10234 
10235 	if(t)
10236 		throw t;
10237 
10238 	return true;
10239 }
10240 
10241 // note it runs sync if this is the gui thread....
10242 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10243 	claimGuiThread();
10244 
10245 	try {
10246 
10247 		if(thisIsGuiThread) {
10248 			dg();
10249 			return;
10250 		}
10251 
10252 		if(guiThreadTerminating)
10253 			return;
10254 
10255 		RunQueueMember* rqm = new RunQueueMember;
10256 		rqm.dg = cast(typeof(rqm.dg)) dg;
10257 		rqm.signal = null;
10258 		rqm.thrown = null;
10259 
10260 		synchronized(runInGuiThreadLock) {
10261 			runInGuiThreadQueue ~= rqm;
10262 		}
10263 
10264 		if(!SimpleWindow.eventWakeUp())
10265 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10266 	} catch(Exception e) {
10267 		if(handleError)
10268 			handleError(e);
10269 	}
10270 }
10271 
10272 private void runPendingRunInGuiThreadDelegates() {
10273 	more:
10274 	RunQueueMember* next;
10275 	synchronized(runInGuiThreadLock) {
10276 		if(runInGuiThreadQueue.length) {
10277 			next = runInGuiThreadQueue[0];
10278 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10279 		} else {
10280 			next = null;
10281 		}
10282 	}
10283 
10284 	if(next) {
10285 		try {
10286 			next.dg();
10287 			next.thrown = null;
10288 		} catch(Throwable t) {
10289 			next.thrown = t;
10290 		}
10291 
10292 		if(next.signal)
10293 			next.signal.notify();
10294 
10295 		goto more;
10296 	}
10297 }
10298 
10299 private void claimGuiThread() nothrow {
10300 	import core.atomic;
10301 	if(cas(&guiThreadExists_, false, true))
10302 		thisIsGuiThread = true;
10303 }
10304 
10305 private struct RunQueueMember {
10306 	void delegate() dg;
10307 	import core.sync.semaphore;
10308 	Semaphore signal;
10309 	Throwable thrown;
10310 }
10311 
10312 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10313 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10314 private bool thisIsGuiThread = false;
10315 private shared bool guiThreadExists_ = false;
10316 private shared bool guiThreadTerminating = false;
10317 
10318 /++
10319 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10320 	event loop. All windows must be exclusively created and managed by a single thread.
10321 
10322 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10323 	when you call one of its constructors.
10324 
10325 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10326 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10327 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10328 
10329 	The reason this function is available is in case you want to message pass between a gui
10330 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10331 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10332 
10333 	History:
10334 		Added December 3, 2021 (dub v10.5)
10335 +/
10336 public bool guiThreadExists() {
10337 	return guiThreadExists_;
10338 }
10339 
10340 /++
10341 	Returns `true` if this thread is either running or set to be running the
10342 	simpledisplay.d gui core event loop because it owns windows.
10343 
10344 	It is important to keep gui-related functionality in the right thread, so you will
10345 	want to `runInGuiThread` when you call them (with some specific exceptions called
10346 	out in those specific functions' documentation). Notably, all windows must be
10347 	created and managed only from the gui thread.
10348 
10349 	Will return false if simpledisplay's other functions haven't been called
10350 	yet; check [guiThreadExists] in addition to this.
10351 
10352 	History:
10353 		Added December 3, 2021 (dub v10.5)
10354 +/
10355 public bool thisThreadRunningGui() {
10356 	return thisIsGuiThread;
10357 }
10358 
10359 /++
10360 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10361 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10362 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10363 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10364 	file instead if you are in one of those situations).
10365 
10366 	It does not support outputting very many types; just strings and ints are likely to actually work.
10367 
10368 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10369 	is unspecified meaning I can change it at any time. The only point of this function is to help
10370 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10371 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10372 	in those contexts.
10373 
10374 	$(WARNING
10375 		I reserve the right to change this function at any time. You can use it if it helps you
10376 		but do not rely on it for anything permanent.
10377 	)
10378 
10379 	History:
10380 		Added December 3, 2021. Not formally supported under any stable tag.
10381 +/
10382 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10383 	try {
10384 		version(Windows) {
10385 			import core.sys.windows.wincon;
10386 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10387 				AllocConsole();
10388 			const(char)* fn = "CONOUT$";
10389 		} else version(Posix) {
10390 			const(char)* fn = "/dev/tty";
10391 		} else static assert(0, "Function not implemented for your system");
10392 
10393 		if(fileOverride.length)
10394 			fn = fileOverride.ptr;
10395 
10396 		import core.stdc.stdio;
10397 		auto fp = fopen(fn, "wt");
10398 		if(fp is null) return;
10399 		scope(exit) fclose(fp);
10400 
10401 		string str;
10402 		foreach(item; t) {
10403 			static if(is(typeof(item) : const(char)[]))
10404 				str ~= item;
10405 			else
10406 				str ~= toInternal!string(item);
10407 			str ~= " ";
10408 		}
10409 		str ~= "\n";
10410 
10411 		fwrite(str.ptr, 1, str.length, fp);
10412 		fflush(fp);
10413 	} catch(Exception e) {
10414 		// sorry no hope
10415 	}
10416 }
10417 
10418 private void guiThreadFinalize() {
10419 	assert(thisIsGuiThread);
10420 
10421 	guiThreadTerminating = true; // don't add any more from this point on
10422 	runPendingRunInGuiThreadDelegates();
10423 }
10424 
10425 /+
10426 interface IPromise {
10427 	void reportProgress(int current, int max, string message);
10428 
10429 	/+ // not formally in cuz of templates but still
10430 	IPromise Then();
10431 	IPromise Catch();
10432 	IPromise Finally();
10433 	+/
10434 }
10435 
10436 /+
10437 	auto promise = async({ ... });
10438 	promise.Then(whatever).
10439 		Then(whateverelse).
10440 		Catch((exception) { });
10441 
10442 
10443 	A promise is run inside a fiber and it looks something like:
10444 
10445 	try {
10446 		auto res = whatever();
10447 		auto res2 = whateverelse(res);
10448 	} catch(Exception e) {
10449 		{ }(e);
10450 	}
10451 
10452 	When a thing succeeds, it is passed as an arg to the next
10453 +/
10454 class Promise(T) : IPromise {
10455 	auto Then() { return null; }
10456 	auto Catch() { return null; }
10457 	auto Finally() { return null; }
10458 
10459 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10460 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10461 	T await();
10462 }
10463 
10464 interface Task {
10465 }
10466 
10467 interface Resolvable(T) : Task {
10468 	void run();
10469 
10470 	void resolve(T);
10471 
10472 	Resolvable!T then(void delegate(T)); // returns a new promise
10473 	Resolvable!T error(Throwable); // js catch
10474 	Resolvable!T completed(); // js finally
10475 
10476 }
10477 
10478 /++
10479 	Runs `work` in a helper thread and sends its return value back to the main gui
10480 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10481 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10482 	kill the program.
10483 
10484 	You can call reportProgress(position, max, message) to update your parent window
10485 	on your progress.
10486 
10487 	I should also use `shared` methods. FIXME
10488 
10489 	History:
10490 		Added March 6, 2021 (dub version 9.3).
10491 +/
10492 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10493 	uponCompletion(work(null));
10494 }
10495 
10496 +/
10497 
10498 /// Used internal to dispatch events to various classes.
10499 interface CapableOfHandlingNativeEvent {
10500 	NativeEventHandler getNativeEventHandler();
10501 
10502 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10503 
10504 	version(X11) {
10505 		// if this is impossible, you are allowed to just throw from it
10506 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10507 		void recreateAfterDisconnect();
10508 		// discard any *connection specific* state, but keep enough that you
10509 		// can be recreated if possible. discardConnectionState() is always called immediately
10510 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10511 		// you need initialization order
10512 		void discardConnectionState();
10513 	}
10514 }
10515 
10516 version(X11)
10517 /++
10518 	State of keys on mouse events, especially motion.
10519 
10520 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10521 +/
10522 enum ModifierState : uint {
10523 	shift = 1, ///
10524 	capsLock = 2, ///
10525 	ctrl = 4, ///
10526 	alt = 8, /// Not always available on Windows
10527 	windows = 64, /// ditto
10528 	numLock = 16, ///
10529 
10530 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10531 	middleButtonDown = 512, /// ditto
10532 	rightButtonDown = 1024, /// ditto
10533 }
10534 else version(Windows)
10535 /// ditto
10536 enum ModifierState : uint {
10537 	shift = 4, ///
10538 	ctrl = 8, ///
10539 
10540 	// i'm not sure if the next two are available
10541 	alt = 256, /// not always available on Windows
10542 	windows = 512, /// ditto
10543 
10544 	capsLock = 1024, ///
10545 	numLock = 2048, ///
10546 
10547 	leftButtonDown = 1, /// not available on key events
10548 	middleButtonDown = 16, /// ditto
10549 	rightButtonDown = 2, /// ditto
10550 
10551 	backButtonDown = 0x20, /// not available on X
10552 	forwardButtonDown = 0x40, /// ditto
10553 }
10554 else version(OSXCocoa)
10555 // FIXME FIXME NotYetImplementedException
10556 enum ModifierState : uint {
10557 	shift = 1, ///
10558 	capsLock = 2, ///
10559 	ctrl = 4, ///
10560 	alt = 8, /// Not always available on Windows
10561 	windows = 64, /// ditto
10562 	numLock = 16, ///
10563 
10564 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10565 	middleButtonDown = 512, /// ditto
10566 	rightButtonDown = 1024, /// ditto
10567 }
10568 
10569 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10570 enum MouseButton : int {
10571 	none = 0,
10572 	left = 1, ///
10573 	right = 2, ///
10574 	middle = 4, ///
10575 	wheelUp = 8, ///
10576 	wheelDown = 16, ///
10577 	backButton = 32, /// often found on the thumb and used for back in browsers
10578 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10579 }
10580 
10581 version(X11) {
10582 	// FIXME: match ASCII whenever we can. Most of it is already there,
10583 	// but there's a few exceptions and mismatches with Windows
10584 
10585 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10586 	enum Key {
10587 		Escape = 0xff1b, ///
10588 		F1 = 0xffbe, ///
10589 		F2 = 0xffbf, ///
10590 		F3 = 0xffc0, ///
10591 		F4 = 0xffc1, ///
10592 		F5 = 0xffc2, ///
10593 		F6 = 0xffc3, ///
10594 		F7 = 0xffc4, ///
10595 		F8 = 0xffc5, ///
10596 		F9 = 0xffc6, ///
10597 		F10 = 0xffc7, ///
10598 		F11 = 0xffc8, ///
10599 		F12 = 0xffc9, ///
10600 		PrintScreen = 0xff61, ///
10601 		ScrollLock = 0xff14, ///
10602 		Pause = 0xff13, ///
10603 		Grave = 0x60, /// The $(BACKTICK) ~ key
10604 		// number keys across the top of the keyboard
10605 		N1 = 0x31, /// Number key atop the keyboard
10606 		N2 = 0x32, ///
10607 		N3 = 0x33, ///
10608 		N4 = 0x34, ///
10609 		N5 = 0x35, ///
10610 		N6 = 0x36, ///
10611 		N7 = 0x37, ///
10612 		N8 = 0x38, ///
10613 		N9 = 0x39, ///
10614 		N0 = 0x30, ///
10615 		Dash = 0x2d, ///
10616 		Equals = 0x3d, ///
10617 		Backslash = 0x5c, /// The \ | key
10618 		Backspace = 0xff08, ///
10619 		Insert = 0xff63, ///
10620 		Home = 0xff50, ///
10621 		PageUp = 0xff55, ///
10622 		Delete = 0xffff, ///
10623 		End = 0xff57, ///
10624 		PageDown = 0xff56, ///
10625 		Up = 0xff52, ///
10626 		Down = 0xff54, ///
10627 		Left = 0xff51, ///
10628 		Right = 0xff53, ///
10629 
10630 		Tab = 0xff09, ///
10631 		Q = 0x71, ///
10632 		W = 0x77, ///
10633 		E = 0x65, ///
10634 		R = 0x72, ///
10635 		T = 0x74, ///
10636 		Y = 0x79, ///
10637 		U = 0x75, ///
10638 		I = 0x69, ///
10639 		O = 0x6f, ///
10640 		P = 0x70, ///
10641 		LeftBracket = 0x5b, /// the [ { key
10642 		RightBracket = 0x5d, /// the ] } key
10643 		CapsLock = 0xffe5, ///
10644 		A = 0x61, ///
10645 		S = 0x73, ///
10646 		D = 0x64, ///
10647 		F = 0x66, ///
10648 		G = 0x67, ///
10649 		H = 0x68, ///
10650 		J = 0x6a, ///
10651 		K = 0x6b, ///
10652 		L = 0x6c, ///
10653 		Semicolon = 0x3b, ///
10654 		Apostrophe = 0x27, ///
10655 		Enter = 0xff0d, ///
10656 		Shift = 0xffe1, ///
10657 		Z = 0x7a, ///
10658 		X = 0x78, ///
10659 		C = 0x63, ///
10660 		V = 0x76, ///
10661 		B = 0x62, ///
10662 		N = 0x6e, ///
10663 		M = 0x6d, ///
10664 		Comma = 0x2c, ///
10665 		Period = 0x2e, ///
10666 		Slash = 0x2f, /// the / ? key
10667 		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
10668 		Ctrl = 0xffe3, ///
10669 		Windows = 0xffeb, ///
10670 		Alt = 0xffe9, ///
10671 		Space = 0x20, ///
10672 		Alt_r = 0xffea, /// ditto of shift_r
10673 		Windows_r = 0xffec, ///
10674 		Menu = 0xff67, ///
10675 		Ctrl_r = 0xffe4, ///
10676 
10677 		NumLock = 0xff7f, ///
10678 		Divide = 0xffaf, /// The / key on the number pad
10679 		Multiply = 0xffaa, /// The * key on the number pad
10680 		Minus = 0xffad, /// The - key on the number pad
10681 		Plus = 0xffab, /// The + key on the number pad
10682 		PadEnter = 0xff8d, /// Numberpad enter key
10683 		Pad1 = 0xff9c, /// Numberpad keys
10684 		Pad2 = 0xff99, ///
10685 		Pad3 = 0xff9b, ///
10686 		Pad4 = 0xff96, ///
10687 		Pad5 = 0xff9d, ///
10688 		Pad6 = 0xff98, ///
10689 		Pad7 = 0xff95, ///
10690 		Pad8 = 0xff97, ///
10691 		Pad9 = 0xff9a, ///
10692 		Pad0 = 0xff9e, ///
10693 		PadDot = 0xff9f, ///
10694 	}
10695 } else version(Windows) {
10696 	// the character here is for en-us layouts and for illustration only
10697 	// if you actually want to get characters, wait for character events
10698 	// (the argument to your event handler is simply a dchar)
10699 	// those will be converted by the OS for the right locale.
10700 
10701 	enum Key {
10702 		Escape = 0x1b,
10703 		F1 = 0x70,
10704 		F2 = 0x71,
10705 		F3 = 0x72,
10706 		F4 = 0x73,
10707 		F5 = 0x74,
10708 		F6 = 0x75,
10709 		F7 = 0x76,
10710 		F8 = 0x77,
10711 		F9 = 0x78,
10712 		F10 = 0x79,
10713 		F11 = 0x7a,
10714 		F12 = 0x7b,
10715 		PrintScreen = 0x2c,
10716 		ScrollLock = 0x91,
10717 		Pause = 0x13,
10718 		Grave = 0xc0,
10719 		// number keys across the top of the keyboard
10720 		N1 = 0x31,
10721 		N2 = 0x32,
10722 		N3 = 0x33,
10723 		N4 = 0x34,
10724 		N5 = 0x35,
10725 		N6 = 0x36,
10726 		N7 = 0x37,
10727 		N8 = 0x38,
10728 		N9 = 0x39,
10729 		N0 = 0x30,
10730 		Dash = 0xbd,
10731 		Equals = 0xbb,
10732 		Backslash = 0xdc,
10733 		Backspace = 0x08,
10734 		Insert = 0x2d,
10735 		Home = 0x24,
10736 		PageUp = 0x21,
10737 		Delete = 0x2e,
10738 		End = 0x23,
10739 		PageDown = 0x22,
10740 		Up = 0x26,
10741 		Down = 0x28,
10742 		Left = 0x25,
10743 		Right = 0x27,
10744 
10745 		Tab = 0x09,
10746 		Q = 0x51,
10747 		W = 0x57,
10748 		E = 0x45,
10749 		R = 0x52,
10750 		T = 0x54,
10751 		Y = 0x59,
10752 		U = 0x55,
10753 		I = 0x49,
10754 		O = 0x4f,
10755 		P = 0x50,
10756 		LeftBracket = 0xdb,
10757 		RightBracket = 0xdd,
10758 		CapsLock = 0x14,
10759 		A = 0x41,
10760 		S = 0x53,
10761 		D = 0x44,
10762 		F = 0x46,
10763 		G = 0x47,
10764 		H = 0x48,
10765 		J = 0x4a,
10766 		K = 0x4b,
10767 		L = 0x4c,
10768 		Semicolon = 0xba,
10769 		Apostrophe = 0xde,
10770 		Enter = 0x0d,
10771 		Shift = 0x10,
10772 		Z = 0x5a,
10773 		X = 0x58,
10774 		C = 0x43,
10775 		V = 0x56,
10776 		B = 0x42,
10777 		N = 0x4e,
10778 		M = 0x4d,
10779 		Comma = 0xbc,
10780 		Period = 0xbe,
10781 		Slash = 0xbf,
10782 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10783 		Ctrl = 0x11,
10784 		Windows = 0x5b,
10785 		Alt = -5, // FIXME
10786 		Space = 0x20,
10787 		Alt_r = 0xffea, // ditto of shift_r
10788 		Windows_r = 0x5c, // ditto of shift_r
10789 		Menu = 0x5d,
10790 		Ctrl_r = 0xa3, // ditto of shift_r
10791 
10792 		NumLock = 0x90,
10793 		Divide = 0x6f,
10794 		Multiply = 0x6a,
10795 		Minus = 0x6d,
10796 		Plus = 0x6b,
10797 		PadEnter = -8, // FIXME
10798 		Pad1 = 0x61,
10799 		Pad2 = 0x62,
10800 		Pad3 = 0x63,
10801 		Pad4 = 0x64,
10802 		Pad5 = 0x65,
10803 		Pad6 = 0x66,
10804 		Pad7 = 0x67,
10805 		Pad8 = 0x68,
10806 		Pad9 = 0x69,
10807 		Pad0 = 0x60,
10808 		PadDot = 0x6e,
10809 	}
10810 
10811 	// I'm keeping this around for reference purposes
10812 	// ideally all these buttons will be listed for all platforms,
10813 	// but now now I'm just focusing on my US keyboard
10814 	version(none)
10815 	enum Key {
10816 		LBUTTON = 0x01,
10817 		RBUTTON = 0x02,
10818 		CANCEL = 0x03,
10819 		MBUTTON = 0x04,
10820 		//static if (_WIN32_WINNT > =  0x500) {
10821 		XBUTTON1 = 0x05,
10822 		XBUTTON2 = 0x06,
10823 		//}
10824 		BACK = 0x08,
10825 		TAB = 0x09,
10826 		CLEAR = 0x0C,
10827 		RETURN = 0x0D,
10828 		SHIFT = 0x10,
10829 		CONTROL = 0x11,
10830 		MENU = 0x12,
10831 		PAUSE = 0x13,
10832 		CAPITAL = 0x14,
10833 		KANA = 0x15,
10834 		HANGEUL = 0x15,
10835 		HANGUL = 0x15,
10836 		JUNJA = 0x17,
10837 		FINAL = 0x18,
10838 		HANJA = 0x19,
10839 		KANJI = 0x19,
10840 		ESCAPE = 0x1B,
10841 		CONVERT = 0x1C,
10842 		NONCONVERT = 0x1D,
10843 		ACCEPT = 0x1E,
10844 		MODECHANGE = 0x1F,
10845 		SPACE = 0x20,
10846 		PRIOR = 0x21,
10847 		NEXT = 0x22,
10848 		END = 0x23,
10849 		HOME = 0x24,
10850 		LEFT = 0x25,
10851 		UP = 0x26,
10852 		RIGHT = 0x27,
10853 		DOWN = 0x28,
10854 		SELECT = 0x29,
10855 		PRINT = 0x2A,
10856 		EXECUTE = 0x2B,
10857 		SNAPSHOT = 0x2C,
10858 		INSERT = 0x2D,
10859 		DELETE = 0x2E,
10860 		HELP = 0x2F,
10861 		LWIN = 0x5B,
10862 		RWIN = 0x5C,
10863 		APPS = 0x5D,
10864 		SLEEP = 0x5F,
10865 		NUMPAD0 = 0x60,
10866 		NUMPAD1 = 0x61,
10867 		NUMPAD2 = 0x62,
10868 		NUMPAD3 = 0x63,
10869 		NUMPAD4 = 0x64,
10870 		NUMPAD5 = 0x65,
10871 		NUMPAD6 = 0x66,
10872 		NUMPAD7 = 0x67,
10873 		NUMPAD8 = 0x68,
10874 		NUMPAD9 = 0x69,
10875 		MULTIPLY = 0x6A,
10876 		ADD = 0x6B,
10877 		SEPARATOR = 0x6C,
10878 		SUBTRACT = 0x6D,
10879 		DECIMAL = 0x6E,
10880 		DIVIDE = 0x6F,
10881 		F1 = 0x70,
10882 		F2 = 0x71,
10883 		F3 = 0x72,
10884 		F4 = 0x73,
10885 		F5 = 0x74,
10886 		F6 = 0x75,
10887 		F7 = 0x76,
10888 		F8 = 0x77,
10889 		F9 = 0x78,
10890 		F10 = 0x79,
10891 		F11 = 0x7A,
10892 		F12 = 0x7B,
10893 		F13 = 0x7C,
10894 		F14 = 0x7D,
10895 		F15 = 0x7E,
10896 		F16 = 0x7F,
10897 		F17 = 0x80,
10898 		F18 = 0x81,
10899 		F19 = 0x82,
10900 		F20 = 0x83,
10901 		F21 = 0x84,
10902 		F22 = 0x85,
10903 		F23 = 0x86,
10904 		F24 = 0x87,
10905 		NUMLOCK = 0x90,
10906 		SCROLL = 0x91,
10907 		LSHIFT = 0xA0,
10908 		RSHIFT = 0xA1,
10909 		LCONTROL = 0xA2,
10910 		RCONTROL = 0xA3,
10911 		LMENU = 0xA4,
10912 		RMENU = 0xA5,
10913 		//static if (_WIN32_WINNT > =  0x500) {
10914 		BROWSER_BACK = 0xA6,
10915 		BROWSER_FORWARD = 0xA7,
10916 		BROWSER_REFRESH = 0xA8,
10917 		BROWSER_STOP = 0xA9,
10918 		BROWSER_SEARCH = 0xAA,
10919 		BROWSER_FAVORITES = 0xAB,
10920 		BROWSER_HOME = 0xAC,
10921 		VOLUME_MUTE = 0xAD,
10922 		VOLUME_DOWN = 0xAE,
10923 		VOLUME_UP = 0xAF,
10924 		MEDIA_NEXT_TRACK = 0xB0,
10925 		MEDIA_PREV_TRACK = 0xB1,
10926 		MEDIA_STOP = 0xB2,
10927 		MEDIA_PLAY_PAUSE = 0xB3,
10928 		LAUNCH_MAIL = 0xB4,
10929 		LAUNCH_MEDIA_SELECT = 0xB5,
10930 		LAUNCH_APP1 = 0xB6,
10931 		LAUNCH_APP2 = 0xB7,
10932 		//}
10933 		OEM_1 = 0xBA,
10934 		//static if (_WIN32_WINNT > =  0x500) {
10935 		OEM_PLUS = 0xBB,
10936 		OEM_COMMA = 0xBC,
10937 		OEM_MINUS = 0xBD,
10938 		OEM_PERIOD = 0xBE,
10939 		//}
10940 		OEM_2 = 0xBF,
10941 		OEM_3 = 0xC0,
10942 		OEM_4 = 0xDB,
10943 		OEM_5 = 0xDC,
10944 		OEM_6 = 0xDD,
10945 		OEM_7 = 0xDE,
10946 		OEM_8 = 0xDF,
10947 		//static if (_WIN32_WINNT > =  0x500) {
10948 		OEM_102 = 0xE2,
10949 		//}
10950 		PROCESSKEY = 0xE5,
10951 		//static if (_WIN32_WINNT > =  0x500) {
10952 		PACKET = 0xE7,
10953 		//}
10954 		ATTN = 0xF6,
10955 		CRSEL = 0xF7,
10956 		EXSEL = 0xF8,
10957 		EREOF = 0xF9,
10958 		PLAY = 0xFA,
10959 		ZOOM = 0xFB,
10960 		NONAME = 0xFC,
10961 		PA1 = 0xFD,
10962 		OEM_CLEAR = 0xFE,
10963 	}
10964 
10965 } else version(OSXCocoa) {
10966 	enum Key {
10967 		Escape = 53,
10968 		F1 = 122,
10969 		F2 = 120,
10970 		F3 = 99,
10971 		F4 = 118,
10972 		F5 = 96,
10973 		F6 = 97,
10974 		F7 = 98,
10975 		F8 = 100,
10976 		F9 = 101,
10977 		F10 = 109,
10978 		F11 = 103,
10979 		F12 = 111,
10980 		PrintScreen = 105,
10981 		ScrollLock = 107,
10982 		Pause = 113,
10983 		Grave = 50,
10984 		// number keys across the top of the keyboard
10985 		N1 = 18,
10986 		N2 = 19,
10987 		N3 = 20,
10988 		N4 = 21,
10989 		N5 = 23,
10990 		N6 = 22,
10991 		N7 = 26,
10992 		N8 = 28,
10993 		N9 = 25,
10994 		N0 = 29,
10995 		Dash = 27,
10996 		Equals = 24,
10997 		Backslash = 42,
10998 		Backspace = 51,
10999 		Insert = 114,
11000 		Home = 115,
11001 		PageUp = 116,
11002 		Delete = 117,
11003 		End = 119,
11004 		PageDown = 121,
11005 		Up = 126,
11006 		Down = 125,
11007 		Left = 123,
11008 		Right = 124,
11009 
11010 		Tab = 48,
11011 		Q = 12,
11012 		W = 13,
11013 		E = 14,
11014 		R = 15,
11015 		T = 17,
11016 		Y = 16,
11017 		U = 32,
11018 		I = 34,
11019 		O = 31,
11020 		P = 35,
11021 		LeftBracket = 33,
11022 		RightBracket = 30,
11023 		CapsLock = 57,
11024 		A = 0,
11025 		S = 1,
11026 		D = 2,
11027 		F = 3,
11028 		G = 5,
11029 		H = 4,
11030 		J = 38,
11031 		K = 40,
11032 		L = 37,
11033 		Semicolon = 41,
11034 		Apostrophe = 39,
11035 		Enter = 36,
11036 		Shift = 56,
11037 		Z = 6,
11038 		X = 7,
11039 		C = 8,
11040 		V = 9,
11041 		B = 11,
11042 		N = 45,
11043 		M = 46,
11044 		Comma = 43,
11045 		Period = 47,
11046 		Slash = 44,
11047 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11048 		Ctrl = 59,
11049 		Windows = 55,
11050 		Alt = 58,
11051 		Space = 49,
11052 		Alt_r = -3, // ditto of shift_r
11053 		Windows_r = -2,
11054 		Menu = 110,
11055 		Ctrl_r = -1,
11056 
11057 		NumLock = 1,
11058 		Divide = 75,
11059 		Multiply = 67,
11060 		Minus = 78,
11061 		Plus = 69,
11062 		PadEnter = 76,
11063 		Pad1 = 83,
11064 		Pad2 = 84,
11065 		Pad3 = 85,
11066 		Pad4 = 86,
11067 		Pad5 = 87,
11068 		Pad6 = 88,
11069 		Pad7 = 89,
11070 		Pad8 = 91,
11071 		Pad9 = 92,
11072 		Pad0 = 82,
11073 		PadDot = 65,
11074 	}
11075 
11076 }
11077 
11078 /* Additional utilities */
11079 
11080 
11081 Color fromHsl(real h, real s, real l) {
11082 	return arsd.color.fromHsl([h,s,l]);
11083 }
11084 
11085 
11086 
11087 /* ********** What follows is the system-specific implementations *********/
11088 version(Windows) {
11089 
11090 
11091 	// helpers for making HICONs from MemoryImages
11092 	class WindowsIcon {
11093 		struct Win32Icon {
11094 			align(1):
11095 			uint biSize;
11096 			int biWidth;
11097 			int biHeight;
11098 			ushort biPlanes;
11099 			ushort biBitCount;
11100 			uint biCompression;
11101 			uint biSizeImage;
11102 			int biXPelsPerMeter;
11103 			int biYPelsPerMeter;
11104 			uint biClrUsed;
11105 			uint biClrImportant;
11106 			// RGBQUAD[colorCount] biColors;
11107 			/* Pixels:
11108 			Uint8 pixels[]
11109 			*/
11110 			/* Mask:
11111 			Uint8 mask[]
11112 			*/
11113 		}
11114 
11115 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11116 
11117 			assert(mi.width <= 256, "image too wide");
11118 			assert(mi.height <= 256, "image too tall");
11119 			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
11120 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11121 
11122 			int icon_plen = mi.width * mi.height * 4;
11123 			int icon_mlen = mi.width * mi.height / 8;
11124 
11125 			int colorCount = 0;
11126 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11127 
11128 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11129 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11130 
11131 			auto data = memory[Win32Icon.sizeof .. $];
11132 
11133 			width = mi.width;
11134 			height = mi.height;
11135 
11136 			auto trueColorImage = mi.getAsTrueColorImage();
11137 
11138 			icon_win32.biSize = 40;
11139 			icon_win32.biWidth = mi.width;
11140 			icon_win32.biHeight = mi.height*2;
11141 			icon_win32.biPlanes = 1;
11142 			icon_win32.biBitCount = 32;
11143 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11144 
11145 			int offset = 0;
11146 			int andOff = icon_plen * 8; // the and offset is in bits
11147 
11148 			// leaving the and mask as the default 0 so the rgba alpha blend
11149 			// does its thing instead
11150 			for(int y = height - 1; y >= 0; y--) {
11151 				int off2 = y * width * 4;
11152 				foreach(x; 0 .. width) {
11153 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11154 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11155 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11156 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11157 
11158 					offset += 4;
11159 					off2 += 4;
11160 				}
11161 			}
11162 
11163 			return memory;
11164 		}
11165 
11166 		this(MemoryImage mi) {
11167 			int icon_len, width, height;
11168 
11169 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11170 
11171 			/*
11172 			PNG* png = readPnpngData);
11173 			PNGHeader pngh = getHeader(png);
11174 			void* icon_win32;
11175 			if(pngh.depth == 4) {
11176 				auto i = new Win32Icon!(16);
11177 				i.fromPNG(png, pngh, icon_len, width, height);
11178 				icon_win32 = i;
11179 			}
11180 			else if(pngh.depth == 8) {
11181 				auto i = new Win32Icon!(256);
11182 				i.fromPNG(png, pngh, icon_len, width, height);
11183 				icon_win32 = i;
11184 			} else assert(0);
11185 			*/
11186 
11187 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11188 
11189 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11190 		}
11191 
11192 		~this() {
11193 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11194 			DestroyIcon(hIcon);
11195 		}
11196 
11197 		HICON hIcon;
11198 	}
11199 
11200 
11201 
11202 
11203 
11204 
11205 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11206 	alias HWND NativeWindowHandle;
11207 
11208 	extern(Windows)
11209 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11210 		try {
11211 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11212 				// it returns zero if the message is handled, so we won't do anything more there
11213 				// do I like that though?
11214 				int mustReturn;
11215 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11216 				if(mustReturn)
11217 					return ret;
11218 			}
11219 
11220 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11221 				if(window.getNativeEventHandler !is null) {
11222 					int mustReturn;
11223 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11224 					if(mustReturn)
11225 						return ret;
11226 				}
11227 				if(auto w = cast(SimpleWindow) (*window))
11228 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11229 				else
11230 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11231 			} else {
11232 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11233 			}
11234 		} catch (Exception e) {
11235 			try {
11236 				sdpy_abort(e);
11237 				return 0;
11238 			} catch(Exception e) { assert(0); }
11239 		}
11240 	}
11241 
11242 	void sdpy_abort(Throwable e) nothrow {
11243 		try
11244 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11245 		catch(Exception e)
11246 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11247 		ExitProcess(1);
11248 	}
11249 
11250 	mixin template NativeScreenPainterImplementation() {
11251 		HDC hdc;
11252 		HWND hwnd;
11253 		//HDC windowHdc;
11254 		HBITMAP oldBmp;
11255 
11256 		void create(PaintingHandle window) {
11257 			hwnd = window;
11258 
11259 			if(auto sw = cast(SimpleWindow) this.window) {
11260 				// drawing on a window, double buffer
11261 				auto windowHdc = GetDC(hwnd);
11262 
11263 				auto buffer = sw.impl.buffer;
11264 				if(buffer is null) {
11265 					hdc = windowHdc;
11266 					windowDc = true;
11267 				} else {
11268 					hdc = CreateCompatibleDC(windowHdc);
11269 
11270 					ReleaseDC(hwnd, windowHdc);
11271 
11272 					oldBmp = SelectObject(hdc, buffer);
11273 				}
11274 			} else {
11275 				// drawing on something else, draw directly
11276 				hdc = CreateCompatibleDC(null);
11277 				SelectObject(hdc, window);
11278 			}
11279 
11280 			// X doesn't draw a text background, so neither should we
11281 			SetBkMode(hdc, TRANSPARENT);
11282 
11283 			ensureDefaultFontLoaded();
11284 
11285 			if(defaultGuiFont) {
11286 				SelectObject(hdc, defaultGuiFont);
11287 				// DeleteObject(defaultGuiFont);
11288 			}
11289 		}
11290 
11291 		static HFONT defaultGuiFont;
11292 		static void ensureDefaultFontLoaded() {
11293 			static bool triedDefaultGuiFont = false;
11294 			if(!triedDefaultGuiFont) {
11295 				NONCLIENTMETRICS params;
11296 				params.cbSize = params.sizeof;
11297 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11298 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11299 				}
11300 				triedDefaultGuiFont = true;
11301 			}
11302 		}
11303 
11304 		private OperatingSystemFont _activeFont;
11305 
11306 		void setFont(OperatingSystemFont font) {
11307 			_activeFont = font;
11308 			if(font && font.font) {
11309 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11310 					// error... how to handle tho?
11311 				} else {
11312 
11313 				}
11314 			}
11315 			else if(defaultGuiFont)
11316 				SelectObject(hdc, defaultGuiFont);
11317 		}
11318 
11319 		arsd.color.Rectangle _clipRectangle;
11320 
11321 		void setClipRectangle(int x, int y, int width, int height) {
11322 			auto old = _clipRectangle;
11323 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11324 			if(old == _clipRectangle)
11325 				return;
11326 
11327 			if(width == 0 || height == 0) {
11328 				SelectClipRgn(hdc, null);
11329 			} else {
11330 				auto region = CreateRectRgn(x, y, x + width, y + height);
11331 				SelectClipRgn(hdc, region);
11332 				DeleteObject(region);
11333 			}
11334 		}
11335 
11336 
11337 		// just because we can on Windows...
11338 		//void create(Image image);
11339 
11340 		void invalidateRect(Rectangle invalidRect) {
11341 			RECT rect;
11342 			rect.left = invalidRect.left;
11343 			rect.right = invalidRect.right;
11344 			rect.top = invalidRect.top;
11345 			rect.bottom = invalidRect.bottom;
11346 			InvalidateRect(hwnd, &rect, false);
11347 		}
11348 		bool manualInvalidations;
11349 
11350 		void dispose() {
11351 			// FIXME: this.window.width/height is probably wrong
11352 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11353 			// ReleaseDC(hwnd, windowHdc);
11354 
11355 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11356 			if(cast(SimpleWindow) this.window) {
11357 				if(!manualInvalidations)
11358 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11359 			}
11360 
11361 			if(originalPen !is null)
11362 				SelectObject(hdc, originalPen);
11363 			if(currentPen !is null)
11364 				DeleteObject(currentPen);
11365 			if(originalBrush !is null)
11366 				SelectObject(hdc, originalBrush);
11367 			if(currentBrush !is null)
11368 				DeleteObject(currentBrush);
11369 
11370 			SelectObject(hdc, oldBmp);
11371 
11372 			if(windowDc)
11373 				ReleaseDC(hwnd, hdc);
11374 			else
11375 				DeleteDC(hdc);
11376 
11377 			if(window.paintingFinishedDg !is null)
11378 				window.paintingFinishedDg()();
11379 		}
11380 
11381 		bool windowDc;
11382 		HPEN originalPen;
11383 		HPEN currentPen;
11384 
11385 		Pen _activePen;
11386 
11387 		Color _outlineColor;
11388 
11389 		@property void pen(Pen p) {
11390 			_activePen = p;
11391 			_outlineColor = p.color;
11392 
11393 			HPEN pen;
11394 			if(p.color.a == 0) {
11395 				pen = GetStockObject(NULL_PEN);
11396 			} else {
11397 				int style = PS_SOLID;
11398 				final switch(p.style) {
11399 					case Pen.Style.Solid:
11400 						style = PS_SOLID;
11401 					break;
11402 					case Pen.Style.Dashed:
11403 						style = PS_DASH;
11404 					break;
11405 					case Pen.Style.Dotted:
11406 						style = PS_DOT;
11407 					break;
11408 				}
11409 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11410 			}
11411 			auto orig = SelectObject(hdc, pen);
11412 			if(originalPen is null)
11413 				originalPen = orig;
11414 
11415 			if(currentPen !is null)
11416 				DeleteObject(currentPen);
11417 
11418 			currentPen = pen;
11419 
11420 			// the outline is like a foreground since it's done that way on X
11421 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11422 
11423 		}
11424 
11425 		@property void rasterOp(RasterOp op) {
11426 			int mode;
11427 			final switch(op) {
11428 				case RasterOp.normal:
11429 					mode = R2_COPYPEN;
11430 				break;
11431 				case RasterOp.xor:
11432 					mode = R2_XORPEN;
11433 				break;
11434 			}
11435 			SetROP2(hdc, mode);
11436 		}
11437 
11438 		HBRUSH originalBrush;
11439 		HBRUSH currentBrush;
11440 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11441 		@property void fillColor(Color c) {
11442 			if(c == _fillColor)
11443 				return;
11444 			_fillColor = c;
11445 			HBRUSH brush;
11446 			if(c.a == 0) {
11447 				brush = GetStockObject(HOLLOW_BRUSH);
11448 			} else {
11449 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11450 			}
11451 			auto orig = SelectObject(hdc, brush);
11452 			if(originalBrush is null)
11453 				originalBrush = orig;
11454 
11455 			if(currentBrush !is null)
11456 				DeleteObject(currentBrush);
11457 
11458 			currentBrush = brush;
11459 
11460 			// background color is NOT set because X doesn't draw text backgrounds
11461 			//   SetBkColor(hdc, RGB(255, 255, 255));
11462 		}
11463 
11464 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11465 			BITMAP bm;
11466 
11467 			HDC hdcMem = CreateCompatibleDC(hdc);
11468 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11469 
11470 			GetObject(i.handle, bm.sizeof, &bm);
11471 
11472 			// or should I AlphaBlend!??!?!
11473 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11474 
11475 			SelectObject(hdcMem, hbmOld);
11476 			DeleteDC(hdcMem);
11477 		}
11478 
11479 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11480 			BITMAP bm;
11481 
11482 			HDC hdcMem = CreateCompatibleDC(hdc);
11483 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11484 
11485 			GetObject(s.handle, bm.sizeof, &bm);
11486 
11487 			version(CRuntime_DigitalMars) goto noalpha;
11488 
11489 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11490 			if(s.enableAlpha) {
11491 				auto dw = w ? w : bm.bmWidth;
11492 				auto dh = h ? h : bm.bmHeight;
11493 				BLENDFUNCTION bf;
11494 				bf.BlendOp = AC_SRC_OVER;
11495 				bf.SourceConstantAlpha = 255;
11496 				bf.AlphaFormat = AC_SRC_ALPHA;
11497 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11498 			} else {
11499 				noalpha:
11500 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11501 			}
11502 
11503 			SelectObject(hdcMem, hbmOld);
11504 			DeleteDC(hdcMem);
11505 		}
11506 
11507 		Size textSize(scope const(char)[] text) {
11508 			bool dummyX;
11509 			if(text.length == 0) {
11510 				text = " ";
11511 				dummyX = true;
11512 			}
11513 			RECT rect;
11514 			WCharzBuffer buffer = WCharzBuffer(text);
11515 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11516 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11517 		}
11518 
11519 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11520 			if(text.length && text[$-1] == '\n')
11521 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11522 			if(text.length && text[$-1] == '\r')
11523 				text = text[0 .. $-1];
11524 
11525 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11526 			if(x2 == 0 && y2 == 0) {
11527 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11528 			} else {
11529 				RECT rect;
11530 				rect.left = x;
11531 				rect.top = y;
11532 				rect.right = x2;
11533 				rect.bottom = y2;
11534 
11535 				uint mode = DT_LEFT;
11536 				if(alignment & TextAlignment.Right)
11537 					mode = DT_RIGHT;
11538 				else if(alignment & TextAlignment.Center)
11539 					mode = DT_CENTER;
11540 
11541 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11542 				if(alignment & TextAlignment.VerticalCenter)
11543 					mode |= DT_VCENTER | DT_SINGLELINE;
11544 
11545 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11546 			}
11547 
11548 			/*
11549 			uint mode;
11550 
11551 			if(alignment & TextAlignment.Center)
11552 				mode = TA_CENTER;
11553 
11554 			SetTextAlign(hdc, mode);
11555 			*/
11556 		}
11557 
11558 		int fontHeight() {
11559 			TEXTMETRIC metric;
11560 			if(GetTextMetricsW(hdc, &metric)) {
11561 				return metric.tmHeight;
11562 			}
11563 
11564 			return 16; // idk just guessing here, maybe we should throw
11565 		}
11566 
11567 		void drawPixel(int x, int y) {
11568 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11569 		}
11570 
11571 		// The basic shapes, outlined
11572 
11573 		void drawLine(int x1, int y1, int x2, int y2) {
11574 			MoveToEx(hdc, x1, y1, null);
11575 			LineTo(hdc, x2, y2);
11576 		}
11577 
11578 		void drawRectangle(int x, int y, int width, int height) {
11579 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11580 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11581 		}
11582 
11583 		/// Arguments are the points of the bounding rectangle
11584 		void drawEllipse(int x1, int y1, int x2, int y2) {
11585 			Ellipse(hdc, x1, y1, x2, y2);
11586 		}
11587 
11588 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11589 			if((start % (360*64)) == (finish % (360*64)))
11590 				drawEllipse(x1, y1, x1 + width, y1 + height);
11591 			else {
11592 				import core.stdc.math;
11593 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11594 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11595 
11596 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11597 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11598 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11599 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11600 
11601 				if(_activePen.color.a)
11602 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11603 				if(_fillColor.a)
11604 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11605 			}
11606 		}
11607 
11608 		void drawPolygon(Point[] vertexes) {
11609 			POINT[] points;
11610 			points.length = vertexes.length;
11611 
11612 			foreach(i, p; vertexes) {
11613 				points[i].x = p.x;
11614 				points[i].y = p.y;
11615 			}
11616 
11617 			Polygon(hdc, points.ptr, cast(int) points.length);
11618 		}
11619 	}
11620 
11621 
11622 	// Mix this into the SimpleWindow class
11623 	mixin template NativeSimpleWindowImplementation() {
11624 		int curHidden = 0; // counter
11625 		__gshared static bool[string] knownWinClasses;
11626 		static bool altPressed = false;
11627 
11628 		HANDLE oldCursor;
11629 
11630 		void hideCursor () {
11631 			if(curHidden == 0)
11632 				oldCursor = SetCursor(null);
11633 			++curHidden;
11634 		}
11635 
11636 		void showCursor () {
11637 			--curHidden;
11638 			if(curHidden == 0) {
11639 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11640 			}
11641 		}
11642 
11643 
11644 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11645 
11646 		void setMinSize (int minwidth, int minheight) {
11647 			minWidth = minwidth;
11648 			minHeight = minheight;
11649 		}
11650 		void setMaxSize (int maxwidth, int maxheight) {
11651 			maxWidth = maxwidth;
11652 			maxHeight = maxheight;
11653 		}
11654 
11655 		// FIXME i'm not sure that Windows has this functionality
11656 		// though it is nonessential anyway.
11657 		void setResizeGranularity (int granx, int grany) {}
11658 
11659 		ScreenPainter getPainter(bool manualInvalidations) {
11660 			return ScreenPainter(this, hwnd, manualInvalidations);
11661 		}
11662 
11663 		HBITMAP buffer;
11664 
11665 		void setTitle(string title) {
11666 			WCharzBuffer bfr = WCharzBuffer(title);
11667 			SetWindowTextW(hwnd, bfr.ptr);
11668 		}
11669 
11670 		string getTitle() {
11671 			auto len = GetWindowTextLengthW(hwnd);
11672 			if (!len)
11673 				return null;
11674 			wchar[256] tmpBuffer;
11675 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11676 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11677 			auto str = buffer[0 .. len2];
11678 			return makeUtf8StringFromWindowsString(str);
11679 		}
11680 
11681 		void move(int x, int y) {
11682 			RECT rect;
11683 			GetWindowRect(hwnd, &rect);
11684 			// move it while maintaining the same size...
11685 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11686 		}
11687 
11688 		void resize(int w, int h) {
11689 			RECT rect;
11690 			GetWindowRect(hwnd, &rect);
11691 
11692 			RECT client;
11693 			GetClientRect(hwnd, &client);
11694 
11695 			rect.right = rect.right - client.right + w;
11696 			rect.bottom = rect.bottom - client.bottom + h;
11697 
11698 			// same position, new size for the client rectangle
11699 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11700 
11701 			updateOpenglViewportIfNeeded(w, h);
11702 		}
11703 
11704 		void moveResize (int x, int y, int w, int h) {
11705 			// what's given is the client rectangle, we need to adjust
11706 
11707 			RECT rect;
11708 			rect.left = x;
11709 			rect.top = y;
11710 			rect.right = w + x;
11711 			rect.bottom = h + y;
11712 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11713 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11714 
11715 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11716 			updateOpenglViewportIfNeeded(w, h);
11717 			if (windowResized !is null) windowResized(w, h);
11718 		}
11719 
11720 		version(without_opengl) {} else {
11721 			HGLRC ghRC;
11722 			HDC ghDC;
11723 		}
11724 
11725 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11726 			string cnamec;
11727 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11728 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11729 				cnamec = "DSimpleWindow";
11730 			} else {
11731 				cnamec = sdpyWindowClass;
11732 			}
11733 
11734 			WCharzBuffer cn = WCharzBuffer(cnamec);
11735 
11736 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11737 
11738 			if(cnamec !in knownWinClasses) {
11739 				WNDCLASSEX wc;
11740 
11741 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11742 				// to the object. Maybe.
11743 				wc.cbSize = wc.sizeof;
11744 				wc.cbClsExtra = 0;
11745 				wc.cbWndExtra = 0;
11746 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11747 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11748 				wc.hIcon = LoadIcon(hInstance, null);
11749 				wc.hInstance = hInstance;
11750 				wc.lpfnWndProc = &WndProc;
11751 				wc.lpszClassName = cn.ptr;
11752 				wc.hIconSm = null;
11753 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11754 				if(!RegisterClassExW(&wc))
11755 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11756 				knownWinClasses[cnamec] = true;
11757 			}
11758 
11759 			int style;
11760 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11761 
11762 			// FIXME: windowType and customizationFlags
11763 			final switch(windowType) {
11764 				case WindowTypes.normal:
11765 					if(resizability == Resizability.fixedSize) {
11766 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11767 					} else {
11768 						style = WS_OVERLAPPEDWINDOW;
11769 					}
11770 				break;
11771 				case WindowTypes.undecorated:
11772 					style = WS_POPUP | WS_SYSMENU;
11773 				break;
11774 				case WindowTypes.eventOnly:
11775 					_hidden = true;
11776 				break;
11777 				case WindowTypes.dropdownMenu:
11778 				case WindowTypes.popupMenu:
11779 				case WindowTypes.notification:
11780 					style = WS_POPUP;
11781 					flags |= WS_EX_NOACTIVATE;
11782 				break;
11783 				case WindowTypes.nestedChild:
11784 					style = WS_CHILD;
11785 				break;
11786 				case WindowTypes.minimallyWrapped:
11787 					assert(0, "construct minimally wrapped through the other ctor overlad");
11788 			}
11789 
11790 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11791 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11792 
11793 			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
11794 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11795 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11796 
11797 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11798 				setOpacity(255);
11799 
11800 			SimpleWindow.nativeMapping[hwnd] = this;
11801 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11802 
11803 			if(windowType == WindowTypes.eventOnly)
11804 				return;
11805 
11806 			HDC hdc = GetDC(hwnd);
11807 
11808 
11809 			version(without_opengl) {}
11810 			else {
11811 				if(opengl == OpenGlOptions.yes) {
11812 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11813 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11814 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11815 					ghDC = hdc;
11816 					PIXELFORMATDESCRIPTOR pfd;
11817 
11818 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11819 					pfd.nVersion = 1;
11820 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11821 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11822 					pfd.iPixelType = PFD_TYPE_RGBA;
11823 					pfd.cColorBits = 24;
11824 					pfd.cDepthBits = 24;
11825 					pfd.cAccumBits = 0;
11826 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11827 
11828 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11829 
11830 					if (pixelformat == 0)
11831 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11832 
11833 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11834 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11835 
11836 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11837 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11838 						// so we will create fake context to get that stupid address
11839 						auto tmpcc = wglCreateContext(ghDC);
11840 						if (tmpcc !is null) {
11841 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11842 							wglMakeCurrent(ghDC, tmpcc);
11843 							wglInitOtherFunctions();
11844 						}
11845 					}
11846 
11847 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11848 						int[9] contextAttribs = [
11849 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11850 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11851 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11852 							// for modern context, set "forward compatibility" flag too
11853 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11854 							0/*None*/,
11855 						];
11856 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11857 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11858 							// activate fallback mode
11859 							// 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;
11860 							ghRC = wglCreateContext(ghDC);
11861 						}
11862 						if (ghRC is null)
11863 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11864 					} else {
11865 						// try to do at least something
11866 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11867 							sdpyOpenGLContextVersion = 0;
11868 							ghRC = wglCreateContext(ghDC);
11869 						}
11870 						if (ghRC is null)
11871 							throw new WindowsApiException("wglCreateContext", GetLastError());
11872 					}
11873 				}
11874 			}
11875 
11876 			if(opengl == OpenGlOptions.no) {
11877 				buffer = CreateCompatibleBitmap(hdc, width, height);
11878 
11879 				auto hdcBmp = CreateCompatibleDC(hdc);
11880 				// make sure it's filled with a blank slate
11881 				auto oldBmp = SelectObject(hdcBmp, buffer);
11882 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11883 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11884 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11885 				SelectObject(hdcBmp, oldBmp);
11886 				SelectObject(hdcBmp, oldBrush);
11887 				SelectObject(hdcBmp, oldPen);
11888 				DeleteDC(hdcBmp);
11889 
11890 				bmpWidth = width;
11891 				bmpHeight = height;
11892 
11893 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11894 			}
11895 
11896 			// We want the window's client area to match the image size
11897 			RECT rcClient, rcWindow;
11898 			POINT ptDiff;
11899 			GetClientRect(hwnd, &rcClient);
11900 			GetWindowRect(hwnd, &rcWindow);
11901 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11902 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11903 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11904 
11905 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11906 				ShowWindow(hwnd, SW_SHOWNORMAL);
11907 			} else {
11908 				_hidden = true;
11909 			}
11910 			this._visibleForTheFirstTimeCalled = false; // hack!
11911 		}
11912 
11913 
11914 		void dispose() {
11915 			if(buffer)
11916 				DeleteObject(buffer);
11917 		}
11918 
11919 		void closeWindow() {
11920 			if(ghRC) {
11921 				wglDeleteContext(ghRC);
11922 				ghRC = null;
11923 			}
11924 			DestroyWindow(hwnd);
11925 		}
11926 
11927 		bool setOpacity(ubyte alpha) {
11928 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11929 		}
11930 
11931 		HANDLE currentCursor;
11932 
11933 		// returns zero if it recognized the event
11934 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11935 			MouseEvent mouse;
11936 
11937 			void mouseEvent(bool isScreen, ulong mods) {
11938 				auto x = LOWORD(lParam);
11939 				auto y = HIWORD(lParam);
11940 				if(isScreen) {
11941 					POINT p;
11942 					p.x = x;
11943 					p.y = y;
11944 					ScreenToClient(hwnd, &p);
11945 					x = cast(ushort) p.x;
11946 					y = cast(ushort) p.y;
11947 				}
11948 
11949 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11950 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11951 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11952 				}
11953 
11954 				mouse.x = x + offsetX;
11955 				mouse.y = y + offsetY;
11956 
11957 				wind.mdx(mouse);
11958 				mouse.modifierState = cast(int) mods;
11959 				mouse.window = wind;
11960 
11961 				if(wind.handleMouseEvent)
11962 					wind.handleMouseEvent(mouse);
11963 			}
11964 
11965 			switch(msg) {
11966 				case WM_GETMINMAXINFO:
11967 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11968 
11969 					if(wind.minWidth > 0) {
11970 						RECT rect;
11971 						rect.left = 100;
11972 						rect.top = 100;
11973 						rect.right = wind.minWidth + 100;
11974 						rect.bottom = wind.minHeight + 100;
11975 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11976 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11977 
11978 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11979 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11980 					}
11981 
11982 					if(wind.maxWidth < int.max) {
11983 						RECT rect;
11984 						rect.left = 100;
11985 						rect.top = 100;
11986 						rect.right = wind.maxWidth + 100;
11987 						rect.bottom = wind.maxHeight + 100;
11988 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11989 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11990 
11991 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11992 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11993 					}
11994 				break;
11995 				case WM_CHAR:
11996 					wchar c = cast(wchar) wParam;
11997 					if(wind.handleCharEvent)
11998 						wind.handleCharEvent(cast(dchar) c);
11999 				break;
12000 				  case WM_SETFOCUS:
12001 				  case WM_KILLFOCUS:
12002 					wind._focused = (msg == WM_SETFOCUS);
12003 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12004 					if(wind.onFocusChange)
12005 						wind.onFocusChange(msg == WM_SETFOCUS);
12006 				  break;
12007 
12008 				case WM_SYSKEYDOWN:
12009 					goto case;
12010 				case WM_SYSKEYUP:
12011 					if(lParam & (1 << 29)) {
12012 						goto case;
12013 					} else {
12014 						// no window has keyboard focus
12015 						goto default;
12016 					}
12017 				case WM_KEYDOWN:
12018 				case WM_KEYUP:
12019 					KeyEvent ev;
12020 					ev.key = cast(Key) wParam;
12021 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12022 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12023 
12024 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12025 
12026 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12027 						ev.modifierState |= ModifierState.shift;
12028 					//k8: this doesn't work; thanks for nothing, windows
12029 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12030 						ev.modifierState |= ModifierState.alt;*/
12031 					// this never seems to actually be set
12032 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12033 
12034 					if (wParam == 0x12) {
12035 						altPressed = (msg == WM_SYSKEYDOWN);
12036 					}
12037 
12038 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12039 						altPressed = false;
12040 					}
12041 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12042 
12043 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12044 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12045 						ev.modifierState |= ModifierState.ctrl;
12046 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12047 						ev.modifierState |= ModifierState.windows;
12048 					if(GetKeyState(Key.NumLock))
12049 						ev.modifierState |= ModifierState.numLock;
12050 					if(GetKeyState(Key.CapsLock))
12051 						ev.modifierState |= ModifierState.capsLock;
12052 
12053 					/+
12054 					// we always want to send the character too, so let's convert it
12055 					ubyte[256] state;
12056 					wchar[16] buffer;
12057 					GetKeyboardState(state.ptr);
12058 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12059 
12060 					foreach(dchar d; buffer) {
12061 						ev.character = d;
12062 						break;
12063 					}
12064 					+/
12065 
12066 					ev.window = wind;
12067 					if(wind.handleKeyEvent)
12068 						wind.handleKeyEvent(ev);
12069 				break;
12070 				case 0x020a /*WM_MOUSEWHEEL*/:
12071 					// send click
12072 					mouse.type = cast(MouseEventType) 1;
12073 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12074 					mouseEvent(true, LOWORD(wParam));
12075 
12076 					// also send release
12077 					mouse.type = cast(MouseEventType) 2;
12078 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12079 					mouseEvent(true, LOWORD(wParam));
12080 				break;
12081 				case WM_MOUSEMOVE:
12082 					mouse.type = cast(MouseEventType) 0;
12083 					mouseEvent(false, wParam);
12084 				break;
12085 				case WM_LBUTTONDOWN:
12086 				case WM_LBUTTONDBLCLK:
12087 					mouse.type = cast(MouseEventType) 1;
12088 					mouse.button = MouseButton.left;
12089 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12090 					mouseEvent(false, wParam);
12091 				break;
12092 				case WM_LBUTTONUP:
12093 					mouse.type = cast(MouseEventType) 2;
12094 					mouse.button = MouseButton.left;
12095 					mouseEvent(false, wParam);
12096 				break;
12097 				case WM_RBUTTONDOWN:
12098 				case WM_RBUTTONDBLCLK:
12099 					mouse.type = cast(MouseEventType) 1;
12100 					mouse.button = MouseButton.right;
12101 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12102 					mouseEvent(false, wParam);
12103 				break;
12104 				case WM_RBUTTONUP:
12105 					mouse.type = cast(MouseEventType) 2;
12106 					mouse.button = MouseButton.right;
12107 					mouseEvent(false, wParam);
12108 				break;
12109 				case WM_MBUTTONDOWN:
12110 				case WM_MBUTTONDBLCLK:
12111 					mouse.type = cast(MouseEventType) 1;
12112 					mouse.button = MouseButton.middle;
12113 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12114 					mouseEvent(false, wParam);
12115 				break;
12116 				case WM_MBUTTONUP:
12117 					mouse.type = cast(MouseEventType) 2;
12118 					mouse.button = MouseButton.middle;
12119 					mouseEvent(false, wParam);
12120 				break;
12121 				case WM_XBUTTONDOWN:
12122 				case WM_XBUTTONDBLCLK:
12123 					mouse.type = cast(MouseEventType) 1;
12124 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12125 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12126 					mouseEvent(false, wParam);
12127 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12128 				case WM_XBUTTONUP:
12129 					mouse.type = cast(MouseEventType) 2;
12130 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12131 					mouseEvent(false, wParam);
12132 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12133 
12134 				default: return 1;
12135 			}
12136 			return 0;
12137 		}
12138 
12139 		HWND hwnd;
12140 		private int oldWidth;
12141 		private int oldHeight;
12142 		private bool inSizeMove;
12143 
12144 		/++
12145 			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.
12146 
12147 			History:
12148 				Added November 23, 2021
12149 
12150 				Not fully stable, may be moved out of the impl struct.
12151 
12152 				Default value changed to `true` on February 15, 2021
12153 		+/
12154 		bool doLiveResizing = true;
12155 
12156 		package int bmpWidth;
12157 		package int bmpHeight;
12158 
12159 		// the extern(Windows) wndproc should just forward to this
12160 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12161 		try {
12162 			assert(hwnd is this.hwnd);
12163 
12164 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12165 			switch(msg) {
12166 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12167 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12168 					// The main things we can do are select, execute, close, or ignore
12169 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12170 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12171 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12172 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12173 
12174 					// returns the value in the *high order word* of the return value
12175 					// hence the << 16
12176 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12177 				case WM_SETCURSOR:
12178 					if(cast(HWND) wParam !is hwnd)
12179 						return 0; // further processing elsewhere
12180 
12181 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12182 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12183 						return 1;
12184 					} else {
12185 						return DefWindowProc(hwnd, msg, wParam, lParam);
12186 					}
12187 				//break;
12188 
12189 				case WM_CLOSE:
12190 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12191 				break;
12192 				case WM_DESTROY:
12193 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12194 					SimpleWindow.nativeMapping.remove(hwnd);
12195 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12196 
12197 					bool anyImportant = false;
12198 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12199 						if(w.beingOpenKeepsAppOpen) {
12200 							anyImportant = true;
12201 							break;
12202 						}
12203 					if(!anyImportant) {
12204 						PostQuitMessage(0);
12205 					}
12206 				break;
12207 				case 0x02E0 /*WM_DPICHANGED*/:
12208 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12209 
12210 					RECT* prcNewWindow = cast(RECT*)lParam;
12211 					// docs say this is the recommended position and we should honor it
12212 					SetWindowPos(hwnd,
12213 							null,
12214 							prcNewWindow.left,
12215 							prcNewWindow.top,
12216 							prcNewWindow.right - prcNewWindow.left,
12217 							prcNewWindow.bottom - prcNewWindow.top,
12218 							SWP_NOZORDER | SWP_NOACTIVATE);
12219 
12220 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12221 					// im not sure it is completely correct
12222 					// but without it the tabs and such do look weird as things change.
12223 					if(SystemParametersInfoForDpi) {
12224 						LOGFONT lfText;
12225 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12226 						HFONT hFontNew = CreateFontIndirect(&lfText);
12227 						if (hFontNew)
12228 						{
12229 							//DeleteObject(hFontOld);
12230 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12231 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12232 								return TRUE;
12233 							}
12234 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12235 						}
12236 					}
12237 
12238 					if(this.onDpiChanged)
12239 						this.onDpiChanged();
12240 				break;
12241 				case WM_ENTERIDLE:
12242 					// when a menu is up, it stops normal event processing (modal message loop)
12243 					// but this at least gives us a chance to SOMETIMES catch up
12244 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12245 					SimpleWindow.processAllCustomEvents;
12246 					SimpleWindow.processAllCustomEvents;
12247 					SleepEx(0, true);
12248 					break;
12249 				case WM_SIZE:
12250 					if(wParam == 1 /* SIZE_MINIMIZED */)
12251 						break;
12252 					_width = LOWORD(lParam);
12253 					_height = HIWORD(lParam);
12254 
12255 					// I want to avoid tearing in the windows (my code is inefficient
12256 					// so this is a hack around that) so while sizing, we don't trigger,
12257 					// but we do want to trigger on events like mazimize.
12258 					if(!inSizeMove || doLiveResizing)
12259 						goto size_changed;
12260 				break;
12261 				/+
12262 				case WM_SIZING:
12263 					writeln("size");
12264 				break;
12265 				+/
12266 				// I don't like the tearing I get when redrawing on WM_SIZE
12267 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12268 				// so instead it is going to redraw only at the end of a size.
12269 				case 0x0231: /* WM_ENTERSIZEMOVE */
12270 					inSizeMove = true;
12271 				break;
12272 				case 0x0232: /* WM_EXITSIZEMOVE */
12273 					inSizeMove = false;
12274 
12275 					size_changed:
12276 
12277 					// nothing relevant changed, don't bother redrawing
12278 					if(oldWidth == _width && oldHeight == _height) {
12279 						break;
12280 					}
12281 
12282 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12283 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12284 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12285 						// gotta get the double buffer bmp to match the window
12286 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12287 
12288 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12289 						if(resizability != Resizability.automaticallyScaleIfPossible)
12290 						if(_width > bmpWidth || _height > bmpHeight) {
12291 							auto hdc = GetDC(hwnd);
12292 							auto oldBuffer = buffer;
12293 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12294 
12295 							auto hdcBmp = CreateCompatibleDC(hdc);
12296 							auto oldBmp = SelectObject(hdcBmp, buffer);
12297 
12298 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12299 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12300 
12301 							/+
12302 							RECT r;
12303 							r.left = 0;
12304 							r.top = 0;
12305 							r.right = width;
12306 							r.bottom = height;
12307 							auto c = Color.green;
12308 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12309 							FillRect(hdcBmp, &r, brush);
12310 							DeleteObject(brush);
12311 							+/
12312 
12313 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12314 
12315 							bmpWidth = _width;
12316 							bmpHeight = _height;
12317 
12318 							SelectObject(hdcOldBmp, oldOldBmp);
12319 							DeleteDC(hdcOldBmp);
12320 
12321 							SelectObject(hdcBmp, oldBmp);
12322 							DeleteDC(hdcBmp);
12323 
12324 							ReleaseDC(hwnd, hdc);
12325 
12326 							DeleteObject(oldBuffer);
12327 						}
12328 					}
12329 
12330 					updateOpenglViewportIfNeeded(_width, _height);
12331 
12332 					if(resizability != Resizability.automaticallyScaleIfPossible)
12333 					if(windowResized !is null)
12334 						windowResized(_width, _height);
12335 
12336 					if(inSizeMove) {
12337 						SimpleWindow.processAllCustomEvents();
12338 						SimpleWindow.processAllCustomEvents();
12339 					} else {
12340 						// when it is all done, make sure everything is freshly drawn or there might be
12341 						// weird bugs left.
12342 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12343 					}
12344 
12345 					oldWidth = this._width;
12346 					oldHeight = this._height;
12347 				break;
12348 				case WM_ERASEBKGND:
12349 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12350 					if (!this._visibleForTheFirstTimeCalled) {
12351 						this._visibleForTheFirstTimeCalled = true;
12352 						if (this.visibleForTheFirstTime !is null) {
12353 							this.visibleForTheFirstTime();
12354 						}
12355 					}
12356 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12357 					version(without_opengl) {} else {
12358 						if (openglMode == OpenGlOptions.yes) return 1;
12359 					}
12360 					// call windows default handler, so it can paint standard controls
12361 					goto default;
12362 				case WM_CTLCOLORBTN:
12363 				case WM_CTLCOLORSTATIC:
12364 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12365 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12366 					GetSysColorBrush(COLOR_3DFACE);
12367 				//break;
12368 				case WM_SHOWWINDOW:
12369 					this._visible = (wParam != 0);
12370 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12371 						this._visibleForTheFirstTimeCalled = true;
12372 						if (this.visibleForTheFirstTime !is null) {
12373 							this.visibleForTheFirstTime();
12374 						}
12375 					}
12376 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12377 					break;
12378 				case WM_PAINT: {
12379 					if (!this._visibleForTheFirstTimeCalled) {
12380 						this._visibleForTheFirstTimeCalled = true;
12381 						if (this.visibleForTheFirstTime !is null) {
12382 							this.visibleForTheFirstTime();
12383 						}
12384 					}
12385 
12386 					BITMAP bm;
12387 					PAINTSTRUCT ps;
12388 
12389 					HDC hdc = BeginPaint(hwnd, &ps);
12390 
12391 					if(openglMode == OpenGlOptions.no) {
12392 
12393 						HDC hdcMem = CreateCompatibleDC(hdc);
12394 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12395 
12396 						GetObject(buffer, bm.sizeof, &bm);
12397 
12398 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12399 						if(resizability == Resizability.automaticallyScaleIfPossible)
12400 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12401 						else
12402 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12403 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12404 
12405 						SelectObject(hdcMem, hbmOld);
12406 						DeleteDC(hdcMem);
12407 						EndPaint(hwnd, &ps);
12408 					} else {
12409 						EndPaint(hwnd, &ps);
12410 						version(without_opengl) {} else
12411 							redrawOpenGlSceneSoon();
12412 					}
12413 				} break;
12414 				  default:
12415 					return DefWindowProc(hwnd, msg, wParam, lParam);
12416 			}
12417 			 return 0;
12418 
12419 		}
12420 		catch(Throwable t) {
12421 			sdpyPrintDebugString(t.toString);
12422 			return 0;
12423 		}
12424 		}
12425 	}
12426 
12427 	mixin template NativeImageImplementation() {
12428 		HBITMAP handle;
12429 		ubyte* rawData;
12430 
12431 	final:
12432 
12433 		Color getPixel(int x, int y) {
12434 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12435 			// remember, bmps are upside down
12436 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12437 
12438 			Color c;
12439 			if(enableAlpha)
12440 				c.a = rawData[offset + 3];
12441 			else
12442 				c.a = 255;
12443 			c.b = rawData[offset + 0];
12444 			c.g = rawData[offset + 1];
12445 			c.r = rawData[offset + 2];
12446 			c.unPremultiply();
12447 			return c;
12448 		}
12449 
12450 		void setPixel(int x, int y, Color c) {
12451 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12452 			// remember, bmps are upside down
12453 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12454 
12455 			if(enableAlpha)
12456 				c.premultiply();
12457 
12458 			rawData[offset + 0] = c.b;
12459 			rawData[offset + 1] = c.g;
12460 			rawData[offset + 2] = c.r;
12461 			if(enableAlpha)
12462 				rawData[offset + 3] = c.a;
12463 		}
12464 
12465 		void convertToRgbaBytes(ubyte[] where) {
12466 			assert(where.length == this.width * this.height * 4);
12467 
12468 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12469 			int idx = 0;
12470 			int offset = itemsPerLine * (height - 1);
12471 			// remember, bmps are upside down
12472 			for(int y = height - 1; y >= 0; y--) {
12473 				auto offsetStart = offset;
12474 				for(int x = 0; x < width; x++) {
12475 					where[idx + 0] = rawData[offset + 2]; // r
12476 					where[idx + 1] = rawData[offset + 1]; // g
12477 					where[idx + 2] = rawData[offset + 0]; // b
12478 					if(enableAlpha) {
12479 						where[idx + 3] = rawData[offset + 3]; // a
12480 						unPremultiplyRgba(where[idx .. idx + 4]);
12481 						offset++;
12482 					} else
12483 						where[idx + 3] = 255; // a
12484 					idx += 4;
12485 					offset += 3;
12486 				}
12487 
12488 				offset = offsetStart - itemsPerLine;
12489 			}
12490 		}
12491 
12492 		void setFromRgbaBytes(in ubyte[] what) {
12493 			assert(what.length == this.width * this.height * 4);
12494 
12495 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12496 			int idx = 0;
12497 			int offset = itemsPerLine * (height - 1);
12498 			// remember, bmps are upside down
12499 			for(int y = height - 1; y >= 0; y--) {
12500 				auto offsetStart = offset;
12501 				for(int x = 0; x < width; x++) {
12502 					if(enableAlpha) {
12503 						auto a = what[idx + 3];
12504 
12505 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12506 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12507 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12508 						rawData[offset + 3] = a; // a
12509 						//premultiplyBgra(rawData[offset .. offset + 4]);
12510 						offset++;
12511 					} else {
12512 						rawData[offset + 2] = what[idx + 0]; // r
12513 						rawData[offset + 1] = what[idx + 1]; // g
12514 						rawData[offset + 0] = what[idx + 2]; // b
12515 					}
12516 					idx += 4;
12517 					offset += 3;
12518 				}
12519 
12520 				offset = offsetStart - itemsPerLine;
12521 			}
12522 		}
12523 
12524 
12525 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12526 			BITMAPINFO infoheader;
12527 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12528 			infoheader.bmiHeader.biWidth = width;
12529 			infoheader.bmiHeader.biHeight = height;
12530 			infoheader.bmiHeader.biPlanes = 1;
12531 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12532 			infoheader.bmiHeader.biCompression = BI_RGB;
12533 
12534 			handle = CreateDIBSection(
12535 				null,
12536 				&infoheader,
12537 				DIB_RGB_COLORS,
12538 				cast(void**) &rawData,
12539 				null,
12540 				0);
12541 			if(handle is null)
12542 				throw new WindowsApiException("create image failed", GetLastError());
12543 
12544 		}
12545 
12546 		void dispose() {
12547 			DeleteObject(handle);
12548 		}
12549 	}
12550 
12551 	enum KEY_ESCAPE = 27;
12552 }
12553 version(X11) {
12554 	/// This is the default font used. You might change this before doing anything else with
12555 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12556 	/// for cross-platform compatibility.
12557 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12558 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12559 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12560 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12561 
12562 	alias int delegate(XEvent) NativeEventHandler;
12563 	alias Window NativeWindowHandle;
12564 
12565 	enum KEY_ESCAPE = 9;
12566 
12567 	mixin template NativeScreenPainterImplementation() {
12568 		Display* display;
12569 		Drawable d;
12570 		Drawable destiny;
12571 
12572 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12573 		GC gc;
12574 
12575 		__gshared bool fontAttempted;
12576 
12577 		__gshared XFontStruct* defaultfont;
12578 		__gshared XFontSet defaultfontset;
12579 
12580 		XFontStruct* font;
12581 		XFontSet fontset;
12582 
12583 		void create(PaintingHandle window) {
12584 			this.display = XDisplayConnection.get();
12585 
12586 			Drawable buffer = None;
12587 			if(auto sw = cast(SimpleWindow) this.window) {
12588 				buffer = sw.impl.buffer;
12589 				this.destiny = cast(Drawable) window;
12590 			} else {
12591 				buffer = cast(Drawable) window;
12592 				this.destiny = None;
12593 			}
12594 
12595 			this.d = cast(Drawable) buffer;
12596 
12597 			auto dgc = DefaultGC(display, DefaultScreen(display));
12598 
12599 			this.gc = XCreateGC(display, d, 0, null);
12600 
12601 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12602 
12603 			ensureDefaultFontLoaded();
12604 
12605 			font = defaultfont;
12606 			fontset = defaultfontset;
12607 
12608 			if(font) {
12609 				XSetFont(display, gc, font.fid);
12610 			}
12611 		}
12612 
12613 		static void ensureDefaultFontLoaded() {
12614 			if(!fontAttempted) {
12615 				auto display = XDisplayConnection.get;
12616 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12617 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12618 				if(font is null) {
12619 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12620 					font = XLoadQueryFont(display, xfontstr.ptr);
12621 				}
12622 
12623 				char** lol;
12624 				int lol2;
12625 				char* lol3;
12626 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12627 
12628 				fontAttempted = true;
12629 
12630 				defaultfont = font;
12631 				defaultfontset = fontset;
12632 			}
12633 		}
12634 
12635 		arsd.color.Rectangle _clipRectangle;
12636 		void setClipRectangle(int x, int y, int width, int height) {
12637 			auto old = _clipRectangle;
12638 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12639 			if(old == _clipRectangle)
12640 				return;
12641 
12642 			if(width == 0 || height == 0) {
12643 				XSetClipMask(display, gc, None);
12644 
12645 				if(xrenderPicturePainter) {
12646 
12647 					XRectangle[1] rects;
12648 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12649 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12650 				}
12651 
12652 				version(with_xft) {
12653 					if(xftFont is null || xftDraw is null)
12654 						return;
12655 					XftDrawSetClip(xftDraw, null);
12656 				}
12657 			} else {
12658 				XRectangle[1] rects;
12659 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12660 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12661 
12662 				if(xrenderPicturePainter)
12663 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12664 
12665 				version(with_xft) {
12666 					if(xftFont is null || xftDraw is null)
12667 						return;
12668 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12669 				}
12670 			}
12671 		}
12672 
12673 		version(with_xft) {
12674 			XftFont* xftFont;
12675 			XftDraw* xftDraw;
12676 
12677 			XftColor xftColor;
12678 
12679 			void updateXftColor() {
12680 				if(xftFont is null)
12681 					return;
12682 
12683 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12684 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12685 
12686 				XftColorAllocValue(
12687 					display,
12688 					DefaultVisual(display, DefaultScreen(display)),
12689 					DefaultColormap(display, 0),
12690 					&colorIn,
12691 					&xftColor
12692 				);
12693 			}
12694 		}
12695 
12696 		private OperatingSystemFont _activeFont;
12697 		void setFont(OperatingSystemFont font) {
12698 			_activeFont = font;
12699 			version(with_xft) {
12700 				if(font && font.isXft && font.xftFont)
12701 					this.xftFont = font.xftFont;
12702 				else
12703 					this.xftFont = null;
12704 
12705 				if(this.xftFont) {
12706 					if(xftDraw is null) {
12707 						xftDraw = XftDrawCreate(
12708 							display,
12709 							d,
12710 							DefaultVisual(display, DefaultScreen(display)),
12711 							DefaultColormap(display, 0)
12712 						);
12713 
12714 						updateXftColor();
12715 					}
12716 
12717 					return;
12718 				}
12719 			}
12720 
12721 			if(font && font.font) {
12722 				this.font = font.font;
12723 				this.fontset = font.fontset;
12724 				XSetFont(display, gc, font.font.fid);
12725 			} else {
12726 				this.font = defaultfont;
12727 				this.fontset = defaultfontset;
12728 			}
12729 
12730 		}
12731 
12732 		private Picture xrenderPicturePainter;
12733 
12734 		bool manualInvalidations;
12735 		void invalidateRect(Rectangle invalidRect) {
12736 			// FIXME if manualInvalidations
12737 		}
12738 
12739 		void dispose() {
12740 			this.rasterOp = RasterOp.normal;
12741 
12742 			if(xrenderPicturePainter) {
12743 				XRenderFreePicture(display, xrenderPicturePainter);
12744 				xrenderPicturePainter = None;
12745 			}
12746 
12747 			// FIXME: this.window.width/height is probably wrong
12748 
12749 			// src x,y     then dest x, y
12750 			if(destiny != None) {
12751 				// FIXME: if manual invalidations we can actually only copy some of the area.
12752 				// if(manualInvalidations)
12753 				XSetClipMask(display, gc, None);
12754 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12755 			}
12756 
12757 			XFreeGC(display, gc);
12758 
12759 			version(with_xft)
12760 			if(xftDraw) {
12761 				XftDrawDestroy(xftDraw);
12762 				xftDraw = null;
12763 			}
12764 
12765 			/+
12766 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12767 			if(font && font !is defaultfont) {
12768 				XFreeFont(display, font);
12769 				font = null;
12770 			}
12771 			if(fontset && fontset !is defaultfontset) {
12772 				XFreeFontSet(display, fontset);
12773 				fontset = null;
12774 			}
12775 			+/
12776 			XFlush(display);
12777 
12778 			if(window.paintingFinishedDg !is null)
12779 				window.paintingFinishedDg()();
12780 		}
12781 
12782 		bool backgroundIsNotTransparent = true;
12783 		bool foregroundIsNotTransparent = true;
12784 
12785 		bool _penInitialized = false;
12786 		Pen _activePen;
12787 
12788 		Color _outlineColor;
12789 		Color _fillColor;
12790 
12791 		@property void pen(Pen p) {
12792 			if(_penInitialized && p == _activePen) {
12793 				return;
12794 			}
12795 			_penInitialized = true;
12796 			_activePen = p;
12797 			_outlineColor = p.color;
12798 
12799 			int style;
12800 
12801 			byte dashLength;
12802 
12803 			final switch(p.style) {
12804 				case Pen.Style.Solid:
12805 					style = 0 /*LineSolid*/;
12806 				break;
12807 				case Pen.Style.Dashed:
12808 					style = 1 /*LineOnOffDash*/;
12809 					dashLength = 4;
12810 				break;
12811 				case Pen.Style.Dotted:
12812 					style = 1 /*LineOnOffDash*/;
12813 					dashLength = 1;
12814 				break;
12815 			}
12816 
12817 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
12818 			if(dashLength)
12819 				XSetDashes(display, gc, 0, &dashLength, 1);
12820 
12821 			if(p.color.a == 0) {
12822 				foregroundIsNotTransparent = false;
12823 				return;
12824 			}
12825 
12826 			foregroundIsNotTransparent = true;
12827 
12828 			XSetForeground(display, gc, colorToX(p.color, display));
12829 
12830 			version(with_xft)
12831 				updateXftColor();
12832 		}
12833 
12834 		RasterOp _currentRasterOp;
12835 		bool _currentRasterOpInitialized = false;
12836 		@property void rasterOp(RasterOp op) {
12837 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12838 				return;
12839 			_currentRasterOp = op;
12840 			_currentRasterOpInitialized = true;
12841 			int mode;
12842 			final switch(op) {
12843 				case RasterOp.normal:
12844 					mode = GXcopy;
12845 				break;
12846 				case RasterOp.xor:
12847 					mode = GXxor;
12848 				break;
12849 			}
12850 			XSetFunction(display, gc, mode);
12851 		}
12852 
12853 
12854 		bool _fillColorInitialized = false;
12855 
12856 		@property void fillColor(Color c) {
12857 			if(_fillColorInitialized && _fillColor == c)
12858 				return; // already good, no need to waste time calling it
12859 			_fillColor = c;
12860 			_fillColorInitialized = true;
12861 			if(c.a == 0) {
12862 				backgroundIsNotTransparent = false;
12863 				return;
12864 			}
12865 
12866 			backgroundIsNotTransparent = true;
12867 
12868 			XSetBackground(display, gc, colorToX(c, display));
12869 
12870 		}
12871 
12872 		void swapColors() {
12873 			auto tmp = _fillColor;
12874 			fillColor = _outlineColor;
12875 			auto newPen = _activePen;
12876 			newPen.color = tmp;
12877 			pen(newPen);
12878 		}
12879 
12880 		uint colorToX(Color c, Display* display) {
12881 			auto visual = DefaultVisual(display, DefaultScreen(display));
12882 			import core.bitop;
12883 			uint color = 0;
12884 			{
12885 			auto startBit = bsf(visual.red_mask);
12886 			auto lastBit = bsr(visual.red_mask);
12887 			auto r = cast(uint) c.r;
12888 			r >>= 7 - (lastBit - startBit);
12889 			r <<= startBit;
12890 			color |= r;
12891 			}
12892 			{
12893 			auto startBit = bsf(visual.green_mask);
12894 			auto lastBit = bsr(visual.green_mask);
12895 			auto g = cast(uint) c.g;
12896 			g >>= 7 - (lastBit - startBit);
12897 			g <<= startBit;
12898 			color |= g;
12899 			}
12900 			{
12901 			auto startBit = bsf(visual.blue_mask);
12902 			auto lastBit = bsr(visual.blue_mask);
12903 			auto b = cast(uint) c.b;
12904 			b >>= 7 - (lastBit - startBit);
12905 			b <<= startBit;
12906 			color |= b;
12907 			}
12908 
12909 
12910 
12911 			return color;
12912 		}
12913 
12914 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12915 			// source x, source y
12916 			if(ix >= i.width) return;
12917 			if(iy >= i.height) return;
12918 			if(ix + w > i.width) w = i.width - ix;
12919 			if(iy + h > i.height) h = i.height - iy;
12920 			if(i.usingXshm)
12921 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12922 			else
12923 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12924 		}
12925 
12926 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12927 			if(s.enableAlpha) {
12928 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12929 				if(this.xrenderPicturePainter == None) {
12930 					XRenderPictureAttributes attrs;
12931 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12932 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12933 
12934 					// need to initialize the clip
12935 					XRectangle[1] rects;
12936 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12937 
12938 					if(_clipRectangle != Rectangle.init)
12939 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12940 				}
12941 
12942 				XRenderComposite(
12943 					display,
12944 					3, // PicOpOver
12945 					s.xrenderPicture,
12946 					None,
12947 					this.xrenderPicturePainter,
12948 					ix,
12949 					iy,
12950 					0,
12951 					0,
12952 					x,
12953 					y,
12954 					w ? w : s.width,
12955 					h ? h : s.height
12956 				);
12957 			} else {
12958 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12959 			}
12960 		}
12961 
12962 		int fontHeight() {
12963 			version(with_xft)
12964 				if(xftFont !is null)
12965 					return xftFont.height;
12966 			if(font)
12967 				return font.max_bounds.ascent + font.max_bounds.descent;
12968 			return 12; // pretty common default...
12969 		}
12970 
12971 		int textWidth(in char[] line) {
12972 			version(with_xft)
12973 			if(xftFont) {
12974 				if(line.length == 0)
12975 					return 0;
12976 				XGlyphInfo extents;
12977 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12978 				return extents.width;
12979 			}
12980 
12981 			if(fontset) {
12982 				if(line.length == 0)
12983 					return 0;
12984 				XRectangle rect;
12985 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12986 
12987 				return rect.width;
12988 			}
12989 
12990 			if(font)
12991 				// FIXME: unicode
12992 				return XTextWidth( font, line.ptr, cast(int) line.length);
12993 			else
12994 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
12995 		}
12996 
12997 		Size textSize(in char[] text) {
12998 			auto maxWidth = 0;
12999 			auto lineHeight = fontHeight;
13000 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13001 			foreach(line; text.split('\n')) {
13002 				int textWidth = this.textWidth(line);
13003 				if(textWidth > maxWidth)
13004 					maxWidth = textWidth;
13005 				h += lineHeight + 4;
13006 			}
13007 			return Size(maxWidth, h);
13008 		}
13009 
13010 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13011 			const(char)[] text;
13012 			version(with_xft)
13013 			if(xftFont) {
13014 				text = originalText;
13015 				goto loaded;
13016 			}
13017 
13018 			if(fontset)
13019 				text = originalText;
13020 			else {
13021 				text.reserve(originalText.length);
13022 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13023 				// then strip the rest so there isn't garbage
13024 				foreach(dchar ch; originalText)
13025 					if(ch < 256)
13026 						text ~= cast(ubyte) ch;
13027 					else
13028 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13029 			}
13030 			loaded:
13031 			if(text.length == 0)
13032 				return;
13033 
13034 			// FIXME: should we clip it to the bounding box?
13035 			int textHeight = fontHeight;
13036 
13037 			auto lines = text.split('\n');
13038 
13039 			const lineHeight = textHeight;
13040 			textHeight *= lines.length;
13041 
13042 			int cy = y;
13043 
13044 			if(alignment & TextAlignment.VerticalBottom) {
13045 				if(y2 <= 0)
13046 					return;
13047 				auto h = y2 - y;
13048 				if(h > textHeight) {
13049 					cy += h - textHeight;
13050 					cy -= lineHeight / 2;
13051 				}
13052 			} else if(alignment & TextAlignment.VerticalCenter) {
13053 				if(y2 <= 0)
13054 					return;
13055 				auto h = y2 - y;
13056 				if(textHeight < h) {
13057 					cy += (h - textHeight) / 2;
13058 					//cy -= lineHeight / 4;
13059 				}
13060 			}
13061 
13062 			foreach(line; text.split('\n')) {
13063 				int textWidth = this.textWidth(line);
13064 
13065 				int px = x, py = cy;
13066 
13067 				if(alignment & TextAlignment.Center) {
13068 					if(x2 <= 0)
13069 						return;
13070 					auto w = x2 - x;
13071 					if(w > textWidth)
13072 						px += (w - textWidth) / 2;
13073 				} else if(alignment & TextAlignment.Right) {
13074 					if(x2 <= 0)
13075 						return;
13076 					auto pos = x2 - textWidth;
13077 					if(pos > x)
13078 						px = pos;
13079 				}
13080 
13081 				version(with_xft)
13082 				if(xftFont) {
13083 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13084 
13085 					goto carry_on;
13086 				}
13087 
13088 				if(fontset)
13089 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13090 				else
13091 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13092 				carry_on:
13093 				cy += lineHeight + 4;
13094 			}
13095 		}
13096 
13097 		void drawPixel(int x, int y) {
13098 			XDrawPoint(display, d, gc, x, y);
13099 		}
13100 
13101 		// The basic shapes, outlined
13102 
13103 		void drawLine(int x1, int y1, int x2, int y2) {
13104 			if(foregroundIsNotTransparent)
13105 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13106 		}
13107 
13108 		void drawRectangle(int x, int y, int width, int height) {
13109 			if(backgroundIsNotTransparent) {
13110 				swapColors();
13111 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13112 				swapColors();
13113 			}
13114 			// 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
13115 			if(foregroundIsNotTransparent)
13116 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13117 		}
13118 
13119 		/// Arguments are the points of the bounding rectangle
13120 		void drawEllipse(int x1, int y1, int x2, int y2) {
13121 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13122 		}
13123 
13124 		// NOTE: start and finish are in units of degrees * 64
13125 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
13126 			if(backgroundIsNotTransparent) {
13127 				swapColors();
13128 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
13129 				swapColors();
13130 			}
13131 			if(foregroundIsNotTransparent) {
13132 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
13133 
13134 				// Windows draws the straight lines on the edges too so FIXME sort of
13135 			}
13136 		}
13137 
13138 		void drawPolygon(Point[] vertexes) {
13139 			XPoint[16] pointsBuffer;
13140 			XPoint[] points;
13141 			if(vertexes.length <= pointsBuffer.length)
13142 				points = pointsBuffer[0 .. vertexes.length];
13143 			else
13144 				points.length = vertexes.length;
13145 
13146 			foreach(i, p; vertexes) {
13147 				points[i].x = cast(short) p.x;
13148 				points[i].y = cast(short) p.y;
13149 			}
13150 
13151 			if(backgroundIsNotTransparent) {
13152 				swapColors();
13153 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13154 				swapColors();
13155 			}
13156 			if(foregroundIsNotTransparent) {
13157 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13158 			}
13159 		}
13160 	}
13161 
13162 	/* XRender { */
13163 
13164 	struct XRenderColor {
13165 		ushort red;
13166 		ushort green;
13167 		ushort blue;
13168 		ushort alpha;
13169 	}
13170 
13171 	alias Picture = XID;
13172 	alias PictFormat = XID;
13173 
13174 	struct XGlyphInfo {
13175 		ushort width;
13176 		ushort height;
13177 		short x;
13178 		short y;
13179 		short xOff;
13180 		short yOff;
13181 	}
13182 
13183 struct XRenderDirectFormat {
13184     short   red;
13185     short   redMask;
13186     short   green;
13187     short   greenMask;
13188     short   blue;
13189     short   blueMask;
13190     short   alpha;
13191     short   alphaMask;
13192 }
13193 
13194 struct XRenderPictFormat {
13195     PictFormat		id;
13196     int			type;
13197     int			depth;
13198     XRenderDirectFormat	direct;
13199     Colormap		colormap;
13200 }
13201 
13202 enum PictFormatID	=   (1 << 0);
13203 enum PictFormatType	=   (1 << 1);
13204 enum PictFormatDepth	=   (1 << 2);
13205 enum PictFormatRed	=   (1 << 3);
13206 enum PictFormatRedMask  =(1 << 4);
13207 enum PictFormatGreen	=   (1 << 5);
13208 enum PictFormatGreenMask=(1 << 6);
13209 enum PictFormatBlue	=   (1 << 7);
13210 enum PictFormatBlueMask =(1 << 8);
13211 enum PictFormatAlpha	=   (1 << 9);
13212 enum PictFormatAlphaMask=(1 << 10);
13213 enum PictFormatColormap =(1 << 11);
13214 
13215 struct XRenderPictureAttributes {
13216 	int 		repeat;
13217 	Picture		alpha_map;
13218 	int			alpha_x_origin;
13219 	int			alpha_y_origin;
13220 	int			clip_x_origin;
13221 	int			clip_y_origin;
13222 	Pixmap		clip_mask;
13223 	Bool		graphics_exposures;
13224 	int			subwindow_mode;
13225 	int			poly_edge;
13226 	int			poly_mode;
13227 	Atom		dither;
13228 	Bool		component_alpha;
13229 }
13230 
13231 alias int XFixed;
13232 
13233 struct XPointFixed {
13234     XFixed  x, y;
13235 }
13236 
13237 struct XCircle {
13238     XFixed x;
13239     XFixed y;
13240     XFixed radius;
13241 }
13242 
13243 struct XTransform {
13244     XFixed[3][3]  matrix;
13245 }
13246 
13247 struct XFilters {
13248     int	    nfilter;
13249     char    **filter;
13250     int	    nalias;
13251     short   *alias_;
13252 }
13253 
13254 struct XIndexValue {
13255     c_ulong    pixel;
13256     ushort   red, green, blue, alpha;
13257 }
13258 
13259 struct XAnimCursor {
13260     Cursor	    cursor;
13261     c_ulong   delay;
13262 }
13263 
13264 struct XLinearGradient {
13265     XPointFixed p1;
13266     XPointFixed p2;
13267 }
13268 
13269 struct XRadialGradient {
13270     XCircle inner;
13271     XCircle outer;
13272 }
13273 
13274 struct XConicalGradient {
13275     XPointFixed center;
13276     XFixed angle; /* in degrees */
13277 }
13278 
13279 enum PictStandardARGB32  = 0;
13280 enum PictStandardRGB24   = 1;
13281 enum PictStandardA8	 =  2;
13282 enum PictStandardA4	 =  3;
13283 enum PictStandardA1	 =  4;
13284 enum PictStandardNUM	 =  5;
13285 
13286 interface XRender {
13287 extern(C) @nogc:
13288 
13289 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13290 
13291 	Status XRenderQueryVersion (Display *dpy,
13292 			int     *major_versionp,
13293 			int     *minor_versionp);
13294 
13295 	Status XRenderQueryFormats (Display *dpy);
13296 
13297 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13298 
13299 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13300 
13301 	XRenderPictFormat *
13302 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13303 
13304 	XRenderPictFormat *
13305 		XRenderFindFormat (Display			*dpy,
13306 				c_ulong		mask,
13307 				const XRenderPictFormat	*templ,
13308 				int				count);
13309 	XRenderPictFormat *
13310 		XRenderFindStandardFormat (Display		*dpy,
13311 				int			format);
13312 
13313 	XIndexValue *
13314 		XRenderQueryPictIndexValues(Display			*dpy,
13315 				const XRenderPictFormat	*format,
13316 				int				*num);
13317 
13318 	Picture XRenderCreatePicture(
13319 		Display *dpy,
13320 		Drawable drawable,
13321 		const XRenderPictFormat *format,
13322 		c_ulong valuemask,
13323 		const XRenderPictureAttributes *attributes);
13324 
13325 	void XRenderChangePicture (Display				*dpy,
13326 				Picture				picture,
13327 				c_ulong			valuemask,
13328 				const XRenderPictureAttributes  *attributes);
13329 
13330 	void
13331 		XRenderSetPictureClipRectangles (Display	    *dpy,
13332 				Picture	    picture,
13333 				int		    xOrigin,
13334 				int		    yOrigin,
13335 				const XRectangle *rects,
13336 				int		    n);
13337 
13338 	void
13339 		XRenderSetPictureClipRegion (Display	    *dpy,
13340 				Picture	    picture,
13341 				Region	    r);
13342 
13343 	void
13344 		XRenderSetPictureTransform (Display	    *dpy,
13345 				Picture	    picture,
13346 				XTransform	    *transform);
13347 
13348 	void
13349 		XRenderFreePicture (Display                   *dpy,
13350 				Picture                   picture);
13351 
13352 	void
13353 		XRenderComposite (Display   *dpy,
13354 				int	    op,
13355 				Picture   src,
13356 				Picture   mask,
13357 				Picture   dst,
13358 				int	    src_x,
13359 				int	    src_y,
13360 				int	    mask_x,
13361 				int	    mask_y,
13362 				int	    dst_x,
13363 				int	    dst_y,
13364 				uint	width,
13365 				uint	height);
13366 
13367 
13368 	Picture XRenderCreateSolidFill (Display *dpy,
13369 			const XRenderColor *color);
13370 
13371 	Picture XRenderCreateLinearGradient (Display *dpy,
13372 			const XLinearGradient *gradient,
13373 			const XFixed *stops,
13374 			const XRenderColor *colors,
13375 			int nstops);
13376 
13377 	Picture XRenderCreateRadialGradient (Display *dpy,
13378 			const XRadialGradient *gradient,
13379 			const XFixed *stops,
13380 			const XRenderColor *colors,
13381 			int nstops);
13382 
13383 	Picture XRenderCreateConicalGradient (Display *dpy,
13384 			const XConicalGradient *gradient,
13385 			const XFixed *stops,
13386 			const XRenderColor *colors,
13387 			int nstops);
13388 
13389 
13390 
13391 	Cursor
13392 		XRenderCreateCursor (Display	    *dpy,
13393 				Picture	    source,
13394 				uint   x,
13395 				uint   y);
13396 
13397 	XFilters *
13398 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13399 
13400 	void
13401 		XRenderSetPictureFilter (Display    *dpy,
13402 				Picture    picture,
13403 				const char *filter,
13404 				XFixed	    *params,
13405 				int	    nparams);
13406 
13407 	Cursor
13408 		XRenderCreateAnimCursor (Display	*dpy,
13409 				int		ncursor,
13410 				XAnimCursor	*cursors);
13411 }
13412 
13413 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13414 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13415 
13416 	/* XRender } */
13417 
13418 	/* Xrandr { */
13419 
13420 struct XRRMonitorInfo {
13421     Atom name;
13422     Bool primary;
13423     Bool automatic;
13424     int noutput;
13425     int x;
13426     int y;
13427     int width;
13428     int height;
13429     int mwidth;
13430     int mheight;
13431     /*RROutput*/ void *outputs;
13432 }
13433 
13434 struct XRRScreenChangeNotifyEvent {
13435     int type;                   /* event base */
13436     c_ulong serial;       /* # of last request processed by server */
13437     Bool send_event;            /* true if this came from a SendEvent request */
13438     Display *display;           /* Display the event was read from */
13439     Window window;              /* window which selected for this event */
13440     Window root;                /* Root window for changed screen */
13441     Time timestamp;             /* when the screen change occurred */
13442     Time config_timestamp;      /* when the last configuration change */
13443     ushort/*SizeID*/ size_index;
13444     ushort/*SubpixelOrder*/ subpixel_order;
13445     ushort/*Rotation*/ rotation;
13446     int width;
13447     int height;
13448     int mwidth;
13449     int mheight;
13450 }
13451 
13452 enum RRScreenChangeNotify = 0;
13453 
13454 enum RRScreenChangeNotifyMask = 1;
13455 
13456 __gshared int xrrEventBase = -1;
13457 
13458 
13459 interface XRandr {
13460 extern(C) @nogc:
13461 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13462 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13463 
13464 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13465 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13466 
13467 	void XRRSelectInput(Display *dpy, Window window, int mask);
13468 }
13469 
13470 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13471 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13472 	/* Xrandr } */
13473 
13474 	/* Xft { */
13475 
13476 	// actually freetype
13477 	alias void FT_Face;
13478 
13479 	// actually fontconfig
13480 	private alias FcBool = int;
13481 	alias void FcCharSet;
13482 	alias void FcPattern;
13483 	alias void FcResult;
13484 	enum FcEndian { FcEndianBig, FcEndianLittle }
13485 	struct FcFontSet {
13486 		int nfont;
13487 		int sfont;
13488 		FcPattern** fonts;
13489 	}
13490 
13491 	// actually XRegion
13492 	struct BOX {
13493 		short x1, x2, y1, y2;
13494 	}
13495 	struct _XRegion {
13496 		c_long size;
13497 		c_long numRects;
13498 		BOX* rects;
13499 		BOX extents;
13500 	}
13501 
13502 	alias Region = _XRegion*;
13503 
13504 	// ok actually Xft
13505 
13506 	struct XftFontInfo;
13507 
13508 	struct XftFont {
13509 		int         ascent;
13510 		int         descent;
13511 		int         height;
13512 		int         max_advance_width;
13513 		FcCharSet*  charset;
13514 		FcPattern*  pattern;
13515 	}
13516 
13517 	struct XftDraw;
13518 
13519 	struct XftColor {
13520 		c_ulong pixel;
13521 		XRenderColor color;
13522 	}
13523 
13524 	struct XftCharSpec {
13525 		dchar           ucs4;
13526 		short           x;
13527 		short           y;
13528 	}
13529 
13530 	struct XftCharFontSpec {
13531 		XftFont         *font;
13532 		dchar           ucs4;
13533 		short           x;
13534 		short           y;
13535 	}
13536 
13537 	struct XftGlyphSpec {
13538 		uint            glyph;
13539 		short           x;
13540 		short           y;
13541 	}
13542 
13543 	struct XftGlyphFontSpec {
13544 		XftFont         *font;
13545 		uint            glyph;
13546 		short           x;
13547 		short           y;
13548 	}
13549 
13550 	interface Xft {
13551 	extern(C) @nogc pure:
13552 
13553 	Bool XftColorAllocName (Display  *dpy,
13554 				const Visual   *visual,
13555 				Colormap cmap,
13556 				const char     *name,
13557 				XftColor *result);
13558 
13559 	Bool XftColorAllocValue (Display         *dpy,
13560 				Visual          *visual,
13561 				Colormap        cmap,
13562 				const XRenderColor    *color,
13563 				XftColor        *result);
13564 
13565 	void XftColorFree (Display   *dpy,
13566 				Visual    *visual,
13567 				Colormap  cmap,
13568 				XftColor  *color);
13569 
13570 	Bool XftDefaultHasRender (Display *dpy);
13571 
13572 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13573 
13574 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13575 
13576 	XftDraw * XftDrawCreate (Display   *dpy,
13577 		       Drawable  drawable,
13578 		       Visual    *visual,
13579 		       Colormap  colormap);
13580 
13581 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13582 			     Pixmap   bitmap);
13583 
13584 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13585 			    Pixmap  pixmap,
13586 			    int     depth);
13587 
13588 	void XftDrawChange (XftDraw  *draw,
13589 		       Drawable drawable);
13590 
13591 	Display * XftDrawDisplay (XftDraw *draw);
13592 
13593 	Drawable XftDrawDrawable (XftDraw *draw);
13594 
13595 	Colormap XftDrawColormap (XftDraw *draw);
13596 
13597 	Visual * XftDrawVisual (XftDraw *draw);
13598 
13599 	void XftDrawDestroy (XftDraw *draw);
13600 
13601 	Picture XftDrawPicture (XftDraw *draw);
13602 
13603 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13604 
13605 	void XftDrawGlyphs (XftDraw          *draw,
13606 				const XftColor *color,
13607 				XftFont          *pub,
13608 				int              x,
13609 				int              y,
13610 				const uint  *glyphs,
13611 				int              nglyphs);
13612 
13613 	void XftDrawString8 (XftDraw             *draw,
13614 				const XftColor    *color,
13615 				XftFont             *pub,
13616 				int                 x,
13617 				int                 y,
13618 				const char     *string,
13619 				int                 len);
13620 
13621 	void XftDrawString16 (XftDraw            *draw,
13622 				const XftColor   *color,
13623 				XftFont            *pub,
13624 				int                x,
13625 				int                y,
13626 				const wchar   *string,
13627 				int                len);
13628 
13629 	void XftDrawString32 (XftDraw            *draw,
13630 				const XftColor   *color,
13631 				XftFont            *pub,
13632 				int                x,
13633 				int                y,
13634 				const dchar   *string,
13635 				int                len);
13636 
13637 	void XftDrawStringUtf8 (XftDraw          *draw,
13638 				const XftColor *color,
13639 				XftFont          *pub,
13640 				int              x,
13641 				int              y,
13642 				const char  *string,
13643 				int              len);
13644 	void XftDrawStringUtf16 (XftDraw             *draw,
13645 				const XftColor    *color,
13646 				XftFont             *pub,
13647 				int                 x,
13648 				int                 y,
13649 				const char     *string,
13650 				FcEndian            endian,
13651 				int                 len);
13652 
13653 	void XftDrawCharSpec (XftDraw                *draw,
13654 				const XftColor       *color,
13655 				XftFont                *pub,
13656 				const XftCharSpec    *chars,
13657 				int                    len);
13658 
13659 	void XftDrawCharFontSpec (XftDraw                    *draw,
13660 				const XftColor           *color,
13661 				const XftCharFontSpec    *chars,
13662 				int                        len);
13663 
13664 	void XftDrawGlyphSpec (XftDraw               *draw,
13665 				const XftColor      *color,
13666 				XftFont               *pub,
13667 				const XftGlyphSpec  *glyphs,
13668 				int                   len);
13669 
13670 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13671 				const XftColor          *color,
13672 				const XftGlyphFontSpec  *glyphs,
13673 				int                       len);
13674 
13675 	void XftDrawRect (XftDraw            *draw,
13676 				const XftColor   *color,
13677 				int                x,
13678 				int                y,
13679 				uint       width,
13680 				uint       height);
13681 
13682 	Bool XftDrawSetClip (XftDraw     *draw,
13683 				Region      r);
13684 
13685 
13686 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13687 				int                   xOrigin,
13688 				int                   yOrigin,
13689 				const XRectangle    *rects,
13690 				int                   n);
13691 
13692 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13693 				int        mode);
13694 
13695 	void XftGlyphExtents (Display            *dpy,
13696 				XftFont            *pub,
13697 				const uint    *glyphs,
13698 				int                nglyphs,
13699 				XGlyphInfo         *extents);
13700 
13701 	void XftTextExtents8 (Display            *dpy,
13702 				XftFont            *pub,
13703 				const char    *string,
13704 				int                len,
13705 				XGlyphInfo         *extents);
13706 
13707 	void XftTextExtents16 (Display           *dpy,
13708 				XftFont           *pub,
13709 				const wchar  *string,
13710 				int               len,
13711 				XGlyphInfo        *extents);
13712 
13713 	void XftTextExtents32 (Display           *dpy,
13714 				XftFont           *pub,
13715 				const dchar  *string,
13716 				int               len,
13717 				XGlyphInfo        *extents);
13718 
13719 	void XftTextExtentsUtf8 (Display         *dpy,
13720 				XftFont         *pub,
13721 				const char *string,
13722 				int             len,
13723 				XGlyphInfo      *extents);
13724 
13725 	void XftTextExtentsUtf16 (Display            *dpy,
13726 				XftFont            *pub,
13727 				const char    *string,
13728 				FcEndian           endian,
13729 				int                len,
13730 				XGlyphInfo         *extents);
13731 
13732 	FcPattern * XftFontMatch (Display           *dpy,
13733 				int               screen,
13734 				const FcPattern *pattern,
13735 				FcResult          *result);
13736 
13737 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13738 
13739 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13740 
13741 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13742 
13743 	FT_Face XftLockFace (XftFont *pub);
13744 
13745 	void XftUnlockFace (XftFont *pub);
13746 
13747 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13748 
13749 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13750 
13751 	dchar XftFontInfoHash (const XftFontInfo *fi);
13752 
13753 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13754 
13755 	XftFont * XftFontOpenInfo (Display        *dpy,
13756 				FcPattern      *pattern,
13757 				XftFontInfo    *fi);
13758 
13759 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13760 
13761 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13762 
13763 	void XftFontClose (Display *dpy, XftFont *pub);
13764 
13765 	FcBool XftInitFtLibrary();
13766 	void XftFontLoadGlyphs (Display          *dpy,
13767 				XftFont          *pub,
13768 				FcBool           need_bitmaps,
13769 				const uint  *glyphs,
13770 				int              nglyph);
13771 
13772 	void XftFontUnloadGlyphs (Display            *dpy,
13773 				XftFont            *pub,
13774 				const uint    *glyphs,
13775 				int                nglyph);
13776 
13777 	FcBool XftFontCheckGlyph (Display  *dpy,
13778 				XftFont  *pub,
13779 				FcBool   need_bitmaps,
13780 				uint  glyph,
13781 				uint  *missing,
13782 				int      *nmissing);
13783 
13784 	FcBool XftCharExists (Display      *dpy,
13785 				XftFont      *pub,
13786 				dchar    ucs4);
13787 
13788 	uint XftCharIndex (Display       *dpy,
13789 				XftFont       *pub,
13790 				dchar      ucs4);
13791 	FcBool XftInit (const char *config);
13792 
13793 	int XftGetVersion ();
13794 
13795 	FcFontSet * XftListFonts (Display   *dpy,
13796 				int       screen,
13797 				...);
13798 
13799 	FcPattern *XftNameParse (const char *name);
13800 
13801 	void XftGlyphRender (Display         *dpy,
13802 				int             op,
13803 				Picture         src,
13804 				XftFont         *pub,
13805 				Picture         dst,
13806 				int             srcx,
13807 				int             srcy,
13808 				int             x,
13809 				int             y,
13810 				const uint *glyphs,
13811 				int             nglyphs);
13812 
13813 	void XftGlyphSpecRender (Display                 *dpy,
13814 				int                     op,
13815 				Picture                 src,
13816 				XftFont                 *pub,
13817 				Picture                 dst,
13818 				int                     srcx,
13819 				int                     srcy,
13820 				const XftGlyphSpec    *glyphs,
13821 				int                     nglyphs);
13822 
13823 	void XftCharSpecRender (Display              *dpy,
13824 				int                  op,
13825 				Picture              src,
13826 				XftFont              *pub,
13827 				Picture              dst,
13828 				int                  srcx,
13829 				int                  srcy,
13830 				const XftCharSpec  *chars,
13831 				int                  len);
13832 	void XftGlyphFontSpecRender (Display                     *dpy,
13833 				int                         op,
13834 				Picture                     src,
13835 				Picture                     dst,
13836 				int                         srcx,
13837 				int                         srcy,
13838 				const XftGlyphFontSpec    *glyphs,
13839 				int                         nglyphs);
13840 
13841 	void XftCharFontSpecRender (Display                  *dpy,
13842 				int                      op,
13843 				Picture                  src,
13844 				Picture                  dst,
13845 				int                      srcx,
13846 				int                      srcy,
13847 				const XftCharFontSpec  *chars,
13848 				int                      len);
13849 
13850 	void XftTextRender8 (Display         *dpy,
13851 				int             op,
13852 				Picture         src,
13853 				XftFont         *pub,
13854 				Picture         dst,
13855 				int             srcx,
13856 				int             srcy,
13857 				int             x,
13858 				int             y,
13859 				const char *string,
13860 				int             len);
13861 	void XftTextRender16 (Display            *dpy,
13862 				int                op,
13863 				Picture            src,
13864 				XftFont            *pub,
13865 				Picture            dst,
13866 				int                srcx,
13867 				int                srcy,
13868 				int                x,
13869 				int                y,
13870 				const wchar   *string,
13871 				int                len);
13872 
13873 	void XftTextRender16BE (Display          *dpy,
13874 				int              op,
13875 				Picture          src,
13876 				XftFont          *pub,
13877 				Picture          dst,
13878 				int              srcx,
13879 				int              srcy,
13880 				int              x,
13881 				int              y,
13882 				const char  *string,
13883 				int              len);
13884 
13885 	void XftTextRender16LE (Display          *dpy,
13886 				int              op,
13887 				Picture          src,
13888 				XftFont          *pub,
13889 				Picture          dst,
13890 				int              srcx,
13891 				int              srcy,
13892 				int              x,
13893 				int              y,
13894 				const char  *string,
13895 				int              len);
13896 
13897 	void XftTextRender32 (Display            *dpy,
13898 				int                op,
13899 				Picture            src,
13900 				XftFont            *pub,
13901 				Picture            dst,
13902 				int                srcx,
13903 				int                srcy,
13904 				int                x,
13905 				int                y,
13906 				const dchar   *string,
13907 				int                len);
13908 
13909 	void XftTextRender32BE (Display          *dpy,
13910 				int              op,
13911 				Picture          src,
13912 				XftFont          *pub,
13913 				Picture          dst,
13914 				int              srcx,
13915 				int              srcy,
13916 				int              x,
13917 				int              y,
13918 				const char  *string,
13919 				int              len);
13920 
13921 	void XftTextRender32LE (Display          *dpy,
13922 				int              op,
13923 				Picture          src,
13924 				XftFont          *pub,
13925 				Picture          dst,
13926 				int              srcx,
13927 				int              srcy,
13928 				int              x,
13929 				int              y,
13930 				const char  *string,
13931 				int              len);
13932 
13933 	void XftTextRenderUtf8 (Display          *dpy,
13934 				int              op,
13935 				Picture          src,
13936 				XftFont          *pub,
13937 				Picture          dst,
13938 				int              srcx,
13939 				int              srcy,
13940 				int              x,
13941 				int              y,
13942 				const char  *string,
13943 				int              len);
13944 
13945 	void XftTextRenderUtf16 (Display         *dpy,
13946 				int             op,
13947 				Picture         src,
13948 				XftFont         *pub,
13949 				Picture         dst,
13950 				int             srcx,
13951 				int             srcy,
13952 				int             x,
13953 				int             y,
13954 				const char *string,
13955 				FcEndian        endian,
13956 				int             len);
13957 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13958 
13959 	}
13960 
13961 	interface FontConfig {
13962 	extern(C) @nogc pure:
13963 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13964 		void FcFontSetDestroy(FcFontSet*);
13965 		char* FcNameUnparse(const FcPattern *);
13966 	}
13967 
13968 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13969 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13970 
13971 
13972 	/* Xft } */
13973 
13974 	class XDisconnectException : Exception {
13975 		bool userRequested;
13976 		this(bool userRequested = true) {
13977 			this.userRequested = userRequested;
13978 			super("X disconnected");
13979 		}
13980 	}
13981 
13982 	/++
13983 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
13984 
13985 		Please note that it returns
13986 	+/
13987 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
13988 
13989 		static XErrorEvent[] errorBuffer;
13990 
13991 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
13992 			errorBuffer ~= *evt;
13993 			return 0;
13994 		}
13995 
13996 		auto savedErrorHandler = XSetErrorHandler(&handler);
13997 
13998 		try {
13999 			dg();
14000 		} finally {
14001 			XSync(XDisplayConnection.get, 0/*False*/);
14002 			XSetErrorHandler(savedErrorHandler);
14003 		}
14004 
14005 		auto bfr = errorBuffer;
14006 		errorBuffer = null;
14007 
14008 		return bfr;
14009 	}
14010 
14011 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14012 	class XDisplayConnection {
14013 		private __gshared Display* display;
14014 		private __gshared XIM xim;
14015 		private __gshared char* displayName;
14016 
14017 		private __gshared int connectionSequence_;
14018 		private __gshared bool isLocal_;
14019 
14020 		/// use this for lazy caching when reconnection
14021 		static int connectionSequenceNumber() { return connectionSequence_; }
14022 
14023 		/++
14024 			Guesses if the connection appears to be local.
14025 
14026 			History:
14027 				Added June 3, 2021
14028 		+/
14029 		static @property bool isLocal() nothrow @trusted @nogc {
14030 			return isLocal_;
14031 		}
14032 
14033 		/// Attempts recreation of state, may require application assistance
14034 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14035 		/// then call this, and if successful, reenter the loop.
14036 		static void discardAndRecreate(string newDisplayString = null) {
14037 			if(insideXEventLoop)
14038 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14039 
14040 			// 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
14041 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14042 
14043 			foreach(handle; chnenhm) {
14044 				handle.discardConnectionState();
14045 			}
14046 
14047 			discardState();
14048 
14049 			if(newDisplayString !is null)
14050 				setDisplayName(newDisplayString);
14051 
14052 			auto display = get();
14053 
14054 			foreach(handle; chnenhm) {
14055 				handle.recreateAfterDisconnect();
14056 			}
14057 		}
14058 
14059 		private __gshared EventMask rootEventMask;
14060 
14061 		/++
14062 			Requests the specified input from the root window on the connection, in addition to any other request.
14063 
14064 
14065 			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.
14066 
14067 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14068 		+/
14069 		static void addRootInput(EventMask mask) {
14070 			auto old = rootEventMask;
14071 			rootEventMask |= mask;
14072 			get(); // to ensure display connected
14073 			if(display !is null && rootEventMask != old)
14074 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14075 		}
14076 
14077 		static void discardState() {
14078 			freeImages();
14079 
14080 			foreach(atomPtr; interredAtoms)
14081 				*atomPtr = 0;
14082 			interredAtoms = null;
14083 			interredAtoms.assumeSafeAppend();
14084 
14085 			ScreenPainterImplementation.fontAttempted = false;
14086 			ScreenPainterImplementation.defaultfont = null;
14087 			ScreenPainterImplementation.defaultfontset = null;
14088 
14089 			Image.impl.xshmQueryCompleted = false;
14090 			Image.impl._xshmAvailable = false;
14091 
14092 			SimpleWindow.nativeMapping = null;
14093 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14094 			// GlobalHotkeyManager
14095 
14096 			display = null;
14097 			xim = null;
14098 		}
14099 
14100 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14101 		private static void createXIM () {
14102 			import core.stdc.locale : setlocale, LC_ALL;
14103 			import core.stdc.stdio : stderr, fprintf;
14104 			import core.stdc.stdlib : free;
14105 			import core.stdc.string : strdup;
14106 
14107 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14108 
14109 			auto olocale = strdup(setlocale(LC_ALL, null));
14110 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14111 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14112 
14113 			//fprintf(stderr, "opening IM...\n");
14114 			foreach (string s; mtry) {
14115 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14116 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14117 			}
14118 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14119 		}
14120 
14121 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14122 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14123 		static struct ImgList {
14124 			size_t img; // class; hide it from GC
14125 			ImgList* next;
14126 		}
14127 
14128 		static __gshared ImgList* imglist = null;
14129 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14130 
14131 		static void registerImage (Image img) {
14132 			if (!imglistLocked && img !is null) {
14133 				import core.stdc.stdlib : malloc;
14134 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14135 				assert(it !is null); // do proper checks
14136 				it.img = cast(size_t)cast(void*)img;
14137 				it.next = imglist;
14138 				imglist = it;
14139 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14140 			}
14141 		}
14142 
14143 		static void unregisterImage (Image img) {
14144 			if (!imglistLocked && img !is null) {
14145 				import core.stdc.stdlib : free;
14146 				ImgList* prev = null;
14147 				ImgList* cur = imglist;
14148 				while (cur !is null) {
14149 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14150 					prev = cur;
14151 					cur = cur.next;
14152 				}
14153 				if (cur !is null) {
14154 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14155 					free(cur);
14156 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14157 				} else {
14158 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14159 				}
14160 			}
14161 		}
14162 
14163 		static void freeImages () { // needed for discardAndRecreate
14164 			imglistLocked = true;
14165 			scope(exit) imglistLocked = false;
14166 			ImgList* cur = imglist;
14167 			ImgList* next = null;
14168 			while (cur !is null) {
14169 				import core.stdc.stdlib : free;
14170 				next = cur.next;
14171 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14172 				(cast(Image)cast(void*)cur.img).dispose();
14173 				free(cur);
14174 				cur = next;
14175 			}
14176 			imglist = null;
14177 		}
14178 
14179 		/// can be used to override normal handling of display name
14180 		/// from environment and/or command line
14181 		static setDisplayName(string newDisplayName) {
14182 			displayName = cast(char*) (newDisplayName ~ '\0');
14183 		}
14184 
14185 		/// resets to the default display string
14186 		static resetDisplayName() {
14187 			displayName = null;
14188 		}
14189 
14190 		///
14191 		static Display* get() {
14192 			if(display is null) {
14193 				if(!librariesSuccessfullyLoaded)
14194 					throw new Exception("Unable to load X11 client libraries");
14195 				display = XOpenDisplay(displayName);
14196 
14197 				isLocal_ = false;
14198 
14199 				connectionSequence_++;
14200 				if(display is null)
14201 					throw new Exception("Unable to open X display");
14202 
14203 				auto str = display.display_name;
14204 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14205 				// and otherwise it probably isn't
14206 				if(str is null || (str[0] != ':' && str[0] != '/'))
14207 					isLocal_ = false;
14208 				else
14209 					isLocal_ = true;
14210 
14211 				debug(sdpy_x_errors) {
14212 					XSetErrorHandler(&adrlogger);
14213 					XSynchronize(display, true);
14214 
14215 					extern(C) int wtf() {
14216 						if(errorHappened) {
14217 							asm { int 3; }
14218 							errorHappened = false;
14219 						}
14220 						return 0;
14221 					}
14222 					XSetAfterFunction(display, &wtf);
14223 				}
14224 
14225 
14226 				XSetIOErrorHandler(&x11ioerrCB);
14227 				Bool sup;
14228 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14229 				createXIM();
14230 				version(with_eventloop) {
14231 					import arsd.eventloop;
14232 					addFileEventListeners(display.fd, &eventListener, null, null);
14233 				}
14234 			}
14235 
14236 			return display;
14237 		}
14238 
14239 		extern(C)
14240 		static int x11ioerrCB(Display* dpy) {
14241 			throw new XDisconnectException(false);
14242 		}
14243 
14244 		version(with_eventloop) {
14245 			import arsd.eventloop;
14246 			static void eventListener(OsFileHandle fd) {
14247 				//this.mtLock();
14248 				//scope(exit) this.mtUnlock();
14249 				while(XPending(display))
14250 					doXNextEvent(display);
14251 			}
14252 		}
14253 
14254 		// close connection on program exit -- we need this to properly free all images
14255 		static ~this () {
14256 			// the gui thread must clean up after itself or else Xlib might deadlock
14257 			// using this flag on any thread destruction is the easiest way i know of
14258 			// (shared static this is run by the LAST thread to exit, which may not be
14259 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14260 			if(thisIsGuiThread)
14261 				close();
14262 		}
14263 
14264 		///
14265 		static void close() {
14266 			if(display is null)
14267 				return;
14268 
14269 			version(with_eventloop) {
14270 				import arsd.eventloop;
14271 				removeFileEventListeners(display.fd);
14272 			}
14273 
14274 			// now remove all registered images to prevent shared memory leaks
14275 			freeImages();
14276 
14277 			// tbh I don't know why it is doing this but like if this happens to run
14278 			// from the other thread there's frequent hanging inside here.
14279 			if(thisIsGuiThread)
14280 				XCloseDisplay(display);
14281 			display = null;
14282 		}
14283 	}
14284 
14285 	mixin template NativeImageImplementation() {
14286 		XImage* handle;
14287 		ubyte* rawData;
14288 
14289 		XShmSegmentInfo shminfo;
14290 		bool premultiply = true;
14291 
14292 		__gshared bool xshmQueryCompleted;
14293 		__gshared bool _xshmAvailable;
14294 		public static @property bool xshmAvailable() {
14295 			if(!xshmQueryCompleted) {
14296 				int i1, i2, i3;
14297 				xshmQueryCompleted = true;
14298 
14299 				if(!XDisplayConnection.isLocal)
14300 					_xshmAvailable = false;
14301 				else
14302 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14303 			}
14304 			return _xshmAvailable;
14305 		}
14306 
14307 		bool usingXshm;
14308 	final:
14309 
14310 		private __gshared bool xshmfailed;
14311 
14312 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14313 			auto display = XDisplayConnection.get();
14314 			assert(display !is null);
14315 			auto screen = DefaultScreen(display);
14316 
14317 			// it will only use shared memory for somewhat largish images,
14318 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14319 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14320 
14321 
14322 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14323 				// the actual use still fails. For example, if the program is in a container and permission denied
14324 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14325 				//
14326 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14327 
14328 
14329 				// synchronize so preexisting buffers are clear
14330 				XSync(display, false);
14331 				xshmfailed = false;
14332 
14333 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14334 
14335 
14336 				usingXshm = true;
14337 				handle = XShmCreateImage(
14338 					display,
14339 					DefaultVisual(display, screen),
14340 					enableAlpha ? 32: 24,
14341 					ImageFormat.ZPixmap,
14342 					null,
14343 					&shminfo,
14344 					width, height);
14345 				if(handle is null)
14346 					goto abortXshm1;
14347 
14348 				if(handle.bytes_per_line != 4 * width)
14349 					goto abortXshm2;
14350 
14351 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14352 				if(shminfo.shmid < 0)
14353 					goto abortXshm3;
14354 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14355 				if(rawData == cast(ubyte*) -1)
14356 					goto abortXshm4;
14357 				shminfo.readOnly = 0;
14358 				XShmAttach(display, &shminfo);
14359 
14360 				// and now to the final error check to ensure it actually worked.
14361 				XSync(display, false);
14362 				if(xshmfailed)
14363 					goto abortXshm5;
14364 
14365 				XSetErrorHandler(oldErrorHandler);
14366 
14367 				XDisplayConnection.registerImage(this);
14368 				// if I don't flush here there's a chance the dtor will run before the
14369 				// ctor and lead to a bad value X error. While this hurts the efficiency
14370 				// it is local anyway so prolly better to keep it simple
14371 				XFlush(display);
14372 
14373 				return;
14374 
14375 				abortXshm5:
14376 					shmdt(shminfo.shmaddr);
14377 					rawData = null;
14378 
14379 				abortXshm4:
14380 					shmctl(shminfo.shmid, IPC_RMID, null);
14381 
14382 				abortXshm3:
14383 					// nothing needed, the shmget failed so there's nothing to free
14384 
14385 				abortXshm2:
14386 					XDestroyImage(handle);
14387 					handle = null;
14388 
14389 				abortXshm1:
14390 					XSetErrorHandler(oldErrorHandler);
14391 					usingXshm = false;
14392 					handle = null;
14393 
14394 					shminfo = typeof(shminfo).init;
14395 
14396 					_xshmAvailable = false; // don't try again in the future
14397 
14398 					// writeln("fallingback");
14399 
14400 					goto fallback;
14401 
14402 			} else {
14403 				fallback:
14404 
14405 				if (forcexshm) throw new Exception("can't create XShm Image");
14406 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14407 				import core.stdc.stdlib : malloc;
14408 				rawData = cast(ubyte*) malloc(width * height * 4);
14409 
14410 				handle = XCreateImage(
14411 					display,
14412 					DefaultVisual(display, screen),
14413 					enableAlpha ? 32 : 24, // bpp
14414 					ImageFormat.ZPixmap,
14415 					0, // offset
14416 					rawData,
14417 					width, height,
14418 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14419 			}
14420 		}
14421 
14422 		void dispose() {
14423 			// note: this calls free(rawData) for us
14424 			if(handle) {
14425 				if (usingXshm) {
14426 					XDisplayConnection.unregisterImage(this);
14427 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14428 				}
14429 				XDestroyImage(handle);
14430 				if(usingXshm) {
14431 					shmdt(shminfo.shmaddr);
14432 					shmctl(shminfo.shmid, IPC_RMID, null);
14433 				}
14434 				handle = null;
14435 			}
14436 		}
14437 
14438 		Color getPixel(int x, int y) {
14439 			auto offset = (y * width + x) * 4;
14440 			Color c;
14441 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14442 			c.b = rawData[offset + 0];
14443 			c.g = rawData[offset + 1];
14444 			c.r = rawData[offset + 2];
14445 			if(enableAlpha && premultiply)
14446 				c.unPremultiply;
14447 			return c;
14448 		}
14449 
14450 		void setPixel(int x, int y, Color c) {
14451 			if(enableAlpha && premultiply)
14452 				c.premultiply();
14453 			auto offset = (y * width + x) * 4;
14454 			rawData[offset + 0] = c.b;
14455 			rawData[offset + 1] = c.g;
14456 			rawData[offset + 2] = c.r;
14457 			if(enableAlpha)
14458 				rawData[offset + 3] = c.a;
14459 		}
14460 
14461 		void convertToRgbaBytes(ubyte[] where) {
14462 			assert(where.length == this.width * this.height * 4);
14463 
14464 			// if rawData had a length....
14465 			//assert(rawData.length == where.length);
14466 			for(int idx = 0; idx < where.length; idx += 4) {
14467 				where[idx + 0] = rawData[idx + 2]; // r
14468 				where[idx + 1] = rawData[idx + 1]; // g
14469 				where[idx + 2] = rawData[idx + 0]; // b
14470 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14471 
14472 				if(enableAlpha && premultiply)
14473 					unPremultiplyRgba(where[idx .. idx + 4]);
14474 			}
14475 		}
14476 
14477 		void setFromRgbaBytes(in ubyte[] where) {
14478 			assert(where.length == this.width * this.height * 4);
14479 
14480 			// if rawData had a length....
14481 			//assert(rawData.length == where.length);
14482 			for(int idx = 0; idx < where.length; idx += 4) {
14483 				rawData[idx + 2] = where[idx + 0]; // r
14484 				rawData[idx + 1] = where[idx + 1]; // g
14485 				rawData[idx + 0] = where[idx + 2]; // b
14486 				if(enableAlpha) {
14487 					rawData[idx + 3] = where[idx + 3]; // a
14488 					if(premultiply)
14489 						premultiplyBgra(rawData[idx .. idx + 4]);
14490 				}
14491 			}
14492 		}
14493 
14494 	}
14495 
14496 	mixin template NativeSimpleWindowImplementation() {
14497 		GC gc;
14498 		Window window;
14499 		Display* display;
14500 
14501 		Pixmap buffer;
14502 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14503 		XIC xic; // input context
14504 		int curHidden = 0; // counter
14505 		Cursor blankCurPtr = 0;
14506 		int cursorSequenceNumber = 0;
14507 		int warpEventCount = 0; // number of mouse movement events to eat
14508 
14509 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14510 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14511 
14512 		version(without_opengl) {} else
14513 		GLXContext glc;
14514 
14515 		private void fixFixedSize(bool forced=false) (int width, int height) {
14516 			if (forced || this.resizability == Resizability.fixedSize) {
14517 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14518 				XSizeHints sh;
14519 				static if (!forced) {
14520 					c_long spr;
14521 					XGetWMNormalHints(display, window, &sh, &spr);
14522 					sh.flags |= PMaxSize | PMinSize;
14523 				} else {
14524 					sh.flags = PMaxSize | PMinSize;
14525 				}
14526 				sh.min_width = width;
14527 				sh.min_height = height;
14528 				sh.max_width = width;
14529 				sh.max_height = height;
14530 				XSetWMNormalHints(display, window, &sh);
14531 				//XFlush(display);
14532 			}
14533 		}
14534 
14535 		ScreenPainter getPainter(bool manualInvalidations) {
14536 			return ScreenPainter(this, window, manualInvalidations);
14537 		}
14538 
14539 		void move(int x, int y) {
14540 			XMoveWindow(display, window, x, y);
14541 		}
14542 
14543 		void resize(int w, int h) {
14544 			if (w < 1) w = 1;
14545 			if (h < 1) h = 1;
14546 			XResizeWindow(display, window, w, h);
14547 
14548 			// calling this now to avoid waiting for the server to
14549 			// acknowledge the resize; draws without returning to the
14550 			// event loop will thus actually work. the server's event
14551 			// btw might overrule this and resize it again
14552 			recordX11Resize(display, this, w, h);
14553 
14554 			updateOpenglViewportIfNeeded(w, h);
14555 		}
14556 
14557 		void moveResize (int x, int y, int w, int h) {
14558 			if (w < 1) w = 1;
14559 			if (h < 1) h = 1;
14560 			XMoveResizeWindow(display, window, x, y, w, h);
14561 			updateOpenglViewportIfNeeded(w, h);
14562 		}
14563 
14564 		void hideCursor () {
14565 			if (curHidden++ == 0) {
14566 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14567 					static const(char)[1] cmbmp = 0;
14568 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14569 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14570 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14571 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14572 					XFreePixmap(display, pm);
14573 				}
14574 				XDefineCursor(display, window, blankCurPtr);
14575 			}
14576 		}
14577 
14578 		void showCursor () {
14579 			if (--curHidden == 0) XUndefineCursor(display, window);
14580 		}
14581 
14582 		void warpMouse (int x, int y) {
14583 			// here i will send dummy "ignore next mouse motion" event,
14584 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14585 			// and we don't need to report it to the user (as warping is
14586 			// used when the user needs movement deltas).
14587 			//XClientMessageEvent xclient;
14588 			XEvent e;
14589 			e.xclient.type = EventType.ClientMessage;
14590 			e.xclient.window = window;
14591 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14592 			e.xclient.format = 32;
14593 			e.xclient.data.l[0] = 0;
14594 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14595 			//{ 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]); }
14596 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14597 			// now warp pointer...
14598 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14599 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14600 			// ...and flush
14601 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14602 			XFlush(display);
14603 		}
14604 
14605 		void sendDummyEvent () {
14606 			// here i will send dummy event to ping event queue
14607 			XEvent e;
14608 			e.xclient.type = EventType.ClientMessage;
14609 			e.xclient.window = window;
14610 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14611 			e.xclient.format = 32;
14612 			e.xclient.data.l[0] = 0;
14613 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14614 			XFlush(display);
14615 		}
14616 
14617 		void setTitle(string title) {
14618 			if (title.ptr is null) title = "";
14619 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14620 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14621 			XTextProperty windowName;
14622 			windowName.value = title.ptr;
14623 			windowName.encoding = XA_UTF8; //XA_STRING;
14624 			windowName.format = 8;
14625 			windowName.nitems = cast(uint)title.length;
14626 			XSetWMName(display, window, &windowName);
14627 			char[1024] namebuf = 0;
14628 			auto maxlen = namebuf.length-1;
14629 			if (maxlen > title.length) maxlen = title.length;
14630 			namebuf[0..maxlen] = title[0..maxlen];
14631 			XStoreName(display, window, namebuf.ptr);
14632 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14633 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14634 		}
14635 
14636 		string[] getTitles() {
14637 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14638 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14639 			XTextProperty textProp;
14640 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14641 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14642 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14643 				} else
14644 					return [];
14645 			} else
14646 				return null;
14647 		}
14648 
14649 		string getTitle() {
14650 			auto titles = getTitles();
14651 			return titles.length ? titles[0] : null;
14652 		}
14653 
14654 		void setMinSize (int minwidth, int minheight) {
14655 			import core.stdc.config : c_long;
14656 			if (minwidth < 1) minwidth = 1;
14657 			if (minheight < 1) minheight = 1;
14658 			XSizeHints sh;
14659 			c_long spr;
14660 			XGetWMNormalHints(display, window, &sh, &spr);
14661 			sh.min_width = minwidth;
14662 			sh.min_height = minheight;
14663 			sh.flags |= PMinSize;
14664 			XSetWMNormalHints(display, window, &sh);
14665 			flushGui();
14666 		}
14667 
14668 		void setMaxSize (int maxwidth, int maxheight) {
14669 			import core.stdc.config : c_long;
14670 			if (maxwidth < 1) maxwidth = 1;
14671 			if (maxheight < 1) maxheight = 1;
14672 			XSizeHints sh;
14673 			c_long spr;
14674 			XGetWMNormalHints(display, window, &sh, &spr);
14675 			sh.max_width = maxwidth;
14676 			sh.max_height = maxheight;
14677 			sh.flags |= PMaxSize;
14678 			XSetWMNormalHints(display, window, &sh);
14679 			flushGui();
14680 		}
14681 
14682 		void setResizeGranularity (int granx, int grany) {
14683 			import core.stdc.config : c_long;
14684 			if (granx < 1) granx = 1;
14685 			if (grany < 1) grany = 1;
14686 			XSizeHints sh;
14687 			c_long spr;
14688 			XGetWMNormalHints(display, window, &sh, &spr);
14689 			sh.width_inc = granx;
14690 			sh.height_inc = grany;
14691 			sh.flags |= PResizeInc;
14692 			XSetWMNormalHints(display, window, &sh);
14693 			flushGui();
14694 		}
14695 
14696 		void setOpacity (uint opacity) {
14697 			arch_ulong o = opacity;
14698 			if (opacity == uint.max)
14699 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14700 			else
14701 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14702 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14703 		}
14704 
14705 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14706 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14707 			display = XDisplayConnection.get();
14708 			auto screen = DefaultScreen(display);
14709 
14710 			bool overrideRedirect = false;
14711 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14712 				overrideRedirect = true;
14713 
14714 			version(without_opengl) {}
14715 			else {
14716 				if(opengl == OpenGlOptions.yes) {
14717 					GLXFBConfig fbconf = null;
14718 					XVisualInfo* vi = null;
14719 					bool useLegacy = false;
14720 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14721 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14722 						int[23] visualAttribs = [
14723 							GLX_X_RENDERABLE , 1/*True*/,
14724 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14725 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14726 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14727 							GLX_RED_SIZE     , 8,
14728 							GLX_GREEN_SIZE   , 8,
14729 							GLX_BLUE_SIZE    , 8,
14730 							GLX_ALPHA_SIZE   , 8,
14731 							GLX_DEPTH_SIZE   , 24,
14732 							GLX_STENCIL_SIZE , 8,
14733 							GLX_DOUBLEBUFFER , 1/*True*/,
14734 							0/*None*/,
14735 						];
14736 						int fbcount;
14737 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14738 						if (fbcount == 0) {
14739 							useLegacy = true; // try to do at least something
14740 						} else {
14741 							// pick the FB config/visual with the most samples per pixel
14742 							int bestidx = -1, bestns = -1;
14743 							foreach (int fbi; 0..fbcount) {
14744 								int sb, samples;
14745 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14746 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14747 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14748 							}
14749 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14750 							fbconf = fbc[bestidx];
14751 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14752 							XFree(fbc);
14753 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14754 						}
14755 					}
14756 					if (vi is null || useLegacy) {
14757 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14758 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14759 						useLegacy = true;
14760 					}
14761 					if (vi is null) throw new Exception("no open gl visual found");
14762 
14763 					XSetWindowAttributes swa;
14764 					auto root = RootWindow(display, screen);
14765 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14766 
14767 					swa.override_redirect = overrideRedirect;
14768 
14769 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14770 						0, 0, width, height,
14771 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14772 
14773 					// now try to use `glXCreateContextAttribsARB()` if it's here
14774 					if (!useLegacy) {
14775 						// request fairly advanced context, even with stencil buffer!
14776 						int[9] contextAttribs = [
14777 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14778 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14779 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14780 							// for modern context, set "forward compatibility" flag too
14781 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14782 							0/*None*/,
14783 						];
14784 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14785 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14786 							sdpyOpenGLContextVersion = 0;
14787 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14788 						}
14789 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14790 					} else {
14791 						// fallback to old GLX call
14792 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14793 							sdpyOpenGLContextVersion = 0;
14794 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14795 						}
14796 					}
14797 					// sync to ensure any errors generated are processed
14798 					XSync(display, 0/*False*/);
14799 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14800 					if(glc is null)
14801 						throw new Exception("glc");
14802 				}
14803 			}
14804 
14805 			if(opengl == OpenGlOptions.no) {
14806 
14807 				XSetWindowAttributes swa;
14808 				swa.background_pixel = WhitePixel(display, screen);
14809 				swa.border_pixel = BlackPixel(display, screen);
14810 				swa.override_redirect = overrideRedirect;
14811 				auto root = RootWindow(display, screen);
14812 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14813 
14814 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14815 					0, 0, width, height,
14816 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14817 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14818 
14819 
14820 
14821 				/*
14822 				window = XCreateSimpleWindow(
14823 					display,
14824 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14825 					0, 0, // x, y
14826 					width, height,
14827 					1, // border width
14828 					BlackPixel(display, screen), // border
14829 					WhitePixel(display, screen)); // background
14830 				*/
14831 
14832 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14833 				bufferw = width;
14834 				bufferh = height;
14835 
14836 				gc = DefaultGC(display, screen);
14837 
14838 				// clear out the buffer to get us started...
14839 				XSetForeground(display, gc, WhitePixel(display, screen));
14840 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14841 				XSetForeground(display, gc, BlackPixel(display, screen));
14842 			}
14843 
14844 			// input context
14845 			//TODO: create this only for top-level windows, and reuse that?
14846 			populateXic();
14847 
14848 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14849 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14850 			// window class
14851 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14852 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14853 				XClassHint klass;
14854 				XWMHints wh;
14855 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14856 					wh.input = true;
14857 					wh.flags |= InputHint;
14858 				}
14859 				XSizeHints size;
14860 				klass.res_name = sdpyWindowClassStr;
14861 				klass.res_class = sdpyWindowClassStr;
14862 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14863 			}
14864 
14865 			setTitle(title);
14866 			SimpleWindow.nativeMapping[window] = this;
14867 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14868 
14869 			// This gives our window a close button
14870 			if (windowType != WindowTypes.eventOnly) {
14871 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14872 				int useAtoms;
14873 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14874 					useAtoms = 2;
14875 				} else {
14876 					useAtoms = 1;
14877 				}
14878 				assert(useAtoms <= atoms.length);
14879 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14880 			}
14881 
14882 			// FIXME: windowType and customizationFlags
14883 			Atom[8] wsatoms; // here, due to goto
14884 			int wmsacount = 0; // here, due to goto
14885 
14886 			try
14887 			final switch(windowType) {
14888 				case WindowTypes.normal:
14889 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14890 				break;
14891 				case WindowTypes.undecorated:
14892 					motifHideDecorations();
14893 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14894 				break;
14895 				case WindowTypes.eventOnly:
14896 					_hidden = true;
14897 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14898 					goto hiddenWindow;
14899 				//break;
14900 				case WindowTypes.nestedChild:
14901 					// handled in XCreateWindow calls
14902 				break;
14903 
14904 				case WindowTypes.dropdownMenu:
14905 					motifHideDecorations();
14906 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14907 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14908 				break;
14909 				case WindowTypes.popupMenu:
14910 					motifHideDecorations();
14911 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14912 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14913 				break;
14914 				case WindowTypes.notification:
14915 					motifHideDecorations();
14916 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14917 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14918 				break;
14919 				case WindowTypes.minimallyWrapped:
14920 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14921 				/+
14922 				case WindowTypes.menu:
14923 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14924 					motifHideDecorations();
14925 				break;
14926 				case WindowTypes.desktop:
14927 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14928 				break;
14929 				case WindowTypes.dock:
14930 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14931 				break;
14932 				case WindowTypes.toolbar:
14933 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14934 				break;
14935 				case WindowTypes.menu:
14936 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14937 				break;
14938 				case WindowTypes.utility:
14939 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14940 				break;
14941 				case WindowTypes.splash:
14942 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14943 				break;
14944 				case WindowTypes.dialog:
14945 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14946 				break;
14947 				case WindowTypes.tooltip:
14948 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14949 				break;
14950 				case WindowTypes.notification:
14951 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14952 				break;
14953 				case WindowTypes.combo:
14954 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14955 				break;
14956 				case WindowTypes.dnd:
14957 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14958 				break;
14959 				+/
14960 			}
14961 			catch(Exception e) {
14962 				// XInternAtom failed, prolly a WM
14963 				// that doesn't support these things
14964 			}
14965 
14966 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14967 			// the two following flags may be ignored by WM
14968 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14969 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14970 
14971 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14972 
14973 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14974 
14975 			// What would be ideal here is if they only were
14976 			// selected if there was actually an event handler
14977 			// for them...
14978 
14979 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14980 
14981 			hiddenWindow:
14982 
14983 			// set the pid property for lookup later by window managers
14984 			// a standard convenience
14985 			import core.sys.posix.unistd;
14986 			arch_ulong pid = getpid();
14987 
14988 			XChangeProperty(
14989 				display,
14990 				impl.window,
14991 				GetAtom!("_NET_WM_PID", true)(display),
14992 				XA_CARDINAL,
14993 				32 /* bits */,
14994 				0 /*PropModeReplace*/,
14995 				&pid,
14996 				1);
14997 
14998 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
14999 				if(parent is null) assert(0);
15000 				XChangeProperty(
15001 					display,
15002 					impl.window,
15003 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15004 					XA_WINDOW,
15005 					32 /* bits */,
15006 					0 /*PropModeReplace*/,
15007 					&parent.impl.window,
15008 					1);
15009 
15010 			}
15011 
15012 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15013 				XMapWindow(display, window);
15014 			} else {
15015 				_hidden = true;
15016 			}
15017 		}
15018 
15019 		void populateXic() {
15020 			if (XDisplayConnection.xim !is null) {
15021 				xic = XCreateIC(XDisplayConnection.xim,
15022 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15023 						/*XNClientWindow*/"clientWindow".ptr, window,
15024 						/*XNFocusWindow*/"focusWindow".ptr, window,
15025 						null);
15026 				if (xic is null) {
15027 					import core.stdc.stdio : stderr, fprintf;
15028 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15029 				}
15030 			}
15031 		}
15032 
15033 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15034 			auto mask = EventMask.ExposureMask |
15035 				EventMask.KeyPressMask |
15036 				EventMask.KeyReleaseMask |
15037 				EventMask.PropertyChangeMask |
15038 				EventMask.FocusChangeMask |
15039 				EventMask.StructureNotifyMask |
15040 				EventMask.SubstructureNotifyMask |
15041 				EventMask.VisibilityChangeMask
15042 				| EventMask.ButtonPressMask
15043 				| EventMask.ButtonReleaseMask
15044 			;
15045 
15046 			// xshm is our shortcut for local connections
15047 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15048 				mask |= EventMask.PointerMotionMask;
15049 			else
15050 				mask |= EventMask.ButtonMotionMask;
15051 
15052 			XSelectInput(display, window, mask);
15053 		}
15054 
15055 
15056 		void setNetWMWindowType(Atom type) {
15057 			Atom[2] atoms;
15058 
15059 			atoms[0] = type;
15060 			// generic fallback
15061 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15062 
15063 			XChangeProperty(
15064 				display,
15065 				impl.window,
15066 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15067 				XA_ATOM,
15068 				32 /* bits */,
15069 				0 /*PropModeReplace*/,
15070 				atoms.ptr,
15071 				cast(int) atoms.length);
15072 		}
15073 
15074 		void motifHideDecorations(bool hide = true) {
15075 			MwmHints hints;
15076 			hints.flags = MWM_HINTS_DECORATIONS;
15077 			hints.decorations = hide ? 0 : 1;
15078 
15079 			XChangeProperty(
15080 				display,
15081 				impl.window,
15082 				GetAtom!"_MOTIF_WM_HINTS"(display),
15083 				GetAtom!"_MOTIF_WM_HINTS"(display),
15084 				32 /* bits */,
15085 				0 /*PropModeReplace*/,
15086 				&hints,
15087 				hints.sizeof / 4);
15088 		}
15089 
15090 		/*k8: unused
15091 		void createOpenGlContext() {
15092 
15093 		}
15094 		*/
15095 
15096 		void closeWindow() {
15097 			// I can't close this or a child window closing will
15098 			// break events for everyone. So I'm just leaking it right
15099 			// now and that is probably perfectly fine...
15100 			version(none)
15101 			if (customEventFDRead != -1) {
15102 				import core.sys.posix.unistd : close;
15103 				auto same = customEventFDRead == customEventFDWrite;
15104 
15105 				close(customEventFDRead);
15106 				if(!same)
15107 					close(customEventFDWrite);
15108 				customEventFDRead = -1;
15109 				customEventFDWrite = -1;
15110 			}
15111 
15112 			version(without_opengl) {} else
15113 			if(glc !is null) {
15114 				glXDestroyContext(display, glc);
15115 				glc = null;
15116 			}
15117 
15118 			if(buffer)
15119 				XFreePixmap(display, buffer);
15120 			bufferw = bufferh = 0;
15121 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15122 			XDestroyWindow(display, window);
15123 			XFlush(display);
15124 		}
15125 
15126 		void dispose() {
15127 		}
15128 
15129 		bool destroyed = false;
15130 	}
15131 
15132 	bool insideXEventLoop;
15133 }
15134 
15135 version(X11) {
15136 
15137 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15138 
15139 	private class ResizeEvent {
15140 		int width, height;
15141 	}
15142 
15143 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15144 		if(win.windowType == WindowTypes.minimallyWrapped)
15145 			return;
15146 
15147 		if(win.pendingResizeEvent is null) {
15148 			win.pendingResizeEvent = new ResizeEvent();
15149 			win.addEventListener((ResizeEvent re) {
15150 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15151 			});
15152 		}
15153 		win.pendingResizeEvent.width = width;
15154 		win.pendingResizeEvent.height = height;
15155 		if(!win.eventQueued!ResizeEvent) {
15156 			win.postEvent(win.pendingResizeEvent);
15157 		}
15158 	}
15159 
15160 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15161 		if(win.windowType == WindowTypes.minimallyWrapped)
15162 			return;
15163 		if(win.closed)
15164 			return;
15165 
15166 		if(width != win.width || height != win.height) {
15167 
15168 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15169 			win._width = width;
15170 			win._height = height;
15171 
15172 			if(win.openglMode == OpenGlOptions.no) {
15173 				// FIXME: could this be more efficient?
15174 
15175 				if (win.bufferw < width || win.bufferh < height) {
15176 					//{ 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); }
15177 					// grow the internal buffer to match the window...
15178 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15179 					{
15180 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15181 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15182 						scope(exit) XFreeGC(win.display, xgc);
15183 						XSetClipMask(win.display, xgc, None);
15184 						XSetForeground(win.display, xgc, 0);
15185 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15186 					}
15187 					XCopyArea(display,
15188 						cast(Drawable) win.buffer,
15189 						cast(Drawable) newPixmap,
15190 						win.gc, 0, 0,
15191 						win.bufferw < width ? win.bufferw : win.width,
15192 						win.bufferh < height ? win.bufferh : win.height,
15193 						0, 0);
15194 
15195 					XFreePixmap(display, win.buffer);
15196 					win.buffer = newPixmap;
15197 					win.bufferw = width;
15198 					win.bufferh = height;
15199 				}
15200 
15201 				// clear unused parts of the buffer
15202 				if (win.bufferw > width || win.bufferh > height) {
15203 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15204 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15205 					scope(exit) XFreeGC(win.display, xgc);
15206 					XSetClipMask(win.display, xgc, None);
15207 					XSetForeground(win.display, xgc, 0);
15208 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15209 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15210 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15211 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15212 				}
15213 
15214 			}
15215 
15216 			win.updateOpenglViewportIfNeeded(width, height);
15217 
15218 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15219 
15220 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15221 			if(win.windowResized !is null) {
15222 				XUnlockDisplay(display);
15223 				scope(exit) XLockDisplay(display);
15224 				win.windowResized(width, height);
15225 			}
15226 		}
15227 	}
15228 
15229 
15230 	/// Platform-specific, you might use it when doing a custom event loop.
15231 	bool doXNextEvent(Display* display) {
15232 		bool done;
15233 		XEvent e;
15234 		XNextEvent(display, &e);
15235 		version(sddddd) {
15236 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15237 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15238 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15239 			}
15240 		}
15241 
15242 		// filter out compose events
15243 		if (XFilterEvent(&e, None)) {
15244 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15245 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15246 			return false;
15247 		}
15248 		// process keyboard mapping changes
15249 		if (e.type == EventType.KeymapNotify) {
15250 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15251 			XRefreshKeyboardMapping(&e.xmapping);
15252 			return false;
15253 		}
15254 
15255 		version(with_eventloop)
15256 			import arsd.eventloop;
15257 
15258 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15259 			// see windows impl's comments
15260 			XUnlockDisplay(display);
15261 			scope(exit) XLockDisplay(display);
15262 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15263 			if(ret == 0)
15264 				return done;
15265 		}
15266 
15267 
15268 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15269 			if(win.getNativeEventHandler !is null) {
15270 				XUnlockDisplay(display);
15271 				scope(exit) XLockDisplay(display);
15272 				auto ret = win.getNativeEventHandler()(e);
15273 				if(ret == 0)
15274 					return done;
15275 			}
15276 		}
15277 
15278 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15279 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15280 				// we get this because of the RRScreenChangeNotifyMask
15281 
15282 				// this isn't actually an ideal way to do it since it wastes time
15283 				// but meh it is simple and it works.
15284 				win.actualDpiLoadAttempted = false;
15285 				SimpleWindow.xRandrInfoLoadAttemped = false;
15286 				win.updateActualDpi(); // trigger a reload
15287 			}
15288 		}
15289 
15290 		switch(e.type) {
15291 		  case EventType.SelectionClear:
15292 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15293 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15294 				// writeln("SelectionClear");
15295 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15296 			}
15297 		  break;
15298 		  case EventType.SelectionRequest:
15299 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15300 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15301 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15302 				XUnlockDisplay(display);
15303 				scope(exit) XLockDisplay(display);
15304 				(*ssh).handleRequest(e);
15305 			}
15306 		  break;
15307 		  case EventType.PropertyNotify:
15308 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15309 
15310 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15311 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15312 					ssh.sendMoreIncr(&e.xproperty);
15313 			}
15314 
15315 
15316 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15317 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15318 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15319 					Atom target;
15320 					int format;
15321 					arch_ulong bytesafter, length;
15322 					void* value;
15323 
15324 					ubyte[] s;
15325 					Atom targetToKeep;
15326 
15327 					XGetWindowProperty(
15328 						e.xproperty.display,
15329 						e.xproperty.window,
15330 						e.xproperty.atom,
15331 						0,
15332 						100000 /* length */,
15333 						true, /* erase it to signal we got it and want more */
15334 						0 /*AnyPropertyType*/,
15335 						&target, &format, &length, &bytesafter, &value);
15336 
15337 					if(!targetToKeep)
15338 						targetToKeep = target;
15339 
15340 					auto id = (cast(ubyte*) value)[0 .. length];
15341 
15342 					handler.handleIncrData(targetToKeep, id);
15343 
15344 					XFree(value);
15345 				}
15346 			}
15347 		  break;
15348 		  case EventType.SelectionNotify:
15349 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15350 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15351 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15352 					XUnlockDisplay(display);
15353 					scope(exit) XLockDisplay(display);
15354 					handler.handleData(None, null);
15355 				} else {
15356 					Atom target;
15357 					int format;
15358 					arch_ulong bytesafter, length;
15359 					void* value;
15360 					XGetWindowProperty(
15361 						e.xselection.display,
15362 						e.xselection.requestor,
15363 						e.xselection.property,
15364 						0,
15365 						100000 /* length */,
15366 						//false, /* don't erase it */
15367 						true, /* do erase it lol */
15368 						0 /*AnyPropertyType*/,
15369 						&target, &format, &length, &bytesafter, &value);
15370 
15371 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15372 
15373 					{
15374 						XUnlockDisplay(display);
15375 						scope(exit) XLockDisplay(display);
15376 
15377 						if(target == XA_ATOM) {
15378 							// initial request, see what they are able to work with and request the best one
15379 							// we can handle, if available
15380 
15381 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15382 							Atom best = handler.findBestFormat(answer);
15383 
15384 							/+
15385 							writeln("got ", answer);
15386 							foreach(a; answer)
15387 								printf("%s\n", XGetAtomName(display, a));
15388 							writeln("best ", best);
15389 							+/
15390 
15391 							if(best != None) {
15392 								// actually request the best format
15393 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15394 							}
15395 						} else if(target == GetAtom!"INCR"(display)) {
15396 							// incremental
15397 
15398 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15399 
15400 							// signal the sending program that we see
15401 							// the incr and are ready to receive more.
15402 							XDeleteProperty(
15403 								e.xselection.display,
15404 								e.xselection.requestor,
15405 								e.xselection.property);
15406 						} else {
15407 							// unsupported type... maybe, forward
15408 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15409 						}
15410 					}
15411 					XFree(value);
15412 					/*
15413 					XDeleteProperty(
15414 						e.xselection.display,
15415 						e.xselection.requestor,
15416 						e.xselection.property);
15417 					*/
15418 				}
15419 			}
15420 		  break;
15421 		  case EventType.ConfigureNotify:
15422 			auto event = e.xconfigure;
15423 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15424 				if(win.windowType == WindowTypes.minimallyWrapped)
15425 					break;
15426 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15427 
15428 				/+
15429 					The ICCCM says window managers must send a synthetic event when the window
15430 					is moved but NOT when it is resized. In the resize case, an event is sent
15431 					with position (0, 0) which can be wrong and break the dpi calculations.
15432 
15433 					So we only consider the synthetic events from the WM and otherwise
15434 					need to wait for some other event to get the position which... sucks.
15435 
15436 					I'd rather not have windows changing their layout on mouse motion after
15437 					switching monitors... might be forced to but for now just ignoring it.
15438 
15439 					Easiest way to switch monitors without sending a size position is by
15440 					maximize or fullscreen in a setup like mine, but on most setups those
15441 					work on the monitor it is already living on, so it should be ok most the
15442 					time.
15443 				+/
15444 				if(event.send_event) {
15445 					win.screenPositionKnown = true;
15446 					win.screenPositionX = event.x;
15447 					win.screenPositionY = event.y;
15448 					win.updateActualDpi();
15449 				}
15450 
15451 				win.updateIMEPopupLocation();
15452 				recordX11ResizeAsync(display, *win, event.width, event.height);
15453 			}
15454 		  break;
15455 		  case EventType.Expose:
15456 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15457 				if(win.windowType == WindowTypes.minimallyWrapped)
15458 					break;
15459 				// if it is closing from a popup menu, it can get
15460 				// an Expose event right by the end and trigger a
15461 				// BadDrawable error ... we'll just check
15462 				// closed to handle that.
15463 				if((*win).closed) break;
15464 				if((*win).openglMode == OpenGlOptions.no) {
15465 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15466 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15467 					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);
15468 				} else {
15469 					// need to redraw the scene somehow
15470 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15471 						XUnlockDisplay(display);
15472 						scope(exit) XLockDisplay(display);
15473 						version(without_opengl) {} else
15474 						win.redrawOpenGlSceneSoon();
15475 					}
15476 				}
15477 			}
15478 		  break;
15479 		  case EventType.FocusIn:
15480 		  case EventType.FocusOut:
15481 
15482 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15483 				/+
15484 
15485 				void info(string detail) {
15486 					string s;
15487 					// import std.conv;
15488 					// import std.datetime;
15489 					s ~= to!string(Clock.currTime);
15490 					s ~= " ";
15491 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15492 					s ~= " ";
15493 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15494 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15495 					s ~= detail;
15496 					s ~= " ";
15497 
15498 					sdpyPrintDebugString(s);
15499 
15500 				}
15501 
15502 				switch(e.xfocus.detail) {
15503 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15504 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15505 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15506 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15507 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15508 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15509 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15510 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15511 					default:
15512 
15513 				}
15514 				+/
15515 
15516 
15517 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15518 					break; // just ignore these they seem irrelevant
15519 
15520 				auto old = win._focused;
15521 				win._focused = e.type == EventType.FocusIn;
15522 
15523 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15524 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15525 					win._focused = true;
15526 
15527 				if(win.demandingAttention)
15528 					demandAttention(*win, false);
15529 
15530 				win.updateIMEFocused();
15531 
15532 				if(old != win._focused && win.onFocusChange) {
15533 					XUnlockDisplay(display);
15534 					scope(exit) XLockDisplay(display);
15535 					win.onFocusChange(win._focused);
15536 				}
15537 			}
15538 		  break;
15539 		  case EventType.VisibilityNotify:
15540 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15541 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15542 						if (win.visibilityChanged !is null) {
15543 								XUnlockDisplay(display);
15544 								scope(exit) XLockDisplay(display);
15545 								win.visibilityChanged(false);
15546 							}
15547 					} else {
15548 						if (win.visibilityChanged !is null) {
15549 							XUnlockDisplay(display);
15550 							scope(exit) XLockDisplay(display);
15551 							win.visibilityChanged(true);
15552 						}
15553 					}
15554 				}
15555 				break;
15556 		  case EventType.ClientMessage:
15557 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15558 					// "ignore next mouse motion" event, increment ignore counter for teh window
15559 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15560 						++(*win).warpEventCount;
15561 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15562 					} else {
15563 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15564 					}
15565 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15566 					// user clicked the close button on the window manager
15567 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15568 						XUnlockDisplay(display);
15569 						scope(exit) XLockDisplay(display);
15570 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15571 					}
15572 
15573 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15574 					// writeln("HAPPENED");
15575 					// user clicked the close button on the window manager
15576 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15577 						XUnlockDisplay(display);
15578 						scope(exit) XLockDisplay(display);
15579 
15580 						auto setTo = *win;
15581 
15582 						if(win.setRequestedInputFocus !is null) {
15583 							auto s = win.setRequestedInputFocus();
15584 							if(s !is null) {
15585 								setTo = s;
15586 							}
15587 						}
15588 
15589 						assert(setTo !is null);
15590 
15591 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15592 
15593 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15594 					}
15595 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15596 					foreach(nai; NotificationAreaIcon.activeIcons)
15597 						nai.newManager();
15598 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15599 
15600 					bool xDragWindow = true;
15601 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15602 						//XDefineCursor(display, xDragWindow.impl.window,
15603 							//writeln("XdndStatus ", e.xclient.data.l);
15604 					}
15605 					if(auto dh = win.dropHandler) {
15606 
15607 						static Atom[3] xFormatsBuffer;
15608 						static Atom[] xFormats;
15609 
15610 						void resetXFormats() {
15611 							xFormatsBuffer[] = 0;
15612 							xFormats = xFormatsBuffer[];
15613 						}
15614 
15615 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15616 							// on Windows it is supposed to return the effect you actually do FIXME
15617 
15618 							auto sourceWindow =  e.xclient.data.l[0];
15619 
15620 							xFormatsBuffer[0] = e.xclient.data.l[2];
15621 							xFormatsBuffer[1] = e.xclient.data.l[3];
15622 							xFormatsBuffer[2] = e.xclient.data.l[4];
15623 
15624 							if(e.xclient.data.l[1] & 1) {
15625 								// can just grab it all but like we don't necessarily need them...
15626 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15627 							} else {
15628 								int len;
15629 								foreach(fmt; xFormatsBuffer)
15630 									if(fmt) len++;
15631 								xFormats = xFormatsBuffer[0 .. len];
15632 							}
15633 
15634 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15635 
15636 							dh.dragEnter(&pkg);
15637 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15638 
15639 							auto pack = e.xclient.data.l[2];
15640 
15641 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15642 
15643 
15644 							XClientMessageEvent xclient;
15645 
15646 							xclient.type = EventType.ClientMessage;
15647 							xclient.window = e.xclient.data.l[0];
15648 							xclient.message_type = GetAtom!"XdndStatus"(display);
15649 							xclient.format = 32;
15650 							xclient.data.l[0] = win.impl.window;
15651 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15652 							auto r = result.consistentWithin;
15653 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15654 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15655 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15656 
15657 							XSendEvent(
15658 								display,
15659 								e.xclient.data.l[0],
15660 								false,
15661 								EventMask.NoEventMask,
15662 								cast(XEvent*) &xclient
15663 							);
15664 
15665 
15666 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15667 							//writeln("XdndLeave");
15668 							// drop cancelled.
15669 							// data.l[0] is the source window
15670 							dh.dragLeave();
15671 
15672 							resetXFormats();
15673 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15674 							// drop happening, should fetch data, then send finished
15675 							// writeln("XdndDrop");
15676 
15677 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15678 
15679 							dh.drop(&pkg);
15680 
15681 							resetXFormats();
15682 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15683 							// writeln("XdndFinished");
15684 
15685 							dh.finish();
15686 						}
15687 
15688 					}
15689 				}
15690 		  break;
15691 		  case EventType.MapNotify:
15692 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15693 					(*win)._visible = true;
15694 					if (!(*win)._visibleForTheFirstTimeCalled) {
15695 						(*win)._visibleForTheFirstTimeCalled = true;
15696 						if ((*win).visibleForTheFirstTime !is null) {
15697 							XUnlockDisplay(display);
15698 							scope(exit) XLockDisplay(display);
15699 							(*win).visibleForTheFirstTime();
15700 						}
15701 					}
15702 					if ((*win).visibilityChanged !is null) {
15703 						XUnlockDisplay(display);
15704 						scope(exit) XLockDisplay(display);
15705 						(*win).visibilityChanged(true);
15706 					}
15707 				}
15708 		  break;
15709 		  case EventType.UnmapNotify:
15710 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15711 					win._visible = false;
15712 					if (win.visibilityChanged !is null) {
15713 						XUnlockDisplay(display);
15714 						scope(exit) XLockDisplay(display);
15715 						win.visibilityChanged(false);
15716 					}
15717 			}
15718 		  break;
15719 		  case EventType.DestroyNotify:
15720 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15721 				if(win.destroyed)
15722 					break; // might get a notification both for itself and from its parent
15723 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15724 				win._closed = true; // just in case
15725 				win.destroyed = true;
15726 				if (win.xic !is null) {
15727 					XDestroyIC(win.xic);
15728 					win.xic = null; // just in case
15729 				}
15730 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15731 				bool anyImportant = false;
15732 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15733 					if(w.beingOpenKeepsAppOpen) {
15734 						anyImportant = true;
15735 						break;
15736 					}
15737 				if(!anyImportant) {
15738 					EventLoop.quitApplication();
15739 					done = true;
15740 				}
15741 			}
15742 			auto window = e.xdestroywindow.window;
15743 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15744 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15745 
15746 			version(with_eventloop) {
15747 				if(done) exit();
15748 			}
15749 		  break;
15750 
15751 		  case EventType.MotionNotify:
15752 			MouseEvent mouse;
15753 			auto event = e.xmotion;
15754 
15755 			mouse.type = MouseEventType.motion;
15756 			mouse.x = event.x;
15757 			mouse.y = event.y;
15758 			mouse.modifierState = event.state;
15759 
15760 			mouse.timestamp = event.time;
15761 
15762 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15763 				mouse.window = *win;
15764 				if (win.warpEventCount > 0) {
15765 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15766 					--(*win).warpEventCount;
15767 					(*win).mdx(mouse); // so deltas will be correctly updated
15768 				} else {
15769 					win.warpEventCount = 0; // just in case
15770 					(*win).mdx(mouse);
15771 					if((*win).handleMouseEvent) {
15772 						XUnlockDisplay(display);
15773 						scope(exit) XLockDisplay(display);
15774 						(*win).handleMouseEvent(mouse);
15775 					}
15776 				}
15777 			}
15778 
15779 		  	version(with_eventloop)
15780 				send(mouse);
15781 		  break;
15782 		  case EventType.ButtonPress:
15783 		  case EventType.ButtonRelease:
15784 			MouseEvent mouse;
15785 			auto event = e.xbutton;
15786 
15787 			mouse.timestamp = event.time;
15788 
15789 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15790 			mouse.x = event.x;
15791 			mouse.y = event.y;
15792 
15793 			static Time lastMouseDownTime = 0;
15794 			static int lastMouseDownButton = -1;
15795 
15796 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15797 			if(e.type == EventType.ButtonPress) {
15798 				lastMouseDownTime = event.time;
15799 				lastMouseDownButton = event.button;
15800 			}
15801 
15802 			switch(event.button) {
15803 				case 1: mouse.button = MouseButton.left; break; // left
15804 				case 2: mouse.button = MouseButton.middle; break; // middle
15805 				case 3: mouse.button = MouseButton.right; break; // right
15806 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15807 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15808 				case 6: break; // idk
15809 				case 7: break; // idk
15810 				case 8: mouse.button = MouseButton.backButton; break;
15811 				case 9: mouse.button = MouseButton.forwardButton; break;
15812 				default:
15813 			}
15814 
15815 			// FIXME: double check this
15816 			mouse.modifierState = event.state;
15817 
15818 			//mouse.modifierState = event.detail;
15819 
15820 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15821 				mouse.window = *win;
15822 				(*win).mdx(mouse);
15823 				if((*win).handleMouseEvent) {
15824 					XUnlockDisplay(display);
15825 					scope(exit) XLockDisplay(display);
15826 					(*win).handleMouseEvent(mouse);
15827 				}
15828 			}
15829 			version(with_eventloop)
15830 				send(mouse);
15831 		  break;
15832 
15833 		  case EventType.KeyPress:
15834 		  case EventType.KeyRelease:
15835 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15836 			KeyEvent ke;
15837 			ke.pressed = e.type == EventType.KeyPress;
15838 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15839 
15840 			auto sym = XKeycodeToKeysym(
15841 				XDisplayConnection.get(),
15842 				e.xkey.keycode,
15843 				0);
15844 
15845 			ke.key = cast(Key) sym;//e.xkey.keycode;
15846 
15847 			ke.modifierState = e.xkey.state;
15848 
15849 			// writefln("%x", sym);
15850 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15851 			int charbuflen = 0; // return value of XwcLookupString
15852 			if (ke.pressed) {
15853 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15854 				if (win !is null && win.xic !is null) {
15855 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15856 					Status status;
15857 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15858 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15859 				} else {
15860 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15861 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15862 					char[16] buffer;
15863 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15864 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15865 				}
15866 			}
15867 
15868 			// if there's no char, subst one
15869 			if (charbuflen == 0) {
15870 				switch (sym) {
15871 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15872 					case 0xff8d: // keypad enter
15873 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15874 					default : // ignore
15875 				}
15876 			}
15877 
15878 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15879 				ke.window = *win;
15880 
15881 
15882 				if(win.inputProxy)
15883 					win = &win.inputProxy;
15884 
15885 				// char events are separate since they are on Windows too
15886 				// also, xcompose can generate long char sequences
15887 				// don't send char events if Meta and/or Hyper is pressed
15888 				// TODO: ctrl+char should only send control chars; not yet
15889 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15890 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15891 				}
15892 
15893 				dchar[32] charsComingBuffer;
15894 				int charsComingPosition;
15895 				dchar[] charsComing = charsComingBuffer[];
15896 
15897 				if (ke.pressed && charbuflen > 0) {
15898 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15899 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15900 						if(charsComingPosition >= charsComing.length)
15901 							charsComing.length = charsComingPosition + 8;
15902 
15903 						charsComing[charsComingPosition++] = ch;
15904 					}
15905 
15906 					charsComing = charsComing[0 .. charsComingPosition];
15907 				} else {
15908 					charsComing = null;
15909 				}
15910 
15911 				ke.charsPossible = charsComing;
15912 
15913 				if (win.handleKeyEvent) {
15914 					XUnlockDisplay(display);
15915 					scope(exit) XLockDisplay(display);
15916 					win.handleKeyEvent(ke);
15917 				}
15918 
15919 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15920 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15921 					XUnlockDisplay(display);
15922 					scope(exit) XLockDisplay(display);
15923 					foreach(ch; charsComing)
15924 						win.handleCharEvent(ch);
15925 				}
15926 			}
15927 
15928 			version(with_eventloop)
15929 				send(ke);
15930 		  break;
15931 		  default:
15932 		}
15933 
15934 		return done;
15935 	}
15936 }
15937 
15938 /* *************************************** */
15939 /*      Done with simpledisplay stuff      */
15940 /* *************************************** */
15941 
15942 // Necessary C library bindings follow
15943 version(Windows) {} else
15944 version(X11) {
15945 
15946 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15947 
15948 // X11 bindings needed here
15949 /*
15950 	A little of this is from the bindings project on
15951 	D Source and some of it is copy/paste from the C
15952 	header.
15953 
15954 	The DSource listing consistently used D's long
15955 	where C used long. That's wrong - C long is 32 bit, so
15956 	it should be int in D. I changed that here.
15957 
15958 	Note:
15959 	This isn't complete, just took what I needed for myself.
15960 */
15961 
15962 import core.stdc.stddef : wchar_t;
15963 
15964 interface XLib {
15965 extern(C) nothrow @nogc {
15966 	char* XResourceManagerString(Display*);
15967 	void XrmInitialize();
15968 	XrmDatabase XrmGetStringDatabase(char* data);
15969 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15970 
15971 	Cursor XCreateFontCursor(Display*, uint shape);
15972 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15973 	int XUndefineCursor(Display* display, Window w);
15974 
15975 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15976 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15977 	int XFreeCursor(Display* display, Cursor cursor);
15978 
15979 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
15980 
15981 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
15982 
15983 	XVaNestedList XVaCreateNestedList(int unused, ...);
15984 
15985 	char *XKeysymToString(KeySym keysym);
15986 	KeySym XKeycodeToKeysym(
15987 		Display*		/* display */,
15988 		KeyCode		/* keycode */,
15989 		int			/* index */
15990 	);
15991 
15992 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
15993 
15994 	int XFree(void*);
15995 	int XDeleteProperty(Display *display, Window w, Atom property);
15996 
15997 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
15998 
15999 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16000 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16001 		*actual_type_return, int *actual_format_return, arch_ulong
16002 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16003 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16004 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16005 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16006 
16007 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16008 
16009 	Window XGetSelectionOwner(Display *display, Atom selection);
16010 
16011 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16012 
16013 	char** XListFonts(Display*, const char*, int, int*);
16014 	void XFreeFontNames(char**);
16015 
16016 	Display* XOpenDisplay(const char*);
16017 	int XCloseDisplay(Display*);
16018 
16019 	int function() XSynchronize(Display*, bool);
16020 	int function() XSetAfterFunction(Display*, int function() proc);
16021 
16022 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16023 
16024 	Bool XSupportsLocale();
16025 	char* XSetLocaleModifiers(const(char)* modifier_list);
16026 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16027 	Status XCloseOM(XOM om);
16028 
16029 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16030 	Status XCloseIM(XIM im);
16031 
16032 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16033 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16034 	Display* XDisplayOfIM(XIM im);
16035 	char* XLocaleOfIM(XIM im);
16036 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16037 	void XDestroyIC(XIC ic);
16038 	void XSetICFocus(XIC ic);
16039 	void XUnsetICFocus(XIC ic);
16040 	//wchar_t* XwcResetIC(XIC ic);
16041 	char* XmbResetIC(XIC ic);
16042 	char* Xutf8ResetIC(XIC ic);
16043 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16044 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16045 	XIM XIMOfIC(XIC ic);
16046 
16047 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16048 
16049 
16050 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16051 	int XFreeFont(Display *display, XFontStruct *font_struct);
16052 	int XSetFont(Display* display, GC gc, Font font);
16053 	int XTextWidth(XFontStruct*, scope const char*, int);
16054 
16055 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16056 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16057 
16058 	Window XCreateSimpleWindow(
16059 		Display*	/* display */,
16060 		Window		/* parent */,
16061 		int			/* x */,
16062 		int			/* y */,
16063 		uint		/* width */,
16064 		uint		/* height */,
16065 		uint		/* border_width */,
16066 		uint		/* border */,
16067 		uint		/* background */
16068 	);
16069 	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);
16070 
16071 	int XReparentWindow(Display*, Window, Window, int, int);
16072 	int XClearWindow(Display*, Window);
16073 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16074 	int XMoveWindow(Display*, Window, int, int);
16075 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16076 
16077 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16078 
16079 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16080 
16081 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16082 
16083 	XImage *XCreateImage(
16084 		Display*		/* display */,
16085 		Visual*		/* visual */,
16086 		uint	/* depth */,
16087 		int			/* format */,
16088 		int			/* offset */,
16089 		ubyte*		/* data */,
16090 		uint	/* width */,
16091 		uint	/* height */,
16092 		int			/* bitmap_pad */,
16093 		int			/* bytes_per_line */
16094 	);
16095 
16096 	Status XInitImage (XImage* image);
16097 
16098 	Atom XInternAtom(
16099 		Display*		/* display */,
16100 		const char*	/* atom_name */,
16101 		Bool		/* only_if_exists */
16102 	);
16103 
16104 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16105 	char* XGetAtomName(Display*, Atom);
16106 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16107 
16108 	int XPutImage(
16109 		Display*	/* display */,
16110 		Drawable	/* d */,
16111 		GC			/* gc */,
16112 		XImage*	/* image */,
16113 		int			/* src_x */,
16114 		int			/* src_y */,
16115 		int			/* dest_x */,
16116 		int			/* dest_y */,
16117 		uint		/* width */,
16118 		uint		/* height */
16119 	);
16120 
16121 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16122 
16123 
16124 	int XDestroyWindow(
16125 		Display*	/* display */,
16126 		Window		/* w */
16127 	);
16128 
16129 	int XDestroyImage(XImage*);
16130 
16131 	int XSelectInput(
16132 		Display*	/* display */,
16133 		Window		/* w */,
16134 		EventMask	/* event_mask */
16135 	);
16136 
16137 	int XMapWindow(
16138 		Display*	/* display */,
16139 		Window		/* w */
16140 	);
16141 
16142 	Status XIconifyWindow(Display*, Window, int);
16143 	int XMapRaised(Display*, Window);
16144 	int XMapSubwindows(Display*, Window);
16145 
16146 	int XNextEvent(
16147 		Display*	/* display */,
16148 		XEvent*		/* event_return */
16149 	);
16150 
16151 	int XMaskEvent(Display*, arch_long, XEvent*);
16152 
16153 	Bool XFilterEvent(XEvent *event, Window window);
16154 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16155 
16156 	Status XSetWMProtocols(
16157 		Display*	/* display */,
16158 		Window		/* w */,
16159 		Atom*		/* protocols */,
16160 		int			/* count */
16161 	);
16162 
16163 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16164 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16165 
16166 
16167 	Status XInitThreads();
16168 	void XLockDisplay (Display* display);
16169 	void XUnlockDisplay (Display* display);
16170 
16171 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16172 
16173 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16174 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16175 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16176 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16177 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16178 
16179 
16180 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16181 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16182 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16183 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16184 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16185 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16186 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16187 	int XDrawPoint(Display*, Drawable, GC, int, int);
16188 	int XSetForeground(Display*, GC, uint);
16189 	int XSetBackground(Display*, GC, uint);
16190 
16191 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16192 	void XFreeFontSet(Display*, XFontSet);
16193 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16194 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16195 
16196 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16197 
16198 
16199 //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);
16200 
16201 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16202 	int XSetFunction(Display*, GC, int);
16203 
16204 	GC XCreateGC(Display*, Drawable, uint, void*);
16205 	int XCopyGC(Display*, GC, uint, GC);
16206 	int XFreeGC(Display*, GC);
16207 
16208 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16209 	bool XCheckMaskEvent(Display*, int, XEvent*);
16210 
16211 	int XPending(Display*);
16212 	int XEventsQueued(Display* display, int mode);
16213 
16214 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16215 	int XFreePixmap(Display*, Pixmap);
16216 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16217 	int XFlush(Display*);
16218 	int XBell(Display*, int);
16219 	int XSync(Display*, bool);
16220 
16221 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16222 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16223 
16224 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16225 	int XUngrabKeyboard(Display*, Time);
16226 
16227 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16228 
16229 	KeySym XStringToKeysym(const char *string);
16230 
16231 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16232 
16233 	Window XDefaultRootWindow(Display*);
16234 
16235 	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);
16236 
16237 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16238 
16239 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16240 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16241 
16242 	Status XAllocColor(Display*, Colormap, XColor*);
16243 
16244 	int XWithdrawWindow(Display*, Window, int);
16245 	int XUnmapWindow(Display*, Window);
16246 	int XLowerWindow(Display*, Window);
16247 	int XRaiseWindow(Display*, Window);
16248 
16249 	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);
16250 	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);
16251 
16252 	int XGetInputFocus(Display*, Window*, int*);
16253 	int XSetInputFocus(Display*, Window, int, Time);
16254 
16255 	XErrorHandler XSetErrorHandler(XErrorHandler);
16256 
16257 	int XGetErrorText(Display*, int, char*, int);
16258 
16259 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16260 
16261 
16262 	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);
16263 	int XUngrabPointer(Display *display, Time time);
16264 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16265 
16266 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16267 
16268 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16269 	int XSetClipMask(Display*, GC, Pixmap);
16270 	int XSetClipOrigin(Display*, GC, int, int);
16271 
16272 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16273 
16274 	void XSetWMName(Display*, Window, XTextProperty*);
16275 	Status XGetWMName(Display*, Window, XTextProperty*);
16276 	int XStoreName(Display* display, Window w, const(char)* window_name);
16277 
16278 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16279 
16280 }
16281 }
16282 
16283 interface Xext {
16284 extern(C) nothrow @nogc {
16285 	Status XShmAttach(Display*, XShmSegmentInfo*);
16286 	Status XShmDetach(Display*, XShmSegmentInfo*);
16287 	Status XShmPutImage(
16288 		Display*            /* dpy */,
16289 		Drawable            /* d */,
16290 		GC                  /* gc */,
16291 		XImage*             /* image */,
16292 		int                 /* src_x */,
16293 		int                 /* src_y */,
16294 		int                 /* dst_x */,
16295 		int                 /* dst_y */,
16296 		uint        /* src_width */,
16297 		uint        /* src_height */,
16298 		Bool                /* send_event */
16299 	);
16300 
16301 	Status XShmQueryExtension(Display*);
16302 
16303 	XImage *XShmCreateImage(
16304 		Display*            /* dpy */,
16305 		Visual*             /* visual */,
16306 		uint        /* depth */,
16307 		int                 /* format */,
16308 		char*               /* data */,
16309 		XShmSegmentInfo*    /* shminfo */,
16310 		uint        /* width */,
16311 		uint        /* height */
16312 	);
16313 
16314 	Pixmap XShmCreatePixmap(
16315 		Display*            /* dpy */,
16316 		Drawable            /* d */,
16317 		char*               /* data */,
16318 		XShmSegmentInfo*    /* shminfo */,
16319 		uint        /* width */,
16320 		uint        /* height */,
16321 		uint        /* depth */
16322 	);
16323 
16324 }
16325 }
16326 
16327 	// this requires -lXpm
16328 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16329 
16330 
16331 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16332 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16333 shared static this() {
16334 	xlib.loadDynamicLibrary();
16335 	xext.loadDynamicLibrary();
16336 }
16337 
16338 
16339 extern(C) nothrow @nogc {
16340 
16341 alias XrmDatabase = void*;
16342 struct XrmValue {
16343 	uint size;
16344 	void* addr;
16345 }
16346 
16347 struct XVisualInfo {
16348 	Visual* visual;
16349 	VisualID visualid;
16350 	int screen;
16351 	uint depth;
16352 	int c_class;
16353 	c_ulong red_mask;
16354 	c_ulong green_mask;
16355 	c_ulong blue_mask;
16356 	int colormap_size;
16357 	int bits_per_rgb;
16358 }
16359 
16360 enum VisualNoMask=	0x0;
16361 enum VisualIDMask=	0x1;
16362 enum VisualScreenMask=0x2;
16363 enum VisualDepthMask=	0x4;
16364 enum VisualClassMask=	0x8;
16365 enum VisualRedMaskMask=0x10;
16366 enum VisualGreenMaskMask=0x20;
16367 enum VisualBlueMaskMask=0x40;
16368 enum VisualColormapSizeMask=0x80;
16369 enum VisualBitsPerRGBMask=0x100;
16370 enum VisualAllMask=	0x1FF;
16371 
16372 enum AnyKey = 0;
16373 enum AnyModifier = 1 << 15;
16374 
16375 // XIM and other crap
16376 struct _XOM {}
16377 struct _XIM {}
16378 struct _XIC {}
16379 alias XOM = _XOM*;
16380 alias XIM = _XIM*;
16381 alias XIC = _XIC*;
16382 
16383 alias XVaNestedList = void*;
16384 
16385 alias XIMStyle = arch_ulong;
16386 enum : arch_ulong {
16387 	XIMPreeditArea      = 0x0001,
16388 	XIMPreeditCallbacks = 0x0002,
16389 	XIMPreeditPosition  = 0x0004,
16390 	XIMPreeditNothing   = 0x0008,
16391 	XIMPreeditNone      = 0x0010,
16392 	XIMStatusArea       = 0x0100,
16393 	XIMStatusCallbacks  = 0x0200,
16394 	XIMStatusNothing    = 0x0400,
16395 	XIMStatusNone       = 0x0800,
16396 }
16397 
16398 
16399 /* X Shared Memory Extension functions */
16400 	//pragma(lib, "Xshm");
16401 	alias arch_ulong ShmSeg;
16402 	struct XShmSegmentInfo {
16403 		ShmSeg shmseg;
16404 		int shmid;
16405 		ubyte* shmaddr;
16406 		Bool readOnly;
16407 	}
16408 
16409 	// and the necessary OS functions
16410 	int shmget(int, size_t, int);
16411 	void* shmat(int, scope const void*, int);
16412 	int shmdt(scope const void*);
16413 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16414 
16415 	enum IPC_PRIVATE = 0;
16416 	enum IPC_CREAT = 512;
16417 	enum IPC_RMID = 0;
16418 
16419 /* MIT-SHM end */
16420 
16421 
16422 enum MappingType:int {
16423 	MappingModifier		=0,
16424 	MappingKeyboard		=1,
16425 	MappingPointer		=2
16426 }
16427 
16428 /* ImageFormat -- PutImage, GetImage */
16429 enum ImageFormat:int {
16430 	XYBitmap	=0,	/* depth 1, XYFormat */
16431 	XYPixmap	=1,	/* depth == drawable depth */
16432 	ZPixmap	=2	/* depth == drawable depth */
16433 }
16434 
16435 enum ModifierName:int {
16436 	ShiftMapIndex	=0,
16437 	LockMapIndex	=1,
16438 	ControlMapIndex	=2,
16439 	Mod1MapIndex	=3,
16440 	Mod2MapIndex	=4,
16441 	Mod3MapIndex	=5,
16442 	Mod4MapIndex	=6,
16443 	Mod5MapIndex	=7
16444 }
16445 
16446 enum ButtonMask:int {
16447 	Button1Mask	=1<<8,
16448 	Button2Mask	=1<<9,
16449 	Button3Mask	=1<<10,
16450 	Button4Mask	=1<<11,
16451 	Button5Mask	=1<<12,
16452 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16453 }
16454 
16455 enum KeyOrButtonMask:uint {
16456 	ShiftMask	=1<<0,
16457 	LockMask	=1<<1,
16458 	ControlMask	=1<<2,
16459 	Mod1Mask	=1<<3,
16460 	Mod2Mask	=1<<4,
16461 	Mod3Mask	=1<<5,
16462 	Mod4Mask	=1<<6,
16463 	Mod5Mask	=1<<7,
16464 	Button1Mask	=1<<8,
16465 	Button2Mask	=1<<9,
16466 	Button3Mask	=1<<10,
16467 	Button4Mask	=1<<11,
16468 	Button5Mask	=1<<12,
16469 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16470 }
16471 
16472 enum ButtonName:int {
16473 	Button1	=1,
16474 	Button2	=2,
16475 	Button3	=3,
16476 	Button4	=4,
16477 	Button5	=5
16478 }
16479 
16480 /* Notify modes */
16481 enum NotifyModes:int
16482 {
16483 	NotifyNormal		=0,
16484 	NotifyGrab			=1,
16485 	NotifyUngrab		=2,
16486 	NotifyWhileGrabbed	=3
16487 }
16488 enum NotifyHint = 1;	/* for MotionNotify events */
16489 
16490 /* Notify detail */
16491 enum NotifyDetail:int
16492 {
16493 	NotifyAncestor			=0,
16494 	NotifyVirtual			=1,
16495 	NotifyInferior			=2,
16496 	NotifyNonlinear			=3,
16497 	NotifyNonlinearVirtual	=4,
16498 	NotifyPointer			=5,
16499 	NotifyPointerRoot		=6,
16500 	NotifyDetailNone		=7
16501 }
16502 
16503 /* Visibility notify */
16504 
16505 enum VisibilityNotify:int
16506 {
16507 VisibilityUnobscured		=0,
16508 VisibilityPartiallyObscured	=1,
16509 VisibilityFullyObscured		=2
16510 }
16511 
16512 
16513 enum WindowStackingMethod:int
16514 {
16515 	Above		=0,
16516 	Below		=1,
16517 	TopIf		=2,
16518 	BottomIf	=3,
16519 	Opposite	=4
16520 }
16521 
16522 /* Circulation request */
16523 enum CirculationRequest:int
16524 {
16525 	PlaceOnTop		=0,
16526 	PlaceOnBottom	=1
16527 }
16528 
16529 enum PropertyNotification:int
16530 {
16531 	PropertyNewValue	=0,
16532 	PropertyDelete		=1
16533 }
16534 
16535 enum ColorMapNotification:int
16536 {
16537 	ColormapUninstalled	=0,
16538 	ColormapInstalled		=1
16539 }
16540 
16541 
16542 	struct _XPrivate {}
16543 	struct _XrmHashBucketRec {}
16544 
16545 	alias void* XPointer;
16546 	alias void* XExtData;
16547 
16548 	version( X86_64 ) {
16549 		alias ulong XID;
16550 		alias ulong arch_ulong;
16551 		alias long arch_long;
16552 	} else version (AArch64) {
16553 		alias ulong XID;
16554 		alias ulong arch_ulong;
16555 		alias long arch_long;
16556 	} else {
16557 		alias uint XID;
16558 		alias uint arch_ulong;
16559 		alias int arch_long;
16560 	}
16561 
16562 	alias XID Window;
16563 	alias XID Drawable;
16564 	alias XID Pixmap;
16565 
16566 	alias arch_ulong Atom;
16567 	alias int Bool;
16568 	alias Display XDisplay;
16569 
16570 	alias int ByteOrder;
16571 	alias arch_ulong Time;
16572 	alias void ScreenFormat;
16573 
16574 	struct XImage {
16575 		int width, height;			/* size of image */
16576 		int xoffset;				/* number of pixels offset in X direction */
16577 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16578 		void *data;					/* pointer to image data */
16579 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16580 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16581 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16582 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16583 		int depth;					/* depth of image */
16584 		int bytes_per_line;			/* accelarator to next line */
16585 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16586 		arch_ulong red_mask;	/* bits in z arrangment */
16587 		arch_ulong green_mask;
16588 		arch_ulong blue_mask;
16589 		XPointer obdata;			/* hook for the object routines to hang on */
16590 		static struct F {				/* image manipulation routines */
16591 			XImage* function(
16592 				XDisplay* 			/* display */,
16593 				Visual*				/* visual */,
16594 				uint				/* depth */,
16595 				int					/* format */,
16596 				int					/* offset */,
16597 				ubyte*				/* data */,
16598 				uint				/* width */,
16599 				uint				/* height */,
16600 				int					/* bitmap_pad */,
16601 				int					/* bytes_per_line */) create_image;
16602 			int function(XImage *) destroy_image;
16603 			arch_ulong function(XImage *, int, int) get_pixel;
16604 			int function(XImage *, int, int, arch_ulong) put_pixel;
16605 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16606 			int function(XImage *, arch_long) add_pixel;
16607 		}
16608 		F f;
16609 	}
16610 	version(X86_64) static assert(XImage.sizeof == 136);
16611 	else version(X86) static assert(XImage.sizeof == 88);
16612 
16613 struct XCharStruct {
16614 	short       lbearing;       /* origin to left edge of raster */
16615 	short       rbearing;       /* origin to right edge of raster */
16616 	short       width;          /* advance to next char's origin */
16617 	short       ascent;         /* baseline to top edge of raster */
16618 	short       descent;        /* baseline to bottom edge of raster */
16619 	ushort attributes;  /* per char flags (not predefined) */
16620 }
16621 
16622 /*
16623  * To allow arbitrary information with fonts, there are additional properties
16624  * returned.
16625  */
16626 struct XFontProp {
16627 	Atom name;
16628 	arch_ulong card32;
16629 }
16630 
16631 alias Atom Font;
16632 
16633 struct XFontStruct {
16634 	XExtData *ext_data;           /* Hook for extension to hang data */
16635 	Font fid;                     /* Font ID for this font */
16636 	uint direction;           /* Direction the font is painted */
16637 	uint min_char_or_byte2;   /* First character */
16638 	uint max_char_or_byte2;   /* Last character */
16639 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16640 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16641 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16642 	uint default_char;        /* Char to print for undefined character */
16643 	int n_properties;             /* How many properties there are */
16644 	XFontProp *properties;        /* Pointer to array of additional properties*/
16645 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16646 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16647 	XCharStruct *per_char;        /* first_char to last_char information */
16648 	int ascent;                   /* Max extent above baseline for spacing */
16649 	int descent;                  /* Max descent below baseline for spacing */
16650 }
16651 
16652 
16653 /*
16654  * Definitions of specific events.
16655  */
16656 struct XKeyEvent
16657 {
16658 	int type;			/* of event */
16659 	arch_ulong serial;		/* # of last request processed by server */
16660 	Bool send_event;	/* true if this came from a SendEvent request */
16661 	Display *display;	/* Display the event was read from */
16662 	Window window;	        /* "event" window it is reported relative to */
16663 	Window root;	        /* root window that the event occurred on */
16664 	Window subwindow;	/* child window */
16665 	Time time;		/* milliseconds */
16666 	int x, y;		/* pointer x, y coordinates in event window */
16667 	int x_root, y_root;	/* coordinates relative to root */
16668 	KeyOrButtonMask state;	/* key or button mask */
16669 	uint keycode;	/* detail */
16670 	Bool same_screen;	/* same screen flag */
16671 }
16672 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16673 alias XKeyEvent XKeyPressedEvent;
16674 alias XKeyEvent XKeyReleasedEvent;
16675 
16676 struct XButtonEvent
16677 {
16678 	int type;		/* of event */
16679 	arch_ulong serial;	/* # of last request processed by server */
16680 	Bool send_event;	/* true if this came from a SendEvent request */
16681 	Display *display;	/* Display the event was read from */
16682 	Window window;	        /* "event" window it is reported relative to */
16683 	Window root;	        /* root window that the event occurred on */
16684 	Window subwindow;	/* child window */
16685 	Time time;		/* milliseconds */
16686 	int x, y;		/* pointer x, y coordinates in event window */
16687 	int x_root, y_root;	/* coordinates relative to root */
16688 	KeyOrButtonMask state;	/* key or button mask */
16689 	uint button;	/* detail */
16690 	Bool same_screen;	/* same screen flag */
16691 }
16692 alias XButtonEvent XButtonPressedEvent;
16693 alias XButtonEvent XButtonReleasedEvent;
16694 
16695 struct XMotionEvent{
16696 	int type;		/* of event */
16697 	arch_ulong serial;	/* # of last request processed by server */
16698 	Bool send_event;	/* true if this came from a SendEvent request */
16699 	Display *display;	/* Display the event was read from */
16700 	Window window;	        /* "event" window reported relative to */
16701 	Window root;	        /* root window that the event occurred on */
16702 	Window subwindow;	/* child window */
16703 	Time time;		/* milliseconds */
16704 	int x, y;		/* pointer x, y coordinates in event window */
16705 	int x_root, y_root;	/* coordinates relative to root */
16706 	KeyOrButtonMask state;	/* key or button mask */
16707 	byte is_hint;		/* detail */
16708 	Bool same_screen;	/* same screen flag */
16709 }
16710 alias XMotionEvent XPointerMovedEvent;
16711 
16712 struct XCrossingEvent{
16713 	int type;		/* of event */
16714 	arch_ulong serial;	/* # of last request processed by server */
16715 	Bool send_event;	/* true if this came from a SendEvent request */
16716 	Display *display;	/* Display the event was read from */
16717 	Window window;	        /* "event" window reported relative to */
16718 	Window root;	        /* root window that the event occurred on */
16719 	Window subwindow;	/* child window */
16720 	Time time;		/* milliseconds */
16721 	int x, y;		/* pointer x, y coordinates in event window */
16722 	int x_root, y_root;	/* coordinates relative to root */
16723 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16724 	NotifyDetail detail;
16725 	/*
16726 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16727 	 * NotifyNonlinear,NotifyNonlinearVirtual
16728 	 */
16729 	Bool same_screen;	/* same screen flag */
16730 	Bool focus;		/* Boolean focus */
16731 	KeyOrButtonMask state;	/* key or button mask */
16732 }
16733 alias XCrossingEvent XEnterWindowEvent;
16734 alias XCrossingEvent XLeaveWindowEvent;
16735 
16736 struct XFocusChangeEvent{
16737 	int type;		/* FocusIn or FocusOut */
16738 	arch_ulong serial;	/* # of last request processed by server */
16739 	Bool send_event;	/* true if this came from a SendEvent request */
16740 	Display *display;	/* Display the event was read from */
16741 	Window window;		/* window of event */
16742 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16743 				   NotifyGrab, NotifyUngrab */
16744 	NotifyDetail detail;
16745 	/*
16746 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16747 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16748 	 * NotifyPointerRoot, NotifyDetailNone
16749 	 */
16750 }
16751 alias XFocusChangeEvent XFocusInEvent;
16752 alias XFocusChangeEvent XFocusOutEvent;
16753 
16754 enum CWBackPixmap              = (1L<<0);
16755 enum CWBackPixel               = (1L<<1);
16756 enum CWBorderPixmap            = (1L<<2);
16757 enum CWBorderPixel             = (1L<<3);
16758 enum CWBitGravity              = (1L<<4);
16759 enum CWWinGravity              = (1L<<5);
16760 enum CWBackingStore            = (1L<<6);
16761 enum CWBackingPlanes           = (1L<<7);
16762 enum CWBackingPixel            = (1L<<8);
16763 enum CWOverrideRedirect        = (1L<<9);
16764 enum CWSaveUnder               = (1L<<10);
16765 enum CWEventMask               = (1L<<11);
16766 enum CWDontPropagate           = (1L<<12);
16767 enum CWColormap                = (1L<<13);
16768 enum CWCursor                  = (1L<<14);
16769 
16770 struct XWindowAttributes {
16771 	int x, y;			/* location of window */
16772 	int width, height;		/* width and height of window */
16773 	int border_width;		/* border width of window */
16774 	int depth;			/* depth of window */
16775 	Visual *visual;			/* the associated visual structure */
16776 	Window root;			/* root of screen containing window */
16777 	int class_;			/* InputOutput, InputOnly*/
16778 	int bit_gravity;		/* one of the bit gravity values */
16779 	int win_gravity;		/* one of the window gravity values */
16780 	int backing_store;		/* NotUseful, WhenMapped, Always */
16781 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16782 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16783 	Bool save_under;		/* boolean, should bits under be saved? */
16784 	Colormap colormap;		/* color map to be associated with window */
16785 	Bool map_installed;		/* boolean, is color map currently installed*/
16786 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16787 	arch_long all_event_masks;		/* set of events all people have interest in*/
16788 	arch_long your_event_mask;		/* my event mask */
16789 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16790 	Bool override_redirect;		/* boolean value for override-redirect */
16791 	Screen *screen;			/* back pointer to correct screen */
16792 }
16793 
16794 enum IsUnmapped = 0;
16795 enum IsUnviewable = 1;
16796 enum IsViewable = 2;
16797 
16798 struct XSetWindowAttributes {
16799 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16800 	arch_ulong background_pixel;/* background pixel */
16801 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16802 	arch_ulong border_pixel;/* border pixel value */
16803 	int bit_gravity;         /* one of bit gravity values */
16804 	int win_gravity;         /* one of the window gravity values */
16805 	int backing_store;       /* NotUseful, WhenMapped, Always */
16806 	arch_ulong backing_planes;/* planes to be preserved if possible */
16807 	arch_ulong backing_pixel;/* value to use in restoring planes */
16808 	Bool save_under;         /* should bits under be saved? (popups) */
16809 	arch_long event_mask;         /* set of events that should be saved */
16810 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16811 	Bool override_redirect;  /* boolean value for override_redirect */
16812 	Colormap colormap;       /* color map to be associated with window */
16813 	Cursor cursor;           /* cursor to be displayed (or None) */
16814 }
16815 
16816 
16817 alias int Status;
16818 
16819 
16820 enum EventMask:int
16821 {
16822 	NoEventMask				=0,
16823 	KeyPressMask			=1<<0,
16824 	KeyReleaseMask			=1<<1,
16825 	ButtonPressMask			=1<<2,
16826 	ButtonReleaseMask		=1<<3,
16827 	EnterWindowMask			=1<<4,
16828 	LeaveWindowMask			=1<<5,
16829 	PointerMotionMask		=1<<6,
16830 	PointerMotionHintMask	=1<<7,
16831 	Button1MotionMask		=1<<8,
16832 	Button2MotionMask		=1<<9,
16833 	Button3MotionMask		=1<<10,
16834 	Button4MotionMask		=1<<11,
16835 	Button5MotionMask		=1<<12,
16836 	ButtonMotionMask		=1<<13,
16837 	KeymapStateMask		=1<<14,
16838 	ExposureMask			=1<<15,
16839 	VisibilityChangeMask	=1<<16,
16840 	StructureNotifyMask		=1<<17,
16841 	ResizeRedirectMask		=1<<18,
16842 	SubstructureNotifyMask	=1<<19,
16843 	SubstructureRedirectMask=1<<20,
16844 	FocusChangeMask			=1<<21,
16845 	PropertyChangeMask		=1<<22,
16846 	ColormapChangeMask		=1<<23,
16847 	OwnerGrabButtonMask		=1<<24
16848 }
16849 
16850 struct MwmHints {
16851 	c_ulong flags;
16852 	c_ulong functions;
16853 	c_ulong decorations;
16854 	c_long input_mode;
16855 	c_ulong status;
16856 }
16857 
16858 enum {
16859 	MWM_HINTS_FUNCTIONS = (1L << 0),
16860 	MWM_HINTS_DECORATIONS =  (1L << 1),
16861 
16862 	MWM_FUNC_ALL = (1L << 0),
16863 	MWM_FUNC_RESIZE = (1L << 1),
16864 	MWM_FUNC_MOVE = (1L << 2),
16865 	MWM_FUNC_MINIMIZE = (1L << 3),
16866 	MWM_FUNC_MAXIMIZE = (1L << 4),
16867 	MWM_FUNC_CLOSE = (1L << 5),
16868 
16869 	MWM_DECOR_ALL = (1L << 0),
16870 	MWM_DECOR_BORDER = (1L << 1),
16871 	MWM_DECOR_RESIZEH = (1L << 2),
16872 	MWM_DECOR_TITLE = (1L << 3),
16873 	MWM_DECOR_MENU = (1L << 4),
16874 	MWM_DECOR_MINIMIZE = (1L << 5),
16875 	MWM_DECOR_MAXIMIZE = (1L << 6),
16876 }
16877 
16878 import core.stdc.config : c_long, c_ulong;
16879 
16880 	/* Size hints mask bits */
16881 
16882 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16883 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16884 	enum   PPosition   = (1L << 2)          /* program specified position */;
16885 	enum   PSize       = (1L << 3)          /* program specified size */;
16886 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16887 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16888 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16889 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16890 	enum   PBaseSize   = (1L << 8);
16891 	enum   PWinGravity = (1L << 9);
16892 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16893 	struct XSizeHints {
16894 		arch_long flags;         /* marks which fields in this structure are defined */
16895 		int x, y;           /* Obsolete */
16896 		int width, height;  /* Obsolete */
16897 		int min_width, min_height;
16898 		int max_width, max_height;
16899 		int width_inc, height_inc;
16900 		struct Aspect {
16901 			int x;       /* numerator */
16902 			int y;       /* denominator */
16903 		}
16904 
16905 		Aspect min_aspect;
16906 		Aspect max_aspect;
16907 		int base_width, base_height;
16908 		int win_gravity;
16909 		/* this structure may be extended in the future */
16910 	}
16911 
16912 
16913 
16914 enum EventType:int
16915 {
16916 	KeyPress			=2,
16917 	KeyRelease			=3,
16918 	ButtonPress			=4,
16919 	ButtonRelease		=5,
16920 	MotionNotify		=6,
16921 	EnterNotify			=7,
16922 	LeaveNotify			=8,
16923 	FocusIn				=9,
16924 	FocusOut			=10,
16925 	KeymapNotify		=11,
16926 	Expose				=12,
16927 	GraphicsExpose		=13,
16928 	NoExpose			=14,
16929 	VisibilityNotify	=15,
16930 	CreateNotify		=16,
16931 	DestroyNotify		=17,
16932 	UnmapNotify		=18,
16933 	MapNotify			=19,
16934 	MapRequest			=20,
16935 	ReparentNotify		=21,
16936 	ConfigureNotify		=22,
16937 	ConfigureRequest	=23,
16938 	GravityNotify		=24,
16939 	ResizeRequest		=25,
16940 	CirculateNotify		=26,
16941 	CirculateRequest	=27,
16942 	PropertyNotify		=28,
16943 	SelectionClear		=29,
16944 	SelectionRequest	=30,
16945 	SelectionNotify		=31,
16946 	ColormapNotify		=32,
16947 	ClientMessage		=33,
16948 	MappingNotify		=34,
16949 	LASTEvent			=35	/* must be bigger than any event # */
16950 }
16951 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16952 struct XKeymapEvent
16953 {
16954 	int type;
16955 	arch_ulong serial;	/* # of last request processed by server */
16956 	Bool send_event;	/* true if this came from a SendEvent request */
16957 	Display *display;	/* Display the event was read from */
16958 	Window window;
16959 	byte[32] key_vector;
16960 }
16961 
16962 struct XExposeEvent
16963 {
16964 	int type;
16965 	arch_ulong serial;	/* # of last request processed by server */
16966 	Bool send_event;	/* true if this came from a SendEvent request */
16967 	Display *display;	/* Display the event was read from */
16968 	Window window;
16969 	int x, y;
16970 	int width, height;
16971 	int count;		/* if non-zero, at least this many more */
16972 }
16973 
16974 struct XGraphicsExposeEvent{
16975 	int type;
16976 	arch_ulong serial;	/* # of last request processed by server */
16977 	Bool send_event;	/* true if this came from a SendEvent request */
16978 	Display *display;	/* Display the event was read from */
16979 	Drawable drawable;
16980 	int x, y;
16981 	int width, height;
16982 	int count;		/* if non-zero, at least this many more */
16983 	int major_code;		/* core is CopyArea or CopyPlane */
16984 	int minor_code;		/* not defined in the core */
16985 }
16986 
16987 struct XNoExposeEvent{
16988 	int type;
16989 	arch_ulong serial;	/* # of last request processed by server */
16990 	Bool send_event;	/* true if this came from a SendEvent request */
16991 	Display *display;	/* Display the event was read from */
16992 	Drawable drawable;
16993 	int major_code;		/* core is CopyArea or CopyPlane */
16994 	int minor_code;		/* not defined in the core */
16995 }
16996 
16997 struct XVisibilityEvent{
16998 	int type;
16999 	arch_ulong serial;	/* # of last request processed by server */
17000 	Bool send_event;	/* true if this came from a SendEvent request */
17001 	Display *display;	/* Display the event was read from */
17002 	Window window;
17003 	VisibilityNotify state;		/* Visibility state */
17004 }
17005 
17006 struct XCreateWindowEvent{
17007 	int type;
17008 	arch_ulong serial;	/* # of last request processed by server */
17009 	Bool send_event;	/* true if this came from a SendEvent request */
17010 	Display *display;	/* Display the event was read from */
17011 	Window parent;		/* parent of the window */
17012 	Window window;		/* window id of window created */
17013 	int x, y;		/* window location */
17014 	int width, height;	/* size of window */
17015 	int border_width;	/* border width */
17016 	Bool override_redirect;	/* creation should be overridden */
17017 }
17018 
17019 struct XDestroyWindowEvent
17020 {
17021 	int type;
17022 	arch_ulong serial;		/* # of last request processed by server */
17023 	Bool send_event;	/* true if this came from a SendEvent request */
17024 	Display *display;	/* Display the event was read from */
17025 	Window event;
17026 	Window window;
17027 }
17028 
17029 struct XUnmapEvent
17030 {
17031 	int type;
17032 	arch_ulong serial;		/* # of last request processed by server */
17033 	Bool send_event;	/* true if this came from a SendEvent request */
17034 	Display *display;	/* Display the event was read from */
17035 	Window event;
17036 	Window window;
17037 	Bool from_configure;
17038 }
17039 
17040 struct XMapEvent
17041 {
17042 	int type;
17043 	arch_ulong serial;		/* # of last request processed by server */
17044 	Bool send_event;	/* true if this came from a SendEvent request */
17045 	Display *display;	/* Display the event was read from */
17046 	Window event;
17047 	Window window;
17048 	Bool override_redirect;	/* Boolean, is override set... */
17049 }
17050 
17051 struct XMapRequestEvent
17052 {
17053 	int type;
17054 	arch_ulong serial;	/* # of last request processed by server */
17055 	Bool send_event;	/* true if this came from a SendEvent request */
17056 	Display *display;	/* Display the event was read from */
17057 	Window parent;
17058 	Window window;
17059 }
17060 
17061 struct XReparentEvent
17062 {
17063 	int type;
17064 	arch_ulong serial;	/* # of last request processed by server */
17065 	Bool send_event;	/* true if this came from a SendEvent request */
17066 	Display *display;	/* Display the event was read from */
17067 	Window event;
17068 	Window window;
17069 	Window parent;
17070 	int x, y;
17071 	Bool override_redirect;
17072 }
17073 
17074 struct XConfigureEvent
17075 {
17076 	int type;
17077 	arch_ulong serial;	/* # of last request processed by server */
17078 	Bool send_event;	/* true if this came from a SendEvent request */
17079 	Display *display;	/* Display the event was read from */
17080 	Window event;
17081 	Window window;
17082 	int x, y;
17083 	int width, height;
17084 	int border_width;
17085 	Window above;
17086 	Bool override_redirect;
17087 }
17088 
17089 struct XGravityEvent
17090 {
17091 	int type;
17092 	arch_ulong serial;	/* # of last request processed by server */
17093 	Bool send_event;	/* true if this came from a SendEvent request */
17094 	Display *display;	/* Display the event was read from */
17095 	Window event;
17096 	Window window;
17097 	int x, y;
17098 }
17099 
17100 struct XResizeRequestEvent
17101 {
17102 	int type;
17103 	arch_ulong serial;	/* # of last request processed by server */
17104 	Bool send_event;	/* true if this came from a SendEvent request */
17105 	Display *display;	/* Display the event was read from */
17106 	Window window;
17107 	int width, height;
17108 }
17109 
17110 struct  XConfigureRequestEvent
17111 {
17112 	int type;
17113 	arch_ulong serial;	/* # of last request processed by server */
17114 	Bool send_event;	/* true if this came from a SendEvent request */
17115 	Display *display;	/* Display the event was read from */
17116 	Window parent;
17117 	Window window;
17118 	int x, y;
17119 	int width, height;
17120 	int border_width;
17121 	Window above;
17122 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17123 	arch_ulong value_mask;
17124 }
17125 
17126 struct XCirculateEvent
17127 {
17128 	int type;
17129 	arch_ulong serial;	/* # of last request processed by server */
17130 	Bool send_event;	/* true if this came from a SendEvent request */
17131 	Display *display;	/* Display the event was read from */
17132 	Window event;
17133 	Window window;
17134 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17135 }
17136 
17137 struct XCirculateRequestEvent
17138 {
17139 	int type;
17140 	arch_ulong serial;	/* # of last request processed by server */
17141 	Bool send_event;	/* true if this came from a SendEvent request */
17142 	Display *display;	/* Display the event was read from */
17143 	Window parent;
17144 	Window window;
17145 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17146 }
17147 
17148 struct XPropertyEvent
17149 {
17150 	int type;
17151 	arch_ulong serial;	/* # of last request processed by server */
17152 	Bool send_event;	/* true if this came from a SendEvent request */
17153 	Display *display;	/* Display the event was read from */
17154 	Window window;
17155 	Atom atom;
17156 	Time time;
17157 	PropertyNotification state;		/* NewValue, Deleted */
17158 }
17159 
17160 struct XSelectionClearEvent
17161 {
17162 	int type;
17163 	arch_ulong serial;	/* # of last request processed by server */
17164 	Bool send_event;	/* true if this came from a SendEvent request */
17165 	Display *display;	/* Display the event was read from */
17166 	Window window;
17167 	Atom selection;
17168 	Time time;
17169 }
17170 
17171 struct XSelectionRequestEvent
17172 {
17173 	int type;
17174 	arch_ulong serial;	/* # of last request processed by server */
17175 	Bool send_event;	/* true if this came from a SendEvent request */
17176 	Display *display;	/* Display the event was read from */
17177 	Window owner;
17178 	Window requestor;
17179 	Atom selection;
17180 	Atom target;
17181 	Atom property;
17182 	Time time;
17183 }
17184 
17185 struct XSelectionEvent
17186 {
17187 	int type;
17188 	arch_ulong serial;	/* # of last request processed by server */
17189 	Bool send_event;	/* true if this came from a SendEvent request */
17190 	Display *display;	/* Display the event was read from */
17191 	Window requestor;
17192 	Atom selection;
17193 	Atom target;
17194 	Atom property;		/* ATOM or None */
17195 	Time time;
17196 }
17197 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17198 
17199 struct XColormapEvent
17200 {
17201 	int type;
17202 	arch_ulong serial;	/* # of last request processed by server */
17203 	Bool send_event;	/* true if this came from a SendEvent request */
17204 	Display *display;	/* Display the event was read from */
17205 	Window window;
17206 	Colormap colormap;	/* COLORMAP or None */
17207 	Bool new_;		/* C++ */
17208 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17209 }
17210 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17211 
17212 struct XClientMessageEvent
17213 {
17214 	int type;
17215 	arch_ulong serial;	/* # of last request processed by server */
17216 	Bool send_event;	/* true if this came from a SendEvent request */
17217 	Display *display;	/* Display the event was read from */
17218 	Window window;
17219 	Atom message_type;
17220 	int format;
17221 	union Data{
17222 		byte[20] b;
17223 		short[10] s;
17224 		arch_ulong[5] l;
17225 	}
17226 	Data data;
17227 
17228 }
17229 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17230 
17231 struct XMappingEvent
17232 {
17233 	int type;
17234 	arch_ulong serial;	/* # of last request processed by server */
17235 	Bool send_event;	/* true if this came from a SendEvent request */
17236 	Display *display;	/* Display the event was read from */
17237 	Window window;		/* unused */
17238 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17239 				   MappingPointer */
17240 	int first_keycode;	/* first keycode */
17241 	int count;		/* defines range of change w. first_keycode*/
17242 }
17243 
17244 struct XErrorEvent
17245 {
17246 	int type;
17247 	Display *display;	/* Display the event was read from */
17248 	XID resourceid;		/* resource id */
17249 	arch_ulong serial;	/* serial number of failed request */
17250 	ubyte error_code;	/* error code of failed request */
17251 	ubyte request_code;	/* Major op-code of failed request */
17252 	ubyte minor_code;	/* Minor op-code of failed request */
17253 }
17254 
17255 struct XAnyEvent
17256 {
17257 	int type;
17258 	arch_ulong serial;	/* # of last request processed by server */
17259 	Bool send_event;	/* true if this came from a SendEvent request */
17260 	Display *display;/* Display the event was read from */
17261 	Window window;	/* window on which event was requested in event mask */
17262 }
17263 
17264 union XEvent{
17265 	int type;		/* must not be changed; first element */
17266 	XAnyEvent xany;
17267 	XKeyEvent xkey;
17268 	XButtonEvent xbutton;
17269 	XMotionEvent xmotion;
17270 	XCrossingEvent xcrossing;
17271 	XFocusChangeEvent xfocus;
17272 	XExposeEvent xexpose;
17273 	XGraphicsExposeEvent xgraphicsexpose;
17274 	XNoExposeEvent xnoexpose;
17275 	XVisibilityEvent xvisibility;
17276 	XCreateWindowEvent xcreatewindow;
17277 	XDestroyWindowEvent xdestroywindow;
17278 	XUnmapEvent xunmap;
17279 	XMapEvent xmap;
17280 	XMapRequestEvent xmaprequest;
17281 	XReparentEvent xreparent;
17282 	XConfigureEvent xconfigure;
17283 	XGravityEvent xgravity;
17284 	XResizeRequestEvent xresizerequest;
17285 	XConfigureRequestEvent xconfigurerequest;
17286 	XCirculateEvent xcirculate;
17287 	XCirculateRequestEvent xcirculaterequest;
17288 	XPropertyEvent xproperty;
17289 	XSelectionClearEvent xselectionclear;
17290 	XSelectionRequestEvent xselectionrequest;
17291 	XSelectionEvent xselection;
17292 	XColormapEvent xcolormap;
17293 	XClientMessageEvent xclient;
17294 	XMappingEvent xmapping;
17295 	XErrorEvent xerror;
17296 	XKeymapEvent xkeymap;
17297 	arch_ulong[24] pad;
17298 }
17299 
17300 
17301 	struct Display {
17302 		XExtData *ext_data;	/* hook for extension to hang data */
17303 		_XPrivate *private1;
17304 		int fd;			/* Network socket. */
17305 		int private2;
17306 		int proto_major_version;/* major version of server's X protocol */
17307 		int proto_minor_version;/* minor version of servers X protocol */
17308 		char *vendor;		/* vendor of the server hardware */
17309 	    	XID private3;
17310 		XID private4;
17311 		XID private5;
17312 		int private6;
17313 		XID function(Display*)resource_alloc;/* allocator function */
17314 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17315 		int bitmap_unit;	/* padding and data requirements */
17316 		int bitmap_pad;		/* padding requirements on bitmaps */
17317 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17318 		int nformats;		/* number of pixmap formats in list */
17319 		ScreenFormat *pixmap_format;	/* pixmap format list */
17320 		int private8;
17321 		int release;		/* release of the server */
17322 		_XPrivate *private9;
17323 		_XPrivate *private10;
17324 		int qlen;		/* Length of input event queue */
17325 		arch_ulong last_request_read; /* seq number of last event read */
17326 		arch_ulong request;	/* sequence number of last request. */
17327 		XPointer private11;
17328 		XPointer private12;
17329 		XPointer private13;
17330 		XPointer private14;
17331 		uint max_request_size; /* maximum number 32 bit words in request*/
17332 		_XrmHashBucketRec *db;
17333 		int function  (Display*)private15;
17334 		char *display_name;	/* "host:display" string used on this connect*/
17335 		int default_screen;	/* default screen for operations */
17336 		int nscreens;		/* number of screens on this server*/
17337 		Screen *screens;	/* pointer to list of screens */
17338 		arch_ulong motion_buffer;	/* size of motion buffer */
17339 		arch_ulong private16;
17340 		int min_keycode;	/* minimum defined keycode */
17341 		int max_keycode;	/* maximum defined keycode */
17342 		XPointer private17;
17343 		XPointer private18;
17344 		int private19;
17345 		byte *xdefaults;	/* contents of defaults from server */
17346 		/* there is more to this structure, but it is private to Xlib */
17347 	}
17348 
17349 	// I got these numbers from a C program as a sanity test
17350 	version(X86_64) {
17351 		static assert(Display.sizeof == 296);
17352 		static assert(XPointer.sizeof == 8);
17353 		static assert(XErrorEvent.sizeof == 40);
17354 		static assert(XAnyEvent.sizeof == 40);
17355 		static assert(XMappingEvent.sizeof == 56);
17356 		static assert(XEvent.sizeof == 192);
17357     	} else version (AArch64) {
17358         	// omit check for aarch64
17359 	} else {
17360 		static assert(Display.sizeof == 176);
17361 		static assert(XPointer.sizeof == 4);
17362 		static assert(XEvent.sizeof == 96);
17363 	}
17364 
17365 struct Depth
17366 {
17367 	int depth;		/* this depth (Z) of the depth */
17368 	int nvisuals;		/* number of Visual types at this depth */
17369 	Visual *visuals;	/* list of visuals possible at this depth */
17370 }
17371 
17372 alias void* GC;
17373 alias c_ulong VisualID;
17374 alias XID Colormap;
17375 alias XID Cursor;
17376 alias XID KeySym;
17377 alias uint KeyCode;
17378 enum None = 0;
17379 }
17380 
17381 version(without_opengl) {}
17382 else {
17383 extern(C) nothrow @nogc {
17384 
17385 
17386 static if(!SdpyIsUsingIVGLBinds) {
17387 enum GLX_USE_GL=            1;       /* support GLX rendering */
17388 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17389 enum GLX_LEVEL=             3;       /* level in plane stacking */
17390 enum GLX_RGBA=              4;       /* true if RGBA mode */
17391 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17392 enum GLX_STEREO=            6;       /* stereo buffering supported */
17393 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17394 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17395 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17396 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17397 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17398 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17399 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17400 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17401 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17402 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17403 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17404 
17405 
17406 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17407 
17408 
17409 
17410 enum GL_TRUE = 1;
17411 enum GL_FALSE = 0;
17412 }
17413 
17414 alias XID GLXContextID;
17415 alias XID GLXPixmap;
17416 alias XID GLXDrawable;
17417 alias XID GLXPbuffer;
17418 alias XID GLXWindow;
17419 alias XID GLXFBConfigID;
17420 alias void* GLXContext;
17421 
17422 }
17423 }
17424 
17425 enum AllocNone = 0;
17426 
17427 extern(C) {
17428 	/* WARNING, this type not in Xlib spec */
17429 	extern(C) alias XIOErrorHandler = int function (Display* display);
17430 }
17431 
17432 extern(C) nothrow
17433 alias XErrorHandler = int function(Display*, XErrorEvent*);
17434 
17435 extern(C) nothrow @nogc {
17436 struct Screen{
17437 	XExtData *ext_data;		/* hook for extension to hang data */
17438 	Display *display;		/* back pointer to display structure */
17439 	Window root;			/* Root window id. */
17440 	int width, height;		/* width and height of screen */
17441 	int mwidth, mheight;	/* width and height of  in millimeters */
17442 	int ndepths;			/* number of depths possible */
17443 	Depth *depths;			/* list of allowable depths on the screen */
17444 	int root_depth;			/* bits per pixel */
17445 	Visual *root_visual;	/* root visual */
17446 	GC default_gc;			/* GC for the root root visual */
17447 	Colormap cmap;			/* default color map */
17448 	uint white_pixel;
17449 	uint black_pixel;		/* White and Black pixel values */
17450 	int max_maps, min_maps;	/* max and min color maps */
17451 	int backing_store;		/* Never, WhenMapped, Always */
17452 	bool save_unders;
17453 	int root_input_mask;	/* initial root input mask */
17454 }
17455 
17456 struct Visual
17457 {
17458 	XExtData *ext_data;	/* hook for extension to hang data */
17459 	VisualID visualid;	/* visual id of this visual */
17460 	int class_;			/* class of screen (monochrome, etc.) */
17461 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17462 	int bits_per_rgb;	/* log base 2 of distinct color values */
17463 	int map_entries;	/* color map entries */
17464 }
17465 
17466 	alias Display* _XPrivDisplay;
17467 
17468 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17469 		assert(dpy !is null);
17470 		return &dpy.screens[scr];
17471 	}
17472 
17473 	extern(D) Window RootWindow(Display *dpy,int scr) {
17474 		return ScreenOfDisplay(dpy,scr).root;
17475 	}
17476 
17477 	struct XWMHints {
17478 		arch_long flags;
17479 		Bool input;
17480 		int initial_state;
17481 		Pixmap icon_pixmap;
17482 		Window icon_window;
17483 		int icon_x, icon_y;
17484 		Pixmap icon_mask;
17485 		XID window_group;
17486 	}
17487 
17488 	struct XClassHint {
17489 		char* res_name;
17490 		char* res_class;
17491 	}
17492 
17493 	extern(D) int DefaultScreen(Display *dpy) {
17494 		return dpy.default_screen;
17495 	}
17496 
17497 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17498 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17499 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17500 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17501 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17502 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17503 
17504 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17505 
17506 	enum int AnyPropertyType = 0;
17507 	enum int Success = 0;
17508 
17509 	enum int RevertToNone = None;
17510 	enum int PointerRoot = 1;
17511 	enum Time CurrentTime = 0;
17512 	enum int RevertToPointerRoot = PointerRoot;
17513 	enum int RevertToParent = 2;
17514 
17515 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17516 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17517 	}
17518 
17519 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17520 		return ScreenOfDisplay(dpy,scr).root_visual;
17521 	}
17522 
17523 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17524 		return ScreenOfDisplay(dpy,scr).default_gc;
17525 	}
17526 
17527 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17528 		return ScreenOfDisplay(dpy,scr).black_pixel;
17529 	}
17530 
17531 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17532 		return ScreenOfDisplay(dpy,scr).white_pixel;
17533 	}
17534 
17535 	alias void* XFontSet; // i think
17536 	struct XmbTextItem {
17537 		char* chars;
17538 		int nchars;
17539 		int delta;
17540 		XFontSet font_set;
17541 	}
17542 
17543 	struct XTextItem {
17544 		char* chars;
17545 		int nchars;
17546 		int delta;
17547 		Font font;
17548 	}
17549 
17550 	enum {
17551 		GXclear        = 0x0, /* 0 */
17552 		GXand          = 0x1, /* src AND dst */
17553 		GXandReverse   = 0x2, /* src AND NOT dst */
17554 		GXcopy         = 0x3, /* src */
17555 		GXandInverted  = 0x4, /* NOT src AND dst */
17556 		GXnoop         = 0x5, /* dst */
17557 		GXxor          = 0x6, /* src XOR dst */
17558 		GXor           = 0x7, /* src OR dst */
17559 		GXnor          = 0x8, /* NOT src AND NOT dst */
17560 		GXequiv        = 0x9, /* NOT src XOR dst */
17561 		GXinvert       = 0xa, /* NOT dst */
17562 		GXorReverse    = 0xb, /* src OR NOT dst */
17563 		GXcopyInverted = 0xc, /* NOT src */
17564 		GXorInverted   = 0xd, /* NOT src OR dst */
17565 		GXnand         = 0xe, /* NOT src OR NOT dst */
17566 		GXset          = 0xf, /* 1 */
17567 	}
17568 	enum QueueMode : int {
17569 		QueuedAlready,
17570 		QueuedAfterReading,
17571 		QueuedAfterFlush
17572 	}
17573 
17574 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17575 
17576 	struct XPoint {
17577 		short x;
17578 		short y;
17579 	}
17580 
17581 	enum CoordMode:int {
17582 		CoordModeOrigin = 0,
17583 		CoordModePrevious = 1
17584 	}
17585 
17586 	enum PolygonShape:int {
17587 		Complex = 0,
17588 		Nonconvex = 1,
17589 		Convex = 2
17590 	}
17591 
17592 	struct XTextProperty {
17593 		const(char)* value;		/* same as Property routines */
17594 		Atom encoding;			/* prop type */
17595 		int format;				/* prop data format: 8, 16, or 32 */
17596 		arch_ulong nitems;		/* number of data items in value */
17597 	}
17598 
17599 	version( X86_64 ) {
17600 		static assert(XTextProperty.sizeof == 32);
17601 	}
17602 
17603 
17604 	struct XGCValues {
17605 		int function_;           /* logical operation */
17606 		arch_ulong plane_mask;/* plane mask */
17607 		arch_ulong foreground;/* foreground pixel */
17608 		arch_ulong background;/* background pixel */
17609 		int line_width;         /* line width */
17610 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17611 		int cap_style;          /* CapNotLast, CapButt,
17612 					   CapRound, CapProjecting */
17613 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17614 		int fill_style;         /* FillSolid, FillTiled,
17615 					   FillStippled, FillOpaeueStippled */
17616 		int fill_rule;          /* EvenOddRule, WindingRule */
17617 		int arc_mode;           /* ArcChord, ArcPieSlice */
17618 		Pixmap tile;            /* tile pixmap for tiling operations */
17619 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17620 		int ts_x_origin;        /* offset for tile or stipple operations */
17621 		int ts_y_origin;
17622 		Font font;              /* default text font for text operations */
17623 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17624 		Bool graphics_exposures;/* boolean, should exposures be generated */
17625 		int clip_x_origin;      /* origin for clipping */
17626 		int clip_y_origin;
17627 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17628 		int dash_offset;        /* patterned/dashed line information */
17629 		char dashes;
17630 	}
17631 
17632 	struct XColor {
17633 		arch_ulong pixel;
17634 		ushort red, green, blue;
17635 		byte flags;
17636 		byte pad;
17637 	}
17638 
17639 	struct XRectangle {
17640 		short x;
17641 		short y;
17642 		ushort width;
17643 		ushort height;
17644 	}
17645 
17646 	enum ClipByChildren = 0;
17647 	enum IncludeInferiors = 1;
17648 
17649 	enum Atom XA_PRIMARY = 1;
17650 	enum Atom XA_SECONDARY = 2;
17651 	enum Atom XA_STRING = 31;
17652 	enum Atom XA_CARDINAL = 6;
17653 	enum Atom XA_WM_NAME = 39;
17654 	enum Atom XA_ATOM = 4;
17655 	enum Atom XA_WINDOW = 33;
17656 	enum Atom XA_WM_HINTS = 35;
17657 	enum int PropModeAppend = 2;
17658 	enum int PropModeReplace = 0;
17659 	enum int PropModePrepend = 1;
17660 
17661 	enum int CopyFromParent = 0;
17662 	enum int InputOutput = 1;
17663 
17664 	// XWMHints
17665 	enum InputHint = 1 << 0;
17666 	enum StateHint = 1 << 1;
17667 	enum IconPixmapHint = (1L << 2);
17668 	enum IconWindowHint = (1L << 3);
17669 	enum IconPositionHint = (1L << 4);
17670 	enum IconMaskHint = (1L << 5);
17671 	enum WindowGroupHint = (1L << 6);
17672 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17673 	enum XUrgencyHint = (1L << 8);
17674 
17675 	// GC Components
17676 	enum GCFunction           =   (1L<<0);
17677 	enum GCPlaneMask         =    (1L<<1);
17678 	enum GCForeground       =     (1L<<2);
17679 	enum GCBackground      =      (1L<<3);
17680 	enum GCLineWidth      =       (1L<<4);
17681 	enum GCLineStyle     =        (1L<<5);
17682 	enum GCCapStyle     =         (1L<<6);
17683 	enum GCJoinStyle   =          (1L<<7);
17684 	enum GCFillStyle  =           (1L<<8);
17685 	enum GCFillRule  =            (1L<<9);
17686 	enum GCTile     =             (1L<<10);
17687 	enum GCStipple           =    (1L<<11);
17688 	enum GCTileStipXOrigin  =     (1L<<12);
17689 	enum GCTileStipYOrigin =      (1L<<13);
17690 	enum GCFont               =   (1L<<14);
17691 	enum GCSubwindowMode     =    (1L<<15);
17692 	enum GCGraphicsExposures=     (1L<<16);
17693 	enum GCClipXOrigin     =      (1L<<17);
17694 	enum GCClipYOrigin    =       (1L<<18);
17695 	enum GCClipMask      =        (1L<<19);
17696 	enum GCDashOffset   =         (1L<<20);
17697 	enum GCDashList    =          (1L<<21);
17698 	enum GCArcMode    =           (1L<<22);
17699 	enum GCLastBit   =            22;
17700 
17701 
17702 	enum int WithdrawnState = 0;
17703 	enum int NormalState = 1;
17704 	enum int IconicState = 3;
17705 
17706 }
17707 } else version (OSXCocoa) {
17708 
17709 /+
17710 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
17711 +/
17712 
17713 	private __gshared AppDelegate globalAppDelegate;
17714 
17715 	extern(Objective-C)
17716 	class AppDelegate : NSObject, NSApplicationDelegate {
17717 		override static AppDelegate alloc() @selector("alloc");
17718 
17719 
17720 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
17721 			SimpleWindow.processAllCustomEvents();
17722 		}
17723 
17724 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
17725 			immutable style = NSWindowStyleMask.resizable |
17726 				NSWindowStyleMask.closable |
17727 				NSWindowStyleMask.miniaturizable |
17728 				NSWindowStyleMask.titled;
17729 
17730 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
17731 
17732 			{
17733 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
17734 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
17735 				mainMenu.setSubmenu(menu, item);
17736 
17737 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
17738 				newItem.target = NSApp;
17739 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
17740 				newItem2.target = NSApp;
17741 			}
17742 
17743 			{
17744 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
17745 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
17746 				mainMenu.setSubmenu(menu, item);
17747 
17748 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
17749 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
17750 			}
17751 
17752 
17753 			NSApp.menu = mainMenu;
17754 
17755 
17756 			// auto controller = ViewController.alloc.init;
17757 
17758 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
17759 
17760 			/+
17761 			this.window = window;
17762 			this.controller = controller;
17763 			+/
17764 		}
17765 
17766 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
17767 			NSApplication.shared_.activateIgnoringOtherApps(true);
17768 		}
17769 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
17770 			return true;
17771 		}
17772 	}
17773 
17774 	extern(Objective-C)
17775 	class SDWindowDelegate : NSObject, NSWindowDelegate {
17776 		override static SDWindowDelegate alloc() @selector("alloc");
17777 		override SDWindowDelegate init() @selector("init");
17778 
17779 		SimpleWindow simpleWindow;
17780 
17781 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
17782 			auto window = cast(void*) notification.object;
17783 
17784 			// FIXME: do i need to release it?
17785 			SimpleWindow.nativeMapping.remove(window);
17786 		}
17787 
17788 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
17789 			if(simpleWindow.windowResized) {
17790 				// FIXME: automaticallyScaleIfPossible behaviors
17791 
17792 				simpleWindow._width = cast(int) frameSize.width;
17793 				simpleWindow._height = cast(int) frameSize.height;
17794 
17795 				simpleWindow.view.setFrameSize(frameSize);
17796 
17797 				/+
17798 				auto size = simpleWindow.view.frame.size;
17799 				writeln(cast(int) size.width, "x", cast(int) size.height);
17800 				+/
17801 
17802 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
17803 
17804 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
17805 
17806 				// simpleWindow.view.setNeedsDisplay(true);
17807 			}
17808 
17809 			return frameSize;
17810 		}
17811 
17812 		/+
17813 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
17814 			if(simpleWindow.windowResized) {
17815 				auto window = simpleWindow.window;
17816 				auto rect = window.contentRectForFrameRect(window.frame);
17817 				import std.stdio; writeln(window.frame.size);
17818 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
17819 			}
17820 		}
17821 		+/
17822 	}
17823 
17824 	extern(Objective-C)
17825 	class SDGraphicsView : NSView {
17826 		SimpleWindow simpleWindow;
17827 
17828 		override static SDGraphicsView alloc() @selector("alloc");
17829 		override SDGraphicsView init() @selector("init") {
17830 			super.init();
17831 			return this;
17832 		}
17833 
17834 		override void drawRect(NSRect rect) @selector("drawRect:") {
17835 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
17836 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17837 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
17838 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17839 			CGImageRelease(cgImage);
17840 		}
17841 
17842 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
17843 			MouseEvent me;
17844 			me.type = type;
17845 
17846 			auto pos = event.locationInWindow;
17847 
17848 			me.x = cast(int) pos.x;
17849 			me.y = cast(int) (simpleWindow.height - pos.y);
17850 
17851 			me.dx = 0; // FIXME
17852 			me.dy = 0; // FIXME
17853 
17854 			me.button = button;
17855 			me.modifierState = cast(uint) event.modifierFlags;
17856 			me.window = simpleWindow;
17857 
17858 			me.doubleClick = false;
17859 
17860 			if(simpleWindow && simpleWindow.handleMouseEvent)
17861 				simpleWindow.handleMouseEvent(me);
17862 		}
17863 
17864 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
17865 			// writeln(event.pressedMouseButtons);
17866 
17867 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17868 		}
17869 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
17870 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
17871 		}
17872 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
17873 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
17874 		}
17875 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
17876 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
17877 		}
17878 		/+
17879 			// FIXME
17880 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
17881 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17882 		}
17883 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
17884 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17885 		}
17886 		+/
17887 
17888 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
17889 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
17890 		}
17891 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
17892 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
17893 		}
17894 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
17895 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
17896 		}
17897 
17898 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
17899 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
17900 		}
17901 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
17902 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
17903 		}
17904 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
17905 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
17906 		}
17907 
17908 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
17909 			import std.stdio;
17910 			writeln(event.deltaY);
17911 		}
17912 
17913 		override void keyDown(NSEvent event) @selector("keyDown:") {
17914 			// the event may have multiple characters, and we send them all at once.
17915 			if (simpleWindow.handleCharEvent) {
17916 				auto chars = DeifiedNSString(event.characters);
17917 				foreach (dchar dc; chars.str)
17918 					simpleWindow.handleCharEvent(dc);
17919 			}
17920 
17921 			keyHelper(event, true);
17922 		}
17923 
17924 		override void keyUp(NSEvent event) @selector("keyUp:") {
17925 			keyHelper(event, false);
17926 		}
17927 
17928 		private void keyHelper(NSEvent event, bool pressed) {
17929 			if(simpleWindow.handleKeyEvent) {
17930 				KeyEvent ev;
17931 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
17932 				ev.pressed = pressed;
17933 				ev.hardwareCode = cast(ubyte) event.keyCode;
17934 				ev.modifierState = cast(uint) event.modifierFlags;
17935 				ev.window = simpleWindow;
17936 
17937 				simpleWindow.handleKeyEvent(ev);
17938 			}
17939 		}
17940 
17941 		override bool isFlipped() @selector("isFlipped") {
17942 			return true;
17943 		}
17944 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
17945 			return true;
17946 		}
17947 
17948 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
17949 			if(simpleWindow && simpleWindow.handlePulse)
17950 				simpleWindow.handlePulse();
17951 			/+
17952 			setNeedsDisplay = true;
17953 			+/
17954 		}
17955 	}
17956 
17957 private:
17958 	alias const(void)* CFStringRef;
17959 	alias const(void)* CFAllocatorRef;
17960 	alias const(void)* CFTypeRef;
17961 	alias const(void)* CGColorSpaceRef;
17962 	alias const(void)* CGImageRef;
17963 	alias ulong CGBitmapInfo;
17964 	alias NSGraphicsContext CGContextRef;
17965 
17966 	alias NSPoint CGPoint;
17967 	alias NSSize CGSize;
17968 	alias NSRect CGRect;
17969 
17970 	struct CGAffineTransform {
17971 		double a, b, c, d, tx, ty;
17972 	}
17973 
17974 	enum NSApplicationActivationPolicyRegular = 0;
17975 	enum NSBackingStoreBuffered = 2;
17976 	enum kCFStringEncodingUTF8 = 0x08000100;
17977 
17978 	enum : size_t {
17979 		NSBorderlessWindowMask = 0,
17980 		NSTitledWindowMask = 1 << 0,
17981 		NSClosableWindowMask = 1 << 1,
17982 		NSMiniaturizableWindowMask = 1 << 2,
17983 		NSResizableWindowMask = 1 << 3,
17984 		NSTexturedBackgroundWindowMask = 1 << 8
17985 	}
17986 
17987 	enum : ulong {
17988 		kCGImageAlphaNone,
17989 		kCGImageAlphaPremultipliedLast,
17990 		kCGImageAlphaPremultipliedFirst,
17991 		kCGImageAlphaLast,
17992 		kCGImageAlphaFirst,
17993 		kCGImageAlphaNoneSkipLast,
17994 		kCGImageAlphaNoneSkipFirst
17995 	}
17996 	enum : ulong {
17997 		kCGBitmapAlphaInfoMask = 0x1F,
17998 		kCGBitmapFloatComponents = (1 << 8),
17999 		kCGBitmapByteOrderMask = 0x7000,
18000 		kCGBitmapByteOrderDefault = (0 << 12),
18001 		kCGBitmapByteOrder16Little = (1 << 12),
18002 		kCGBitmapByteOrder32Little = (2 << 12),
18003 		kCGBitmapByteOrder16Big = (3 << 12),
18004 		kCGBitmapByteOrder32Big = (4 << 12)
18005 	}
18006 	enum CGPathDrawingMode {
18007 		kCGPathFill,
18008 		kCGPathEOFill,
18009 		kCGPathStroke,
18010 		kCGPathFillStroke,
18011 		kCGPathEOFillStroke
18012 	}
18013 	enum objc_AssociationPolicy : size_t {
18014 		OBJC_ASSOCIATION_ASSIGN = 0,
18015 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18016 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18017 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18018 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18019 	}
18020 
18021 	extern(C) {
18022 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18023 		void CGContextRelease(CGContextRef c);
18024 		ubyte* CGBitmapContextGetData(CGContextRef c);
18025 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18026 		size_t CGBitmapContextGetWidth(CGContextRef c);
18027 		size_t CGBitmapContextGetHeight(CGContextRef c);
18028 
18029 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18030 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18031 
18032 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18033 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18034 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18035 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18036 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18037 
18038 		void CGContextBeginPath(CGContextRef c);
18039 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18040 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18041 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18042 		void CGContextAddRect(CGContextRef c, CGRect rect);
18043 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18044 		void CGContextSaveGState(CGContextRef c);
18045 		void CGContextRestoreGState(CGContextRef c);
18046 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18047 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18048 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18049 
18050 		void CGImageRelease(CGImageRef image);
18051 	}
18052 } else static assert(0, "Unsupported operating system");
18053 
18054 
18055 version(OSXCocoa) {
18056 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18057 	//
18058 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18059 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18060 	//
18061 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18062 	// Probably won't even fully compile right now
18063 
18064 	private enum double PI = 3.14159265358979323;
18065 
18066 	alias NSWindow NativeWindowHandle;
18067 	alias void delegate(NSid) NativeEventHandler;
18068 
18069 	enum KEY_ESCAPE = 27;
18070 
18071 	mixin template NativeImageImplementation() {
18072 		CGContextRef context;
18073 		ubyte* rawData;
18074 
18075 		final:
18076 
18077 		void convertToRgbaBytes(ubyte[] where) {
18078 			assert(where.length == this.width * this.height * 4);
18079 
18080 			// if rawData had a length....
18081 			//assert(rawData.length == where.length);
18082 			for(long idx = 0; idx < where.length; idx += 4) {
18083 				auto alpha = rawData[idx + 3];
18084 				if(alpha == 255) {
18085 					where[idx + 0] = rawData[idx + 0]; // r
18086 					where[idx + 1] = rawData[idx + 1]; // g
18087 					where[idx + 2] = rawData[idx + 2]; // b
18088 					where[idx + 3] = rawData[idx + 3]; // a
18089 				} else {
18090 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18091 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18092 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18093 					where[idx + 3] = rawData[idx + 3]; // a
18094 
18095 				}
18096 			}
18097 		}
18098 
18099 		void setFromRgbaBytes(in ubyte[] where) {
18100 			// FIXME: this is probably wrong
18101 			assert(where.length == this.width * this.height * 4);
18102 
18103 			// if rawData had a length....
18104 			//assert(rawData.length == where.length);
18105 			for(long idx = 0; idx < where.length; idx += 4) {
18106 				auto alpha = where[idx + 3];
18107 				if(alpha == 255) {
18108 					rawData[idx + 0] = where[idx + 0]; // r
18109 					rawData[idx + 1] = where[idx + 1]; // g
18110 					rawData[idx + 2] = where[idx + 2]; // b
18111 					rawData[idx + 3] = where[idx + 3]; // a
18112 				} else if(alpha == 0) {
18113 					rawData[idx + 0] = 0;
18114 					rawData[idx + 1] = 0;
18115 					rawData[idx + 2] = 0;
18116 					rawData[idx + 3] = 0;
18117 				} else {
18118 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18119 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18120 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18121 					rawData[idx + 3] = where[idx + 3]; // a
18122 				}
18123 			}
18124 		}
18125 
18126 
18127 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18128 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18129 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18130 			CGColorSpaceRelease(colorSpace);
18131 			rawData = CGBitmapContextGetData(context);
18132 		}
18133 		void dispose() {
18134 			CGContextRelease(context);
18135 		}
18136 
18137 		void setPixel(int x, int y, Color c) {
18138 			auto offset = (y * width + x) * 4;
18139 			if (c.a == 255) {
18140 				rawData[offset + 0] = c.r;
18141 				rawData[offset + 1] = c.g;
18142 				rawData[offset + 2] = c.b;
18143 				rawData[offset + 3] = c.a;
18144 			} else {
18145 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18146 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18147 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18148 				rawData[offset + 3] = c.a;
18149 			}
18150 		}
18151 	}
18152 
18153 	mixin template NativeScreenPainterImplementation() {
18154 		CGContextRef context;
18155 		ubyte[4] _outlineComponents;
18156 		NSView view;
18157 
18158 		void create(PaintingHandle window) {
18159 			// this.destiny = window;
18160 			if(auto sw = cast(SimpleWindow) this.window) {
18161 				context = sw.drawingContext;
18162 				view = sw.view;
18163 			} else {
18164 				throw new NotYetImplementedException();
18165 			}
18166 		}
18167 
18168 		void dispose() {
18169 			view.setNeedsDisplay(true);
18170 		}
18171 
18172 		bool manualInvalidations;
18173 		void invalidateRect(Rectangle invalidRect) { }
18174 
18175 		// NotYetImplementedException
18176 		Size textSize(in char[] txt) {
18177 			return Size(32, 16); /*throw new NotYetImplementedException();*/
18178 		}
18179 		void rasterOp(RasterOp op) {
18180 		}
18181 		Pen _activePen;
18182 		Color _fillColor;
18183 		Rectangle _clipRectangle;
18184 		void setClipRectangle(int, int, int, int) {
18185 		}
18186 		void setFont(OperatingSystemFont) {
18187 		}
18188 		int fontHeight() {
18189 			return 14;
18190 		}
18191 
18192 		// end
18193 
18194 		void pen(Pen pen) {
18195 			_activePen = pen;
18196 			auto color = pen.color; // FIXME
18197 			double alphaComponent = color.a/255.0f;
18198 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
18199 
18200 			if (color.a != 255) {
18201 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
18202 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
18203 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18204 				_outlineComponents[3] = color.a;
18205 			} else {
18206 				_outlineComponents[0] = color.r;
18207 				_outlineComponents[1] = color.g;
18208 				_outlineComponents[2] = color.b;
18209 				_outlineComponents[3] = color.a;
18210 			}
18211 		}
18212 
18213 		@property void fillColor(Color color) {
18214 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18215 		}
18216 
18217 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18218 		// NotYetImplementedException for upper left/width/height
18219 			auto cgImage = CGBitmapContextCreateImage(image.context);
18220 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
18221 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18222 			CGImageRelease(cgImage);
18223 		}
18224 
18225 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
18226 		// FIXME: is this efficient?
18227 			auto cgImage = CGBitmapContextCreateImage(s.handle);
18228 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
18229 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18230 			CGImageRelease(cgImage);
18231 		}
18232 
18233 
18234 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18235 		// FIXME: alignment
18236 			if (_outlineComponents[3] != 0) {
18237 				CGContextSaveGState(context);
18238 				auto invAlpha = 1.0f/_outlineComponents[3];
18239 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18240 												  _outlineComponents[1]*invAlpha,
18241 												  _outlineComponents[2]*invAlpha,
18242 												  _outlineComponents[3]/255.0f);
18243 				CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
18244 // auto cfstr = cast(NSid)createCFString(text);
18245 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18246 // NSPoint(x, y), null);
18247 // CFRelease(cfstr);
18248 				CGContextRestoreGState(context);
18249 			}
18250 		}
18251 
18252 		void drawPixel(int x, int y) {
18253 			auto rawData = CGBitmapContextGetData(context);
18254 			auto width = CGBitmapContextGetWidth(context);
18255 			auto height = CGBitmapContextGetHeight(context);
18256 			auto offset = ((height - y - 1) * width + x) * 4;
18257 			rawData[offset .. offset+4] = _outlineComponents;
18258 		}
18259 
18260 		void drawLine(int x1, int y1, int x2, int y2) {
18261 			CGPoint[2] linePoints;
18262 			linePoints[0] = CGPoint(x1, y1);
18263 			linePoints[1] = CGPoint(x2, y2);
18264 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18265 		}
18266 
18267 		void drawRectangle(int x, int y, int width, int height) {
18268 			CGContextBeginPath(context);
18269 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18270 			CGContextAddRect(context, rect);
18271 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18272 		}
18273 
18274 		void drawEllipse(int x1, int y1, int x2, int y2) {
18275 			CGContextBeginPath(context);
18276 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18277 			CGContextAddEllipseInRect(context, rect);
18278 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18279 		}
18280 
18281 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18282 			// @@@BUG@@@ Does not support elliptic arc (width != height).
18283 			CGContextBeginPath(context);
18284 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18285 							start*PI/(180*64), finish*PI/(180*64), 0);
18286 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18287 		}
18288 
18289 		void drawPolygon(Point[] intPoints) {
18290 			CGContextBeginPath(context);
18291 			CGPoint[16] pointsBuffer;
18292 			CGPoint[] points;
18293 			if(intPoints.length <= pointsBuffer.length)
18294 				points = pointsBuffer[0 .. intPoints.length];
18295 			else
18296 				points = new CGPoint[](intPoints.length);
18297 
18298 			foreach(idx, pt; intPoints)
18299 				points[idx] = CGPoint(pt.x, pt.y);
18300 
18301 			CGContextAddLines(context, points.ptr, points.length);
18302 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18303 		}
18304 	}
18305 
18306 	private bool appInitialized = false;
18307 	void initializeApp() {
18308 		if(appInitialized)
18309 			return;
18310 		synchronized {
18311 			if(appInitialized)
18312 				return;
18313 
18314 			auto app = NSApp(); // ensure the is initialized
18315 
18316 			auto dg = AppDelegate.alloc;
18317 			globalAppDelegate = dg;
18318 			NSApp.delegate_ = dg;
18319 
18320 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
18321 
18322 			appInitialized = true;
18323 		}
18324 	}
18325 
18326 	mixin template NativeSimpleWindowImplementation() {
18327 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18328 			initializeApp();
18329 
18330 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18331 
18332 			auto window = NSWindow.alloc.initWithContentRect(
18333 				contentRect,
18334 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
18335 				NSBackingStoreType.buffered,
18336 				true
18337 			);
18338 
18339 			SimpleWindow.nativeMapping[cast(void*) window] = this;
18340 
18341 			window.title = MacString(title).borrow;
18342 
18343 			auto dg = SDWindowDelegate.alloc.init;
18344 			dg.simpleWindow = this;
18345 			window.delegate_ = dg;
18346 
18347 			auto view = SDGraphicsView.alloc.init;
18348 			assert(view !is null);
18349 			window.contentView = view;
18350 			this.view = view;
18351 			view.simpleWindow = this;
18352 
18353 			window.center();
18354 
18355 			window.makeKeyAndOrderFront(null);
18356 
18357 			// no need to make a bitmap on mac since everything is double buffered already
18358 
18359 			// create area to draw on.
18360 			createNewDrawingContext(width, height);
18361 
18362 			window.setBackgroundColor(NSColor.whiteColor);
18363 		}
18364 
18365 		void createNewDrawingContext(int width, int height) {
18366 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
18367 			if(this.drawingContext)
18368 				CGContextRelease(this.drawingContext);
18369 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18370 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18371 			CGColorSpaceRelease(colorSpace);
18372 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18373 			auto matrix = CGContextGetTextMatrix(drawingContext);
18374 			matrix.c = -matrix.c;
18375 			matrix.d = -matrix.d;
18376 			CGContextSetTextMatrix(drawingContext, matrix);
18377 
18378 		}
18379 
18380 		void dispose() {
18381 			closeWindow();
18382 			// window.release(); // closing the window does this automatically i think
18383 		}
18384 		void closeWindow() {
18385 			if(timer)
18386 				timer.invalidate();
18387 			window.close();
18388 		}
18389 
18390 		ScreenPainter getPainter(bool manualInvalidations) {
18391 			return ScreenPainter(this, this.window, manualInvalidations);
18392 		}
18393 
18394 		NSWindow window;
18395 		NSTimer timer;
18396 		NSView view;
18397 		CGContextRef drawingContext;
18398 	}
18399 }
18400 
18401 version(without_opengl) {} else
18402 extern(System) nothrow @nogc {
18403 	//enum uint GL_VERSION = 0x1F02;
18404 	//const(char)* glGetString (/*GLenum*/uint);
18405 	version(X11) {
18406 	static if (!SdpyIsUsingIVGLBinds) {
18407 
18408 		enum GLX_X_RENDERABLE = 0x8012;
18409 		enum GLX_DRAWABLE_TYPE = 0x8010;
18410 		enum GLX_RENDER_TYPE = 0x8011;
18411 		enum GLX_X_VISUAL_TYPE = 0x22;
18412 		enum GLX_TRUE_COLOR = 0x8002;
18413 		enum GLX_WINDOW_BIT = 0x00000001;
18414 		enum GLX_RGBA_BIT = 0x00000001;
18415 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18416 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18417 		enum GLX_SAMPLES = 0x186a1;
18418 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18419 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18420 	}
18421 
18422 		// GLX_EXT_swap_control
18423 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18424 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18425 
18426 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18427 		extern(System) {
18428 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18429 		}
18430 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18431 
18432 		// this made public so we don't have to get it again and again
18433 		public bool glXCreateContextAttribsARB_present () {
18434 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18435 				// get it
18436 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18437 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18438 			}
18439 			return (glXCreateContextAttribsARBFn !is null);
18440 		}
18441 
18442 		// this made public so we don't have to get it again and again
18443 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18444 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18445 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18446 		}
18447 
18448 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18449 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18450 
18451 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18452 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18453 			if (_glx_swapInterval_fn is null) {
18454 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18455 				if (_glx_swapInterval_fn is null) {
18456 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18457 					return;
18458 				}
18459 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
18460 			}
18461 
18462 			if(glXSwapIntervalMESA is null) {
18463 				// it seems to require both to actually take effect on many computers
18464 				// idk why
18465 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18466 				if(glXSwapIntervalMESA is null)
18467 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18468 			}
18469 
18470 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18471 				glXSwapIntervalMESA(wait ? 1 : 0);
18472 
18473 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18474 		}
18475 	} else version(Windows) {
18476 	static if (!SdpyIsUsingIVGLBinds) {
18477 	enum GL_TRUE = 1;
18478 	enum GL_FALSE = 0;
18479 
18480 	public void* glbindGetProcAddress (const(char)* name) {
18481 		void* res = wglGetProcAddress(name);
18482 		if (res is null) {
18483 			/+
18484 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18485 			import core.sys.windows.windef, core.sys.windows.winbase;
18486 			__gshared HINSTANCE dll = null;
18487 			if (dll is null) {
18488 				dll = LoadLibraryA("opengl32.dll");
18489 				if (dll is null) return null; // <32, but idc
18490 			}
18491 			res = GetProcAddress(dll, name);
18492 			+/
18493 			res = GetProcAddress(gl.libHandle, name);
18494 		}
18495 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18496 		return res;
18497 	}
18498 	}
18499 
18500 
18501  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18502         void wglSetVSync(bool wait) {
18503 		if(wglSwapIntervalEXT is null) {
18504 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18505 			if(wglSwapIntervalEXT is null)
18506 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18507 		}
18508 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18509 			return;
18510 
18511 		wglSwapIntervalEXT(wait ? 1 : 0);
18512 	}
18513 
18514 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18515 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18516 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18517 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18518 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18519 
18520 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18521 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18522 
18523 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18524 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18525 
18526 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18527 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18528 
18529 		void wglInitOtherFunctions () {
18530 			if (wglCreateContextAttribsARB is null) {
18531 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18532 			}
18533 		}
18534 	}
18535 
18536 	static if (!SdpyIsUsingIVGLBinds) {
18537 
18538 	interface GL {
18539 		extern(System) @nogc nothrow:
18540 
18541 		void glGetIntegerv(int, void*);
18542 		void glMatrixMode(int);
18543 		void glPushMatrix();
18544 		void glLoadIdentity();
18545 		void glOrtho(double, double, double, double, double, double);
18546 		void glFrustum(double, double, double, double, double, double);
18547 
18548 		void glPopMatrix();
18549 		void glEnable(int);
18550 		void glDisable(int);
18551 		void glClear(int);
18552 		void glBegin(int);
18553 		void glVertex2f(float, float);
18554 		void glVertex3f(float, float, float);
18555 		void glEnd();
18556 		void glColor3b(byte, byte, byte);
18557 		void glColor3ub(ubyte, ubyte, ubyte);
18558 		void glColor4b(byte, byte, byte, byte);
18559 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18560 		void glColor3i(int, int, int);
18561 		void glColor3ui(uint, uint, uint);
18562 		void glColor4i(int, int, int, int);
18563 		void glColor4ui(uint, uint, uint, uint);
18564 		void glColor3f(float, float, float);
18565 		void glColor4f(float, float, float, float);
18566 		void glTranslatef(float, float, float);
18567 		void glScalef(float, float, float);
18568 		version(X11) {
18569 			void glSecondaryColor3b(byte, byte, byte);
18570 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18571 			void glSecondaryColor3i(int, int, int);
18572 			void glSecondaryColor3ui(uint, uint, uint);
18573 			void glSecondaryColor3f(float, float, float);
18574 		}
18575 
18576 		void glDrawElements(int, int, int, void*);
18577 
18578 		void glRotatef(float, float, float, float);
18579 
18580 		uint glGetError();
18581 
18582 		void glDeleteTextures(int, uint*);
18583 
18584 
18585 		void glRasterPos2i(int, int);
18586 		void glDrawPixels(int, int, uint, uint, void*);
18587 		void glClearColor(float, float, float, float);
18588 
18589 
18590 		void glPixelStorei(uint, int);
18591 
18592 		void glGenTextures(uint, uint*);
18593 		void glBindTexture(int, int);
18594 		void glTexParameteri(uint, uint, int);
18595 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18596 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
18597 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18598 			/*GLsizei*/int width, /*GLsizei*/int height,
18599 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18600 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18601 
18602 		void glLineWidth(int);
18603 
18604 
18605 		void glTexCoord2f(float, float);
18606 		void glVertex2i(int, int);
18607 		void glBlendFunc (int, int);
18608 		void glDepthFunc (int);
18609 		void glViewport(int, int, int, int);
18610 
18611 		void glClearDepth(double);
18612 
18613 		void glReadBuffer(uint);
18614 		void glReadPixels(int, int, int, int, int, int, void*);
18615 
18616 		void glFlush();
18617 		void glFinish();
18618 
18619 		version(Windows) {
18620 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18621 			HGLRC wglCreateContext(HDC);
18622 			HGLRC wglCreateLayerContext(HDC, int);
18623 			BOOL wglDeleteContext(HGLRC);
18624 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18625 			HGLRC wglGetCurrentContext();
18626 			HDC wglGetCurrentDC();
18627 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18628 			PROC wglGetProcAddress(LPCSTR);
18629 			BOOL wglMakeCurrent(HDC, HGLRC);
18630 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18631 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18632 			BOOL wglShareLists(HGLRC, HGLRC);
18633 			BOOL wglSwapLayerBuffers(HDC, UINT);
18634 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18635 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18636 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18637 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18638 		}
18639 
18640 	}
18641 
18642 	interface GL3 {
18643 		extern(System) @nogc nothrow:
18644 
18645 		void glGenVertexArrays(GLsizei, GLuint*);
18646 		void glBindVertexArray(GLuint);
18647 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18648 		void glGenerateMipmap(GLenum);
18649 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18650 		void glStencilMask(GLuint);
18651 		void glStencilFunc(GLenum, GLint, GLuint);
18652 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18653 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18654 		GLuint glCreateProgram();
18655 		GLuint glCreateShader(GLenum);
18656 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18657 		void glCompileShader(GLuint);
18658 		void glGetShaderiv(GLuint, GLenum, GLint*);
18659 		void glAttachShader(GLuint, GLuint);
18660 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18661 		void glLinkProgram(GLuint);
18662 		void glGetProgramiv(GLuint, GLenum, GLint*);
18663 		void glDeleteProgram(GLuint);
18664 		void glDeleteShader(GLuint);
18665 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18666 		void glGenBuffers(GLsizei, GLuint*);
18667 
18668 		void glUniform1f(GLint location, GLfloat v0);
18669 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18670 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18671 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18672 		void glUniform1i(GLint location, GLint v0);
18673 		void glUniform2i(GLint location, GLint v0, GLint v1);
18674 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18675 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18676 		void glUniform1ui(GLint location, GLuint v0);
18677 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18678 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18679 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18680 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18681 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18682 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18683 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18684 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18685 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18686 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18687 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18688 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18689 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18690 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18691 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18692 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18693 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18694 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18695 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18696 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18697 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18698 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18699 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18700 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18701 
18702 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18703 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18704 		void glDrawArrays(GLenum, GLint, GLsizei);
18705 		void glStencilOp(GLenum, GLenum, GLenum);
18706 		void glUseProgram(GLuint);
18707 		void glCullFace(GLenum);
18708 		void glFrontFace(GLenum);
18709 		void glActiveTexture(GLenum);
18710 		void glBindBuffer(GLenum, GLuint);
18711 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18712 		void glEnableVertexAttribArray(GLuint);
18713 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18714 		void glUniform1i(GLint, GLint);
18715 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18716 		void glDisableVertexAttribArray(GLuint);
18717 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18718 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18719 		void glLogicOp (GLenum opcode);
18720 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18721 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18722 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18723 		GLenum glCheckFramebufferStatus (GLenum target);
18724 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18725 	}
18726 
18727 	interface GL4 {
18728 		extern(System) @nogc nothrow:
18729 
18730 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18731 			/*GLsizei*/int width, /*GLsizei*/int height,
18732 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18733 	}
18734 
18735 	interface GLU {
18736 		extern(System) @nogc nothrow:
18737 
18738 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18739 		void gluPerspective(double, double, double, double);
18740 
18741 		char* gluErrorString(uint);
18742 	}
18743 
18744 
18745 	enum GL_RED = 0x1903;
18746 	enum GL_ALPHA = 0x1906;
18747 
18748 	enum uint GL_FRONT = 0x0404;
18749 
18750 	enum uint GL_BLEND = 0x0be2;
18751 	enum uint GL_LEQUAL = 0x0203;
18752 
18753 
18754 	enum uint GL_RGB = 0x1907;
18755 	enum uint GL_BGRA = 0x80e1;
18756 	enum uint GL_RGBA = 0x1908;
18757 	enum uint GL_TEXTURE_2D =   0x0DE1;
18758 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18759 	enum uint GL_NEAREST = 0x2600;
18760 	enum uint GL_LINEAR = 0x2601;
18761 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18762 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18763 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18764 	enum uint GL_REPEAT = 0x2901;
18765 	enum uint GL_CLAMP = 0x2900;
18766 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18767 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18768 	enum uint GL_DECAL = 0x2101;
18769 	enum uint GL_MODULATE = 0x2100;
18770 	enum uint GL_TEXTURE_ENV = 0x2300;
18771 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18772 	enum uint GL_REPLACE = 0x1E01;
18773 	enum uint GL_LIGHTING = 0x0B50;
18774 	enum uint GL_DITHER = 0x0BD0;
18775 
18776 	enum uint GL_NO_ERROR = 0;
18777 
18778 
18779 
18780 	enum int GL_VIEWPORT = 0x0BA2;
18781 	enum int GL_MODELVIEW = 0x1700;
18782 	enum int GL_TEXTURE = 0x1702;
18783 	enum int GL_PROJECTION = 0x1701;
18784 	enum int GL_DEPTH_TEST = 0x0B71;
18785 
18786 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18787 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18788 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18789 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18790 
18791 	enum int GL_POINTS = 0x0000;
18792 	enum int GL_LINES =  0x0001;
18793 	enum int GL_LINE_LOOP = 0x0002;
18794 	enum int GL_LINE_STRIP = 0x0003;
18795 	enum int GL_TRIANGLES = 0x0004;
18796 	enum int GL_TRIANGLE_STRIP = 5;
18797 	enum int GL_TRIANGLE_FAN = 6;
18798 	enum int GL_QUADS = 7;
18799 	enum int GL_QUAD_STRIP = 8;
18800 	enum int GL_POLYGON = 9;
18801 
18802 	alias GLvoid = void;
18803 	alias GLboolean = ubyte;
18804 	alias GLint = int;
18805 	alias GLuint = uint;
18806 	alias GLenum = uint;
18807 	alias GLchar = char;
18808 	alias GLsizei = int;
18809 	alias GLfloat = float;
18810 	alias GLintptr = size_t;
18811 	alias GLsizeiptr = ptrdiff_t;
18812 
18813 
18814 	enum uint GL_INVALID_ENUM = 0x0500;
18815 
18816 	enum uint GL_ZERO = 0;
18817 	enum uint GL_ONE = 1;
18818 
18819 	enum uint GL_BYTE = 0x1400;
18820 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18821 	enum uint GL_SHORT = 0x1402;
18822 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18823 	enum uint GL_INT = 0x1404;
18824 	enum uint GL_UNSIGNED_INT = 0x1405;
18825 	enum uint GL_FLOAT = 0x1406;
18826 	enum uint GL_2_BYTES = 0x1407;
18827 	enum uint GL_3_BYTES = 0x1408;
18828 	enum uint GL_4_BYTES = 0x1409;
18829 	enum uint GL_DOUBLE = 0x140A;
18830 
18831 	enum uint GL_STREAM_DRAW = 0x88E0;
18832 
18833 	enum uint GL_CCW = 0x0901;
18834 
18835 	enum uint GL_STENCIL_TEST = 0x0B90;
18836 	enum uint GL_SCISSOR_TEST = 0x0C11;
18837 
18838 	enum uint GL_EQUAL = 0x0202;
18839 	enum uint GL_NOTEQUAL = 0x0205;
18840 
18841 	enum uint GL_ALWAYS = 0x0207;
18842 	enum uint GL_KEEP = 0x1E00;
18843 
18844 	enum uint GL_INCR = 0x1E02;
18845 
18846 	enum uint GL_INCR_WRAP = 0x8507;
18847 	enum uint GL_DECR_WRAP = 0x8508;
18848 
18849 	enum uint GL_CULL_FACE = 0x0B44;
18850 	enum uint GL_BACK = 0x0405;
18851 
18852 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18853 	enum uint GL_VERTEX_SHADER = 0x8B31;
18854 
18855 	enum uint GL_COMPILE_STATUS = 0x8B81;
18856 	enum uint GL_LINK_STATUS = 0x8B82;
18857 
18858 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18859 
18860 	enum uint GL_STATIC_DRAW = 0x88E4;
18861 
18862 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18863 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18864 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18865 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18866 
18867 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18868 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18869 
18870 	enum uint GL_TEXTURE0 = 0x84C0U;
18871 	enum uint GL_TEXTURE1 = 0x84C1U;
18872 
18873 	enum uint GL_ARRAY_BUFFER = 0x8892;
18874 
18875 	enum uint GL_SRC_COLOR = 0x0300;
18876 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18877 	enum uint GL_SRC_ALPHA = 0x0302;
18878 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18879 	enum uint GL_DST_ALPHA = 0x0304;
18880 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18881 	enum uint GL_DST_COLOR = 0x0306;
18882 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18883 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18884 
18885 	enum uint GL_INVERT = 0x150AU;
18886 
18887 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18888 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18889 
18890 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18891 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18892 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18893 
18894 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18895 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18896 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18897 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18898 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18899 
18900 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18901 	enum uint GL_CLEAR = 0x1500U;
18902 	enum uint GL_COPY = 0x1503U;
18903 	enum uint GL_XOR = 0x1506U;
18904 
18905 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18906 
18907 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18908 
18909 	}
18910 }
18911 
18912 /++
18913 	History:
18914 		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.
18915 +/
18916 __gshared bool gluSuccessfullyLoaded = true;
18917 
18918 version(without_opengl) {} else {
18919 static if(!SdpyIsUsingIVGLBinds) {
18920 	version(Windows) {
18921 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18922 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18923 	} else {
18924 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18925 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18926 	}
18927 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18928 
18929 
18930 	shared static this() {
18931 		gl.loadDynamicLibrary();
18932 
18933 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18934 		// unless those functions are actually used
18935 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18936 		glu.loadDynamicLibrary();
18937 	}
18938 }
18939 }
18940 
18941 /++
18942 	Convenience method for converting D arrays to opengl buffer data
18943 
18944 	I would LOVE to overload it with the original glBufferData, but D won't
18945 	let me since glBufferData is a function pointer :(
18946 
18947 	Added: August 25, 2020 (version 8.5)
18948 +/
18949 version(without_opengl) {} else
18950 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18951 	glBufferData(target, data.length, data.ptr, usage);
18952 }
18953 
18954 /+
18955 /++
18956 	A matrix for simple uses that easily integrates with [OpenGlShader].
18957 
18958 	Might not be useful to you since it only as some simple functions and
18959 	probably isn't that fast.
18960 
18961 	Note it uses an inline static array for its storage, so copying it
18962 	may be expensive.
18963 +/
18964 struct BasicMatrix(int columns, int rows, T = float) {
18965 	import core.stdc.math;
18966 
18967 	T[columns * rows] data = 0.0;
18968 
18969 	/++
18970 		Basic operations that operate *in place*.
18971 	+/
18972 	void translate() {
18973 
18974 	}
18975 
18976 	/// ditto
18977 	void scale() {
18978 
18979 	}
18980 
18981 	/// ditto
18982 	void rotate() {
18983 
18984 	}
18985 
18986 	/++
18987 
18988 	+/
18989 	static if(columns == rows)
18990 	static BasicMatrix identity() {
18991 		BasicMatrix m;
18992 		foreach(i; 0 .. columns)
18993 			data[0 + i + i * columns] = 1.0;
18994 		return m;
18995 	}
18996 
18997 	static BasicMatrix ortho() {
18998 		return BasicMatrix.init;
18999 	}
19000 }
19001 +/
19002 
19003 /++
19004 	Convenience class for using opengl shaders.
19005 
19006 	Ensure that you've loaded opengl 3+ and set your active
19007 	context before trying to use this.
19008 
19009 	Added: August 25, 2020 (version 8.5)
19010 +/
19011 version(without_opengl) {} else
19012 final class OpenGlShader {
19013 	private int shaderProgram_;
19014 	private @property void shaderProgram(int a) {
19015 		shaderProgram_ = a;
19016 	}
19017 	/// Get the program ID for use in OpenGL functions.
19018 	public @property int shaderProgram() {
19019 		return shaderProgram_;
19020 	}
19021 
19022 	/++
19023 
19024 	+/
19025 	static struct Source {
19026 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19027 		string code; ///
19028 	}
19029 
19030 	/++
19031 		Helper method to just compile some shader code and check for errors
19032 		while you do glCreateShader, etc. on the outside yourself.
19033 
19034 		This just does `glShaderSource` and `glCompileShader` for the given code.
19035 
19036 		If you the OpenGlShader class constructor, you never need to call this yourself.
19037 	+/
19038 	static void compile(int sid, Source code) {
19039 		const(char)*[1] buffer;
19040 		int[1] lengthBuffer;
19041 
19042 		buffer[0] = code.code.ptr;
19043 		lengthBuffer[0] = cast(int) code.code.length;
19044 
19045 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19046 		glCompileShader(sid);
19047 
19048 		int success;
19049 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19050 		if(!success) {
19051 			char[512] info;
19052 			int len;
19053 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19054 
19055 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19056 		}
19057 	}
19058 
19059 	/++
19060 		Calls `glLinkProgram` and throws if error a occurs.
19061 
19062 		If you the OpenGlShader class constructor, you never need to call this yourself.
19063 	+/
19064 	static void link(int shaderProgram) {
19065 		glLinkProgram(shaderProgram);
19066 		int success;
19067 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19068 		if(!success) {
19069 			char[512] info;
19070 			int len;
19071 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19072 
19073 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19074 		}
19075 	}
19076 
19077 	/++
19078 		Constructs the shader object by calling `glCreateProgram`, then
19079 		compiling each given [Source], and finally, linking them together.
19080 
19081 		Throws: on compile or link failure.
19082 	+/
19083 	this(Source[] codes...) {
19084 		shaderProgram = glCreateProgram();
19085 
19086 		int[16] shadersBufferStack;
19087 
19088 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19089 			shadersBufferStack[0 .. codes.length] :
19090 			new int[](codes.length);
19091 
19092 		foreach(idx, code; codes) {
19093 			shadersBuffer[idx] = glCreateShader(code.type);
19094 
19095 			compile(shadersBuffer[idx], code);
19096 
19097 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19098 		}
19099 
19100 		link(shaderProgram);
19101 
19102 		foreach(s; shadersBuffer)
19103 			glDeleteShader(s);
19104 	}
19105 
19106 	/// Calls `glUseProgram(this.shaderProgram)`
19107 	void use() {
19108 		glUseProgram(this.shaderProgram);
19109 	}
19110 
19111 	/// Deletes the program.
19112 	void delete_() {
19113 		glDeleteProgram(shaderProgram);
19114 		shaderProgram = 0;
19115 	}
19116 
19117 	/++
19118 		[OpenGlShader.uniforms].name gives you one of these.
19119 
19120 		You can get the id out of it or just assign
19121 	+/
19122 	static struct Uniform {
19123 		/// the id passed to glUniform*
19124 		int id;
19125 
19126 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19127 		void opAssign(float x, float y, float z, float w) {
19128 			if(id != -1)
19129 			glUniform4f(id, x, y, z, w);
19130 		}
19131 
19132 		void opAssign(float x) {
19133 			if(id != -1)
19134 			glUniform1f(id, x);
19135 		}
19136 
19137 		void opAssign(float x, float y) {
19138 			if(id != -1)
19139 			glUniform2f(id, x, y);
19140 		}
19141 
19142 		void opAssign(T)(T t) {
19143 			t.glUniform(id);
19144 		}
19145 	}
19146 
19147 	static struct UniformsHelper {
19148 		OpenGlShader _shader;
19149 
19150 		@property Uniform opDispatch(string name)() {
19151 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19152 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19153 			//if(i == -1)
19154 				//throw new Exception("Could not find uniform " ~ name);
19155 			return Uniform(i);
19156 		}
19157 
19158 		@property void opDispatch(string name, T)(T t) {
19159 			Uniform f = this.opDispatch!name;
19160 			t.glUniform(f);
19161 		}
19162 	}
19163 
19164 	/++
19165 		Gives access to the uniforms through dot access.
19166 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19167 	+/
19168 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19169 }
19170 
19171 version(without_opengl) {} else {
19172 /++
19173 	A static container of experimental types and value constructors for opengl 3+ shaders.
19174 
19175 
19176 	You can declare variables like:
19177 
19178 	```
19179 	OGL.vec3f something;
19180 	```
19181 
19182 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19183 
19184 	```
19185 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19186 	```
19187 
19188 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19189 
19190 
19191 	History:
19192 		Added December 7, 2021. Not yet stable.
19193 +/
19194 final class OGL {
19195 	static:
19196 
19197 	private template typeFromSpecifier(string specifier) {
19198 		static if(specifier == "f")
19199 			alias typeFromSpecifier = GLfloat;
19200 		else static if(specifier == "i")
19201 			alias typeFromSpecifier = GLint;
19202 		else static if(specifier == "ui")
19203 			alias typeFromSpecifier = GLuint;
19204 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19205 	}
19206 
19207 	private template CommonType(T...) {
19208 		static if(T.length == 1)
19209 			alias CommonType = T[0];
19210 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19211 			alias CommonType = CommonType!(C, T[2 .. $]);
19212 	}
19213 
19214 	private template typesToSpecifier(T...) {
19215 		static if(is(CommonType!T == float))
19216 			enum typesToSpecifier = "f";
19217 		else static if(is(CommonType!T == int))
19218 			enum typesToSpecifier = "i";
19219 		else static if(is(CommonType!T == uint))
19220 			enum typesToSpecifier = "ui";
19221 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19222 	}
19223 
19224 	private template genNames(size_t dim, size_t dim2 = 0) {
19225 		string helper() {
19226 			string s;
19227 			if(dim2) {
19228 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19229 			} else {
19230 				if(dim > 0) s ~= "type x = 0;";
19231 				if(dim > 1) s ~= "type y = 0;";
19232 				if(dim > 2) s ~= "type z = 0;";
19233 				if(dim > 3) s ~= "type w = 0;";
19234 			}
19235 			return s;
19236 		}
19237 
19238 		enum genNames = helper();
19239 	}
19240 
19241 	// there's vec, arrays of vec, mat, and arrays of mat
19242 	template opDispatch(string name)
19243 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19244 	{
19245 		static if(name[4] == 'x') {
19246 			enum dimX = cast(int) (name[3] - '0');
19247 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19248 
19249 			enum dimY = cast(int) (name[5] - '0');
19250 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19251 
19252 			enum isArray = name[$ - 1] == 'v';
19253 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19254 			alias type = typeFromSpecifier!typeSpecifier;
19255 		} else {
19256 			enum dim = cast(int) (name[3] - '0');
19257 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19258 			enum isArray = name[$ - 1] == 'v';
19259 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19260 			alias type = typeFromSpecifier!typeSpecifier;
19261 		}
19262 
19263 		align(1)
19264 		struct opDispatch {
19265 			align(1):
19266 			static if(name[4] == 'x')
19267 				mixin(genNames!(dimX, dimY));
19268 			else
19269 				mixin(genNames!dim);
19270 
19271 			private void glUniform(OpenGlShader.Uniform assignTo) {
19272 				glUniform(assignTo.id);
19273 			}
19274 			private void glUniform(int assignTo) {
19275 				static if(name[4] == 'x') {
19276 					// FIXME
19277 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19278 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19279 				} else
19280 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19281 			}
19282 		}
19283 	}
19284 
19285 	auto vec(T...)(T members) {
19286 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19287 	}
19288 }
19289 }
19290 
19291 version(linux) {
19292 	version(with_eventloop) {} else {
19293 		private int epollFd = -1;
19294 		void prepareEventLoop() {
19295 			if(epollFd != -1)
19296 				return; // already initialized, no need to do it again
19297 			import ep = core.sys.linux.epoll;
19298 
19299 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19300 			if(epollFd == -1)
19301 				throw new Exception("epoll create failure");
19302 		}
19303 	}
19304 } else version(Posix) {
19305 	void prepareEventLoop() {}
19306 }
19307 
19308 version(X11) {
19309 	import core.stdc.locale : LC_ALL; // rdmd fix
19310 	__gshared bool sdx_isUTF8Locale;
19311 
19312 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19313 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19314 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19315 	// anal magic is here. I (Ketmar) hope you like it.
19316 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19317 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19318 	// later.
19319 
19320 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19321 	shared static this () {
19322 		if(!librariesSuccessfullyLoaded)
19323 			return;
19324 
19325 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19326 
19327 		// this doesn't hurt; it may add some locking, but the speed is still
19328 		// allows doing 60 FPS videogames; also, ignore the result, as most
19329 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19330 		// never seen this failing).
19331 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19332 
19333 		setlocale(LC_ALL, "");
19334 		// check if out locale is UTF-8
19335 		auto lct = setlocale(LC_CTYPE, null);
19336 		if (lct is null) {
19337 			sdx_isUTF8Locale = false;
19338 		} else {
19339 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19340 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19341 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19342 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19343 				{
19344 					sdx_isUTF8Locale = true;
19345 					break;
19346 				}
19347 			}
19348 		}
19349 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19350 	}
19351 }
19352 
19353 class ExperimentalTextComponent2 {
19354 	/+
19355 		Stage 1: get it working monospace
19356 		Stage 2: use proportional font
19357 		Stage 3: allow changes in inline style
19358 		Stage 4: allow new fonts and sizes in the middle
19359 		Stage 5: optimize gap buffer
19360 		Stage 6: optimize layout
19361 		Stage 7: word wrap
19362 		Stage 8: justification
19363 		Stage 9: editing, selection, etc.
19364 
19365 			Operations:
19366 				insert text
19367 				overstrike text
19368 				select
19369 				cut
19370 				modify
19371 	+/
19372 
19373 	/++
19374 		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.
19375 	+/
19376 	this(SimpleWindow window) {
19377 		this.window = window;
19378 	}
19379 
19380 	private SimpleWindow window;
19381 
19382 
19383 	/++
19384 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19385 		representing the internal parts. The first pass is focused on the x parameter, then the
19386 		renderer is responsible for going back to the parts in the current line and calling
19387 		adjustDownForAscent to change the y params.
19388 	+/
19389 	static interface ComponentRenderHelper {
19390 
19391 		/+
19392 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19393 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19394 			to move (adjust y to make room for new line) until you get back to the same position,
19395 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19396 
19397 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19398 			once you reach something that is unchanged, you can stop.
19399 		+/
19400 
19401 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19402 
19403 		int ascent() const;
19404 		int descent() const;
19405 
19406 		int advance() const;
19407 
19408 		bool endsWithExplititLineBreak() const;
19409 	}
19410 
19411 	static interface RenderResult {
19412 		/++
19413 			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.
19414 		+/
19415 		void popFront();
19416 		@property bool empty() const;
19417 		@property ComponentRenderHelper front() const;
19418 
19419 		void repositionForNextLine(Point baseline, int availableWidth);
19420 	}
19421 
19422 	static interface ComponentInFlow {
19423 		void draw(ScreenPainter painter);
19424 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19425 
19426 		bool startsWithExplicitLineBreak() const;
19427 	}
19428 
19429 	static class TextFlowComponent : ComponentInFlow {
19430 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19431 
19432 		Color foreground;
19433 		Color background;
19434 
19435 		OperatingSystemFont font; // should NEVER be null
19436 
19437 		ubyte attributes; // underline, strike through, display on new block
19438 
19439 		version(Windows)
19440 			const(wchar)[] content;
19441 		else
19442 			const(char)[] content; // this should NEVER have a newline, except at the end
19443 
19444 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19445 
19446 		// could prolly put some spacing around it too like margin / padding
19447 
19448 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19449 			in { assert(font !is null);
19450 			     assert(!font.isNull); }
19451 			do
19452 		{
19453 			this.foreground = f;
19454 			this.background = b;
19455 			this.font = font;
19456 
19457 			this.attributes = attr;
19458 			version(Windows) {
19459 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19460 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19461 				auto buffer = new wchar[](sz);
19462 				this.content = makeWindowsString(c, buffer, conversionFlags);
19463 			} else {
19464 				this.content = c.dup;
19465 			}
19466 		}
19467 
19468 		void draw(ScreenPainter painter) {
19469 			painter.setFont(this.font);
19470 			painter.outlineColor = this.foreground;
19471 			painter.fillColor = Color.transparent;
19472 			foreach(rendered; this.rendered) {
19473 				// the component works in term of baseline,
19474 				// but the painter works in term of upper left bounding box
19475 				// so need to translate that
19476 
19477 				if(this.background.a) {
19478 					painter.fillColor = this.background;
19479 					painter.outlineColor = this.background;
19480 
19481 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19482 
19483 					painter.outlineColor = this.foreground;
19484 					painter.fillColor = Color.transparent;
19485 				}
19486 
19487 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19488 
19489 				// FIXME: strike through, underline, highlight selection, etc.
19490 			}
19491 		}
19492 	}
19493 
19494 	// I could split the parts into words on render
19495 	// for easier word-wrap, each one being an unbreakable "inline-block"
19496 	private TextFlowComponent[] parts;
19497 	private int needsRerenderFrom;
19498 
19499 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19500 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19501 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19502 	}
19503 
19504 	static struct RenderedComponent {
19505 		int startX;
19506 		int startY;
19507 		short width;
19508 		// 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!
19509 		// for individual chars in here you've gotta process on demand
19510 		version(Windows)
19511 			const(wchar)[] slice;
19512 		else
19513 			const(char)[] slice;
19514 	}
19515 
19516 
19517 	void rerender(Rectangle boundingBox) {
19518 		Point baseline = boundingBox.upperLeft;
19519 
19520 		this.boundingBox.left = boundingBox.left;
19521 		this.boundingBox.top = boundingBox.top;
19522 
19523 		auto remainingParts = parts;
19524 
19525 		int largestX;
19526 
19527 
19528 		foreach(part; parts)
19529 			part.font.prepareContext(window);
19530 		scope(exit)
19531 		foreach(part; parts)
19532 			part.font.releaseContext();
19533 
19534 		calculateNextLine:
19535 
19536 		int nextLineHeight = 0;
19537 		int nextBiggestDescent = 0;
19538 
19539 		foreach(part; remainingParts) {
19540 			auto height = part.font.ascent;
19541 			if(height > nextLineHeight)
19542 				nextLineHeight = height;
19543 			if(part.font.descent > nextBiggestDescent)
19544 				nextBiggestDescent = part.font.descent;
19545 			if(part.content.length && part.content[$-1] == '\n')
19546 				break;
19547 		}
19548 
19549 		baseline.y += nextLineHeight;
19550 		auto lineStart = baseline;
19551 
19552 		while(remainingParts.length) {
19553 			remainingParts[0].rendered = null;
19554 
19555 			bool eol;
19556 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19557 				eol = true;
19558 
19559 			// FIXME: word wrap
19560 			auto font = remainingParts[0].font;
19561 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19562 			auto width = font.stringWidth(slice, window);
19563 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19564 
19565 			remainingParts = remainingParts[1 .. $];
19566 			baseline.x += width;
19567 
19568 			if(eol) {
19569 				baseline.y += nextBiggestDescent;
19570 				if(baseline.x > largestX)
19571 					largestX = baseline.x;
19572 				baseline.x = lineStart.x;
19573 				goto calculateNextLine;
19574 			}
19575 		}
19576 
19577 		if(baseline.x > largestX)
19578 			largestX = baseline.x;
19579 
19580 		this.boundingBox.right = largestX;
19581 		this.boundingBox.bottom = baseline.y;
19582 	}
19583 
19584 	// you must call rerender first!
19585 	void draw(ScreenPainter painter) {
19586 		foreach(part; parts) {
19587 			part.draw(painter);
19588 		}
19589 	}
19590 
19591 	struct IdentifyResult {
19592 		TextFlowComponent part;
19593 		int charIndexInPart;
19594 		int totalCharIndex = -1; // if this is -1, it just means the end
19595 
19596 		Rectangle boundingBox;
19597 	}
19598 
19599 	IdentifyResult identify(Point pt, bool exact = false) {
19600 		if(parts.length == 0)
19601 			return IdentifyResult(null, 0);
19602 
19603 		if(pt.y < boundingBox.top) {
19604 			if(exact)
19605 				return IdentifyResult(null, 1);
19606 			return IdentifyResult(parts[0], 0);
19607 		}
19608 		if(pt.y > boundingBox.bottom) {
19609 			if(exact)
19610 				return IdentifyResult(null, 2);
19611 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19612 		}
19613 
19614 		int tci = 0;
19615 
19616 		// I should probably like binary search this or something...
19617 		foreach(ref part; parts) {
19618 			foreach(rendered; part.rendered) {
19619 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19620 				if(rect.contains(pt)) {
19621 					auto x = pt.x - rendered.startX;
19622 					auto estimatedIdx = x / part.font.averageWidth;
19623 
19624 					if(estimatedIdx < 0)
19625 						estimatedIdx = 0;
19626 
19627 					if(estimatedIdx > rendered.slice.length)
19628 						estimatedIdx = cast(int) rendered.slice.length;
19629 
19630 					int idx;
19631 					int x1, x2;
19632 					if(part.font.isMonospace) {
19633 						auto w = part.font.averageWidth;
19634 						if(!exact && x > (estimatedIdx + 1) * w)
19635 							return IdentifyResult(null, 4);
19636 						idx = estimatedIdx;
19637 						x1 = idx * w;
19638 						x2 = (idx + 1) * w;
19639 					} else {
19640 						idx = estimatedIdx;
19641 
19642 						part.font.prepareContext(window);
19643 						scope(exit) part.font.releaseContext();
19644 
19645 						// int iterations;
19646 
19647 						while(true) {
19648 							// iterations++;
19649 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19650 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19651 
19652 							x1 += rendered.startX;
19653 							x2 += rendered.startX;
19654 
19655 							if(pt.x < x1) {
19656 								if(idx == 0) {
19657 									if(exact)
19658 										return IdentifyResult(null, 6);
19659 									else
19660 										break;
19661 								}
19662 								idx--;
19663 							} else if(pt.x > x2) {
19664 								idx++;
19665 								if(idx > rendered.slice.length) {
19666 									if(exact)
19667 										return IdentifyResult(null, 5);
19668 									else
19669 										break;
19670 								}
19671 							} else if(pt.x >= x1 && pt.x <= x2) {
19672 								if(idx)
19673 									idx--; // point it at the original index
19674 								break; // we fit
19675 							}
19676 						}
19677 
19678 						// writeln(iterations)
19679 					}
19680 
19681 
19682 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19683 				}
19684 			}
19685 			tci += cast(int) part.content.length; // FIXME: utf-8?
19686 		}
19687 		return IdentifyResult(null, 3);
19688 	}
19689 
19690 	Rectangle boundingBox; // only set after [rerender]
19691 
19692 	// text will be positioned around the exclusion zone
19693 	static struct ExclusionZone {
19694 
19695 	}
19696 
19697 	ExclusionZone[] exclusionZones;
19698 }
19699 
19700 
19701 // Don't use this yet. When I'm happy with it, I will move it to the
19702 // regular module namespace.
19703 mixin template ExperimentalTextComponent() {
19704 
19705 static:
19706 
19707 	alias Rectangle = arsd.color.Rectangle;
19708 
19709 	struct ForegroundColor {
19710 		Color color;
19711 		alias color this;
19712 
19713 		this(Color c) {
19714 			color = c;
19715 		}
19716 
19717 		this(int r, int g, int b, int a = 255) {
19718 			color = Color(r, g, b, a);
19719 		}
19720 
19721 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19722 			return ForegroundColor(mixin("Color." ~ s));
19723 		}
19724 	}
19725 
19726 	struct BackgroundColor {
19727 		Color color;
19728 		alias color this;
19729 
19730 		this(Color c) {
19731 			color = c;
19732 		}
19733 
19734 		this(int r, int g, int b, int a = 255) {
19735 			color = Color(r, g, b, a);
19736 		}
19737 
19738 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19739 			return BackgroundColor(mixin("Color." ~ s));
19740 		}
19741 	}
19742 
19743 	static class InlineElement {
19744 		string text;
19745 
19746 		BlockElement containingBlock;
19747 
19748 		Color color = Color.black;
19749 		Color backgroundColor = Color.transparent;
19750 		ushort styles;
19751 
19752 		string font;
19753 		int fontSize;
19754 
19755 		int lineHeight;
19756 
19757 		void* identifier;
19758 
19759 		Rectangle boundingBox;
19760 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19761 
19762 		bool isMergeCompatible(InlineElement other) {
19763 			return
19764 				containingBlock is other.containingBlock &&
19765 				color == other.color &&
19766 				backgroundColor == other.backgroundColor &&
19767 				styles == other.styles &&
19768 				font == other.font &&
19769 				fontSize == other.fontSize &&
19770 				lineHeight == other.lineHeight &&
19771 				true;
19772 		}
19773 
19774 		int xOfIndex(size_t index) {
19775 			if(index < letterXs.length)
19776 				return letterXs[index];
19777 			else
19778 				return boundingBox.right;
19779 		}
19780 
19781 		InlineElement clone() {
19782 			auto ie = new InlineElement();
19783 			ie.tupleof = this.tupleof;
19784 			return ie;
19785 		}
19786 
19787 		InlineElement getPreviousInlineElement() {
19788 			InlineElement prev = null;
19789 			foreach(ie; this.containingBlock.parts) {
19790 				if(ie is this)
19791 					break;
19792 				prev = ie;
19793 			}
19794 			if(prev is null) {
19795 				BlockElement pb;
19796 				BlockElement cb = this.containingBlock;
19797 				moar:
19798 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19799 					if(ie is cb)
19800 						break;
19801 					pb = ie;
19802 				}
19803 				if(pb is null)
19804 					return null;
19805 				if(pb.parts.length == 0) {
19806 					cb = pb;
19807 					goto moar;
19808 				}
19809 
19810 				prev = pb.parts[$-1];
19811 
19812 			}
19813 			return prev;
19814 		}
19815 
19816 		InlineElement getNextInlineElement() {
19817 			InlineElement next = null;
19818 			foreach(idx, ie; this.containingBlock.parts) {
19819 				if(ie is this) {
19820 					if(idx + 1 < this.containingBlock.parts.length)
19821 						next = this.containingBlock.parts[idx + 1];
19822 					break;
19823 				}
19824 			}
19825 			if(next is null) {
19826 				BlockElement n;
19827 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19828 					if(ie is this.containingBlock) {
19829 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19830 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19831 						break;
19832 					}
19833 				}
19834 				if(n is null)
19835 					return null;
19836 
19837 				if(n.parts.length)
19838 					next = n.parts[0];
19839 				else {} // FIXME
19840 
19841 			}
19842 			return next;
19843 		}
19844 
19845 	}
19846 
19847 	// Block elements are used entirely for positioning inline elements,
19848 	// which are the things that are actually drawn.
19849 	class BlockElement {
19850 		InlineElement[] parts;
19851 		uint alignment;
19852 
19853 		int whiteSpace; // pre, pre-wrap, wrap
19854 
19855 		TextLayout containingLayout;
19856 
19857 		// inputs
19858 		Point where;
19859 		Size minimumSize;
19860 		Size maximumSize;
19861 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19862 		void* identifier;
19863 
19864 		Rectangle margin;
19865 		Rectangle padding;
19866 
19867 		// outputs
19868 		Rectangle[] boundingBoxes;
19869 	}
19870 
19871 	struct TextIdentifyResult {
19872 		InlineElement element;
19873 		int offset;
19874 
19875 		private TextIdentifyResult fixupNewline() {
19876 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19877 				offset--;
19878 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19879 				offset--;
19880 			}
19881 			return this;
19882 		}
19883 	}
19884 
19885 	class TextLayout {
19886 		BlockElement[] blocks;
19887 		Rectangle boundingBox_;
19888 		Rectangle boundingBox() { return boundingBox_; }
19889 		void boundingBox(Rectangle r) {
19890 			if(r != boundingBox_) {
19891 				boundingBox_ = r;
19892 				layoutInvalidated = true;
19893 			}
19894 		}
19895 
19896 		Rectangle contentBoundingBox() {
19897 			Rectangle r;
19898 			foreach(block; blocks)
19899 			foreach(ie; block.parts) {
19900 				if(ie.boundingBox.right > r.right)
19901 					r.right = ie.boundingBox.right;
19902 				if(ie.boundingBox.bottom > r.bottom)
19903 					r.bottom = ie.boundingBox.bottom;
19904 			}
19905 			return r;
19906 		}
19907 
19908 		BlockElement[] getBlocks() {
19909 			return blocks;
19910 		}
19911 
19912 		InlineElement[] getTexts() {
19913 			InlineElement[] elements;
19914 			foreach(block; blocks)
19915 				elements ~= block.parts;
19916 			return elements;
19917 		}
19918 
19919 		string getPlainText() {
19920 			string text;
19921 			foreach(block; blocks)
19922 				foreach(part; block.parts)
19923 					text ~= part.text;
19924 			return text;
19925 		}
19926 
19927 		string getHtml() {
19928 			return null; // FIXME
19929 		}
19930 
19931 		this(Rectangle boundingBox) {
19932 			this.boundingBox = boundingBox;
19933 		}
19934 
19935 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19936 			auto be = new BlockElement();
19937 			be.containingLayout = this;
19938 			if(after is null)
19939 				blocks ~= be;
19940 			else {
19941 				foreach(idx, b; blocks) {
19942 					if(b is after.containingBlock) {
19943 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19944 						break;
19945 					}
19946 				}
19947 			}
19948 			return be;
19949 		}
19950 
19951 		void clear() {
19952 			blocks = null;
19953 			selectionStart = selectionEnd = caret = Caret.init;
19954 		}
19955 
19956 		void addText(Args...)(Args args) {
19957 			if(blocks.length == 0)
19958 				addBlock();
19959 
19960 			InlineElement ie = new InlineElement();
19961 			foreach(idx, arg; args) {
19962 				static if(is(typeof(arg) == ForegroundColor))
19963 					ie.color = arg;
19964 				else static if(is(typeof(arg) == TextFormat)) {
19965 					if(arg & 0x8000) // ~TextFormat.something turns it off
19966 						ie.styles &= arg;
19967 					else
19968 						ie.styles |= arg;
19969 				} else static if(is(typeof(arg) == string)) {
19970 					static if(idx == 0 && args.length > 1)
19971 						static assert(0, "Put styles before the string.");
19972 					size_t lastLineIndex;
19973 					foreach(cidx, char a; arg) {
19974 						if(a == '\n') {
19975 							ie.text = arg[lastLineIndex .. cidx + 1];
19976 							lastLineIndex = cidx + 1;
19977 							ie.containingBlock = blocks[$-1];
19978 							blocks[$-1].parts ~= ie.clone;
19979 							ie.text = null;
19980 						} else {
19981 
19982 						}
19983 					}
19984 
19985 					ie.text = arg[lastLineIndex .. $];
19986 					ie.containingBlock = blocks[$-1];
19987 					blocks[$-1].parts ~= ie.clone;
19988 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
19989 				}
19990 			}
19991 
19992 			invalidateLayout();
19993 		}
19994 
19995 		void tryMerge(InlineElement into, InlineElement what) {
19996 			if(!into.isMergeCompatible(what)) {
19997 				return; // cannot merge, different configs
19998 			}
19999 
20000 			// cool, can merge, bring text together...
20001 			into.text ~= what.text;
20002 
20003 			// and remove what
20004 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
20005 				if(what.containingBlock.parts[a] is what) {
20006 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
20007 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
20008 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
20009 
20010 				}
20011 			}
20012 
20013 			// FIXME: ensure no other carets have a reference to it
20014 		}
20015 
20016 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
20017 		TextIdentifyResult identify(int x, int y, bool exact = false) {
20018 			TextIdentifyResult inexactMatch;
20019 			foreach(block; blocks) {
20020 				foreach(part; block.parts) {
20021 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
20022 
20023 						// FIXME binary search
20024 						int tidx;
20025 						int lastX;
20026 						foreach_reverse(idxo, lx; part.letterXs) {
20027 							int idx = cast(int) idxo;
20028 							if(lx <= x) {
20029 								if(lastX && lastX - x < x - lx)
20030 									tidx = idx + 1;
20031 								else
20032 									tidx = idx;
20033 								break;
20034 							}
20035 							lastX = lx;
20036 						}
20037 
20038 						return TextIdentifyResult(part, tidx).fixupNewline;
20039 					} else if(!exact) {
20040 						// we're not in the box, but are we on the same line?
20041 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
20042 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
20043 					}
20044 				}
20045 			}
20046 
20047 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
20048 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
20049 
20050 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
20051 		}
20052 
20053 		void moveCaretToPixelCoordinates(int x, int y) {
20054 			auto result = identify(x, y);
20055 			caret.inlineElement = result.element;
20056 			caret.offset = result.offset;
20057 		}
20058 
20059 		void selectToPixelCoordinates(int x, int y) {
20060 			auto result = identify(x, y);
20061 
20062 			if(y < caretLastDrawnY1) {
20063 				// on a previous line, carat is selectionEnd
20064 				selectionEnd = caret;
20065 
20066 				selectionStart = Caret(this, result.element, result.offset);
20067 			} else if(y > caretLastDrawnY2) {
20068 				// on a later line
20069 				selectionStart = caret;
20070 
20071 				selectionEnd = Caret(this, result.element, result.offset);
20072 			} else {
20073 				// on the same line...
20074 				if(x <= caretLastDrawnX) {
20075 					selectionEnd = caret;
20076 					selectionStart = Caret(this, result.element, result.offset);
20077 				} else {
20078 					selectionStart = caret;
20079 					selectionEnd = Caret(this, result.element, result.offset);
20080 				}
20081 
20082 			}
20083 		}
20084 
20085 
20086 		/// Call this if the inputs change. It will reflow everything
20087 		void redoLayout(ScreenPainter painter) {
20088 			//painter.setClipRectangle(boundingBox);
20089 			auto pos = Point(boundingBox.left, boundingBox.top);
20090 
20091 			int lastHeight;
20092 			void nl() {
20093 				pos.x = boundingBox.left;
20094 				pos.y += lastHeight;
20095 			}
20096 			foreach(block; blocks) {
20097 				nl();
20098 				foreach(part; block.parts) {
20099 					part.letterXs = null;
20100 
20101 					auto size = painter.textSize(part.text);
20102 					version(Windows)
20103 						if(part.text.length && part.text[$-1] == '\n')
20104 							size.height /= 2; // windows counts the new line at the end, but we don't want that
20105 
20106 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
20107 
20108 					foreach(idx, char c; part.text) {
20109 							// FIXME: unicode
20110 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
20111 					}
20112 
20113 					pos.x += size.width;
20114 					if(pos.x >= boundingBox.right) {
20115 						pos.y += size.height;
20116 						pos.x = boundingBox.left;
20117 						lastHeight = 0;
20118 					} else {
20119 						lastHeight = size.height;
20120 					}
20121 
20122 					if(part.text.length && part.text[$-1] == '\n')
20123 						nl();
20124 				}
20125 			}
20126 
20127 			layoutInvalidated = false;
20128 		}
20129 
20130 		bool layoutInvalidated = true;
20131 		void invalidateLayout() {
20132 			layoutInvalidated = true;
20133 		}
20134 
20135 // FIXME: caret can remain sometimes when inserting
20136 // FIXME: inserting at the beginning once you already have something can eff it up.
20137 		void drawInto(ScreenPainter painter, bool focused = false) {
20138 			if(layoutInvalidated)
20139 				redoLayout(painter);
20140 			foreach(block; blocks) {
20141 				foreach(part; block.parts) {
20142 					painter.outlineColor = part.color;
20143 					painter.fillColor = part.backgroundColor;
20144 
20145 					auto pos = part.boundingBox.upperLeft;
20146 					auto size = part.boundingBox.size;
20147 
20148 					painter.drawText(pos, part.text);
20149 					if(part.styles & TextFormat.underline)
20150 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
20151 					if(part.styles & TextFormat.strikethrough)
20152 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
20153 				}
20154 			}
20155 
20156 			// on every redraw, I will force the caret to be
20157 			// redrawn too, in order to eliminate perceived lag
20158 			// when moving around with the mouse.
20159 			eraseCaret(painter);
20160 
20161 			if(focused) {
20162 				highlightSelection(painter);
20163 				drawCaret(painter);
20164 			}
20165 		}
20166 
20167 		Color selectionXorColor = Color(255, 255, 127);
20168 
20169 		void highlightSelection(ScreenPainter painter) {
20170 			if(selectionStart is selectionEnd)
20171 				return; // no selection
20172 
20173 			if(selectionStart.inlineElement is null) return;
20174 			if(selectionEnd.inlineElement is null) return;
20175 
20176 			assert(selectionStart.inlineElement !is null);
20177 			assert(selectionEnd.inlineElement !is null);
20178 
20179 			painter.rasterOp = RasterOp.xor;
20180 			painter.outlineColor = Color.transparent;
20181 			painter.fillColor = selectionXorColor;
20182 
20183 			auto at = selectionStart.inlineElement;
20184 			auto atOffset = selectionStart.offset;
20185 			bool done;
20186 			while(at) {
20187 				auto box = at.boundingBox;
20188 				if(atOffset < at.letterXs.length)
20189 					box.left = at.letterXs[atOffset];
20190 
20191 				if(at is selectionEnd.inlineElement) {
20192 					if(selectionEnd.offset < at.letterXs.length)
20193 						box.right = at.letterXs[selectionEnd.offset];
20194 					done = true;
20195 				}
20196 
20197 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20198 
20199 				if(done)
20200 					break;
20201 
20202 				at = at.getNextInlineElement();
20203 				atOffset = 0;
20204 			}
20205 		}
20206 
20207 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20208 		bool caretShowingOnScreen = false;
20209 		void drawCaret(ScreenPainter painter) {
20210 			//painter.setClipRectangle(boundingBox);
20211 			int x, y1, y2;
20212 			if(caret.inlineElement is null) {
20213 				x = boundingBox.left;
20214 				y1 = boundingBox.top + 2;
20215 				y2 = boundingBox.top + painter.fontHeight;
20216 			} else {
20217 				x = caret.inlineElement.xOfIndex(caret.offset);
20218 				y1 = caret.inlineElement.boundingBox.top + 2;
20219 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20220 			}
20221 
20222 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20223 				eraseCaret(painter);
20224 
20225 			painter.pen = Pen(Color.white, 1);
20226 			painter.rasterOp = RasterOp.xor;
20227 			painter.drawLine(
20228 				Point(x, y1),
20229 				Point(x, y2)
20230 			);
20231 			painter.rasterOp = RasterOp.normal;
20232 			caretShowingOnScreen = !caretShowingOnScreen;
20233 
20234 			if(caretShowingOnScreen) {
20235 				caretLastDrawnX = x;
20236 				caretLastDrawnY1 = y1;
20237 				caretLastDrawnY2 = y2;
20238 			}
20239 		}
20240 
20241 		Rectangle caretBoundingBox() {
20242 			int x, y1, y2;
20243 			if(caret.inlineElement is null) {
20244 				x = boundingBox.left;
20245 				y1 = boundingBox.top + 2;
20246 				y2 = boundingBox.top + 16;
20247 			} else {
20248 				x = caret.inlineElement.xOfIndex(caret.offset);
20249 				y1 = caret.inlineElement.boundingBox.top + 2;
20250 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20251 			}
20252 
20253 			return Rectangle(x, y1, x + 1, y2);
20254 		}
20255 
20256 		void eraseCaret(ScreenPainter painter) {
20257 			//painter.setClipRectangle(boundingBox);
20258 			if(!caretShowingOnScreen) return;
20259 			painter.pen = Pen(Color.white, 1);
20260 			painter.rasterOp = RasterOp.xor;
20261 			painter.drawLine(
20262 				Point(caretLastDrawnX, caretLastDrawnY1),
20263 				Point(caretLastDrawnX, caretLastDrawnY2)
20264 			);
20265 
20266 			caretShowingOnScreen = false;
20267 			painter.rasterOp = RasterOp.normal;
20268 		}
20269 
20270 		/// Caret movement api
20271 		/// These should give the user a logical result based on what they see on screen...
20272 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20273 		void moveUp() {
20274 			if(caret.inlineElement is null) return;
20275 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20276 			auto y = caret.inlineElement.boundingBox.top + 2;
20277 
20278 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20279 			if(y < 0)
20280 				return;
20281 
20282 			auto i = identify(x, y);
20283 
20284 			if(i.element) {
20285 				caret.inlineElement = i.element;
20286 				caret.offset = i.offset;
20287 			}
20288 		}
20289 		void moveDown() {
20290 			if(caret.inlineElement is null) return;
20291 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20292 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20293 
20294 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20295 
20296 			auto i = identify(x, y);
20297 			if(i.element) {
20298 				caret.inlineElement = i.element;
20299 				caret.offset = i.offset;
20300 			}
20301 		}
20302 		void moveLeft() {
20303 			if(caret.inlineElement is null) return;
20304 			if(caret.offset)
20305 				caret.offset--;
20306 			else {
20307 				auto p = caret.inlineElement.getPreviousInlineElement();
20308 				if(p) {
20309 					caret.inlineElement = p;
20310 					if(p.text.length && p.text[$-1] == '\n')
20311 						caret.offset = cast(int) p.text.length - 1;
20312 					else
20313 						caret.offset = cast(int) p.text.length;
20314 				}
20315 			}
20316 		}
20317 		void moveRight() {
20318 			if(caret.inlineElement is null) return;
20319 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20320 				caret.offset++;
20321 			} else {
20322 				auto p = caret.inlineElement.getNextInlineElement();
20323 				if(p) {
20324 					caret.inlineElement = p;
20325 					caret.offset = 0;
20326 				}
20327 			}
20328 		}
20329 		void moveHome() {
20330 			if(caret.inlineElement is null) return;
20331 			auto x = 0;
20332 			auto y = caret.inlineElement.boundingBox.top + 2;
20333 
20334 			auto i = identify(x, y);
20335 
20336 			if(i.element) {
20337 				caret.inlineElement = i.element;
20338 				caret.offset = i.offset;
20339 			}
20340 		}
20341 		void moveEnd() {
20342 			if(caret.inlineElement is null) return;
20343 			auto x = int.max;
20344 			auto y = caret.inlineElement.boundingBox.top + 2;
20345 
20346 			auto i = identify(x, y);
20347 
20348 			if(i.element) {
20349 				caret.inlineElement = i.element;
20350 				caret.offset = i.offset;
20351 			}
20352 
20353 		}
20354 		void movePageUp(ref Caret caret) {}
20355 		void movePageDown(ref Caret caret) {}
20356 
20357 		void moveDocumentStart(ref Caret caret) {
20358 			if(blocks.length && blocks[0].parts.length)
20359 				caret = Caret(this, blocks[0].parts[0], 0);
20360 			else
20361 				caret = Caret.init;
20362 		}
20363 
20364 		void moveDocumentEnd(ref Caret caret) {
20365 			if(blocks.length) {
20366 				auto parts = blocks[$-1].parts;
20367 				if(parts.length) {
20368 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20369 				} else {
20370 					caret = Caret.init;
20371 				}
20372 			} else
20373 				caret = Caret.init;
20374 		}
20375 
20376 		void deleteSelection() {
20377 			if(selectionStart is selectionEnd)
20378 				return;
20379 
20380 			if(selectionStart.inlineElement is null) return;
20381 			if(selectionEnd.inlineElement is null) return;
20382 
20383 			assert(selectionStart.inlineElement !is null);
20384 			assert(selectionEnd.inlineElement !is null);
20385 
20386 			auto at = selectionStart.inlineElement;
20387 
20388 			if(selectionEnd.inlineElement is at) {
20389 				// same element, need to chop out
20390 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20391 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20392 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20393 			} else {
20394 				// different elements, we can do it with slicing
20395 				at.text = at.text[0 .. selectionStart.offset];
20396 				if(selectionStart.offset < at.letterXs.length)
20397 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20398 
20399 				at = at.getNextInlineElement();
20400 
20401 				while(at) {
20402 					if(at is selectionEnd.inlineElement) {
20403 						at.text = at.text[selectionEnd.offset .. $];
20404 						if(selectionEnd.offset < at.letterXs.length)
20405 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20406 						selectionEnd.offset = 0;
20407 						break;
20408 					} else {
20409 						auto cfd = at;
20410 						cfd.text = null; // delete the whole thing
20411 
20412 						at = at.getNextInlineElement();
20413 
20414 						if(cfd.text.length == 0) {
20415 							// and remove cfd
20416 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20417 								if(cfd.containingBlock.parts[a] is cfd) {
20418 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20419 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20420 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20421 
20422 								}
20423 							}
20424 						}
20425 					}
20426 				}
20427 			}
20428 
20429 			caret = selectionEnd;
20430 			selectNone();
20431 
20432 			invalidateLayout();
20433 
20434 		}
20435 
20436 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20437 		void insert(in char[] text) {
20438 			foreach(dchar ch; text)
20439 				insert(ch);
20440 		}
20441 		/// ditto
20442 		void insert(dchar ch) {
20443 
20444 			bool selectionDeleted = false;
20445 			if(selectionStart !is selectionEnd) {
20446 				deleteSelection();
20447 				selectionDeleted = true;
20448 			}
20449 
20450 			if(ch == 127) {
20451 				delete_();
20452 				return;
20453 			}
20454 			if(ch == 8) {
20455 				if(!selectionDeleted)
20456 					backspace();
20457 				return;
20458 			}
20459 
20460 			invalidateLayout();
20461 
20462 			if(ch == 13) ch = 10;
20463 			auto e = caret.inlineElement;
20464 			if(e is null) {
20465 				addText("" ~ cast(char) ch) ; // FIXME
20466 				return;
20467 			}
20468 
20469 			if(caret.offset == e.text.length) {
20470 				e.text ~= cast(char) ch; // FIXME
20471 				caret.offset++;
20472 				if(ch == 10) {
20473 					auto c = caret.inlineElement.clone;
20474 					c.text = null;
20475 					c.letterXs = null;
20476 					insertPartAfter(c,e);
20477 					caret = Caret(this, c, 0);
20478 				}
20479 			} else {
20480 				// FIXME cast char sucks
20481 				if(ch == 10) {
20482 					auto c = caret.inlineElement.clone;
20483 					c.text = e.text[caret.offset .. $];
20484 					if(caret.offset < c.letterXs.length)
20485 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20486 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20487 					if(caret.offset <= e.letterXs.length) {
20488 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20489 					}
20490 					insertPartAfter(c,e);
20491 					caret = Caret(this, c, 0);
20492 				} else {
20493 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20494 					caret.offset++;
20495 				}
20496 			}
20497 		}
20498 
20499 		void insertPartAfter(InlineElement what, InlineElement where) {
20500 			foreach(idx, p; where.containingBlock.parts) {
20501 				if(p is where) {
20502 					if(idx + 1 == where.containingBlock.parts.length)
20503 						where.containingBlock.parts ~= what;
20504 					else
20505 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20506 					return;
20507 				}
20508 			}
20509 		}
20510 
20511 		void cleanupStructures() {
20512 			for(size_t i = 0; i < blocks.length; i++) {
20513 				auto block = blocks[i];
20514 				for(size_t a = 0; a < block.parts.length; a++) {
20515 					auto part = block.parts[a];
20516 					if(part.text.length == 0) {
20517 						for(size_t b = a; b < block.parts.length - 1; b++)
20518 							block.parts[b] = block.parts[b+1];
20519 						block.parts = block.parts[0 .. $-1];
20520 					}
20521 				}
20522 				if(block.parts.length == 0) {
20523 					for(size_t a = i; a < blocks.length - 1; a++)
20524 						blocks[a] = blocks[a+1];
20525 					blocks = blocks[0 .. $-1];
20526 				}
20527 			}
20528 		}
20529 
20530 		void backspace() {
20531 			try_again:
20532 			auto e = caret.inlineElement;
20533 			if(e is null)
20534 				return;
20535 			if(caret.offset == 0) {
20536 				auto prev = e.getPreviousInlineElement();
20537 				if(prev is null)
20538 					return;
20539 				auto newOffset = cast(int) prev.text.length;
20540 				tryMerge(prev, e);
20541 				caret.inlineElement = prev;
20542 				caret.offset = prev is null ? 0 : newOffset;
20543 
20544 				goto try_again;
20545 			} else if(caret.offset == e.text.length) {
20546 				e.text = e.text[0 .. $-1];
20547 				caret.offset--;
20548 			} else {
20549 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20550 				caret.offset--;
20551 			}
20552 			//cleanupStructures();
20553 
20554 			invalidateLayout();
20555 		}
20556 		void delete_() {
20557 			if(selectionStart !is selectionEnd)
20558 				deleteSelection();
20559 			else {
20560 				auto before = caret;
20561 				moveRight();
20562 				if(caret != before) {
20563 					backspace();
20564 				}
20565 			}
20566 
20567 			invalidateLayout();
20568 		}
20569 		void overstrike() {}
20570 
20571 		/// Selection API. See also: caret movement.
20572 		void selectAll() {
20573 			moveDocumentStart(selectionStart);
20574 			moveDocumentEnd(selectionEnd);
20575 		}
20576 		bool selectNone() {
20577 			if(selectionStart != selectionEnd) {
20578 				selectionStart = selectionEnd = Caret.init;
20579 				return true;
20580 			}
20581 			return false;
20582 		}
20583 
20584 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20585 		/// They will modify the current selection if there is one and will splice one in if needed.
20586 		void changeAttributes() {}
20587 
20588 
20589 		/// Text search api. They manipulate the selection and/or caret.
20590 		void findText(string text) {}
20591 		void findIndex(size_t textIndex) {}
20592 
20593 		// sample event handlers
20594 
20595 		void handleEvent(KeyEvent event) {
20596 			//if(event.type == KeyEvent.Type.KeyPressed) {
20597 
20598 			//}
20599 		}
20600 
20601 		void handleEvent(dchar ch) {
20602 
20603 		}
20604 
20605 		void handleEvent(MouseEvent event) {
20606 
20607 		}
20608 
20609 		bool contentEditable; // can it be edited?
20610 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20611 		bool contentSelectable; // selectable?
20612 
20613 		Caret caret;
20614 		Caret selectionStart;
20615 		Caret selectionEnd;
20616 
20617 		bool insertMode;
20618 	}
20619 
20620 	struct Caret {
20621 		TextLayout layout;
20622 		InlineElement inlineElement;
20623 		int offset;
20624 	}
20625 
20626 	enum TextFormat : ushort {
20627 		// decorations
20628 		underline = 1,
20629 		strikethrough = 2,
20630 
20631 		// font selectors
20632 
20633 		bold = 0x4000 | 1, // weight 700
20634 		light = 0x4000 | 2, // weight 300
20635 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20636 		// bold | light is really invalid but should give weight 500
20637 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20638 
20639 		italic = 0x4000 | 8,
20640 		smallcaps = 0x4000 | 16,
20641 	}
20642 
20643 	void* findFont(string family, int weight, TextFormat formats) {
20644 		return null;
20645 	}
20646 
20647 }
20648 
20649 /++
20650 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20651 
20652 	History:
20653 		Added February 19, 2021
20654 +/
20655 /// Group: drag_and_drop
20656 interface DropHandler {
20657 	/++
20658 		Called when the drag enters the handler's area.
20659 	+/
20660 	DragAndDropAction dragEnter(DropPackage*);
20661 	/++
20662 		Called when the drag leaves the handler's area or is
20663 		cancelled. You should free your resources when this is called.
20664 	+/
20665 	void dragLeave();
20666 	/++
20667 		Called continually as the drag moves over the handler's area.
20668 
20669 		Returns: feedback to the dragger
20670 	+/
20671 	DropParameters dragOver(Point pt);
20672 	/++
20673 		The user dropped the data and you should process it now. You can
20674 		access the data through the given [DropPackage].
20675 	+/
20676 	void drop(scope DropPackage*);
20677 	/++
20678 		Called when the drop is complete. You should free whatever temporary
20679 		resources you were using. It is often reasonable to simply forward
20680 		this call to [dragLeave].
20681 	+/
20682 	void finish();
20683 
20684 	/++
20685 		Parameters returned by [DropHandler.drop].
20686 	+/
20687 	static struct DropParameters {
20688 		/++
20689 			Acceptable action over this area.
20690 		+/
20691 		DragAndDropAction action;
20692 		/++
20693 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20694 
20695 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20696 		+/
20697 		Rectangle consistentWithin;
20698 	}
20699 }
20700 
20701 /++
20702 	History:
20703 		Added February 19, 2021
20704 +/
20705 /// Group: drag_and_drop
20706 enum DragAndDropAction {
20707 	none = 0,
20708 	copy,
20709 	move,
20710 	link,
20711 	ask,
20712 	custom
20713 }
20714 
20715 /++
20716 	An opaque structure representing dropped data. It contains
20717 	private, platform-specific data that your `drop` function
20718 	should simply forward.
20719 
20720 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20721 
20722 	History:
20723 		Added February 19, 2021
20724 +/
20725 /// Group: drag_and_drop
20726 struct DropPackage {
20727 	/++
20728 		Lists the available formats as magic numbers. You should compare these
20729 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20730 		understand the passed data.
20731 	+/
20732 	DraggableData.FormatId[] availableFormats() {
20733 		version(X11) {
20734 			return xFormats;
20735 		} else version(Windows) {
20736 			if(pDataObj is null)
20737 				return null;
20738 
20739 			typeof(return) ret;
20740 
20741 			IEnumFORMATETC ef;
20742 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20743 				FORMATETC fmt;
20744 				ULONG fetched;
20745 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20746 					if(fetched == 0)
20747 						break;
20748 
20749 					if(fmt.lindex != -1)
20750 						continue;
20751 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20752 						continue;
20753 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20754 						continue;
20755 
20756 					ret ~= fmt.cfFormat;
20757 				}
20758 			}
20759 
20760 			return ret;
20761 		} else throw new NotYetImplementedException();
20762 	}
20763 
20764 	/++
20765 		Gets data from the drop and optionally accepts it.
20766 
20767 		Returns:
20768 			void because the data is fed asynchronously through the `dg` parameter.
20769 
20770 		Params:
20771 			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.
20772 
20773 			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.
20774 
20775 			Calling `getData` again after accepting a drop is not permitted.
20776 
20777 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20778 
20779 			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.
20780 
20781 		Throws:
20782 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20783 
20784 		History:
20785 			Included in first release of [DropPackage].
20786 	+/
20787 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20788 		version(X11) {
20789 
20790 			auto display = XDisplayConnection.get();
20791 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20792 			auto best = format;
20793 
20794 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20795 
20796 				XDisplay* display;
20797 				Atom selectionAtom;
20798 				DraggableData.FormatId best;
20799 				DraggableData.FormatId format;
20800 				void delegate(scope ubyte[] data) dg;
20801 				DragAndDropAction acceptedAction;
20802 				Window sourceWindow;
20803 				SimpleWindow win;
20804 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20805 					this.display = display;
20806 					this.win = win;
20807 					this.sourceWindow = sourceWindow;
20808 					this.format = format;
20809 					this.selectionAtom = selectionAtom;
20810 					this.best = best;
20811 					this.dg = dg;
20812 					this.acceptedAction = acceptedAction;
20813 				}
20814 
20815 
20816 				mixin X11GetSelectionHandler_Basics;
20817 
20818 				void handleData(Atom target, in ubyte[] data) {
20819 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20820 
20821 					dg(cast(ubyte[]) data);
20822 
20823 					if(acceptedAction != DragAndDropAction.none) {
20824 						auto display = XDisplayConnection.get;
20825 
20826 						XClientMessageEvent xclient;
20827 
20828 						xclient.type = EventType.ClientMessage;
20829 						xclient.window = sourceWindow;
20830 						xclient.message_type = GetAtom!"XdndFinished"(display);
20831 						xclient.format = 32;
20832 						xclient.data.l[0] = win.impl.window;
20833 						xclient.data.l[1] = 1; // drop successful
20834 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20835 
20836 						XSendEvent(
20837 							display,
20838 							sourceWindow,
20839 							false,
20840 							EventMask.NoEventMask,
20841 							cast(XEvent*) &xclient
20842 						);
20843 
20844 						XFlush(display);
20845 					}
20846 				}
20847 
20848 				Atom findBestFormat(Atom[] answer) {
20849 					Atom best = None;
20850 					foreach(option; answer) {
20851 						if(option == format) {
20852 							best = option;
20853 							break;
20854 						}
20855 						/*
20856 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20857 							best = option;
20858 							break;
20859 						} else if(option == XA_STRING) {
20860 							best = option;
20861 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20862 							best = option;
20863 						}
20864 						*/
20865 					}
20866 					return best;
20867 				}
20868 			}
20869 
20870 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20871 
20872 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20873 
20874 		} else version(Windows) {
20875 
20876 			// clean up like DragLeave
20877 			// pass effect back up
20878 
20879 			FORMATETC t;
20880 			assert(format >= 0 && format <= ushort.max);
20881 			t.cfFormat = cast(ushort) format;
20882 			t.lindex = -1;
20883 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20884 			t.tymed = TYMED.TYMED_HGLOBAL;
20885 
20886 			STGMEDIUM m;
20887 
20888 			if(pDataObj.GetData(&t, &m) != S_OK) {
20889 				// fail
20890 			} else {
20891 				// succeed, take the data and clean up
20892 
20893 				// FIXME: ensure it is legit HGLOBAL
20894 				auto handle = m.hGlobal;
20895 
20896 				if(handle) {
20897 					auto sz = GlobalSize(handle);
20898 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20899 						scope(exit) GlobalUnlock(handle);
20900 						scope(exit) GlobalFree(handle);
20901 
20902 						auto data = ptr[0 .. sz];
20903 
20904 						dg(data);
20905 					}
20906 				}
20907 			}
20908 		}
20909 	}
20910 
20911 	private:
20912 
20913 	version(X11) {
20914 		SimpleWindow win;
20915 		Window sourceWindow;
20916 		Time dataTimestamp;
20917 
20918 		Atom[] xFormats;
20919 	}
20920 	version(Windows) {
20921 		IDataObject pDataObj;
20922 	}
20923 }
20924 
20925 /++
20926 	A generic helper base class for making a drop handler with a preference list of custom types.
20927 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20928 	droppers too.
20929 
20930 	It assumes the whole window it used, but you can subclass to change that.
20931 
20932 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20933 
20934 	History:
20935 		Added February 19, 2021
20936 +/
20937 /// Group: drag_and_drop
20938 class GenericDropHandlerBase : DropHandler {
20939 	// no fancy state here so no need to do anything here
20940 	void finish() { }
20941 	void dragLeave() { }
20942 
20943 	private DragAndDropAction acceptedAction;
20944 	private DraggableData.FormatId acceptedFormat;
20945 	private void delegate(scope ubyte[]) acceptedHandler;
20946 
20947 	struct FormatHandler {
20948 		DraggableData.FormatId format;
20949 		void delegate(scope ubyte[]) handler;
20950 	}
20951 
20952 	protected abstract FormatHandler[] formatHandlers();
20953 
20954 	DragAndDropAction dragEnter(DropPackage* pkg) {
20955 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20956 		foreach(fmt; formatHandlers())
20957 		foreach(f; pkg.availableFormats())
20958 			if(f == fmt.format) {
20959 				acceptedFormat = f;
20960 				acceptedHandler = fmt.handler;
20961 				return acceptedAction = DragAndDropAction.copy;
20962 			}
20963 		return acceptedAction = DragAndDropAction.none;
20964 	}
20965 	DropParameters dragOver(Point pt) {
20966 		return DropParameters(acceptedAction);
20967 	}
20968 
20969 	void drop(scope DropPackage* dropPackage) {
20970 		if(!acceptedFormat || acceptedHandler is null) {
20971 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20972 			return; // prolly shouldn't happen anyway...
20973 		}
20974 
20975 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20976 	}
20977 }
20978 
20979 /++
20980 	A simple handler for making your window accept drops of plain text.
20981 
20982 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20983 
20984 	History:
20985 		Added February 22, 2021
20986 +/
20987 /// Group: drag_and_drop
20988 class TextDropHandler : GenericDropHandlerBase {
20989 	private void delegate(in char[] text) dg;
20990 
20991 	/++
20992 
20993 	+/
20994 	this(void delegate(in char[] text) dg) {
20995 		this.dg = dg;
20996 	}
20997 
20998 	protected override FormatHandler[] formatHandlers() {
20999 		version(X11)
21000 			return [
21001 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21002 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21003 			];
21004 		else version(Windows)
21005 			return [
21006 				FormatHandler(CF_UNICODETEXT, &translator),
21007 			];
21008 		else throw new NotYetImplementedException();
21009 	}
21010 
21011 	private void translator(scope ubyte[] data) {
21012 		version(X11)
21013 			dg(cast(char[]) data);
21014 		else version(Windows)
21015 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21016 	}
21017 }
21018 
21019 /++
21020 	A simple handler for making your window accept drops of files, issued to you as file names.
21021 
21022 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21023 
21024 	History:
21025 		Added February 22, 2021
21026 +/
21027 /// Group: drag_and_drop
21028 
21029 class FilesDropHandler : GenericDropHandlerBase {
21030 	private void delegate(in char[][]) dg;
21031 
21032 	/++
21033 
21034 	+/
21035 	this(void delegate(in char[][] fileNames) dg) {
21036 		this.dg = dg;
21037 	}
21038 
21039 	protected override FormatHandler[] formatHandlers() {
21040 		version(X11)
21041 			return [
21042 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21043 			];
21044 		else version(Windows)
21045 			return [
21046 				FormatHandler(CF_HDROP, &translator),
21047 			];
21048 		else throw new NotYetImplementedException();
21049 	}
21050 
21051 	private void translator(scope ubyte[] data) {
21052 		version(X11) {
21053 			char[] listString = cast(char[]) data;
21054 			char[][16] buffer;
21055 			int count;
21056 			char[][] result = buffer[];
21057 
21058 			void commit(char[] s) {
21059 				if(count == result.length)
21060 					result.length += 16;
21061 				if(s.length > 7 && s[0 ..7] == "file://")
21062 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21063 				result[count++] = s;
21064 			}
21065 
21066 			size_t last;
21067 			foreach(idx, char c; listString) {
21068 				if(c == '\n') {
21069 					commit(listString[last .. idx - 1]); // a \r
21070 					last = idx + 1; // a \n
21071 				}
21072 			}
21073 
21074 			if(last < listString.length) {
21075 				commit(listString[last .. $]);
21076 			}
21077 
21078 			// FIXME: they are uris now, should I translate it to local file names?
21079 			// of course the host name is supposed to be there cuz of X rokking...
21080 
21081 			dg(result[0 .. count]);
21082 		} else version(Windows) {
21083 
21084 			static struct DROPFILES {
21085 				DWORD pFiles;
21086 				POINT pt;
21087 				BOOL  fNC;
21088 				BOOL  fWide;
21089 			}
21090 
21091 
21092 			const(char)[][16] buffer;
21093 			int count;
21094 			const(char)[][] result = buffer[];
21095 			size_t last;
21096 
21097 			void commitA(in char[] stuff) {
21098 				if(count == result.length)
21099 					result.length += 16;
21100 				result[count++] = stuff;
21101 			}
21102 
21103 			void commitW(in wchar[] stuff) {
21104 				commitA(makeUtf8StringFromWindowsString(stuff));
21105 			}
21106 
21107 			void magic(T)(T chars) {
21108 				size_t idx;
21109 				while(chars[idx]) {
21110 					last = idx;
21111 					while(chars[idx]) {
21112 						idx++;
21113 					}
21114 					static if(is(T == char*))
21115 						commitA(chars[last .. idx]);
21116 					else
21117 						commitW(chars[last .. idx]);
21118 					idx++;
21119 				}
21120 			}
21121 
21122 			auto df = cast(DROPFILES*) data.ptr;
21123 			if(df.fWide) {
21124 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21125 				magic(chars);
21126 			} else {
21127 				char* chars = cast(char*) (data.ptr + df.pFiles);
21128 				magic(chars);
21129 			}
21130 			dg(result[0 .. count]);
21131 		}
21132 		else throw new NotYetImplementedException();
21133 	}
21134 }
21135 
21136 /++
21137 	Interface to describe data being dragged. See also [draggable] helper function.
21138 
21139 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21140 
21141 	History:
21142 		Added February 19, 2021
21143 +/
21144 interface DraggableData {
21145 	version(X11)
21146 		alias FormatId = Atom;
21147 	else
21148 		alias FormatId = uint;
21149 	/++
21150 		Gets the platform-specific FormatId associated with the given named format.
21151 
21152 		This may be a MIME type, but may also be other various strings defined by the
21153 		programs you want to interoperate with.
21154 
21155 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
21156 		and convert it to some particular type for you.
21157 	+/
21158 	static FormatId getFormatId(string name)() {
21159 		version(X11)
21160 			return GetAtom!name(XDisplayConnection.get);
21161 		else version(Windows) {
21162 			static UINT cache;
21163 			if(!cache)
21164 				cache = RegisterClipboardFormatA(name);
21165 			return cache;
21166 		} else
21167 			throw new NotYetImplementedException();
21168 	}
21169 
21170 	/++
21171 		Looks up a string to represent the name for the given format, if there is one.
21172 
21173 		You should avoid using this function because it is slow. It is provided more for
21174 		debugging than for primary use.
21175 	+/
21176 	static string getFormatName(FormatId format) {
21177 		version(X11) {
21178 			if(format == 0)
21179 				return "None";
21180 			else
21181 				return getAtomName(format, XDisplayConnection.get);
21182 		} else version(Windows) {
21183 			switch(format) {
21184 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
21185 				case CF_DIBV5: return "CF_DIBV5";
21186 				case CF_RIFF: return "CF_RIFF";
21187 				case CF_WAVE: return "CF_WAVE";
21188 				case CF_HDROP: return "CF_HDROP";
21189 				default:
21190 					char[1024] name;
21191 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21192 					return name[0 .. count].idup;
21193 			}
21194 		} else throw new NotYetImplementedException();
21195 	}
21196 
21197 	FormatId[] availableFormats();
21198 	// Return the slice of data you filled, empty slice if done.
21199 	// this is to support the incremental thing
21200 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21201 
21202 	size_t dataLength(FormatId format);
21203 }
21204 
21205 /++
21206 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21207 
21208 	History:
21209 		Added February 19, 2021
21210 +/
21211 DraggableData draggable(string s) {
21212 	version(X11)
21213 	return new class X11SetSelectionHandler_Text, DraggableData {
21214 		this() {
21215 			super(s);
21216 		}
21217 
21218 		override FormatId[] availableFormats() {
21219 			return X11SetSelectionHandler_Text.availableFormats();
21220 		}
21221 
21222 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21223 			return X11SetSelectionHandler_Text.getData(format, data);
21224 		}
21225 
21226 		size_t dataLength(FormatId format) {
21227 			return s.length;
21228 		}
21229 	};
21230 	else version(Windows)
21231 	return new class DraggableData {
21232 		FormatId[] availableFormats() {
21233 			return [CF_UNICODETEXT];
21234 		}
21235 
21236 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21237 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21238 		}
21239 
21240 		size_t dataLength(FormatId format) {
21241 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21242 		}
21243 	};
21244 	else
21245 	throw new NotYetImplementedException();
21246 }
21247 
21248 /++
21249 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21250 
21251 	History:
21252 		Added February 19, 2021
21253 +/
21254 /// Group: drag_and_drop
21255 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21256 in {
21257 	assert(window !is null);
21258 	assert(handler !is null);
21259 }
21260 do
21261 {
21262 	version(X11) {
21263 		auto sh = cast(X11SetSelectionHandler) handler;
21264 		if(sh is null) {
21265 			// gotta make my own adapter.
21266 			sh = new class X11SetSelectionHandler {
21267 				mixin X11SetSelectionHandler_Basics;
21268 
21269 				Atom[] availableFormats() { return handler.availableFormats(); }
21270 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21271 					return handler.getData(format, data);
21272 				}
21273 
21274 				// since the drop selection is only ever used once it isn't important
21275 				// to reset it.
21276 				void done() {}
21277 			};
21278 		}
21279 		return doDragDropX11(window, sh, action);
21280 	} else version(Windows) {
21281 		return doDragDropWindows(window, handler, action);
21282 	} else throw new NotYetImplementedException();
21283 }
21284 
21285 version(Windows)
21286 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21287 	IDataObject obj = new class IDataObject {
21288 		ULONG refCount;
21289 		ULONG AddRef() {
21290 			return ++refCount;
21291 		}
21292 		ULONG Release() {
21293 			return --refCount;
21294 		}
21295 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21296 			if (IID_IUnknown == *riid) {
21297 				*ppv = cast(void*) cast(IUnknown) this;
21298 			}
21299 			else if (IID_IDataObject == *riid) {
21300 				*ppv = cast(void*) cast(IDataObject) this;
21301 			}
21302 			else {
21303 				*ppv = null;
21304 				return E_NOINTERFACE;
21305 			}
21306 
21307 			AddRef();
21308 			return NOERROR;
21309 		}
21310 
21311 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21312 			//  writeln("Advise");
21313 			return E_NOTIMPL;
21314 		}
21315 		HRESULT DUnadvise(DWORD dwConnection) {
21316 			return E_NOTIMPL;
21317 		}
21318 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21319 			//  writeln("EnumDAdvise");
21320 			return OLE_E_ADVISENOTSUPPORTED;
21321 		}
21322 		// tell what formats it supports
21323 
21324 		FORMATETC[] types;
21325 		this() {
21326 			FORMATETC t;
21327 			foreach(ty; handler.availableFormats()) {
21328 				assert(ty <= ushort.max && ty >= 0);
21329 				t.cfFormat = cast(ushort) ty;
21330 				t.lindex = -1;
21331 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21332 				t.tymed = TYMED.TYMED_HGLOBAL;
21333 			}
21334 			types ~= t;
21335 		}
21336 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21337 			if(dwDirection == DATADIR.DATADIR_GET) {
21338 				*ppenumFormatEtc = new class IEnumFORMATETC {
21339 					ULONG refCount;
21340 					ULONG AddRef() {
21341 						return ++refCount;
21342 					}
21343 					ULONG Release() {
21344 						return --refCount;
21345 					}
21346 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21347 						if (IID_IUnknown == *riid) {
21348 							*ppv = cast(void*) cast(IUnknown) this;
21349 						}
21350 						else if (IID_IEnumFORMATETC == *riid) {
21351 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21352 						}
21353 						else {
21354 							*ppv = null;
21355 							return E_NOINTERFACE;
21356 						}
21357 
21358 						AddRef();
21359 						return NOERROR;
21360 					}
21361 
21362 
21363 					int pos;
21364 					this() {
21365 						pos = 0;
21366 					}
21367 
21368 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21369 						// writeln("clone");
21370 						return E_NOTIMPL; // FIXME
21371 					}
21372 
21373 					// Caller is responsible for freeing memory
21374 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21375 						// fetched may be null if celt is one
21376 						if(celt != 1)
21377 							return E_NOTIMPL; // FIXME
21378 
21379 						if(celt + pos > types.length)
21380 							return S_FALSE;
21381 
21382 						*rgelt = types[pos++];
21383 
21384 						if(pceltFetched !is null)
21385 							*pceltFetched = 1;
21386 
21387 						// writeln("ok celt ", celt);
21388 						return S_OK;
21389 					}
21390 
21391 					HRESULT Reset() {
21392 						pos = 0;
21393 						return S_OK;
21394 					}
21395 
21396 					HRESULT Skip(ULONG celt) {
21397 						if(celt + pos <= types.length) {
21398 							pos += celt;
21399 							return S_OK;
21400 						}
21401 						return S_FALSE;
21402 					}
21403 				};
21404 
21405 				return S_OK;
21406 			} else
21407 				return E_NOTIMPL;
21408 		}
21409 		// given a format, return the format you'd prefer to use cuz it is identical
21410 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21411 			// FIXME: prolly could be better but meh
21412 			// writeln("gcf: ", *pformatectIn);
21413 			*pformatetcOut = *pformatectIn;
21414 			return S_OK;
21415 		}
21416 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21417 			foreach(ty; types) {
21418 				if(ty == *pformatetcIn) {
21419 					auto format = ty.cfFormat;
21420 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21421 					STGMEDIUM medium;
21422 					medium.tymed = TYMED.TYMED_HGLOBAL;
21423 
21424 					auto sz = handler.dataLength(format);
21425 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21426 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21427 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21428 						auto slice = data[0 .. sz];
21429 						scope(exit)
21430 							GlobalUnlock(handle);
21431 
21432 						handler.getData(format, cast(ubyte[]) slice[]);
21433 					}
21434 
21435 
21436 					medium.hGlobal = handle; // FIXME
21437 					*pmedium = medium;
21438 					return S_OK;
21439 				}
21440 			}
21441 			return DV_E_FORMATETC;
21442 		}
21443 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21444 			// writeln("GDH: ", *pformatetcIn);
21445 			return E_NOTIMPL; // FIXME
21446 		}
21447 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21448 			auto search = *pformatetc;
21449 			search.tymed &= TYMED.TYMED_HGLOBAL;
21450 			foreach(ty; types)
21451 				if(ty == search) {
21452 					// writeln("QueryGetData ", search, " ", types[0]);
21453 					return S_OK;
21454 				}
21455 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21456 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21457 			}
21458 			return S_FALSE;
21459 		}
21460 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21461 			//  writeln("SetData: ");
21462 			return E_NOTIMPL;
21463 		}
21464 	};
21465 
21466 
21467 	IDropSource src = new class IDropSource {
21468 		ULONG refCount;
21469 		ULONG AddRef() {
21470 			return ++refCount;
21471 		}
21472 		ULONG Release() {
21473 			return --refCount;
21474 		}
21475 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21476 			if (IID_IUnknown == *riid) {
21477 				*ppv = cast(void*) cast(IUnknown) this;
21478 			}
21479 			else if (IID_IDropSource == *riid) {
21480 				*ppv = cast(void*) cast(IDropSource) this;
21481 			}
21482 			else {
21483 				*ppv = null;
21484 				return E_NOINTERFACE;
21485 			}
21486 
21487 			AddRef();
21488 			return NOERROR;
21489 		}
21490 
21491 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21492 			if(fEscapePressed)
21493 				return DRAGDROP_S_CANCEL;
21494 			if(!(grfKeyState & MK_LBUTTON))
21495 				return DRAGDROP_S_DROP;
21496 			return S_OK;
21497 		}
21498 
21499 		int GiveFeedback(uint dwEffect) {
21500 			return DRAGDROP_S_USEDEFAULTCURSORS;
21501 		}
21502 	};
21503 
21504 	DWORD effect;
21505 
21506 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21507 
21508 	DROPEFFECT de = win32DragAndDropAction(action);
21509 
21510 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21511 	// but still prolly a FIXME
21512 
21513 	auto ret = DoDragDrop(obj, src, de, &effect);
21514 	/+
21515 	if(ret == DRAGDROP_S_DROP)
21516 		writeln("drop ", effect);
21517 	else if(ret == DRAGDROP_S_CANCEL)
21518 		writeln("cancel");
21519 	else if(ret == S_OK)
21520 		writeln("ok");
21521 	else writeln(ret);
21522 	+/
21523 
21524 	return ret;
21525 }
21526 
21527 version(Windows)
21528 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21529 	DROPEFFECT de;
21530 
21531 	with(DragAndDropAction)
21532 	with(DROPEFFECT)
21533 	final switch(action) {
21534 		case none: de = DROPEFFECT_NONE; break;
21535 		case copy: de = DROPEFFECT_COPY; break;
21536 		case move: de = DROPEFFECT_MOVE; break;
21537 		case link: de = DROPEFFECT_LINK; break;
21538 		case ask: throw new Exception("ask not implemented yet");
21539 		case custom: throw new Exception("custom not implemented yet");
21540 	}
21541 
21542 	return de;
21543 }
21544 
21545 
21546 /++
21547 	History:
21548 		Added February 19, 2021
21549 +/
21550 /// Group: drag_and_drop
21551 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21552 	version(X11) {
21553 		auto display = XDisplayConnection.get;
21554 
21555 		Atom atom = 5; // right???
21556 
21557 		XChangeProperty(
21558 			display,
21559 			window.impl.window,
21560 			GetAtom!"XdndAware"(display),
21561 			XA_ATOM,
21562 			32 /* bits */,
21563 			PropModeReplace,
21564 			&atom,
21565 			1);
21566 
21567 		window.dropHandler = handler;
21568 	} else version(Windows) {
21569 
21570 		initDnd();
21571 
21572 		auto dropTarget = new class (handler) IDropTarget {
21573 			DropHandler handler;
21574 			this(DropHandler handler) {
21575 				this.handler = handler;
21576 			}
21577 			ULONG refCount;
21578 			ULONG AddRef() {
21579 				return ++refCount;
21580 			}
21581 			ULONG Release() {
21582 				return --refCount;
21583 			}
21584 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21585 				if (IID_IUnknown == *riid) {
21586 					*ppv = cast(void*) cast(IUnknown) this;
21587 				}
21588 				else if (IID_IDropTarget == *riid) {
21589 					*ppv = cast(void*) cast(IDropTarget) this;
21590 				}
21591 				else {
21592 					*ppv = null;
21593 					return E_NOINTERFACE;
21594 				}
21595 
21596 				AddRef();
21597 				return NOERROR;
21598 			}
21599 
21600 
21601 			// ///////////////////
21602 
21603 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21604 				DropPackage dropPackage = DropPackage(pDataObj);
21605 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21606 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21607 			}
21608 
21609 			HRESULT DragLeave() {
21610 				handler.dragLeave();
21611 				// release the IDataObject if needed
21612 				return S_OK;
21613 			}
21614 
21615 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21616 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21617 
21618 				*pdwEffect = win32DragAndDropAction(res.action);
21619 				// same as DragEnter basically
21620 				return S_OK;
21621 			}
21622 
21623 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21624 				DropPackage pkg = DropPackage(pDataObj);
21625 				handler.drop(&pkg);
21626 
21627 				return S_OK;
21628 			}
21629 		};
21630 		// Windows can hold on to the handler and try to call it
21631 		// during which time the GC can't see it. so important to
21632 		// manually manage this. At some point i'll FIXME and make
21633 		// all my com instances manually managed since they supposed
21634 		// to respect the refcount.
21635 		import core.memory;
21636 		GC.addRoot(cast(void*) dropTarget);
21637 
21638 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21639 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21640 
21641 		window.dropHandler = handler;
21642 	} else throw new NotYetImplementedException();
21643 }
21644 
21645 
21646 
21647 static if(UsingSimpledisplayX11) {
21648 
21649 enum _NET_WM_STATE_ADD = 1;
21650 enum _NET_WM_STATE_REMOVE = 0;
21651 enum _NET_WM_STATE_TOGGLE = 2;
21652 
21653 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21654 void demandAttention(SimpleWindow window, bool needs = true) {
21655 	demandAttention(window.impl.window, needs);
21656 }
21657 
21658 /// ditto
21659 void demandAttention(Window window, bool needs = true) {
21660 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21661 }
21662 
21663 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21664 	auto display = XDisplayConnection.get();
21665 	if(atom == None)
21666 		return; // non-failure error
21667 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21668 
21669 	XClientMessageEvent xclient;
21670 
21671 	xclient.type = EventType.ClientMessage;
21672 	xclient.window = window;
21673 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21674 	xclient.format = 32;
21675 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21676 	xclient.data.l[1] = atom;
21677 	xclient.data.l[2] = atom2;
21678 	xclient.data.l[3] = 1;
21679 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21680 
21681 	XSendEvent(
21682 		display,
21683 		RootWindow(display, DefaultScreen(display)),
21684 		false,
21685 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21686 		cast(XEvent*) &xclient
21687 	);
21688 
21689 	/+
21690 	XChangeProperty(
21691 		display,
21692 		window.impl.window,
21693 		GetAtom!"_NET_WM_STATE"(display),
21694 		XA_ATOM,
21695 		32 /* bits */,
21696 		PropModeAppend,
21697 		&atom,
21698 		1);
21699 	+/
21700 }
21701 
21702 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21703 	Atom actionAtom;
21704 	with(DragAndDropAction)
21705 	final switch(action) {
21706 		case none: actionAtom = None; break;
21707 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21708 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21709 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21710 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21711 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21712 	}
21713 
21714 	return actionAtom;
21715 }
21716 
21717 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21718 	// FIXME: I need to show user feedback somehow.
21719 	auto display = XDisplayConnection.get;
21720 
21721 	auto actionAtom = dndActionAtom(display, action);
21722 	assert(actionAtom, "Don't use action none to accept a drop");
21723 
21724 	setX11Selection!"XdndSelection"(window, handler, null);
21725 
21726 	auto oldKeyHandler = window.handleKeyEvent;
21727 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21728 
21729 	auto oldCharHandler = window.handleCharEvent;
21730 	scope(exit) window.handleCharEvent = oldCharHandler;
21731 
21732 	auto oldMouseHandler = window.handleMouseEvent;
21733 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21734 
21735 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21736 
21737 	import core.sys.posix.sys.time;
21738 	timeval tv;
21739 	gettimeofday(&tv, null);
21740 
21741 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21742 
21743 	Time lastMouseTimestamp;
21744 
21745 	bool dnding = true;
21746 	Window lastIn = None;
21747 
21748 	void leave() {
21749 		if(lastIn == None)
21750 			return;
21751 
21752 		XEvent ev;
21753 		ev.xclient.type = EventType.ClientMessage;
21754 		ev.xclient.window = lastIn;
21755 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21756 		ev.xclient.format = 32;
21757 		ev.xclient.data.l[0] = window.impl.window;
21758 
21759 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21760 		XFlush(display);
21761 
21762 		lastIn = None;
21763 	}
21764 
21765 	void enter(Window w) {
21766 		assert(lastIn == None);
21767 
21768 		lastIn = w;
21769 
21770 		XEvent ev;
21771 		ev.xclient.type = EventType.ClientMessage;
21772 		ev.xclient.window = lastIn;
21773 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21774 		ev.xclient.format = 32;
21775 		ev.xclient.data.l[0] = window.impl.window;
21776 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21777 
21778 		auto types = handler.availableFormats();
21779 		assert(types.length > 0);
21780 
21781 		ev.xclient.data.l[2] = types[0];
21782 		if(types.length > 1)
21783 			ev.xclient.data.l[3] = types[1];
21784 		if(types.length > 2)
21785 			ev.xclient.data.l[4] = types[2];
21786 
21787 		// FIXME: other types?!?!? and make sure we skip TARGETS
21788 
21789 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21790 		XFlush(display);
21791 	}
21792 
21793 	void position(int rootX, int rootY) {
21794 		assert(lastIn != None);
21795 
21796 		XEvent ev;
21797 		ev.xclient.type = EventType.ClientMessage;
21798 		ev.xclient.window = lastIn;
21799 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21800 		ev.xclient.format = 32;
21801 		ev.xclient.data.l[0] = window.impl.window;
21802 		ev.xclient.data.l[1] = 0; // reserved
21803 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21804 		ev.xclient.data.l[3] = dataTimestamp;
21805 		ev.xclient.data.l[4] = actionAtom;
21806 
21807 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21808 		XFlush(display);
21809 
21810 	}
21811 
21812 	void drop() {
21813 		XEvent ev;
21814 		ev.xclient.type = EventType.ClientMessage;
21815 		ev.xclient.window = lastIn;
21816 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21817 		ev.xclient.format = 32;
21818 		ev.xclient.data.l[0] = window.impl.window;
21819 		ev.xclient.data.l[1] = 0; // reserved
21820 		ev.xclient.data.l[2] = dataTimestamp;
21821 
21822 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21823 		XFlush(display);
21824 
21825 		lastIn = None;
21826 		dnding = false;
21827 	}
21828 
21829 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21830 	// but idk if i should...
21831 
21832 	window.setEventHandlers(
21833 		delegate(KeyEvent ev) {
21834 			if(ev.pressed == true && ev.key == Key.Escape) {
21835 				// cancel
21836 				dnding = false;
21837 			}
21838 		},
21839 		delegate(MouseEvent ev) {
21840 			if(ev.timestamp < lastMouseTimestamp)
21841 				return;
21842 
21843 			lastMouseTimestamp = ev.timestamp;
21844 
21845 			if(ev.type == MouseEventType.motion) {
21846 				auto display = XDisplayConnection.get;
21847 				auto root = RootWindow(display, DefaultScreen(display));
21848 
21849 				Window topWindow;
21850 				int rootX, rootY;
21851 
21852 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21853 
21854 				if(topWindow == None)
21855 					return;
21856 
21857 				top:
21858 				if(auto result = topWindow in eligibility) {
21859 					auto dropWindow = *result;
21860 					if(dropWindow == None) {
21861 						leave();
21862 						return;
21863 					}
21864 
21865 					if(dropWindow != lastIn) {
21866 						leave();
21867 						enter(dropWindow);
21868 						position(rootX, rootY);
21869 					} else {
21870 						position(rootX, rootY);
21871 					}
21872 				} else {
21873 					// determine eligibility
21874 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21875 					if(data.length == 1) {
21876 						// in case there is no WM or it isn't reparenting
21877 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21878 					} else {
21879 
21880 						Window tryScanChildren(Window search, int maxRecurse) {
21881 							// could be reparenting window manager, so gotta check the next few children too
21882 							Window child;
21883 							int x;
21884 							int y;
21885 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21886 
21887 							if(child == None)
21888 								return None;
21889 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21890 							if(data.length == 1) {
21891 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21892 							} else {
21893 								if(maxRecurse)
21894 									return tryScanChildren(child, maxRecurse - 1);
21895 								else
21896 									return None;
21897 							}
21898 
21899 						}
21900 
21901 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21902 						auto topResult = tryScanChildren(topWindow, 3);
21903 						// it is easy to have a false negative due to the mouse going over a WM
21904 						// child window like the close button if separate from the frame... so I
21905 						// can't really cache negatives, :(
21906 						if(topResult != None) {
21907 							eligibility[topWindow] = topResult;
21908 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21909 						}
21910 					}
21911 
21912 				}
21913 
21914 			} else if(ev.type == MouseEventType.buttonReleased) {
21915 				drop();
21916 				dnding = false;
21917 			}
21918 		}
21919 	);
21920 
21921 	window.grabInput();
21922 	scope(exit)
21923 		window.releaseInputGrab();
21924 
21925 
21926 	EventLoop.get.run(() => dnding);
21927 
21928 	return 0;
21929 }
21930 
21931 /// X-specific
21932 TrueColorImage getWindowNetWmIcon(Window window) {
21933 	try {
21934 		auto display = XDisplayConnection.get;
21935 
21936 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21937 
21938 		if (data.length > arch_ulong.sizeof * 2) {
21939 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21940 			// these are an array of rgba images that we have to convert into pixmaps ourself
21941 
21942 			int width = cast(int) meta[0];
21943 			int height = cast(int) meta[1];
21944 
21945 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21946 
21947 			static if(arch_ulong.sizeof == 4) {
21948 				bytes = bytes[0 .. width * height * 4];
21949 				alias imageData = bytes;
21950 			} else static if(arch_ulong.sizeof == 8) {
21951 				bytes = bytes[0 .. width * height * 8];
21952 				auto imageData = new ubyte[](4 * width * height);
21953 			} else static assert(0);
21954 
21955 
21956 
21957 			// this returns ARGB. Remember it is little-endian so
21958 			//                                         we have BGRA
21959 			// our thing uses RGBA, which in little endian, is ABGR
21960 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21961 				auto r = bytes[idx + 2];
21962 				auto g = bytes[idx + 1];
21963 				auto b = bytes[idx + 0];
21964 				auto a = bytes[idx + 3];
21965 
21966 				imageData[idx2 + 0] = r;
21967 				imageData[idx2 + 1] = g;
21968 				imageData[idx2 + 2] = b;
21969 				imageData[idx2 + 3] = a;
21970 			}
21971 
21972 			return new TrueColorImage(width, height, imageData);
21973 		}
21974 
21975 		return null;
21976 	} catch(Exception e) {
21977 		return null;
21978 	}
21979 }
21980 
21981 } /* UsingSimpledisplayX11 */
21982 
21983 
21984 void loadBinNameToWindowClassName () {
21985 	import core.stdc.stdlib : realloc;
21986 	version(linux) {
21987 		// args[0] MAY be empty, so we'll just use this
21988 		import core.sys.posix.unistd : readlink;
21989 		char[1024] ebuf = void; // 1KB should be enough for everyone!
21990 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
21991 		if (len < 1) return;
21992 	} else /*version(Windows)*/ {
21993 		import core.runtime : Runtime;
21994 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
21995 		auto ebuf = Runtime.args[0];
21996 		auto len = ebuf.length;
21997 	}
21998 	auto pos = len;
21999 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22000 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22001 	if (sdpyWindowClassStr is null) return; // oops
22002 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22003 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22004 }
22005 
22006 /++
22007 	An interface representing a font that is drawn with custom facilities.
22008 
22009 	You might want [OperatingSystemFont] instead, which represents
22010 	a font loaded and drawn by functions native to the operating system.
22011 
22012 	WARNING: I might still change this.
22013 +/
22014 interface DrawableFont : MeasurableFont {
22015 	/++
22016 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22017 
22018 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22019 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22020 		fill color, but that's up to the implementation.
22021 	+/
22022 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22023 
22024 	/++
22025 		Requests that the given string is added to the image cache. You should only do this rarely, but
22026 		if you have a string that you know will be used over and over again, adding it to a cache can
22027 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22028 		to implement this as a do-nothing method).
22029 	+/
22030 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22031 }
22032 
22033 /++
22034 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22035 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22036 
22037 	You should also consider [OperatingSystemFont], which loads and draws a font with
22038 	facilities native to the user's operating system. You might also consider
22039 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22040 	of game, as they have their own ways to draw text too.
22041 
22042 	Be warned: this can be slow, especially on remote connections to the X server, since
22043 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22044 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22045 	experiment in your specific case.
22046 
22047 	Please note that the return type of [DrawableFont] also includes an implementation of
22048 	[MeasurableFont].
22049 +/
22050 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22051 	import arsd.ttf;
22052 	static class ArsdTtfFont : DrawableFont {
22053 		TtfFont font;
22054 		int size;
22055 		this(in ubyte[] data, int size) {
22056 			font = TtfFont(data);
22057 			this.size = size;
22058 
22059 
22060 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22061 			int ascent_, descent_, line_gap;
22062 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22063 
22064 			int advance, lsb;
22065 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22066 			xWidth = cast(int) (advance * scale);
22067 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22068 			MWidth = cast(int) (advance * scale);
22069 		}
22070 
22071 		private int ascent_;
22072 		private int descent_;
22073 		private int xWidth;
22074 		private int MWidth;
22075 
22076 		bool isMonospace() {
22077 			return xWidth == MWidth;
22078 		}
22079 		int averageWidth() {
22080 			return xWidth;
22081 		}
22082 		int height() {
22083 			return size;
22084 		}
22085 		int ascent() {
22086 			return ascent_;
22087 		}
22088 		int descent() {
22089 			return descent_;
22090 		}
22091 
22092 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
22093 			int width, height;
22094 			font.getStringSize(s, size, width, height);
22095 			return width;
22096 		}
22097 
22098 
22099 
22100 		Sprite[string] cache;
22101 
22102 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
22103 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
22104 			cache[text] = sprite;
22105 		}
22106 
22107 		Image stringToImage(Color fg, Color bg, in char[] text) {
22108 			int width, height;
22109 			auto data = font.renderString(text, size, width, height);
22110 			auto image = new TrueColorImage(width, height);
22111 			int pos = 0;
22112 			foreach(y; 0 .. height)
22113 			foreach(x; 0 .. width) {
22114 				fg.a = data[0];
22115 				bg.a = 255;
22116 				auto color = alphaBlend(fg, bg);
22117 				image.imageData.bytes[pos++] = color.r;
22118 				image.imageData.bytes[pos++] = color.g;
22119 				image.imageData.bytes[pos++] = color.b;
22120 				image.imageData.bytes[pos++] = data[0];
22121 				data = data[1 .. $];
22122 			}
22123 			assert(data.length == 0);
22124 
22125 			return Image.fromMemoryImage(image);
22126 		}
22127 
22128 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22129 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22130 
22131 			auto fg = painter.impl._outlineColor;
22132 			auto bg = painter.impl._fillColor;
22133 
22134 			if(sprite !is null) {
22135 				auto w = cast(SimpleWindow) painter.window;
22136 				assert(w !is null);
22137 
22138 				sprite.drawAt(painter, upperLeft);
22139 			} else {
22140 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
22141 			}
22142 		}
22143 	}
22144 
22145 	return new ArsdTtfFont(data, size);
22146 }
22147 
22148 class NotYetImplementedException : Exception {
22149 	this(string file = __FILE__, size_t line = __LINE__) {
22150 		super("Not yet implemented", file, line);
22151 	}
22152 }
22153 
22154 ///
22155 __gshared bool librariesSuccessfullyLoaded = true;
22156 ///
22157 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
22158 
22159 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
22160 	mixin(staticForeachReplacement!Iface);
22161 
22162 	void loadDynamicLibrary() @nogc {
22163 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22164 	}
22165 
22166         void loadDynamicLibraryForReal() {
22167                 foreach(name; __traits(derivedMembers, Iface)) {
22168                         mixin("alias tmp = " ~ name ~ ";");
22169                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
22170                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
22171                 }
22172         }
22173 }
22174 
22175 private const(char)[] staticForeachReplacement(Iface)() pure {
22176 /*
22177 	// just this for gdc 9....
22178 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
22179 
22180         static foreach(name; __traits(derivedMembers, Iface))
22181                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22182 */
22183 
22184 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
22185 	size_t pos;
22186 
22187 	void append(in char[] what) {
22188 		if(pos + what.length > code.length)
22189 			code.length = (code.length * 3) / 2;
22190 		code[pos .. pos + what.length] = what[];
22191 		pos += what.length;
22192 	}
22193 
22194         foreach(name; __traits(derivedMembers, Iface)) {
22195                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
22196 		append(name);
22197 		append(`")) `);
22198 		append(name);
22199 		append(";");
22200 	}
22201 
22202 	return code[0 .. pos];
22203 }
22204 
22205 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22206 	mixin(staticForeachReplacement!Iface);
22207 
22208 	private __gshared void* libHandle;
22209 	private __gshared bool attempted;
22210 
22211         void loadDynamicLibrary() @nogc {
22212 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22213 	}
22214 
22215 	bool loadAttempted() {
22216 		return attempted;
22217 	}
22218 	bool loadSuccessful() {
22219 		return libHandle !is null;
22220 	}
22221 
22222         void loadDynamicLibraryForReal() {
22223 		attempted = true;
22224                 version(Posix) {
22225                         import core.sys.posix.dlfcn;
22226 			version(OSX) {
22227 				version(X11)
22228                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22229 				else
22230                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22231 			} else {
22232                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22233 				if(libHandle is null)
22234                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22235 			}
22236 
22237 			static void* loadsym(void* l, const char* name) {
22238 				import core.stdc.stdlib;
22239 				if(l is null)
22240 					return &abort;
22241 				return dlsym(l, name);
22242 			}
22243                 } else version(Windows) {
22244                         import core.sys.windows.winbase;
22245                         libHandle = LoadLibrary(library ~ ".dll");
22246 			static void* loadsym(void* l, const char* name) {
22247 				import core.stdc.stdlib;
22248 				if(l is null)
22249 					return &abort;
22250 				return GetProcAddress(l, name);
22251 			}
22252                 }
22253                 if(libHandle is null) {
22254 			success = false;
22255                         //throw new Exception("load failure of library " ~ library);
22256 		}
22257                 foreach(name; __traits(derivedMembers, Iface)) {
22258                         mixin("alias tmp = " ~ name ~ ";");
22259                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22260                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22261                 }
22262         }
22263 
22264         void unloadDynamicLibrary() {
22265                 version(Posix) {
22266                         import core.sys.posix.dlfcn;
22267                         dlclose(libHandle);
22268                 } else version(Windows) {
22269                         import core.sys.windows.winbase;
22270                         FreeLibrary(libHandle);
22271                 }
22272                 foreach(name; __traits(derivedMembers, Iface))
22273                         mixin(name ~ " = null;");
22274         }
22275 }
22276 
22277 /+
22278 	The GC can be called from any thread, and a lot of cleanup must be done
22279 	on the gui thread. Since the GC can interrupt any locks - including being
22280 	triggered inside a critical section - it is vital to avoid deadlocks to get
22281 	these functions called from the right place.
22282 
22283 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22284 	right now.
22285 
22286 	The cleanup function is run when the event loop gets around to it, which is just
22287 	whenever there's something there after it has been woken up for other work. It does
22288 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22289 	(Well actually it might be ok but i don't wanna mess with it right now.)
22290 +/
22291 private struct CleanupQueue {
22292 	import core.stdc.stdlib;
22293 
22294 	void queue(alias func, T...)(T args) {
22295 		static struct Args {
22296 			T args;
22297 		}
22298 		static struct RealJob {
22299 			Job j;
22300 			Args a;
22301 		}
22302 		static void call(Job* data) {
22303 			auto rj = cast(RealJob*) data;
22304 			func(rj.a.args);
22305 		}
22306 
22307 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22308 		thing.j.call = &call;
22309 		thing.a.args = args;
22310 
22311 		buffer[tail++] = cast(Job*) thing;
22312 
22313 		// FIXME: set overflowed
22314 	}
22315 
22316 	void process() {
22317 		const tail = this.tail;
22318 
22319 		while(tail != head) {
22320 			Job* job = cast(Job*) buffer[head++];
22321 			job.call(job);
22322 			free(job);
22323 		}
22324 
22325 		if(overflowed)
22326 			throw new Exception("cleanup overflowed");
22327 	}
22328 
22329 	private:
22330 
22331 	ubyte tail; // must ONLY be written by queue
22332 	ubyte head; // must ONLY be written by process
22333 	bool overflowed;
22334 
22335 	static struct Job {
22336 		void function(Job*) call;
22337 	}
22338 
22339 	void*[256] buffer;
22340 }
22341 private __gshared CleanupQueue cleanupQueue;
22342 
22343 version(X11)
22344 /++
22345 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22346 
22347 	$(WARNING
22348 		This function is exempted from stability guarantees.
22349 	)
22350 +/
22351 float customScalingFactorForMonitor(int monitorNumber) {
22352 	import core.stdc.stdlib;
22353 	auto val = getenv("ARSD_SCALING_FACTOR");
22354 
22355 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
22356 	if(val is null)
22357 		return 1.0;
22358 
22359 	char[16] buffer = 0;
22360 	int pos;
22361 
22362 	const(char)* at = val;
22363 
22364 	foreach(item; 0 .. monitorNumber + 1) {
22365 		if(*at == 0)
22366 			break; // reuse the last number when we at the end of the string
22367 		pos = 0;
22368 		while(pos + 1 < buffer.length && *at && *at != ';') {
22369 			buffer[pos++] = *at;
22370 			at++;
22371 		}
22372 		if(*at)
22373 			at++; // skip the semicolon
22374 		buffer[pos] = 0;
22375 	}
22376 
22377 	//sdpyPrintDebugString(buffer[0 .. pos]);
22378 
22379 	import core.stdc.math;
22380 	auto f = atof(buffer.ptr);
22381 
22382 	if(f <= 0.0 || isnan(f) || isinf(f))
22383 		return 1.0;
22384 
22385 	return f;
22386 }
22387 
22388 void guiAbortProcess(string msg) {
22389 	import core.stdc.stdlib;
22390 	version(Windows) {
22391 		WCharzBuffer t = WCharzBuffer(msg);
22392 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22393 	} else {
22394 		import core.stdc.stdio;
22395 		fwrite(msg.ptr, 1, msg.length, stderr);
22396 		msg = "\n";
22397 		fwrite(msg.ptr, 1, msg.length, stderr);
22398 		fflush(stderr);
22399 	}
22400 
22401 	abort();
22402 }
22403 
22404 private int minInternal(int a, int b) {
22405 	return (a < b) ? a : b;
22406 }
22407 
22408 private alias scriptable = arsd_jsvar_compatible;