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/VulkanizeDSdpy
479 
480 	https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
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 __gshared 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 		__gshared static Atom a;
6122 		if(!a) {
6123 			a = XInternAtom(display, name, !create);
6124 			// FIXME: might need to synchronize this and attach it to the actual object
6125 			interredAtoms ~= &a;
6126 		}
6127 		if(a == None)
6128 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6129 		return a;
6130 	}
6131 
6132 	/// Platform-specific for X11 - gets atom names as a string.
6133 	string getAtomName(Atom atom, Display* display) {
6134 		auto got = XGetAtomName(display, atom);
6135 		scope(exit) XFree(got);
6136 		import core.stdc.string;
6137 		string s = got[0 .. strlen(got)].idup;
6138 		return s;
6139 	}
6140 
6141 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6142 	void setPrimarySelection(SimpleWindow window, string text) {
6143 		setX11Selection!"PRIMARY"(window, text);
6144 	}
6145 
6146 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6147 	void setSecondarySelection(SimpleWindow window, string text) {
6148 		setX11Selection!"SECONDARY"(window, text);
6149 	}
6150 
6151 	interface X11SetSelectionHandler {
6152 		// should include TARGETS right now
6153 		Atom[] availableFormats();
6154 		// Return the slice of data you filled, empty slice if done.
6155 		// this is to support the incremental thing
6156 		ubyte[] getData(Atom format, return scope ubyte[] data);
6157 
6158 		void done();
6159 
6160 		void handleRequest(XEvent);
6161 
6162 		bool matchesIncr(Window, Atom);
6163 		void sendMoreIncr(XPropertyEvent*);
6164 	}
6165 
6166 	mixin template X11SetSelectionHandler_Basics() {
6167 		Window incrWindow;
6168 		Atom incrAtom;
6169 		Atom selectionAtom;
6170 		Atom formatAtom;
6171 		ubyte[] toSend;
6172 		bool matchesIncr(Window w, Atom a) {
6173 			return incrAtom && incrAtom == a && w == incrWindow;
6174 		}
6175 		void sendMoreIncr(XPropertyEvent* event) {
6176 			auto display = XDisplayConnection.get;
6177 
6178 			XChangeProperty (display,
6179 				incrWindow,
6180 				incrAtom,
6181 				formatAtom,
6182 				8 /* bits */, PropModeReplace,
6183 				toSend.ptr, cast(int) toSend.length);
6184 
6185 			if(toSend.length != 0) {
6186 				toSend = this.getData(formatAtom, toSend[]);
6187 			} else {
6188 				this.done();
6189 				incrWindow = None;
6190 				incrAtom = None;
6191 				selectionAtom = None;
6192 				formatAtom = None;
6193 				toSend = null;
6194 			}
6195 		}
6196 		void handleRequest(XEvent ev) {
6197 
6198 			auto display = XDisplayConnection.get;
6199 
6200 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6201 			XSelectionEvent selectionEvent;
6202 			selectionEvent.type = EventType.SelectionNotify;
6203 			selectionEvent.display = event.display;
6204 			selectionEvent.requestor = event.requestor;
6205 			selectionEvent.selection = event.selection;
6206 			selectionEvent.time = event.time;
6207 			selectionEvent.target = event.target;
6208 
6209 			bool supportedType() {
6210 				foreach(t; this.availableFormats())
6211 					if(t == event.target)
6212 						return true;
6213 				return false;
6214 			}
6215 
6216 			if(event.property == None) {
6217 				selectionEvent.property = event.target;
6218 
6219 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6220 				XFlush(display);
6221 			} if(event.target == GetAtom!"TARGETS"(display)) {
6222 				/* respond with the supported types */
6223 				auto tlist = this.availableFormats();
6224 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6225 				selectionEvent.property = event.property;
6226 
6227 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6228 				XFlush(display);
6229 			} else if(supportedType()) {
6230 				auto buffer = new ubyte[](1024 * 64);
6231 				auto toSend = this.getData(event.target, buffer[]);
6232 
6233 				if(toSend.length < 32 * 1024) {
6234 					// small enough to send directly...
6235 					selectionEvent.property = event.property;
6236 					XChangeProperty (display,
6237 						selectionEvent.requestor,
6238 						selectionEvent.property,
6239 						event.target,
6240 						8 /* bits */, 0 /* PropModeReplace */,
6241 						toSend.ptr, cast(int) toSend.length);
6242 
6243 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6244 					XFlush(display);
6245 				} else {
6246 					// large, let's send incrementally
6247 					arch_ulong l = toSend.length;
6248 
6249 					// if I wanted other events from this window don't want to clear that out....
6250 					XWindowAttributes xwa;
6251 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6252 
6253 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6254 
6255 					incrWindow = event.requestor;
6256 					incrAtom = event.property;
6257 					formatAtom = event.target;
6258 					selectionAtom = event.selection;
6259 					this.toSend = toSend;
6260 
6261 					selectionEvent.property = event.property;
6262 					XChangeProperty (display,
6263 						selectionEvent.requestor,
6264 						selectionEvent.property,
6265 						GetAtom!"INCR"(display),
6266 						32 /* bits */, PropModeReplace,
6267 						&l, 1);
6268 
6269 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6270 					XFlush(display);
6271 				}
6272 				//if(after)
6273 					//after();
6274 			} else {
6275 				debug(sdpy_clip) {
6276 					writeln("Unsupported data ", getAtomName(event.target, display));
6277 				}
6278 				selectionEvent.property = None; // I don't know how to handle this type...
6279 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6280 				XFlush(display);
6281 			}
6282 		}
6283 	}
6284 
6285 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6286 		mixin X11SetSelectionHandler_Basics;
6287 		private const(ubyte)[] text;
6288 		private const(ubyte)[] text_original;
6289 		this(string text) {
6290 			this.text = cast(const ubyte[]) text;
6291 			this.text_original = this.text;
6292 		}
6293 		Atom[] availableFormats() {
6294 			auto display = XDisplayConnection.get;
6295 			return [
6296 				GetAtom!"UTF8_STRING"(display),
6297 				GetAtom!"text/plain"(display),
6298 				XA_STRING,
6299 				GetAtom!"TARGETS"(display)
6300 			];
6301 		}
6302 
6303 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6304 			if(text.length < data.length) {
6305 				data[0 .. text.length] = text[];
6306 				return data[0 .. text.length];
6307 			} else {
6308 				data[] = text[0 .. data.length];
6309 				text = text[data.length .. $];
6310 				return data[];
6311 			}
6312 		}
6313 
6314 		void done() {
6315 			text = text_original;
6316 		}
6317 	}
6318 
6319 	/// 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?!)
6320 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6321 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6322 	}
6323 
6324 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6325 		assert(window !is null);
6326 
6327 		auto display = XDisplayConnection.get();
6328 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6329 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6330 		else Atom a = GetAtom!atomName(display);
6331 
6332 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6333 
6334 		window.impl.setSelectionHandlers[a] = data;
6335 	}
6336 
6337 	///
6338 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6339 		getX11Selection!"PRIMARY"(window, handler);
6340 	}
6341 
6342 	// added July 28, 2020
6343 	// undocumented as experimental tho
6344 	interface X11GetSelectionHandler {
6345 		void handleData(Atom target, in ubyte[] data);
6346 		Atom findBestFormat(Atom[] answer);
6347 
6348 		void prepareIncremental(Window, Atom);
6349 		bool matchesIncr(Window, Atom);
6350 		void handleIncrData(Atom, in ubyte[] data);
6351 	}
6352 
6353 	mixin template X11GetSelectionHandler_Basics() {
6354 		Window incrWindow;
6355 		Atom incrAtom;
6356 
6357 		void prepareIncremental(Window w, Atom a) {
6358 			incrWindow = w;
6359 			incrAtom = a;
6360 		}
6361 		bool matchesIncr(Window w, Atom a) {
6362 			return incrWindow == w && incrAtom == a;
6363 		}
6364 
6365 		Atom incrFormatAtom;
6366 		ubyte[] incrData;
6367 		void handleIncrData(Atom format, in ubyte[] data) {
6368 			incrFormatAtom = format;
6369 
6370 			if(data.length)
6371 				incrData ~= data;
6372 			else
6373 				handleData(incrFormatAtom, incrData);
6374 
6375 		}
6376 	}
6377 
6378 	///
6379 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6380 		assert(window !is null);
6381 
6382 		auto display = XDisplayConnection.get();
6383 		auto atom = GetAtom!atomName(display);
6384 
6385 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6386 			this(void delegate(in char[]) handler) {
6387 				this.handler = handler;
6388 			}
6389 
6390 			mixin X11GetSelectionHandler_Basics;
6391 
6392 			void delegate(in char[]) handler;
6393 
6394 			void handleData(Atom target, in ubyte[] data) {
6395 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6396 					handler(cast(const char[]) data);
6397 			}
6398 
6399 			Atom findBestFormat(Atom[] answer) {
6400 				Atom best = None;
6401 				foreach(option; answer) {
6402 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6403 						best = option;
6404 						break;
6405 					} else if(option == XA_STRING) {
6406 						best = option;
6407 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6408 						best = option;
6409 					}
6410 				}
6411 				return best;
6412 			}
6413 		}
6414 
6415 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6416 
6417 		auto target = GetAtom!"TARGETS"(display);
6418 
6419 		// SDD_DATA is "simpledisplay.d data"
6420 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6421 	}
6422 
6423 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6424 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6425 		assert(window !is null);
6426 
6427 		auto display = XDisplayConnection.get();
6428 		auto atom = GetAtom!atomName(display);
6429 
6430 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6431 			this(void delegate(MemoryImage) handler) {
6432 				this.handler = handler;
6433 			}
6434 
6435 			mixin X11GetSelectionHandler_Basics;
6436 
6437 			void delegate(MemoryImage) handler;
6438 
6439 			void handleData(Atom target, in ubyte[] data) {
6440 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6441 					import arsd.bmp;
6442 					handler(readBmp(data));
6443 				}
6444 			}
6445 
6446 			Atom findBestFormat(Atom[] answer) {
6447 				Atom best = None;
6448 				foreach(option; answer) {
6449 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6450 						best = option;
6451 					}
6452 				}
6453 				return best;
6454 			}
6455 
6456 		}
6457 
6458 
6459 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6460 
6461 		auto target = GetAtom!"TARGETS"(display);
6462 
6463 		// SDD_DATA is "simpledisplay.d data"
6464 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6465 	}
6466 
6467 
6468 	///
6469 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6470 		Atom actualType;
6471 		int actualFormat;
6472 		arch_ulong actualItems;
6473 		arch_ulong bytesRemaining;
6474 		void* data;
6475 
6476 		auto display = XDisplayConnection.get();
6477 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6478 			if(actualFormat == 0)
6479 				return null;
6480 			else {
6481 				int byteLength;
6482 				if(actualFormat == 32) {
6483 					// 32 means it is a C long... which is variable length
6484 					actualFormat = cast(int) arch_long.sizeof * 8;
6485 				}
6486 
6487 				// then it is just a bit count
6488 				byteLength = cast(int) (actualItems * actualFormat / 8);
6489 
6490 				auto d = new ubyte[](byteLength);
6491 				d[] = cast(ubyte[]) data[0 .. byteLength];
6492 				XFree(data);
6493 				return d;
6494 			}
6495 		}
6496 		return null;
6497 	}
6498 
6499 	/* defined in the systray spec */
6500 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6501 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6502 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6503 
6504 
6505 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6506 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6507 	public class GlobalHotkey {
6508 		KeyEvent key;
6509 		void delegate () handler;
6510 
6511 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6512 
6513 		/// Create from initialzed KeyEvent object
6514 		this (KeyEvent akey, void delegate () ahandler=null) {
6515 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6516 			key = akey;
6517 			handler = ahandler;
6518 		}
6519 
6520 		/// Create from emacs-like key name ("C-M-Y", etc.)
6521 		this (const(char)[] akey, void delegate () ahandler=null) {
6522 			key = KeyEvent.parse(akey);
6523 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6524 			handler = ahandler;
6525 		}
6526 
6527 	}
6528 
6529 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6530 		//conwriteln("failed to grab key");
6531 		GlobalHotkeyManager.ghfailed = true;
6532 		return 0;
6533 	}
6534 
6535 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6536 		Image.impl.xshmfailed = true;
6537 		return 0;
6538 	}
6539 
6540 	private __gshared int errorHappened;
6541 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6542 		import core.stdc.stdio;
6543 		char[265] buffer;
6544 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6545 		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);
6546 		errorHappened = true;
6547 		return 0;
6548 	}
6549 
6550 	/++
6551 		Global hotkey manager. It contains static methods to manage global hotkeys.
6552 
6553 		---
6554 		 try {
6555 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6556 		} catch (Exception e) {
6557 			conwriteln("ERROR registering hotkey!");
6558 		}
6559 		EventLoop.get.run();
6560 		---
6561 
6562 		The key strings are based on Emacs. In practical terms,
6563 		`M` means `alt` and `H` means the Windows logo key. `C`
6564 		is `ctrl`.
6565 
6566 		$(WARNING
6567 			This is X-specific right now. If you are on
6568 			Windows, try [registerHotKey] instead.
6569 
6570 			We will probably merge these into a single
6571 			interface later.
6572 		)
6573 	+/
6574 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6575 		version(X11) {
6576 			void recreateAfterDisconnect() {
6577 				throw new Exception("NOT IMPLEMENTED");
6578 			}
6579 			void discardConnectionState() {
6580 				throw new Exception("NOT IMPLEMENTED");
6581 			}
6582 		}
6583 
6584 		private static immutable uint[8] masklist = [ 0,
6585 			KeyOrButtonMask.LockMask,
6586 			KeyOrButtonMask.Mod2Mask,
6587 			KeyOrButtonMask.Mod3Mask,
6588 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6589 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6590 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6591 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6592 		];
6593 		private __gshared GlobalHotkeyManager ghmanager;
6594 		private __gshared bool ghfailed = false;
6595 
6596 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6597 			if (modmask == 0) return false;
6598 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6599 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6600 			return true;
6601 		}
6602 
6603 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6604 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6605 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6606 			return modmask;
6607 		}
6608 
6609 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6610 			uint keycode = cast(uint)ke.key;
6611 			auto dpy = XDisplayConnection.get;
6612 			return XKeysymToKeycode(dpy, keycode);
6613 		}
6614 
6615 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6616 
6617 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6618 
6619 		NativeEventHandler getNativeEventHandler () {
6620 			return delegate int (XEvent e) {
6621 				if (e.type != EventType.KeyPress) return 1;
6622 				auto kev = cast(const(XKeyEvent)*)&e;
6623 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6624 				if (auto ghkp = hash in globalHotkeyList) {
6625 					try {
6626 						ghkp.doHandle();
6627 					} catch (Exception e) {
6628 						import core.stdc.stdio : stderr, fprintf;
6629 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6630 					}
6631 				}
6632 				return 1;
6633 			};
6634 		}
6635 
6636 		private this () {
6637 			auto dpy = XDisplayConnection.get;
6638 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6639 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6640 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6641 		}
6642 
6643 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6644 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6645 		static void register (GlobalHotkey gh) {
6646 			if (gh is null) return;
6647 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6648 
6649 			auto dpy = XDisplayConnection.get;
6650 			immutable keycode = keyEvent2KeyCode(gh.key);
6651 
6652 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6653 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6654 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6655 			XSync(dpy, 0/*False*/);
6656 
6657 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6658 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6659 			ghfailed = false;
6660 			foreach (immutable uint ormask; masklist[]) {
6661 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6662 			}
6663 			XSync(dpy, 0/*False*/);
6664 			XSetErrorHandler(savedErrorHandler);
6665 
6666 			if (ghfailed) {
6667 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6668 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6669 				XSync(dpy, 0/*False*/);
6670 				XSetErrorHandler(savedErrorHandler);
6671 				throw new Exception("cannot register global hotkey");
6672 			}
6673 
6674 			globalHotkeyList[hash] = gh;
6675 		}
6676 
6677 		/// Ditto
6678 		static void register (const(char)[] akey, void delegate () ahandler) {
6679 			register(new GlobalHotkey(akey, ahandler));
6680 		}
6681 
6682 		private static void removeByHash (ulong hash) {
6683 			if (auto ghp = hash in globalHotkeyList) {
6684 				auto dpy = XDisplayConnection.get;
6685 				immutable keycode = keyEvent2KeyCode(ghp.key);
6686 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6687 				XSync(dpy, 0/*False*/);
6688 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6689 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6690 				XSync(dpy, 0/*False*/);
6691 				XSetErrorHandler(savedErrorHandler);
6692 				globalHotkeyList.remove(hash);
6693 			}
6694 		}
6695 
6696 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6697 		/// It is safe to unregister unknown or invalid hotkey.
6698 		static void unregister (GlobalHotkey gh) {
6699 			//TODO: add second AA for faster search? prolly doesn't worth it.
6700 			if (gh is null) return;
6701 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6702 				if (kv.value is gh) {
6703 					removeByHash(kv.key);
6704 					return;
6705 				}
6706 			}
6707 		}
6708 
6709 		/// Ditto.
6710 		static void unregister (const(char)[] key) {
6711 			auto kev = KeyEvent.parse(key);
6712 			immutable keycode = keyEvent2KeyCode(kev);
6713 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6714 		}
6715 	}
6716 }
6717 
6718 version(Windows) {
6719 	/++
6720 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6721 
6722 		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).
6723 	+/
6724 	void sendSyntheticInput(wstring s) {
6725 			INPUT[] inputs;
6726 			inputs.reserve(s.length * 2);
6727 
6728 			foreach(wchar c; s) {
6729 				INPUT input;
6730 				input.type = INPUT_KEYBOARD;
6731 				input.ki.wScan = c;
6732 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6733 				inputs ~= input;
6734 
6735 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6736 				inputs ~= input;
6737 			}
6738 
6739 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6740 				throw new WindowsApiException("SendInput", GetLastError());
6741 			}
6742 
6743 	}
6744 
6745 
6746 	// global hotkey helper function
6747 
6748 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6749 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6750 		__gshared int hotkeyId = 0;
6751 		int id = ++hotkeyId;
6752 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6753 			throw new Exception("RegisterHotKey");
6754 
6755 		__gshared void delegate()[WPARAM][HWND] handlers;
6756 
6757 		handlers[window.impl.hwnd][id] = handler;
6758 
6759 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6760 
6761 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6762 			switch(msg) {
6763 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6764 				case WM_HOTKEY:
6765 					if(auto list = hwnd in handlers) {
6766 						if(auto h = wParam in *list) {
6767 							(*h)();
6768 							return 0;
6769 						}
6770 					}
6771 				goto default;
6772 				default:
6773 			}
6774 			if(oldHandler)
6775 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6776 			return 1; // pass it on
6777 		};
6778 
6779 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6780 			oldHandler = window.handleNativeEvent;
6781 			window.handleNativeEvent = nativeEventHandler;
6782 		}
6783 
6784 		return id;
6785 	}
6786 
6787 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6788 	void unregisterHotKey(SimpleWindow window, int id) {
6789 		if(!UnregisterHotKey(window.impl.hwnd, id))
6790 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6791 	}
6792 }
6793 
6794 version (X11) {
6795 	pragma(lib, "dl");
6796 	import core.sys.posix.dlfcn;
6797 }
6798 
6799 /++
6800 	Allows for sending synthetic input to the X server via the Xtst
6801 	extension or on Windows using SendInput.
6802 
6803 	Please remember user input is meant to be user - don't use this
6804 	if you have some other alternative!
6805 
6806 	History:
6807 		Added May 17, 2020 with the X implementation.
6808 
6809 		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.)
6810 	Bugs:
6811 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6812 +/
6813 struct SyntheticInput {
6814 	@disable this();
6815 
6816 	private int* refcount;
6817 
6818 	version(X11) {
6819 		private void* lib;
6820 
6821 		private extern(C) {
6822 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6823 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6824 		}
6825 	}
6826 
6827 	/// The dummy param must be 0.
6828 	this(int dummy) {
6829 		version(X11) {
6830 			lib = dlopen("libXtst.so", RTLD_NOW);
6831 			if(lib is null)
6832 				throw new Exception("cannot load xtest lib extension");
6833 			scope(failure)
6834 				dlclose(lib);
6835 
6836 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6837 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6838 
6839 			if(XTestFakeKeyEvent is null)
6840 				throw new Exception("No XTestFakeKeyEvent");
6841 			if(XTestFakeButtonEvent is null)
6842 				throw new Exception("No XTestFakeButtonEvent");
6843 		}
6844 
6845 		refcount = new int;
6846 		*refcount = 1;
6847 	}
6848 
6849 	this(this) {
6850 		if(refcount)
6851 			*refcount += 1;
6852 	}
6853 
6854 	~this() {
6855 		if(refcount) {
6856 			*refcount -= 1;
6857 			if(*refcount == 0)
6858 				// I commented this because if I close the lib before
6859 				// XCloseDisplay, it is liable to segfault... so just
6860 				// gonna keep it loaded if it is loaded, no big deal
6861 				// anyway.
6862 				{} // dlclose(lib);
6863 		}
6864 	}
6865 
6866 	/++
6867 		Simulates typing a string into the keyboard.
6868 
6869 		Bugs:
6870 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6871 
6872 			Not implemented except on Windows and X11.
6873 	+/
6874 	void sendSyntheticInput(string s) {
6875 		version(Windows) {
6876 			INPUT[] inputs;
6877 			inputs.reserve(s.length * 2);
6878 
6879 			auto ei = GetMessageExtraInfo();
6880 
6881 			foreach(wchar c; s) {
6882 				INPUT input;
6883 				input.type = INPUT_KEYBOARD;
6884 				input.ki.wScan = c;
6885 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6886 				input.ki.dwExtraInfo = ei;
6887 				inputs ~= input;
6888 
6889 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6890 				inputs ~= input;
6891 			}
6892 
6893 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6894 				throw new WindowsApiException("SendInput", GetLastError());
6895 			}
6896 		} else version(X11) {
6897 			int delay = 0;
6898 			foreach(ch; s) {
6899 				pressKey(cast(Key) ch, true, delay);
6900 				pressKey(cast(Key) ch, false, delay);
6901 				delay += 5;
6902 			}
6903 		} else throw new NotYetImplementedException();
6904 	}
6905 
6906 	/++
6907 		Sends a fake press or release key event.
6908 
6909 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6910 
6911 		Bugs:
6912 			The `delay` parameter is not implemented yet on Windows.
6913 
6914 			Not implemented except on Windows and X11.
6915 	+/
6916 	void pressKey(Key key, bool pressed, int delay = 0) {
6917 		version(Windows) {
6918 			INPUT input;
6919 			input.type = INPUT_KEYBOARD;
6920 			input.ki.wVk = cast(ushort) key;
6921 
6922 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6923 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6924 
6925 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6926 				throw new WindowsApiException("SendInput", GetLastError());
6927 			}
6928 		} else version(X11) {
6929 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6930 		} else throw new NotYetImplementedException();
6931 	}
6932 
6933 	/++
6934 		Sends a fake mouse button press or release event.
6935 
6936 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6937 
6938 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6939 
6940 		Bugs:
6941 			The `delay` parameter is not implemented yet on Windows.
6942 
6943 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6944 
6945 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6946 	+/
6947 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6948 		version(Windows) {
6949 			INPUT input;
6950 			input.type = INPUT_MOUSE;
6951 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6952 
6953 			// input.mi.mouseData for a wheel event
6954 
6955 			switch(button) {
6956 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6957 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6958 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6959 				case MouseButton.wheelUp:
6960 				case MouseButton.wheelDown:
6961 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6962 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6963 				break;
6964 				case MouseButton.backButton: throw new NotYetImplementedException();
6965 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6966 				default:
6967 			}
6968 
6969 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6970 				throw new WindowsApiException("SendInput", GetLastError());
6971 			}
6972 		} else version(X11) {
6973 			int btn;
6974 
6975 			switch(button) {
6976 				case MouseButton.left: btn = 1; break;
6977 				case MouseButton.middle: btn = 2; break;
6978 				case MouseButton.right: btn = 3; break;
6979 				case MouseButton.wheelUp: btn = 4; break;
6980 				case MouseButton.wheelDown: btn = 5; break;
6981 				case MouseButton.backButton: btn = 8; break;
6982 				case MouseButton.forwardButton: btn = 9; break;
6983 				default:
6984 			}
6985 
6986 			assert(btn);
6987 
6988 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6989 		} else throw new NotYetImplementedException();
6990 	}
6991 
6992 	///
6993 	static void moveMouseArrowBy(int dx, int dy) {
6994 		version(Windows) {
6995 			INPUT input;
6996 			input.type = INPUT_MOUSE;
6997 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6998 			input.mi.dx = dx;
6999 			input.mi.dy = dy;
7000 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7001 
7002 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7003 				throw new WindowsApiException("SendInput", GetLastError());
7004 			}
7005 		} else version(X11) {
7006 			auto disp = XDisplayConnection.get();
7007 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7008 			XFlush(disp);
7009 		} else throw new NotYetImplementedException();
7010 	}
7011 
7012 	///
7013 	static void moveMouseArrowTo(int x, int y) {
7014 		version(Windows) {
7015 			INPUT input;
7016 			input.type = INPUT_MOUSE;
7017 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7018 			input.mi.dx = x;
7019 			input.mi.dy = y;
7020 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7021 
7022 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7023 				throw new WindowsApiException("SendInput", GetLastError());
7024 			}
7025 		} else version(X11) {
7026 			auto disp = XDisplayConnection.get();
7027 			auto root = RootWindow(disp, DefaultScreen(disp));
7028 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7029 			XFlush(disp);
7030 		} else throw new NotYetImplementedException();
7031 	}
7032 }
7033 
7034 
7035 
7036 /++
7037 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7038 
7039 	See_Also:
7040 	$(LIST
7041 		*[ScreenPainter]
7042 		*[ScreenPainter.rasterOp]
7043 	)
7044 +/
7045 enum RasterOp {
7046 	normal, /// Replaces the pixel.
7047 	xor, /// Uses bitwise xor to draw.
7048 }
7049 
7050 // being phobos-free keeps the size WAY down
7051 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7052 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7053 package(arsd) const(wchar)* toWStringz(string s) {
7054 	wstring r;
7055 	foreach(dchar c; s)
7056 		r ~= c;
7057 	r ~= '\0';
7058 	return r.ptr;
7059 }
7060 private string[] split(in void[] a, char c) {
7061 		string[] ret;
7062 		size_t previous = 0;
7063 		foreach(i, char ch; cast(ubyte[]) a) {
7064 			if(ch == c) {
7065 				ret ~= cast(string) a[previous .. i];
7066 				previous = i + 1;
7067 			}
7068 		}
7069 		if(previous != a.length)
7070 			ret ~= cast(string) a[previous .. $];
7071 		return ret;
7072 	}
7073 
7074 version(without_opengl) {
7075 	enum OpenGlOptions {
7076 		no,
7077 	}
7078 } else {
7079 	/++
7080 		Determines if you want an OpenGL context created on the new window.
7081 
7082 
7083 		See more: [#topics-3d|in the 3d topic].
7084 
7085 		---
7086 		import arsd.simpledisplay;
7087 		void main() {
7088 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7089 
7090 			// Set up the matrix
7091 			window.setAsCurrentOpenGlContext(); // make this window active
7092 
7093 			// This is called on each frame, we will draw our scene
7094 			window.redrawOpenGlScene = delegate() {
7095 
7096 			};
7097 
7098 			window.eventLoop(0);
7099 		}
7100 		---
7101 	+/
7102 	enum OpenGlOptions {
7103 		no, /// No OpenGL context is created
7104 		yes, /// Yes, create an OpenGL context
7105 	}
7106 
7107 	version(X11) {
7108 		static if (!SdpyIsUsingIVGLBinds) {
7109 
7110 
7111 			struct __GLXFBConfigRec {}
7112 			alias GLXFBConfig = __GLXFBConfigRec*;
7113 
7114 			//pragma(lib, "GL");
7115 			//pragma(lib, "GLU");
7116 			interface GLX {
7117 			extern(C) nothrow @nogc {
7118 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7119 						const int *attrib_list);
7120 
7121 				 void glXCopyContext(Display *dpy, GLXContext src,
7122 						GLXContext dst, arch_ulong mask);
7123 
7124 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7125 						GLXContext share_list, Bool direct);
7126 
7127 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7128 						Pixmap pixmap);
7129 
7130 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7131 
7132 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7133 
7134 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7135 						int attrib, int *value);
7136 
7137 				 GLXContext glXGetCurrentContext();
7138 
7139 				 GLXDrawable glXGetCurrentDrawable();
7140 
7141 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7142 
7143 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7144 						GLXContext ctx);
7145 
7146 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7147 
7148 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7149 
7150 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7151 
7152 				 void glXUseXFont(Font font, int first, int count, int list_base);
7153 
7154 				 void glXWaitGL();
7155 
7156 				 void glXWaitX();
7157 
7158 
7159 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7160 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7161 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7162 
7163 				char* glXQueryExtensionsString (Display*, int);
7164 				void* glXGetProcAddress (const(char)*);
7165 
7166 			}
7167 			}
7168 
7169 			version(OSX)
7170 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7171 			else
7172 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7173 			shared static this() {
7174 				glx.loadDynamicLibrary();
7175 			}
7176 
7177 			alias glbindGetProcAddress = glXGetProcAddress;
7178 		}
7179 	} else version(Windows) {
7180 		/* it is done below by interface GL */
7181 	} else
7182 		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.");
7183 }
7184 
7185 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7186 alias Resizablity = Resizability;
7187 
7188 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7189 enum Resizability {
7190 	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.
7191 	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.
7192 	/++
7193 		$(PITFALL
7194 			Planned for the future but not implemented.
7195 		)
7196 
7197 		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.
7198 
7199 		History:
7200 			Added November 11, 2022, but not yet implemented and may not be for some time.
7201 	+/
7202 	/*@__future*/ allowResizingMaintainingAspectRatio,
7203 	/++
7204 		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.
7205 
7206 		History:
7207 			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.
7208 
7209 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7210 	+/
7211 	automaticallyScaleIfPossible,
7212 }
7213 /// ditto
7214 alias Resizeability = Resizability;
7215 
7216 
7217 /++
7218 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7219 +/
7220 enum TextAlignment : uint {
7221 	Left = 0, ///
7222 	Center = 1, ///
7223 	Right = 2, ///
7224 
7225 	VerticalTop = 0, ///
7226 	VerticalCenter = 4, ///
7227 	VerticalBottom = 8, ///
7228 }
7229 
7230 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7231 alias Rectangle = arsd.color.Rectangle;
7232 
7233 
7234 /++
7235 	Keyboard press and release events.
7236 +/
7237 struct KeyEvent {
7238 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7239 	Key key;
7240 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7241 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7242 
7243 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7244 
7245 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7246 
7247 	SimpleWindow window; /// associated Window
7248 
7249 	/++
7250 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7251 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7252 		to predict if char events are actually coming..
7253 
7254 		Only available on X systems since this information is not given ahead of time elsewhere.
7255 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7256 
7257 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7258 		and potential quirks I'd recommend avoiding it.
7259 
7260 		History:
7261 			Added April 26, 2021 (dub v9.5)
7262 	+/
7263 	version(X11)
7264 		dchar[] charsPossible;
7265 
7266 	// convert key event to simplified string representation a-la emacs
7267 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7268 		uint dpos = 0;
7269 		void put (const(char)[] s...) nothrow @trusted {
7270 			static if (growdest) {
7271 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7272 			} else {
7273 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7274 			}
7275 		}
7276 
7277 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7278 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7279 		}
7280 
7281 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7282 
7283 		// put modifiers
7284 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7285 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7286 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7287 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7288 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7289 
7290 		if (this.key) {
7291 			foreach (string kn; __traits(allMembers, Key)) {
7292 				if (this.key == __traits(getMember, Key, kn)) {
7293 					// HACK!
7294 					static if (kn == "N0") put("0");
7295 					else static if (kn == "N1") put("1");
7296 					else static if (kn == "N2") put("2");
7297 					else static if (kn == "N3") put("3");
7298 					else static if (kn == "N4") put("4");
7299 					else static if (kn == "N5") put("5");
7300 					else static if (kn == "N6") put("6");
7301 					else static if (kn == "N7") put("7");
7302 					else static if (kn == "N8") put("8");
7303 					else static if (kn == "N9") put("9");
7304 					else put(kn);
7305 					return dest[0..dpos];
7306 				}
7307 			}
7308 			put("Unknown");
7309 		} else {
7310 			if (dpos && dest[dpos-1] == '+') --dpos;
7311 		}
7312 		return dest[0..dpos];
7313 	}
7314 
7315 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7316 
7317 	/** Parse string into key name with modifiers. It accepts things like:
7318 	 *
7319 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7320 	 *
7321 	 * Ctrl+Win+1 -- windows style
7322 	 *
7323 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7324 	 *
7325 	 * Ctrl Win 1 -- and space
7326 	 *
7327 	 * and even "Win + 1 + Ctrl".
7328 	 */
7329 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7330 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7331 
7332 		// remove trailing spaces
7333 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7334 
7335 		// tokens delimited by blank, '+', or '-'
7336 		// null on eol
7337 		const(char)[] getToken () nothrow @trusted @nogc {
7338 			// remove leading spaces and delimiters
7339 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7340 			if (name.length == 0) return null; // oops, no more tokens
7341 			// get token
7342 			size_t epos = 0;
7343 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7344 			assert(epos > 0 && epos <= name.length);
7345 			auto res = name[0..epos];
7346 			name = name[epos..$];
7347 			return res;
7348 		}
7349 
7350 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7351 			if (s0.length != s1.length) return false;
7352 			foreach (immutable ci, char c0; s0) {
7353 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7354 				char c1 = s1[ci];
7355 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7356 				if (c0 != c1) return false;
7357 			}
7358 			return true;
7359 		}
7360 
7361 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7362 		if (updown !is null) *updown = -1;
7363 		KeyEvent res;
7364 		res.key = cast(Key)0; // just in case
7365 		const(char)[] tk, tkn; // last token
7366 		bool allowEmascStyle = true;
7367 		bool ignoreModifiers = false;
7368 		tokenloop: for (;;) {
7369 			tk = tkn;
7370 			tkn = getToken();
7371 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7372 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7373 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7374 			if (allowEmascStyle && tkn.length != 0) {
7375 				if (tk.length == 1) {
7376 					char mdc = tk[0];
7377 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7378 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7379 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7380 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7381 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7382 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7383 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7384 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7385 				}
7386 			}
7387 			allowEmascStyle = false;
7388 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7389 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7390 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7391 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7392 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7393 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7394 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7395 			if (tk.length == 0) continue;
7396 			// try key name
7397 			if (res.key == 0) {
7398 				// little hack
7399 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7400 					final switch (tk[0]) {
7401 						case '0': tk = "N0"; break;
7402 						case '1': tk = "N1"; break;
7403 						case '2': tk = "N2"; break;
7404 						case '3': tk = "N3"; break;
7405 						case '4': tk = "N4"; break;
7406 						case '5': tk = "N5"; break;
7407 						case '6': tk = "N6"; break;
7408 						case '7': tk = "N7"; break;
7409 						case '8': tk = "N8"; break;
7410 						case '9': tk = "N9"; break;
7411 					}
7412 				}
7413 				foreach (string kn; __traits(allMembers, Key)) {
7414 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7415 				}
7416 			}
7417 			// unknown or duplicate key name, get out of here
7418 			break;
7419 		}
7420 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7421 		return res; // something
7422 	}
7423 
7424 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7425 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7426 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7427 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7428 		}
7429 		bool ignoreMods;
7430 		int updown;
7431 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7432 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7433 		if (this.key != ke.key) {
7434 			// things like "ctrl+alt" are complicated
7435 			uint tkm = this.modifierState&modmask;
7436 			uint kkm = ke.modifierState&modmask;
7437 			Key tk = this.key;
7438 			// ke
7439 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7440 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7441 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7442 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7443 			// this
7444 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7445 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7446 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7447 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7448 			return (tk == ke.key && tkm == kkm);
7449 		}
7450 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7451 	}
7452 }
7453 
7454 /// Sets the application name.
7455 @property string ApplicationName(string name) {
7456 	return _applicationName = name;
7457 }
7458 
7459 string _applicationName;
7460 
7461 /// ditto
7462 @property string ApplicationName() {
7463 	if(_applicationName is null) {
7464 		import core.runtime;
7465 		return Runtime.args[0];
7466 	}
7467 	return _applicationName;
7468 }
7469 
7470 
7471 /// Type of a [MouseEvent].
7472 enum MouseEventType : int {
7473 	motion = 0, /// The mouse moved inside the window
7474 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7475 	buttonReleased = 2, /// A mouse button was released
7476 }
7477 
7478 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7479 /++
7480 	Listen for this on your event listeners if you are interested in mouse action.
7481 
7482 	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.
7483 
7484 	Examples:
7485 
7486 	This will draw boxes on the window with the mouse as you hold the left button.
7487 	---
7488 	import arsd.simpledisplay;
7489 
7490 	void main() {
7491 		auto window = new SimpleWindow();
7492 
7493 		window.eventLoop(0,
7494 			(MouseEvent ev) {
7495 				if(ev.modifierState & ModifierState.leftButtonDown) {
7496 					auto painter = window.draw();
7497 					painter.fillColor = Color.red;
7498 					painter.outlineColor = Color.black;
7499 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7500 				}
7501 			}
7502 		);
7503 	}
7504 	---
7505 +/
7506 struct MouseEvent {
7507 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7508 
7509 	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.
7510 	int y; /// Current Y position of the cursor when the event fired.
7511 
7512 	int dx; /// Change in X position since last report
7513 	int dy; /// Change in Y position since last report
7514 
7515 	MouseButton button; /// See [MouseButton]
7516 	int modifierState; /// See [ModifierState]
7517 
7518 	version(X11)
7519 		private Time timestamp;
7520 
7521 	/// Returns a linear representation of mouse button,
7522 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7523 	///
7524 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7525 	@property ubyte buttonLinear() const {
7526 		import core.bitop;
7527 		if(button == 0)
7528 			return 0;
7529 		return (bsf(button) + 1) & 0b1111;
7530 	}
7531 
7532 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7533 
7534 	SimpleWindow window; /// The window in which the event happened.
7535 
7536 	Point globalCoordinates() {
7537 		Point p;
7538 		if(window is null)
7539 			throw new Exception("wtf");
7540 		static if(UsingSimpledisplayX11) {
7541 			Window child;
7542 			XTranslateCoordinates(
7543 				XDisplayConnection.get,
7544 				window.impl.window,
7545 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7546 				x, y, &p.x, &p.y, &child);
7547 			return p;
7548 		} else version(Windows) {
7549 			POINT[1] points;
7550 			points[0].x = x;
7551 			points[0].y = y;
7552 			MapWindowPoints(
7553 				window.impl.hwnd,
7554 				null,
7555 				points.ptr,
7556 				points.length
7557 			);
7558 			p.x = points[0].x;
7559 			p.y = points[0].y;
7560 
7561 			return p;
7562 		} else version(OSXCocoa) {
7563 			throw new NotYetImplementedException();
7564 		} else static assert(0);
7565 	}
7566 
7567 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7568 
7569 	/**
7570 	can contain emacs-like modifier prefix
7571 	case-insensitive names:
7572 		lmbX/leftX
7573 		rmbX/rightX
7574 		mmbX/middleX
7575 		wheelX
7576 		motion (no prefix allowed)
7577 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7578 	*/
7579 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7580 		if (str.length == 0) return false; // just in case
7581 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7582 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7583 		auto anchor = str;
7584 		uint mods = 0; // uint.max == any
7585 		// interesting bits in kmod
7586 		uint kmodmask =
7587 			ModifierState.shift|
7588 			ModifierState.ctrl|
7589 			ModifierState.alt|
7590 			ModifierState.windows|
7591 			ModifierState.leftButtonDown|
7592 			ModifierState.middleButtonDown|
7593 			ModifierState.rightButtonDown|
7594 			0;
7595 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7596 		bool wasButtons = false;
7597 		while (str.length) {
7598 			if (str.ptr[0] <= ' ') {
7599 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7600 				continue;
7601 			}
7602 			// one-letter modifier?
7603 			if (str.length >= 2 && str.ptr[1] == '-') {
7604 				switch (str.ptr[0]) {
7605 					case '*': // "any" modifier (cannot be undone)
7606 						mods = mods.max;
7607 						break;
7608 					case 'C': case 'c': // emacs "ctrl"
7609 						if (mods != mods.max) mods |= ModifierState.ctrl;
7610 						break;
7611 					case 'M': case 'm': // emacs "meta"
7612 						if (mods != mods.max) mods |= ModifierState.alt;
7613 						break;
7614 					case 'S': case 's': // emacs "shift"
7615 						if (mods != mods.max) mods |= ModifierState.shift;
7616 						break;
7617 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7618 						if (mods != mods.max) mods |= ModifierState.windows;
7619 						break;
7620 					default:
7621 						return false; // unknown modifier
7622 				}
7623 				str = str[2..$];
7624 				continue;
7625 			}
7626 			// word
7627 			char[16] buf = void; // locased
7628 			auto wep = 0;
7629 			while (str.length) {
7630 				immutable char ch = str.ptr[0];
7631 				if (ch <= ' ' || ch == '-') break;
7632 				str = str[1..$];
7633 				if (wep > buf.length) return false; // too long
7634 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7635 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7636 				else return false; // invalid char
7637 			}
7638 			if (wep == 0) return false; // just in case
7639 			uint bnum;
7640 			enum UpDown { None = -1, Up, Down, Any }
7641 			auto updown = UpDown.None; // 0: up; 1: down
7642 			switch (buf[0..wep]) {
7643 				// left button
7644 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7645 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7646 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7647 				case "lmb": case "left": bnum = 0; break;
7648 				// middle button
7649 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7650 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7651 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7652 				case "mmb": case "middle": bnum = 1; break;
7653 				// right button
7654 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7655 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7656 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7657 				case "rmb": case "right": bnum = 2; break;
7658 				// wheel
7659 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7660 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7661 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7662 				case "wheel": bnum = 3; break;
7663 				// motion
7664 				case "motion": bnum = 7; break;
7665 				// unknown
7666 				default: return false;
7667 			}
7668 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7669 			// parse possible "-up" or "-down"
7670 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7671 				wep = 0;
7672 				foreach (immutable idx, immutable char ch; str[1..$]) {
7673 					if (ch <= ' ' || ch == '-') break;
7674 					assert(idx == wep); // for now; trick
7675 					if (wep > buf.length) { wep = 0; break; } // too long
7676 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7677 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7678 					else { wep = 0; break; } // invalid char
7679 				}
7680 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7681 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7682 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7683 				// remove parsed part
7684 				if (updown != UpDown.None) str = str[wep+1..$];
7685 			}
7686 			if (updown == UpDown.None) {
7687 				updown = UpDown.Down;
7688 			}
7689 			wasButtons = wasButtons || (bnum <= 2);
7690 			//assert(updown != UpDown.None);
7691 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7692 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7693 			if (lastButt != lastButt.max) {
7694 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7695 				if (mods != mods.max) {
7696 					uint butbit = 0;
7697 					final switch (lastButt&0x03) {
7698 						case 0: butbit = ModifierState.leftButtonDown; break;
7699 						case 1: butbit = ModifierState.middleButtonDown; break;
7700 						case 2: butbit = ModifierState.rightButtonDown; break;
7701 					}
7702 					     if (lastButt&Flag.Down) mods |= butbit;
7703 					else if (lastButt&Flag.Up) mods &= ~butbit;
7704 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7705 				}
7706 			}
7707 			// remember last button
7708 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7709 		}
7710 		// no button -- nothing to do
7711 		if (lastButt == lastButt.max) return false;
7712 		// done parsing, check if something's left
7713 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7714 		// remove action button from mask
7715 		if ((lastButt&0xff) < 3) {
7716 			final switch (lastButt&0x03) {
7717 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7718 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7719 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7720 			}
7721 		}
7722 		// special case: "Motion" means "ignore buttons"
7723 		if ((lastButt&0xff) == 7 && !wasButtons) {
7724 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7725 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7726 		}
7727 		uint kmod = event.modifierState&kmodmask;
7728 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7729 		// check modifier state
7730 		if (mods != mods.max) {
7731 			if (kmod != mods) return false;
7732 		}
7733 		// now check type
7734 		if ((lastButt&0xff) == 7) {
7735 			// motion
7736 			if (event.type != MouseEventType.motion) return false;
7737 		} else if ((lastButt&0xff) == 3) {
7738 			// wheel
7739 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7740 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7741 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7742 			return false;
7743 		} else {
7744 			// buttons
7745 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7746 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7747 			{
7748 				return false;
7749 			}
7750 			// button number
7751 			switch (lastButt&0x03) {
7752 				case 0: if (event.button != MouseButton.left) return false; break;
7753 				case 1: if (event.button != MouseButton.middle) return false; break;
7754 				case 2: if (event.button != MouseButton.right) return false; break;
7755 				default: return false;
7756 			}
7757 		}
7758 		return true;
7759 	}
7760 }
7761 
7762 version(arsd_mevent_strcmp_test) unittest {
7763 	MouseEvent event;
7764 	event.type = MouseEventType.buttonPressed;
7765 	event.button = MouseButton.left;
7766 	event.modifierState = ModifierState.ctrl;
7767 	assert(event == "C-LMB");
7768 	assert(event != "C-LMBUP");
7769 	assert(event != "C-LMB-UP");
7770 	assert(event != "C-S-LMB");
7771 	assert(event == "*-LMB");
7772 	assert(event != "*-LMB-UP");
7773 
7774 	event.type = MouseEventType.buttonReleased;
7775 	assert(event != "C-LMB");
7776 	assert(event == "C-LMBUP");
7777 	assert(event == "C-LMB-UP");
7778 	assert(event != "C-S-LMB");
7779 	assert(event != "*-LMB");
7780 	assert(event == "*-LMB-UP");
7781 
7782 	event.button = MouseButton.right;
7783 	event.modifierState |= ModifierState.shift;
7784 	event.type = MouseEventType.buttonPressed;
7785 	assert(event != "C-LMB");
7786 	assert(event != "C-LMBUP");
7787 	assert(event != "C-LMB-UP");
7788 	assert(event != "C-S-LMB");
7789 	assert(event != "*-LMB");
7790 	assert(event != "*-LMB-UP");
7791 
7792 	assert(event != "C-RMB");
7793 	assert(event != "C-RMBUP");
7794 	assert(event != "C-RMB-UP");
7795 	assert(event == "C-S-RMB");
7796 	assert(event == "*-RMB");
7797 	assert(event != "*-RMB-UP");
7798 }
7799 
7800 /// This gives a few more options to drawing lines and such
7801 struct Pen {
7802 	Color color; /// the foreground color
7803 	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.
7804 	Style style; /// See [Style]
7805 /+
7806 // From X.h
7807 
7808 #define LineSolid		0
7809 #define LineOnOffDash		1
7810 #define LineDoubleDash		2
7811        LineDou-        The full path of the line is drawn, but the
7812        bleDash         even dashes are filled differently from the
7813                        odd dashes (see fill-style) with CapButt
7814                        style used where even and odd dashes meet.
7815 
7816 
7817 
7818 /* capStyle */
7819 
7820 #define CapNotLast		0
7821 #define CapButt			1
7822 #define CapRound		2
7823 #define CapProjecting		3
7824 
7825 /* joinStyle */
7826 
7827 #define JoinMiter		0
7828 #define JoinRound		1
7829 #define JoinBevel		2
7830 
7831 /* fillStyle */
7832 
7833 #define FillSolid		0
7834 #define FillTiled		1
7835 #define FillStippled		2
7836 #define FillOpaqueStippled	3
7837 
7838 
7839 +/
7840 	/// Style of lines drawn
7841 	enum Style {
7842 		Solid, /// a solid line
7843 		Dashed, /// a dashed line
7844 		Dotted, /// a dotted line
7845 	}
7846 }
7847 
7848 
7849 /++
7850 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7851 
7852 
7853 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7854 
7855 	$(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.)
7856 
7857 	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.
7858 
7859 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7860 
7861 	$(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.
7862 
7863 	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!
7864 
7865 	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!)
7866 
7867 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7868 
7869 	---
7870 		auto image = new Image(256, 256);
7871 		scope(exit) destroy(image);
7872 	---
7873 
7874 	As long as you don't hold on to it outside the scope.
7875 
7876 	I might change it to be an owned pointer at some point in the future.
7877 
7878 	)
7879 
7880 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7881 	you can also often get a fair amount of speedup by getting the raw data format and
7882 	writing some custom code.
7883 
7884 	FIXME INSERT EXAMPLES HERE
7885 
7886 
7887 +/
7888 final class Image {
7889 	///
7890 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7891 		this.width = width;
7892 		this.height = height;
7893 		this.enableAlpha = enableAlpha;
7894 
7895 		impl.createImage(width, height, forcexshm, enableAlpha);
7896 	}
7897 
7898 	///
7899 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7900 		this(size.width, size.height, forcexshm, enableAlpha);
7901 	}
7902 
7903 	private bool suppressDestruction;
7904 
7905 	version(X11)
7906 	this(XImage* handle) {
7907 		this.handle = handle;
7908 		this.rawData = cast(ubyte*) handle.data;
7909 		this.width = handle.width;
7910 		this.height = handle.height;
7911 		this.enableAlpha = handle.depth == 32;
7912 		suppressDestruction = true;
7913 	}
7914 
7915 	~this() {
7916 		if(suppressDestruction) return;
7917 		impl.dispose();
7918 	}
7919 
7920 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7921 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7922 	pure const @system nothrow {
7923 		/*
7924 			To use these to draw a blue rectangle with size WxH at position X,Y...
7925 
7926 			// make certain that it will fit before we proceed
7927 			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!
7928 
7929 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7930 			// (though calculating them isn't really that expensive).
7931 			auto nextLineAdjustment = img.adjustmentForNextLine();
7932 			auto offR = img.redByteOffset();
7933 			auto offB = img.blueByteOffset();
7934 			auto offG = img.greenByteOffset();
7935 			auto bpp = img.bytesPerPixel();
7936 
7937 			auto data = img.getDataPointer();
7938 
7939 			// figure out the starting byte offset
7940 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7941 
7942 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7943 
7944 			// and now our drawing loop for the rectangle
7945 			foreach(y; 0 .. H) {
7946 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7947 				foreach(x; 0 .. W) {
7948 					// write our color
7949 					data[offR] = 0;
7950 					data[offG] = 0;
7951 					data[offB] = 255;
7952 
7953 					data += bpp; // moving to the next pixel is just an addition...
7954 				}
7955 				startOfLine += nextLineAdjustment;
7956 			}
7957 
7958 
7959 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7960 
7961 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7962 			can be made into a bitmask or something so we can write them as *uint...
7963 		*/
7964 
7965 		///
7966 		int offsetForTopLeftPixel() {
7967 			version(X11) {
7968 				return 0;
7969 			} else version(Windows) {
7970 				if(enableAlpha) {
7971 					return (width * 4) * (height - 1);
7972 				} else {
7973 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7974 				}
7975 			} else version(OSXCocoa) {
7976 				return 0 ; //throw new NotYetImplementedException();
7977 			} else static assert(0, "fill in this info for other OSes");
7978 		}
7979 
7980 		///
7981 		int offsetForPixel(int x, int y) {
7982 			version(X11) {
7983 				auto offset = (y * width + x) * 4;
7984 				return offset;
7985 			} else version(Windows) {
7986 				if(enableAlpha) {
7987 					auto itemsPerLine = width * 4;
7988 					// remember, bmps are upside down
7989 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7990 					return offset;
7991 				} else {
7992 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7993 					// remember, bmps are upside down
7994 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7995 					return offset;
7996 				}
7997 			} else version(OSXCocoa) {
7998 				return 0 ; //throw new NotYetImplementedException();
7999 			} else static assert(0, "fill in this info for other OSes");
8000 		}
8001 
8002 		///
8003 		int adjustmentForNextLine() {
8004 			version(X11) {
8005 				return width * 4;
8006 			} else version(Windows) {
8007 				// windows bmps are upside down, so the adjustment is actually negative
8008 				if(enableAlpha)
8009 					return - (cast(int) width * 4);
8010 				else
8011 					return -((cast(int) width * 3 + 3) / 4) * 4;
8012 			} else version(OSXCocoa) {
8013 				return 0 ; //throw new NotYetImplementedException();
8014 			} else static assert(0, "fill in this info for other OSes");
8015 		}
8016 
8017 		/// once you have the position of a pixel, use these to get to the proper color
8018 		int redByteOffset() {
8019 			version(X11) {
8020 				return 2;
8021 			} else version(Windows) {
8022 				return 2;
8023 			} else version(OSXCocoa) {
8024 				return 0 ; //throw new NotYetImplementedException();
8025 			} else static assert(0, "fill in this info for other OSes");
8026 		}
8027 
8028 		///
8029 		int greenByteOffset() {
8030 			version(X11) {
8031 				return 1;
8032 			} else version(Windows) {
8033 				return 1;
8034 			} else version(OSXCocoa) {
8035 				return 0 ; //throw new NotYetImplementedException();
8036 			} else static assert(0, "fill in this info for other OSes");
8037 		}
8038 
8039 		///
8040 		int blueByteOffset() {
8041 			version(X11) {
8042 				return 0;
8043 			} else version(Windows) {
8044 				return 0;
8045 			} else version(OSXCocoa) {
8046 				return 0 ; //throw new NotYetImplementedException();
8047 			} else static assert(0, "fill in this info for other OSes");
8048 		}
8049 
8050 		/// Only valid if [enableAlpha] is true
8051 		int alphaByteOffset() {
8052 			version(X11) {
8053 				return 3;
8054 			} else version(Windows) {
8055 				return 3;
8056 			} else version(OSXCocoa) {
8057 				return 3; //throw new NotYetImplementedException();
8058 			} else static assert(0, "fill in this info for other OSes");
8059 		}
8060 	}
8061 
8062 	///
8063 	final void putPixel(int x, int y, Color c) {
8064 		if(x < 0 || x >= width)
8065 			return;
8066 		if(y < 0 || y >= height)
8067 			return;
8068 
8069 		impl.setPixel(x, y, c);
8070 	}
8071 
8072 	///
8073 	final Color getPixel(int x, int y) {
8074 		if(x < 0 || x >= width)
8075 			return Color.transparent;
8076 		if(y < 0 || y >= height)
8077 			return Color.transparent;
8078 
8079 		version(OSXCocoa) throw new NotYetImplementedException(); else
8080 		return impl.getPixel(x, y);
8081 	}
8082 
8083 	///
8084 	final void opIndexAssign(Color c, int x, int y) {
8085 		putPixel(x, y, c);
8086 	}
8087 
8088 	///
8089 	TrueColorImage toTrueColorImage() {
8090 		auto tci = new TrueColorImage(width, height);
8091 		convertToRgbaBytes(tci.imageData.bytes);
8092 		return tci;
8093 	}
8094 
8095 	///
8096 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8097 		auto tci = i.getAsTrueColorImage();
8098 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8099 		static if(UsingSimpledisplayX11)
8100 			img.premultiply = premultiply;
8101 		img.setRgbaBytes(tci.imageData.bytes);
8102 		return img;
8103 	}
8104 
8105 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8106 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8107 	/// if you pass null, it will allocate a new one.
8108 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8109 		if(where is null)
8110 			where = new ubyte[this.width*this.height*4];
8111 		convertToRgbaBytes(where);
8112 		return where;
8113 	}
8114 
8115 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8116 	void setRgbaBytes(in ubyte[] from ) {
8117 		assert(from.length == this.width * this.height * 4);
8118 		setFromRgbaBytes(from);
8119 	}
8120 
8121 	// FIXME: make properly cross platform by getting rgba right
8122 
8123 	/// warning: this is not portable across platforms because the data format can change
8124 	ubyte* getDataPointer() {
8125 		return impl.rawData;
8126 	}
8127 
8128 	/// for use with getDataPointer
8129 	final int bytesPerLine() const pure @safe nothrow {
8130 		version(Windows)
8131 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8132 		else version(X11)
8133 			return 4 * width;
8134 		else version(OSXCocoa)
8135 			return 4 * width;
8136 		else static assert(0);
8137 	}
8138 
8139 	/// for use with getDataPointer
8140 	final int bytesPerPixel() const pure @safe nothrow {
8141 		version(Windows)
8142 			return enableAlpha ? 4 : 3;
8143 		else version(X11)
8144 			return 4;
8145 		else version(OSXCocoa)
8146 			return 4;
8147 		else static assert(0);
8148 	}
8149 
8150 	///
8151 	immutable int width;
8152 
8153 	///
8154 	immutable int height;
8155 
8156 	///
8157 	immutable bool enableAlpha;
8158     //private:
8159 	mixin NativeImageImplementation!() impl;
8160 }
8161 
8162 /++
8163 	A convenience function to pop up a window displaying the image.
8164 	If you pass a win, it will draw the image in it. Otherwise, it will
8165 	create a window with the size of the image and run its event loop, closing
8166 	when a key is pressed.
8167 
8168 	History:
8169 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8170 		always block until the application quit which could cause bizarre behavior
8171 		inside a more complex application. Now, the default is to block until
8172 		this window closes if it is the only event loop running, and otherwise,
8173 		not to block at all and just pop up the display window asynchronously.
8174 +/
8175 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8176 	if(win is null) {
8177 		win = new SimpleWindow(image);
8178 		{
8179 			auto p = win.draw;
8180 			p.drawImage(Point(0, 0), image);
8181 		}
8182 		win.eventLoopWithBlockingMode(
8183 			bm, 0,
8184 			(KeyEvent ev) {
8185 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8186 			} );
8187 	} else {
8188 		win.image = image;
8189 	}
8190 }
8191 
8192 enum FontWeight : int {
8193 	dontcare = 0,
8194 	thin = 100,
8195 	extralight = 200,
8196 	light = 300,
8197 	regular = 400,
8198 	medium = 500,
8199 	semibold = 600,
8200 	bold = 700,
8201 	extrabold = 800,
8202 	heavy = 900
8203 }
8204 
8205 /++
8206 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8207 
8208 	History:
8209 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8210 +/
8211 interface MeasurableFont {
8212 	/++
8213 		Returns true if it is a monospace font, meaning each of the
8214 		glyphs (at least the ascii characters) have matching width
8215 		and no kerning, so you can determine the display width of some
8216 		strings by simply multiplying the string width by [averageWidth].
8217 
8218 		(Please note that multiply doesn't $(I actually) work in general,
8219 		consider characters like tab and newline, but it does sometimes.)
8220 	+/
8221 	bool isMonospace();
8222 
8223 	/++
8224 		The average width of glyphs in the font, traditionally equal to the
8225 		width of the lowercase x. Can be used to estimate bounding boxes,
8226 		especially if the font [isMonospace].
8227 
8228 		Given in pixels.
8229 	+/
8230 	int averageWidth();
8231 	/++
8232 		The height of the bounding box of a line.
8233 	+/
8234 	int height();
8235 	/++
8236 		The maximum ascent of a glyph above the baseline.
8237 
8238 		Given in pixels.
8239 	+/
8240 	int ascent();
8241 	/++
8242 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8243 
8244 		Given in pixels.
8245 	+/
8246 	int descent();
8247 	/++
8248 		The display width of the given string, and if you provide a window, it will use it to
8249 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8250 
8251 		Given in pixels.
8252 	+/
8253 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8254 
8255 }
8256 
8257 // FIXME: i need a font cache and it needs to handle disconnects.
8258 
8259 /++
8260 	Represents a font loaded off the operating system or the X server.
8261 
8262 
8263 	While the api here is unified cross platform, the fonts are not necessarily
8264 	available, even across machines of the same platform, so be sure to always check
8265 	for null (using [isNull]) and have a fallback plan.
8266 
8267 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8268 
8269 	Worst case, a null font will automatically fall back to the default font loaded
8270 	for your system.
8271 +/
8272 class OperatingSystemFont : MeasurableFont {
8273 	// FIXME: when the X Connection is lost, these need to be invalidated!
8274 	// that means I need to store the original stuff again to reconstruct it too.
8275 
8276 	version(X11) {
8277 		XFontStruct* font;
8278 		XFontSet fontset;
8279 
8280 		version(with_xft) {
8281 			XftFont* xftFont;
8282 			bool isXft;
8283 		}
8284 	} else version(Windows) {
8285 		HFONT font;
8286 		int width_;
8287 		int height_;
8288 	} else version(OSXCocoa) {
8289 		NSFont font;
8290 	} else static assert(0);
8291 
8292 	/++
8293 		Constructs the class and immediately calls [load].
8294 	+/
8295 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8296 		load(name, size, weight, italic);
8297 	}
8298 
8299 	/++
8300 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8301 
8302 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8303 
8304 		History:
8305 			Added January 24, 2021.
8306 	+/
8307 	this() {
8308 		// this space intentionally left blank
8309 	}
8310 
8311 	/++
8312 		Constructs a copy of the given font object.
8313 
8314 		History:
8315 			Added January 7, 2023.
8316 	+/
8317 	this(OperatingSystemFont font) {
8318 		if(font is null || font.loadedInfo is LoadedInfo.init)
8319 			loadDefault();
8320 		else
8321 			load(font.loadedInfo.tupleof);
8322 	}
8323 
8324 	/++
8325 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8326 
8327 		History:
8328 			Added November 13, 2020.
8329 	+/
8330 	version(with_xft)
8331 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8332 		unload();
8333 
8334 		if(!XftLibrary.attempted) {
8335 			XftLibrary.loadDynamicLibrary();
8336 		}
8337 
8338 		if(!XftLibrary.loadSuccessful)
8339 			return false;
8340 
8341 		auto display = XDisplayConnection.get;
8342 
8343 		char[256] nameBuffer = void;
8344 		int nbp = 0;
8345 
8346 		void add(in char[] a) {
8347 			nameBuffer[nbp .. nbp + a.length] = a[];
8348 			nbp += a.length;
8349 		}
8350 		add(name);
8351 
8352 		if(size) {
8353 			add(":size=");
8354 			add(toInternal!string(size));
8355 		}
8356 		if(weight != FontWeight.dontcare) {
8357 			add(":weight=");
8358 			add(weightToString(weight));
8359 		}
8360 		if(italic)
8361 			add(":slant=100");
8362 
8363 		nameBuffer[nbp] = 0;
8364 
8365 		this.xftFont = XftFontOpenName(
8366 			display,
8367 			DefaultScreen(display),
8368 			nameBuffer.ptr
8369 		);
8370 
8371 		this.isXft = true;
8372 
8373 		if(xftFont !is null) {
8374 			isMonospace_ = stringWidth("x") == stringWidth("M");
8375 			ascent_ = xftFont.ascent;
8376 			descent_ = xftFont.descent;
8377 		}
8378 
8379 		return !isNull();
8380 	}
8381 
8382 	/++
8383 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8384 
8385 
8386 		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.
8387 
8388 		If `pattern` is null, it returns all available font families.
8389 
8390 		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.
8391 
8392 		The format of the pattern is platform-specific.
8393 
8394 		History:
8395 			Added May 1, 2021 (dub v9.5)
8396 	+/
8397 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8398 		version(Windows) {
8399 			auto hdc = GetDC(null);
8400 			scope(exit) ReleaseDC(null, hdc);
8401 			LOGFONT logfont;
8402 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8403 				auto localHandler = *(cast(typeof(handler)*) p);
8404 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8405 			}
8406 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8407 		} else version(X11) {
8408 			//import core.stdc.stdio;
8409 			bool done = false;
8410 			version(with_xft) {
8411 				if(!XftLibrary.attempted) {
8412 					XftLibrary.loadDynamicLibrary();
8413 				}
8414 
8415 				if(!XftLibrary.loadSuccessful)
8416 					goto skipXft;
8417 
8418 				if(!FontConfigLibrary.attempted)
8419 					FontConfigLibrary.loadDynamicLibrary();
8420 				if(!FontConfigLibrary.loadSuccessful)
8421 					goto skipXft;
8422 
8423 				{
8424 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8425 					if(got is null)
8426 						goto skipXft;
8427 					scope(exit) FcFontSetDestroy(got);
8428 
8429 					auto fontPatterns = got.fonts[0 .. got.nfont];
8430 					foreach(candidate; fontPatterns) {
8431 						char* where, whereStyle;
8432 
8433 						char* pmg = FcNameUnparse(candidate);
8434 
8435 						//FcPatternGetString(candidate, "family", 0, &where);
8436 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8437 						//if(where && whereStyle) {
8438 						if(pmg) {
8439 							if(!handler(pmg.sliceCString))
8440 								return;
8441 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8442 						}
8443 					}
8444 				}
8445 			}
8446 
8447 			skipXft:
8448 
8449 			if(pattern is null)
8450 				pattern = "*";
8451 
8452 			int count;
8453 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8454 			scope(exit) XFreeFontNames(coreFontsRaw);
8455 
8456 			auto coreFonts = coreFontsRaw[0 .. count];
8457 
8458 			foreach(font; coreFonts) {
8459 				char[128] tmp;
8460 				tmp[0 ..5] = "core:";
8461 				auto cf = font.sliceCString;
8462 				if(5 + cf.length > tmp.length)
8463 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8464 				tmp[5 .. 5 + cf.length] = cf;
8465 				if(!handler(tmp[0 .. 5 + cf.length]))
8466 					return;
8467 			}
8468 		}
8469 	}
8470 
8471 	/++
8472 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8473 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8474 
8475 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8476 		underlying system doesn't support returning the raw bytes.
8477 
8478 		History:
8479 			Added September 10, 2021 (dub v10.3)
8480 	+/
8481 	ubyte[] getTtfBytes() {
8482 		if(isNull)
8483 			return null;
8484 
8485 		version(Windows) {
8486 			auto dc = GetDC(null);
8487 			auto orig = SelectObject(dc, font);
8488 
8489 			scope(exit) {
8490 				SelectObject(dc, orig);
8491 				ReleaseDC(null, dc);
8492 			}
8493 
8494 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8495 			if(res == GDI_ERROR)
8496 				return null;
8497 
8498 			ubyte[] buffer = new ubyte[](res);
8499 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8500 			if(res == GDI_ERROR)
8501 				return null; // wtf really tbh
8502 
8503 			return buffer;
8504 		} else version(with_xft) {
8505 			if(isXft && xftFont) {
8506 				if(!FontConfigLibrary.attempted)
8507 					FontConfigLibrary.loadDynamicLibrary();
8508 				if(!FontConfigLibrary.loadSuccessful)
8509 					return null;
8510 
8511 				char* file;
8512 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8513 					if (file !is null && file[0]) {
8514 						import core.stdc.stdio;
8515 						auto fp = fopen(file, "rb");
8516 						if(fp is null)
8517 							return null;
8518 						scope(exit)
8519 							fclose(fp);
8520 						fseek(fp, 0, SEEK_END);
8521 						ubyte[] buffer = new ubyte[](ftell(fp));
8522 						fseek(fp, 0, SEEK_SET);
8523 
8524 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8525 						if(got != buffer.length)
8526 							return null;
8527 
8528 						return buffer;
8529 					}
8530 				}
8531 			}
8532 			return null;
8533 		} else throw new NotYetImplementedException();
8534 	}
8535 
8536 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8537 
8538 	private string weightToString(FontWeight weight) {
8539 		with(FontWeight)
8540 		final switch(weight) {
8541 			case dontcare: return "*";
8542 			case thin: return "extralight";
8543 			case extralight: return "extralight";
8544 			case light: return "light";
8545 			case regular: return "regular";
8546 			case medium: return "medium";
8547 			case semibold: return "demibold";
8548 			case bold: return "bold";
8549 			case extrabold: return "demibold";
8550 			case heavy: return "black";
8551 		}
8552 	}
8553 
8554 	/++
8555 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8556 
8557 		History:
8558 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8559 	+/
8560 	version(X11)
8561 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8562 		unload();
8563 
8564 		string xfontstr;
8565 
8566 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8567 			// this is kinda a disgusting hack but if the user sends an exact
8568 			// string I'd like to honor it...
8569 			xfontstr = name;
8570 		} else {
8571 			string weightstr = weightToString(weight);
8572 			string sizestr;
8573 			if(size == 0)
8574 				sizestr = "*";
8575 			else
8576 				sizestr = toInternal!string(size);
8577 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8578 		}
8579 
8580 		// writeln(xfontstr);
8581 
8582 		auto display = XDisplayConnection.get;
8583 
8584 		font = XLoadQueryFont(display, xfontstr.ptr);
8585 		if(font is null)
8586 			return false;
8587 
8588 		char** lol;
8589 		int lol2;
8590 		char* lol3;
8591 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8592 
8593 		prepareFontInfo();
8594 
8595 		return !isNull();
8596 	}
8597 
8598 	version(X11)
8599 	private void prepareFontInfo() {
8600 		if(font !is null) {
8601 			isMonospace_ = stringWidth("l") == stringWidth("M");
8602 			ascent_ = font.max_bounds.ascent;
8603 			descent_ = font.max_bounds.descent;
8604 		}
8605 	}
8606 
8607 	version(OSXCocoa)
8608 	private void prepareFontInfo() {
8609 		if(font !is null) {
8610 			isMonospace_ = font.isFixedPitch;
8611 			ascent_ = cast(int) font.ascender;
8612 			descent_ = cast(int) - font.descender;
8613 		}
8614 	}
8615 
8616 
8617 	/++
8618 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8619 
8620 		History:
8621 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8622 	+/
8623 	version(Windows)
8624 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8625 		unload();
8626 
8627 		WCharzBuffer buffer = WCharzBuffer(name);
8628 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8629 
8630 		prepareFontInfo(hdc);
8631 
8632 		return !isNull();
8633 	}
8634 
8635 	version(Windows)
8636 	void prepareFontInfo(HDC hdc = null) {
8637 		if(font is null)
8638 			return;
8639 
8640 		TEXTMETRIC tm;
8641 		auto dc = hdc ? hdc : GetDC(null);
8642 		auto orig = SelectObject(dc, font);
8643 		GetTextMetrics(dc, &tm);
8644 		SelectObject(dc, orig);
8645 		if(hdc is null)
8646 			ReleaseDC(null, dc);
8647 
8648 		width_ = tm.tmAveCharWidth;
8649 		height_ = tm.tmHeight;
8650 		ascent_ = tm.tmAscent;
8651 		descent_ = tm.tmDescent;
8652 		// 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.
8653 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8654 	}
8655 
8656 
8657 	/++
8658 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8659 
8660 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8661 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8662 
8663 		On Windows, it forwards directly to [loadWin32].
8664 
8665 		Params:
8666 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8667 			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.
8668 			weight = approximate boldness, results may vary.
8669 			italic = try to get a slanted version of the given font.
8670 
8671 		History:
8672 			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.
8673 	+/
8674 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8675 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8676 		version(X11) {
8677 			version(with_xft) {
8678 				if(name.length > 5 && name[0 .. 5] == "core:") {
8679 					goto core;
8680 				}
8681 
8682 				if(loadXft(name, size, weight, italic))
8683 					return true;
8684 				// if xft fails, fallback to core to avoid breaking
8685 				// code that already depended on this.
8686 			}
8687 
8688 			core:
8689 
8690 			if(name.length > 5 && name[0 .. 5] == "core:") {
8691 				name = name[5 .. $];
8692 			}
8693 
8694 			return loadCoreX(name, size, weight, italic);
8695 		} else version(Windows) {
8696 			return loadWin32(name, size, weight, italic);
8697 		} else version(OSXCocoa) {
8698 			return loadCocoa(name, size, weight, italic);
8699 		} else static assert(0);
8700 	}
8701 
8702 	version(OSXCocoa)
8703 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
8704 		unload();
8705 
8706 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
8707 		prepareFontInfo();
8708 
8709 		return !isNull();
8710 	}
8711 
8712 	private struct LoadedInfo {
8713 		string name;
8714 		int size;
8715 		FontWeight weight;
8716 		bool italic;
8717 	}
8718 	private LoadedInfo loadedInfo;
8719 
8720 	///
8721 	void unload() {
8722 		if(isNull())
8723 			return;
8724 
8725 		version(X11) {
8726 			auto display = XDisplayConnection.display;
8727 
8728 			if(display is null)
8729 				return;
8730 
8731 			version(with_xft) {
8732 				if(isXft) {
8733 					if(xftFont)
8734 						XftFontClose(display, xftFont);
8735 					isXft = false;
8736 					xftFont = null;
8737 					return;
8738 				}
8739 			}
8740 
8741 			if(font && font !is ScreenPainterImplementation.defaultfont)
8742 				XFreeFont(display, font);
8743 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8744 				XFreeFontSet(display, fontset);
8745 
8746 			font = null;
8747 			fontset = null;
8748 		} else version(Windows) {
8749 			DeleteObject(font);
8750 			font = null;
8751 		} else version(OSXCocoa) {
8752 			font.release();
8753 			font = null;
8754 		} else static assert(0);
8755 	}
8756 
8757 	private bool isMonospace_;
8758 
8759 	/++
8760 		History:
8761 			Added January 16, 2021
8762 	+/
8763 	bool isMonospace() {
8764 		return isMonospace_;
8765 	}
8766 
8767 	/++
8768 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8769 
8770 		History:
8771 			Added March 26, 2020
8772 			Documented January 16, 2021
8773 	+/
8774 	int averageWidth() {
8775 		version(X11) {
8776 			return stringWidth("x");
8777 		} version(OSXCocoa) {
8778 			return stringWidth("x");
8779 		} else version(Windows)
8780 			return width_;
8781 		else assert(0);
8782 	}
8783 
8784 	/++
8785 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8786 
8787 		History:
8788 			Added January 16, 2021
8789 	+/
8790 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8791 	// FIXME: what about tab?
8792 		if(isNull)
8793 			return 0;
8794 
8795 		version(X11) {
8796 			version(with_xft)
8797 				if(isXft && xftFont !is null) {
8798 					//return xftFont.max_advance_width;
8799 					XGlyphInfo extents;
8800 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8801 					// writeln(extents);
8802 					return extents.xOff;
8803 				}
8804 			if(font is null)
8805 				return 0;
8806 			else if(fontset) {
8807 				XRectangle rect;
8808 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8809 
8810 				return rect.width;
8811 			} else {
8812 				return XTextWidth(font, s.ptr, cast(int) s.length);
8813 			}
8814 		} else version(Windows) {
8815 			WCharzBuffer buffer = WCharzBuffer(s);
8816 
8817 			return stringWidth(buffer.slice, window);
8818 		} else version(OSXCocoa) {
8819 			/+
8820 			int charCount = [string length];
8821 			CGGlyph glyphs[charCount];
8822 			CGRect rects[charCount];
8823 
8824 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
8825 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
8826 
8827 			int totalwidth = 0, maxheight = 0;
8828 			for (int i=0; i < charCount; i++)
8829 			{
8830 				totalwidth += rects[i].size.width;
8831 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
8832 			}
8833 
8834 			dim = CGSizeMake(totalwidth, maxheight);
8835 			+/
8836 
8837 			return 16; // FIXME
8838 		}
8839 		else assert(0);
8840 	}
8841 
8842 	version(Windows)
8843 	/// ditto
8844 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8845 		if(isNull)
8846 			return 0;
8847 		version(Windows) {
8848 			SIZE size;
8849 
8850 			prepareContext(window);
8851 			scope(exit) releaseContext();
8852 
8853 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8854 
8855 			return size.cx;
8856 		} else {
8857 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8858 			static assert(0, "not implemented yet");
8859 			//return stringWidth(s, window);
8860 		}
8861 	}
8862 
8863 	private {
8864 		int prepRefcount;
8865 
8866 		version(Windows) {
8867 			HDC dc;
8868 			HANDLE orig;
8869 			HWND hwnd;
8870 		}
8871 	}
8872 	/++
8873 		[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.
8874 
8875 		History:
8876 			Added January 23, 2021
8877 	+/
8878 	void prepareContext(SimpleWindow window = null) {
8879 		prepRefcount++;
8880 		if(prepRefcount == 1) {
8881 			version(Windows) {
8882 				hwnd = window is null ? null : window.impl.hwnd;
8883 				dc = GetDC(hwnd);
8884 				orig = SelectObject(dc, font);
8885 			}
8886 		}
8887 	}
8888 	/// ditto
8889 	void releaseContext() {
8890 		prepRefcount--;
8891 		if(prepRefcount == 0) {
8892 			version(Windows) {
8893 				SelectObject(dc, orig);
8894 				ReleaseDC(hwnd, dc);
8895 				hwnd = null;
8896 				dc = null;
8897 				orig = null;
8898 			}
8899 		}
8900 	}
8901 
8902 	/+
8903 		FIXME: I think I need advance and kerning pair
8904 
8905 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8906 	+/
8907 
8908 	/++
8909 		Returns the height of the font.
8910 
8911 		History:
8912 			Added March 26, 2020
8913 			Documented January 16, 2021
8914 	+/
8915 	int height() {
8916 		version(X11) {
8917 			version(with_xft)
8918 				if(isXft && xftFont !is null) {
8919 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8920 				}
8921 			if(font is null)
8922 				return 0;
8923 			return font.max_bounds.ascent + font.max_bounds.descent;
8924 		} else version(Windows) {
8925 			return height_;
8926 		} else version(OSXCocoa) {
8927 			if(font is null)
8928 				return 0;
8929 			return cast(int) font.capHeight;
8930 		}
8931 		else assert(0);
8932 	}
8933 
8934 	private int ascent_;
8935 	private int descent_;
8936 
8937 	/++
8938 		Max ascent above the baseline.
8939 
8940 		History:
8941 			Added January 22, 2021
8942 	+/
8943 	int ascent() {
8944 		return ascent_;
8945 	}
8946 
8947 	/++
8948 		Max descent below the baseline.
8949 
8950 		History:
8951 			Added January 22, 2021
8952 	+/
8953 	int descent() {
8954 		return descent_;
8955 	}
8956 
8957 	/++
8958 		Loads the default font used by [ScreenPainter] if none others are loaded.
8959 
8960 		Returns:
8961 			This method mutates the `this` object, but then returns `this` for
8962 			easy chaining like:
8963 
8964 			---
8965 			auto font = foo.isNull ? foo : foo.loadDefault
8966 			---
8967 
8968 		History:
8969 			Added previously, but left unimplemented until January 24, 2021.
8970 	+/
8971 	OperatingSystemFont loadDefault() {
8972 		unload();
8973 
8974 		loadedInfo = LoadedInfo.init;
8975 
8976 		version(X11) {
8977 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8978 			// but meh since sdpy does its own thing, this should be ok too
8979 
8980 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8981 			this.font = ScreenPainterImplementation.defaultfont;
8982 			this.fontset = ScreenPainterImplementation.defaultfontset;
8983 
8984 			prepareFontInfo();
8985 			return this;
8986 		} else version(Windows) {
8987 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8988 			this.font = ScreenPainterImplementation.defaultGuiFont;
8989 
8990 			prepareFontInfo();
8991 			return this;
8992 		} else version(OSXCocoa) {
8993 			this.font = NSFont.systemFontOfSize(12);
8994 
8995 			prepareFontInfo();
8996 			return this;
8997 		} else throw new NotYetImplementedException();
8998 	}
8999 
9000 	///
9001 	bool isNull() {
9002 		version(with_xft)
9003 			if(isXft)
9004 				return xftFont is null;
9005 		return font is null;
9006 	}
9007 
9008 	/* Metrics */
9009 	/+
9010 		GetABCWidth
9011 		GetKerningPairs
9012 
9013 		if I do it right, I can size it all here, and match
9014 		what happens when I draw the full string with the OS functions.
9015 
9016 		subclasses might do the same thing while getting the glyphs on images
9017 	struct GlyphInfo {
9018 		int glyph;
9019 
9020 		size_t stringIdxStart;
9021 		size_t stringIdxEnd;
9022 
9023 		Rectangle boundingBox;
9024 	}
9025 	GlyphInfo[] getCharBoxes() {
9026 		// XftTextExtentsUtf8
9027 		return null;
9028 
9029 	}
9030 	+/
9031 
9032 	~this() {
9033 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9034 		unload();
9035 	}
9036 }
9037 
9038 version(Windows)
9039 private string sliceCString(const(wchar)[] w) {
9040 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9041 }
9042 
9043 private inout(char)[] sliceCString(inout(char)* s) {
9044 	import core.stdc.string;
9045 	auto len = strlen(s);
9046 	return s[0 .. len];
9047 }
9048 
9049 version(OSXCocoa)
9050 	alias PaintingHandle = NSObject;
9051 else
9052 	alias PaintingHandle = NativeWindowHandle;
9053 
9054 /**
9055 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9056 	than constructing it directly. Then, it is reference counted so you can pass it
9057 	at around and when the last ref goes out of scope, the buffered drawing activities
9058 	are all carried out.
9059 
9060 
9061 	Most functions use the outlineColor instead of taking a color themselves.
9062 	ScreenPainter is reference counted and draws its buffer to the screen when its
9063 	final reference goes out of scope.
9064 */
9065 struct ScreenPainter {
9066 	CapableOfBeingDrawnUpon window;
9067 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9068 		this.window = window;
9069 		if(window.closed)
9070 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9071 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9072 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9073 		if(window.activeScreenPainter !is null) {
9074 			impl = window.activeScreenPainter;
9075 			if(impl.referenceCount == 0) {
9076 				impl.window = window;
9077 				impl.create(handle);
9078 			}
9079 			impl.manualInvalidations = manualInvalidations;
9080 			impl.referenceCount++;
9081 		//	writeln("refcount ++ ", impl.referenceCount);
9082 		} else {
9083 			impl = new ScreenPainterImplementation;
9084 			impl.window = window;
9085 			impl.create(handle);
9086 			impl.referenceCount = 1;
9087 			impl.manualInvalidations = manualInvalidations;
9088 			window.activeScreenPainter = impl;
9089 			// writeln("constructed");
9090 		}
9091 
9092 		copyActiveOriginals();
9093 	}
9094 
9095 	/++
9096 		EXPERIMENTAL. subject to change.
9097 
9098 		When you draw a cursor, you can draw this to notify your window of where it is,
9099 		for IME systems to use.
9100 	+/
9101 	void notifyCursorPosition(int x, int y, int width, int height) {
9102 		if(auto w = cast(SimpleWindow) window) {
9103 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9104 		}
9105 	}
9106 
9107 	/++
9108 		If you are using manual invalidations, this informs the
9109 		window system that a section needs to be redrawn.
9110 
9111 		If you didn't opt into manual invalidation, you don't
9112 		have to call this.
9113 
9114 		History:
9115 			Added December 30, 2021 (dub v10.5)
9116 	+/
9117 	void invalidateRect(Rectangle rect) {
9118 		if(impl is null) return;
9119 
9120 		// transform(rect)
9121 		rect.left += _originX;
9122 		rect.right += _originX;
9123 		rect.top += _originY;
9124 		rect.bottom += _originY;
9125 
9126 		impl.invalidateRect(rect);
9127 	}
9128 
9129 	private Pen originalPen;
9130 	private Color originalFillColor;
9131 	private arsd.color.Rectangle originalClipRectangle;
9132 	private OperatingSystemFont originalFont;
9133 	void copyActiveOriginals() {
9134 		if(impl is null) return;
9135 		originalPen = impl._activePen;
9136 		originalFillColor = impl._fillColor;
9137 		originalClipRectangle = impl._clipRectangle;
9138 		version(OSXCocoa) {} else
9139 		originalFont = impl._activeFont;
9140 	}
9141 
9142 	~this() {
9143 		if(impl is null) return;
9144 		impl.referenceCount--;
9145 		//writeln("refcount -- ", impl.referenceCount);
9146 		if(impl.referenceCount == 0) {
9147 			// writeln("destructed");
9148 			impl.dispose();
9149 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9150 			// writeln("paint finished");
9151 		} else {
9152 			// there is still an active reference, reset stuff so the
9153 			// next user doesn't get weirdness via the reference
9154 			this.rasterOp = RasterOp.normal;
9155 			pen = originalPen;
9156 			fillColor = originalFillColor;
9157 			if(originalFont)
9158 				setFont(originalFont);
9159 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9160 		}
9161 	}
9162 
9163 	this(this) {
9164 		if(impl is null) return;
9165 		impl.referenceCount++;
9166 		//writeln("refcount ++ ", impl.referenceCount);
9167 
9168 		copyActiveOriginals();
9169 	}
9170 
9171 	private int _originX;
9172 	private int _originY;
9173 	@property int originX() { return _originX; }
9174 	@property int originY() { return _originY; }
9175 	@property int originX(int a) {
9176 		_originX = a;
9177 		return _originX;
9178 	}
9179 	@property int originY(int a) {
9180 		_originY = a;
9181 		return _originY;
9182 	}
9183 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9184 	private void transform(ref Point p) {
9185 		if(impl is null) return;
9186 		p.x += _originX;
9187 		p.y += _originY;
9188 	}
9189 
9190 	// this needs to be checked BEFORE the originX/Y transformation
9191 	private bool isClipped(Point p) {
9192 		return !currentClipRectangle.contains(p);
9193 	}
9194 	private bool isClipped(Point p, int width, int height) {
9195 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9196 	}
9197 	private bool isClipped(Point p, Size s) {
9198 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9199 	}
9200 	private bool isClipped(Point p, Point p2) {
9201 		// need to ensure the end points are actually included inside, so the +1 does that
9202 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9203 	}
9204 
9205 
9206 	/++
9207 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9208 
9209 		Returns:
9210 			The old clip rectangle.
9211 
9212 		History:
9213 			Return value was `void` prior to May 10, 2021.
9214 
9215 	+/
9216 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9217 		if(impl is null) return currentClipRectangle;
9218 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9219 			return currentClipRectangle; // no need to do anything
9220 		auto old = currentClipRectangle;
9221 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9222 		transform(pt);
9223 
9224 		impl.setClipRectangle(pt.x, pt.y, width, height);
9225 
9226 		return old;
9227 	}
9228 
9229 	/// ditto
9230 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9231 		if(impl is null) return currentClipRectangle;
9232 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9233 	}
9234 
9235 	///
9236 	void setFont(OperatingSystemFont font) {
9237 		if(impl is null) return;
9238 		impl.setFont(font);
9239 	}
9240 
9241 	///
9242 	int fontHeight() {
9243 		if(impl is null) return 0;
9244 		return impl.fontHeight();
9245 	}
9246 
9247 	private Pen activePen;
9248 
9249 	///
9250 	@property void pen(Pen p) {
9251 		if(impl is null) return;
9252 		activePen = p;
9253 		impl.pen(p);
9254 	}
9255 
9256 	///
9257 	@scriptable
9258 	@property void outlineColor(Color c) {
9259 		if(impl is null) return;
9260 		if(activePen.color == c)
9261 			return;
9262 		activePen.color = c;
9263 		impl.pen(activePen);
9264 	}
9265 
9266 	///
9267 	@scriptable
9268 	@property void fillColor(Color c) {
9269 		if(impl is null) return;
9270 		impl.fillColor(c);
9271 	}
9272 
9273 	///
9274 	@property void rasterOp(RasterOp op) {
9275 		if(impl is null) return;
9276 		impl.rasterOp(op);
9277 	}
9278 
9279 
9280 	void updateDisplay() {
9281 		// FIXME this should do what the dtor does
9282 	}
9283 
9284 	/// 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)
9285 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9286 		if(impl is null) return;
9287 		if(isClipped(upperLeft, width, height)) return;
9288 		transform(upperLeft);
9289 		version(Windows) {
9290 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9291 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9292 			RECT clip = scroll;
9293 			RECT uncovered;
9294 			HRGN hrgn;
9295 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9296 				throw new WindowsApiException("ScrollDC", GetLastError());
9297 
9298 		} else version(X11) {
9299 			// FIXME: clip stuff outside this rectangle
9300 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9301 		} else version(OSXCocoa) {
9302 			throw new NotYetImplementedException();
9303 		} else static assert(0);
9304 	}
9305 
9306 	///
9307 	void clear(Color color = Color.white()) {
9308 		if(impl is null) return;
9309 		fillColor = color;
9310 		outlineColor = color;
9311 		drawRectangle(Point(0, 0), window.width, window.height);
9312 	}
9313 
9314 	/++
9315 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9316 
9317 		Params:
9318 			upperLeft = point on the window where the upper left corner of the image will be drawn
9319 			imageUpperLeft = point on the image to start the slice to draw
9320 			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.
9321 		History:
9322 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9323 	+/
9324 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9325 		if(impl is null) return;
9326 		if(isClipped(upperLeft, s.width, s.height)) return;
9327 		transform(upperLeft);
9328 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9329 	}
9330 
9331 	///
9332 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9333 		if(impl is null) return;
9334 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9335 		transform(upperLeft);
9336 		if(w == 0 || w > i.width)
9337 			w = i.width;
9338 		if(h == 0 || h > i.height)
9339 			h = i.height;
9340 		if(upperLeftOfImage.x < 0)
9341 			upperLeftOfImage.x = 0;
9342 		if(upperLeftOfImage.y < 0)
9343 			upperLeftOfImage.y = 0;
9344 
9345 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9346 	}
9347 
9348 	///
9349 	Size textSize(in char[] text) {
9350 		if(impl is null) return Size(0, 0);
9351 		return impl.textSize(text);
9352 	}
9353 
9354 	/++
9355 		Draws a string in the window with the set font (see [setFont] to change it).
9356 
9357 		Params:
9358 			upperLeft = the upper left point of the bounding box of the text
9359 			text = the string to draw
9360 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9361 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9362 	+/
9363 	@scriptable
9364 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9365 		if(impl is null) return;
9366 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9367 			if(isClipped(upperLeft, lowerRight)) return;
9368 			transform(lowerRight);
9369 		} else {
9370 			if(isClipped(upperLeft, textSize(text))) return;
9371 		}
9372 		transform(upperLeft);
9373 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9374 	}
9375 
9376 	/++
9377 		Draws text using a custom font.
9378 
9379 		This is still MAJOR work in progress.
9380 
9381 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9382 	+/
9383 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9384 		if(impl is null) return;
9385 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9386 		transform(upperLeft);
9387 		font.drawString(this, upperLeft, text);
9388 	}
9389 
9390 	version(Windows)
9391 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9392 		if(impl is null) return;
9393 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9394 		transform(upperLeft);
9395 
9396 		if(text.length && text[$-1] == '\n')
9397 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9398 
9399 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9400 	}
9401 
9402 	static struct TextDrawingContext {
9403 		Point boundingBoxUpperLeft;
9404 		Point boundingBoxLowerRight;
9405 
9406 		Point currentLocation;
9407 
9408 		Point lastDrewUpperLeft;
9409 		Point lastDrewLowerRight;
9410 
9411 		// how do i do right aligned rich text?
9412 		// i kinda want to do a pre-made drawing then right align
9413 		// draw the whole block.
9414 		//
9415 		// That's exactly the diff: inline vs block stuff.
9416 
9417 		// I need to get coordinates of an inline section out too,
9418 		// not just a bounding box, but a series of bounding boxes
9419 		// should be ok. Consider what's needed to detect a click
9420 		// on a link in the middle of a paragraph breaking a line.
9421 		//
9422 		// Generally, we should be able to get the rectangles of
9423 		// any portion we draw.
9424 		//
9425 		// It also needs to tell what text is left if it overflows
9426 		// out of the box, so we can do stuff like float images around
9427 		// it. It should not attempt to draw a letter that would be
9428 		// clipped.
9429 		//
9430 		// I might also turn off word wrap stuff.
9431 	}
9432 
9433 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9434 		if(impl is null) return;
9435 		// FIXME
9436 	}
9437 
9438 	/// Drawing an individual pixel is slow. Avoid it if possible.
9439 	void drawPixel(Point where) {
9440 		if(impl is null) return;
9441 		if(isClipped(where)) return;
9442 		transform(where);
9443 		impl.drawPixel(where.x, where.y);
9444 	}
9445 
9446 
9447 	/// Draws a pen using the current pen / outlineColor
9448 	@scriptable
9449 	void drawLine(Point starting, Point ending) {
9450 		if(impl is null) return;
9451 		if(isClipped(starting, ending)) return;
9452 		transform(starting);
9453 		transform(ending);
9454 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9455 	}
9456 
9457 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9458 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9459 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9460 	@scriptable
9461 	void drawRectangle(Point upperLeft, int width, int height) {
9462 		if(impl is null) return;
9463 		if(isClipped(upperLeft, width, height)) return;
9464 		transform(upperLeft);
9465 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9466 	}
9467 
9468 	/// ditto
9469 	void drawRectangle(Point upperLeft, Size size) {
9470 		if(impl is null) return;
9471 		if(isClipped(upperLeft, size.width, size.height)) return;
9472 		transform(upperLeft);
9473 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9474 	}
9475 
9476 	/// ditto
9477 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9478 		if(impl is null) return;
9479 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9480 		transform(upperLeft);
9481 		transform(lowerRightInclusive);
9482 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9483 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9484 	}
9485 
9486 	// overload added on May 12, 2021
9487 	/// ditto
9488 	void drawRectangle(Rectangle rect) {
9489 		drawRectangle(rect.upperLeft, rect.size);
9490 	}
9491 
9492 	/// Arguments are the points of the bounding rectangle
9493 	void drawEllipse(Point upperLeft, Point lowerRight) {
9494 		if(impl is null) return;
9495 		if(isClipped(upperLeft, lowerRight)) return;
9496 		transform(upperLeft);
9497 		transform(lowerRight);
9498 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9499 	}
9500 
9501 	/++
9502 		start and finish are units of degrees * 64
9503 
9504 		History:
9505 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9506 
9507 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9508 	+/
9509 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9510 		if(impl is null) return;
9511 		// FIXME: not actually implemented
9512 		if(isClipped(upperLeft, width, height)) return;
9513 		transform(upperLeft);
9514 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9515 	}
9516 
9517 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9518 	void drawCircle(Point upperLeft, int diameter) {
9519 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9520 	}
9521 
9522 	/// .
9523 	void drawPolygon(Point[] vertexes) {
9524 		if(impl is null) return;
9525 		assert(vertexes.length);
9526 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9527 		foreach(ref vertex; vertexes) {
9528 			if(vertex.x < minX)
9529 				minX = vertex.x;
9530 			if(vertex.y < minY)
9531 				minY = vertex.y;
9532 			if(vertex.x > maxX)
9533 				maxX = vertex.x;
9534 			if(vertex.y > maxY)
9535 				maxY = vertex.y;
9536 			transform(vertex);
9537 		}
9538 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9539 		impl.drawPolygon(vertexes);
9540 	}
9541 
9542 	/// ditto
9543 	void drawPolygon(Point[] vertexes...) {
9544 		if(impl is null) return;
9545 		drawPolygon(vertexes);
9546 	}
9547 
9548 
9549 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9550 
9551 	//mixin NativeScreenPainterImplementation!() impl;
9552 
9553 
9554 	// HACK: if I mixin the impl directly, it won't let me override the copy
9555 	// constructor! The linker complains about there being multiple definitions.
9556 	// I'll make the best of it and reference count it though.
9557 	ScreenPainterImplementation* impl;
9558 }
9559 
9560 	// HACK: I need a pointer to the implementation so it's separate
9561 	struct ScreenPainterImplementation {
9562 		CapableOfBeingDrawnUpon window;
9563 		int referenceCount;
9564 		mixin NativeScreenPainterImplementation!();
9565 	}
9566 
9567 // FIXME: i haven't actually tested the sprite class on MS Windows
9568 
9569 /**
9570 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9571 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9572 
9573 
9574 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9575 	though I'm not sure that's ideal and the implementation might change.
9576 
9577 	You create one by giving a window and an image. It optimizes for that window,
9578 	and copies the image into it to use as the initial picture. Creating a sprite
9579 	can be quite slow (especially over a network connection) so you should do it
9580 	as little as possible and just hold on to your sprite handles after making them.
9581 	simpledisplay does try to do its best though, using the XSHM extension if available,
9582 	but you should still write your code as if it will always be slow.
9583 
9584 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9585 	a fast operation - much faster than drawing the Image itself every time.
9586 
9587 	`Sprite` represents a scarce resource which should be freed when you
9588 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9589 	after it has been disposed. If you are unsure about this, don't take chances,
9590 	just let the garbage collector do it for you. But ideally, you can manage its
9591 	lifetime more efficiently.
9592 
9593 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9594 	support alpha blending in its drawing at this time. That might change in the
9595 	future, but if you need alpha blending right now, use OpenGL instead. See
9596 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9597 
9598 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9599 	in by setting the enableAlpha = true in the constructor.
9600 */
9601 class Sprite : CapableOfBeingDrawnUpon {
9602 
9603 	///
9604 	ScreenPainter draw() {
9605 		return ScreenPainter(this, handle, false);
9606 	}
9607 
9608 	/++
9609 		Copies the sprite's current state into a [TrueColorImage].
9610 
9611 		Be warned: this can be a very slow operation
9612 
9613 		History:
9614 			Actually implemented on March 14, 2021
9615 	+/
9616 	TrueColorImage takeScreenshot() {
9617 		return trueColorImageFromNativeHandle(handle, width, height);
9618 	}
9619 
9620 	void delegate() paintingFinishedDg() { return null; }
9621 	bool closed() { return false; }
9622 	ScreenPainterImplementation* activeScreenPainter_;
9623 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9624 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9625 
9626 	version(Windows)
9627 		private ubyte* rawData;
9628 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9629 	// ditto on the XPicture stuff
9630 
9631 	version(X11) {
9632 		private static XRenderPictFormat* RGB24;
9633 		private static XRenderPictFormat* ARGB32;
9634 
9635 		private Picture xrenderPicture;
9636 	}
9637 
9638 	version(X11)
9639 	private static void requireXRender() {
9640 		if(!XRenderLibrary.loadAttempted) {
9641 			XRenderLibrary.loadDynamicLibrary();
9642 		}
9643 
9644 		if(!XRenderLibrary.loadSuccessful)
9645 			throw new Exception("XRender library load failure");
9646 
9647 		auto display = XDisplayConnection.get;
9648 
9649 		// FIXME: if we migrate X displays, these need to be changed
9650 		if(RGB24 is null)
9651 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9652 		if(ARGB32 is null)
9653 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9654 	}
9655 
9656 	protected this() {}
9657 
9658 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9659 		this._width = width;
9660 		this._height = height;
9661 		this.enableAlpha = enableAlpha;
9662 
9663 		version(X11) {
9664 			auto display = XDisplayConnection.get();
9665 
9666 			if(enableAlpha) {
9667 				requireXRender();
9668 			}
9669 
9670 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9671 
9672 			if(enableAlpha) {
9673 				XRenderPictureAttributes attrs;
9674 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9675 			}
9676 		} else version(Windows) {
9677 			version(CRuntime_DigitalMars) {
9678 				//if(enableAlpha)
9679 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9680 			}
9681 
9682 			BITMAPINFO infoheader;
9683 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9684 			infoheader.bmiHeader.biWidth = width;
9685 			infoheader.bmiHeader.biHeight = height;
9686 			infoheader.bmiHeader.biPlanes = 1;
9687 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9688 			infoheader.bmiHeader.biCompression = BI_RGB;
9689 
9690 			// FIXME: this should prolly be a device dependent bitmap...
9691 			handle = CreateDIBSection(
9692 				null,
9693 				&infoheader,
9694 				DIB_RGB_COLORS,
9695 				cast(void**) &rawData,
9696 				null,
9697 				0);
9698 
9699 			if(handle is null)
9700 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9701 		}
9702 	}
9703 
9704 	/// Makes a sprite based on the image with the initial contents from the Image
9705 	this(SimpleWindow win, Image i) {
9706 		this(win, i.width, i.height, i.enableAlpha);
9707 
9708 		version(X11) {
9709 			auto display = XDisplayConnection.get();
9710 			auto gc = XCreateGC(display, this.handle, 0, null);
9711 			scope(exit) XFreeGC(display, gc);
9712 			if(i.usingXshm)
9713 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9714 			else
9715 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9716 		} else version(Windows) {
9717 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9718 			auto arrLength = itemsPerLine * height;
9719 			rawData[0..arrLength] = i.rawData[0..arrLength];
9720 		} else version(OSXCocoa) {
9721 			// FIXME: I have no idea if this is even any good
9722 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9723 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
9724 				colorSpace,
9725 				kCGImageAlphaPremultipliedLast
9726 				|kCGBitmapByteOrder32Big);
9727 			CGColorSpaceRelease(colorSpace);
9728 			auto rawData = CGBitmapContextGetData(handle);
9729 
9730 			auto rdl = (width * height * 4);
9731 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9732 		} else static assert(0);
9733 	}
9734 
9735 	/++
9736 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9737 
9738 		Params:
9739 			where = point on the window where the upper left corner of the image will be drawn
9740 			imageUpperLeft = point on the image to start the slice to draw
9741 			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.
9742 		History:
9743 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9744 	+/
9745 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9746 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9747 	}
9748 
9749 	/// Call this when you're ready to get rid of it
9750 	void dispose() {
9751 		version(X11) {
9752 			staticDispose(xrenderPicture, handle);
9753 			xrenderPicture = None;
9754 			handle = None;
9755 		} else version(Windows) {
9756 			staticDispose(handle);
9757 			handle = null;
9758 		} else version(OSXCocoa) {
9759 			staticDispose(handle);
9760 			handle = null;
9761 		} else static assert(0);
9762 
9763 	}
9764 
9765 	version(X11)
9766 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9767 		if(xrenderPicture)
9768 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9769 		if(handle)
9770 			XFreePixmap(XDisplayConnection.get(), handle);
9771 	}
9772 	else version(Windows)
9773 	static void staticDispose(HBITMAP handle) {
9774 		if(handle)
9775 			DeleteObject(handle);
9776 	}
9777 	else version(OSXCocoa)
9778 	static void staticDispose(CGContextRef context) {
9779 		if(context)
9780 			CGContextRelease(context);
9781 	}
9782 
9783 	~this() {
9784 		version(X11) { if(xrenderPicture || handle)
9785 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9786 		} else version(Windows) { if(handle)
9787 			cleanupQueue.queue!staticDispose(handle);
9788 		} else version(OSXCocoa) { if(handle)
9789 			cleanupQueue.queue!staticDispose(handle);
9790 		} else static assert(0);
9791 	}
9792 
9793 	///
9794 	final @property int width() { return _width; }
9795 
9796 	///
9797 	final @property int height() { return _height; }
9798 
9799 	///
9800 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9801 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9802 	}
9803 
9804 	auto nativeHandle() {
9805 		return handle;
9806 	}
9807 
9808 	private:
9809 
9810 	int _width;
9811 	int _height;
9812 	bool enableAlpha;
9813 	version(X11)
9814 		Pixmap handle;
9815 	else version(Windows)
9816 		HBITMAP handle;
9817 	else version(OSXCocoa)
9818 		CGContextRef handle;
9819 	else static assert(0);
9820 }
9821 
9822 /++
9823 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9824 
9825 	History:
9826 		Added November 20, 2021 (dub v10.4)
9827 +/
9828 version(OSXCocoa) {} else // NotYetImplementedException
9829 abstract class Gradient : Sprite {
9830 	protected this(int w, int h) {
9831 		version(X11) {
9832 			Sprite.requireXRender();
9833 
9834 			super();
9835 			enableAlpha = true;
9836 			_width = w;
9837 			_height = h;
9838 		} else version(Windows) {
9839 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9840 		}
9841 	}
9842 
9843 	version(Windows)
9844 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9845 		auto ptr = rawData;
9846 		foreach(j; 0 .. _height)
9847 		foreach(i; 0 .. _width) {
9848 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9849 			*rawData = (color.a * color.b) / 255; rawData++;
9850 			*rawData = (color.a * color.g) / 255; rawData++;
9851 			*rawData = (color.a * color.r) / 255; rawData++;
9852 			*rawData = color.a; rawData++;
9853 		}
9854 	}
9855 
9856 	version(X11)
9857 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9858 		assert(stops.length > 0);
9859 		assert(stops.length <= 16, "I got lazy with buffers");
9860 
9861 		XFixed[16] stopsPositions = void;
9862 		XRenderColor[16] colors = void;
9863 
9864 		foreach(idx, stop; stops) {
9865 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9866 			auto c = stop.c;
9867 			colors[idx] = XRenderColor(
9868 				cast(ushort)(c.r * ushort.max / 255),
9869 				cast(ushort)(c.g * ushort.max / 255),
9870 				cast(ushort)(c.b * ushort.max / 255),
9871 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9872 			);
9873 		}
9874 
9875 		xrenderPicture = dg(stopsPositions, colors);
9876 	}
9877 
9878 	///
9879 	static struct Stop {
9880 		float percentage; /// between 0 and 1.0
9881 		Color c;
9882 	}
9883 }
9884 
9885 /++
9886 	Creates a linear gradient between p1 and p2.
9887 
9888 	X ONLY RIGHT NOW
9889 
9890 	History:
9891 		Added November 20, 2021 (dub v10.4)
9892 
9893 	Bugs:
9894 		Not yet implemented on Windows.
9895 +/
9896 version(OSXCocoa) {} else // NotYetImplementedException
9897 class LinearGradient : Gradient {
9898 	/++
9899 
9900 	+/
9901 	this(Point p1, Point p2, Stop[] stops...) {
9902 		super(p2.x, p2.y);
9903 
9904 		version(X11) {
9905 			XLinearGradient gradient;
9906 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9907 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9908 
9909 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9910 				return XRenderCreateLinearGradient(
9911 					XDisplayConnection.get,
9912 					&gradient,
9913 					stopsPositions.ptr,
9914 					colors.ptr,
9915 					cast(int) stops.length);
9916 			});
9917 		} else version(Windows) {
9918 			// FIXME
9919 			forEachPixel((int x, int y) {
9920 				import core.stdc.math;
9921 
9922 				//sqrtf(
9923 
9924 				return Color.transparent;
9925 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9926 			});
9927 		}
9928 	}
9929 }
9930 
9931 /++
9932 	A conical gradient goes from color to color around a circumference from a center point.
9933 
9934 	X ONLY RIGHT NOW
9935 
9936 	History:
9937 		Added November 20, 2021 (dub v10.4)
9938 
9939 	Bugs:
9940 		Not yet implemented on Windows.
9941 +/
9942 version(OSXCocoa) {} else // NotYetImplementedException
9943 class ConicalGradient : Gradient {
9944 	/++
9945 
9946 	+/
9947 	this(Point center, float angleInDegrees, Stop[] stops...) {
9948 		super(center.x * 2, center.y * 2);
9949 
9950 		version(X11) {
9951 			XConicalGradient gradient;
9952 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9953 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9954 
9955 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9956 				return XRenderCreateConicalGradient(
9957 					XDisplayConnection.get,
9958 					&gradient,
9959 					stopsPositions.ptr,
9960 					colors.ptr,
9961 					cast(int) stops.length);
9962 			});
9963 		} else version(Windows) {
9964 			// FIXME
9965 			forEachPixel((int x, int y) {
9966 				import core.stdc.math;
9967 
9968 				//sqrtf(
9969 
9970 				return Color.transparent;
9971 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9972 			});
9973 
9974 		}
9975 	}
9976 }
9977 
9978 /++
9979 	A radial gradient goes from color to color based on distance from the center.
9980 	It is like rings of color.
9981 
9982 	X ONLY RIGHT NOW
9983 
9984 
9985 	More specifically, you create two circles: an inner circle and an outer circle.
9986 	The gradient is only drawn in the area outside the inner circle but inside the outer
9987 	circle. The closest line between those two circles forms the line for the gradient
9988 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9989 
9990 	History:
9991 		Added November 20, 2021 (dub v10.4)
9992 
9993 	Bugs:
9994 		Not yet implemented on Windows.
9995 +/
9996 version(OSXCocoa) {} else // NotYetImplementedException
9997 class RadialGradient : Gradient {
9998 	/++
9999 
10000 	+/
10001 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10002 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10003 
10004 		version(X11) {
10005 			XRadialGradient gradient;
10006 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10007 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10008 
10009 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10010 				return XRenderCreateRadialGradient(
10011 					XDisplayConnection.get,
10012 					&gradient,
10013 					stopsPositions.ptr,
10014 					colors.ptr,
10015 					cast(int) stops.length);
10016 			});
10017 		} else version(Windows) {
10018 			// FIXME
10019 			forEachPixel((int x, int y) {
10020 				import core.stdc.math;
10021 
10022 				//sqrtf(
10023 
10024 				return Color.transparent;
10025 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10026 			});
10027 		}
10028 	}
10029 }
10030 
10031 
10032 
10033 /+
10034 	NOT IMPLEMENTED
10035 
10036 	A display-stored image optimized for relatively quick drawing, like
10037 	[Sprite], but this one supports alpha channel blending and does NOT
10038 	support direct drawing upon it with a [ScreenPainter].
10039 
10040 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10041 	plain [ScreenPainter]... sort of.
10042 
10043 	On X11, it requires the Xrender extension and library. This is available
10044 	almost everywhere though.
10045 
10046 	History:
10047 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10048 +/
10049 version(none)
10050 class AlphaSprite {
10051 	/++
10052 		Copies the given image into it.
10053 	+/
10054 	this(MemoryImage img) {
10055 
10056 		if(!XRenderLibrary.loadAttempted) {
10057 			XRenderLibrary.loadDynamicLibrary();
10058 
10059 			// FIXME: this needs to be reconstructed when the X server changes
10060 			repopulateX();
10061 		}
10062 		if(!XRenderLibrary.loadSuccessful)
10063 			throw new Exception("XRender library load failure");
10064 
10065 		// I probably need to put the alpha mask in a separate Picture
10066 		// ugh
10067 		// maybe the Sprite itself can have an alpha bitmask anyway
10068 
10069 
10070 		auto display = XDisplayConnection.get();
10071 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10072 
10073 
10074 		XRenderPictureAttributes attrs;
10075 
10076 		handle = XRenderCreatePicture(
10077 			XDisplayConnection.get,
10078 			pixmap,
10079 			RGBA,
10080 			0,
10081 			&attrs
10082 		);
10083 
10084 	}
10085 
10086 	// maybe i'll use the create gradient functions too with static factories..
10087 
10088 	void drawAt(ScreenPainter painter, Point where) {
10089 		//painter.drawPixmap(this, where);
10090 
10091 		XRenderPictureAttributes attrs;
10092 
10093 		auto pic = XRenderCreatePicture(
10094 			XDisplayConnection.get,
10095 			painter.impl.d,
10096 			RGB,
10097 			0,
10098 			&attrs
10099 		);
10100 
10101 		XRenderComposite(
10102 			XDisplayConnection.get,
10103 			3, // PictOpOver
10104 			handle,
10105 			None,
10106 			pic,
10107 			0, // src
10108 			0,
10109 			0, // mask
10110 			0,
10111 			10, // dest
10112 			10,
10113 			100, // width
10114 			100
10115 		);
10116 
10117 		/+
10118 		XRenderFreePicture(
10119 			XDisplayConnection.get,
10120 			pic
10121 		);
10122 
10123 		XRenderFreePicture(
10124 			XDisplayConnection.get,
10125 			fill
10126 		);
10127 		+/
10128 		// on Windows you can stretch but Xrender still can't :(
10129 	}
10130 
10131 	static XRenderPictFormat* RGB;
10132 	static XRenderPictFormat* RGBA;
10133 	static void repopulateX() {
10134 		auto display = XDisplayConnection.get;
10135 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10136 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10137 	}
10138 
10139 	XPixmap pixmap;
10140 	Picture handle;
10141 }
10142 
10143 ///
10144 interface CapableOfBeingDrawnUpon {
10145 	///
10146 	ScreenPainter draw();
10147 	///
10148 	int width();
10149 	///
10150 	int height();
10151 	protected ScreenPainterImplementation* activeScreenPainter();
10152 	protected void activeScreenPainter(ScreenPainterImplementation*);
10153 	bool closed();
10154 
10155 	void delegate() paintingFinishedDg();
10156 
10157 	/// Be warned: this can be a very slow operation
10158 	TrueColorImage takeScreenshot();
10159 }
10160 
10161 /// 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].
10162 void flushGui() {
10163 	version(X11) {
10164 		auto dpy = XDisplayConnection.get();
10165 		XLockDisplay(dpy);
10166 		scope(exit) XUnlockDisplay(dpy);
10167 		XFlush(dpy);
10168 	}
10169 }
10170 
10171 /++
10172 	Runs the given code in the GUI thread when its event loop
10173 	is available, blocking until it completes. This allows you
10174 	to create and manipulate windows from another thread without
10175 	invoking undefined behavior.
10176 
10177 	If this is the gui thread, it runs the code immediately.
10178 
10179 	If no gui thread exists yet, the current thread is assumed
10180 	to be it. Attempting to create windows or run the event loop
10181 	in any other thread will cause an assertion failure.
10182 
10183 
10184 	$(TIP
10185 		Did you know you can use UFCS on delegate literals?
10186 
10187 		() {
10188 			// code here
10189 		}.runInGuiThread;
10190 	)
10191 
10192 	Returns:
10193 		`true` if the function was called, `false` if it was not.
10194 		The function may not be called because the gui thread had
10195 		already terminated by the time you called this.
10196 
10197 	History:
10198 		Added April 10, 2020 (v7.2.0)
10199 
10200 		Return value added and implementation tweaked to avoid locking
10201 		at program termination on February 24, 2021 (v9.2.1).
10202 +/
10203 bool runInGuiThread(scope void delegate() dg) @trusted {
10204 	claimGuiThread();
10205 
10206 	if(thisIsGuiThread) {
10207 		dg();
10208 		return true;
10209 	}
10210 
10211 	if(guiThreadTerminating)
10212 		return false;
10213 
10214 	import core.sync.semaphore;
10215 	static Semaphore sc;
10216 	if(sc is null)
10217 		sc = new Semaphore();
10218 
10219 	static RunQueueMember* rqm;
10220 	if(rqm is null)
10221 		rqm = new RunQueueMember;
10222 	rqm.dg = cast(typeof(rqm.dg)) dg;
10223 	rqm.signal = sc;
10224 	rqm.thrown = null;
10225 
10226 	synchronized(runInGuiThreadLock) {
10227 		runInGuiThreadQueue ~= rqm;
10228 	}
10229 
10230 	if(!SimpleWindow.eventWakeUp())
10231 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10232 
10233 	rqm.signal.wait();
10234 	auto t = rqm.thrown;
10235 
10236 	if(t)
10237 		throw t;
10238 
10239 	return true;
10240 }
10241 
10242 // note it runs sync if this is the gui thread....
10243 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10244 	claimGuiThread();
10245 
10246 	try {
10247 
10248 		if(thisIsGuiThread) {
10249 			dg();
10250 			return;
10251 		}
10252 
10253 		if(guiThreadTerminating)
10254 			return;
10255 
10256 		RunQueueMember* rqm = new RunQueueMember;
10257 		rqm.dg = cast(typeof(rqm.dg)) dg;
10258 		rqm.signal = null;
10259 		rqm.thrown = null;
10260 
10261 		synchronized(runInGuiThreadLock) {
10262 			runInGuiThreadQueue ~= rqm;
10263 		}
10264 
10265 		if(!SimpleWindow.eventWakeUp())
10266 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10267 	} catch(Exception e) {
10268 		if(handleError)
10269 			handleError(e);
10270 	}
10271 }
10272 
10273 private void runPendingRunInGuiThreadDelegates() {
10274 	more:
10275 	RunQueueMember* next;
10276 	synchronized(runInGuiThreadLock) {
10277 		if(runInGuiThreadQueue.length) {
10278 			next = runInGuiThreadQueue[0];
10279 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10280 		} else {
10281 			next = null;
10282 		}
10283 	}
10284 
10285 	if(next) {
10286 		try {
10287 			next.dg();
10288 			next.thrown = null;
10289 		} catch(Throwable t) {
10290 			next.thrown = t;
10291 		}
10292 
10293 		if(next.signal)
10294 			next.signal.notify();
10295 
10296 		goto more;
10297 	}
10298 }
10299 
10300 private void claimGuiThread() nothrow {
10301 	import core.atomic;
10302 	if(cas(&guiThreadExists_, false, true))
10303 		thisIsGuiThread = true;
10304 }
10305 
10306 private struct RunQueueMember {
10307 	void delegate() dg;
10308 	import core.sync.semaphore;
10309 	Semaphore signal;
10310 	Throwable thrown;
10311 }
10312 
10313 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10314 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10315 private bool thisIsGuiThread = false;
10316 private shared bool guiThreadExists_ = false;
10317 private shared bool guiThreadTerminating = false;
10318 
10319 /++
10320 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10321 	event loop. All windows must be exclusively created and managed by a single thread.
10322 
10323 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10324 	when you call one of its constructors.
10325 
10326 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10327 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10328 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10329 
10330 	The reason this function is available is in case you want to message pass between a gui
10331 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10332 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10333 
10334 	History:
10335 		Added December 3, 2021 (dub v10.5)
10336 +/
10337 public bool guiThreadExists() {
10338 	return guiThreadExists_;
10339 }
10340 
10341 /++
10342 	Returns `true` if this thread is either running or set to be running the
10343 	simpledisplay.d gui core event loop because it owns windows.
10344 
10345 	It is important to keep gui-related functionality in the right thread, so you will
10346 	want to `runInGuiThread` when you call them (with some specific exceptions called
10347 	out in those specific functions' documentation). Notably, all windows must be
10348 	created and managed only from the gui thread.
10349 
10350 	Will return false if simpledisplay's other functions haven't been called
10351 	yet; check [guiThreadExists] in addition to this.
10352 
10353 	History:
10354 		Added December 3, 2021 (dub v10.5)
10355 +/
10356 public bool thisThreadRunningGui() {
10357 	return thisIsGuiThread;
10358 }
10359 
10360 /++
10361 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10362 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10363 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10364 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10365 	file instead if you are in one of those situations).
10366 
10367 	It does not support outputting very many types; just strings and ints are likely to actually work.
10368 
10369 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10370 	is unspecified meaning I can change it at any time. The only point of this function is to help
10371 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10372 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10373 	in those contexts.
10374 
10375 	$(WARNING
10376 		I reserve the right to change this function at any time. You can use it if it helps you
10377 		but do not rely on it for anything permanent.
10378 	)
10379 
10380 	History:
10381 		Added December 3, 2021. Not formally supported under any stable tag.
10382 +/
10383 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10384 	try {
10385 		version(Windows) {
10386 			import core.sys.windows.wincon;
10387 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10388 				AllocConsole();
10389 			const(char)* fn = "CONOUT$";
10390 		} else version(Posix) {
10391 			const(char)* fn = "/dev/tty";
10392 		} else static assert(0, "Function not implemented for your system");
10393 
10394 		if(fileOverride.length)
10395 			fn = fileOverride.ptr;
10396 
10397 		import core.stdc.stdio;
10398 		auto fp = fopen(fn, "wt");
10399 		if(fp is null) return;
10400 		scope(exit) fclose(fp);
10401 
10402 		string str;
10403 		foreach(item; t) {
10404 			static if(is(typeof(item) : const(char)[]))
10405 				str ~= item;
10406 			else
10407 				str ~= toInternal!string(item);
10408 			str ~= " ";
10409 		}
10410 		str ~= "\n";
10411 
10412 		fwrite(str.ptr, 1, str.length, fp);
10413 		fflush(fp);
10414 	} catch(Exception e) {
10415 		// sorry no hope
10416 	}
10417 }
10418 
10419 private void guiThreadFinalize() {
10420 	assert(thisIsGuiThread);
10421 
10422 	guiThreadTerminating = true; // don't add any more from this point on
10423 	runPendingRunInGuiThreadDelegates();
10424 }
10425 
10426 /+
10427 interface IPromise {
10428 	void reportProgress(int current, int max, string message);
10429 
10430 	/+ // not formally in cuz of templates but still
10431 	IPromise Then();
10432 	IPromise Catch();
10433 	IPromise Finally();
10434 	+/
10435 }
10436 
10437 /+
10438 	auto promise = async({ ... });
10439 	promise.Then(whatever).
10440 		Then(whateverelse).
10441 		Catch((exception) { });
10442 
10443 
10444 	A promise is run inside a fiber and it looks something like:
10445 
10446 	try {
10447 		auto res = whatever();
10448 		auto res2 = whateverelse(res);
10449 	} catch(Exception e) {
10450 		{ }(e);
10451 	}
10452 
10453 	When a thing succeeds, it is passed as an arg to the next
10454 +/
10455 class Promise(T) : IPromise {
10456 	auto Then() { return null; }
10457 	auto Catch() { return null; }
10458 	auto Finally() { return null; }
10459 
10460 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10461 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10462 	T await();
10463 }
10464 
10465 interface Task {
10466 }
10467 
10468 interface Resolvable(T) : Task {
10469 	void run();
10470 
10471 	void resolve(T);
10472 
10473 	Resolvable!T then(void delegate(T)); // returns a new promise
10474 	Resolvable!T error(Throwable); // js catch
10475 	Resolvable!T completed(); // js finally
10476 
10477 }
10478 
10479 /++
10480 	Runs `work` in a helper thread and sends its return value back to the main gui
10481 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10482 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10483 	kill the program.
10484 
10485 	You can call reportProgress(position, max, message) to update your parent window
10486 	on your progress.
10487 
10488 	I should also use `shared` methods. FIXME
10489 
10490 	History:
10491 		Added March 6, 2021 (dub version 9.3).
10492 +/
10493 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10494 	uponCompletion(work(null));
10495 }
10496 
10497 +/
10498 
10499 /// Used internal to dispatch events to various classes.
10500 interface CapableOfHandlingNativeEvent {
10501 	NativeEventHandler getNativeEventHandler();
10502 
10503 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10504 
10505 	version(X11) {
10506 		// if this is impossible, you are allowed to just throw from it
10507 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10508 		void recreateAfterDisconnect();
10509 		// discard any *connection specific* state, but keep enough that you
10510 		// can be recreated if possible. discardConnectionState() is always called immediately
10511 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10512 		// you need initialization order
10513 		void discardConnectionState();
10514 	}
10515 }
10516 
10517 version(X11)
10518 /++
10519 	State of keys on mouse events, especially motion.
10520 
10521 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10522 +/
10523 enum ModifierState : uint {
10524 	shift = 1, ///
10525 	capsLock = 2, ///
10526 	ctrl = 4, ///
10527 	alt = 8, /// Not always available on Windows
10528 	windows = 64, /// ditto
10529 	numLock = 16, ///
10530 
10531 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10532 	middleButtonDown = 512, /// ditto
10533 	rightButtonDown = 1024, /// ditto
10534 }
10535 else version(Windows)
10536 /// ditto
10537 enum ModifierState : uint {
10538 	shift = 4, ///
10539 	ctrl = 8, ///
10540 
10541 	// i'm not sure if the next two are available
10542 	alt = 256, /// not always available on Windows
10543 	windows = 512, /// ditto
10544 
10545 	capsLock = 1024, ///
10546 	numLock = 2048, ///
10547 
10548 	leftButtonDown = 1, /// not available on key events
10549 	middleButtonDown = 16, /// ditto
10550 	rightButtonDown = 2, /// ditto
10551 
10552 	backButtonDown = 0x20, /// not available on X
10553 	forwardButtonDown = 0x40, /// ditto
10554 }
10555 else version(OSXCocoa)
10556 // FIXME FIXME NotYetImplementedException
10557 enum ModifierState : uint {
10558 	shift = 1, ///
10559 	capsLock = 2, ///
10560 	ctrl = 4, ///
10561 	alt = 8, /// Not always available on Windows
10562 	windows = 64, /// ditto
10563 	numLock = 16, ///
10564 
10565 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10566 	middleButtonDown = 512, /// ditto
10567 	rightButtonDown = 1024, /// ditto
10568 }
10569 
10570 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10571 enum MouseButton : int {
10572 	none = 0,
10573 	left = 1, ///
10574 	right = 2, ///
10575 	middle = 4, ///
10576 	wheelUp = 8, ///
10577 	wheelDown = 16, ///
10578 	backButton = 32, /// often found on the thumb and used for back in browsers
10579 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10580 }
10581 
10582 version(X11) {
10583 	// FIXME: match ASCII whenever we can. Most of it is already there,
10584 	// but there's a few exceptions and mismatches with Windows
10585 
10586 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10587 	enum Key {
10588 		Escape = 0xff1b, ///
10589 		F1 = 0xffbe, ///
10590 		F2 = 0xffbf, ///
10591 		F3 = 0xffc0, ///
10592 		F4 = 0xffc1, ///
10593 		F5 = 0xffc2, ///
10594 		F6 = 0xffc3, ///
10595 		F7 = 0xffc4, ///
10596 		F8 = 0xffc5, ///
10597 		F9 = 0xffc6, ///
10598 		F10 = 0xffc7, ///
10599 		F11 = 0xffc8, ///
10600 		F12 = 0xffc9, ///
10601 		PrintScreen = 0xff61, ///
10602 		ScrollLock = 0xff14, ///
10603 		Pause = 0xff13, ///
10604 		Grave = 0x60, /// The $(BACKTICK) ~ key
10605 		// number keys across the top of the keyboard
10606 		N1 = 0x31, /// Number key atop the keyboard
10607 		N2 = 0x32, ///
10608 		N3 = 0x33, ///
10609 		N4 = 0x34, ///
10610 		N5 = 0x35, ///
10611 		N6 = 0x36, ///
10612 		N7 = 0x37, ///
10613 		N8 = 0x38, ///
10614 		N9 = 0x39, ///
10615 		N0 = 0x30, ///
10616 		Dash = 0x2d, ///
10617 		Equals = 0x3d, ///
10618 		Backslash = 0x5c, /// The \ | key
10619 		Backspace = 0xff08, ///
10620 		Insert = 0xff63, ///
10621 		Home = 0xff50, ///
10622 		PageUp = 0xff55, ///
10623 		Delete = 0xffff, ///
10624 		End = 0xff57, ///
10625 		PageDown = 0xff56, ///
10626 		Up = 0xff52, ///
10627 		Down = 0xff54, ///
10628 		Left = 0xff51, ///
10629 		Right = 0xff53, ///
10630 
10631 		Tab = 0xff09, ///
10632 		Q = 0x71, ///
10633 		W = 0x77, ///
10634 		E = 0x65, ///
10635 		R = 0x72, ///
10636 		T = 0x74, ///
10637 		Y = 0x79, ///
10638 		U = 0x75, ///
10639 		I = 0x69, ///
10640 		O = 0x6f, ///
10641 		P = 0x70, ///
10642 		LeftBracket = 0x5b, /// the [ { key
10643 		RightBracket = 0x5d, /// the ] } key
10644 		CapsLock = 0xffe5, ///
10645 		A = 0x61, ///
10646 		S = 0x73, ///
10647 		D = 0x64, ///
10648 		F = 0x66, ///
10649 		G = 0x67, ///
10650 		H = 0x68, ///
10651 		J = 0x6a, ///
10652 		K = 0x6b, ///
10653 		L = 0x6c, ///
10654 		Semicolon = 0x3b, ///
10655 		Apostrophe = 0x27, ///
10656 		Enter = 0xff0d, ///
10657 		Shift = 0xffe1, ///
10658 		Z = 0x7a, ///
10659 		X = 0x78, ///
10660 		C = 0x63, ///
10661 		V = 0x76, ///
10662 		B = 0x62, ///
10663 		N = 0x6e, ///
10664 		M = 0x6d, ///
10665 		Comma = 0x2c, ///
10666 		Period = 0x2e, ///
10667 		Slash = 0x2f, /// the / ? key
10668 		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
10669 		Ctrl = 0xffe3, ///
10670 		Windows = 0xffeb, ///
10671 		Alt = 0xffe9, ///
10672 		Space = 0x20, ///
10673 		Alt_r = 0xffea, /// ditto of shift_r
10674 		Windows_r = 0xffec, ///
10675 		Menu = 0xff67, ///
10676 		Ctrl_r = 0xffe4, ///
10677 
10678 		NumLock = 0xff7f, ///
10679 		Divide = 0xffaf, /// The / key on the number pad
10680 		Multiply = 0xffaa, /// The * key on the number pad
10681 		Minus = 0xffad, /// The - key on the number pad
10682 		Plus = 0xffab, /// The + key on the number pad
10683 		PadEnter = 0xff8d, /// Numberpad enter key
10684 		Pad1 = 0xff9c, /// Numberpad keys
10685 		Pad2 = 0xff99, ///
10686 		Pad3 = 0xff9b, ///
10687 		Pad4 = 0xff96, ///
10688 		Pad5 = 0xff9d, ///
10689 		Pad6 = 0xff98, ///
10690 		Pad7 = 0xff95, ///
10691 		Pad8 = 0xff97, ///
10692 		Pad9 = 0xff9a, ///
10693 		Pad0 = 0xff9e, ///
10694 		PadDot = 0xff9f, ///
10695 	}
10696 } else version(Windows) {
10697 	// the character here is for en-us layouts and for illustration only
10698 	// if you actually want to get characters, wait for character events
10699 	// (the argument to your event handler is simply a dchar)
10700 	// those will be converted by the OS for the right locale.
10701 
10702 	enum Key {
10703 		Escape = 0x1b,
10704 		F1 = 0x70,
10705 		F2 = 0x71,
10706 		F3 = 0x72,
10707 		F4 = 0x73,
10708 		F5 = 0x74,
10709 		F6 = 0x75,
10710 		F7 = 0x76,
10711 		F8 = 0x77,
10712 		F9 = 0x78,
10713 		F10 = 0x79,
10714 		F11 = 0x7a,
10715 		F12 = 0x7b,
10716 		PrintScreen = 0x2c,
10717 		ScrollLock = 0x91,
10718 		Pause = 0x13,
10719 		Grave = 0xc0,
10720 		// number keys across the top of the keyboard
10721 		N1 = 0x31,
10722 		N2 = 0x32,
10723 		N3 = 0x33,
10724 		N4 = 0x34,
10725 		N5 = 0x35,
10726 		N6 = 0x36,
10727 		N7 = 0x37,
10728 		N8 = 0x38,
10729 		N9 = 0x39,
10730 		N0 = 0x30,
10731 		Dash = 0xbd,
10732 		Equals = 0xbb,
10733 		Backslash = 0xdc,
10734 		Backspace = 0x08,
10735 		Insert = 0x2d,
10736 		Home = 0x24,
10737 		PageUp = 0x21,
10738 		Delete = 0x2e,
10739 		End = 0x23,
10740 		PageDown = 0x22,
10741 		Up = 0x26,
10742 		Down = 0x28,
10743 		Left = 0x25,
10744 		Right = 0x27,
10745 
10746 		Tab = 0x09,
10747 		Q = 0x51,
10748 		W = 0x57,
10749 		E = 0x45,
10750 		R = 0x52,
10751 		T = 0x54,
10752 		Y = 0x59,
10753 		U = 0x55,
10754 		I = 0x49,
10755 		O = 0x4f,
10756 		P = 0x50,
10757 		LeftBracket = 0xdb,
10758 		RightBracket = 0xdd,
10759 		CapsLock = 0x14,
10760 		A = 0x41,
10761 		S = 0x53,
10762 		D = 0x44,
10763 		F = 0x46,
10764 		G = 0x47,
10765 		H = 0x48,
10766 		J = 0x4a,
10767 		K = 0x4b,
10768 		L = 0x4c,
10769 		Semicolon = 0xba,
10770 		Apostrophe = 0xde,
10771 		Enter = 0x0d,
10772 		Shift = 0x10,
10773 		Z = 0x5a,
10774 		X = 0x58,
10775 		C = 0x43,
10776 		V = 0x56,
10777 		B = 0x42,
10778 		N = 0x4e,
10779 		M = 0x4d,
10780 		Comma = 0xbc,
10781 		Period = 0xbe,
10782 		Slash = 0xbf,
10783 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10784 		Ctrl = 0x11,
10785 		Windows = 0x5b,
10786 		Alt = -5, // FIXME
10787 		Space = 0x20,
10788 		Alt_r = 0xffea, // ditto of shift_r
10789 		Windows_r = 0x5c, // ditto of shift_r
10790 		Menu = 0x5d,
10791 		Ctrl_r = 0xa3, // ditto of shift_r
10792 
10793 		NumLock = 0x90,
10794 		Divide = 0x6f,
10795 		Multiply = 0x6a,
10796 		Minus = 0x6d,
10797 		Plus = 0x6b,
10798 		PadEnter = -8, // FIXME
10799 		Pad1 = 0x61,
10800 		Pad2 = 0x62,
10801 		Pad3 = 0x63,
10802 		Pad4 = 0x64,
10803 		Pad5 = 0x65,
10804 		Pad6 = 0x66,
10805 		Pad7 = 0x67,
10806 		Pad8 = 0x68,
10807 		Pad9 = 0x69,
10808 		Pad0 = 0x60,
10809 		PadDot = 0x6e,
10810 	}
10811 
10812 	// I'm keeping this around for reference purposes
10813 	// ideally all these buttons will be listed for all platforms,
10814 	// but now now I'm just focusing on my US keyboard
10815 	version(none)
10816 	enum Key {
10817 		LBUTTON = 0x01,
10818 		RBUTTON = 0x02,
10819 		CANCEL = 0x03,
10820 		MBUTTON = 0x04,
10821 		//static if (_WIN32_WINNT > =  0x500) {
10822 		XBUTTON1 = 0x05,
10823 		XBUTTON2 = 0x06,
10824 		//}
10825 		BACK = 0x08,
10826 		TAB = 0x09,
10827 		CLEAR = 0x0C,
10828 		RETURN = 0x0D,
10829 		SHIFT = 0x10,
10830 		CONTROL = 0x11,
10831 		MENU = 0x12,
10832 		PAUSE = 0x13,
10833 		CAPITAL = 0x14,
10834 		KANA = 0x15,
10835 		HANGEUL = 0x15,
10836 		HANGUL = 0x15,
10837 		JUNJA = 0x17,
10838 		FINAL = 0x18,
10839 		HANJA = 0x19,
10840 		KANJI = 0x19,
10841 		ESCAPE = 0x1B,
10842 		CONVERT = 0x1C,
10843 		NONCONVERT = 0x1D,
10844 		ACCEPT = 0x1E,
10845 		MODECHANGE = 0x1F,
10846 		SPACE = 0x20,
10847 		PRIOR = 0x21,
10848 		NEXT = 0x22,
10849 		END = 0x23,
10850 		HOME = 0x24,
10851 		LEFT = 0x25,
10852 		UP = 0x26,
10853 		RIGHT = 0x27,
10854 		DOWN = 0x28,
10855 		SELECT = 0x29,
10856 		PRINT = 0x2A,
10857 		EXECUTE = 0x2B,
10858 		SNAPSHOT = 0x2C,
10859 		INSERT = 0x2D,
10860 		DELETE = 0x2E,
10861 		HELP = 0x2F,
10862 		LWIN = 0x5B,
10863 		RWIN = 0x5C,
10864 		APPS = 0x5D,
10865 		SLEEP = 0x5F,
10866 		NUMPAD0 = 0x60,
10867 		NUMPAD1 = 0x61,
10868 		NUMPAD2 = 0x62,
10869 		NUMPAD3 = 0x63,
10870 		NUMPAD4 = 0x64,
10871 		NUMPAD5 = 0x65,
10872 		NUMPAD6 = 0x66,
10873 		NUMPAD7 = 0x67,
10874 		NUMPAD8 = 0x68,
10875 		NUMPAD9 = 0x69,
10876 		MULTIPLY = 0x6A,
10877 		ADD = 0x6B,
10878 		SEPARATOR = 0x6C,
10879 		SUBTRACT = 0x6D,
10880 		DECIMAL = 0x6E,
10881 		DIVIDE = 0x6F,
10882 		F1 = 0x70,
10883 		F2 = 0x71,
10884 		F3 = 0x72,
10885 		F4 = 0x73,
10886 		F5 = 0x74,
10887 		F6 = 0x75,
10888 		F7 = 0x76,
10889 		F8 = 0x77,
10890 		F9 = 0x78,
10891 		F10 = 0x79,
10892 		F11 = 0x7A,
10893 		F12 = 0x7B,
10894 		F13 = 0x7C,
10895 		F14 = 0x7D,
10896 		F15 = 0x7E,
10897 		F16 = 0x7F,
10898 		F17 = 0x80,
10899 		F18 = 0x81,
10900 		F19 = 0x82,
10901 		F20 = 0x83,
10902 		F21 = 0x84,
10903 		F22 = 0x85,
10904 		F23 = 0x86,
10905 		F24 = 0x87,
10906 		NUMLOCK = 0x90,
10907 		SCROLL = 0x91,
10908 		LSHIFT = 0xA0,
10909 		RSHIFT = 0xA1,
10910 		LCONTROL = 0xA2,
10911 		RCONTROL = 0xA3,
10912 		LMENU = 0xA4,
10913 		RMENU = 0xA5,
10914 		//static if (_WIN32_WINNT > =  0x500) {
10915 		BROWSER_BACK = 0xA6,
10916 		BROWSER_FORWARD = 0xA7,
10917 		BROWSER_REFRESH = 0xA8,
10918 		BROWSER_STOP = 0xA9,
10919 		BROWSER_SEARCH = 0xAA,
10920 		BROWSER_FAVORITES = 0xAB,
10921 		BROWSER_HOME = 0xAC,
10922 		VOLUME_MUTE = 0xAD,
10923 		VOLUME_DOWN = 0xAE,
10924 		VOLUME_UP = 0xAF,
10925 		MEDIA_NEXT_TRACK = 0xB0,
10926 		MEDIA_PREV_TRACK = 0xB1,
10927 		MEDIA_STOP = 0xB2,
10928 		MEDIA_PLAY_PAUSE = 0xB3,
10929 		LAUNCH_MAIL = 0xB4,
10930 		LAUNCH_MEDIA_SELECT = 0xB5,
10931 		LAUNCH_APP1 = 0xB6,
10932 		LAUNCH_APP2 = 0xB7,
10933 		//}
10934 		OEM_1 = 0xBA,
10935 		//static if (_WIN32_WINNT > =  0x500) {
10936 		OEM_PLUS = 0xBB,
10937 		OEM_COMMA = 0xBC,
10938 		OEM_MINUS = 0xBD,
10939 		OEM_PERIOD = 0xBE,
10940 		//}
10941 		OEM_2 = 0xBF,
10942 		OEM_3 = 0xC0,
10943 		OEM_4 = 0xDB,
10944 		OEM_5 = 0xDC,
10945 		OEM_6 = 0xDD,
10946 		OEM_7 = 0xDE,
10947 		OEM_8 = 0xDF,
10948 		//static if (_WIN32_WINNT > =  0x500) {
10949 		OEM_102 = 0xE2,
10950 		//}
10951 		PROCESSKEY = 0xE5,
10952 		//static if (_WIN32_WINNT > =  0x500) {
10953 		PACKET = 0xE7,
10954 		//}
10955 		ATTN = 0xF6,
10956 		CRSEL = 0xF7,
10957 		EXSEL = 0xF8,
10958 		EREOF = 0xF9,
10959 		PLAY = 0xFA,
10960 		ZOOM = 0xFB,
10961 		NONAME = 0xFC,
10962 		PA1 = 0xFD,
10963 		OEM_CLEAR = 0xFE,
10964 	}
10965 
10966 } else version(OSXCocoa) {
10967 	enum Key {
10968 		Escape = 53,
10969 		F1 = 122,
10970 		F2 = 120,
10971 		F3 = 99,
10972 		F4 = 118,
10973 		F5 = 96,
10974 		F6 = 97,
10975 		F7 = 98,
10976 		F8 = 100,
10977 		F9 = 101,
10978 		F10 = 109,
10979 		F11 = 103,
10980 		F12 = 111,
10981 		PrintScreen = 105,
10982 		ScrollLock = 107,
10983 		Pause = 113,
10984 		Grave = 50,
10985 		// number keys across the top of the keyboard
10986 		N1 = 18,
10987 		N2 = 19,
10988 		N3 = 20,
10989 		N4 = 21,
10990 		N5 = 23,
10991 		N6 = 22,
10992 		N7 = 26,
10993 		N8 = 28,
10994 		N9 = 25,
10995 		N0 = 29,
10996 		Dash = 27,
10997 		Equals = 24,
10998 		Backslash = 42,
10999 		Backspace = 51,
11000 		Insert = 114,
11001 		Home = 115,
11002 		PageUp = 116,
11003 		Delete = 117,
11004 		End = 119,
11005 		PageDown = 121,
11006 		Up = 126,
11007 		Down = 125,
11008 		Left = 123,
11009 		Right = 124,
11010 
11011 		Tab = 48,
11012 		Q = 12,
11013 		W = 13,
11014 		E = 14,
11015 		R = 15,
11016 		T = 17,
11017 		Y = 16,
11018 		U = 32,
11019 		I = 34,
11020 		O = 31,
11021 		P = 35,
11022 		LeftBracket = 33,
11023 		RightBracket = 30,
11024 		CapsLock = 57,
11025 		A = 0,
11026 		S = 1,
11027 		D = 2,
11028 		F = 3,
11029 		G = 5,
11030 		H = 4,
11031 		J = 38,
11032 		K = 40,
11033 		L = 37,
11034 		Semicolon = 41,
11035 		Apostrophe = 39,
11036 		Enter = 36,
11037 		Shift = 56,
11038 		Z = 6,
11039 		X = 7,
11040 		C = 8,
11041 		V = 9,
11042 		B = 11,
11043 		N = 45,
11044 		M = 46,
11045 		Comma = 43,
11046 		Period = 47,
11047 		Slash = 44,
11048 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11049 		Ctrl = 59,
11050 		Windows = 55,
11051 		Alt = 58,
11052 		Space = 49,
11053 		Alt_r = -3, // ditto of shift_r
11054 		Windows_r = -2,
11055 		Menu = 110,
11056 		Ctrl_r = -1,
11057 
11058 		NumLock = 1,
11059 		Divide = 75,
11060 		Multiply = 67,
11061 		Minus = 78,
11062 		Plus = 69,
11063 		PadEnter = 76,
11064 		Pad1 = 83,
11065 		Pad2 = 84,
11066 		Pad3 = 85,
11067 		Pad4 = 86,
11068 		Pad5 = 87,
11069 		Pad6 = 88,
11070 		Pad7 = 89,
11071 		Pad8 = 91,
11072 		Pad9 = 92,
11073 		Pad0 = 82,
11074 		PadDot = 65,
11075 	}
11076 
11077 }
11078 
11079 /* Additional utilities */
11080 
11081 
11082 Color fromHsl(real h, real s, real l) {
11083 	return arsd.color.fromHsl([h,s,l]);
11084 }
11085 
11086 
11087 
11088 /* ********** What follows is the system-specific implementations *********/
11089 version(Windows) {
11090 
11091 
11092 	// helpers for making HICONs from MemoryImages
11093 	class WindowsIcon {
11094 		struct Win32Icon {
11095 			align(1):
11096 			uint biSize;
11097 			int biWidth;
11098 			int biHeight;
11099 			ushort biPlanes;
11100 			ushort biBitCount;
11101 			uint biCompression;
11102 			uint biSizeImage;
11103 			int biXPelsPerMeter;
11104 			int biYPelsPerMeter;
11105 			uint biClrUsed;
11106 			uint biClrImportant;
11107 			// RGBQUAD[colorCount] biColors;
11108 			/* Pixels:
11109 			Uint8 pixels[]
11110 			*/
11111 			/* Mask:
11112 			Uint8 mask[]
11113 			*/
11114 		}
11115 
11116 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11117 
11118 			assert(mi.width <= 256, "image too wide");
11119 			assert(mi.height <= 256, "image too tall");
11120 			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
11121 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11122 
11123 			int icon_plen = mi.width * mi.height * 4;
11124 			int icon_mlen = mi.width * mi.height / 8;
11125 
11126 			int colorCount = 0;
11127 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11128 
11129 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11130 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11131 
11132 			auto data = memory[Win32Icon.sizeof .. $];
11133 
11134 			width = mi.width;
11135 			height = mi.height;
11136 
11137 			auto trueColorImage = mi.getAsTrueColorImage();
11138 
11139 			icon_win32.biSize = 40;
11140 			icon_win32.biWidth = mi.width;
11141 			icon_win32.biHeight = mi.height*2;
11142 			icon_win32.biPlanes = 1;
11143 			icon_win32.biBitCount = 32;
11144 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11145 
11146 			int offset = 0;
11147 			int andOff = icon_plen * 8; // the and offset is in bits
11148 
11149 			// leaving the and mask as the default 0 so the rgba alpha blend
11150 			// does its thing instead
11151 			for(int y = height - 1; y >= 0; y--) {
11152 				int off2 = y * width * 4;
11153 				foreach(x; 0 .. width) {
11154 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11155 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11156 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11157 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11158 
11159 					offset += 4;
11160 					off2 += 4;
11161 				}
11162 			}
11163 
11164 			return memory;
11165 		}
11166 
11167 		this(MemoryImage mi) {
11168 			int icon_len, width, height;
11169 
11170 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11171 
11172 			/*
11173 			PNG* png = readPnpngData);
11174 			PNGHeader pngh = getHeader(png);
11175 			void* icon_win32;
11176 			if(pngh.depth == 4) {
11177 				auto i = new Win32Icon!(16);
11178 				i.fromPNG(png, pngh, icon_len, width, height);
11179 				icon_win32 = i;
11180 			}
11181 			else if(pngh.depth == 8) {
11182 				auto i = new Win32Icon!(256);
11183 				i.fromPNG(png, pngh, icon_len, width, height);
11184 				icon_win32 = i;
11185 			} else assert(0);
11186 			*/
11187 
11188 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11189 
11190 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11191 		}
11192 
11193 		~this() {
11194 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11195 			DestroyIcon(hIcon);
11196 		}
11197 
11198 		HICON hIcon;
11199 	}
11200 
11201 
11202 
11203 
11204 
11205 
11206 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11207 	alias HWND NativeWindowHandle;
11208 
11209 	extern(Windows)
11210 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11211 		try {
11212 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11213 				// it returns zero if the message is handled, so we won't do anything more there
11214 				// do I like that though?
11215 				int mustReturn;
11216 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11217 				if(mustReturn)
11218 					return ret;
11219 			}
11220 
11221 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11222 				if(window.getNativeEventHandler !is null) {
11223 					int mustReturn;
11224 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11225 					if(mustReturn)
11226 						return ret;
11227 				}
11228 				if(auto w = cast(SimpleWindow) (*window))
11229 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11230 				else
11231 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11232 			} else {
11233 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11234 			}
11235 		} catch (Exception e) {
11236 			try {
11237 				sdpy_abort(e);
11238 				return 0;
11239 			} catch(Exception e) { assert(0); }
11240 		}
11241 	}
11242 
11243 	void sdpy_abort(Throwable e) nothrow {
11244 		try
11245 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11246 		catch(Exception e)
11247 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11248 		ExitProcess(1);
11249 	}
11250 
11251 	mixin template NativeScreenPainterImplementation() {
11252 		HDC hdc;
11253 		HWND hwnd;
11254 		//HDC windowHdc;
11255 		HBITMAP oldBmp;
11256 
11257 		void create(PaintingHandle window) {
11258 			hwnd = window;
11259 
11260 			if(auto sw = cast(SimpleWindow) this.window) {
11261 				// drawing on a window, double buffer
11262 				auto windowHdc = GetDC(hwnd);
11263 
11264 				auto buffer = sw.impl.buffer;
11265 				if(buffer is null) {
11266 					hdc = windowHdc;
11267 					windowDc = true;
11268 				} else {
11269 					hdc = CreateCompatibleDC(windowHdc);
11270 
11271 					ReleaseDC(hwnd, windowHdc);
11272 
11273 					oldBmp = SelectObject(hdc, buffer);
11274 				}
11275 			} else {
11276 				// drawing on something else, draw directly
11277 				hdc = CreateCompatibleDC(null);
11278 				SelectObject(hdc, window);
11279 			}
11280 
11281 			// X doesn't draw a text background, so neither should we
11282 			SetBkMode(hdc, TRANSPARENT);
11283 
11284 			ensureDefaultFontLoaded();
11285 
11286 			if(defaultGuiFont) {
11287 				SelectObject(hdc, defaultGuiFont);
11288 				// DeleteObject(defaultGuiFont);
11289 			}
11290 		}
11291 
11292 		static HFONT defaultGuiFont;
11293 		static void ensureDefaultFontLoaded() {
11294 			static bool triedDefaultGuiFont = false;
11295 			if(!triedDefaultGuiFont) {
11296 				NONCLIENTMETRICS params;
11297 				params.cbSize = params.sizeof;
11298 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11299 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11300 				}
11301 				triedDefaultGuiFont = true;
11302 			}
11303 		}
11304 
11305 		private OperatingSystemFont _activeFont;
11306 
11307 		void setFont(OperatingSystemFont font) {
11308 			_activeFont = font;
11309 			if(font && font.font) {
11310 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11311 					// error... how to handle tho?
11312 				} else {
11313 
11314 				}
11315 			}
11316 			else if(defaultGuiFont)
11317 				SelectObject(hdc, defaultGuiFont);
11318 		}
11319 
11320 		arsd.color.Rectangle _clipRectangle;
11321 
11322 		void setClipRectangle(int x, int y, int width, int height) {
11323 			auto old = _clipRectangle;
11324 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11325 			if(old == _clipRectangle)
11326 				return;
11327 
11328 			if(width == 0 || height == 0) {
11329 				SelectClipRgn(hdc, null);
11330 			} else {
11331 				auto region = CreateRectRgn(x, y, x + width, y + height);
11332 				SelectClipRgn(hdc, region);
11333 				DeleteObject(region);
11334 			}
11335 		}
11336 
11337 
11338 		// just because we can on Windows...
11339 		//void create(Image image);
11340 
11341 		void invalidateRect(Rectangle invalidRect) {
11342 			RECT rect;
11343 			rect.left = invalidRect.left;
11344 			rect.right = invalidRect.right;
11345 			rect.top = invalidRect.top;
11346 			rect.bottom = invalidRect.bottom;
11347 			InvalidateRect(hwnd, &rect, false);
11348 		}
11349 		bool manualInvalidations;
11350 
11351 		void dispose() {
11352 			// FIXME: this.window.width/height is probably wrong
11353 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11354 			// ReleaseDC(hwnd, windowHdc);
11355 
11356 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11357 			if(cast(SimpleWindow) this.window) {
11358 				if(!manualInvalidations)
11359 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11360 			}
11361 
11362 			if(originalPen !is null)
11363 				SelectObject(hdc, originalPen);
11364 			if(currentPen !is null)
11365 				DeleteObject(currentPen);
11366 			if(originalBrush !is null)
11367 				SelectObject(hdc, originalBrush);
11368 			if(currentBrush !is null)
11369 				DeleteObject(currentBrush);
11370 
11371 			SelectObject(hdc, oldBmp);
11372 
11373 			if(windowDc)
11374 				ReleaseDC(hwnd, hdc);
11375 			else
11376 				DeleteDC(hdc);
11377 
11378 			if(window.paintingFinishedDg !is null)
11379 				window.paintingFinishedDg()();
11380 		}
11381 
11382 		bool windowDc;
11383 		HPEN originalPen;
11384 		HPEN currentPen;
11385 
11386 		Pen _activePen;
11387 
11388 		Color _outlineColor;
11389 
11390 		@property void pen(Pen p) {
11391 			_activePen = p;
11392 			_outlineColor = p.color;
11393 
11394 			HPEN pen;
11395 			if(p.color.a == 0) {
11396 				pen = GetStockObject(NULL_PEN);
11397 			} else {
11398 				int style = PS_SOLID;
11399 				final switch(p.style) {
11400 					case Pen.Style.Solid:
11401 						style = PS_SOLID;
11402 					break;
11403 					case Pen.Style.Dashed:
11404 						style = PS_DASH;
11405 					break;
11406 					case Pen.Style.Dotted:
11407 						style = PS_DOT;
11408 					break;
11409 				}
11410 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11411 			}
11412 			auto orig = SelectObject(hdc, pen);
11413 			if(originalPen is null)
11414 				originalPen = orig;
11415 
11416 			if(currentPen !is null)
11417 				DeleteObject(currentPen);
11418 
11419 			currentPen = pen;
11420 
11421 			// the outline is like a foreground since it's done that way on X
11422 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11423 
11424 		}
11425 
11426 		@property void rasterOp(RasterOp op) {
11427 			int mode;
11428 			final switch(op) {
11429 				case RasterOp.normal:
11430 					mode = R2_COPYPEN;
11431 				break;
11432 				case RasterOp.xor:
11433 					mode = R2_XORPEN;
11434 				break;
11435 			}
11436 			SetROP2(hdc, mode);
11437 		}
11438 
11439 		HBRUSH originalBrush;
11440 		HBRUSH currentBrush;
11441 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11442 		@property void fillColor(Color c) {
11443 			if(c == _fillColor)
11444 				return;
11445 			_fillColor = c;
11446 			HBRUSH brush;
11447 			if(c.a == 0) {
11448 				brush = GetStockObject(HOLLOW_BRUSH);
11449 			} else {
11450 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11451 			}
11452 			auto orig = SelectObject(hdc, brush);
11453 			if(originalBrush is null)
11454 				originalBrush = orig;
11455 
11456 			if(currentBrush !is null)
11457 				DeleteObject(currentBrush);
11458 
11459 			currentBrush = brush;
11460 
11461 			// background color is NOT set because X doesn't draw text backgrounds
11462 			//   SetBkColor(hdc, RGB(255, 255, 255));
11463 		}
11464 
11465 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11466 			BITMAP bm;
11467 
11468 			HDC hdcMem = CreateCompatibleDC(hdc);
11469 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11470 
11471 			GetObject(i.handle, bm.sizeof, &bm);
11472 
11473 			// or should I AlphaBlend!??!?!
11474 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11475 
11476 			SelectObject(hdcMem, hbmOld);
11477 			DeleteDC(hdcMem);
11478 		}
11479 
11480 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11481 			BITMAP bm;
11482 
11483 			HDC hdcMem = CreateCompatibleDC(hdc);
11484 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11485 
11486 			GetObject(s.handle, bm.sizeof, &bm);
11487 
11488 			version(CRuntime_DigitalMars) goto noalpha;
11489 
11490 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11491 			if(s.enableAlpha) {
11492 				auto dw = w ? w : bm.bmWidth;
11493 				auto dh = h ? h : bm.bmHeight;
11494 				BLENDFUNCTION bf;
11495 				bf.BlendOp = AC_SRC_OVER;
11496 				bf.SourceConstantAlpha = 255;
11497 				bf.AlphaFormat = AC_SRC_ALPHA;
11498 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11499 			} else {
11500 				noalpha:
11501 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11502 			}
11503 
11504 			SelectObject(hdcMem, hbmOld);
11505 			DeleteDC(hdcMem);
11506 		}
11507 
11508 		Size textSize(scope const(char)[] text) {
11509 			bool dummyX;
11510 			if(text.length == 0) {
11511 				text = " ";
11512 				dummyX = true;
11513 			}
11514 			RECT rect;
11515 			WCharzBuffer buffer = WCharzBuffer(text);
11516 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11517 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11518 		}
11519 
11520 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11521 			if(text.length && text[$-1] == '\n')
11522 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11523 			if(text.length && text[$-1] == '\r')
11524 				text = text[0 .. $-1];
11525 
11526 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11527 			if(x2 == 0 && y2 == 0) {
11528 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11529 			} else {
11530 				RECT rect;
11531 				rect.left = x;
11532 				rect.top = y;
11533 				rect.right = x2;
11534 				rect.bottom = y2;
11535 
11536 				uint mode = DT_LEFT;
11537 				if(alignment & TextAlignment.Right)
11538 					mode = DT_RIGHT;
11539 				else if(alignment & TextAlignment.Center)
11540 					mode = DT_CENTER;
11541 
11542 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11543 				if(alignment & TextAlignment.VerticalCenter)
11544 					mode |= DT_VCENTER | DT_SINGLELINE;
11545 
11546 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11547 			}
11548 
11549 			/*
11550 			uint mode;
11551 
11552 			if(alignment & TextAlignment.Center)
11553 				mode = TA_CENTER;
11554 
11555 			SetTextAlign(hdc, mode);
11556 			*/
11557 		}
11558 
11559 		int fontHeight() {
11560 			TEXTMETRIC metric;
11561 			if(GetTextMetricsW(hdc, &metric)) {
11562 				return metric.tmHeight;
11563 			}
11564 
11565 			return 16; // idk just guessing here, maybe we should throw
11566 		}
11567 
11568 		void drawPixel(int x, int y) {
11569 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11570 		}
11571 
11572 		// The basic shapes, outlined
11573 
11574 		void drawLine(int x1, int y1, int x2, int y2) {
11575 			MoveToEx(hdc, x1, y1, null);
11576 			LineTo(hdc, x2, y2);
11577 		}
11578 
11579 		void drawRectangle(int x, int y, int width, int height) {
11580 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11581 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11582 		}
11583 
11584 		/// Arguments are the points of the bounding rectangle
11585 		void drawEllipse(int x1, int y1, int x2, int y2) {
11586 			Ellipse(hdc, x1, y1, x2, y2);
11587 		}
11588 
11589 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11590 			if((start % (360*64)) == (finish % (360*64)))
11591 				drawEllipse(x1, y1, x1 + width, y1 + height);
11592 			else {
11593 				import core.stdc.math;
11594 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11595 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11596 
11597 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11598 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11599 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11600 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11601 
11602 				if(_activePen.color.a)
11603 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11604 				if(_fillColor.a)
11605 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11606 			}
11607 		}
11608 
11609 		void drawPolygon(Point[] vertexes) {
11610 			POINT[] points;
11611 			points.length = vertexes.length;
11612 
11613 			foreach(i, p; vertexes) {
11614 				points[i].x = p.x;
11615 				points[i].y = p.y;
11616 			}
11617 
11618 			Polygon(hdc, points.ptr, cast(int) points.length);
11619 		}
11620 	}
11621 
11622 
11623 	// Mix this into the SimpleWindow class
11624 	mixin template NativeSimpleWindowImplementation() {
11625 		int curHidden = 0; // counter
11626 		__gshared static bool[string] knownWinClasses;
11627 		static bool altPressed = false;
11628 
11629 		HANDLE oldCursor;
11630 
11631 		void hideCursor () {
11632 			if(curHidden == 0)
11633 				oldCursor = SetCursor(null);
11634 			++curHidden;
11635 		}
11636 
11637 		void showCursor () {
11638 			--curHidden;
11639 			if(curHidden == 0) {
11640 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11641 			}
11642 		}
11643 
11644 
11645 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11646 
11647 		void setMinSize (int minwidth, int minheight) {
11648 			minWidth = minwidth;
11649 			minHeight = minheight;
11650 		}
11651 		void setMaxSize (int maxwidth, int maxheight) {
11652 			maxWidth = maxwidth;
11653 			maxHeight = maxheight;
11654 		}
11655 
11656 		// FIXME i'm not sure that Windows has this functionality
11657 		// though it is nonessential anyway.
11658 		void setResizeGranularity (int granx, int grany) {}
11659 
11660 		ScreenPainter getPainter(bool manualInvalidations) {
11661 			return ScreenPainter(this, hwnd, manualInvalidations);
11662 		}
11663 
11664 		HBITMAP buffer;
11665 
11666 		void setTitle(string title) {
11667 			WCharzBuffer bfr = WCharzBuffer(title);
11668 			SetWindowTextW(hwnd, bfr.ptr);
11669 		}
11670 
11671 		string getTitle() {
11672 			auto len = GetWindowTextLengthW(hwnd);
11673 			if (!len)
11674 				return null;
11675 			wchar[256] tmpBuffer;
11676 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11677 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11678 			auto str = buffer[0 .. len2];
11679 			return makeUtf8StringFromWindowsString(str);
11680 		}
11681 
11682 		void move(int x, int y) {
11683 			RECT rect;
11684 			GetWindowRect(hwnd, &rect);
11685 			// move it while maintaining the same size...
11686 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11687 		}
11688 
11689 		void resize(int w, int h) {
11690 			RECT rect;
11691 			GetWindowRect(hwnd, &rect);
11692 
11693 			RECT client;
11694 			GetClientRect(hwnd, &client);
11695 
11696 			rect.right = rect.right - client.right + w;
11697 			rect.bottom = rect.bottom - client.bottom + h;
11698 
11699 			// same position, new size for the client rectangle
11700 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11701 
11702 			updateOpenglViewportIfNeeded(w, h);
11703 		}
11704 
11705 		void moveResize (int x, int y, int w, int h) {
11706 			// what's given is the client rectangle, we need to adjust
11707 
11708 			RECT rect;
11709 			rect.left = x;
11710 			rect.top = y;
11711 			rect.right = w + x;
11712 			rect.bottom = h + y;
11713 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11714 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11715 
11716 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11717 			updateOpenglViewportIfNeeded(w, h);
11718 			if (windowResized !is null) windowResized(w, h);
11719 		}
11720 
11721 		version(without_opengl) {} else {
11722 			HGLRC ghRC;
11723 			HDC ghDC;
11724 		}
11725 
11726 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11727 			string cnamec;
11728 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11729 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11730 				cnamec = "DSimpleWindow";
11731 			} else {
11732 				cnamec = sdpyWindowClass;
11733 			}
11734 
11735 			WCharzBuffer cn = WCharzBuffer(cnamec);
11736 
11737 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11738 
11739 			if(cnamec !in knownWinClasses) {
11740 				WNDCLASSEX wc;
11741 
11742 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11743 				// to the object. Maybe.
11744 				wc.cbSize = wc.sizeof;
11745 				wc.cbClsExtra = 0;
11746 				wc.cbWndExtra = 0;
11747 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11748 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11749 				wc.hIcon = LoadIcon(hInstance, null);
11750 				wc.hInstance = hInstance;
11751 				wc.lpfnWndProc = &WndProc;
11752 				wc.lpszClassName = cn.ptr;
11753 				wc.hIconSm = null;
11754 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11755 				if(!RegisterClassExW(&wc))
11756 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11757 				knownWinClasses[cnamec] = true;
11758 			}
11759 
11760 			int style;
11761 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11762 
11763 			// FIXME: windowType and customizationFlags
11764 			final switch(windowType) {
11765 				case WindowTypes.normal:
11766 					if(resizability == Resizability.fixedSize) {
11767 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11768 					} else {
11769 						style = WS_OVERLAPPEDWINDOW;
11770 					}
11771 				break;
11772 				case WindowTypes.undecorated:
11773 					style = WS_POPUP | WS_SYSMENU;
11774 				break;
11775 				case WindowTypes.eventOnly:
11776 					_hidden = true;
11777 				break;
11778 				case WindowTypes.dropdownMenu:
11779 				case WindowTypes.popupMenu:
11780 				case WindowTypes.notification:
11781 					style = WS_POPUP;
11782 					flags |= WS_EX_NOACTIVATE;
11783 				break;
11784 				case WindowTypes.nestedChild:
11785 					style = WS_CHILD;
11786 				break;
11787 				case WindowTypes.minimallyWrapped:
11788 					assert(0, "construct minimally wrapped through the other ctor overlad");
11789 			}
11790 
11791 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11792 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11793 
11794 			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
11795 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11796 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11797 
11798 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11799 				setOpacity(255);
11800 
11801 			SimpleWindow.nativeMapping[hwnd] = this;
11802 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11803 
11804 			if(windowType == WindowTypes.eventOnly)
11805 				return;
11806 
11807 			HDC hdc = GetDC(hwnd);
11808 
11809 
11810 			version(without_opengl) {}
11811 			else {
11812 				if(opengl == OpenGlOptions.yes) {
11813 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11814 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11815 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11816 					ghDC = hdc;
11817 					PIXELFORMATDESCRIPTOR pfd;
11818 
11819 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11820 					pfd.nVersion = 1;
11821 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11822 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11823 					pfd.iPixelType = PFD_TYPE_RGBA;
11824 					pfd.cColorBits = 24;
11825 					pfd.cDepthBits = 24;
11826 					pfd.cAccumBits = 0;
11827 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11828 
11829 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11830 
11831 					if (pixelformat == 0)
11832 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11833 
11834 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11835 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11836 
11837 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11838 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11839 						// so we will create fake context to get that stupid address
11840 						auto tmpcc = wglCreateContext(ghDC);
11841 						if (tmpcc !is null) {
11842 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11843 							wglMakeCurrent(ghDC, tmpcc);
11844 							wglInitOtherFunctions();
11845 						}
11846 					}
11847 
11848 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11849 						int[9] contextAttribs = [
11850 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11851 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11852 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11853 							// for modern context, set "forward compatibility" flag too
11854 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11855 							0/*None*/,
11856 						];
11857 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11858 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11859 							// activate fallback mode
11860 							// 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;
11861 							ghRC = wglCreateContext(ghDC);
11862 						}
11863 						if (ghRC is null)
11864 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11865 					} else {
11866 						// try to do at least something
11867 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11868 							sdpyOpenGLContextVersion = 0;
11869 							ghRC = wglCreateContext(ghDC);
11870 						}
11871 						if (ghRC is null)
11872 							throw new WindowsApiException("wglCreateContext", GetLastError());
11873 					}
11874 				}
11875 			}
11876 
11877 			if(opengl == OpenGlOptions.no) {
11878 				buffer = CreateCompatibleBitmap(hdc, width, height);
11879 
11880 				auto hdcBmp = CreateCompatibleDC(hdc);
11881 				// make sure it's filled with a blank slate
11882 				auto oldBmp = SelectObject(hdcBmp, buffer);
11883 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11884 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11885 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11886 				SelectObject(hdcBmp, oldBmp);
11887 				SelectObject(hdcBmp, oldBrush);
11888 				SelectObject(hdcBmp, oldPen);
11889 				DeleteDC(hdcBmp);
11890 
11891 				bmpWidth = width;
11892 				bmpHeight = height;
11893 
11894 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11895 			}
11896 
11897 			// We want the window's client area to match the image size
11898 			RECT rcClient, rcWindow;
11899 			POINT ptDiff;
11900 			GetClientRect(hwnd, &rcClient);
11901 			GetWindowRect(hwnd, &rcWindow);
11902 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11903 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11904 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11905 
11906 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11907 				ShowWindow(hwnd, SW_SHOWNORMAL);
11908 			} else {
11909 				_hidden = true;
11910 			}
11911 			this._visibleForTheFirstTimeCalled = false; // hack!
11912 		}
11913 
11914 
11915 		void dispose() {
11916 			if(buffer)
11917 				DeleteObject(buffer);
11918 		}
11919 
11920 		void closeWindow() {
11921 			if(ghRC) {
11922 				wglDeleteContext(ghRC);
11923 				ghRC = null;
11924 			}
11925 			DestroyWindow(hwnd);
11926 		}
11927 
11928 		bool setOpacity(ubyte alpha) {
11929 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11930 		}
11931 
11932 		HANDLE currentCursor;
11933 
11934 		// returns zero if it recognized the event
11935 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11936 			MouseEvent mouse;
11937 
11938 			void mouseEvent(bool isScreen, ulong mods) {
11939 				auto x = LOWORD(lParam);
11940 				auto y = HIWORD(lParam);
11941 				if(isScreen) {
11942 					POINT p;
11943 					p.x = x;
11944 					p.y = y;
11945 					ScreenToClient(hwnd, &p);
11946 					x = cast(ushort) p.x;
11947 					y = cast(ushort) p.y;
11948 				}
11949 
11950 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11951 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11952 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11953 				}
11954 
11955 				mouse.x = x + offsetX;
11956 				mouse.y = y + offsetY;
11957 
11958 				wind.mdx(mouse);
11959 				mouse.modifierState = cast(int) mods;
11960 				mouse.window = wind;
11961 
11962 				if(wind.handleMouseEvent)
11963 					wind.handleMouseEvent(mouse);
11964 			}
11965 
11966 			switch(msg) {
11967 				case WM_GETMINMAXINFO:
11968 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11969 
11970 					if(wind.minWidth > 0) {
11971 						RECT rect;
11972 						rect.left = 100;
11973 						rect.top = 100;
11974 						rect.right = wind.minWidth + 100;
11975 						rect.bottom = wind.minHeight + 100;
11976 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11977 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11978 
11979 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11980 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11981 					}
11982 
11983 					if(wind.maxWidth < int.max) {
11984 						RECT rect;
11985 						rect.left = 100;
11986 						rect.top = 100;
11987 						rect.right = wind.maxWidth + 100;
11988 						rect.bottom = wind.maxHeight + 100;
11989 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11990 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11991 
11992 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11993 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11994 					}
11995 				break;
11996 				case WM_CHAR:
11997 					wchar c = cast(wchar) wParam;
11998 					if(wind.handleCharEvent)
11999 						wind.handleCharEvent(cast(dchar) c);
12000 				break;
12001 				  case WM_SETFOCUS:
12002 				  case WM_KILLFOCUS:
12003 					wind._focused = (msg == WM_SETFOCUS);
12004 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12005 					if(wind.onFocusChange)
12006 						wind.onFocusChange(msg == WM_SETFOCUS);
12007 				  break;
12008 
12009 				case WM_SYSKEYDOWN:
12010 					goto case;
12011 				case WM_SYSKEYUP:
12012 					if(lParam & (1 << 29)) {
12013 						goto case;
12014 					} else {
12015 						// no window has keyboard focus
12016 						goto default;
12017 					}
12018 				case WM_KEYDOWN:
12019 				case WM_KEYUP:
12020 					KeyEvent ev;
12021 					ev.key = cast(Key) wParam;
12022 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12023 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12024 
12025 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12026 
12027 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12028 						ev.modifierState |= ModifierState.shift;
12029 					//k8: this doesn't work; thanks for nothing, windows
12030 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12031 						ev.modifierState |= ModifierState.alt;*/
12032 					// this never seems to actually be set
12033 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12034 
12035 					if (wParam == 0x12) {
12036 						altPressed = (msg == WM_SYSKEYDOWN);
12037 					}
12038 
12039 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12040 						altPressed = false;
12041 					}
12042 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12043 
12044 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12045 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12046 						ev.modifierState |= ModifierState.ctrl;
12047 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12048 						ev.modifierState |= ModifierState.windows;
12049 					if(GetKeyState(Key.NumLock))
12050 						ev.modifierState |= ModifierState.numLock;
12051 					if(GetKeyState(Key.CapsLock))
12052 						ev.modifierState |= ModifierState.capsLock;
12053 
12054 					/+
12055 					// we always want to send the character too, so let's convert it
12056 					ubyte[256] state;
12057 					wchar[16] buffer;
12058 					GetKeyboardState(state.ptr);
12059 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12060 
12061 					foreach(dchar d; buffer) {
12062 						ev.character = d;
12063 						break;
12064 					}
12065 					+/
12066 
12067 					ev.window = wind;
12068 					if(wind.handleKeyEvent)
12069 						wind.handleKeyEvent(ev);
12070 				break;
12071 				case 0x020a /*WM_MOUSEWHEEL*/:
12072 					// send click
12073 					mouse.type = cast(MouseEventType) 1;
12074 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12075 					mouseEvent(true, LOWORD(wParam));
12076 
12077 					// also send release
12078 					mouse.type = cast(MouseEventType) 2;
12079 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12080 					mouseEvent(true, LOWORD(wParam));
12081 				break;
12082 				case WM_MOUSEMOVE:
12083 					mouse.type = cast(MouseEventType) 0;
12084 					mouseEvent(false, wParam);
12085 				break;
12086 				case WM_LBUTTONDOWN:
12087 				case WM_LBUTTONDBLCLK:
12088 					mouse.type = cast(MouseEventType) 1;
12089 					mouse.button = MouseButton.left;
12090 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12091 					mouseEvent(false, wParam);
12092 				break;
12093 				case WM_LBUTTONUP:
12094 					mouse.type = cast(MouseEventType) 2;
12095 					mouse.button = MouseButton.left;
12096 					mouseEvent(false, wParam);
12097 				break;
12098 				case WM_RBUTTONDOWN:
12099 				case WM_RBUTTONDBLCLK:
12100 					mouse.type = cast(MouseEventType) 1;
12101 					mouse.button = MouseButton.right;
12102 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12103 					mouseEvent(false, wParam);
12104 				break;
12105 				case WM_RBUTTONUP:
12106 					mouse.type = cast(MouseEventType) 2;
12107 					mouse.button = MouseButton.right;
12108 					mouseEvent(false, wParam);
12109 				break;
12110 				case WM_MBUTTONDOWN:
12111 				case WM_MBUTTONDBLCLK:
12112 					mouse.type = cast(MouseEventType) 1;
12113 					mouse.button = MouseButton.middle;
12114 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12115 					mouseEvent(false, wParam);
12116 				break;
12117 				case WM_MBUTTONUP:
12118 					mouse.type = cast(MouseEventType) 2;
12119 					mouse.button = MouseButton.middle;
12120 					mouseEvent(false, wParam);
12121 				break;
12122 				case WM_XBUTTONDOWN:
12123 				case WM_XBUTTONDBLCLK:
12124 					mouse.type = cast(MouseEventType) 1;
12125 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12126 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12127 					mouseEvent(false, wParam);
12128 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12129 				case WM_XBUTTONUP:
12130 					mouse.type = cast(MouseEventType) 2;
12131 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12132 					mouseEvent(false, wParam);
12133 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12134 
12135 				default: return 1;
12136 			}
12137 			return 0;
12138 		}
12139 
12140 		HWND hwnd;
12141 		private int oldWidth;
12142 		private int oldHeight;
12143 		private bool inSizeMove;
12144 
12145 		/++
12146 			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.
12147 
12148 			History:
12149 				Added November 23, 2021
12150 
12151 				Not fully stable, may be moved out of the impl struct.
12152 
12153 				Default value changed to `true` on February 15, 2021
12154 		+/
12155 		bool doLiveResizing = true;
12156 
12157 		package int bmpWidth;
12158 		package int bmpHeight;
12159 
12160 		// the extern(Windows) wndproc should just forward to this
12161 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12162 		try {
12163 			assert(hwnd is this.hwnd);
12164 
12165 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12166 			switch(msg) {
12167 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12168 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12169 					// The main things we can do are select, execute, close, or ignore
12170 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12171 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12172 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12173 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12174 
12175 					// returns the value in the *high order word* of the return value
12176 					// hence the << 16
12177 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12178 				case WM_SETCURSOR:
12179 					if(cast(HWND) wParam !is hwnd)
12180 						return 0; // further processing elsewhere
12181 
12182 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12183 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12184 						return 1;
12185 					} else {
12186 						return DefWindowProc(hwnd, msg, wParam, lParam);
12187 					}
12188 				//break;
12189 
12190 				case WM_CLOSE:
12191 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12192 				break;
12193 				case WM_DESTROY:
12194 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12195 					SimpleWindow.nativeMapping.remove(hwnd);
12196 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12197 
12198 					bool anyImportant = false;
12199 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12200 						if(w.beingOpenKeepsAppOpen) {
12201 							anyImportant = true;
12202 							break;
12203 						}
12204 					if(!anyImportant) {
12205 						PostQuitMessage(0);
12206 					}
12207 				break;
12208 				case 0x02E0 /*WM_DPICHANGED*/:
12209 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12210 
12211 					RECT* prcNewWindow = cast(RECT*)lParam;
12212 					// docs say this is the recommended position and we should honor it
12213 					SetWindowPos(hwnd,
12214 							null,
12215 							prcNewWindow.left,
12216 							prcNewWindow.top,
12217 							prcNewWindow.right - prcNewWindow.left,
12218 							prcNewWindow.bottom - prcNewWindow.top,
12219 							SWP_NOZORDER | SWP_NOACTIVATE);
12220 
12221 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12222 					// im not sure it is completely correct
12223 					// but without it the tabs and such do look weird as things change.
12224 					if(SystemParametersInfoForDpi) {
12225 						LOGFONT lfText;
12226 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12227 						HFONT hFontNew = CreateFontIndirect(&lfText);
12228 						if (hFontNew)
12229 						{
12230 							//DeleteObject(hFontOld);
12231 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12232 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12233 								return TRUE;
12234 							}
12235 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12236 						}
12237 					}
12238 
12239 					if(this.onDpiChanged)
12240 						this.onDpiChanged();
12241 				break;
12242 				case WM_ENTERIDLE:
12243 					// when a menu is up, it stops normal event processing (modal message loop)
12244 					// but this at least gives us a chance to SOMETIMES catch up
12245 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12246 					SimpleWindow.processAllCustomEvents;
12247 					SimpleWindow.processAllCustomEvents;
12248 					SleepEx(0, true);
12249 					break;
12250 				case WM_SIZE:
12251 					if(wParam == 1 /* SIZE_MINIMIZED */)
12252 						break;
12253 					_width = LOWORD(lParam);
12254 					_height = HIWORD(lParam);
12255 
12256 					// I want to avoid tearing in the windows (my code is inefficient
12257 					// so this is a hack around that) so while sizing, we don't trigger,
12258 					// but we do want to trigger on events like mazimize.
12259 					if(!inSizeMove || doLiveResizing)
12260 						goto size_changed;
12261 				break;
12262 				/+
12263 				case WM_SIZING:
12264 					writeln("size");
12265 				break;
12266 				+/
12267 				// I don't like the tearing I get when redrawing on WM_SIZE
12268 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12269 				// so instead it is going to redraw only at the end of a size.
12270 				case 0x0231: /* WM_ENTERSIZEMOVE */
12271 					inSizeMove = true;
12272 				break;
12273 				case 0x0232: /* WM_EXITSIZEMOVE */
12274 					inSizeMove = false;
12275 
12276 					size_changed:
12277 
12278 					// nothing relevant changed, don't bother redrawing
12279 					if(oldWidth == _width && oldHeight == _height) {
12280 						if(msg == 0x0232)
12281 							goto finalize_resize;
12282 						break;
12283 					}
12284 
12285 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12286 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12287 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12288 						// gotta get the double buffer bmp to match the window
12289 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12290 
12291 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12292 						if(resizability != Resizability.automaticallyScaleIfPossible)
12293 						if(_width > bmpWidth || _height > bmpHeight) {
12294 							auto hdc = GetDC(hwnd);
12295 							auto oldBuffer = buffer;
12296 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12297 
12298 							auto hdcBmp = CreateCompatibleDC(hdc);
12299 							auto oldBmp = SelectObject(hdcBmp, buffer);
12300 
12301 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12302 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12303 
12304 							/+
12305 							RECT r;
12306 							r.left = 0;
12307 							r.top = 0;
12308 							r.right = width;
12309 							r.bottom = height;
12310 							auto c = Color.green;
12311 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12312 							FillRect(hdcBmp, &r, brush);
12313 							DeleteObject(brush);
12314 							+/
12315 
12316 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12317 
12318 							bmpWidth = _width;
12319 							bmpHeight = _height;
12320 
12321 							SelectObject(hdcOldBmp, oldOldBmp);
12322 							DeleteDC(hdcOldBmp);
12323 
12324 							SelectObject(hdcBmp, oldBmp);
12325 							DeleteDC(hdcBmp);
12326 
12327 							ReleaseDC(hwnd, hdc);
12328 
12329 							DeleteObject(oldBuffer);
12330 						}
12331 					}
12332 
12333 					updateOpenglViewportIfNeeded(_width, _height);
12334 
12335 					if(resizability != Resizability.automaticallyScaleIfPossible)
12336 					if(windowResized !is null)
12337 						windowResized(_width, _height);
12338 
12339 					/+
12340 					if(inSizeMove) {
12341 						// SimpleWindow.processAllCustomEvents();
12342 						// SimpleWindow.processAllCustomEvents();
12343 
12344 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12345 						//sdpyPrintDebugString("redraw b");
12346 					} else {
12347 					+/ {
12348 						finalize_resize:
12349 						// when it is all done, make sure everything is freshly drawn or there might be
12350 						// weird bugs left.
12351 						SimpleWindow.processAllCustomEvents();
12352 						SimpleWindow.processAllCustomEvents();
12353 
12354 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12355 						// sdpyPrintDebugString("redraw");
12356 					}
12357 
12358 					oldWidth = this._width;
12359 					oldHeight = this._height;
12360 				break;
12361 				case WM_ERASEBKGND:
12362 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12363 					if (!this._visibleForTheFirstTimeCalled) {
12364 						this._visibleForTheFirstTimeCalled = true;
12365 						if (this.visibleForTheFirstTime !is null) {
12366 							this.visibleForTheFirstTime();
12367 						}
12368 					}
12369 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12370 					version(without_opengl) {} else {
12371 						if (openglMode == OpenGlOptions.yes) return 1;
12372 					}
12373 					// call windows default handler, so it can paint standard controls
12374 					goto default;
12375 				case WM_CTLCOLORBTN:
12376 				case WM_CTLCOLORSTATIC:
12377 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12378 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12379 					GetSysColorBrush(COLOR_3DFACE);
12380 				//break;
12381 				case WM_SHOWWINDOW:
12382 					this._visible = (wParam != 0);
12383 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12384 						this._visibleForTheFirstTimeCalled = true;
12385 						if (this.visibleForTheFirstTime !is null) {
12386 							this.visibleForTheFirstTime();
12387 						}
12388 					}
12389 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12390 					break;
12391 				case WM_PAINT: {
12392 					if (!this._visibleForTheFirstTimeCalled) {
12393 						this._visibleForTheFirstTimeCalled = true;
12394 						if (this.visibleForTheFirstTime !is null) {
12395 							this.visibleForTheFirstTime();
12396 						}
12397 					}
12398 
12399 					BITMAP bm;
12400 					PAINTSTRUCT ps;
12401 
12402 					HDC hdc = BeginPaint(hwnd, &ps);
12403 
12404 					if(openglMode == OpenGlOptions.no) {
12405 
12406 						HDC hdcMem = CreateCompatibleDC(hdc);
12407 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12408 
12409 						GetObject(buffer, bm.sizeof, &bm);
12410 
12411 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12412 						if(resizability == Resizability.automaticallyScaleIfPossible)
12413 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12414 						else
12415 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12416 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12417 
12418 						SelectObject(hdcMem, hbmOld);
12419 						DeleteDC(hdcMem);
12420 						EndPaint(hwnd, &ps);
12421 					} else {
12422 						EndPaint(hwnd, &ps);
12423 						version(without_opengl) {} else
12424 							redrawOpenGlSceneSoon();
12425 					}
12426 				} break;
12427 				  default:
12428 					return DefWindowProc(hwnd, msg, wParam, lParam);
12429 			}
12430 			 return 0;
12431 
12432 		}
12433 		catch(Throwable t) {
12434 			sdpyPrintDebugString(t.toString);
12435 			return 0;
12436 		}
12437 		}
12438 	}
12439 
12440 	mixin template NativeImageImplementation() {
12441 		HBITMAP handle;
12442 		ubyte* rawData;
12443 
12444 	final:
12445 
12446 		Color getPixel(int x, int y) {
12447 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12448 			// remember, bmps are upside down
12449 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12450 
12451 			Color c;
12452 			if(enableAlpha)
12453 				c.a = rawData[offset + 3];
12454 			else
12455 				c.a = 255;
12456 			c.b = rawData[offset + 0];
12457 			c.g = rawData[offset + 1];
12458 			c.r = rawData[offset + 2];
12459 			c.unPremultiply();
12460 			return c;
12461 		}
12462 
12463 		void setPixel(int x, int y, Color c) {
12464 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12465 			// remember, bmps are upside down
12466 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12467 
12468 			if(enableAlpha)
12469 				c.premultiply();
12470 
12471 			rawData[offset + 0] = c.b;
12472 			rawData[offset + 1] = c.g;
12473 			rawData[offset + 2] = c.r;
12474 			if(enableAlpha)
12475 				rawData[offset + 3] = c.a;
12476 		}
12477 
12478 		void convertToRgbaBytes(ubyte[] where) {
12479 			assert(where.length == this.width * this.height * 4);
12480 
12481 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12482 			int idx = 0;
12483 			int offset = itemsPerLine * (height - 1);
12484 			// remember, bmps are upside down
12485 			for(int y = height - 1; y >= 0; y--) {
12486 				auto offsetStart = offset;
12487 				for(int x = 0; x < width; x++) {
12488 					where[idx + 0] = rawData[offset + 2]; // r
12489 					where[idx + 1] = rawData[offset + 1]; // g
12490 					where[idx + 2] = rawData[offset + 0]; // b
12491 					if(enableAlpha) {
12492 						where[idx + 3] = rawData[offset + 3]; // a
12493 						unPremultiplyRgba(where[idx .. idx + 4]);
12494 						offset++;
12495 					} else
12496 						where[idx + 3] = 255; // a
12497 					idx += 4;
12498 					offset += 3;
12499 				}
12500 
12501 				offset = offsetStart - itemsPerLine;
12502 			}
12503 		}
12504 
12505 		void setFromRgbaBytes(in ubyte[] what) {
12506 			assert(what.length == this.width * this.height * 4);
12507 
12508 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12509 			int idx = 0;
12510 			int offset = itemsPerLine * (height - 1);
12511 			// remember, bmps are upside down
12512 			for(int y = height - 1; y >= 0; y--) {
12513 				auto offsetStart = offset;
12514 				for(int x = 0; x < width; x++) {
12515 					if(enableAlpha) {
12516 						auto a = what[idx + 3];
12517 
12518 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12519 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12520 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12521 						rawData[offset + 3] = a; // a
12522 						//premultiplyBgra(rawData[offset .. offset + 4]);
12523 						offset++;
12524 					} else {
12525 						rawData[offset + 2] = what[idx + 0]; // r
12526 						rawData[offset + 1] = what[idx + 1]; // g
12527 						rawData[offset + 0] = what[idx + 2]; // b
12528 					}
12529 					idx += 4;
12530 					offset += 3;
12531 				}
12532 
12533 				offset = offsetStart - itemsPerLine;
12534 			}
12535 		}
12536 
12537 
12538 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12539 			BITMAPINFO infoheader;
12540 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12541 			infoheader.bmiHeader.biWidth = width;
12542 			infoheader.bmiHeader.biHeight = height;
12543 			infoheader.bmiHeader.biPlanes = 1;
12544 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12545 			infoheader.bmiHeader.biCompression = BI_RGB;
12546 
12547 			handle = CreateDIBSection(
12548 				null,
12549 				&infoheader,
12550 				DIB_RGB_COLORS,
12551 				cast(void**) &rawData,
12552 				null,
12553 				0);
12554 			if(handle is null)
12555 				throw new WindowsApiException("create image failed", GetLastError());
12556 
12557 		}
12558 
12559 		void dispose() {
12560 			DeleteObject(handle);
12561 		}
12562 	}
12563 
12564 	enum KEY_ESCAPE = 27;
12565 }
12566 version(X11) {
12567 	/// This is the default font used. You might change this before doing anything else with
12568 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12569 	/// for cross-platform compatibility.
12570 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12571 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12572 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12573 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12574 
12575 	alias int delegate(XEvent) NativeEventHandler;
12576 	alias Window NativeWindowHandle;
12577 
12578 	enum KEY_ESCAPE = 9;
12579 
12580 	mixin template NativeScreenPainterImplementation() {
12581 		Display* display;
12582 		Drawable d;
12583 		Drawable destiny;
12584 
12585 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12586 		GC gc;
12587 
12588 		__gshared bool fontAttempted;
12589 
12590 		__gshared XFontStruct* defaultfont;
12591 		__gshared XFontSet defaultfontset;
12592 
12593 		XFontStruct* font;
12594 		XFontSet fontset;
12595 
12596 		void create(PaintingHandle window) {
12597 			this.display = XDisplayConnection.get();
12598 
12599 			Drawable buffer = None;
12600 			if(auto sw = cast(SimpleWindow) this.window) {
12601 				buffer = sw.impl.buffer;
12602 				this.destiny = cast(Drawable) window;
12603 			} else {
12604 				buffer = cast(Drawable) window;
12605 				this.destiny = None;
12606 			}
12607 
12608 			this.d = cast(Drawable) buffer;
12609 
12610 			auto dgc = DefaultGC(display, DefaultScreen(display));
12611 
12612 			this.gc = XCreateGC(display, d, 0, null);
12613 
12614 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12615 
12616 			ensureDefaultFontLoaded();
12617 
12618 			font = defaultfont;
12619 			fontset = defaultfontset;
12620 
12621 			if(font) {
12622 				XSetFont(display, gc, font.fid);
12623 			}
12624 		}
12625 
12626 		static void ensureDefaultFontLoaded() {
12627 			if(!fontAttempted) {
12628 				auto display = XDisplayConnection.get;
12629 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12630 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12631 				if(font is null) {
12632 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12633 					font = XLoadQueryFont(display, xfontstr.ptr);
12634 				}
12635 
12636 				char** lol;
12637 				int lol2;
12638 				char* lol3;
12639 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12640 
12641 				fontAttempted = true;
12642 
12643 				defaultfont = font;
12644 				defaultfontset = fontset;
12645 			}
12646 		}
12647 
12648 		arsd.color.Rectangle _clipRectangle;
12649 		void setClipRectangle(int x, int y, int width, int height) {
12650 			auto old = _clipRectangle;
12651 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12652 			if(old == _clipRectangle)
12653 				return;
12654 
12655 			if(width == 0 || height == 0) {
12656 				XSetClipMask(display, gc, None);
12657 
12658 				if(xrenderPicturePainter) {
12659 
12660 					XRectangle[1] rects;
12661 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12662 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12663 				}
12664 
12665 				version(with_xft) {
12666 					if(xftFont is null || xftDraw is null)
12667 						return;
12668 					XftDrawSetClip(xftDraw, null);
12669 				}
12670 			} else {
12671 				XRectangle[1] rects;
12672 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12673 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12674 
12675 				if(xrenderPicturePainter)
12676 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12677 
12678 				version(with_xft) {
12679 					if(xftFont is null || xftDraw is null)
12680 						return;
12681 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12682 				}
12683 			}
12684 		}
12685 
12686 		version(with_xft) {
12687 			XftFont* xftFont;
12688 			XftDraw* xftDraw;
12689 
12690 			XftColor xftColor;
12691 
12692 			void updateXftColor() {
12693 				if(xftFont is null)
12694 					return;
12695 
12696 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12697 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12698 
12699 				XftColorAllocValue(
12700 					display,
12701 					DefaultVisual(display, DefaultScreen(display)),
12702 					DefaultColormap(display, 0),
12703 					&colorIn,
12704 					&xftColor
12705 				);
12706 			}
12707 		}
12708 
12709 		private OperatingSystemFont _activeFont;
12710 		void setFont(OperatingSystemFont font) {
12711 			_activeFont = font;
12712 			version(with_xft) {
12713 				if(font && font.isXft && font.xftFont)
12714 					this.xftFont = font.xftFont;
12715 				else
12716 					this.xftFont = null;
12717 
12718 				if(this.xftFont) {
12719 					if(xftDraw is null) {
12720 						xftDraw = XftDrawCreate(
12721 							display,
12722 							d,
12723 							DefaultVisual(display, DefaultScreen(display)),
12724 							DefaultColormap(display, 0)
12725 						);
12726 
12727 						updateXftColor();
12728 					}
12729 
12730 					return;
12731 				}
12732 			}
12733 
12734 			if(font && font.font) {
12735 				this.font = font.font;
12736 				this.fontset = font.fontset;
12737 				XSetFont(display, gc, font.font.fid);
12738 			} else {
12739 				this.font = defaultfont;
12740 				this.fontset = defaultfontset;
12741 			}
12742 
12743 		}
12744 
12745 		private Picture xrenderPicturePainter;
12746 
12747 		bool manualInvalidations;
12748 		void invalidateRect(Rectangle invalidRect) {
12749 			// FIXME if manualInvalidations
12750 		}
12751 
12752 		void dispose() {
12753 			this.rasterOp = RasterOp.normal;
12754 
12755 			if(xrenderPicturePainter) {
12756 				XRenderFreePicture(display, xrenderPicturePainter);
12757 				xrenderPicturePainter = None;
12758 			}
12759 
12760 			// FIXME: this.window.width/height is probably wrong
12761 
12762 			// src x,y     then dest x, y
12763 			if(destiny != None) {
12764 				// FIXME: if manual invalidations we can actually only copy some of the area.
12765 				// if(manualInvalidations)
12766 				XSetClipMask(display, gc, None);
12767 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12768 			}
12769 
12770 			XFreeGC(display, gc);
12771 
12772 			version(with_xft)
12773 			if(xftDraw) {
12774 				XftDrawDestroy(xftDraw);
12775 				xftDraw = null;
12776 			}
12777 
12778 			/+
12779 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12780 			if(font && font !is defaultfont) {
12781 				XFreeFont(display, font);
12782 				font = null;
12783 			}
12784 			if(fontset && fontset !is defaultfontset) {
12785 				XFreeFontSet(display, fontset);
12786 				fontset = null;
12787 			}
12788 			+/
12789 			XFlush(display);
12790 
12791 			if(window.paintingFinishedDg !is null)
12792 				window.paintingFinishedDg()();
12793 		}
12794 
12795 		bool backgroundIsNotTransparent = true;
12796 		bool foregroundIsNotTransparent = true;
12797 
12798 		bool _penInitialized = false;
12799 		Pen _activePen;
12800 
12801 		Color _outlineColor;
12802 		Color _fillColor;
12803 
12804 		@property void pen(Pen p) {
12805 			if(_penInitialized && p == _activePen) {
12806 				return;
12807 			}
12808 			_penInitialized = true;
12809 			_activePen = p;
12810 			_outlineColor = p.color;
12811 
12812 			int style;
12813 
12814 			byte dashLength;
12815 
12816 			final switch(p.style) {
12817 				case Pen.Style.Solid:
12818 					style = 0 /*LineSolid*/;
12819 				break;
12820 				case Pen.Style.Dashed:
12821 					style = 1 /*LineOnOffDash*/;
12822 					dashLength = 4;
12823 				break;
12824 				case Pen.Style.Dotted:
12825 					style = 1 /*LineOnOffDash*/;
12826 					dashLength = 1;
12827 				break;
12828 			}
12829 
12830 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
12831 			if(dashLength)
12832 				XSetDashes(display, gc, 0, &dashLength, 1);
12833 
12834 			if(p.color.a == 0) {
12835 				foregroundIsNotTransparent = false;
12836 				return;
12837 			}
12838 
12839 			foregroundIsNotTransparent = true;
12840 
12841 			XSetForeground(display, gc, colorToX(p.color, display));
12842 
12843 			version(with_xft)
12844 				updateXftColor();
12845 		}
12846 
12847 		RasterOp _currentRasterOp;
12848 		bool _currentRasterOpInitialized = false;
12849 		@property void rasterOp(RasterOp op) {
12850 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12851 				return;
12852 			_currentRasterOp = op;
12853 			_currentRasterOpInitialized = true;
12854 			int mode;
12855 			final switch(op) {
12856 				case RasterOp.normal:
12857 					mode = GXcopy;
12858 				break;
12859 				case RasterOp.xor:
12860 					mode = GXxor;
12861 				break;
12862 			}
12863 			XSetFunction(display, gc, mode);
12864 		}
12865 
12866 
12867 		bool _fillColorInitialized = false;
12868 
12869 		@property void fillColor(Color c) {
12870 			if(_fillColorInitialized && _fillColor == c)
12871 				return; // already good, no need to waste time calling it
12872 			_fillColor = c;
12873 			_fillColorInitialized = true;
12874 			if(c.a == 0) {
12875 				backgroundIsNotTransparent = false;
12876 				return;
12877 			}
12878 
12879 			backgroundIsNotTransparent = true;
12880 
12881 			XSetBackground(display, gc, colorToX(c, display));
12882 
12883 		}
12884 
12885 		void swapColors() {
12886 			auto tmp = _fillColor;
12887 			fillColor = _outlineColor;
12888 			auto newPen = _activePen;
12889 			newPen.color = tmp;
12890 			pen(newPen);
12891 		}
12892 
12893 		uint colorToX(Color c, Display* display) {
12894 			auto visual = DefaultVisual(display, DefaultScreen(display));
12895 			import core.bitop;
12896 			uint color = 0;
12897 			{
12898 			auto startBit = bsf(visual.red_mask);
12899 			auto lastBit = bsr(visual.red_mask);
12900 			auto r = cast(uint) c.r;
12901 			r >>= 7 - (lastBit - startBit);
12902 			r <<= startBit;
12903 			color |= r;
12904 			}
12905 			{
12906 			auto startBit = bsf(visual.green_mask);
12907 			auto lastBit = bsr(visual.green_mask);
12908 			auto g = cast(uint) c.g;
12909 			g >>= 7 - (lastBit - startBit);
12910 			g <<= startBit;
12911 			color |= g;
12912 			}
12913 			{
12914 			auto startBit = bsf(visual.blue_mask);
12915 			auto lastBit = bsr(visual.blue_mask);
12916 			auto b = cast(uint) c.b;
12917 			b >>= 7 - (lastBit - startBit);
12918 			b <<= startBit;
12919 			color |= b;
12920 			}
12921 
12922 
12923 
12924 			return color;
12925 		}
12926 
12927 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12928 			// source x, source y
12929 			if(ix >= i.width) return;
12930 			if(iy >= i.height) return;
12931 			if(ix + w > i.width) w = i.width - ix;
12932 			if(iy + h > i.height) h = i.height - iy;
12933 			if(i.usingXshm)
12934 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12935 			else
12936 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12937 		}
12938 
12939 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12940 			if(s.enableAlpha) {
12941 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12942 				if(this.xrenderPicturePainter == None) {
12943 					XRenderPictureAttributes attrs;
12944 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12945 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12946 
12947 					// need to initialize the clip
12948 					XRectangle[1] rects;
12949 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12950 
12951 					if(_clipRectangle != Rectangle.init)
12952 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12953 				}
12954 
12955 				XRenderComposite(
12956 					display,
12957 					3, // PicOpOver
12958 					s.xrenderPicture,
12959 					None,
12960 					this.xrenderPicturePainter,
12961 					ix,
12962 					iy,
12963 					0,
12964 					0,
12965 					x,
12966 					y,
12967 					w ? w : s.width,
12968 					h ? h : s.height
12969 				);
12970 			} else {
12971 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12972 			}
12973 		}
12974 
12975 		int fontHeight() {
12976 			version(with_xft)
12977 				if(xftFont !is null)
12978 					return xftFont.height;
12979 			if(font)
12980 				return font.max_bounds.ascent + font.max_bounds.descent;
12981 			return 12; // pretty common default...
12982 		}
12983 
12984 		int textWidth(in char[] line) {
12985 			version(with_xft)
12986 			if(xftFont) {
12987 				if(line.length == 0)
12988 					return 0;
12989 				XGlyphInfo extents;
12990 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12991 				return extents.width;
12992 			}
12993 
12994 			if(fontset) {
12995 				if(line.length == 0)
12996 					return 0;
12997 				XRectangle rect;
12998 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12999 
13000 				return rect.width;
13001 			}
13002 
13003 			if(font)
13004 				// FIXME: unicode
13005 				return XTextWidth( font, line.ptr, cast(int) line.length);
13006 			else
13007 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13008 		}
13009 
13010 		Size textSize(in char[] text) {
13011 			auto maxWidth = 0;
13012 			auto lineHeight = fontHeight;
13013 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13014 			foreach(line; text.split('\n')) {
13015 				int textWidth = this.textWidth(line);
13016 				if(textWidth > maxWidth)
13017 					maxWidth = textWidth;
13018 				h += lineHeight + 4;
13019 			}
13020 			return Size(maxWidth, h);
13021 		}
13022 
13023 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13024 			const(char)[] text;
13025 			version(with_xft)
13026 			if(xftFont) {
13027 				text = originalText;
13028 				goto loaded;
13029 			}
13030 
13031 			if(fontset)
13032 				text = originalText;
13033 			else {
13034 				text.reserve(originalText.length);
13035 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13036 				// then strip the rest so there isn't garbage
13037 				foreach(dchar ch; originalText)
13038 					if(ch < 256)
13039 						text ~= cast(ubyte) ch;
13040 					else
13041 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13042 			}
13043 			loaded:
13044 			if(text.length == 0)
13045 				return;
13046 
13047 			// FIXME: should we clip it to the bounding box?
13048 			int textHeight = fontHeight;
13049 
13050 			auto lines = text.split('\n');
13051 
13052 			const lineHeight = textHeight;
13053 			textHeight *= lines.length;
13054 
13055 			int cy = y;
13056 
13057 			if(alignment & TextAlignment.VerticalBottom) {
13058 				if(y2 <= 0)
13059 					return;
13060 				auto h = y2 - y;
13061 				if(h > textHeight) {
13062 					cy += h - textHeight;
13063 					cy -= lineHeight / 2;
13064 				}
13065 			} else if(alignment & TextAlignment.VerticalCenter) {
13066 				if(y2 <= 0)
13067 					return;
13068 				auto h = y2 - y;
13069 				if(textHeight < h) {
13070 					cy += (h - textHeight) / 2;
13071 					//cy -= lineHeight / 4;
13072 				}
13073 			}
13074 
13075 			foreach(line; text.split('\n')) {
13076 				int textWidth = this.textWidth(line);
13077 
13078 				int px = x, py = cy;
13079 
13080 				if(alignment & TextAlignment.Center) {
13081 					if(x2 <= 0)
13082 						return;
13083 					auto w = x2 - x;
13084 					if(w > textWidth)
13085 						px += (w - textWidth) / 2;
13086 				} else if(alignment & TextAlignment.Right) {
13087 					if(x2 <= 0)
13088 						return;
13089 					auto pos = x2 - textWidth;
13090 					if(pos > x)
13091 						px = pos;
13092 				}
13093 
13094 				version(with_xft)
13095 				if(xftFont) {
13096 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13097 
13098 					goto carry_on;
13099 				}
13100 
13101 				if(fontset)
13102 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13103 				else
13104 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13105 				carry_on:
13106 				cy += lineHeight + 4;
13107 			}
13108 		}
13109 
13110 		void drawPixel(int x, int y) {
13111 			XDrawPoint(display, d, gc, x, y);
13112 		}
13113 
13114 		// The basic shapes, outlined
13115 
13116 		void drawLine(int x1, int y1, int x2, int y2) {
13117 			if(foregroundIsNotTransparent)
13118 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13119 		}
13120 
13121 		void drawRectangle(int x, int y, int width, int height) {
13122 			if(backgroundIsNotTransparent) {
13123 				swapColors();
13124 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13125 				swapColors();
13126 			}
13127 			// 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
13128 			if(foregroundIsNotTransparent)
13129 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13130 		}
13131 
13132 		/// Arguments are the points of the bounding rectangle
13133 		void drawEllipse(int x1, int y1, int x2, int y2) {
13134 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13135 		}
13136 
13137 		// NOTE: start and finish are in units of degrees * 64
13138 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
13139 			if(backgroundIsNotTransparent) {
13140 				swapColors();
13141 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
13142 				swapColors();
13143 			}
13144 			if(foregroundIsNotTransparent) {
13145 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
13146 
13147 				// Windows draws the straight lines on the edges too so FIXME sort of
13148 			}
13149 		}
13150 
13151 		void drawPolygon(Point[] vertexes) {
13152 			XPoint[16] pointsBuffer;
13153 			XPoint[] points;
13154 			if(vertexes.length <= pointsBuffer.length)
13155 				points = pointsBuffer[0 .. vertexes.length];
13156 			else
13157 				points.length = vertexes.length;
13158 
13159 			foreach(i, p; vertexes) {
13160 				points[i].x = cast(short) p.x;
13161 				points[i].y = cast(short) p.y;
13162 			}
13163 
13164 			if(backgroundIsNotTransparent) {
13165 				swapColors();
13166 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13167 				swapColors();
13168 			}
13169 			if(foregroundIsNotTransparent) {
13170 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13171 			}
13172 		}
13173 	}
13174 
13175 	/* XRender { */
13176 
13177 	struct XRenderColor {
13178 		ushort red;
13179 		ushort green;
13180 		ushort blue;
13181 		ushort alpha;
13182 	}
13183 
13184 	alias Picture = XID;
13185 	alias PictFormat = XID;
13186 
13187 	struct XGlyphInfo {
13188 		ushort width;
13189 		ushort height;
13190 		short x;
13191 		short y;
13192 		short xOff;
13193 		short yOff;
13194 	}
13195 
13196 struct XRenderDirectFormat {
13197     short   red;
13198     short   redMask;
13199     short   green;
13200     short   greenMask;
13201     short   blue;
13202     short   blueMask;
13203     short   alpha;
13204     short   alphaMask;
13205 }
13206 
13207 struct XRenderPictFormat {
13208     PictFormat		id;
13209     int			type;
13210     int			depth;
13211     XRenderDirectFormat	direct;
13212     Colormap		colormap;
13213 }
13214 
13215 enum PictFormatID	=   (1 << 0);
13216 enum PictFormatType	=   (1 << 1);
13217 enum PictFormatDepth	=   (1 << 2);
13218 enum PictFormatRed	=   (1 << 3);
13219 enum PictFormatRedMask  =(1 << 4);
13220 enum PictFormatGreen	=   (1 << 5);
13221 enum PictFormatGreenMask=(1 << 6);
13222 enum PictFormatBlue	=   (1 << 7);
13223 enum PictFormatBlueMask =(1 << 8);
13224 enum PictFormatAlpha	=   (1 << 9);
13225 enum PictFormatAlphaMask=(1 << 10);
13226 enum PictFormatColormap =(1 << 11);
13227 
13228 struct XRenderPictureAttributes {
13229 	int 		repeat;
13230 	Picture		alpha_map;
13231 	int			alpha_x_origin;
13232 	int			alpha_y_origin;
13233 	int			clip_x_origin;
13234 	int			clip_y_origin;
13235 	Pixmap		clip_mask;
13236 	Bool		graphics_exposures;
13237 	int			subwindow_mode;
13238 	int			poly_edge;
13239 	int			poly_mode;
13240 	Atom		dither;
13241 	Bool		component_alpha;
13242 }
13243 
13244 alias int XFixed;
13245 
13246 struct XPointFixed {
13247     XFixed  x, y;
13248 }
13249 
13250 struct XCircle {
13251     XFixed x;
13252     XFixed y;
13253     XFixed radius;
13254 }
13255 
13256 struct XTransform {
13257     XFixed[3][3]  matrix;
13258 }
13259 
13260 struct XFilters {
13261     int	    nfilter;
13262     char    **filter;
13263     int	    nalias;
13264     short   *alias_;
13265 }
13266 
13267 struct XIndexValue {
13268     c_ulong    pixel;
13269     ushort   red, green, blue, alpha;
13270 }
13271 
13272 struct XAnimCursor {
13273     Cursor	    cursor;
13274     c_ulong   delay;
13275 }
13276 
13277 struct XLinearGradient {
13278     XPointFixed p1;
13279     XPointFixed p2;
13280 }
13281 
13282 struct XRadialGradient {
13283     XCircle inner;
13284     XCircle outer;
13285 }
13286 
13287 struct XConicalGradient {
13288     XPointFixed center;
13289     XFixed angle; /* in degrees */
13290 }
13291 
13292 enum PictStandardARGB32  = 0;
13293 enum PictStandardRGB24   = 1;
13294 enum PictStandardA8	 =  2;
13295 enum PictStandardA4	 =  3;
13296 enum PictStandardA1	 =  4;
13297 enum PictStandardNUM	 =  5;
13298 
13299 interface XRender {
13300 extern(C) @nogc:
13301 
13302 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13303 
13304 	Status XRenderQueryVersion (Display *dpy,
13305 			int     *major_versionp,
13306 			int     *minor_versionp);
13307 
13308 	Status XRenderQueryFormats (Display *dpy);
13309 
13310 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13311 
13312 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13313 
13314 	XRenderPictFormat *
13315 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13316 
13317 	XRenderPictFormat *
13318 		XRenderFindFormat (Display			*dpy,
13319 				c_ulong		mask,
13320 				const XRenderPictFormat	*templ,
13321 				int				count);
13322 	XRenderPictFormat *
13323 		XRenderFindStandardFormat (Display		*dpy,
13324 				int			format);
13325 
13326 	XIndexValue *
13327 		XRenderQueryPictIndexValues(Display			*dpy,
13328 				const XRenderPictFormat	*format,
13329 				int				*num);
13330 
13331 	Picture XRenderCreatePicture(
13332 		Display *dpy,
13333 		Drawable drawable,
13334 		const XRenderPictFormat *format,
13335 		c_ulong valuemask,
13336 		const XRenderPictureAttributes *attributes);
13337 
13338 	void XRenderChangePicture (Display				*dpy,
13339 				Picture				picture,
13340 				c_ulong			valuemask,
13341 				const XRenderPictureAttributes  *attributes);
13342 
13343 	void
13344 		XRenderSetPictureClipRectangles (Display	    *dpy,
13345 				Picture	    picture,
13346 				int		    xOrigin,
13347 				int		    yOrigin,
13348 				const XRectangle *rects,
13349 				int		    n);
13350 
13351 	void
13352 		XRenderSetPictureClipRegion (Display	    *dpy,
13353 				Picture	    picture,
13354 				Region	    r);
13355 
13356 	void
13357 		XRenderSetPictureTransform (Display	    *dpy,
13358 				Picture	    picture,
13359 				XTransform	    *transform);
13360 
13361 	void
13362 		XRenderFreePicture (Display                   *dpy,
13363 				Picture                   picture);
13364 
13365 	void
13366 		XRenderComposite (Display   *dpy,
13367 				int	    op,
13368 				Picture   src,
13369 				Picture   mask,
13370 				Picture   dst,
13371 				int	    src_x,
13372 				int	    src_y,
13373 				int	    mask_x,
13374 				int	    mask_y,
13375 				int	    dst_x,
13376 				int	    dst_y,
13377 				uint	width,
13378 				uint	height);
13379 
13380 
13381 	Picture XRenderCreateSolidFill (Display *dpy,
13382 			const XRenderColor *color);
13383 
13384 	Picture XRenderCreateLinearGradient (Display *dpy,
13385 			const XLinearGradient *gradient,
13386 			const XFixed *stops,
13387 			const XRenderColor *colors,
13388 			int nstops);
13389 
13390 	Picture XRenderCreateRadialGradient (Display *dpy,
13391 			const XRadialGradient *gradient,
13392 			const XFixed *stops,
13393 			const XRenderColor *colors,
13394 			int nstops);
13395 
13396 	Picture XRenderCreateConicalGradient (Display *dpy,
13397 			const XConicalGradient *gradient,
13398 			const XFixed *stops,
13399 			const XRenderColor *colors,
13400 			int nstops);
13401 
13402 
13403 
13404 	Cursor
13405 		XRenderCreateCursor (Display	    *dpy,
13406 				Picture	    source,
13407 				uint   x,
13408 				uint   y);
13409 
13410 	XFilters *
13411 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13412 
13413 	void
13414 		XRenderSetPictureFilter (Display    *dpy,
13415 				Picture    picture,
13416 				const char *filter,
13417 				XFixed	    *params,
13418 				int	    nparams);
13419 
13420 	Cursor
13421 		XRenderCreateAnimCursor (Display	*dpy,
13422 				int		ncursor,
13423 				XAnimCursor	*cursors);
13424 }
13425 
13426 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13427 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13428 
13429 	/* XRender } */
13430 
13431 	/* Xrandr { */
13432 
13433 struct XRRMonitorInfo {
13434     Atom name;
13435     Bool primary;
13436     Bool automatic;
13437     int noutput;
13438     int x;
13439     int y;
13440     int width;
13441     int height;
13442     int mwidth;
13443     int mheight;
13444     /*RROutput*/ void *outputs;
13445 }
13446 
13447 struct XRRScreenChangeNotifyEvent {
13448     int type;                   /* event base */
13449     c_ulong serial;       /* # of last request processed by server */
13450     Bool send_event;            /* true if this came from a SendEvent request */
13451     Display *display;           /* Display the event was read from */
13452     Window window;              /* window which selected for this event */
13453     Window root;                /* Root window for changed screen */
13454     Time timestamp;             /* when the screen change occurred */
13455     Time config_timestamp;      /* when the last configuration change */
13456     ushort/*SizeID*/ size_index;
13457     ushort/*SubpixelOrder*/ subpixel_order;
13458     ushort/*Rotation*/ rotation;
13459     int width;
13460     int height;
13461     int mwidth;
13462     int mheight;
13463 }
13464 
13465 enum RRScreenChangeNotify = 0;
13466 
13467 enum RRScreenChangeNotifyMask = 1;
13468 
13469 __gshared int xrrEventBase = -1;
13470 
13471 
13472 interface XRandr {
13473 extern(C) @nogc:
13474 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13475 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13476 
13477 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13478 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13479 
13480 	void XRRSelectInput(Display *dpy, Window window, int mask);
13481 }
13482 
13483 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13484 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13485 	/* Xrandr } */
13486 
13487 	/* Xft { */
13488 
13489 	// actually freetype
13490 	alias void FT_Face;
13491 
13492 	// actually fontconfig
13493 	private alias FcBool = int;
13494 	alias void FcCharSet;
13495 	alias void FcPattern;
13496 	alias void FcResult;
13497 	enum FcEndian { FcEndianBig, FcEndianLittle }
13498 	struct FcFontSet {
13499 		int nfont;
13500 		int sfont;
13501 		FcPattern** fonts;
13502 	}
13503 
13504 	// actually XRegion
13505 	struct BOX {
13506 		short x1, x2, y1, y2;
13507 	}
13508 	struct _XRegion {
13509 		c_long size;
13510 		c_long numRects;
13511 		BOX* rects;
13512 		BOX extents;
13513 	}
13514 
13515 	alias Region = _XRegion*;
13516 
13517 	// ok actually Xft
13518 
13519 	struct XftFontInfo;
13520 
13521 	struct XftFont {
13522 		int         ascent;
13523 		int         descent;
13524 		int         height;
13525 		int         max_advance_width;
13526 		FcCharSet*  charset;
13527 		FcPattern*  pattern;
13528 	}
13529 
13530 	struct XftDraw;
13531 
13532 	struct XftColor {
13533 		c_ulong pixel;
13534 		XRenderColor color;
13535 	}
13536 
13537 	struct XftCharSpec {
13538 		dchar           ucs4;
13539 		short           x;
13540 		short           y;
13541 	}
13542 
13543 	struct XftCharFontSpec {
13544 		XftFont         *font;
13545 		dchar           ucs4;
13546 		short           x;
13547 		short           y;
13548 	}
13549 
13550 	struct XftGlyphSpec {
13551 		uint            glyph;
13552 		short           x;
13553 		short           y;
13554 	}
13555 
13556 	struct XftGlyphFontSpec {
13557 		XftFont         *font;
13558 		uint            glyph;
13559 		short           x;
13560 		short           y;
13561 	}
13562 
13563 	interface Xft {
13564 	extern(C) @nogc pure:
13565 
13566 	Bool XftColorAllocName (Display  *dpy,
13567 				const Visual   *visual,
13568 				Colormap cmap,
13569 				const char     *name,
13570 				XftColor *result);
13571 
13572 	Bool XftColorAllocValue (Display         *dpy,
13573 				Visual          *visual,
13574 				Colormap        cmap,
13575 				const XRenderColor    *color,
13576 				XftColor        *result);
13577 
13578 	void XftColorFree (Display   *dpy,
13579 				Visual    *visual,
13580 				Colormap  cmap,
13581 				XftColor  *color);
13582 
13583 	Bool XftDefaultHasRender (Display *dpy);
13584 
13585 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13586 
13587 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13588 
13589 	XftDraw * XftDrawCreate (Display   *dpy,
13590 		       Drawable  drawable,
13591 		       Visual    *visual,
13592 		       Colormap  colormap);
13593 
13594 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13595 			     Pixmap   bitmap);
13596 
13597 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13598 			    Pixmap  pixmap,
13599 			    int     depth);
13600 
13601 	void XftDrawChange (XftDraw  *draw,
13602 		       Drawable drawable);
13603 
13604 	Display * XftDrawDisplay (XftDraw *draw);
13605 
13606 	Drawable XftDrawDrawable (XftDraw *draw);
13607 
13608 	Colormap XftDrawColormap (XftDraw *draw);
13609 
13610 	Visual * XftDrawVisual (XftDraw *draw);
13611 
13612 	void XftDrawDestroy (XftDraw *draw);
13613 
13614 	Picture XftDrawPicture (XftDraw *draw);
13615 
13616 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13617 
13618 	void XftDrawGlyphs (XftDraw          *draw,
13619 				const XftColor *color,
13620 				XftFont          *pub,
13621 				int              x,
13622 				int              y,
13623 				const uint  *glyphs,
13624 				int              nglyphs);
13625 
13626 	void XftDrawString8 (XftDraw             *draw,
13627 				const XftColor    *color,
13628 				XftFont             *pub,
13629 				int                 x,
13630 				int                 y,
13631 				const char     *string,
13632 				int                 len);
13633 
13634 	void XftDrawString16 (XftDraw            *draw,
13635 				const XftColor   *color,
13636 				XftFont            *pub,
13637 				int                x,
13638 				int                y,
13639 				const wchar   *string,
13640 				int                len);
13641 
13642 	void XftDrawString32 (XftDraw            *draw,
13643 				const XftColor   *color,
13644 				XftFont            *pub,
13645 				int                x,
13646 				int                y,
13647 				const dchar   *string,
13648 				int                len);
13649 
13650 	void XftDrawStringUtf8 (XftDraw          *draw,
13651 				const XftColor *color,
13652 				XftFont          *pub,
13653 				int              x,
13654 				int              y,
13655 				const char  *string,
13656 				int              len);
13657 	void XftDrawStringUtf16 (XftDraw             *draw,
13658 				const XftColor    *color,
13659 				XftFont             *pub,
13660 				int                 x,
13661 				int                 y,
13662 				const char     *string,
13663 				FcEndian            endian,
13664 				int                 len);
13665 
13666 	void XftDrawCharSpec (XftDraw                *draw,
13667 				const XftColor       *color,
13668 				XftFont                *pub,
13669 				const XftCharSpec    *chars,
13670 				int                    len);
13671 
13672 	void XftDrawCharFontSpec (XftDraw                    *draw,
13673 				const XftColor           *color,
13674 				const XftCharFontSpec    *chars,
13675 				int                        len);
13676 
13677 	void XftDrawGlyphSpec (XftDraw               *draw,
13678 				const XftColor      *color,
13679 				XftFont               *pub,
13680 				const XftGlyphSpec  *glyphs,
13681 				int                   len);
13682 
13683 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13684 				const XftColor          *color,
13685 				const XftGlyphFontSpec  *glyphs,
13686 				int                       len);
13687 
13688 	void XftDrawRect (XftDraw            *draw,
13689 				const XftColor   *color,
13690 				int                x,
13691 				int                y,
13692 				uint       width,
13693 				uint       height);
13694 
13695 	Bool XftDrawSetClip (XftDraw     *draw,
13696 				Region      r);
13697 
13698 
13699 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13700 				int                   xOrigin,
13701 				int                   yOrigin,
13702 				const XRectangle    *rects,
13703 				int                   n);
13704 
13705 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13706 				int        mode);
13707 
13708 	void XftGlyphExtents (Display            *dpy,
13709 				XftFont            *pub,
13710 				const uint    *glyphs,
13711 				int                nglyphs,
13712 				XGlyphInfo         *extents);
13713 
13714 	void XftTextExtents8 (Display            *dpy,
13715 				XftFont            *pub,
13716 				const char    *string,
13717 				int                len,
13718 				XGlyphInfo         *extents);
13719 
13720 	void XftTextExtents16 (Display           *dpy,
13721 				XftFont           *pub,
13722 				const wchar  *string,
13723 				int               len,
13724 				XGlyphInfo        *extents);
13725 
13726 	void XftTextExtents32 (Display           *dpy,
13727 				XftFont           *pub,
13728 				const dchar  *string,
13729 				int               len,
13730 				XGlyphInfo        *extents);
13731 
13732 	void XftTextExtentsUtf8 (Display         *dpy,
13733 				XftFont         *pub,
13734 				const char *string,
13735 				int             len,
13736 				XGlyphInfo      *extents);
13737 
13738 	void XftTextExtentsUtf16 (Display            *dpy,
13739 				XftFont            *pub,
13740 				const char    *string,
13741 				FcEndian           endian,
13742 				int                len,
13743 				XGlyphInfo         *extents);
13744 
13745 	FcPattern * XftFontMatch (Display           *dpy,
13746 				int               screen,
13747 				const FcPattern *pattern,
13748 				FcResult          *result);
13749 
13750 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13751 
13752 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13753 
13754 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13755 
13756 	FT_Face XftLockFace (XftFont *pub);
13757 
13758 	void XftUnlockFace (XftFont *pub);
13759 
13760 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13761 
13762 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13763 
13764 	dchar XftFontInfoHash (const XftFontInfo *fi);
13765 
13766 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13767 
13768 	XftFont * XftFontOpenInfo (Display        *dpy,
13769 				FcPattern      *pattern,
13770 				XftFontInfo    *fi);
13771 
13772 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13773 
13774 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13775 
13776 	void XftFontClose (Display *dpy, XftFont *pub);
13777 
13778 	FcBool XftInitFtLibrary();
13779 	void XftFontLoadGlyphs (Display          *dpy,
13780 				XftFont          *pub,
13781 				FcBool           need_bitmaps,
13782 				const uint  *glyphs,
13783 				int              nglyph);
13784 
13785 	void XftFontUnloadGlyphs (Display            *dpy,
13786 				XftFont            *pub,
13787 				const uint    *glyphs,
13788 				int                nglyph);
13789 
13790 	FcBool XftFontCheckGlyph (Display  *dpy,
13791 				XftFont  *pub,
13792 				FcBool   need_bitmaps,
13793 				uint  glyph,
13794 				uint  *missing,
13795 				int      *nmissing);
13796 
13797 	FcBool XftCharExists (Display      *dpy,
13798 				XftFont      *pub,
13799 				dchar    ucs4);
13800 
13801 	uint XftCharIndex (Display       *dpy,
13802 				XftFont       *pub,
13803 				dchar      ucs4);
13804 	FcBool XftInit (const char *config);
13805 
13806 	int XftGetVersion ();
13807 
13808 	FcFontSet * XftListFonts (Display   *dpy,
13809 				int       screen,
13810 				...);
13811 
13812 	FcPattern *XftNameParse (const char *name);
13813 
13814 	void XftGlyphRender (Display         *dpy,
13815 				int             op,
13816 				Picture         src,
13817 				XftFont         *pub,
13818 				Picture         dst,
13819 				int             srcx,
13820 				int             srcy,
13821 				int             x,
13822 				int             y,
13823 				const uint *glyphs,
13824 				int             nglyphs);
13825 
13826 	void XftGlyphSpecRender (Display                 *dpy,
13827 				int                     op,
13828 				Picture                 src,
13829 				XftFont                 *pub,
13830 				Picture                 dst,
13831 				int                     srcx,
13832 				int                     srcy,
13833 				const XftGlyphSpec    *glyphs,
13834 				int                     nglyphs);
13835 
13836 	void XftCharSpecRender (Display              *dpy,
13837 				int                  op,
13838 				Picture              src,
13839 				XftFont              *pub,
13840 				Picture              dst,
13841 				int                  srcx,
13842 				int                  srcy,
13843 				const XftCharSpec  *chars,
13844 				int                  len);
13845 	void XftGlyphFontSpecRender (Display                     *dpy,
13846 				int                         op,
13847 				Picture                     src,
13848 				Picture                     dst,
13849 				int                         srcx,
13850 				int                         srcy,
13851 				const XftGlyphFontSpec    *glyphs,
13852 				int                         nglyphs);
13853 
13854 	void XftCharFontSpecRender (Display                  *dpy,
13855 				int                      op,
13856 				Picture                  src,
13857 				Picture                  dst,
13858 				int                      srcx,
13859 				int                      srcy,
13860 				const XftCharFontSpec  *chars,
13861 				int                      len);
13862 
13863 	void XftTextRender8 (Display         *dpy,
13864 				int             op,
13865 				Picture         src,
13866 				XftFont         *pub,
13867 				Picture         dst,
13868 				int             srcx,
13869 				int             srcy,
13870 				int             x,
13871 				int             y,
13872 				const char *string,
13873 				int             len);
13874 	void XftTextRender16 (Display            *dpy,
13875 				int                op,
13876 				Picture            src,
13877 				XftFont            *pub,
13878 				Picture            dst,
13879 				int                srcx,
13880 				int                srcy,
13881 				int                x,
13882 				int                y,
13883 				const wchar   *string,
13884 				int                len);
13885 
13886 	void XftTextRender16BE (Display          *dpy,
13887 				int              op,
13888 				Picture          src,
13889 				XftFont          *pub,
13890 				Picture          dst,
13891 				int              srcx,
13892 				int              srcy,
13893 				int              x,
13894 				int              y,
13895 				const char  *string,
13896 				int              len);
13897 
13898 	void XftTextRender16LE (Display          *dpy,
13899 				int              op,
13900 				Picture          src,
13901 				XftFont          *pub,
13902 				Picture          dst,
13903 				int              srcx,
13904 				int              srcy,
13905 				int              x,
13906 				int              y,
13907 				const char  *string,
13908 				int              len);
13909 
13910 	void XftTextRender32 (Display            *dpy,
13911 				int                op,
13912 				Picture            src,
13913 				XftFont            *pub,
13914 				Picture            dst,
13915 				int                srcx,
13916 				int                srcy,
13917 				int                x,
13918 				int                y,
13919 				const dchar   *string,
13920 				int                len);
13921 
13922 	void XftTextRender32BE (Display          *dpy,
13923 				int              op,
13924 				Picture          src,
13925 				XftFont          *pub,
13926 				Picture          dst,
13927 				int              srcx,
13928 				int              srcy,
13929 				int              x,
13930 				int              y,
13931 				const char  *string,
13932 				int              len);
13933 
13934 	void XftTextRender32LE (Display          *dpy,
13935 				int              op,
13936 				Picture          src,
13937 				XftFont          *pub,
13938 				Picture          dst,
13939 				int              srcx,
13940 				int              srcy,
13941 				int              x,
13942 				int              y,
13943 				const char  *string,
13944 				int              len);
13945 
13946 	void XftTextRenderUtf8 (Display          *dpy,
13947 				int              op,
13948 				Picture          src,
13949 				XftFont          *pub,
13950 				Picture          dst,
13951 				int              srcx,
13952 				int              srcy,
13953 				int              x,
13954 				int              y,
13955 				const char  *string,
13956 				int              len);
13957 
13958 	void XftTextRenderUtf16 (Display         *dpy,
13959 				int             op,
13960 				Picture         src,
13961 				XftFont         *pub,
13962 				Picture         dst,
13963 				int             srcx,
13964 				int             srcy,
13965 				int             x,
13966 				int             y,
13967 				const char *string,
13968 				FcEndian        endian,
13969 				int             len);
13970 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13971 
13972 	}
13973 
13974 	interface FontConfig {
13975 	extern(C) @nogc pure:
13976 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13977 		void FcFontSetDestroy(FcFontSet*);
13978 		char* FcNameUnparse(const FcPattern *);
13979 	}
13980 
13981 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13982 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13983 
13984 
13985 	/* Xft } */
13986 
13987 	class XDisconnectException : Exception {
13988 		bool userRequested;
13989 		this(bool userRequested = true) {
13990 			this.userRequested = userRequested;
13991 			super("X disconnected");
13992 		}
13993 	}
13994 
13995 	/++
13996 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
13997 
13998 		Please note that it returns
13999 	+/
14000 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14001 
14002 		static XErrorEvent[] errorBuffer;
14003 
14004 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14005 			errorBuffer ~= *evt;
14006 			return 0;
14007 		}
14008 
14009 		auto savedErrorHandler = XSetErrorHandler(&handler);
14010 
14011 		try {
14012 			dg();
14013 		} finally {
14014 			XSync(XDisplayConnection.get, 0/*False*/);
14015 			XSetErrorHandler(savedErrorHandler);
14016 		}
14017 
14018 		auto bfr = errorBuffer;
14019 		errorBuffer = null;
14020 
14021 		return bfr;
14022 	}
14023 
14024 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14025 	class XDisplayConnection {
14026 		private __gshared Display* display;
14027 		private __gshared XIM xim;
14028 		private __gshared char* displayName;
14029 
14030 		private __gshared int connectionSequence_;
14031 		private __gshared bool isLocal_;
14032 
14033 		/// use this for lazy caching when reconnection
14034 		static int connectionSequenceNumber() { return connectionSequence_; }
14035 
14036 		/++
14037 			Guesses if the connection appears to be local.
14038 
14039 			History:
14040 				Added June 3, 2021
14041 		+/
14042 		static @property bool isLocal() nothrow @trusted @nogc {
14043 			return isLocal_;
14044 		}
14045 
14046 		/// Attempts recreation of state, may require application assistance
14047 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14048 		/// then call this, and if successful, reenter the loop.
14049 		static void discardAndRecreate(string newDisplayString = null) {
14050 			if(insideXEventLoop)
14051 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14052 
14053 			// 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
14054 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14055 
14056 			foreach(handle; chnenhm) {
14057 				handle.discardConnectionState();
14058 			}
14059 
14060 			discardState();
14061 
14062 			if(newDisplayString !is null)
14063 				setDisplayName(newDisplayString);
14064 
14065 			auto display = get();
14066 
14067 			foreach(handle; chnenhm) {
14068 				handle.recreateAfterDisconnect();
14069 			}
14070 		}
14071 
14072 		private __gshared EventMask rootEventMask;
14073 
14074 		/++
14075 			Requests the specified input from the root window on the connection, in addition to any other request.
14076 
14077 
14078 			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.
14079 
14080 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14081 		+/
14082 		static void addRootInput(EventMask mask) {
14083 			auto old = rootEventMask;
14084 			rootEventMask |= mask;
14085 			get(); // to ensure display connected
14086 			if(display !is null && rootEventMask != old)
14087 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14088 		}
14089 
14090 		static void discardState() {
14091 			freeImages();
14092 
14093 			foreach(atomPtr; interredAtoms)
14094 				*atomPtr = 0;
14095 			interredAtoms = null;
14096 			interredAtoms.assumeSafeAppend();
14097 
14098 			ScreenPainterImplementation.fontAttempted = false;
14099 			ScreenPainterImplementation.defaultfont = null;
14100 			ScreenPainterImplementation.defaultfontset = null;
14101 
14102 			Image.impl.xshmQueryCompleted = false;
14103 			Image.impl._xshmAvailable = false;
14104 
14105 			SimpleWindow.nativeMapping = null;
14106 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14107 			// GlobalHotkeyManager
14108 
14109 			display = null;
14110 			xim = null;
14111 		}
14112 
14113 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14114 		private static void createXIM () {
14115 			import core.stdc.locale : setlocale, LC_ALL;
14116 			import core.stdc.stdio : stderr, fprintf;
14117 			import core.stdc.stdlib : free;
14118 			import core.stdc.string : strdup;
14119 
14120 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14121 
14122 			auto olocale = strdup(setlocale(LC_ALL, null));
14123 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14124 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14125 
14126 			//fprintf(stderr, "opening IM...\n");
14127 			foreach (string s; mtry) {
14128 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14129 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14130 			}
14131 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14132 		}
14133 
14134 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14135 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14136 		static struct ImgList {
14137 			size_t img; // class; hide it from GC
14138 			ImgList* next;
14139 		}
14140 
14141 		static __gshared ImgList* imglist = null;
14142 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14143 
14144 		static void registerImage (Image img) {
14145 			if (!imglistLocked && img !is null) {
14146 				import core.stdc.stdlib : malloc;
14147 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14148 				assert(it !is null); // do proper checks
14149 				it.img = cast(size_t)cast(void*)img;
14150 				it.next = imglist;
14151 				imglist = it;
14152 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14153 			}
14154 		}
14155 
14156 		static void unregisterImage (Image img) {
14157 			if (!imglistLocked && img !is null) {
14158 				import core.stdc.stdlib : free;
14159 				ImgList* prev = null;
14160 				ImgList* cur = imglist;
14161 				while (cur !is null) {
14162 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14163 					prev = cur;
14164 					cur = cur.next;
14165 				}
14166 				if (cur !is null) {
14167 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14168 					free(cur);
14169 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14170 				} else {
14171 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14172 				}
14173 			}
14174 		}
14175 
14176 		static void freeImages () { // needed for discardAndRecreate
14177 			imglistLocked = true;
14178 			scope(exit) imglistLocked = false;
14179 			ImgList* cur = imglist;
14180 			ImgList* next = null;
14181 			while (cur !is null) {
14182 				import core.stdc.stdlib : free;
14183 				next = cur.next;
14184 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14185 				(cast(Image)cast(void*)cur.img).dispose();
14186 				free(cur);
14187 				cur = next;
14188 			}
14189 			imglist = null;
14190 		}
14191 
14192 		/// can be used to override normal handling of display name
14193 		/// from environment and/or command line
14194 		static setDisplayName(string newDisplayName) {
14195 			displayName = cast(char*) (newDisplayName ~ '\0');
14196 		}
14197 
14198 		/// resets to the default display string
14199 		static resetDisplayName() {
14200 			displayName = null;
14201 		}
14202 
14203 		///
14204 		static Display* get() {
14205 			if(display is null) {
14206 				if(!librariesSuccessfullyLoaded)
14207 					throw new Exception("Unable to load X11 client libraries");
14208 				display = XOpenDisplay(displayName);
14209 
14210 				isLocal_ = false;
14211 
14212 				connectionSequence_++;
14213 				if(display is null)
14214 					throw new Exception("Unable to open X display");
14215 
14216 				auto str = display.display_name;
14217 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14218 				// and otherwise it probably isn't
14219 				if(str is null || (str[0] != ':' && str[0] != '/'))
14220 					isLocal_ = false;
14221 				else
14222 					isLocal_ = true;
14223 
14224 				debug(sdpy_x_errors) {
14225 					XSetErrorHandler(&adrlogger);
14226 					XSynchronize(display, true);
14227 
14228 					extern(C) int wtf() {
14229 						if(errorHappened) {
14230 							asm { int 3; }
14231 							errorHappened = false;
14232 						}
14233 						return 0;
14234 					}
14235 					XSetAfterFunction(display, &wtf);
14236 				}
14237 
14238 
14239 				XSetIOErrorHandler(&x11ioerrCB);
14240 				Bool sup;
14241 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14242 				createXIM();
14243 				version(with_eventloop) {
14244 					import arsd.eventloop;
14245 					addFileEventListeners(display.fd, &eventListener, null, null);
14246 				}
14247 			}
14248 
14249 			return display;
14250 		}
14251 
14252 		extern(C)
14253 		static int x11ioerrCB(Display* dpy) {
14254 			throw new XDisconnectException(false);
14255 		}
14256 
14257 		version(with_eventloop) {
14258 			import arsd.eventloop;
14259 			static void eventListener(OsFileHandle fd) {
14260 				//this.mtLock();
14261 				//scope(exit) this.mtUnlock();
14262 				while(XPending(display))
14263 					doXNextEvent(display);
14264 			}
14265 		}
14266 
14267 		// close connection on program exit -- we need this to properly free all images
14268 		static ~this () {
14269 			// the gui thread must clean up after itself or else Xlib might deadlock
14270 			// using this flag on any thread destruction is the easiest way i know of
14271 			// (shared static this is run by the LAST thread to exit, which may not be
14272 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14273 			if(thisIsGuiThread)
14274 				close();
14275 		}
14276 
14277 		///
14278 		static void close() {
14279 			if(display is null)
14280 				return;
14281 
14282 			version(with_eventloop) {
14283 				import arsd.eventloop;
14284 				removeFileEventListeners(display.fd);
14285 			}
14286 
14287 			// now remove all registered images to prevent shared memory leaks
14288 			freeImages();
14289 
14290 			// tbh I don't know why it is doing this but like if this happens to run
14291 			// from the other thread there's frequent hanging inside here.
14292 			if(thisIsGuiThread)
14293 				XCloseDisplay(display);
14294 			display = null;
14295 		}
14296 	}
14297 
14298 	mixin template NativeImageImplementation() {
14299 		XImage* handle;
14300 		ubyte* rawData;
14301 
14302 		XShmSegmentInfo shminfo;
14303 		bool premultiply = true;
14304 
14305 		__gshared bool xshmQueryCompleted;
14306 		__gshared bool _xshmAvailable;
14307 		public static @property bool xshmAvailable() {
14308 			if(!xshmQueryCompleted) {
14309 				int i1, i2, i3;
14310 				xshmQueryCompleted = true;
14311 
14312 				if(!XDisplayConnection.isLocal)
14313 					_xshmAvailable = false;
14314 				else
14315 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14316 			}
14317 			return _xshmAvailable;
14318 		}
14319 
14320 		bool usingXshm;
14321 	final:
14322 
14323 		private __gshared bool xshmfailed;
14324 
14325 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14326 			auto display = XDisplayConnection.get();
14327 			assert(display !is null);
14328 			auto screen = DefaultScreen(display);
14329 
14330 			// it will only use shared memory for somewhat largish images,
14331 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14332 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14333 
14334 
14335 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14336 				// the actual use still fails. For example, if the program is in a container and permission denied
14337 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14338 				//
14339 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14340 
14341 
14342 				// synchronize so preexisting buffers are clear
14343 				XSync(display, false);
14344 				xshmfailed = false;
14345 
14346 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14347 
14348 
14349 				usingXshm = true;
14350 				handle = XShmCreateImage(
14351 					display,
14352 					DefaultVisual(display, screen),
14353 					enableAlpha ? 32: 24,
14354 					ImageFormat.ZPixmap,
14355 					null,
14356 					&shminfo,
14357 					width, height);
14358 				if(handle is null)
14359 					goto abortXshm1;
14360 
14361 				if(handle.bytes_per_line != 4 * width)
14362 					goto abortXshm2;
14363 
14364 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14365 				if(shminfo.shmid < 0)
14366 					goto abortXshm3;
14367 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14368 				if(rawData == cast(ubyte*) -1)
14369 					goto abortXshm4;
14370 				shminfo.readOnly = 0;
14371 				XShmAttach(display, &shminfo);
14372 
14373 				// and now to the final error check to ensure it actually worked.
14374 				XSync(display, false);
14375 				if(xshmfailed)
14376 					goto abortXshm5;
14377 
14378 				XSetErrorHandler(oldErrorHandler);
14379 
14380 				XDisplayConnection.registerImage(this);
14381 				// if I don't flush here there's a chance the dtor will run before the
14382 				// ctor and lead to a bad value X error. While this hurts the efficiency
14383 				// it is local anyway so prolly better to keep it simple
14384 				XFlush(display);
14385 
14386 				return;
14387 
14388 				abortXshm5:
14389 					shmdt(shminfo.shmaddr);
14390 					rawData = null;
14391 
14392 				abortXshm4:
14393 					shmctl(shminfo.shmid, IPC_RMID, null);
14394 
14395 				abortXshm3:
14396 					// nothing needed, the shmget failed so there's nothing to free
14397 
14398 				abortXshm2:
14399 					XDestroyImage(handle);
14400 					handle = null;
14401 
14402 				abortXshm1:
14403 					XSetErrorHandler(oldErrorHandler);
14404 					usingXshm = false;
14405 					handle = null;
14406 
14407 					shminfo = typeof(shminfo).init;
14408 
14409 					_xshmAvailable = false; // don't try again in the future
14410 
14411 					// writeln("fallingback");
14412 
14413 					goto fallback;
14414 
14415 			} else {
14416 				fallback:
14417 
14418 				if (forcexshm) throw new Exception("can't create XShm Image");
14419 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14420 				import core.stdc.stdlib : malloc;
14421 				rawData = cast(ubyte*) malloc(width * height * 4);
14422 
14423 				handle = XCreateImage(
14424 					display,
14425 					DefaultVisual(display, screen),
14426 					enableAlpha ? 32 : 24, // bpp
14427 					ImageFormat.ZPixmap,
14428 					0, // offset
14429 					rawData,
14430 					width, height,
14431 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14432 			}
14433 		}
14434 
14435 		void dispose() {
14436 			// note: this calls free(rawData) for us
14437 			if(handle) {
14438 				if (usingXshm) {
14439 					XDisplayConnection.unregisterImage(this);
14440 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14441 				}
14442 				XDestroyImage(handle);
14443 				if(usingXshm) {
14444 					shmdt(shminfo.shmaddr);
14445 					shmctl(shminfo.shmid, IPC_RMID, null);
14446 				}
14447 				handle = null;
14448 			}
14449 		}
14450 
14451 		Color getPixel(int x, int y) {
14452 			auto offset = (y * width + x) * 4;
14453 			Color c;
14454 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14455 			c.b = rawData[offset + 0];
14456 			c.g = rawData[offset + 1];
14457 			c.r = rawData[offset + 2];
14458 			if(enableAlpha && premultiply)
14459 				c.unPremultiply;
14460 			return c;
14461 		}
14462 
14463 		void setPixel(int x, int y, Color c) {
14464 			if(enableAlpha && premultiply)
14465 				c.premultiply();
14466 			auto offset = (y * width + x) * 4;
14467 			rawData[offset + 0] = c.b;
14468 			rawData[offset + 1] = c.g;
14469 			rawData[offset + 2] = c.r;
14470 			if(enableAlpha)
14471 				rawData[offset + 3] = c.a;
14472 		}
14473 
14474 		void convertToRgbaBytes(ubyte[] where) {
14475 			assert(where.length == this.width * this.height * 4);
14476 
14477 			// if rawData had a length....
14478 			//assert(rawData.length == where.length);
14479 			for(int idx = 0; idx < where.length; idx += 4) {
14480 				where[idx + 0] = rawData[idx + 2]; // r
14481 				where[idx + 1] = rawData[idx + 1]; // g
14482 				where[idx + 2] = rawData[idx + 0]; // b
14483 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14484 
14485 				if(enableAlpha && premultiply)
14486 					unPremultiplyRgba(where[idx .. idx + 4]);
14487 			}
14488 		}
14489 
14490 		void setFromRgbaBytes(in ubyte[] where) {
14491 			assert(where.length == this.width * this.height * 4);
14492 
14493 			// if rawData had a length....
14494 			//assert(rawData.length == where.length);
14495 			for(int idx = 0; idx < where.length; idx += 4) {
14496 				rawData[idx + 2] = where[idx + 0]; // r
14497 				rawData[idx + 1] = where[idx + 1]; // g
14498 				rawData[idx + 0] = where[idx + 2]; // b
14499 				if(enableAlpha) {
14500 					rawData[idx + 3] = where[idx + 3]; // a
14501 					if(premultiply)
14502 						premultiplyBgra(rawData[idx .. idx + 4]);
14503 				}
14504 			}
14505 		}
14506 
14507 	}
14508 
14509 	mixin template NativeSimpleWindowImplementation() {
14510 		GC gc;
14511 		Window window;
14512 		Display* display;
14513 
14514 		Pixmap buffer;
14515 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14516 		XIC xic; // input context
14517 		int curHidden = 0; // counter
14518 		Cursor blankCurPtr = 0;
14519 		int cursorSequenceNumber = 0;
14520 		int warpEventCount = 0; // number of mouse movement events to eat
14521 
14522 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14523 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14524 
14525 		version(without_opengl) {} else
14526 		GLXContext glc;
14527 
14528 		private void fixFixedSize(bool forced=false) (int width, int height) {
14529 			if (forced || this.resizability == Resizability.fixedSize) {
14530 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14531 				XSizeHints sh;
14532 				static if (!forced) {
14533 					c_long spr;
14534 					XGetWMNormalHints(display, window, &sh, &spr);
14535 					sh.flags |= PMaxSize | PMinSize;
14536 				} else {
14537 					sh.flags = PMaxSize | PMinSize;
14538 				}
14539 				sh.min_width = width;
14540 				sh.min_height = height;
14541 				sh.max_width = width;
14542 				sh.max_height = height;
14543 				XSetWMNormalHints(display, window, &sh);
14544 				//XFlush(display);
14545 			}
14546 		}
14547 
14548 		ScreenPainter getPainter(bool manualInvalidations) {
14549 			return ScreenPainter(this, window, manualInvalidations);
14550 		}
14551 
14552 		void move(int x, int y) {
14553 			XMoveWindow(display, window, x, y);
14554 		}
14555 
14556 		void resize(int w, int h) {
14557 			if (w < 1) w = 1;
14558 			if (h < 1) h = 1;
14559 			XResizeWindow(display, window, w, h);
14560 
14561 			// calling this now to avoid waiting for the server to
14562 			// acknowledge the resize; draws without returning to the
14563 			// event loop will thus actually work. the server's event
14564 			// btw might overrule this and resize it again
14565 			recordX11Resize(display, this, w, h);
14566 
14567 			updateOpenglViewportIfNeeded(w, h);
14568 		}
14569 
14570 		void moveResize (int x, int y, int w, int h) {
14571 			if (w < 1) w = 1;
14572 			if (h < 1) h = 1;
14573 			XMoveResizeWindow(display, window, x, y, w, h);
14574 			updateOpenglViewportIfNeeded(w, h);
14575 		}
14576 
14577 		void hideCursor () {
14578 			if (curHidden++ == 0) {
14579 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14580 					static const(char)[1] cmbmp = 0;
14581 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14582 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14583 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14584 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14585 					XFreePixmap(display, pm);
14586 				}
14587 				XDefineCursor(display, window, blankCurPtr);
14588 			}
14589 		}
14590 
14591 		void showCursor () {
14592 			if (--curHidden == 0) XUndefineCursor(display, window);
14593 		}
14594 
14595 		void warpMouse (int x, int y) {
14596 			// here i will send dummy "ignore next mouse motion" event,
14597 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14598 			// and we don't need to report it to the user (as warping is
14599 			// used when the user needs movement deltas).
14600 			//XClientMessageEvent xclient;
14601 			XEvent e;
14602 			e.xclient.type = EventType.ClientMessage;
14603 			e.xclient.window = window;
14604 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14605 			e.xclient.format = 32;
14606 			e.xclient.data.l[0] = 0;
14607 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14608 			//{ 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]); }
14609 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14610 			// now warp pointer...
14611 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14612 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14613 			// ...and flush
14614 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14615 			XFlush(display);
14616 		}
14617 
14618 		void sendDummyEvent () {
14619 			// here i will send dummy event to ping event queue
14620 			XEvent e;
14621 			e.xclient.type = EventType.ClientMessage;
14622 			e.xclient.window = window;
14623 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14624 			e.xclient.format = 32;
14625 			e.xclient.data.l[0] = 0;
14626 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14627 			XFlush(display);
14628 		}
14629 
14630 		void setTitle(string title) {
14631 			if (title.ptr is null) title = "";
14632 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14633 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14634 			XTextProperty windowName;
14635 			windowName.value = title.ptr;
14636 			windowName.encoding = XA_UTF8; //XA_STRING;
14637 			windowName.format = 8;
14638 			windowName.nitems = cast(uint)title.length;
14639 			XSetWMName(display, window, &windowName);
14640 			char[1024] namebuf = 0;
14641 			auto maxlen = namebuf.length-1;
14642 			if (maxlen > title.length) maxlen = title.length;
14643 			namebuf[0..maxlen] = title[0..maxlen];
14644 			XStoreName(display, window, namebuf.ptr);
14645 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14646 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14647 		}
14648 
14649 		string[] getTitles() {
14650 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14651 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14652 			XTextProperty textProp;
14653 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14654 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14655 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14656 				} else
14657 					return [];
14658 			} else
14659 				return null;
14660 		}
14661 
14662 		string getTitle() {
14663 			auto titles = getTitles();
14664 			return titles.length ? titles[0] : null;
14665 		}
14666 
14667 		void setMinSize (int minwidth, int minheight) {
14668 			import core.stdc.config : c_long;
14669 			if (minwidth < 1) minwidth = 1;
14670 			if (minheight < 1) minheight = 1;
14671 			XSizeHints sh;
14672 			c_long spr;
14673 			XGetWMNormalHints(display, window, &sh, &spr);
14674 			sh.min_width = minwidth;
14675 			sh.min_height = minheight;
14676 			sh.flags |= PMinSize;
14677 			XSetWMNormalHints(display, window, &sh);
14678 			flushGui();
14679 		}
14680 
14681 		void setMaxSize (int maxwidth, int maxheight) {
14682 			import core.stdc.config : c_long;
14683 			if (maxwidth < 1) maxwidth = 1;
14684 			if (maxheight < 1) maxheight = 1;
14685 			XSizeHints sh;
14686 			c_long spr;
14687 			XGetWMNormalHints(display, window, &sh, &spr);
14688 			sh.max_width = maxwidth;
14689 			sh.max_height = maxheight;
14690 			sh.flags |= PMaxSize;
14691 			XSetWMNormalHints(display, window, &sh);
14692 			flushGui();
14693 		}
14694 
14695 		void setResizeGranularity (int granx, int grany) {
14696 			import core.stdc.config : c_long;
14697 			if (granx < 1) granx = 1;
14698 			if (grany < 1) grany = 1;
14699 			XSizeHints sh;
14700 			c_long spr;
14701 			XGetWMNormalHints(display, window, &sh, &spr);
14702 			sh.width_inc = granx;
14703 			sh.height_inc = grany;
14704 			sh.flags |= PResizeInc;
14705 			XSetWMNormalHints(display, window, &sh);
14706 			flushGui();
14707 		}
14708 
14709 		void setOpacity (uint opacity) {
14710 			arch_ulong o = opacity;
14711 			if (opacity == uint.max)
14712 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14713 			else
14714 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14715 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14716 		}
14717 
14718 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14719 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14720 			display = XDisplayConnection.get();
14721 			auto screen = DefaultScreen(display);
14722 
14723 			bool overrideRedirect = false;
14724 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14725 				overrideRedirect = true;
14726 
14727 			version(without_opengl) {}
14728 			else {
14729 				if(opengl == OpenGlOptions.yes) {
14730 					GLXFBConfig fbconf = null;
14731 					XVisualInfo* vi = null;
14732 					bool useLegacy = false;
14733 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14734 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14735 						int[23] visualAttribs = [
14736 							GLX_X_RENDERABLE , 1/*True*/,
14737 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14738 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14739 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14740 							GLX_RED_SIZE     , 8,
14741 							GLX_GREEN_SIZE   , 8,
14742 							GLX_BLUE_SIZE    , 8,
14743 							GLX_ALPHA_SIZE   , 8,
14744 							GLX_DEPTH_SIZE   , 24,
14745 							GLX_STENCIL_SIZE , 8,
14746 							GLX_DOUBLEBUFFER , 1/*True*/,
14747 							0/*None*/,
14748 						];
14749 						int fbcount;
14750 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14751 						if (fbcount == 0) {
14752 							useLegacy = true; // try to do at least something
14753 						} else {
14754 							// pick the FB config/visual with the most samples per pixel
14755 							int bestidx = -1, bestns = -1;
14756 							foreach (int fbi; 0..fbcount) {
14757 								int sb, samples;
14758 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14759 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14760 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14761 							}
14762 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14763 							fbconf = fbc[bestidx];
14764 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14765 							XFree(fbc);
14766 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14767 						}
14768 					}
14769 					if (vi is null || useLegacy) {
14770 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14771 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14772 						useLegacy = true;
14773 					}
14774 					if (vi is null) throw new Exception("no open gl visual found");
14775 
14776 					XSetWindowAttributes swa;
14777 					auto root = RootWindow(display, screen);
14778 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14779 
14780 					swa.override_redirect = overrideRedirect;
14781 
14782 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14783 						0, 0, width, height,
14784 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14785 
14786 					// now try to use `glXCreateContextAttribsARB()` if it's here
14787 					if (!useLegacy) {
14788 						// request fairly advanced context, even with stencil buffer!
14789 						int[9] contextAttribs = [
14790 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14791 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14792 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14793 							// for modern context, set "forward compatibility" flag too
14794 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14795 							0/*None*/,
14796 						];
14797 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14798 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14799 							sdpyOpenGLContextVersion = 0;
14800 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14801 						}
14802 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14803 					} else {
14804 						// fallback to old GLX call
14805 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14806 							sdpyOpenGLContextVersion = 0;
14807 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14808 						}
14809 					}
14810 					// sync to ensure any errors generated are processed
14811 					XSync(display, 0/*False*/);
14812 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14813 					if(glc is null)
14814 						throw new Exception("glc");
14815 				}
14816 			}
14817 
14818 			if(opengl == OpenGlOptions.no) {
14819 
14820 				XSetWindowAttributes swa;
14821 				swa.background_pixel = WhitePixel(display, screen);
14822 				swa.border_pixel = BlackPixel(display, screen);
14823 				swa.override_redirect = overrideRedirect;
14824 				auto root = RootWindow(display, screen);
14825 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14826 
14827 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14828 					0, 0, width, height,
14829 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14830 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14831 
14832 
14833 
14834 				/*
14835 				window = XCreateSimpleWindow(
14836 					display,
14837 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14838 					0, 0, // x, y
14839 					width, height,
14840 					1, // border width
14841 					BlackPixel(display, screen), // border
14842 					WhitePixel(display, screen)); // background
14843 				*/
14844 
14845 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14846 				bufferw = width;
14847 				bufferh = height;
14848 
14849 				gc = DefaultGC(display, screen);
14850 
14851 				// clear out the buffer to get us started...
14852 				XSetForeground(display, gc, WhitePixel(display, screen));
14853 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14854 				XSetForeground(display, gc, BlackPixel(display, screen));
14855 			}
14856 
14857 			// input context
14858 			//TODO: create this only for top-level windows, and reuse that?
14859 			populateXic();
14860 
14861 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14862 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14863 			// window class
14864 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14865 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14866 				XClassHint klass;
14867 				XWMHints wh;
14868 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14869 					wh.input = true;
14870 					wh.flags |= InputHint;
14871 				}
14872 				XSizeHints size;
14873 				klass.res_name = sdpyWindowClassStr;
14874 				klass.res_class = sdpyWindowClassStr;
14875 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14876 			}
14877 
14878 			setTitle(title);
14879 			SimpleWindow.nativeMapping[window] = this;
14880 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14881 
14882 			// This gives our window a close button
14883 			if (windowType != WindowTypes.eventOnly) {
14884 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14885 				int useAtoms;
14886 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14887 					useAtoms = 2;
14888 				} else {
14889 					useAtoms = 1;
14890 				}
14891 				assert(useAtoms <= atoms.length);
14892 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14893 			}
14894 
14895 			// FIXME: windowType and customizationFlags
14896 			Atom[8] wsatoms; // here, due to goto
14897 			int wmsacount = 0; // here, due to goto
14898 
14899 			try
14900 			final switch(windowType) {
14901 				case WindowTypes.normal:
14902 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14903 				break;
14904 				case WindowTypes.undecorated:
14905 					motifHideDecorations();
14906 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14907 				break;
14908 				case WindowTypes.eventOnly:
14909 					_hidden = true;
14910 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14911 					goto hiddenWindow;
14912 				//break;
14913 				case WindowTypes.nestedChild:
14914 					// handled in XCreateWindow calls
14915 				break;
14916 
14917 				case WindowTypes.dropdownMenu:
14918 					motifHideDecorations();
14919 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14920 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14921 				break;
14922 				case WindowTypes.popupMenu:
14923 					motifHideDecorations();
14924 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14925 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14926 				break;
14927 				case WindowTypes.notification:
14928 					motifHideDecorations();
14929 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14930 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14931 				break;
14932 				case WindowTypes.minimallyWrapped:
14933 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14934 				/+
14935 				case WindowTypes.menu:
14936 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14937 					motifHideDecorations();
14938 				break;
14939 				case WindowTypes.desktop:
14940 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14941 				break;
14942 				case WindowTypes.dock:
14943 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14944 				break;
14945 				case WindowTypes.toolbar:
14946 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14947 				break;
14948 				case WindowTypes.menu:
14949 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14950 				break;
14951 				case WindowTypes.utility:
14952 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14953 				break;
14954 				case WindowTypes.splash:
14955 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14956 				break;
14957 				case WindowTypes.dialog:
14958 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14959 				break;
14960 				case WindowTypes.tooltip:
14961 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14962 				break;
14963 				case WindowTypes.notification:
14964 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14965 				break;
14966 				case WindowTypes.combo:
14967 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14968 				break;
14969 				case WindowTypes.dnd:
14970 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14971 				break;
14972 				+/
14973 			}
14974 			catch(Exception e) {
14975 				// XInternAtom failed, prolly a WM
14976 				// that doesn't support these things
14977 			}
14978 
14979 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14980 			// the two following flags may be ignored by WM
14981 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14982 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14983 
14984 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14985 
14986 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14987 
14988 			// What would be ideal here is if they only were
14989 			// selected if there was actually an event handler
14990 			// for them...
14991 
14992 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14993 
14994 			hiddenWindow:
14995 
14996 			// set the pid property for lookup later by window managers
14997 			// a standard convenience
14998 			import core.sys.posix.unistd;
14999 			arch_ulong pid = getpid();
15000 
15001 			XChangeProperty(
15002 				display,
15003 				impl.window,
15004 				GetAtom!("_NET_WM_PID", true)(display),
15005 				XA_CARDINAL,
15006 				32 /* bits */,
15007 				0 /*PropModeReplace*/,
15008 				&pid,
15009 				1);
15010 
15011 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15012 				if(parent is null) assert(0);
15013 				XChangeProperty(
15014 					display,
15015 					impl.window,
15016 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15017 					XA_WINDOW,
15018 					32 /* bits */,
15019 					0 /*PropModeReplace*/,
15020 					&parent.impl.window,
15021 					1);
15022 
15023 			}
15024 
15025 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15026 				XMapWindow(display, window);
15027 			} else {
15028 				_hidden = true;
15029 			}
15030 		}
15031 
15032 		void populateXic() {
15033 			if (XDisplayConnection.xim !is null) {
15034 				xic = XCreateIC(XDisplayConnection.xim,
15035 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15036 						/*XNClientWindow*/"clientWindow".ptr, window,
15037 						/*XNFocusWindow*/"focusWindow".ptr, window,
15038 						null);
15039 				if (xic is null) {
15040 					import core.stdc.stdio : stderr, fprintf;
15041 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15042 				}
15043 			}
15044 		}
15045 
15046 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15047 			auto mask = EventMask.ExposureMask |
15048 				EventMask.KeyPressMask |
15049 				EventMask.KeyReleaseMask |
15050 				EventMask.PropertyChangeMask |
15051 				EventMask.FocusChangeMask |
15052 				EventMask.StructureNotifyMask |
15053 				EventMask.SubstructureNotifyMask |
15054 				EventMask.VisibilityChangeMask
15055 				| EventMask.ButtonPressMask
15056 				| EventMask.ButtonReleaseMask
15057 			;
15058 
15059 			// xshm is our shortcut for local connections
15060 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15061 				mask |= EventMask.PointerMotionMask;
15062 			else
15063 				mask |= EventMask.ButtonMotionMask;
15064 
15065 			XSelectInput(display, window, mask);
15066 		}
15067 
15068 
15069 		void setNetWMWindowType(Atom type) {
15070 			Atom[2] atoms;
15071 
15072 			atoms[0] = type;
15073 			// generic fallback
15074 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15075 
15076 			XChangeProperty(
15077 				display,
15078 				impl.window,
15079 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15080 				XA_ATOM,
15081 				32 /* bits */,
15082 				0 /*PropModeReplace*/,
15083 				atoms.ptr,
15084 				cast(int) atoms.length);
15085 		}
15086 
15087 		void motifHideDecorations(bool hide = true) {
15088 			MwmHints hints;
15089 			hints.flags = MWM_HINTS_DECORATIONS;
15090 			hints.decorations = hide ? 0 : 1;
15091 
15092 			XChangeProperty(
15093 				display,
15094 				impl.window,
15095 				GetAtom!"_MOTIF_WM_HINTS"(display),
15096 				GetAtom!"_MOTIF_WM_HINTS"(display),
15097 				32 /* bits */,
15098 				0 /*PropModeReplace*/,
15099 				&hints,
15100 				hints.sizeof / 4);
15101 		}
15102 
15103 		/*k8: unused
15104 		void createOpenGlContext() {
15105 
15106 		}
15107 		*/
15108 
15109 		void closeWindow() {
15110 			// I can't close this or a child window closing will
15111 			// break events for everyone. So I'm just leaking it right
15112 			// now and that is probably perfectly fine...
15113 			version(none)
15114 			if (customEventFDRead != -1) {
15115 				import core.sys.posix.unistd : close;
15116 				auto same = customEventFDRead == customEventFDWrite;
15117 
15118 				close(customEventFDRead);
15119 				if(!same)
15120 					close(customEventFDWrite);
15121 				customEventFDRead = -1;
15122 				customEventFDWrite = -1;
15123 			}
15124 
15125 			version(without_opengl) {} else
15126 			if(glc !is null) {
15127 				glXDestroyContext(display, glc);
15128 				glc = null;
15129 			}
15130 
15131 			if(buffer)
15132 				XFreePixmap(display, buffer);
15133 			bufferw = bufferh = 0;
15134 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15135 			XDestroyWindow(display, window);
15136 			XFlush(display);
15137 		}
15138 
15139 		void dispose() {
15140 		}
15141 
15142 		bool destroyed = false;
15143 	}
15144 
15145 	bool insideXEventLoop;
15146 }
15147 
15148 version(X11) {
15149 
15150 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15151 
15152 	private class ResizeEvent {
15153 		int width, height;
15154 	}
15155 
15156 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15157 		if(win.windowType == WindowTypes.minimallyWrapped)
15158 			return;
15159 
15160 		if(win.pendingResizeEvent is null) {
15161 			win.pendingResizeEvent = new ResizeEvent();
15162 			win.addEventListener((ResizeEvent re) {
15163 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15164 			});
15165 		}
15166 		win.pendingResizeEvent.width = width;
15167 		win.pendingResizeEvent.height = height;
15168 		if(!win.eventQueued!ResizeEvent) {
15169 			win.postEvent(win.pendingResizeEvent);
15170 		}
15171 	}
15172 
15173 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15174 		if(win.windowType == WindowTypes.minimallyWrapped)
15175 			return;
15176 		if(win.closed)
15177 			return;
15178 
15179 		if(width != win.width || height != win.height) {
15180 
15181 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15182 			win._width = width;
15183 			win._height = height;
15184 
15185 			if(win.openglMode == OpenGlOptions.no) {
15186 				// FIXME: could this be more efficient?
15187 
15188 				if (win.bufferw < width || win.bufferh < height) {
15189 					//{ 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); }
15190 					// grow the internal buffer to match the window...
15191 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15192 					{
15193 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15194 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15195 						scope(exit) XFreeGC(win.display, xgc);
15196 						XSetClipMask(win.display, xgc, None);
15197 						XSetForeground(win.display, xgc, 0);
15198 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15199 					}
15200 					XCopyArea(display,
15201 						cast(Drawable) win.buffer,
15202 						cast(Drawable) newPixmap,
15203 						win.gc, 0, 0,
15204 						win.bufferw < width ? win.bufferw : win.width,
15205 						win.bufferh < height ? win.bufferh : win.height,
15206 						0, 0);
15207 
15208 					XFreePixmap(display, win.buffer);
15209 					win.buffer = newPixmap;
15210 					win.bufferw = width;
15211 					win.bufferh = height;
15212 				}
15213 
15214 				// clear unused parts of the buffer
15215 				if (win.bufferw > width || win.bufferh > height) {
15216 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15217 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15218 					scope(exit) XFreeGC(win.display, xgc);
15219 					XSetClipMask(win.display, xgc, None);
15220 					XSetForeground(win.display, xgc, 0);
15221 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15222 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15223 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15224 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15225 				}
15226 
15227 			}
15228 
15229 			win.updateOpenglViewportIfNeeded(width, height);
15230 
15231 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15232 
15233 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15234 			if(win.windowResized !is null) {
15235 				XUnlockDisplay(display);
15236 				scope(exit) XLockDisplay(display);
15237 				win.windowResized(width, height);
15238 			}
15239 		}
15240 	}
15241 
15242 
15243 	/// Platform-specific, you might use it when doing a custom event loop.
15244 	bool doXNextEvent(Display* display) {
15245 		bool done;
15246 		XEvent e;
15247 		XNextEvent(display, &e);
15248 		version(sddddd) {
15249 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15250 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15251 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15252 			}
15253 		}
15254 
15255 		// filter out compose events
15256 		if (XFilterEvent(&e, None)) {
15257 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15258 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15259 			return false;
15260 		}
15261 		// process keyboard mapping changes
15262 		if (e.type == EventType.KeymapNotify) {
15263 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15264 			XRefreshKeyboardMapping(&e.xmapping);
15265 			return false;
15266 		}
15267 
15268 		version(with_eventloop)
15269 			import arsd.eventloop;
15270 
15271 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15272 			// see windows impl's comments
15273 			XUnlockDisplay(display);
15274 			scope(exit) XLockDisplay(display);
15275 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15276 			if(ret == 0)
15277 				return done;
15278 		}
15279 
15280 
15281 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15282 			if(win.getNativeEventHandler !is null) {
15283 				XUnlockDisplay(display);
15284 				scope(exit) XLockDisplay(display);
15285 				auto ret = win.getNativeEventHandler()(e);
15286 				if(ret == 0)
15287 					return done;
15288 			}
15289 		}
15290 
15291 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15292 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15293 				// we get this because of the RRScreenChangeNotifyMask
15294 
15295 				// this isn't actually an ideal way to do it since it wastes time
15296 				// but meh it is simple and it works.
15297 				win.actualDpiLoadAttempted = false;
15298 				SimpleWindow.xRandrInfoLoadAttemped = false;
15299 				win.updateActualDpi(); // trigger a reload
15300 			}
15301 		}
15302 
15303 		switch(e.type) {
15304 		  case EventType.SelectionClear:
15305 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15306 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15307 				// writeln("SelectionClear");
15308 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15309 			}
15310 		  break;
15311 		  case EventType.SelectionRequest:
15312 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15313 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15314 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15315 				XUnlockDisplay(display);
15316 				scope(exit) XLockDisplay(display);
15317 				(*ssh).handleRequest(e);
15318 			}
15319 		  break;
15320 		  case EventType.PropertyNotify:
15321 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15322 
15323 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15324 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15325 					ssh.sendMoreIncr(&e.xproperty);
15326 			}
15327 
15328 
15329 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15330 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15331 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15332 					Atom target;
15333 					int format;
15334 					arch_ulong bytesafter, length;
15335 					void* value;
15336 
15337 					ubyte[] s;
15338 					Atom targetToKeep;
15339 
15340 					XGetWindowProperty(
15341 						e.xproperty.display,
15342 						e.xproperty.window,
15343 						e.xproperty.atom,
15344 						0,
15345 						100000 /* length */,
15346 						true, /* erase it to signal we got it and want more */
15347 						0 /*AnyPropertyType*/,
15348 						&target, &format, &length, &bytesafter, &value);
15349 
15350 					if(!targetToKeep)
15351 						targetToKeep = target;
15352 
15353 					auto id = (cast(ubyte*) value)[0 .. length];
15354 
15355 					handler.handleIncrData(targetToKeep, id);
15356 
15357 					XFree(value);
15358 				}
15359 			}
15360 		  break;
15361 		  case EventType.SelectionNotify:
15362 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15363 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15364 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15365 					XUnlockDisplay(display);
15366 					scope(exit) XLockDisplay(display);
15367 					handler.handleData(None, null);
15368 				} else {
15369 					Atom target;
15370 					int format;
15371 					arch_ulong bytesafter, length;
15372 					void* value;
15373 					XGetWindowProperty(
15374 						e.xselection.display,
15375 						e.xselection.requestor,
15376 						e.xselection.property,
15377 						0,
15378 						100000 /* length */,
15379 						//false, /* don't erase it */
15380 						true, /* do erase it lol */
15381 						0 /*AnyPropertyType*/,
15382 						&target, &format, &length, &bytesafter, &value);
15383 
15384 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15385 
15386 					{
15387 						XUnlockDisplay(display);
15388 						scope(exit) XLockDisplay(display);
15389 
15390 						if(target == XA_ATOM) {
15391 							// initial request, see what they are able to work with and request the best one
15392 							// we can handle, if available
15393 
15394 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15395 							Atom best = handler.findBestFormat(answer);
15396 
15397 							/+
15398 							writeln("got ", answer);
15399 							foreach(a; answer)
15400 								printf("%s\n", XGetAtomName(display, a));
15401 							writeln("best ", best);
15402 							+/
15403 
15404 							if(best != None) {
15405 								// actually request the best format
15406 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15407 							}
15408 						} else if(target == GetAtom!"INCR"(display)) {
15409 							// incremental
15410 
15411 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15412 
15413 							// signal the sending program that we see
15414 							// the incr and are ready to receive more.
15415 							XDeleteProperty(
15416 								e.xselection.display,
15417 								e.xselection.requestor,
15418 								e.xselection.property);
15419 						} else {
15420 							// unsupported type... maybe, forward
15421 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15422 						}
15423 					}
15424 					XFree(value);
15425 					/*
15426 					XDeleteProperty(
15427 						e.xselection.display,
15428 						e.xselection.requestor,
15429 						e.xselection.property);
15430 					*/
15431 				}
15432 			}
15433 		  break;
15434 		  case EventType.ConfigureNotify:
15435 			auto event = e.xconfigure;
15436 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15437 				if(win.windowType == WindowTypes.minimallyWrapped)
15438 					break;
15439 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15440 
15441 				/+
15442 					The ICCCM says window managers must send a synthetic event when the window
15443 					is moved but NOT when it is resized. In the resize case, an event is sent
15444 					with position (0, 0) which can be wrong and break the dpi calculations.
15445 
15446 					So we only consider the synthetic events from the WM and otherwise
15447 					need to wait for some other event to get the position which... sucks.
15448 
15449 					I'd rather not have windows changing their layout on mouse motion after
15450 					switching monitors... might be forced to but for now just ignoring it.
15451 
15452 					Easiest way to switch monitors without sending a size position is by
15453 					maximize or fullscreen in a setup like mine, but on most setups those
15454 					work on the monitor it is already living on, so it should be ok most the
15455 					time.
15456 				+/
15457 				if(event.send_event) {
15458 					win.screenPositionKnown = true;
15459 					win.screenPositionX = event.x;
15460 					win.screenPositionY = event.y;
15461 					win.updateActualDpi();
15462 				}
15463 
15464 				win.updateIMEPopupLocation();
15465 				recordX11ResizeAsync(display, *win, event.width, event.height);
15466 			}
15467 		  break;
15468 		  case EventType.Expose:
15469 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15470 				if(win.windowType == WindowTypes.minimallyWrapped)
15471 					break;
15472 				// if it is closing from a popup menu, it can get
15473 				// an Expose event right by the end and trigger a
15474 				// BadDrawable error ... we'll just check
15475 				// closed to handle that.
15476 				if((*win).closed) break;
15477 				if((*win).openglMode == OpenGlOptions.no) {
15478 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15479 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15480 					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);
15481 				} else {
15482 					// need to redraw the scene somehow
15483 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15484 						XUnlockDisplay(display);
15485 						scope(exit) XLockDisplay(display);
15486 						version(without_opengl) {} else
15487 						win.redrawOpenGlSceneSoon();
15488 					}
15489 				}
15490 			}
15491 		  break;
15492 		  case EventType.FocusIn:
15493 		  case EventType.FocusOut:
15494 
15495 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15496 				/+
15497 
15498 				void info(string detail) {
15499 					string s;
15500 					// import std.conv;
15501 					// import std.datetime;
15502 					s ~= to!string(Clock.currTime);
15503 					s ~= " ";
15504 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15505 					s ~= " ";
15506 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15507 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15508 					s ~= detail;
15509 					s ~= " ";
15510 
15511 					sdpyPrintDebugString(s);
15512 
15513 				}
15514 
15515 				switch(e.xfocus.detail) {
15516 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15517 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15518 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15519 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15520 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15521 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15522 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15523 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15524 					default:
15525 
15526 				}
15527 				+/
15528 
15529 
15530 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15531 					break; // just ignore these they seem irrelevant
15532 
15533 				auto old = win._focused;
15534 				win._focused = e.type == EventType.FocusIn;
15535 
15536 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15537 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15538 					win._focused = true;
15539 
15540 				if(win.demandingAttention)
15541 					demandAttention(*win, false);
15542 
15543 				win.updateIMEFocused();
15544 
15545 				if(old != win._focused && win.onFocusChange) {
15546 					XUnlockDisplay(display);
15547 					scope(exit) XLockDisplay(display);
15548 					win.onFocusChange(win._focused);
15549 				}
15550 			}
15551 		  break;
15552 		  case EventType.VisibilityNotify:
15553 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15554 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15555 						if (win.visibilityChanged !is null) {
15556 								XUnlockDisplay(display);
15557 								scope(exit) XLockDisplay(display);
15558 								win.visibilityChanged(false);
15559 							}
15560 					} else {
15561 						if (win.visibilityChanged !is null) {
15562 							XUnlockDisplay(display);
15563 							scope(exit) XLockDisplay(display);
15564 							win.visibilityChanged(true);
15565 						}
15566 					}
15567 				}
15568 				break;
15569 		  case EventType.ClientMessage:
15570 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15571 					// "ignore next mouse motion" event, increment ignore counter for teh window
15572 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15573 						++(*win).warpEventCount;
15574 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15575 					} else {
15576 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15577 					}
15578 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15579 					// user clicked the close button on the window manager
15580 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15581 						XUnlockDisplay(display);
15582 						scope(exit) XLockDisplay(display);
15583 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15584 					}
15585 
15586 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15587 					// writeln("HAPPENED");
15588 					// user clicked the close button on the window manager
15589 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15590 						XUnlockDisplay(display);
15591 						scope(exit) XLockDisplay(display);
15592 
15593 						auto setTo = *win;
15594 
15595 						if(win.setRequestedInputFocus !is null) {
15596 							auto s = win.setRequestedInputFocus();
15597 							if(s !is null) {
15598 								setTo = s;
15599 							}
15600 						}
15601 
15602 						assert(setTo !is null);
15603 
15604 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15605 
15606 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15607 					}
15608 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15609 					foreach(nai; NotificationAreaIcon.activeIcons)
15610 						nai.newManager();
15611 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15612 
15613 					bool xDragWindow = true;
15614 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15615 						//XDefineCursor(display, xDragWindow.impl.window,
15616 							//writeln("XdndStatus ", e.xclient.data.l);
15617 					}
15618 					if(auto dh = win.dropHandler) {
15619 
15620 						static Atom[3] xFormatsBuffer;
15621 						static Atom[] xFormats;
15622 
15623 						void resetXFormats() {
15624 							xFormatsBuffer[] = 0;
15625 							xFormats = xFormatsBuffer[];
15626 						}
15627 
15628 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15629 							// on Windows it is supposed to return the effect you actually do FIXME
15630 
15631 							auto sourceWindow =  e.xclient.data.l[0];
15632 
15633 							xFormatsBuffer[0] = e.xclient.data.l[2];
15634 							xFormatsBuffer[1] = e.xclient.data.l[3];
15635 							xFormatsBuffer[2] = e.xclient.data.l[4];
15636 
15637 							if(e.xclient.data.l[1] & 1) {
15638 								// can just grab it all but like we don't necessarily need them...
15639 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15640 							} else {
15641 								int len;
15642 								foreach(fmt; xFormatsBuffer)
15643 									if(fmt) len++;
15644 								xFormats = xFormatsBuffer[0 .. len];
15645 							}
15646 
15647 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15648 
15649 							dh.dragEnter(&pkg);
15650 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15651 
15652 							auto pack = e.xclient.data.l[2];
15653 
15654 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15655 
15656 
15657 							XClientMessageEvent xclient;
15658 
15659 							xclient.type = EventType.ClientMessage;
15660 							xclient.window = e.xclient.data.l[0];
15661 							xclient.message_type = GetAtom!"XdndStatus"(display);
15662 							xclient.format = 32;
15663 							xclient.data.l[0] = win.impl.window;
15664 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15665 							auto r = result.consistentWithin;
15666 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15667 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15668 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15669 
15670 							XSendEvent(
15671 								display,
15672 								e.xclient.data.l[0],
15673 								false,
15674 								EventMask.NoEventMask,
15675 								cast(XEvent*) &xclient
15676 							);
15677 
15678 
15679 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15680 							//writeln("XdndLeave");
15681 							// drop cancelled.
15682 							// data.l[0] is the source window
15683 							dh.dragLeave();
15684 
15685 							resetXFormats();
15686 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15687 							// drop happening, should fetch data, then send finished
15688 							// writeln("XdndDrop");
15689 
15690 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15691 
15692 							dh.drop(&pkg);
15693 
15694 							resetXFormats();
15695 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15696 							// writeln("XdndFinished");
15697 
15698 							dh.finish();
15699 						}
15700 
15701 					}
15702 				}
15703 		  break;
15704 		  case EventType.MapNotify:
15705 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15706 					(*win)._visible = true;
15707 					if (!(*win)._visibleForTheFirstTimeCalled) {
15708 						(*win)._visibleForTheFirstTimeCalled = true;
15709 						if ((*win).visibleForTheFirstTime !is null) {
15710 							XUnlockDisplay(display);
15711 							scope(exit) XLockDisplay(display);
15712 							(*win).visibleForTheFirstTime();
15713 						}
15714 					}
15715 					if ((*win).visibilityChanged !is null) {
15716 						XUnlockDisplay(display);
15717 						scope(exit) XLockDisplay(display);
15718 						(*win).visibilityChanged(true);
15719 					}
15720 				}
15721 		  break;
15722 		  case EventType.UnmapNotify:
15723 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15724 					win._visible = false;
15725 					if (win.visibilityChanged !is null) {
15726 						XUnlockDisplay(display);
15727 						scope(exit) XLockDisplay(display);
15728 						win.visibilityChanged(false);
15729 					}
15730 			}
15731 		  break;
15732 		  case EventType.DestroyNotify:
15733 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15734 				if(win.destroyed)
15735 					break; // might get a notification both for itself and from its parent
15736 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15737 				win._closed = true; // just in case
15738 				win.destroyed = true;
15739 				if (win.xic !is null) {
15740 					XDestroyIC(win.xic);
15741 					win.xic = null; // just in case
15742 				}
15743 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15744 				bool anyImportant = false;
15745 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15746 					if(w.beingOpenKeepsAppOpen) {
15747 						anyImportant = true;
15748 						break;
15749 					}
15750 				if(!anyImportant) {
15751 					EventLoop.quitApplication();
15752 					done = true;
15753 				}
15754 			}
15755 			auto window = e.xdestroywindow.window;
15756 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15757 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15758 
15759 			version(with_eventloop) {
15760 				if(done) exit();
15761 			}
15762 		  break;
15763 
15764 		  case EventType.MotionNotify:
15765 			MouseEvent mouse;
15766 			auto event = e.xmotion;
15767 
15768 			mouse.type = MouseEventType.motion;
15769 			mouse.x = event.x;
15770 			mouse.y = event.y;
15771 			mouse.modifierState = event.state;
15772 
15773 			mouse.timestamp = event.time;
15774 
15775 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15776 				mouse.window = *win;
15777 				if (win.warpEventCount > 0) {
15778 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15779 					--(*win).warpEventCount;
15780 					(*win).mdx(mouse); // so deltas will be correctly updated
15781 				} else {
15782 					win.warpEventCount = 0; // just in case
15783 					(*win).mdx(mouse);
15784 					if((*win).handleMouseEvent) {
15785 						XUnlockDisplay(display);
15786 						scope(exit) XLockDisplay(display);
15787 						(*win).handleMouseEvent(mouse);
15788 					}
15789 				}
15790 			}
15791 
15792 		  	version(with_eventloop)
15793 				send(mouse);
15794 		  break;
15795 		  case EventType.ButtonPress:
15796 		  case EventType.ButtonRelease:
15797 			MouseEvent mouse;
15798 			auto event = e.xbutton;
15799 
15800 			mouse.timestamp = event.time;
15801 
15802 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15803 			mouse.x = event.x;
15804 			mouse.y = event.y;
15805 
15806 			static Time lastMouseDownTime = 0;
15807 			static int lastMouseDownButton = -1;
15808 
15809 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15810 			if(e.type == EventType.ButtonPress) {
15811 				lastMouseDownTime = event.time;
15812 				lastMouseDownButton = event.button;
15813 			}
15814 
15815 			switch(event.button) {
15816 				case 1: mouse.button = MouseButton.left; break; // left
15817 				case 2: mouse.button = MouseButton.middle; break; // middle
15818 				case 3: mouse.button = MouseButton.right; break; // right
15819 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15820 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15821 				case 6: break; // idk
15822 				case 7: break; // idk
15823 				case 8: mouse.button = MouseButton.backButton; break;
15824 				case 9: mouse.button = MouseButton.forwardButton; break;
15825 				default:
15826 			}
15827 
15828 			// FIXME: double check this
15829 			mouse.modifierState = event.state;
15830 
15831 			//mouse.modifierState = event.detail;
15832 
15833 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15834 				mouse.window = *win;
15835 				(*win).mdx(mouse);
15836 				if((*win).handleMouseEvent) {
15837 					XUnlockDisplay(display);
15838 					scope(exit) XLockDisplay(display);
15839 					(*win).handleMouseEvent(mouse);
15840 				}
15841 			}
15842 			version(with_eventloop)
15843 				send(mouse);
15844 		  break;
15845 
15846 		  case EventType.KeyPress:
15847 		  case EventType.KeyRelease:
15848 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15849 			KeyEvent ke;
15850 			ke.pressed = e.type == EventType.KeyPress;
15851 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15852 
15853 			auto sym = XKeycodeToKeysym(
15854 				XDisplayConnection.get(),
15855 				e.xkey.keycode,
15856 				0);
15857 
15858 			ke.key = cast(Key) sym;//e.xkey.keycode;
15859 
15860 			ke.modifierState = e.xkey.state;
15861 
15862 			// writefln("%x", sym);
15863 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15864 			int charbuflen = 0; // return value of XwcLookupString
15865 			if (ke.pressed) {
15866 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15867 				if (win !is null && win.xic !is null) {
15868 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15869 					Status status;
15870 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15871 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15872 				} else {
15873 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15874 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15875 					char[16] buffer;
15876 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15877 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15878 				}
15879 			}
15880 
15881 			// if there's no char, subst one
15882 			if (charbuflen == 0) {
15883 				switch (sym) {
15884 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15885 					case 0xff8d: // keypad enter
15886 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15887 					default : // ignore
15888 				}
15889 			}
15890 
15891 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15892 				ke.window = *win;
15893 
15894 
15895 				if(win.inputProxy)
15896 					win = &win.inputProxy;
15897 
15898 				// char events are separate since they are on Windows too
15899 				// also, xcompose can generate long char sequences
15900 				// don't send char events if Meta and/or Hyper is pressed
15901 				// TODO: ctrl+char should only send control chars; not yet
15902 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15903 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15904 				}
15905 
15906 				dchar[32] charsComingBuffer;
15907 				int charsComingPosition;
15908 				dchar[] charsComing = charsComingBuffer[];
15909 
15910 				if (ke.pressed && charbuflen > 0) {
15911 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15912 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15913 						if(charsComingPosition >= charsComing.length)
15914 							charsComing.length = charsComingPosition + 8;
15915 
15916 						charsComing[charsComingPosition++] = ch;
15917 					}
15918 
15919 					charsComing = charsComing[0 .. charsComingPosition];
15920 				} else {
15921 					charsComing = null;
15922 				}
15923 
15924 				ke.charsPossible = charsComing;
15925 
15926 				if (win.handleKeyEvent) {
15927 					XUnlockDisplay(display);
15928 					scope(exit) XLockDisplay(display);
15929 					win.handleKeyEvent(ke);
15930 				}
15931 
15932 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15933 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15934 					XUnlockDisplay(display);
15935 					scope(exit) XLockDisplay(display);
15936 					foreach(ch; charsComing)
15937 						win.handleCharEvent(ch);
15938 				}
15939 			}
15940 
15941 			version(with_eventloop)
15942 				send(ke);
15943 		  break;
15944 		  default:
15945 		}
15946 
15947 		return done;
15948 	}
15949 }
15950 
15951 /* *************************************** */
15952 /*      Done with simpledisplay stuff      */
15953 /* *************************************** */
15954 
15955 // Necessary C library bindings follow
15956 version(Windows) {} else
15957 version(X11) {
15958 
15959 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15960 
15961 // X11 bindings needed here
15962 /*
15963 	A little of this is from the bindings project on
15964 	D Source and some of it is copy/paste from the C
15965 	header.
15966 
15967 	The DSource listing consistently used D's long
15968 	where C used long. That's wrong - C long is 32 bit, so
15969 	it should be int in D. I changed that here.
15970 
15971 	Note:
15972 	This isn't complete, just took what I needed for myself.
15973 */
15974 
15975 import core.stdc.stddef : wchar_t;
15976 
15977 interface XLib {
15978 extern(C) nothrow @nogc {
15979 	char* XResourceManagerString(Display*);
15980 	void XrmInitialize();
15981 	XrmDatabase XrmGetStringDatabase(char* data);
15982 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15983 
15984 	Cursor XCreateFontCursor(Display*, uint shape);
15985 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15986 	int XUndefineCursor(Display* display, Window w);
15987 
15988 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15989 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15990 	int XFreeCursor(Display* display, Cursor cursor);
15991 
15992 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
15993 
15994 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
15995 
15996 	XVaNestedList XVaCreateNestedList(int unused, ...);
15997 
15998 	char *XKeysymToString(KeySym keysym);
15999 	KeySym XKeycodeToKeysym(
16000 		Display*		/* display */,
16001 		KeyCode		/* keycode */,
16002 		int			/* index */
16003 	);
16004 
16005 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16006 
16007 	int XFree(void*);
16008 	int XDeleteProperty(Display *display, Window w, Atom property);
16009 
16010 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16011 
16012 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16013 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16014 		*actual_type_return, int *actual_format_return, arch_ulong
16015 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16016 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16017 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16018 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16019 
16020 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16021 
16022 	Window XGetSelectionOwner(Display *display, Atom selection);
16023 
16024 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16025 
16026 	char** XListFonts(Display*, const char*, int, int*);
16027 	void XFreeFontNames(char**);
16028 
16029 	Display* XOpenDisplay(const char*);
16030 	int XCloseDisplay(Display*);
16031 
16032 	int function() XSynchronize(Display*, bool);
16033 	int function() XSetAfterFunction(Display*, int function() proc);
16034 
16035 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16036 
16037 	Bool XSupportsLocale();
16038 	char* XSetLocaleModifiers(const(char)* modifier_list);
16039 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16040 	Status XCloseOM(XOM om);
16041 
16042 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16043 	Status XCloseIM(XIM im);
16044 
16045 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16046 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16047 	Display* XDisplayOfIM(XIM im);
16048 	char* XLocaleOfIM(XIM im);
16049 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16050 	void XDestroyIC(XIC ic);
16051 	void XSetICFocus(XIC ic);
16052 	void XUnsetICFocus(XIC ic);
16053 	//wchar_t* XwcResetIC(XIC ic);
16054 	char* XmbResetIC(XIC ic);
16055 	char* Xutf8ResetIC(XIC ic);
16056 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16057 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16058 	XIM XIMOfIC(XIC ic);
16059 
16060 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16061 
16062 
16063 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16064 	int XFreeFont(Display *display, XFontStruct *font_struct);
16065 	int XSetFont(Display* display, GC gc, Font font);
16066 	int XTextWidth(XFontStruct*, scope const char*, int);
16067 
16068 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16069 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16070 
16071 	Window XCreateSimpleWindow(
16072 		Display*	/* display */,
16073 		Window		/* parent */,
16074 		int			/* x */,
16075 		int			/* y */,
16076 		uint		/* width */,
16077 		uint		/* height */,
16078 		uint		/* border_width */,
16079 		uint		/* border */,
16080 		uint		/* background */
16081 	);
16082 	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);
16083 
16084 	int XReparentWindow(Display*, Window, Window, int, int);
16085 	int XClearWindow(Display*, Window);
16086 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16087 	int XMoveWindow(Display*, Window, int, int);
16088 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16089 
16090 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16091 
16092 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16093 
16094 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16095 
16096 	XImage *XCreateImage(
16097 		Display*		/* display */,
16098 		Visual*		/* visual */,
16099 		uint	/* depth */,
16100 		int			/* format */,
16101 		int			/* offset */,
16102 		ubyte*		/* data */,
16103 		uint	/* width */,
16104 		uint	/* height */,
16105 		int			/* bitmap_pad */,
16106 		int			/* bytes_per_line */
16107 	);
16108 
16109 	Status XInitImage (XImage* image);
16110 
16111 	Atom XInternAtom(
16112 		Display*		/* display */,
16113 		const char*	/* atom_name */,
16114 		Bool		/* only_if_exists */
16115 	);
16116 
16117 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16118 	char* XGetAtomName(Display*, Atom);
16119 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16120 
16121 	int XPutImage(
16122 		Display*	/* display */,
16123 		Drawable	/* d */,
16124 		GC			/* gc */,
16125 		XImage*	/* image */,
16126 		int			/* src_x */,
16127 		int			/* src_y */,
16128 		int			/* dest_x */,
16129 		int			/* dest_y */,
16130 		uint		/* width */,
16131 		uint		/* height */
16132 	);
16133 
16134 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16135 
16136 
16137 	int XDestroyWindow(
16138 		Display*	/* display */,
16139 		Window		/* w */
16140 	);
16141 
16142 	int XDestroyImage(XImage*);
16143 
16144 	int XSelectInput(
16145 		Display*	/* display */,
16146 		Window		/* w */,
16147 		EventMask	/* event_mask */
16148 	);
16149 
16150 	int XMapWindow(
16151 		Display*	/* display */,
16152 		Window		/* w */
16153 	);
16154 
16155 	Status XIconifyWindow(Display*, Window, int);
16156 	int XMapRaised(Display*, Window);
16157 	int XMapSubwindows(Display*, Window);
16158 
16159 	int XNextEvent(
16160 		Display*	/* display */,
16161 		XEvent*		/* event_return */
16162 	);
16163 
16164 	int XMaskEvent(Display*, arch_long, XEvent*);
16165 
16166 	Bool XFilterEvent(XEvent *event, Window window);
16167 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16168 
16169 	Status XSetWMProtocols(
16170 		Display*	/* display */,
16171 		Window		/* w */,
16172 		Atom*		/* protocols */,
16173 		int			/* count */
16174 	);
16175 
16176 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16177 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16178 
16179 
16180 	Status XInitThreads();
16181 	void XLockDisplay (Display* display);
16182 	void XUnlockDisplay (Display* display);
16183 
16184 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16185 
16186 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16187 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16188 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16189 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16190 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16191 
16192 
16193 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16194 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16195 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16196 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16197 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16198 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16199 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16200 	int XDrawPoint(Display*, Drawable, GC, int, int);
16201 	int XSetForeground(Display*, GC, uint);
16202 	int XSetBackground(Display*, GC, uint);
16203 
16204 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16205 	void XFreeFontSet(Display*, XFontSet);
16206 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16207 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16208 
16209 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16210 
16211 
16212 //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);
16213 
16214 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16215 	int XSetFunction(Display*, GC, int);
16216 
16217 	GC XCreateGC(Display*, Drawable, uint, void*);
16218 	int XCopyGC(Display*, GC, uint, GC);
16219 	int XFreeGC(Display*, GC);
16220 
16221 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16222 	bool XCheckMaskEvent(Display*, int, XEvent*);
16223 
16224 	int XPending(Display*);
16225 	int XEventsQueued(Display* display, int mode);
16226 
16227 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16228 	int XFreePixmap(Display*, Pixmap);
16229 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16230 	int XFlush(Display*);
16231 	int XBell(Display*, int);
16232 	int XSync(Display*, bool);
16233 
16234 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16235 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16236 
16237 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16238 	int XUngrabKeyboard(Display*, Time);
16239 
16240 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16241 
16242 	KeySym XStringToKeysym(const char *string);
16243 
16244 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16245 
16246 	Window XDefaultRootWindow(Display*);
16247 
16248 	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);
16249 
16250 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16251 
16252 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16253 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16254 
16255 	Status XAllocColor(Display*, Colormap, XColor*);
16256 
16257 	int XWithdrawWindow(Display*, Window, int);
16258 	int XUnmapWindow(Display*, Window);
16259 	int XLowerWindow(Display*, Window);
16260 	int XRaiseWindow(Display*, Window);
16261 
16262 	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);
16263 	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);
16264 
16265 	int XGetInputFocus(Display*, Window*, int*);
16266 	int XSetInputFocus(Display*, Window, int, Time);
16267 
16268 	XErrorHandler XSetErrorHandler(XErrorHandler);
16269 
16270 	int XGetErrorText(Display*, int, char*, int);
16271 
16272 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16273 
16274 
16275 	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);
16276 	int XUngrabPointer(Display *display, Time time);
16277 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16278 
16279 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16280 
16281 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16282 	int XSetClipMask(Display*, GC, Pixmap);
16283 	int XSetClipOrigin(Display*, GC, int, int);
16284 
16285 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16286 
16287 	void XSetWMName(Display*, Window, XTextProperty*);
16288 	Status XGetWMName(Display*, Window, XTextProperty*);
16289 	int XStoreName(Display* display, Window w, const(char)* window_name);
16290 
16291 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16292 
16293 }
16294 }
16295 
16296 interface Xext {
16297 extern(C) nothrow @nogc {
16298 	Status XShmAttach(Display*, XShmSegmentInfo*);
16299 	Status XShmDetach(Display*, XShmSegmentInfo*);
16300 	Status XShmPutImage(
16301 		Display*            /* dpy */,
16302 		Drawable            /* d */,
16303 		GC                  /* gc */,
16304 		XImage*             /* image */,
16305 		int                 /* src_x */,
16306 		int                 /* src_y */,
16307 		int                 /* dst_x */,
16308 		int                 /* dst_y */,
16309 		uint        /* src_width */,
16310 		uint        /* src_height */,
16311 		Bool                /* send_event */
16312 	);
16313 
16314 	Status XShmQueryExtension(Display*);
16315 
16316 	XImage *XShmCreateImage(
16317 		Display*            /* dpy */,
16318 		Visual*             /* visual */,
16319 		uint        /* depth */,
16320 		int                 /* format */,
16321 		char*               /* data */,
16322 		XShmSegmentInfo*    /* shminfo */,
16323 		uint        /* width */,
16324 		uint        /* height */
16325 	);
16326 
16327 	Pixmap XShmCreatePixmap(
16328 		Display*            /* dpy */,
16329 		Drawable            /* d */,
16330 		char*               /* data */,
16331 		XShmSegmentInfo*    /* shminfo */,
16332 		uint        /* width */,
16333 		uint        /* height */,
16334 		uint        /* depth */
16335 	);
16336 
16337 }
16338 }
16339 
16340 	// this requires -lXpm
16341 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16342 
16343 
16344 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16345 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16346 shared static this() {
16347 	xlib.loadDynamicLibrary();
16348 	xext.loadDynamicLibrary();
16349 }
16350 
16351 
16352 extern(C) nothrow @nogc {
16353 
16354 alias XrmDatabase = void*;
16355 struct XrmValue {
16356 	uint size;
16357 	void* addr;
16358 }
16359 
16360 struct XVisualInfo {
16361 	Visual* visual;
16362 	VisualID visualid;
16363 	int screen;
16364 	uint depth;
16365 	int c_class;
16366 	c_ulong red_mask;
16367 	c_ulong green_mask;
16368 	c_ulong blue_mask;
16369 	int colormap_size;
16370 	int bits_per_rgb;
16371 }
16372 
16373 enum VisualNoMask=	0x0;
16374 enum VisualIDMask=	0x1;
16375 enum VisualScreenMask=0x2;
16376 enum VisualDepthMask=	0x4;
16377 enum VisualClassMask=	0x8;
16378 enum VisualRedMaskMask=0x10;
16379 enum VisualGreenMaskMask=0x20;
16380 enum VisualBlueMaskMask=0x40;
16381 enum VisualColormapSizeMask=0x80;
16382 enum VisualBitsPerRGBMask=0x100;
16383 enum VisualAllMask=	0x1FF;
16384 
16385 enum AnyKey = 0;
16386 enum AnyModifier = 1 << 15;
16387 
16388 // XIM and other crap
16389 struct _XOM {}
16390 struct _XIM {}
16391 struct _XIC {}
16392 alias XOM = _XOM*;
16393 alias XIM = _XIM*;
16394 alias XIC = _XIC*;
16395 
16396 alias XVaNestedList = void*;
16397 
16398 alias XIMStyle = arch_ulong;
16399 enum : arch_ulong {
16400 	XIMPreeditArea      = 0x0001,
16401 	XIMPreeditCallbacks = 0x0002,
16402 	XIMPreeditPosition  = 0x0004,
16403 	XIMPreeditNothing   = 0x0008,
16404 	XIMPreeditNone      = 0x0010,
16405 	XIMStatusArea       = 0x0100,
16406 	XIMStatusCallbacks  = 0x0200,
16407 	XIMStatusNothing    = 0x0400,
16408 	XIMStatusNone       = 0x0800,
16409 }
16410 
16411 
16412 /* X Shared Memory Extension functions */
16413 	//pragma(lib, "Xshm");
16414 	alias arch_ulong ShmSeg;
16415 	struct XShmSegmentInfo {
16416 		ShmSeg shmseg;
16417 		int shmid;
16418 		ubyte* shmaddr;
16419 		Bool readOnly;
16420 	}
16421 
16422 	// and the necessary OS functions
16423 	int shmget(int, size_t, int);
16424 	void* shmat(int, scope const void*, int);
16425 	int shmdt(scope const void*);
16426 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16427 
16428 	enum IPC_PRIVATE = 0;
16429 	enum IPC_CREAT = 512;
16430 	enum IPC_RMID = 0;
16431 
16432 /* MIT-SHM end */
16433 
16434 
16435 enum MappingType:int {
16436 	MappingModifier		=0,
16437 	MappingKeyboard		=1,
16438 	MappingPointer		=2
16439 }
16440 
16441 /* ImageFormat -- PutImage, GetImage */
16442 enum ImageFormat:int {
16443 	XYBitmap	=0,	/* depth 1, XYFormat */
16444 	XYPixmap	=1,	/* depth == drawable depth */
16445 	ZPixmap	=2	/* depth == drawable depth */
16446 }
16447 
16448 enum ModifierName:int {
16449 	ShiftMapIndex	=0,
16450 	LockMapIndex	=1,
16451 	ControlMapIndex	=2,
16452 	Mod1MapIndex	=3,
16453 	Mod2MapIndex	=4,
16454 	Mod3MapIndex	=5,
16455 	Mod4MapIndex	=6,
16456 	Mod5MapIndex	=7
16457 }
16458 
16459 enum ButtonMask:int {
16460 	Button1Mask	=1<<8,
16461 	Button2Mask	=1<<9,
16462 	Button3Mask	=1<<10,
16463 	Button4Mask	=1<<11,
16464 	Button5Mask	=1<<12,
16465 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16466 }
16467 
16468 enum KeyOrButtonMask:uint {
16469 	ShiftMask	=1<<0,
16470 	LockMask	=1<<1,
16471 	ControlMask	=1<<2,
16472 	Mod1Mask	=1<<3,
16473 	Mod2Mask	=1<<4,
16474 	Mod3Mask	=1<<5,
16475 	Mod4Mask	=1<<6,
16476 	Mod5Mask	=1<<7,
16477 	Button1Mask	=1<<8,
16478 	Button2Mask	=1<<9,
16479 	Button3Mask	=1<<10,
16480 	Button4Mask	=1<<11,
16481 	Button5Mask	=1<<12,
16482 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16483 }
16484 
16485 enum ButtonName:int {
16486 	Button1	=1,
16487 	Button2	=2,
16488 	Button3	=3,
16489 	Button4	=4,
16490 	Button5	=5
16491 }
16492 
16493 /* Notify modes */
16494 enum NotifyModes:int
16495 {
16496 	NotifyNormal		=0,
16497 	NotifyGrab			=1,
16498 	NotifyUngrab		=2,
16499 	NotifyWhileGrabbed	=3
16500 }
16501 enum NotifyHint = 1;	/* for MotionNotify events */
16502 
16503 /* Notify detail */
16504 enum NotifyDetail:int
16505 {
16506 	NotifyAncestor			=0,
16507 	NotifyVirtual			=1,
16508 	NotifyInferior			=2,
16509 	NotifyNonlinear			=3,
16510 	NotifyNonlinearVirtual	=4,
16511 	NotifyPointer			=5,
16512 	NotifyPointerRoot		=6,
16513 	NotifyDetailNone		=7
16514 }
16515 
16516 /* Visibility notify */
16517 
16518 enum VisibilityNotify:int
16519 {
16520 VisibilityUnobscured		=0,
16521 VisibilityPartiallyObscured	=1,
16522 VisibilityFullyObscured		=2
16523 }
16524 
16525 
16526 enum WindowStackingMethod:int
16527 {
16528 	Above		=0,
16529 	Below		=1,
16530 	TopIf		=2,
16531 	BottomIf	=3,
16532 	Opposite	=4
16533 }
16534 
16535 /* Circulation request */
16536 enum CirculationRequest:int
16537 {
16538 	PlaceOnTop		=0,
16539 	PlaceOnBottom	=1
16540 }
16541 
16542 enum PropertyNotification:int
16543 {
16544 	PropertyNewValue	=0,
16545 	PropertyDelete		=1
16546 }
16547 
16548 enum ColorMapNotification:int
16549 {
16550 	ColormapUninstalled	=0,
16551 	ColormapInstalled		=1
16552 }
16553 
16554 
16555 	struct _XPrivate {}
16556 	struct _XrmHashBucketRec {}
16557 
16558 	alias void* XPointer;
16559 	alias void* XExtData;
16560 
16561 	version( X86_64 ) {
16562 		alias ulong XID;
16563 		alias ulong arch_ulong;
16564 		alias long arch_long;
16565 	} else version (AArch64) {
16566 		alias ulong XID;
16567 		alias ulong arch_ulong;
16568 		alias long arch_long;
16569 	} else {
16570 		alias uint XID;
16571 		alias uint arch_ulong;
16572 		alias int arch_long;
16573 	}
16574 
16575 	alias XID Window;
16576 	alias XID Drawable;
16577 	alias XID Pixmap;
16578 
16579 	alias arch_ulong Atom;
16580 	alias int Bool;
16581 	alias Display XDisplay;
16582 
16583 	alias int ByteOrder;
16584 	alias arch_ulong Time;
16585 	alias void ScreenFormat;
16586 
16587 	struct XImage {
16588 		int width, height;			/* size of image */
16589 		int xoffset;				/* number of pixels offset in X direction */
16590 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16591 		void *data;					/* pointer to image data */
16592 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16593 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16594 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16595 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16596 		int depth;					/* depth of image */
16597 		int bytes_per_line;			/* accelarator to next line */
16598 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16599 		arch_ulong red_mask;	/* bits in z arrangment */
16600 		arch_ulong green_mask;
16601 		arch_ulong blue_mask;
16602 		XPointer obdata;			/* hook for the object routines to hang on */
16603 		static struct F {				/* image manipulation routines */
16604 			XImage* function(
16605 				XDisplay* 			/* display */,
16606 				Visual*				/* visual */,
16607 				uint				/* depth */,
16608 				int					/* format */,
16609 				int					/* offset */,
16610 				ubyte*				/* data */,
16611 				uint				/* width */,
16612 				uint				/* height */,
16613 				int					/* bitmap_pad */,
16614 				int					/* bytes_per_line */) create_image;
16615 			int function(XImage *) destroy_image;
16616 			arch_ulong function(XImage *, int, int) get_pixel;
16617 			int function(XImage *, int, int, arch_ulong) put_pixel;
16618 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16619 			int function(XImage *, arch_long) add_pixel;
16620 		}
16621 		F f;
16622 	}
16623 	version(X86_64) static assert(XImage.sizeof == 136);
16624 	else version(X86) static assert(XImage.sizeof == 88);
16625 
16626 struct XCharStruct {
16627 	short       lbearing;       /* origin to left edge of raster */
16628 	short       rbearing;       /* origin to right edge of raster */
16629 	short       width;          /* advance to next char's origin */
16630 	short       ascent;         /* baseline to top edge of raster */
16631 	short       descent;        /* baseline to bottom edge of raster */
16632 	ushort attributes;  /* per char flags (not predefined) */
16633 }
16634 
16635 /*
16636  * To allow arbitrary information with fonts, there are additional properties
16637  * returned.
16638  */
16639 struct XFontProp {
16640 	Atom name;
16641 	arch_ulong card32;
16642 }
16643 
16644 alias Atom Font;
16645 
16646 struct XFontStruct {
16647 	XExtData *ext_data;           /* Hook for extension to hang data */
16648 	Font fid;                     /* Font ID for this font */
16649 	uint direction;           /* Direction the font is painted */
16650 	uint min_char_or_byte2;   /* First character */
16651 	uint max_char_or_byte2;   /* Last character */
16652 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16653 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16654 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16655 	uint default_char;        /* Char to print for undefined character */
16656 	int n_properties;             /* How many properties there are */
16657 	XFontProp *properties;        /* Pointer to array of additional properties*/
16658 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16659 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16660 	XCharStruct *per_char;        /* first_char to last_char information */
16661 	int ascent;                   /* Max extent above baseline for spacing */
16662 	int descent;                  /* Max descent below baseline for spacing */
16663 }
16664 
16665 
16666 /*
16667  * Definitions of specific events.
16668  */
16669 struct XKeyEvent
16670 {
16671 	int type;			/* of event */
16672 	arch_ulong serial;		/* # of last request processed by server */
16673 	Bool send_event;	/* true if this came from a SendEvent request */
16674 	Display *display;	/* Display the event was read from */
16675 	Window window;	        /* "event" window it is reported relative to */
16676 	Window root;	        /* root window that the event occurred on */
16677 	Window subwindow;	/* child window */
16678 	Time time;		/* milliseconds */
16679 	int x, y;		/* pointer x, y coordinates in event window */
16680 	int x_root, y_root;	/* coordinates relative to root */
16681 	KeyOrButtonMask state;	/* key or button mask */
16682 	uint keycode;	/* detail */
16683 	Bool same_screen;	/* same screen flag */
16684 }
16685 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16686 alias XKeyEvent XKeyPressedEvent;
16687 alias XKeyEvent XKeyReleasedEvent;
16688 
16689 struct XButtonEvent
16690 {
16691 	int type;		/* of event */
16692 	arch_ulong serial;	/* # of last request processed by server */
16693 	Bool send_event;	/* true if this came from a SendEvent request */
16694 	Display *display;	/* Display the event was read from */
16695 	Window window;	        /* "event" window it is reported relative to */
16696 	Window root;	        /* root window that the event occurred on */
16697 	Window subwindow;	/* child window */
16698 	Time time;		/* milliseconds */
16699 	int x, y;		/* pointer x, y coordinates in event window */
16700 	int x_root, y_root;	/* coordinates relative to root */
16701 	KeyOrButtonMask state;	/* key or button mask */
16702 	uint button;	/* detail */
16703 	Bool same_screen;	/* same screen flag */
16704 }
16705 alias XButtonEvent XButtonPressedEvent;
16706 alias XButtonEvent XButtonReleasedEvent;
16707 
16708 struct XMotionEvent{
16709 	int type;		/* of event */
16710 	arch_ulong serial;	/* # of last request processed by server */
16711 	Bool send_event;	/* true if this came from a SendEvent request */
16712 	Display *display;	/* Display the event was read from */
16713 	Window window;	        /* "event" window reported relative to */
16714 	Window root;	        /* root window that the event occurred on */
16715 	Window subwindow;	/* child window */
16716 	Time time;		/* milliseconds */
16717 	int x, y;		/* pointer x, y coordinates in event window */
16718 	int x_root, y_root;	/* coordinates relative to root */
16719 	KeyOrButtonMask state;	/* key or button mask */
16720 	byte is_hint;		/* detail */
16721 	Bool same_screen;	/* same screen flag */
16722 }
16723 alias XMotionEvent XPointerMovedEvent;
16724 
16725 struct XCrossingEvent{
16726 	int type;		/* of event */
16727 	arch_ulong serial;	/* # of last request processed by server */
16728 	Bool send_event;	/* true if this came from a SendEvent request */
16729 	Display *display;	/* Display the event was read from */
16730 	Window window;	        /* "event" window reported relative to */
16731 	Window root;	        /* root window that the event occurred on */
16732 	Window subwindow;	/* child window */
16733 	Time time;		/* milliseconds */
16734 	int x, y;		/* pointer x, y coordinates in event window */
16735 	int x_root, y_root;	/* coordinates relative to root */
16736 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16737 	NotifyDetail detail;
16738 	/*
16739 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16740 	 * NotifyNonlinear,NotifyNonlinearVirtual
16741 	 */
16742 	Bool same_screen;	/* same screen flag */
16743 	Bool focus;		/* Boolean focus */
16744 	KeyOrButtonMask state;	/* key or button mask */
16745 }
16746 alias XCrossingEvent XEnterWindowEvent;
16747 alias XCrossingEvent XLeaveWindowEvent;
16748 
16749 struct XFocusChangeEvent{
16750 	int type;		/* FocusIn or FocusOut */
16751 	arch_ulong serial;	/* # of last request processed by server */
16752 	Bool send_event;	/* true if this came from a SendEvent request */
16753 	Display *display;	/* Display the event was read from */
16754 	Window window;		/* window of event */
16755 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16756 				   NotifyGrab, NotifyUngrab */
16757 	NotifyDetail detail;
16758 	/*
16759 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16760 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16761 	 * NotifyPointerRoot, NotifyDetailNone
16762 	 */
16763 }
16764 alias XFocusChangeEvent XFocusInEvent;
16765 alias XFocusChangeEvent XFocusOutEvent;
16766 
16767 enum CWBackPixmap              = (1L<<0);
16768 enum CWBackPixel               = (1L<<1);
16769 enum CWBorderPixmap            = (1L<<2);
16770 enum CWBorderPixel             = (1L<<3);
16771 enum CWBitGravity              = (1L<<4);
16772 enum CWWinGravity              = (1L<<5);
16773 enum CWBackingStore            = (1L<<6);
16774 enum CWBackingPlanes           = (1L<<7);
16775 enum CWBackingPixel            = (1L<<8);
16776 enum CWOverrideRedirect        = (1L<<9);
16777 enum CWSaveUnder               = (1L<<10);
16778 enum CWEventMask               = (1L<<11);
16779 enum CWDontPropagate           = (1L<<12);
16780 enum CWColormap                = (1L<<13);
16781 enum CWCursor                  = (1L<<14);
16782 
16783 struct XWindowAttributes {
16784 	int x, y;			/* location of window */
16785 	int width, height;		/* width and height of window */
16786 	int border_width;		/* border width of window */
16787 	int depth;			/* depth of window */
16788 	Visual *visual;			/* the associated visual structure */
16789 	Window root;			/* root of screen containing window */
16790 	int class_;			/* InputOutput, InputOnly*/
16791 	int bit_gravity;		/* one of the bit gravity values */
16792 	int win_gravity;		/* one of the window gravity values */
16793 	int backing_store;		/* NotUseful, WhenMapped, Always */
16794 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16795 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16796 	Bool save_under;		/* boolean, should bits under be saved? */
16797 	Colormap colormap;		/* color map to be associated with window */
16798 	Bool map_installed;		/* boolean, is color map currently installed*/
16799 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16800 	arch_long all_event_masks;		/* set of events all people have interest in*/
16801 	arch_long your_event_mask;		/* my event mask */
16802 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16803 	Bool override_redirect;		/* boolean value for override-redirect */
16804 	Screen *screen;			/* back pointer to correct screen */
16805 }
16806 
16807 enum IsUnmapped = 0;
16808 enum IsUnviewable = 1;
16809 enum IsViewable = 2;
16810 
16811 struct XSetWindowAttributes {
16812 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16813 	arch_ulong background_pixel;/* background pixel */
16814 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16815 	arch_ulong border_pixel;/* border pixel value */
16816 	int bit_gravity;         /* one of bit gravity values */
16817 	int win_gravity;         /* one of the window gravity values */
16818 	int backing_store;       /* NotUseful, WhenMapped, Always */
16819 	arch_ulong backing_planes;/* planes to be preserved if possible */
16820 	arch_ulong backing_pixel;/* value to use in restoring planes */
16821 	Bool save_under;         /* should bits under be saved? (popups) */
16822 	arch_long event_mask;         /* set of events that should be saved */
16823 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16824 	Bool override_redirect;  /* boolean value for override_redirect */
16825 	Colormap colormap;       /* color map to be associated with window */
16826 	Cursor cursor;           /* cursor to be displayed (or None) */
16827 }
16828 
16829 
16830 alias int Status;
16831 
16832 
16833 enum EventMask:int
16834 {
16835 	NoEventMask				=0,
16836 	KeyPressMask			=1<<0,
16837 	KeyReleaseMask			=1<<1,
16838 	ButtonPressMask			=1<<2,
16839 	ButtonReleaseMask		=1<<3,
16840 	EnterWindowMask			=1<<4,
16841 	LeaveWindowMask			=1<<5,
16842 	PointerMotionMask		=1<<6,
16843 	PointerMotionHintMask	=1<<7,
16844 	Button1MotionMask		=1<<8,
16845 	Button2MotionMask		=1<<9,
16846 	Button3MotionMask		=1<<10,
16847 	Button4MotionMask		=1<<11,
16848 	Button5MotionMask		=1<<12,
16849 	ButtonMotionMask		=1<<13,
16850 	KeymapStateMask		=1<<14,
16851 	ExposureMask			=1<<15,
16852 	VisibilityChangeMask	=1<<16,
16853 	StructureNotifyMask		=1<<17,
16854 	ResizeRedirectMask		=1<<18,
16855 	SubstructureNotifyMask	=1<<19,
16856 	SubstructureRedirectMask=1<<20,
16857 	FocusChangeMask			=1<<21,
16858 	PropertyChangeMask		=1<<22,
16859 	ColormapChangeMask		=1<<23,
16860 	OwnerGrabButtonMask		=1<<24
16861 }
16862 
16863 struct MwmHints {
16864 	c_ulong flags;
16865 	c_ulong functions;
16866 	c_ulong decorations;
16867 	c_long input_mode;
16868 	c_ulong status;
16869 }
16870 
16871 enum {
16872 	MWM_HINTS_FUNCTIONS = (1L << 0),
16873 	MWM_HINTS_DECORATIONS =  (1L << 1),
16874 
16875 	MWM_FUNC_ALL = (1L << 0),
16876 	MWM_FUNC_RESIZE = (1L << 1),
16877 	MWM_FUNC_MOVE = (1L << 2),
16878 	MWM_FUNC_MINIMIZE = (1L << 3),
16879 	MWM_FUNC_MAXIMIZE = (1L << 4),
16880 	MWM_FUNC_CLOSE = (1L << 5),
16881 
16882 	MWM_DECOR_ALL = (1L << 0),
16883 	MWM_DECOR_BORDER = (1L << 1),
16884 	MWM_DECOR_RESIZEH = (1L << 2),
16885 	MWM_DECOR_TITLE = (1L << 3),
16886 	MWM_DECOR_MENU = (1L << 4),
16887 	MWM_DECOR_MINIMIZE = (1L << 5),
16888 	MWM_DECOR_MAXIMIZE = (1L << 6),
16889 }
16890 
16891 import core.stdc.config : c_long, c_ulong;
16892 
16893 	/* Size hints mask bits */
16894 
16895 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16896 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16897 	enum   PPosition   = (1L << 2)          /* program specified position */;
16898 	enum   PSize       = (1L << 3)          /* program specified size */;
16899 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16900 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16901 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16902 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16903 	enum   PBaseSize   = (1L << 8);
16904 	enum   PWinGravity = (1L << 9);
16905 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16906 	struct XSizeHints {
16907 		arch_long flags;         /* marks which fields in this structure are defined */
16908 		int x, y;           /* Obsolete */
16909 		int width, height;  /* Obsolete */
16910 		int min_width, min_height;
16911 		int max_width, max_height;
16912 		int width_inc, height_inc;
16913 		struct Aspect {
16914 			int x;       /* numerator */
16915 			int y;       /* denominator */
16916 		}
16917 
16918 		Aspect min_aspect;
16919 		Aspect max_aspect;
16920 		int base_width, base_height;
16921 		int win_gravity;
16922 		/* this structure may be extended in the future */
16923 	}
16924 
16925 
16926 
16927 enum EventType:int
16928 {
16929 	KeyPress			=2,
16930 	KeyRelease			=3,
16931 	ButtonPress			=4,
16932 	ButtonRelease		=5,
16933 	MotionNotify		=6,
16934 	EnterNotify			=7,
16935 	LeaveNotify			=8,
16936 	FocusIn				=9,
16937 	FocusOut			=10,
16938 	KeymapNotify		=11,
16939 	Expose				=12,
16940 	GraphicsExpose		=13,
16941 	NoExpose			=14,
16942 	VisibilityNotify	=15,
16943 	CreateNotify		=16,
16944 	DestroyNotify		=17,
16945 	UnmapNotify		=18,
16946 	MapNotify			=19,
16947 	MapRequest			=20,
16948 	ReparentNotify		=21,
16949 	ConfigureNotify		=22,
16950 	ConfigureRequest	=23,
16951 	GravityNotify		=24,
16952 	ResizeRequest		=25,
16953 	CirculateNotify		=26,
16954 	CirculateRequest	=27,
16955 	PropertyNotify		=28,
16956 	SelectionClear		=29,
16957 	SelectionRequest	=30,
16958 	SelectionNotify		=31,
16959 	ColormapNotify		=32,
16960 	ClientMessage		=33,
16961 	MappingNotify		=34,
16962 	LASTEvent			=35	/* must be bigger than any event # */
16963 }
16964 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16965 struct XKeymapEvent
16966 {
16967 	int type;
16968 	arch_ulong serial;	/* # of last request processed by server */
16969 	Bool send_event;	/* true if this came from a SendEvent request */
16970 	Display *display;	/* Display the event was read from */
16971 	Window window;
16972 	byte[32] key_vector;
16973 }
16974 
16975 struct XExposeEvent
16976 {
16977 	int type;
16978 	arch_ulong serial;	/* # of last request processed by server */
16979 	Bool send_event;	/* true if this came from a SendEvent request */
16980 	Display *display;	/* Display the event was read from */
16981 	Window window;
16982 	int x, y;
16983 	int width, height;
16984 	int count;		/* if non-zero, at least this many more */
16985 }
16986 
16987 struct XGraphicsExposeEvent{
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 x, y;
16994 	int width, height;
16995 	int count;		/* if non-zero, at least this many more */
16996 	int major_code;		/* core is CopyArea or CopyPlane */
16997 	int minor_code;		/* not defined in the core */
16998 }
16999 
17000 struct XNoExposeEvent{
17001 	int type;
17002 	arch_ulong serial;	/* # of last request processed by server */
17003 	Bool send_event;	/* true if this came from a SendEvent request */
17004 	Display *display;	/* Display the event was read from */
17005 	Drawable drawable;
17006 	int major_code;		/* core is CopyArea or CopyPlane */
17007 	int minor_code;		/* not defined in the core */
17008 }
17009 
17010 struct XVisibilityEvent{
17011 	int type;
17012 	arch_ulong serial;	/* # of last request processed by server */
17013 	Bool send_event;	/* true if this came from a SendEvent request */
17014 	Display *display;	/* Display the event was read from */
17015 	Window window;
17016 	VisibilityNotify state;		/* Visibility state */
17017 }
17018 
17019 struct XCreateWindowEvent{
17020 	int type;
17021 	arch_ulong serial;	/* # of last request processed by server */
17022 	Bool send_event;	/* true if this came from a SendEvent request */
17023 	Display *display;	/* Display the event was read from */
17024 	Window parent;		/* parent of the window */
17025 	Window window;		/* window id of window created */
17026 	int x, y;		/* window location */
17027 	int width, height;	/* size of window */
17028 	int border_width;	/* border width */
17029 	Bool override_redirect;	/* creation should be overridden */
17030 }
17031 
17032 struct XDestroyWindowEvent
17033 {
17034 	int type;
17035 	arch_ulong serial;		/* # of last request processed by server */
17036 	Bool send_event;	/* true if this came from a SendEvent request */
17037 	Display *display;	/* Display the event was read from */
17038 	Window event;
17039 	Window window;
17040 }
17041 
17042 struct XUnmapEvent
17043 {
17044 	int type;
17045 	arch_ulong serial;		/* # of last request processed by server */
17046 	Bool send_event;	/* true if this came from a SendEvent request */
17047 	Display *display;	/* Display the event was read from */
17048 	Window event;
17049 	Window window;
17050 	Bool from_configure;
17051 }
17052 
17053 struct XMapEvent
17054 {
17055 	int type;
17056 	arch_ulong serial;		/* # of last request processed by server */
17057 	Bool send_event;	/* true if this came from a SendEvent request */
17058 	Display *display;	/* Display the event was read from */
17059 	Window event;
17060 	Window window;
17061 	Bool override_redirect;	/* Boolean, is override set... */
17062 }
17063 
17064 struct XMapRequestEvent
17065 {
17066 	int type;
17067 	arch_ulong serial;	/* # of last request processed by server */
17068 	Bool send_event;	/* true if this came from a SendEvent request */
17069 	Display *display;	/* Display the event was read from */
17070 	Window parent;
17071 	Window window;
17072 }
17073 
17074 struct XReparentEvent
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 	Window parent;
17083 	int x, y;
17084 	Bool override_redirect;
17085 }
17086 
17087 struct XConfigureEvent
17088 {
17089 	int type;
17090 	arch_ulong serial;	/* # of last request processed by server */
17091 	Bool send_event;	/* true if this came from a SendEvent request */
17092 	Display *display;	/* Display the event was read from */
17093 	Window event;
17094 	Window window;
17095 	int x, y;
17096 	int width, height;
17097 	int border_width;
17098 	Window above;
17099 	Bool override_redirect;
17100 }
17101 
17102 struct XGravityEvent
17103 {
17104 	int type;
17105 	arch_ulong serial;	/* # of last request processed by server */
17106 	Bool send_event;	/* true if this came from a SendEvent request */
17107 	Display *display;	/* Display the event was read from */
17108 	Window event;
17109 	Window window;
17110 	int x, y;
17111 }
17112 
17113 struct XResizeRequestEvent
17114 {
17115 	int type;
17116 	arch_ulong serial;	/* # of last request processed by server */
17117 	Bool send_event;	/* true if this came from a SendEvent request */
17118 	Display *display;	/* Display the event was read from */
17119 	Window window;
17120 	int width, height;
17121 }
17122 
17123 struct  XConfigureRequestEvent
17124 {
17125 	int type;
17126 	arch_ulong serial;	/* # of last request processed by server */
17127 	Bool send_event;	/* true if this came from a SendEvent request */
17128 	Display *display;	/* Display the event was read from */
17129 	Window parent;
17130 	Window window;
17131 	int x, y;
17132 	int width, height;
17133 	int border_width;
17134 	Window above;
17135 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17136 	arch_ulong value_mask;
17137 }
17138 
17139 struct XCirculateEvent
17140 {
17141 	int type;
17142 	arch_ulong serial;	/* # of last request processed by server */
17143 	Bool send_event;	/* true if this came from a SendEvent request */
17144 	Display *display;	/* Display the event was read from */
17145 	Window event;
17146 	Window window;
17147 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17148 }
17149 
17150 struct XCirculateRequestEvent
17151 {
17152 	int type;
17153 	arch_ulong serial;	/* # of last request processed by server */
17154 	Bool send_event;	/* true if this came from a SendEvent request */
17155 	Display *display;	/* Display the event was read from */
17156 	Window parent;
17157 	Window window;
17158 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17159 }
17160 
17161 struct XPropertyEvent
17162 {
17163 	int type;
17164 	arch_ulong serial;	/* # of last request processed by server */
17165 	Bool send_event;	/* true if this came from a SendEvent request */
17166 	Display *display;	/* Display the event was read from */
17167 	Window window;
17168 	Atom atom;
17169 	Time time;
17170 	PropertyNotification state;		/* NewValue, Deleted */
17171 }
17172 
17173 struct XSelectionClearEvent
17174 {
17175 	int type;
17176 	arch_ulong serial;	/* # of last request processed by server */
17177 	Bool send_event;	/* true if this came from a SendEvent request */
17178 	Display *display;	/* Display the event was read from */
17179 	Window window;
17180 	Atom selection;
17181 	Time time;
17182 }
17183 
17184 struct XSelectionRequestEvent
17185 {
17186 	int type;
17187 	arch_ulong serial;	/* # of last request processed by server */
17188 	Bool send_event;	/* true if this came from a SendEvent request */
17189 	Display *display;	/* Display the event was read from */
17190 	Window owner;
17191 	Window requestor;
17192 	Atom selection;
17193 	Atom target;
17194 	Atom property;
17195 	Time time;
17196 }
17197 
17198 struct XSelectionEvent
17199 {
17200 	int type;
17201 	arch_ulong serial;	/* # of last request processed by server */
17202 	Bool send_event;	/* true if this came from a SendEvent request */
17203 	Display *display;	/* Display the event was read from */
17204 	Window requestor;
17205 	Atom selection;
17206 	Atom target;
17207 	Atom property;		/* ATOM or None */
17208 	Time time;
17209 }
17210 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17211 
17212 struct XColormapEvent
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 	Colormap colormap;	/* COLORMAP or None */
17220 	Bool new_;		/* C++ */
17221 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17222 }
17223 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17224 
17225 struct XClientMessageEvent
17226 {
17227 	int type;
17228 	arch_ulong serial;	/* # of last request processed by server */
17229 	Bool send_event;	/* true if this came from a SendEvent request */
17230 	Display *display;	/* Display the event was read from */
17231 	Window window;
17232 	Atom message_type;
17233 	int format;
17234 	union Data{
17235 		byte[20] b;
17236 		short[10] s;
17237 		arch_ulong[5] l;
17238 	}
17239 	Data data;
17240 
17241 }
17242 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17243 
17244 struct XMappingEvent
17245 {
17246 	int type;
17247 	arch_ulong serial;	/* # of last request processed by server */
17248 	Bool send_event;	/* true if this came from a SendEvent request */
17249 	Display *display;	/* Display the event was read from */
17250 	Window window;		/* unused */
17251 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17252 				   MappingPointer */
17253 	int first_keycode;	/* first keycode */
17254 	int count;		/* defines range of change w. first_keycode*/
17255 }
17256 
17257 struct XErrorEvent
17258 {
17259 	int type;
17260 	Display *display;	/* Display the event was read from */
17261 	XID resourceid;		/* resource id */
17262 	arch_ulong serial;	/* serial number of failed request */
17263 	ubyte error_code;	/* error code of failed request */
17264 	ubyte request_code;	/* Major op-code of failed request */
17265 	ubyte minor_code;	/* Minor op-code of failed request */
17266 }
17267 
17268 struct XAnyEvent
17269 {
17270 	int type;
17271 	arch_ulong serial;	/* # of last request processed by server */
17272 	Bool send_event;	/* true if this came from a SendEvent request */
17273 	Display *display;/* Display the event was read from */
17274 	Window window;	/* window on which event was requested in event mask */
17275 }
17276 
17277 union XEvent{
17278 	int type;		/* must not be changed; first element */
17279 	XAnyEvent xany;
17280 	XKeyEvent xkey;
17281 	XButtonEvent xbutton;
17282 	XMotionEvent xmotion;
17283 	XCrossingEvent xcrossing;
17284 	XFocusChangeEvent xfocus;
17285 	XExposeEvent xexpose;
17286 	XGraphicsExposeEvent xgraphicsexpose;
17287 	XNoExposeEvent xnoexpose;
17288 	XVisibilityEvent xvisibility;
17289 	XCreateWindowEvent xcreatewindow;
17290 	XDestroyWindowEvent xdestroywindow;
17291 	XUnmapEvent xunmap;
17292 	XMapEvent xmap;
17293 	XMapRequestEvent xmaprequest;
17294 	XReparentEvent xreparent;
17295 	XConfigureEvent xconfigure;
17296 	XGravityEvent xgravity;
17297 	XResizeRequestEvent xresizerequest;
17298 	XConfigureRequestEvent xconfigurerequest;
17299 	XCirculateEvent xcirculate;
17300 	XCirculateRequestEvent xcirculaterequest;
17301 	XPropertyEvent xproperty;
17302 	XSelectionClearEvent xselectionclear;
17303 	XSelectionRequestEvent xselectionrequest;
17304 	XSelectionEvent xselection;
17305 	XColormapEvent xcolormap;
17306 	XClientMessageEvent xclient;
17307 	XMappingEvent xmapping;
17308 	XErrorEvent xerror;
17309 	XKeymapEvent xkeymap;
17310 	arch_ulong[24] pad;
17311 }
17312 
17313 
17314 	struct Display {
17315 		XExtData *ext_data;	/* hook for extension to hang data */
17316 		_XPrivate *private1;
17317 		int fd;			/* Network socket. */
17318 		int private2;
17319 		int proto_major_version;/* major version of server's X protocol */
17320 		int proto_minor_version;/* minor version of servers X protocol */
17321 		char *vendor;		/* vendor of the server hardware */
17322 	    	XID private3;
17323 		XID private4;
17324 		XID private5;
17325 		int private6;
17326 		XID function(Display*)resource_alloc;/* allocator function */
17327 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17328 		int bitmap_unit;	/* padding and data requirements */
17329 		int bitmap_pad;		/* padding requirements on bitmaps */
17330 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17331 		int nformats;		/* number of pixmap formats in list */
17332 		ScreenFormat *pixmap_format;	/* pixmap format list */
17333 		int private8;
17334 		int release;		/* release of the server */
17335 		_XPrivate *private9;
17336 		_XPrivate *private10;
17337 		int qlen;		/* Length of input event queue */
17338 		arch_ulong last_request_read; /* seq number of last event read */
17339 		arch_ulong request;	/* sequence number of last request. */
17340 		XPointer private11;
17341 		XPointer private12;
17342 		XPointer private13;
17343 		XPointer private14;
17344 		uint max_request_size; /* maximum number 32 bit words in request*/
17345 		_XrmHashBucketRec *db;
17346 		int function  (Display*)private15;
17347 		char *display_name;	/* "host:display" string used on this connect*/
17348 		int default_screen;	/* default screen for operations */
17349 		int nscreens;		/* number of screens on this server*/
17350 		Screen *screens;	/* pointer to list of screens */
17351 		arch_ulong motion_buffer;	/* size of motion buffer */
17352 		arch_ulong private16;
17353 		int min_keycode;	/* minimum defined keycode */
17354 		int max_keycode;	/* maximum defined keycode */
17355 		XPointer private17;
17356 		XPointer private18;
17357 		int private19;
17358 		byte *xdefaults;	/* contents of defaults from server */
17359 		/* there is more to this structure, but it is private to Xlib */
17360 	}
17361 
17362 	// I got these numbers from a C program as a sanity test
17363 	version(X86_64) {
17364 		static assert(Display.sizeof == 296);
17365 		static assert(XPointer.sizeof == 8);
17366 		static assert(XErrorEvent.sizeof == 40);
17367 		static assert(XAnyEvent.sizeof == 40);
17368 		static assert(XMappingEvent.sizeof == 56);
17369 		static assert(XEvent.sizeof == 192);
17370     	} else version (AArch64) {
17371         	// omit check for aarch64
17372 	} else {
17373 		static assert(Display.sizeof == 176);
17374 		static assert(XPointer.sizeof == 4);
17375 		static assert(XEvent.sizeof == 96);
17376 	}
17377 
17378 struct Depth
17379 {
17380 	int depth;		/* this depth (Z) of the depth */
17381 	int nvisuals;		/* number of Visual types at this depth */
17382 	Visual *visuals;	/* list of visuals possible at this depth */
17383 }
17384 
17385 alias void* GC;
17386 alias c_ulong VisualID;
17387 alias XID Colormap;
17388 alias XID Cursor;
17389 alias XID KeySym;
17390 alias uint KeyCode;
17391 enum None = 0;
17392 }
17393 
17394 version(without_opengl) {}
17395 else {
17396 extern(C) nothrow @nogc {
17397 
17398 
17399 static if(!SdpyIsUsingIVGLBinds) {
17400 enum GLX_USE_GL=            1;       /* support GLX rendering */
17401 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17402 enum GLX_LEVEL=             3;       /* level in plane stacking */
17403 enum GLX_RGBA=              4;       /* true if RGBA mode */
17404 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17405 enum GLX_STEREO=            6;       /* stereo buffering supported */
17406 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17407 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17408 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17409 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17410 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17411 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17412 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17413 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17414 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17415 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17416 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17417 
17418 
17419 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17420 
17421 
17422 
17423 enum GL_TRUE = 1;
17424 enum GL_FALSE = 0;
17425 }
17426 
17427 alias XID GLXContextID;
17428 alias XID GLXPixmap;
17429 alias XID GLXDrawable;
17430 alias XID GLXPbuffer;
17431 alias XID GLXWindow;
17432 alias XID GLXFBConfigID;
17433 alias void* GLXContext;
17434 
17435 }
17436 }
17437 
17438 enum AllocNone = 0;
17439 
17440 extern(C) {
17441 	/* WARNING, this type not in Xlib spec */
17442 	extern(C) alias XIOErrorHandler = int function (Display* display);
17443 }
17444 
17445 extern(C) nothrow
17446 alias XErrorHandler = int function(Display*, XErrorEvent*);
17447 
17448 extern(C) nothrow @nogc {
17449 struct Screen{
17450 	XExtData *ext_data;		/* hook for extension to hang data */
17451 	Display *display;		/* back pointer to display structure */
17452 	Window root;			/* Root window id. */
17453 	int width, height;		/* width and height of screen */
17454 	int mwidth, mheight;	/* width and height of  in millimeters */
17455 	int ndepths;			/* number of depths possible */
17456 	Depth *depths;			/* list of allowable depths on the screen */
17457 	int root_depth;			/* bits per pixel */
17458 	Visual *root_visual;	/* root visual */
17459 	GC default_gc;			/* GC for the root root visual */
17460 	Colormap cmap;			/* default color map */
17461 	uint white_pixel;
17462 	uint black_pixel;		/* White and Black pixel values */
17463 	int max_maps, min_maps;	/* max and min color maps */
17464 	int backing_store;		/* Never, WhenMapped, Always */
17465 	bool save_unders;
17466 	int root_input_mask;	/* initial root input mask */
17467 }
17468 
17469 struct Visual
17470 {
17471 	XExtData *ext_data;	/* hook for extension to hang data */
17472 	VisualID visualid;	/* visual id of this visual */
17473 	int class_;			/* class of screen (monochrome, etc.) */
17474 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17475 	int bits_per_rgb;	/* log base 2 of distinct color values */
17476 	int map_entries;	/* color map entries */
17477 }
17478 
17479 	alias Display* _XPrivDisplay;
17480 
17481 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17482 		assert(dpy !is null);
17483 		return &dpy.screens[scr];
17484 	}
17485 
17486 	extern(D) Window RootWindow(Display *dpy,int scr) {
17487 		return ScreenOfDisplay(dpy,scr).root;
17488 	}
17489 
17490 	struct XWMHints {
17491 		arch_long flags;
17492 		Bool input;
17493 		int initial_state;
17494 		Pixmap icon_pixmap;
17495 		Window icon_window;
17496 		int icon_x, icon_y;
17497 		Pixmap icon_mask;
17498 		XID window_group;
17499 	}
17500 
17501 	struct XClassHint {
17502 		char* res_name;
17503 		char* res_class;
17504 	}
17505 
17506 	extern(D) int DefaultScreen(Display *dpy) {
17507 		return dpy.default_screen;
17508 	}
17509 
17510 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17511 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17512 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17513 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17514 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17515 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17516 
17517 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17518 
17519 	enum int AnyPropertyType = 0;
17520 	enum int Success = 0;
17521 
17522 	enum int RevertToNone = None;
17523 	enum int PointerRoot = 1;
17524 	enum Time CurrentTime = 0;
17525 	enum int RevertToPointerRoot = PointerRoot;
17526 	enum int RevertToParent = 2;
17527 
17528 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17529 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17530 	}
17531 
17532 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17533 		return ScreenOfDisplay(dpy,scr).root_visual;
17534 	}
17535 
17536 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17537 		return ScreenOfDisplay(dpy,scr).default_gc;
17538 	}
17539 
17540 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17541 		return ScreenOfDisplay(dpy,scr).black_pixel;
17542 	}
17543 
17544 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17545 		return ScreenOfDisplay(dpy,scr).white_pixel;
17546 	}
17547 
17548 	alias void* XFontSet; // i think
17549 	struct XmbTextItem {
17550 		char* chars;
17551 		int nchars;
17552 		int delta;
17553 		XFontSet font_set;
17554 	}
17555 
17556 	struct XTextItem {
17557 		char* chars;
17558 		int nchars;
17559 		int delta;
17560 		Font font;
17561 	}
17562 
17563 	enum {
17564 		GXclear        = 0x0, /* 0 */
17565 		GXand          = 0x1, /* src AND dst */
17566 		GXandReverse   = 0x2, /* src AND NOT dst */
17567 		GXcopy         = 0x3, /* src */
17568 		GXandInverted  = 0x4, /* NOT src AND dst */
17569 		GXnoop         = 0x5, /* dst */
17570 		GXxor          = 0x6, /* src XOR dst */
17571 		GXor           = 0x7, /* src OR dst */
17572 		GXnor          = 0x8, /* NOT src AND NOT dst */
17573 		GXequiv        = 0x9, /* NOT src XOR dst */
17574 		GXinvert       = 0xa, /* NOT dst */
17575 		GXorReverse    = 0xb, /* src OR NOT dst */
17576 		GXcopyInverted = 0xc, /* NOT src */
17577 		GXorInverted   = 0xd, /* NOT src OR dst */
17578 		GXnand         = 0xe, /* NOT src OR NOT dst */
17579 		GXset          = 0xf, /* 1 */
17580 	}
17581 	enum QueueMode : int {
17582 		QueuedAlready,
17583 		QueuedAfterReading,
17584 		QueuedAfterFlush
17585 	}
17586 
17587 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17588 
17589 	struct XPoint {
17590 		short x;
17591 		short y;
17592 	}
17593 
17594 	enum CoordMode:int {
17595 		CoordModeOrigin = 0,
17596 		CoordModePrevious = 1
17597 	}
17598 
17599 	enum PolygonShape:int {
17600 		Complex = 0,
17601 		Nonconvex = 1,
17602 		Convex = 2
17603 	}
17604 
17605 	struct XTextProperty {
17606 		const(char)* value;		/* same as Property routines */
17607 		Atom encoding;			/* prop type */
17608 		int format;				/* prop data format: 8, 16, or 32 */
17609 		arch_ulong nitems;		/* number of data items in value */
17610 	}
17611 
17612 	version( X86_64 ) {
17613 		static assert(XTextProperty.sizeof == 32);
17614 	}
17615 
17616 
17617 	struct XGCValues {
17618 		int function_;           /* logical operation */
17619 		arch_ulong plane_mask;/* plane mask */
17620 		arch_ulong foreground;/* foreground pixel */
17621 		arch_ulong background;/* background pixel */
17622 		int line_width;         /* line width */
17623 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17624 		int cap_style;          /* CapNotLast, CapButt,
17625 					   CapRound, CapProjecting */
17626 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17627 		int fill_style;         /* FillSolid, FillTiled,
17628 					   FillStippled, FillOpaeueStippled */
17629 		int fill_rule;          /* EvenOddRule, WindingRule */
17630 		int arc_mode;           /* ArcChord, ArcPieSlice */
17631 		Pixmap tile;            /* tile pixmap for tiling operations */
17632 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17633 		int ts_x_origin;        /* offset for tile or stipple operations */
17634 		int ts_y_origin;
17635 		Font font;              /* default text font for text operations */
17636 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17637 		Bool graphics_exposures;/* boolean, should exposures be generated */
17638 		int clip_x_origin;      /* origin for clipping */
17639 		int clip_y_origin;
17640 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17641 		int dash_offset;        /* patterned/dashed line information */
17642 		char dashes;
17643 	}
17644 
17645 	struct XColor {
17646 		arch_ulong pixel;
17647 		ushort red, green, blue;
17648 		byte flags;
17649 		byte pad;
17650 	}
17651 
17652 	struct XRectangle {
17653 		short x;
17654 		short y;
17655 		ushort width;
17656 		ushort height;
17657 	}
17658 
17659 	enum ClipByChildren = 0;
17660 	enum IncludeInferiors = 1;
17661 
17662 	enum Atom XA_PRIMARY = 1;
17663 	enum Atom XA_SECONDARY = 2;
17664 	enum Atom XA_STRING = 31;
17665 	enum Atom XA_CARDINAL = 6;
17666 	enum Atom XA_WM_NAME = 39;
17667 	enum Atom XA_ATOM = 4;
17668 	enum Atom XA_WINDOW = 33;
17669 	enum Atom XA_WM_HINTS = 35;
17670 	enum int PropModeAppend = 2;
17671 	enum int PropModeReplace = 0;
17672 	enum int PropModePrepend = 1;
17673 
17674 	enum int CopyFromParent = 0;
17675 	enum int InputOutput = 1;
17676 
17677 	// XWMHints
17678 	enum InputHint = 1 << 0;
17679 	enum StateHint = 1 << 1;
17680 	enum IconPixmapHint = (1L << 2);
17681 	enum IconWindowHint = (1L << 3);
17682 	enum IconPositionHint = (1L << 4);
17683 	enum IconMaskHint = (1L << 5);
17684 	enum WindowGroupHint = (1L << 6);
17685 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17686 	enum XUrgencyHint = (1L << 8);
17687 
17688 	// GC Components
17689 	enum GCFunction           =   (1L<<0);
17690 	enum GCPlaneMask         =    (1L<<1);
17691 	enum GCForeground       =     (1L<<2);
17692 	enum GCBackground      =      (1L<<3);
17693 	enum GCLineWidth      =       (1L<<4);
17694 	enum GCLineStyle     =        (1L<<5);
17695 	enum GCCapStyle     =         (1L<<6);
17696 	enum GCJoinStyle   =          (1L<<7);
17697 	enum GCFillStyle  =           (1L<<8);
17698 	enum GCFillRule  =            (1L<<9);
17699 	enum GCTile     =             (1L<<10);
17700 	enum GCStipple           =    (1L<<11);
17701 	enum GCTileStipXOrigin  =     (1L<<12);
17702 	enum GCTileStipYOrigin =      (1L<<13);
17703 	enum GCFont               =   (1L<<14);
17704 	enum GCSubwindowMode     =    (1L<<15);
17705 	enum GCGraphicsExposures=     (1L<<16);
17706 	enum GCClipXOrigin     =      (1L<<17);
17707 	enum GCClipYOrigin    =       (1L<<18);
17708 	enum GCClipMask      =        (1L<<19);
17709 	enum GCDashOffset   =         (1L<<20);
17710 	enum GCDashList    =          (1L<<21);
17711 	enum GCArcMode    =           (1L<<22);
17712 	enum GCLastBit   =            22;
17713 
17714 
17715 	enum int WithdrawnState = 0;
17716 	enum int NormalState = 1;
17717 	enum int IconicState = 3;
17718 
17719 }
17720 } else version (OSXCocoa) {
17721 
17722 /+
17723 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
17724 +/
17725 
17726 	private __gshared AppDelegate globalAppDelegate;
17727 
17728 	extern(Objective-C)
17729 	class AppDelegate : NSObject, NSApplicationDelegate {
17730 		override static AppDelegate alloc() @selector("alloc");
17731 
17732 
17733 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
17734 			SimpleWindow.processAllCustomEvents();
17735 		}
17736 
17737 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
17738 			immutable style = NSWindowStyleMask.resizable |
17739 				NSWindowStyleMask.closable |
17740 				NSWindowStyleMask.miniaturizable |
17741 				NSWindowStyleMask.titled;
17742 
17743 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
17744 
17745 			{
17746 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
17747 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
17748 				mainMenu.setSubmenu(menu, item);
17749 
17750 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
17751 				newItem.target = NSApp;
17752 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
17753 				newItem2.target = NSApp;
17754 			}
17755 
17756 			{
17757 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
17758 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
17759 				mainMenu.setSubmenu(menu, item);
17760 
17761 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
17762 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
17763 			}
17764 
17765 
17766 			NSApp.menu = mainMenu;
17767 
17768 
17769 			// auto controller = ViewController.alloc.init;
17770 
17771 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
17772 
17773 			/+
17774 			this.window = window;
17775 			this.controller = controller;
17776 			+/
17777 		}
17778 
17779 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
17780 			NSApplication.shared_.activateIgnoringOtherApps(true);
17781 		}
17782 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
17783 			return true;
17784 		}
17785 	}
17786 
17787 	extern(Objective-C)
17788 	class SDWindowDelegate : NSObject, NSWindowDelegate {
17789 		override static SDWindowDelegate alloc() @selector("alloc");
17790 		override SDWindowDelegate init() @selector("init");
17791 
17792 		SimpleWindow simpleWindow;
17793 
17794 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
17795 			auto window = cast(void*) notification.object;
17796 
17797 			// FIXME: do i need to release it?
17798 			SimpleWindow.nativeMapping.remove(window);
17799 		}
17800 
17801 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
17802 			if(simpleWindow.windowResized) {
17803 				// FIXME: automaticallyScaleIfPossible behaviors
17804 
17805 				simpleWindow._width = cast(int) frameSize.width;
17806 				simpleWindow._height = cast(int) frameSize.height;
17807 
17808 				simpleWindow.view.setFrameSize(frameSize);
17809 
17810 				/+
17811 				auto size = simpleWindow.view.frame.size;
17812 				writeln(cast(int) size.width, "x", cast(int) size.height);
17813 				+/
17814 
17815 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
17816 
17817 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
17818 
17819 				// simpleWindow.view.setNeedsDisplay(true);
17820 			}
17821 
17822 			return frameSize;
17823 		}
17824 
17825 		/+
17826 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
17827 			if(simpleWindow.windowResized) {
17828 				auto window = simpleWindow.window;
17829 				auto rect = window.contentRectForFrameRect(window.frame);
17830 				import std.stdio; writeln(window.frame.size);
17831 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
17832 			}
17833 		}
17834 		+/
17835 	}
17836 
17837 	extern(Objective-C)
17838 	class SDGraphicsView : NSView {
17839 		SimpleWindow simpleWindow;
17840 
17841 		override static SDGraphicsView alloc() @selector("alloc");
17842 		override SDGraphicsView init() @selector("init") {
17843 			super.init();
17844 			return this;
17845 		}
17846 
17847 		override void drawRect(NSRect rect) @selector("drawRect:") {
17848 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
17849 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17850 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
17851 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17852 			CGImageRelease(cgImage);
17853 		}
17854 
17855 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
17856 			MouseEvent me;
17857 			me.type = type;
17858 
17859 			auto pos = event.locationInWindow;
17860 
17861 			me.x = cast(int) pos.x;
17862 			me.y = cast(int) (simpleWindow.height - pos.y);
17863 
17864 			me.dx = 0; // FIXME
17865 			me.dy = 0; // FIXME
17866 
17867 			me.button = button;
17868 			me.modifierState = cast(uint) event.modifierFlags;
17869 			me.window = simpleWindow;
17870 
17871 			me.doubleClick = false;
17872 
17873 			if(simpleWindow && simpleWindow.handleMouseEvent)
17874 				simpleWindow.handleMouseEvent(me);
17875 		}
17876 
17877 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
17878 			// writeln(event.pressedMouseButtons);
17879 
17880 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17881 		}
17882 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
17883 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
17884 		}
17885 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
17886 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
17887 		}
17888 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
17889 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
17890 		}
17891 		/+
17892 			// FIXME
17893 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
17894 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17895 		}
17896 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
17897 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17898 		}
17899 		+/
17900 
17901 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
17902 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
17903 		}
17904 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
17905 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
17906 		}
17907 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
17908 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
17909 		}
17910 
17911 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
17912 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
17913 		}
17914 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
17915 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
17916 		}
17917 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
17918 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
17919 		}
17920 
17921 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
17922 			import std.stdio;
17923 			writeln(event.deltaY);
17924 		}
17925 
17926 		override void keyDown(NSEvent event) @selector("keyDown:") {
17927 			// the event may have multiple characters, and we send them all at once.
17928 			if (simpleWindow.handleCharEvent) {
17929 				auto chars = DeifiedNSString(event.characters);
17930 				foreach (dchar dc; chars.str)
17931 					simpleWindow.handleCharEvent(dc);
17932 			}
17933 
17934 			keyHelper(event, true);
17935 		}
17936 
17937 		override void keyUp(NSEvent event) @selector("keyUp:") {
17938 			keyHelper(event, false);
17939 		}
17940 
17941 		private void keyHelper(NSEvent event, bool pressed) {
17942 			if(simpleWindow.handleKeyEvent) {
17943 				KeyEvent ev;
17944 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
17945 				ev.pressed = pressed;
17946 				ev.hardwareCode = cast(ubyte) event.keyCode;
17947 				ev.modifierState = cast(uint) event.modifierFlags;
17948 				ev.window = simpleWindow;
17949 
17950 				simpleWindow.handleKeyEvent(ev);
17951 			}
17952 		}
17953 
17954 		override bool isFlipped() @selector("isFlipped") {
17955 			return true;
17956 		}
17957 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
17958 			return true;
17959 		}
17960 
17961 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
17962 			if(simpleWindow && simpleWindow.handlePulse)
17963 				simpleWindow.handlePulse();
17964 			/+
17965 			setNeedsDisplay = true;
17966 			+/
17967 		}
17968 	}
17969 
17970 private:
17971 	alias const(void)* CFStringRef;
17972 	alias const(void)* CFAllocatorRef;
17973 	alias const(void)* CFTypeRef;
17974 	alias const(void)* CGColorSpaceRef;
17975 	alias const(void)* CGImageRef;
17976 	alias ulong CGBitmapInfo;
17977 	alias NSGraphicsContext CGContextRef;
17978 
17979 	alias NSPoint CGPoint;
17980 	alias NSSize CGSize;
17981 	alias NSRect CGRect;
17982 
17983 	struct CGAffineTransform {
17984 		double a, b, c, d, tx, ty;
17985 	}
17986 
17987 	enum NSApplicationActivationPolicyRegular = 0;
17988 	enum NSBackingStoreBuffered = 2;
17989 	enum kCFStringEncodingUTF8 = 0x08000100;
17990 
17991 	enum : size_t {
17992 		NSBorderlessWindowMask = 0,
17993 		NSTitledWindowMask = 1 << 0,
17994 		NSClosableWindowMask = 1 << 1,
17995 		NSMiniaturizableWindowMask = 1 << 2,
17996 		NSResizableWindowMask = 1 << 3,
17997 		NSTexturedBackgroundWindowMask = 1 << 8
17998 	}
17999 
18000 	enum : ulong {
18001 		kCGImageAlphaNone,
18002 		kCGImageAlphaPremultipliedLast,
18003 		kCGImageAlphaPremultipliedFirst,
18004 		kCGImageAlphaLast,
18005 		kCGImageAlphaFirst,
18006 		kCGImageAlphaNoneSkipLast,
18007 		kCGImageAlphaNoneSkipFirst
18008 	}
18009 	enum : ulong {
18010 		kCGBitmapAlphaInfoMask = 0x1F,
18011 		kCGBitmapFloatComponents = (1 << 8),
18012 		kCGBitmapByteOrderMask = 0x7000,
18013 		kCGBitmapByteOrderDefault = (0 << 12),
18014 		kCGBitmapByteOrder16Little = (1 << 12),
18015 		kCGBitmapByteOrder32Little = (2 << 12),
18016 		kCGBitmapByteOrder16Big = (3 << 12),
18017 		kCGBitmapByteOrder32Big = (4 << 12)
18018 	}
18019 	enum CGPathDrawingMode {
18020 		kCGPathFill,
18021 		kCGPathEOFill,
18022 		kCGPathStroke,
18023 		kCGPathFillStroke,
18024 		kCGPathEOFillStroke
18025 	}
18026 	enum objc_AssociationPolicy : size_t {
18027 		OBJC_ASSOCIATION_ASSIGN = 0,
18028 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18029 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18030 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18031 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18032 	}
18033 
18034 	extern(C) {
18035 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18036 		void CGContextRelease(CGContextRef c);
18037 		ubyte* CGBitmapContextGetData(CGContextRef c);
18038 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18039 		size_t CGBitmapContextGetWidth(CGContextRef c);
18040 		size_t CGBitmapContextGetHeight(CGContextRef c);
18041 
18042 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18043 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18044 
18045 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18046 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18047 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18048 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18049 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18050 
18051 		void CGContextBeginPath(CGContextRef c);
18052 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18053 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18054 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18055 		void CGContextAddRect(CGContextRef c, CGRect rect);
18056 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18057 		void CGContextSaveGState(CGContextRef c);
18058 		void CGContextRestoreGState(CGContextRef c);
18059 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18060 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18061 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18062 
18063 		void CGImageRelease(CGImageRef image);
18064 	}
18065 } else static assert(0, "Unsupported operating system");
18066 
18067 
18068 version(OSXCocoa) {
18069 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18070 	//
18071 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18072 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18073 	//
18074 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18075 	// Probably won't even fully compile right now
18076 
18077 	private enum double PI = 3.14159265358979323;
18078 
18079 	alias NSWindow NativeWindowHandle;
18080 	alias void delegate(NSid) NativeEventHandler;
18081 
18082 	enum KEY_ESCAPE = 27;
18083 
18084 	mixin template NativeImageImplementation() {
18085 		CGContextRef context;
18086 		ubyte* rawData;
18087 
18088 		final:
18089 
18090 		void convertToRgbaBytes(ubyte[] where) {
18091 			assert(where.length == this.width * this.height * 4);
18092 
18093 			// if rawData had a length....
18094 			//assert(rawData.length == where.length);
18095 			for(long idx = 0; idx < where.length; idx += 4) {
18096 				auto alpha = rawData[idx + 3];
18097 				if(alpha == 255) {
18098 					where[idx + 0] = rawData[idx + 0]; // r
18099 					where[idx + 1] = rawData[idx + 1]; // g
18100 					where[idx + 2] = rawData[idx + 2]; // b
18101 					where[idx + 3] = rawData[idx + 3]; // a
18102 				} else {
18103 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18104 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18105 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18106 					where[idx + 3] = rawData[idx + 3]; // a
18107 
18108 				}
18109 			}
18110 		}
18111 
18112 		void setFromRgbaBytes(in ubyte[] where) {
18113 			// FIXME: this is probably wrong
18114 			assert(where.length == this.width * this.height * 4);
18115 
18116 			// if rawData had a length....
18117 			//assert(rawData.length == where.length);
18118 			for(long idx = 0; idx < where.length; idx += 4) {
18119 				auto alpha = where[idx + 3];
18120 				if(alpha == 255) {
18121 					rawData[idx + 0] = where[idx + 0]; // r
18122 					rawData[idx + 1] = where[idx + 1]; // g
18123 					rawData[idx + 2] = where[idx + 2]; // b
18124 					rawData[idx + 3] = where[idx + 3]; // a
18125 				} else if(alpha == 0) {
18126 					rawData[idx + 0] = 0;
18127 					rawData[idx + 1] = 0;
18128 					rawData[idx + 2] = 0;
18129 					rawData[idx + 3] = 0;
18130 				} else {
18131 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18132 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18133 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18134 					rawData[idx + 3] = where[idx + 3]; // a
18135 				}
18136 			}
18137 		}
18138 
18139 
18140 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18141 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18142 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18143 			CGColorSpaceRelease(colorSpace);
18144 			rawData = CGBitmapContextGetData(context);
18145 		}
18146 		void dispose() {
18147 			CGContextRelease(context);
18148 		}
18149 
18150 		void setPixel(int x, int y, Color c) {
18151 			auto offset = (y * width + x) * 4;
18152 			if (c.a == 255) {
18153 				rawData[offset + 0] = c.r;
18154 				rawData[offset + 1] = c.g;
18155 				rawData[offset + 2] = c.b;
18156 				rawData[offset + 3] = c.a;
18157 			} else {
18158 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18159 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18160 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18161 				rawData[offset + 3] = c.a;
18162 			}
18163 		}
18164 	}
18165 
18166 	mixin template NativeScreenPainterImplementation() {
18167 		CGContextRef context;
18168 		ubyte[4] _outlineComponents;
18169 		NSView view;
18170 
18171 		void create(PaintingHandle window) {
18172 			// this.destiny = window;
18173 			if(auto sw = cast(SimpleWindow) this.window) {
18174 				context = sw.drawingContext;
18175 				view = sw.view;
18176 			} else {
18177 				throw new NotYetImplementedException();
18178 			}
18179 		}
18180 
18181 		void dispose() {
18182 			view.setNeedsDisplay(true);
18183 		}
18184 
18185 		bool manualInvalidations;
18186 		void invalidateRect(Rectangle invalidRect) { }
18187 
18188 		// NotYetImplementedException
18189 		Size textSize(in char[] txt) {
18190 			return Size(32, 16); /*throw new NotYetImplementedException();*/
18191 		}
18192 		void rasterOp(RasterOp op) {
18193 		}
18194 		Pen _activePen;
18195 		Color _fillColor;
18196 		Rectangle _clipRectangle;
18197 		void setClipRectangle(int, int, int, int) {
18198 		}
18199 		void setFont(OperatingSystemFont) {
18200 		}
18201 		int fontHeight() {
18202 			return 14;
18203 		}
18204 
18205 		// end
18206 
18207 		void pen(Pen pen) {
18208 			_activePen = pen;
18209 			auto color = pen.color; // FIXME
18210 			double alphaComponent = color.a/255.0f;
18211 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
18212 
18213 			if (color.a != 255) {
18214 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
18215 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
18216 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18217 				_outlineComponents[3] = color.a;
18218 			} else {
18219 				_outlineComponents[0] = color.r;
18220 				_outlineComponents[1] = color.g;
18221 				_outlineComponents[2] = color.b;
18222 				_outlineComponents[3] = color.a;
18223 			}
18224 		}
18225 
18226 		@property void fillColor(Color color) {
18227 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18228 		}
18229 
18230 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18231 		// NotYetImplementedException for upper left/width/height
18232 			auto cgImage = CGBitmapContextCreateImage(image.context);
18233 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
18234 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18235 			CGImageRelease(cgImage);
18236 		}
18237 
18238 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
18239 		// FIXME: is this efficient?
18240 			auto cgImage = CGBitmapContextCreateImage(s.handle);
18241 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
18242 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18243 			CGImageRelease(cgImage);
18244 		}
18245 
18246 
18247 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18248 		// FIXME: alignment
18249 			if (_outlineComponents[3] != 0) {
18250 				CGContextSaveGState(context);
18251 				auto invAlpha = 1.0f/_outlineComponents[3];
18252 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18253 												  _outlineComponents[1]*invAlpha,
18254 												  _outlineComponents[2]*invAlpha,
18255 												  _outlineComponents[3]/255.0f);
18256 				CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
18257 // auto cfstr = cast(NSid)createCFString(text);
18258 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18259 // NSPoint(x, y), null);
18260 // CFRelease(cfstr);
18261 				CGContextRestoreGState(context);
18262 			}
18263 		}
18264 
18265 		void drawPixel(int x, int y) {
18266 			auto rawData = CGBitmapContextGetData(context);
18267 			auto width = CGBitmapContextGetWidth(context);
18268 			auto height = CGBitmapContextGetHeight(context);
18269 			auto offset = ((height - y - 1) * width + x) * 4;
18270 			rawData[offset .. offset+4] = _outlineComponents;
18271 		}
18272 
18273 		void drawLine(int x1, int y1, int x2, int y2) {
18274 			CGPoint[2] linePoints;
18275 			linePoints[0] = CGPoint(x1, y1);
18276 			linePoints[1] = CGPoint(x2, y2);
18277 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18278 		}
18279 
18280 		void drawRectangle(int x, int y, int width, int height) {
18281 			CGContextBeginPath(context);
18282 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18283 			CGContextAddRect(context, rect);
18284 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18285 		}
18286 
18287 		void drawEllipse(int x1, int y1, int x2, int y2) {
18288 			CGContextBeginPath(context);
18289 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18290 			CGContextAddEllipseInRect(context, rect);
18291 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18292 		}
18293 
18294 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18295 			// @@@BUG@@@ Does not support elliptic arc (width != height).
18296 			CGContextBeginPath(context);
18297 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18298 							start*PI/(180*64), finish*PI/(180*64), 0);
18299 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18300 		}
18301 
18302 		void drawPolygon(Point[] intPoints) {
18303 			CGContextBeginPath(context);
18304 			CGPoint[16] pointsBuffer;
18305 			CGPoint[] points;
18306 			if(intPoints.length <= pointsBuffer.length)
18307 				points = pointsBuffer[0 .. intPoints.length];
18308 			else
18309 				points = new CGPoint[](intPoints.length);
18310 
18311 			foreach(idx, pt; intPoints)
18312 				points[idx] = CGPoint(pt.x, pt.y);
18313 
18314 			CGContextAddLines(context, points.ptr, points.length);
18315 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18316 		}
18317 	}
18318 
18319 	private bool appInitialized = false;
18320 	void initializeApp() {
18321 		if(appInitialized)
18322 			return;
18323 		synchronized {
18324 			if(appInitialized)
18325 				return;
18326 
18327 			auto app = NSApp(); // ensure the is initialized
18328 
18329 			auto dg = AppDelegate.alloc;
18330 			globalAppDelegate = dg;
18331 			NSApp.delegate_ = dg;
18332 
18333 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
18334 
18335 			appInitialized = true;
18336 		}
18337 	}
18338 
18339 	mixin template NativeSimpleWindowImplementation() {
18340 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18341 			initializeApp();
18342 
18343 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18344 
18345 			auto window = NSWindow.alloc.initWithContentRect(
18346 				contentRect,
18347 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
18348 				NSBackingStoreType.buffered,
18349 				true
18350 			);
18351 
18352 			SimpleWindow.nativeMapping[cast(void*) window] = this;
18353 
18354 			window.title = MacString(title).borrow;
18355 
18356 			auto dg = SDWindowDelegate.alloc.init;
18357 			dg.simpleWindow = this;
18358 			window.delegate_ = dg;
18359 
18360 			auto view = SDGraphicsView.alloc.init;
18361 			assert(view !is null);
18362 			window.contentView = view;
18363 			this.view = view;
18364 			view.simpleWindow = this;
18365 
18366 			window.center();
18367 
18368 			window.makeKeyAndOrderFront(null);
18369 
18370 			// no need to make a bitmap on mac since everything is double buffered already
18371 
18372 			// create area to draw on.
18373 			createNewDrawingContext(width, height);
18374 
18375 			window.setBackgroundColor(NSColor.whiteColor);
18376 		}
18377 
18378 		void createNewDrawingContext(int width, int height) {
18379 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
18380 			if(this.drawingContext)
18381 				CGContextRelease(this.drawingContext);
18382 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18383 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18384 			CGColorSpaceRelease(colorSpace);
18385 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18386 			auto matrix = CGContextGetTextMatrix(drawingContext);
18387 			matrix.c = -matrix.c;
18388 			matrix.d = -matrix.d;
18389 			CGContextSetTextMatrix(drawingContext, matrix);
18390 
18391 		}
18392 
18393 		void dispose() {
18394 			closeWindow();
18395 			// window.release(); // closing the window does this automatically i think
18396 		}
18397 		void closeWindow() {
18398 			if(timer)
18399 				timer.invalidate();
18400 			window.close();
18401 		}
18402 
18403 		ScreenPainter getPainter(bool manualInvalidations) {
18404 			return ScreenPainter(this, this.window, manualInvalidations);
18405 		}
18406 
18407 		NSWindow window;
18408 		NSTimer timer;
18409 		NSView view;
18410 		CGContextRef drawingContext;
18411 	}
18412 }
18413 
18414 version(without_opengl) {} else
18415 extern(System) nothrow @nogc {
18416 	//enum uint GL_VERSION = 0x1F02;
18417 	//const(char)* glGetString (/*GLenum*/uint);
18418 	version(X11) {
18419 	static if (!SdpyIsUsingIVGLBinds) {
18420 
18421 		enum GLX_X_RENDERABLE = 0x8012;
18422 		enum GLX_DRAWABLE_TYPE = 0x8010;
18423 		enum GLX_RENDER_TYPE = 0x8011;
18424 		enum GLX_X_VISUAL_TYPE = 0x22;
18425 		enum GLX_TRUE_COLOR = 0x8002;
18426 		enum GLX_WINDOW_BIT = 0x00000001;
18427 		enum GLX_RGBA_BIT = 0x00000001;
18428 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18429 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18430 		enum GLX_SAMPLES = 0x186a1;
18431 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18432 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18433 	}
18434 
18435 		// GLX_EXT_swap_control
18436 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18437 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18438 
18439 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18440 		extern(System) {
18441 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18442 		}
18443 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18444 
18445 		// this made public so we don't have to get it again and again
18446 		public bool glXCreateContextAttribsARB_present () {
18447 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18448 				// get it
18449 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18450 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18451 			}
18452 			return (glXCreateContextAttribsARBFn !is null);
18453 		}
18454 
18455 		// this made public so we don't have to get it again and again
18456 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18457 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18458 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18459 		}
18460 
18461 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18462 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18463 
18464 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18465 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18466 			if (_glx_swapInterval_fn is null) {
18467 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18468 				if (_glx_swapInterval_fn is null) {
18469 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18470 					return;
18471 				}
18472 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
18473 			}
18474 
18475 			if(glXSwapIntervalMESA is null) {
18476 				// it seems to require both to actually take effect on many computers
18477 				// idk why
18478 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18479 				if(glXSwapIntervalMESA is null)
18480 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18481 			}
18482 
18483 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18484 				glXSwapIntervalMESA(wait ? 1 : 0);
18485 
18486 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18487 		}
18488 	} else version(Windows) {
18489 	static if (!SdpyIsUsingIVGLBinds) {
18490 	enum GL_TRUE = 1;
18491 	enum GL_FALSE = 0;
18492 
18493 	public void* glbindGetProcAddress (const(char)* name) {
18494 		void* res = wglGetProcAddress(name);
18495 		if (res is null) {
18496 			/+
18497 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18498 			import core.sys.windows.windef, core.sys.windows.winbase;
18499 			__gshared HINSTANCE dll = null;
18500 			if (dll is null) {
18501 				dll = LoadLibraryA("opengl32.dll");
18502 				if (dll is null) return null; // <32, but idc
18503 			}
18504 			res = GetProcAddress(dll, name);
18505 			+/
18506 			res = GetProcAddress(gl.libHandle, name);
18507 		}
18508 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18509 		return res;
18510 	}
18511 	}
18512 
18513 
18514  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18515         void wglSetVSync(bool wait) {
18516 		if(wglSwapIntervalEXT is null) {
18517 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18518 			if(wglSwapIntervalEXT is null)
18519 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18520 		}
18521 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18522 			return;
18523 
18524 		wglSwapIntervalEXT(wait ? 1 : 0);
18525 	}
18526 
18527 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18528 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18529 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18530 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18531 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18532 
18533 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18534 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18535 
18536 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18537 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18538 
18539 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18540 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18541 
18542 		void wglInitOtherFunctions () {
18543 			if (wglCreateContextAttribsARB is null) {
18544 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18545 			}
18546 		}
18547 	}
18548 
18549 	static if (!SdpyIsUsingIVGLBinds) {
18550 
18551 	interface GL {
18552 		extern(System) @nogc nothrow:
18553 
18554 		void glGetIntegerv(int, void*);
18555 		void glMatrixMode(int);
18556 		void glPushMatrix();
18557 		void glLoadIdentity();
18558 		void glOrtho(double, double, double, double, double, double);
18559 		void glFrustum(double, double, double, double, double, double);
18560 
18561 		void glPopMatrix();
18562 		void glEnable(int);
18563 		void glDisable(int);
18564 		void glClear(int);
18565 		void glBegin(int);
18566 		void glVertex2f(float, float);
18567 		void glVertex3f(float, float, float);
18568 		void glEnd();
18569 		void glColor3b(byte, byte, byte);
18570 		void glColor3ub(ubyte, ubyte, ubyte);
18571 		void glColor4b(byte, byte, byte, byte);
18572 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18573 		void glColor3i(int, int, int);
18574 		void glColor3ui(uint, uint, uint);
18575 		void glColor4i(int, int, int, int);
18576 		void glColor4ui(uint, uint, uint, uint);
18577 		void glColor3f(float, float, float);
18578 		void glColor4f(float, float, float, float);
18579 		void glTranslatef(float, float, float);
18580 		void glScalef(float, float, float);
18581 		version(X11) {
18582 			void glSecondaryColor3b(byte, byte, byte);
18583 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18584 			void glSecondaryColor3i(int, int, int);
18585 			void glSecondaryColor3ui(uint, uint, uint);
18586 			void glSecondaryColor3f(float, float, float);
18587 		}
18588 
18589 		void glDrawElements(int, int, int, void*);
18590 
18591 		void glRotatef(float, float, float, float);
18592 
18593 		uint glGetError();
18594 
18595 		void glDeleteTextures(int, uint*);
18596 
18597 
18598 		void glRasterPos2i(int, int);
18599 		void glDrawPixels(int, int, uint, uint, void*);
18600 		void glClearColor(float, float, float, float);
18601 
18602 
18603 		void glPixelStorei(uint, int);
18604 
18605 		void glGenTextures(uint, uint*);
18606 		void glBindTexture(int, int);
18607 		void glTexParameteri(uint, uint, int);
18608 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18609 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
18610 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18611 			/*GLsizei*/int width, /*GLsizei*/int height,
18612 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18613 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18614 
18615 		void glLineWidth(int);
18616 
18617 
18618 		void glTexCoord2f(float, float);
18619 		void glVertex2i(int, int);
18620 		void glBlendFunc (int, int);
18621 		void glDepthFunc (int);
18622 		void glViewport(int, int, int, int);
18623 
18624 		void glClearDepth(double);
18625 
18626 		void glReadBuffer(uint);
18627 		void glReadPixels(int, int, int, int, int, int, void*);
18628 
18629 		void glFlush();
18630 		void glFinish();
18631 
18632 		version(Windows) {
18633 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18634 			HGLRC wglCreateContext(HDC);
18635 			HGLRC wglCreateLayerContext(HDC, int);
18636 			BOOL wglDeleteContext(HGLRC);
18637 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18638 			HGLRC wglGetCurrentContext();
18639 			HDC wglGetCurrentDC();
18640 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18641 			PROC wglGetProcAddress(LPCSTR);
18642 			BOOL wglMakeCurrent(HDC, HGLRC);
18643 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18644 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18645 			BOOL wglShareLists(HGLRC, HGLRC);
18646 			BOOL wglSwapLayerBuffers(HDC, UINT);
18647 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18648 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18649 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18650 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18651 		}
18652 
18653 	}
18654 
18655 	interface GL3 {
18656 		extern(System) @nogc nothrow:
18657 
18658 		void glGenVertexArrays(GLsizei, GLuint*);
18659 		void glBindVertexArray(GLuint);
18660 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18661 		void glGenerateMipmap(GLenum);
18662 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18663 		void glStencilMask(GLuint);
18664 		void glStencilFunc(GLenum, GLint, GLuint);
18665 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18666 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18667 		GLuint glCreateProgram();
18668 		GLuint glCreateShader(GLenum);
18669 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18670 		void glCompileShader(GLuint);
18671 		void glGetShaderiv(GLuint, GLenum, GLint*);
18672 		void glAttachShader(GLuint, GLuint);
18673 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18674 		void glLinkProgram(GLuint);
18675 		void glGetProgramiv(GLuint, GLenum, GLint*);
18676 		void glDeleteProgram(GLuint);
18677 		void glDeleteShader(GLuint);
18678 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18679 		void glGenBuffers(GLsizei, GLuint*);
18680 
18681 		void glUniform1f(GLint location, GLfloat v0);
18682 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18683 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18684 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18685 		void glUniform1i(GLint location, GLint v0);
18686 		void glUniform2i(GLint location, GLint v0, GLint v1);
18687 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18688 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18689 		void glUniform1ui(GLint location, GLuint v0);
18690 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18691 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18692 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18693 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18694 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18695 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18696 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18697 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18698 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18699 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18700 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18701 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18702 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18703 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18704 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18705 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18706 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18707 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18708 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18709 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18710 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18711 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18712 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18713 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18714 
18715 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18716 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18717 		void glDrawArrays(GLenum, GLint, GLsizei);
18718 		void glStencilOp(GLenum, GLenum, GLenum);
18719 		void glUseProgram(GLuint);
18720 		void glCullFace(GLenum);
18721 		void glFrontFace(GLenum);
18722 		void glActiveTexture(GLenum);
18723 		void glBindBuffer(GLenum, GLuint);
18724 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18725 		void glEnableVertexAttribArray(GLuint);
18726 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18727 		void glUniform1i(GLint, GLint);
18728 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18729 		void glDisableVertexAttribArray(GLuint);
18730 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18731 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18732 		void glLogicOp (GLenum opcode);
18733 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18734 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18735 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18736 		GLenum glCheckFramebufferStatus (GLenum target);
18737 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18738 	}
18739 
18740 	interface GL4 {
18741 		extern(System) @nogc nothrow:
18742 
18743 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18744 			/*GLsizei*/int width, /*GLsizei*/int height,
18745 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18746 	}
18747 
18748 	interface GLU {
18749 		extern(System) @nogc nothrow:
18750 
18751 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18752 		void gluPerspective(double, double, double, double);
18753 
18754 		char* gluErrorString(uint);
18755 	}
18756 
18757 
18758 	enum GL_RED = 0x1903;
18759 	enum GL_ALPHA = 0x1906;
18760 
18761 	enum uint GL_FRONT = 0x0404;
18762 
18763 	enum uint GL_BLEND = 0x0be2;
18764 	enum uint GL_LEQUAL = 0x0203;
18765 
18766 
18767 	enum uint GL_RGB = 0x1907;
18768 	enum uint GL_BGRA = 0x80e1;
18769 	enum uint GL_RGBA = 0x1908;
18770 	enum uint GL_TEXTURE_2D =   0x0DE1;
18771 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18772 	enum uint GL_NEAREST = 0x2600;
18773 	enum uint GL_LINEAR = 0x2601;
18774 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18775 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18776 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18777 	enum uint GL_REPEAT = 0x2901;
18778 	enum uint GL_CLAMP = 0x2900;
18779 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18780 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18781 	enum uint GL_DECAL = 0x2101;
18782 	enum uint GL_MODULATE = 0x2100;
18783 	enum uint GL_TEXTURE_ENV = 0x2300;
18784 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18785 	enum uint GL_REPLACE = 0x1E01;
18786 	enum uint GL_LIGHTING = 0x0B50;
18787 	enum uint GL_DITHER = 0x0BD0;
18788 
18789 	enum uint GL_NO_ERROR = 0;
18790 
18791 
18792 
18793 	enum int GL_VIEWPORT = 0x0BA2;
18794 	enum int GL_MODELVIEW = 0x1700;
18795 	enum int GL_TEXTURE = 0x1702;
18796 	enum int GL_PROJECTION = 0x1701;
18797 	enum int GL_DEPTH_TEST = 0x0B71;
18798 
18799 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18800 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18801 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18802 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18803 
18804 	enum int GL_POINTS = 0x0000;
18805 	enum int GL_LINES =  0x0001;
18806 	enum int GL_LINE_LOOP = 0x0002;
18807 	enum int GL_LINE_STRIP = 0x0003;
18808 	enum int GL_TRIANGLES = 0x0004;
18809 	enum int GL_TRIANGLE_STRIP = 5;
18810 	enum int GL_TRIANGLE_FAN = 6;
18811 	enum int GL_QUADS = 7;
18812 	enum int GL_QUAD_STRIP = 8;
18813 	enum int GL_POLYGON = 9;
18814 
18815 	alias GLvoid = void;
18816 	alias GLboolean = ubyte;
18817 	alias GLint = int;
18818 	alias GLuint = uint;
18819 	alias GLenum = uint;
18820 	alias GLchar = char;
18821 	alias GLsizei = int;
18822 	alias GLfloat = float;
18823 	alias GLintptr = size_t;
18824 	alias GLsizeiptr = ptrdiff_t;
18825 
18826 
18827 	enum uint GL_INVALID_ENUM = 0x0500;
18828 
18829 	enum uint GL_ZERO = 0;
18830 	enum uint GL_ONE = 1;
18831 
18832 	enum uint GL_BYTE = 0x1400;
18833 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18834 	enum uint GL_SHORT = 0x1402;
18835 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18836 	enum uint GL_INT = 0x1404;
18837 	enum uint GL_UNSIGNED_INT = 0x1405;
18838 	enum uint GL_FLOAT = 0x1406;
18839 	enum uint GL_2_BYTES = 0x1407;
18840 	enum uint GL_3_BYTES = 0x1408;
18841 	enum uint GL_4_BYTES = 0x1409;
18842 	enum uint GL_DOUBLE = 0x140A;
18843 
18844 	enum uint GL_STREAM_DRAW = 0x88E0;
18845 
18846 	enum uint GL_CCW = 0x0901;
18847 
18848 	enum uint GL_STENCIL_TEST = 0x0B90;
18849 	enum uint GL_SCISSOR_TEST = 0x0C11;
18850 
18851 	enum uint GL_EQUAL = 0x0202;
18852 	enum uint GL_NOTEQUAL = 0x0205;
18853 
18854 	enum uint GL_ALWAYS = 0x0207;
18855 	enum uint GL_KEEP = 0x1E00;
18856 
18857 	enum uint GL_INCR = 0x1E02;
18858 
18859 	enum uint GL_INCR_WRAP = 0x8507;
18860 	enum uint GL_DECR_WRAP = 0x8508;
18861 
18862 	enum uint GL_CULL_FACE = 0x0B44;
18863 	enum uint GL_BACK = 0x0405;
18864 
18865 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18866 	enum uint GL_VERTEX_SHADER = 0x8B31;
18867 
18868 	enum uint GL_COMPILE_STATUS = 0x8B81;
18869 	enum uint GL_LINK_STATUS = 0x8B82;
18870 
18871 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18872 
18873 	enum uint GL_STATIC_DRAW = 0x88E4;
18874 
18875 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18876 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18877 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18878 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18879 
18880 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18881 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18882 
18883 	enum uint GL_TEXTURE0 = 0x84C0U;
18884 	enum uint GL_TEXTURE1 = 0x84C1U;
18885 
18886 	enum uint GL_ARRAY_BUFFER = 0x8892;
18887 
18888 	enum uint GL_SRC_COLOR = 0x0300;
18889 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18890 	enum uint GL_SRC_ALPHA = 0x0302;
18891 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18892 	enum uint GL_DST_ALPHA = 0x0304;
18893 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18894 	enum uint GL_DST_COLOR = 0x0306;
18895 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18896 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18897 
18898 	enum uint GL_INVERT = 0x150AU;
18899 
18900 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18901 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18902 
18903 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18904 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18905 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18906 
18907 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18908 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18909 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18910 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18911 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18912 
18913 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18914 	enum uint GL_CLEAR = 0x1500U;
18915 	enum uint GL_COPY = 0x1503U;
18916 	enum uint GL_XOR = 0x1506U;
18917 
18918 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18919 
18920 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18921 
18922 	}
18923 }
18924 
18925 /++
18926 	History:
18927 		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.
18928 +/
18929 __gshared bool gluSuccessfullyLoaded = true;
18930 
18931 version(without_opengl) {} else {
18932 static if(!SdpyIsUsingIVGLBinds) {
18933 	version(Windows) {
18934 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18935 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18936 	} else {
18937 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18938 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18939 	}
18940 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18941 
18942 
18943 	shared static this() {
18944 		gl.loadDynamicLibrary();
18945 
18946 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18947 		// unless those functions are actually used
18948 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18949 		glu.loadDynamicLibrary();
18950 	}
18951 }
18952 }
18953 
18954 /++
18955 	Convenience method for converting D arrays to opengl buffer data
18956 
18957 	I would LOVE to overload it with the original glBufferData, but D won't
18958 	let me since glBufferData is a function pointer :(
18959 
18960 	Added: August 25, 2020 (version 8.5)
18961 +/
18962 version(without_opengl) {} else
18963 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18964 	glBufferData(target, data.length, data.ptr, usage);
18965 }
18966 
18967 /+
18968 /++
18969 	A matrix for simple uses that easily integrates with [OpenGlShader].
18970 
18971 	Might not be useful to you since it only as some simple functions and
18972 	probably isn't that fast.
18973 
18974 	Note it uses an inline static array for its storage, so copying it
18975 	may be expensive.
18976 +/
18977 struct BasicMatrix(int columns, int rows, T = float) {
18978 	import core.stdc.math;
18979 
18980 	T[columns * rows] data = 0.0;
18981 
18982 	/++
18983 		Basic operations that operate *in place*.
18984 	+/
18985 	void translate() {
18986 
18987 	}
18988 
18989 	/// ditto
18990 	void scale() {
18991 
18992 	}
18993 
18994 	/// ditto
18995 	void rotate() {
18996 
18997 	}
18998 
18999 	/++
19000 
19001 	+/
19002 	static if(columns == rows)
19003 	static BasicMatrix identity() {
19004 		BasicMatrix m;
19005 		foreach(i; 0 .. columns)
19006 			data[0 + i + i * columns] = 1.0;
19007 		return m;
19008 	}
19009 
19010 	static BasicMatrix ortho() {
19011 		return BasicMatrix.init;
19012 	}
19013 }
19014 +/
19015 
19016 /++
19017 	Convenience class for using opengl shaders.
19018 
19019 	Ensure that you've loaded opengl 3+ and set your active
19020 	context before trying to use this.
19021 
19022 	Added: August 25, 2020 (version 8.5)
19023 +/
19024 version(without_opengl) {} else
19025 final class OpenGlShader {
19026 	private int shaderProgram_;
19027 	private @property void shaderProgram(int a) {
19028 		shaderProgram_ = a;
19029 	}
19030 	/// Get the program ID for use in OpenGL functions.
19031 	public @property int shaderProgram() {
19032 		return shaderProgram_;
19033 	}
19034 
19035 	/++
19036 
19037 	+/
19038 	static struct Source {
19039 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19040 		string code; ///
19041 	}
19042 
19043 	/++
19044 		Helper method to just compile some shader code and check for errors
19045 		while you do glCreateShader, etc. on the outside yourself.
19046 
19047 		This just does `glShaderSource` and `glCompileShader` for the given code.
19048 
19049 		If you the OpenGlShader class constructor, you never need to call this yourself.
19050 	+/
19051 	static void compile(int sid, Source code) {
19052 		const(char)*[1] buffer;
19053 		int[1] lengthBuffer;
19054 
19055 		buffer[0] = code.code.ptr;
19056 		lengthBuffer[0] = cast(int) code.code.length;
19057 
19058 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19059 		glCompileShader(sid);
19060 
19061 		int success;
19062 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19063 		if(!success) {
19064 			char[512] info;
19065 			int len;
19066 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19067 
19068 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19069 		}
19070 	}
19071 
19072 	/++
19073 		Calls `glLinkProgram` and throws if error a occurs.
19074 
19075 		If you the OpenGlShader class constructor, you never need to call this yourself.
19076 	+/
19077 	static void link(int shaderProgram) {
19078 		glLinkProgram(shaderProgram);
19079 		int success;
19080 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19081 		if(!success) {
19082 			char[512] info;
19083 			int len;
19084 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19085 
19086 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19087 		}
19088 	}
19089 
19090 	/++
19091 		Constructs the shader object by calling `glCreateProgram`, then
19092 		compiling each given [Source], and finally, linking them together.
19093 
19094 		Throws: on compile or link failure.
19095 	+/
19096 	this(Source[] codes...) {
19097 		shaderProgram = glCreateProgram();
19098 
19099 		int[16] shadersBufferStack;
19100 
19101 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19102 			shadersBufferStack[0 .. codes.length] :
19103 			new int[](codes.length);
19104 
19105 		foreach(idx, code; codes) {
19106 			shadersBuffer[idx] = glCreateShader(code.type);
19107 
19108 			compile(shadersBuffer[idx], code);
19109 
19110 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19111 		}
19112 
19113 		link(shaderProgram);
19114 
19115 		foreach(s; shadersBuffer)
19116 			glDeleteShader(s);
19117 	}
19118 
19119 	/// Calls `glUseProgram(this.shaderProgram)`
19120 	void use() {
19121 		glUseProgram(this.shaderProgram);
19122 	}
19123 
19124 	/// Deletes the program.
19125 	void delete_() {
19126 		glDeleteProgram(shaderProgram);
19127 		shaderProgram = 0;
19128 	}
19129 
19130 	/++
19131 		[OpenGlShader.uniforms].name gives you one of these.
19132 
19133 		You can get the id out of it or just assign
19134 	+/
19135 	static struct Uniform {
19136 		/// the id passed to glUniform*
19137 		int id;
19138 
19139 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19140 		void opAssign(float x, float y, float z, float w) {
19141 			if(id != -1)
19142 			glUniform4f(id, x, y, z, w);
19143 		}
19144 
19145 		void opAssign(float x) {
19146 			if(id != -1)
19147 			glUniform1f(id, x);
19148 		}
19149 
19150 		void opAssign(float x, float y) {
19151 			if(id != -1)
19152 			glUniform2f(id, x, y);
19153 		}
19154 
19155 		void opAssign(T)(T t) {
19156 			t.glUniform(id);
19157 		}
19158 	}
19159 
19160 	static struct UniformsHelper {
19161 		OpenGlShader _shader;
19162 
19163 		@property Uniform opDispatch(string name)() {
19164 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19165 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19166 			//if(i == -1)
19167 				//throw new Exception("Could not find uniform " ~ name);
19168 			return Uniform(i);
19169 		}
19170 
19171 		@property void opDispatch(string name, T)(T t) {
19172 			Uniform f = this.opDispatch!name;
19173 			t.glUniform(f);
19174 		}
19175 	}
19176 
19177 	/++
19178 		Gives access to the uniforms through dot access.
19179 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19180 	+/
19181 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19182 }
19183 
19184 version(without_opengl) {} else {
19185 /++
19186 	A static container of experimental types and value constructors for opengl 3+ shaders.
19187 
19188 
19189 	You can declare variables like:
19190 
19191 	```
19192 	OGL.vec3f something;
19193 	```
19194 
19195 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19196 
19197 	```
19198 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19199 	```
19200 
19201 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19202 
19203 
19204 	History:
19205 		Added December 7, 2021. Not yet stable.
19206 +/
19207 final class OGL {
19208 	static:
19209 
19210 	private template typeFromSpecifier(string specifier) {
19211 		static if(specifier == "f")
19212 			alias typeFromSpecifier = GLfloat;
19213 		else static if(specifier == "i")
19214 			alias typeFromSpecifier = GLint;
19215 		else static if(specifier == "ui")
19216 			alias typeFromSpecifier = GLuint;
19217 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19218 	}
19219 
19220 	private template CommonType(T...) {
19221 		static if(T.length == 1)
19222 			alias CommonType = T[0];
19223 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19224 			alias CommonType = CommonType!(C, T[2 .. $]);
19225 	}
19226 
19227 	private template typesToSpecifier(T...) {
19228 		static if(is(CommonType!T == float))
19229 			enum typesToSpecifier = "f";
19230 		else static if(is(CommonType!T == int))
19231 			enum typesToSpecifier = "i";
19232 		else static if(is(CommonType!T == uint))
19233 			enum typesToSpecifier = "ui";
19234 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19235 	}
19236 
19237 	private template genNames(size_t dim, size_t dim2 = 0) {
19238 		string helper() {
19239 			string s;
19240 			if(dim2) {
19241 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19242 			} else {
19243 				if(dim > 0) s ~= "type x = 0;";
19244 				if(dim > 1) s ~= "type y = 0;";
19245 				if(dim > 2) s ~= "type z = 0;";
19246 				if(dim > 3) s ~= "type w = 0;";
19247 			}
19248 			return s;
19249 		}
19250 
19251 		enum genNames = helper();
19252 	}
19253 
19254 	// there's vec, arrays of vec, mat, and arrays of mat
19255 	template opDispatch(string name)
19256 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19257 	{
19258 		static if(name[4] == 'x') {
19259 			enum dimX = cast(int) (name[3] - '0');
19260 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19261 
19262 			enum dimY = cast(int) (name[5] - '0');
19263 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19264 
19265 			enum isArray = name[$ - 1] == 'v';
19266 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19267 			alias type = typeFromSpecifier!typeSpecifier;
19268 		} else {
19269 			enum dim = cast(int) (name[3] - '0');
19270 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19271 			enum isArray = name[$ - 1] == 'v';
19272 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19273 			alias type = typeFromSpecifier!typeSpecifier;
19274 		}
19275 
19276 		align(1)
19277 		struct opDispatch {
19278 			align(1):
19279 			static if(name[4] == 'x')
19280 				mixin(genNames!(dimX, dimY));
19281 			else
19282 				mixin(genNames!dim);
19283 
19284 			private void glUniform(OpenGlShader.Uniform assignTo) {
19285 				glUniform(assignTo.id);
19286 			}
19287 			private void glUniform(int assignTo) {
19288 				static if(name[4] == 'x') {
19289 					// FIXME
19290 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19291 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19292 				} else
19293 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19294 			}
19295 		}
19296 	}
19297 
19298 	auto vec(T...)(T members) {
19299 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19300 	}
19301 }
19302 }
19303 
19304 version(linux) {
19305 	version(with_eventloop) {} else {
19306 		private int epollFd = -1;
19307 		void prepareEventLoop() {
19308 			if(epollFd != -1)
19309 				return; // already initialized, no need to do it again
19310 			import ep = core.sys.linux.epoll;
19311 
19312 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19313 			if(epollFd == -1)
19314 				throw new Exception("epoll create failure");
19315 		}
19316 	}
19317 } else version(Posix) {
19318 	void prepareEventLoop() {}
19319 }
19320 
19321 version(X11) {
19322 	import core.stdc.locale : LC_ALL; // rdmd fix
19323 	__gshared bool sdx_isUTF8Locale;
19324 
19325 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19326 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19327 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19328 	// anal magic is here. I (Ketmar) hope you like it.
19329 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19330 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19331 	// later.
19332 
19333 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19334 	shared static this () {
19335 		if(!librariesSuccessfullyLoaded)
19336 			return;
19337 
19338 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19339 
19340 		// this doesn't hurt; it may add some locking, but the speed is still
19341 		// allows doing 60 FPS videogames; also, ignore the result, as most
19342 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19343 		// never seen this failing).
19344 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19345 
19346 		setlocale(LC_ALL, "");
19347 		// check if out locale is UTF-8
19348 		auto lct = setlocale(LC_CTYPE, null);
19349 		if (lct is null) {
19350 			sdx_isUTF8Locale = false;
19351 		} else {
19352 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19353 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19354 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19355 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19356 				{
19357 					sdx_isUTF8Locale = true;
19358 					break;
19359 				}
19360 			}
19361 		}
19362 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19363 	}
19364 }
19365 
19366 class ExperimentalTextComponent2 {
19367 	/+
19368 		Stage 1: get it working monospace
19369 		Stage 2: use proportional font
19370 		Stage 3: allow changes in inline style
19371 		Stage 4: allow new fonts and sizes in the middle
19372 		Stage 5: optimize gap buffer
19373 		Stage 6: optimize layout
19374 		Stage 7: word wrap
19375 		Stage 8: justification
19376 		Stage 9: editing, selection, etc.
19377 
19378 			Operations:
19379 				insert text
19380 				overstrike text
19381 				select
19382 				cut
19383 				modify
19384 	+/
19385 
19386 	/++
19387 		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.
19388 	+/
19389 	this(SimpleWindow window) {
19390 		this.window = window;
19391 	}
19392 
19393 	private SimpleWindow window;
19394 
19395 
19396 	/++
19397 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19398 		representing the internal parts. The first pass is focused on the x parameter, then the
19399 		renderer is responsible for going back to the parts in the current line and calling
19400 		adjustDownForAscent to change the y params.
19401 	+/
19402 	static interface ComponentRenderHelper {
19403 
19404 		/+
19405 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19406 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19407 			to move (adjust y to make room for new line) until you get back to the same position,
19408 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19409 
19410 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19411 			once you reach something that is unchanged, you can stop.
19412 		+/
19413 
19414 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19415 
19416 		int ascent() const;
19417 		int descent() const;
19418 
19419 		int advance() const;
19420 
19421 		bool endsWithExplititLineBreak() const;
19422 	}
19423 
19424 	static interface RenderResult {
19425 		/++
19426 			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.
19427 		+/
19428 		void popFront();
19429 		@property bool empty() const;
19430 		@property ComponentRenderHelper front() const;
19431 
19432 		void repositionForNextLine(Point baseline, int availableWidth);
19433 	}
19434 
19435 	static interface ComponentInFlow {
19436 		void draw(ScreenPainter painter);
19437 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19438 
19439 		bool startsWithExplicitLineBreak() const;
19440 	}
19441 
19442 	static class TextFlowComponent : ComponentInFlow {
19443 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19444 
19445 		Color foreground;
19446 		Color background;
19447 
19448 		OperatingSystemFont font; // should NEVER be null
19449 
19450 		ubyte attributes; // underline, strike through, display on new block
19451 
19452 		version(Windows)
19453 			const(wchar)[] content;
19454 		else
19455 			const(char)[] content; // this should NEVER have a newline, except at the end
19456 
19457 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19458 
19459 		// could prolly put some spacing around it too like margin / padding
19460 
19461 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19462 			in { assert(font !is null);
19463 			     assert(!font.isNull); }
19464 			do
19465 		{
19466 			this.foreground = f;
19467 			this.background = b;
19468 			this.font = font;
19469 
19470 			this.attributes = attr;
19471 			version(Windows) {
19472 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19473 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19474 				auto buffer = new wchar[](sz);
19475 				this.content = makeWindowsString(c, buffer, conversionFlags);
19476 			} else {
19477 				this.content = c.dup;
19478 			}
19479 		}
19480 
19481 		void draw(ScreenPainter painter) {
19482 			painter.setFont(this.font);
19483 			painter.outlineColor = this.foreground;
19484 			painter.fillColor = Color.transparent;
19485 			foreach(rendered; this.rendered) {
19486 				// the component works in term of baseline,
19487 				// but the painter works in term of upper left bounding box
19488 				// so need to translate that
19489 
19490 				if(this.background.a) {
19491 					painter.fillColor = this.background;
19492 					painter.outlineColor = this.background;
19493 
19494 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19495 
19496 					painter.outlineColor = this.foreground;
19497 					painter.fillColor = Color.transparent;
19498 				}
19499 
19500 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19501 
19502 				// FIXME: strike through, underline, highlight selection, etc.
19503 			}
19504 		}
19505 	}
19506 
19507 	// I could split the parts into words on render
19508 	// for easier word-wrap, each one being an unbreakable "inline-block"
19509 	private TextFlowComponent[] parts;
19510 	private int needsRerenderFrom;
19511 
19512 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19513 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19514 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19515 	}
19516 
19517 	static struct RenderedComponent {
19518 		int startX;
19519 		int startY;
19520 		short width;
19521 		// 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!
19522 		// for individual chars in here you've gotta process on demand
19523 		version(Windows)
19524 			const(wchar)[] slice;
19525 		else
19526 			const(char)[] slice;
19527 	}
19528 
19529 
19530 	void rerender(Rectangle boundingBox) {
19531 		Point baseline = boundingBox.upperLeft;
19532 
19533 		this.boundingBox.left = boundingBox.left;
19534 		this.boundingBox.top = boundingBox.top;
19535 
19536 		auto remainingParts = parts;
19537 
19538 		int largestX;
19539 
19540 
19541 		foreach(part; parts)
19542 			part.font.prepareContext(window);
19543 		scope(exit)
19544 		foreach(part; parts)
19545 			part.font.releaseContext();
19546 
19547 		calculateNextLine:
19548 
19549 		int nextLineHeight = 0;
19550 		int nextBiggestDescent = 0;
19551 
19552 		foreach(part; remainingParts) {
19553 			auto height = part.font.ascent;
19554 			if(height > nextLineHeight)
19555 				nextLineHeight = height;
19556 			if(part.font.descent > nextBiggestDescent)
19557 				nextBiggestDescent = part.font.descent;
19558 			if(part.content.length && part.content[$-1] == '\n')
19559 				break;
19560 		}
19561 
19562 		baseline.y += nextLineHeight;
19563 		auto lineStart = baseline;
19564 
19565 		while(remainingParts.length) {
19566 			remainingParts[0].rendered = null;
19567 
19568 			bool eol;
19569 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19570 				eol = true;
19571 
19572 			// FIXME: word wrap
19573 			auto font = remainingParts[0].font;
19574 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19575 			auto width = font.stringWidth(slice, window);
19576 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19577 
19578 			remainingParts = remainingParts[1 .. $];
19579 			baseline.x += width;
19580 
19581 			if(eol) {
19582 				baseline.y += nextBiggestDescent;
19583 				if(baseline.x > largestX)
19584 					largestX = baseline.x;
19585 				baseline.x = lineStart.x;
19586 				goto calculateNextLine;
19587 			}
19588 		}
19589 
19590 		if(baseline.x > largestX)
19591 			largestX = baseline.x;
19592 
19593 		this.boundingBox.right = largestX;
19594 		this.boundingBox.bottom = baseline.y;
19595 	}
19596 
19597 	// you must call rerender first!
19598 	void draw(ScreenPainter painter) {
19599 		foreach(part; parts) {
19600 			part.draw(painter);
19601 		}
19602 	}
19603 
19604 	struct IdentifyResult {
19605 		TextFlowComponent part;
19606 		int charIndexInPart;
19607 		int totalCharIndex = -1; // if this is -1, it just means the end
19608 
19609 		Rectangle boundingBox;
19610 	}
19611 
19612 	IdentifyResult identify(Point pt, bool exact = false) {
19613 		if(parts.length == 0)
19614 			return IdentifyResult(null, 0);
19615 
19616 		if(pt.y < boundingBox.top) {
19617 			if(exact)
19618 				return IdentifyResult(null, 1);
19619 			return IdentifyResult(parts[0], 0);
19620 		}
19621 		if(pt.y > boundingBox.bottom) {
19622 			if(exact)
19623 				return IdentifyResult(null, 2);
19624 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19625 		}
19626 
19627 		int tci = 0;
19628 
19629 		// I should probably like binary search this or something...
19630 		foreach(ref part; parts) {
19631 			foreach(rendered; part.rendered) {
19632 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19633 				if(rect.contains(pt)) {
19634 					auto x = pt.x - rendered.startX;
19635 					auto estimatedIdx = x / part.font.averageWidth;
19636 
19637 					if(estimatedIdx < 0)
19638 						estimatedIdx = 0;
19639 
19640 					if(estimatedIdx > rendered.slice.length)
19641 						estimatedIdx = cast(int) rendered.slice.length;
19642 
19643 					int idx;
19644 					int x1, x2;
19645 					if(part.font.isMonospace) {
19646 						auto w = part.font.averageWidth;
19647 						if(!exact && x > (estimatedIdx + 1) * w)
19648 							return IdentifyResult(null, 4);
19649 						idx = estimatedIdx;
19650 						x1 = idx * w;
19651 						x2 = (idx + 1) * w;
19652 					} else {
19653 						idx = estimatedIdx;
19654 
19655 						part.font.prepareContext(window);
19656 						scope(exit) part.font.releaseContext();
19657 
19658 						// int iterations;
19659 
19660 						while(true) {
19661 							// iterations++;
19662 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19663 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19664 
19665 							x1 += rendered.startX;
19666 							x2 += rendered.startX;
19667 
19668 							if(pt.x < x1) {
19669 								if(idx == 0) {
19670 									if(exact)
19671 										return IdentifyResult(null, 6);
19672 									else
19673 										break;
19674 								}
19675 								idx--;
19676 							} else if(pt.x > x2) {
19677 								idx++;
19678 								if(idx > rendered.slice.length) {
19679 									if(exact)
19680 										return IdentifyResult(null, 5);
19681 									else
19682 										break;
19683 								}
19684 							} else if(pt.x >= x1 && pt.x <= x2) {
19685 								if(idx)
19686 									idx--; // point it at the original index
19687 								break; // we fit
19688 							}
19689 						}
19690 
19691 						// writeln(iterations)
19692 					}
19693 
19694 
19695 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19696 				}
19697 			}
19698 			tci += cast(int) part.content.length; // FIXME: utf-8?
19699 		}
19700 		return IdentifyResult(null, 3);
19701 	}
19702 
19703 	Rectangle boundingBox; // only set after [rerender]
19704 
19705 	// text will be positioned around the exclusion zone
19706 	static struct ExclusionZone {
19707 
19708 	}
19709 
19710 	ExclusionZone[] exclusionZones;
19711 }
19712 
19713 
19714 // Don't use this yet. When I'm happy with it, I will move it to the
19715 // regular module namespace.
19716 mixin template ExperimentalTextComponent() {
19717 
19718 static:
19719 
19720 	alias Rectangle = arsd.color.Rectangle;
19721 
19722 	struct ForegroundColor {
19723 		Color color;
19724 		alias color this;
19725 
19726 		this(Color c) {
19727 			color = c;
19728 		}
19729 
19730 		this(int r, int g, int b, int a = 255) {
19731 			color = Color(r, g, b, a);
19732 		}
19733 
19734 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19735 			return ForegroundColor(mixin("Color." ~ s));
19736 		}
19737 	}
19738 
19739 	struct BackgroundColor {
19740 		Color color;
19741 		alias color this;
19742 
19743 		this(Color c) {
19744 			color = c;
19745 		}
19746 
19747 		this(int r, int g, int b, int a = 255) {
19748 			color = Color(r, g, b, a);
19749 		}
19750 
19751 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19752 			return BackgroundColor(mixin("Color." ~ s));
19753 		}
19754 	}
19755 
19756 	static class InlineElement {
19757 		string text;
19758 
19759 		BlockElement containingBlock;
19760 
19761 		Color color = Color.black;
19762 		Color backgroundColor = Color.transparent;
19763 		ushort styles;
19764 
19765 		string font;
19766 		int fontSize;
19767 
19768 		int lineHeight;
19769 
19770 		void* identifier;
19771 
19772 		Rectangle boundingBox;
19773 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19774 
19775 		bool isMergeCompatible(InlineElement other) {
19776 			return
19777 				containingBlock is other.containingBlock &&
19778 				color == other.color &&
19779 				backgroundColor == other.backgroundColor &&
19780 				styles == other.styles &&
19781 				font == other.font &&
19782 				fontSize == other.fontSize &&
19783 				lineHeight == other.lineHeight &&
19784 				true;
19785 		}
19786 
19787 		int xOfIndex(size_t index) {
19788 			if(index < letterXs.length)
19789 				return letterXs[index];
19790 			else
19791 				return boundingBox.right;
19792 		}
19793 
19794 		InlineElement clone() {
19795 			auto ie = new InlineElement();
19796 			ie.tupleof = this.tupleof;
19797 			return ie;
19798 		}
19799 
19800 		InlineElement getPreviousInlineElement() {
19801 			InlineElement prev = null;
19802 			foreach(ie; this.containingBlock.parts) {
19803 				if(ie is this)
19804 					break;
19805 				prev = ie;
19806 			}
19807 			if(prev is null) {
19808 				BlockElement pb;
19809 				BlockElement cb = this.containingBlock;
19810 				moar:
19811 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19812 					if(ie is cb)
19813 						break;
19814 					pb = ie;
19815 				}
19816 				if(pb is null)
19817 					return null;
19818 				if(pb.parts.length == 0) {
19819 					cb = pb;
19820 					goto moar;
19821 				}
19822 
19823 				prev = pb.parts[$-1];
19824 
19825 			}
19826 			return prev;
19827 		}
19828 
19829 		InlineElement getNextInlineElement() {
19830 			InlineElement next = null;
19831 			foreach(idx, ie; this.containingBlock.parts) {
19832 				if(ie is this) {
19833 					if(idx + 1 < this.containingBlock.parts.length)
19834 						next = this.containingBlock.parts[idx + 1];
19835 					break;
19836 				}
19837 			}
19838 			if(next is null) {
19839 				BlockElement n;
19840 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19841 					if(ie is this.containingBlock) {
19842 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19843 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19844 						break;
19845 					}
19846 				}
19847 				if(n is null)
19848 					return null;
19849 
19850 				if(n.parts.length)
19851 					next = n.parts[0];
19852 				else {} // FIXME
19853 
19854 			}
19855 			return next;
19856 		}
19857 
19858 	}
19859 
19860 	// Block elements are used entirely for positioning inline elements,
19861 	// which are the things that are actually drawn.
19862 	class BlockElement {
19863 		InlineElement[] parts;
19864 		uint alignment;
19865 
19866 		int whiteSpace; // pre, pre-wrap, wrap
19867 
19868 		TextLayout containingLayout;
19869 
19870 		// inputs
19871 		Point where;
19872 		Size minimumSize;
19873 		Size maximumSize;
19874 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19875 		void* identifier;
19876 
19877 		Rectangle margin;
19878 		Rectangle padding;
19879 
19880 		// outputs
19881 		Rectangle[] boundingBoxes;
19882 	}
19883 
19884 	struct TextIdentifyResult {
19885 		InlineElement element;
19886 		int offset;
19887 
19888 		private TextIdentifyResult fixupNewline() {
19889 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19890 				offset--;
19891 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19892 				offset--;
19893 			}
19894 			return this;
19895 		}
19896 	}
19897 
19898 	class TextLayout {
19899 		BlockElement[] blocks;
19900 		Rectangle boundingBox_;
19901 		Rectangle boundingBox() { return boundingBox_; }
19902 		void boundingBox(Rectangle r) {
19903 			if(r != boundingBox_) {
19904 				boundingBox_ = r;
19905 				layoutInvalidated = true;
19906 			}
19907 		}
19908 
19909 		Rectangle contentBoundingBox() {
19910 			Rectangle r;
19911 			foreach(block; blocks)
19912 			foreach(ie; block.parts) {
19913 				if(ie.boundingBox.right > r.right)
19914 					r.right = ie.boundingBox.right;
19915 				if(ie.boundingBox.bottom > r.bottom)
19916 					r.bottom = ie.boundingBox.bottom;
19917 			}
19918 			return r;
19919 		}
19920 
19921 		BlockElement[] getBlocks() {
19922 			return blocks;
19923 		}
19924 
19925 		InlineElement[] getTexts() {
19926 			InlineElement[] elements;
19927 			foreach(block; blocks)
19928 				elements ~= block.parts;
19929 			return elements;
19930 		}
19931 
19932 		string getPlainText() {
19933 			string text;
19934 			foreach(block; blocks)
19935 				foreach(part; block.parts)
19936 					text ~= part.text;
19937 			return text;
19938 		}
19939 
19940 		string getHtml() {
19941 			return null; // FIXME
19942 		}
19943 
19944 		this(Rectangle boundingBox) {
19945 			this.boundingBox = boundingBox;
19946 		}
19947 
19948 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19949 			auto be = new BlockElement();
19950 			be.containingLayout = this;
19951 			if(after is null)
19952 				blocks ~= be;
19953 			else {
19954 				foreach(idx, b; blocks) {
19955 					if(b is after.containingBlock) {
19956 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19957 						break;
19958 					}
19959 				}
19960 			}
19961 			return be;
19962 		}
19963 
19964 		void clear() {
19965 			blocks = null;
19966 			selectionStart = selectionEnd = caret = Caret.init;
19967 		}
19968 
19969 		void addText(Args...)(Args args) {
19970 			if(blocks.length == 0)
19971 				addBlock();
19972 
19973 			InlineElement ie = new InlineElement();
19974 			foreach(idx, arg; args) {
19975 				static if(is(typeof(arg) == ForegroundColor))
19976 					ie.color = arg;
19977 				else static if(is(typeof(arg) == TextFormat)) {
19978 					if(arg & 0x8000) // ~TextFormat.something turns it off
19979 						ie.styles &= arg;
19980 					else
19981 						ie.styles |= arg;
19982 				} else static if(is(typeof(arg) == string)) {
19983 					static if(idx == 0 && args.length > 1)
19984 						static assert(0, "Put styles before the string.");
19985 					size_t lastLineIndex;
19986 					foreach(cidx, char a; arg) {
19987 						if(a == '\n') {
19988 							ie.text = arg[lastLineIndex .. cidx + 1];
19989 							lastLineIndex = cidx + 1;
19990 							ie.containingBlock = blocks[$-1];
19991 							blocks[$-1].parts ~= ie.clone;
19992 							ie.text = null;
19993 						} else {
19994 
19995 						}
19996 					}
19997 
19998 					ie.text = arg[lastLineIndex .. $];
19999 					ie.containingBlock = blocks[$-1];
20000 					blocks[$-1].parts ~= ie.clone;
20001 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
20002 				}
20003 			}
20004 
20005 			invalidateLayout();
20006 		}
20007 
20008 		void tryMerge(InlineElement into, InlineElement what) {
20009 			if(!into.isMergeCompatible(what)) {
20010 				return; // cannot merge, different configs
20011 			}
20012 
20013 			// cool, can merge, bring text together...
20014 			into.text ~= what.text;
20015 
20016 			// and remove what
20017 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
20018 				if(what.containingBlock.parts[a] is what) {
20019 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
20020 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
20021 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
20022 
20023 				}
20024 			}
20025 
20026 			// FIXME: ensure no other carets have a reference to it
20027 		}
20028 
20029 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
20030 		TextIdentifyResult identify(int x, int y, bool exact = false) {
20031 			TextIdentifyResult inexactMatch;
20032 			foreach(block; blocks) {
20033 				foreach(part; block.parts) {
20034 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
20035 
20036 						// FIXME binary search
20037 						int tidx;
20038 						int lastX;
20039 						foreach_reverse(idxo, lx; part.letterXs) {
20040 							int idx = cast(int) idxo;
20041 							if(lx <= x) {
20042 								if(lastX && lastX - x < x - lx)
20043 									tidx = idx + 1;
20044 								else
20045 									tidx = idx;
20046 								break;
20047 							}
20048 							lastX = lx;
20049 						}
20050 
20051 						return TextIdentifyResult(part, tidx).fixupNewline;
20052 					} else if(!exact) {
20053 						// we're not in the box, but are we on the same line?
20054 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
20055 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
20056 					}
20057 				}
20058 			}
20059 
20060 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
20061 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
20062 
20063 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
20064 		}
20065 
20066 		void moveCaretToPixelCoordinates(int x, int y) {
20067 			auto result = identify(x, y);
20068 			caret.inlineElement = result.element;
20069 			caret.offset = result.offset;
20070 		}
20071 
20072 		void selectToPixelCoordinates(int x, int y) {
20073 			auto result = identify(x, y);
20074 
20075 			if(y < caretLastDrawnY1) {
20076 				// on a previous line, carat is selectionEnd
20077 				selectionEnd = caret;
20078 
20079 				selectionStart = Caret(this, result.element, result.offset);
20080 			} else if(y > caretLastDrawnY2) {
20081 				// on a later line
20082 				selectionStart = caret;
20083 
20084 				selectionEnd = Caret(this, result.element, result.offset);
20085 			} else {
20086 				// on the same line...
20087 				if(x <= caretLastDrawnX) {
20088 					selectionEnd = caret;
20089 					selectionStart = Caret(this, result.element, result.offset);
20090 				} else {
20091 					selectionStart = caret;
20092 					selectionEnd = Caret(this, result.element, result.offset);
20093 				}
20094 
20095 			}
20096 		}
20097 
20098 
20099 		/// Call this if the inputs change. It will reflow everything
20100 		void redoLayout(ScreenPainter painter) {
20101 			//painter.setClipRectangle(boundingBox);
20102 			auto pos = Point(boundingBox.left, boundingBox.top);
20103 
20104 			int lastHeight;
20105 			void nl() {
20106 				pos.x = boundingBox.left;
20107 				pos.y += lastHeight;
20108 			}
20109 			foreach(block; blocks) {
20110 				nl();
20111 				foreach(part; block.parts) {
20112 					part.letterXs = null;
20113 
20114 					auto size = painter.textSize(part.text);
20115 					version(Windows)
20116 						if(part.text.length && part.text[$-1] == '\n')
20117 							size.height /= 2; // windows counts the new line at the end, but we don't want that
20118 
20119 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
20120 
20121 					foreach(idx, char c; part.text) {
20122 							// FIXME: unicode
20123 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
20124 					}
20125 
20126 					pos.x += size.width;
20127 					if(pos.x >= boundingBox.right) {
20128 						pos.y += size.height;
20129 						pos.x = boundingBox.left;
20130 						lastHeight = 0;
20131 					} else {
20132 						lastHeight = size.height;
20133 					}
20134 
20135 					if(part.text.length && part.text[$-1] == '\n')
20136 						nl();
20137 				}
20138 			}
20139 
20140 			layoutInvalidated = false;
20141 		}
20142 
20143 		bool layoutInvalidated = true;
20144 		void invalidateLayout() {
20145 			layoutInvalidated = true;
20146 		}
20147 
20148 // FIXME: caret can remain sometimes when inserting
20149 // FIXME: inserting at the beginning once you already have something can eff it up.
20150 		void drawInto(ScreenPainter painter, bool focused = false) {
20151 			if(layoutInvalidated)
20152 				redoLayout(painter);
20153 			foreach(block; blocks) {
20154 				foreach(part; block.parts) {
20155 					painter.outlineColor = part.color;
20156 					painter.fillColor = part.backgroundColor;
20157 
20158 					auto pos = part.boundingBox.upperLeft;
20159 					auto size = part.boundingBox.size;
20160 
20161 					painter.drawText(pos, part.text);
20162 					if(part.styles & TextFormat.underline)
20163 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
20164 					if(part.styles & TextFormat.strikethrough)
20165 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
20166 				}
20167 			}
20168 
20169 			// on every redraw, I will force the caret to be
20170 			// redrawn too, in order to eliminate perceived lag
20171 			// when moving around with the mouse.
20172 			eraseCaret(painter);
20173 
20174 			if(focused) {
20175 				highlightSelection(painter);
20176 				drawCaret(painter);
20177 			}
20178 		}
20179 
20180 		Color selectionXorColor = Color(255, 255, 127);
20181 
20182 		void highlightSelection(ScreenPainter painter) {
20183 			if(selectionStart is selectionEnd)
20184 				return; // no selection
20185 
20186 			if(selectionStart.inlineElement is null) return;
20187 			if(selectionEnd.inlineElement is null) return;
20188 
20189 			assert(selectionStart.inlineElement !is null);
20190 			assert(selectionEnd.inlineElement !is null);
20191 
20192 			painter.rasterOp = RasterOp.xor;
20193 			painter.outlineColor = Color.transparent;
20194 			painter.fillColor = selectionXorColor;
20195 
20196 			auto at = selectionStart.inlineElement;
20197 			auto atOffset = selectionStart.offset;
20198 			bool done;
20199 			while(at) {
20200 				auto box = at.boundingBox;
20201 				if(atOffset < at.letterXs.length)
20202 					box.left = at.letterXs[atOffset];
20203 
20204 				if(at is selectionEnd.inlineElement) {
20205 					if(selectionEnd.offset < at.letterXs.length)
20206 						box.right = at.letterXs[selectionEnd.offset];
20207 					done = true;
20208 				}
20209 
20210 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20211 
20212 				if(done)
20213 					break;
20214 
20215 				at = at.getNextInlineElement();
20216 				atOffset = 0;
20217 			}
20218 		}
20219 
20220 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20221 		bool caretShowingOnScreen = false;
20222 		void drawCaret(ScreenPainter painter) {
20223 			//painter.setClipRectangle(boundingBox);
20224 			int x, y1, y2;
20225 			if(caret.inlineElement is null) {
20226 				x = boundingBox.left;
20227 				y1 = boundingBox.top + 2;
20228 				y2 = boundingBox.top + painter.fontHeight;
20229 			} else {
20230 				x = caret.inlineElement.xOfIndex(caret.offset);
20231 				y1 = caret.inlineElement.boundingBox.top + 2;
20232 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20233 			}
20234 
20235 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20236 				eraseCaret(painter);
20237 
20238 			painter.pen = Pen(Color.white, 1);
20239 			painter.rasterOp = RasterOp.xor;
20240 			painter.drawLine(
20241 				Point(x, y1),
20242 				Point(x, y2)
20243 			);
20244 			painter.rasterOp = RasterOp.normal;
20245 			caretShowingOnScreen = !caretShowingOnScreen;
20246 
20247 			if(caretShowingOnScreen) {
20248 				caretLastDrawnX = x;
20249 				caretLastDrawnY1 = y1;
20250 				caretLastDrawnY2 = y2;
20251 			}
20252 		}
20253 
20254 		Rectangle caretBoundingBox() {
20255 			int x, y1, y2;
20256 			if(caret.inlineElement is null) {
20257 				x = boundingBox.left;
20258 				y1 = boundingBox.top + 2;
20259 				y2 = boundingBox.top + 16;
20260 			} else {
20261 				x = caret.inlineElement.xOfIndex(caret.offset);
20262 				y1 = caret.inlineElement.boundingBox.top + 2;
20263 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20264 			}
20265 
20266 			return Rectangle(x, y1, x + 1, y2);
20267 		}
20268 
20269 		void eraseCaret(ScreenPainter painter) {
20270 			//painter.setClipRectangle(boundingBox);
20271 			if(!caretShowingOnScreen) return;
20272 			painter.pen = Pen(Color.white, 1);
20273 			painter.rasterOp = RasterOp.xor;
20274 			painter.drawLine(
20275 				Point(caretLastDrawnX, caretLastDrawnY1),
20276 				Point(caretLastDrawnX, caretLastDrawnY2)
20277 			);
20278 
20279 			caretShowingOnScreen = false;
20280 			painter.rasterOp = RasterOp.normal;
20281 		}
20282 
20283 		/// Caret movement api
20284 		/// These should give the user a logical result based on what they see on screen...
20285 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20286 		void moveUp() {
20287 			if(caret.inlineElement is null) return;
20288 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20289 			auto y = caret.inlineElement.boundingBox.top + 2;
20290 
20291 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20292 			if(y < 0)
20293 				return;
20294 
20295 			auto i = identify(x, y);
20296 
20297 			if(i.element) {
20298 				caret.inlineElement = i.element;
20299 				caret.offset = i.offset;
20300 			}
20301 		}
20302 		void moveDown() {
20303 			if(caret.inlineElement is null) return;
20304 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20305 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20306 
20307 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20308 
20309 			auto i = identify(x, y);
20310 			if(i.element) {
20311 				caret.inlineElement = i.element;
20312 				caret.offset = i.offset;
20313 			}
20314 		}
20315 		void moveLeft() {
20316 			if(caret.inlineElement is null) return;
20317 			if(caret.offset)
20318 				caret.offset--;
20319 			else {
20320 				auto p = caret.inlineElement.getPreviousInlineElement();
20321 				if(p) {
20322 					caret.inlineElement = p;
20323 					if(p.text.length && p.text[$-1] == '\n')
20324 						caret.offset = cast(int) p.text.length - 1;
20325 					else
20326 						caret.offset = cast(int) p.text.length;
20327 				}
20328 			}
20329 		}
20330 		void moveRight() {
20331 			if(caret.inlineElement is null) return;
20332 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20333 				caret.offset++;
20334 			} else {
20335 				auto p = caret.inlineElement.getNextInlineElement();
20336 				if(p) {
20337 					caret.inlineElement = p;
20338 					caret.offset = 0;
20339 				}
20340 			}
20341 		}
20342 		void moveHome() {
20343 			if(caret.inlineElement is null) return;
20344 			auto x = 0;
20345 			auto y = caret.inlineElement.boundingBox.top + 2;
20346 
20347 			auto i = identify(x, y);
20348 
20349 			if(i.element) {
20350 				caret.inlineElement = i.element;
20351 				caret.offset = i.offset;
20352 			}
20353 		}
20354 		void moveEnd() {
20355 			if(caret.inlineElement is null) return;
20356 			auto x = int.max;
20357 			auto y = caret.inlineElement.boundingBox.top + 2;
20358 
20359 			auto i = identify(x, y);
20360 
20361 			if(i.element) {
20362 				caret.inlineElement = i.element;
20363 				caret.offset = i.offset;
20364 			}
20365 
20366 		}
20367 		void movePageUp(ref Caret caret) {}
20368 		void movePageDown(ref Caret caret) {}
20369 
20370 		void moveDocumentStart(ref Caret caret) {
20371 			if(blocks.length && blocks[0].parts.length)
20372 				caret = Caret(this, blocks[0].parts[0], 0);
20373 			else
20374 				caret = Caret.init;
20375 		}
20376 
20377 		void moveDocumentEnd(ref Caret caret) {
20378 			if(blocks.length) {
20379 				auto parts = blocks[$-1].parts;
20380 				if(parts.length) {
20381 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20382 				} else {
20383 					caret = Caret.init;
20384 				}
20385 			} else
20386 				caret = Caret.init;
20387 		}
20388 
20389 		void deleteSelection() {
20390 			if(selectionStart is selectionEnd)
20391 				return;
20392 
20393 			if(selectionStart.inlineElement is null) return;
20394 			if(selectionEnd.inlineElement is null) return;
20395 
20396 			assert(selectionStart.inlineElement !is null);
20397 			assert(selectionEnd.inlineElement !is null);
20398 
20399 			auto at = selectionStart.inlineElement;
20400 
20401 			if(selectionEnd.inlineElement is at) {
20402 				// same element, need to chop out
20403 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20404 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20405 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20406 			} else {
20407 				// different elements, we can do it with slicing
20408 				at.text = at.text[0 .. selectionStart.offset];
20409 				if(selectionStart.offset < at.letterXs.length)
20410 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20411 
20412 				at = at.getNextInlineElement();
20413 
20414 				while(at) {
20415 					if(at is selectionEnd.inlineElement) {
20416 						at.text = at.text[selectionEnd.offset .. $];
20417 						if(selectionEnd.offset < at.letterXs.length)
20418 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20419 						selectionEnd.offset = 0;
20420 						break;
20421 					} else {
20422 						auto cfd = at;
20423 						cfd.text = null; // delete the whole thing
20424 
20425 						at = at.getNextInlineElement();
20426 
20427 						if(cfd.text.length == 0) {
20428 							// and remove cfd
20429 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20430 								if(cfd.containingBlock.parts[a] is cfd) {
20431 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20432 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20433 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20434 
20435 								}
20436 							}
20437 						}
20438 					}
20439 				}
20440 			}
20441 
20442 			caret = selectionEnd;
20443 			selectNone();
20444 
20445 			invalidateLayout();
20446 
20447 		}
20448 
20449 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20450 		void insert(in char[] text) {
20451 			foreach(dchar ch; text)
20452 				insert(ch);
20453 		}
20454 		/// ditto
20455 		void insert(dchar ch) {
20456 
20457 			bool selectionDeleted = false;
20458 			if(selectionStart !is selectionEnd) {
20459 				deleteSelection();
20460 				selectionDeleted = true;
20461 			}
20462 
20463 			if(ch == 127) {
20464 				delete_();
20465 				return;
20466 			}
20467 			if(ch == 8) {
20468 				if(!selectionDeleted)
20469 					backspace();
20470 				return;
20471 			}
20472 
20473 			invalidateLayout();
20474 
20475 			if(ch == 13) ch = 10;
20476 			auto e = caret.inlineElement;
20477 			if(e is null) {
20478 				addText("" ~ cast(char) ch) ; // FIXME
20479 				return;
20480 			}
20481 
20482 			if(caret.offset == e.text.length) {
20483 				e.text ~= cast(char) ch; // FIXME
20484 				caret.offset++;
20485 				if(ch == 10) {
20486 					auto c = caret.inlineElement.clone;
20487 					c.text = null;
20488 					c.letterXs = null;
20489 					insertPartAfter(c,e);
20490 					caret = Caret(this, c, 0);
20491 				}
20492 			} else {
20493 				// FIXME cast char sucks
20494 				if(ch == 10) {
20495 					auto c = caret.inlineElement.clone;
20496 					c.text = e.text[caret.offset .. $];
20497 					if(caret.offset < c.letterXs.length)
20498 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20499 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20500 					if(caret.offset <= e.letterXs.length) {
20501 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20502 					}
20503 					insertPartAfter(c,e);
20504 					caret = Caret(this, c, 0);
20505 				} else {
20506 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20507 					caret.offset++;
20508 				}
20509 			}
20510 		}
20511 
20512 		void insertPartAfter(InlineElement what, InlineElement where) {
20513 			foreach(idx, p; where.containingBlock.parts) {
20514 				if(p is where) {
20515 					if(idx + 1 == where.containingBlock.parts.length)
20516 						where.containingBlock.parts ~= what;
20517 					else
20518 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20519 					return;
20520 				}
20521 			}
20522 		}
20523 
20524 		void cleanupStructures() {
20525 			for(size_t i = 0; i < blocks.length; i++) {
20526 				auto block = blocks[i];
20527 				for(size_t a = 0; a < block.parts.length; a++) {
20528 					auto part = block.parts[a];
20529 					if(part.text.length == 0) {
20530 						for(size_t b = a; b < block.parts.length - 1; b++)
20531 							block.parts[b] = block.parts[b+1];
20532 						block.parts = block.parts[0 .. $-1];
20533 					}
20534 				}
20535 				if(block.parts.length == 0) {
20536 					for(size_t a = i; a < blocks.length - 1; a++)
20537 						blocks[a] = blocks[a+1];
20538 					blocks = blocks[0 .. $-1];
20539 				}
20540 			}
20541 		}
20542 
20543 		void backspace() {
20544 			try_again:
20545 			auto e = caret.inlineElement;
20546 			if(e is null)
20547 				return;
20548 			if(caret.offset == 0) {
20549 				auto prev = e.getPreviousInlineElement();
20550 				if(prev is null)
20551 					return;
20552 				auto newOffset = cast(int) prev.text.length;
20553 				tryMerge(prev, e);
20554 				caret.inlineElement = prev;
20555 				caret.offset = prev is null ? 0 : newOffset;
20556 
20557 				goto try_again;
20558 			} else if(caret.offset == e.text.length) {
20559 				e.text = e.text[0 .. $-1];
20560 				caret.offset--;
20561 			} else {
20562 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20563 				caret.offset--;
20564 			}
20565 			//cleanupStructures();
20566 
20567 			invalidateLayout();
20568 		}
20569 		void delete_() {
20570 			if(selectionStart !is selectionEnd)
20571 				deleteSelection();
20572 			else {
20573 				auto before = caret;
20574 				moveRight();
20575 				if(caret != before) {
20576 					backspace();
20577 				}
20578 			}
20579 
20580 			invalidateLayout();
20581 		}
20582 		void overstrike() {}
20583 
20584 		/// Selection API. See also: caret movement.
20585 		void selectAll() {
20586 			moveDocumentStart(selectionStart);
20587 			moveDocumentEnd(selectionEnd);
20588 		}
20589 		bool selectNone() {
20590 			if(selectionStart != selectionEnd) {
20591 				selectionStart = selectionEnd = Caret.init;
20592 				return true;
20593 			}
20594 			return false;
20595 		}
20596 
20597 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20598 		/// They will modify the current selection if there is one and will splice one in if needed.
20599 		void changeAttributes() {}
20600 
20601 
20602 		/// Text search api. They manipulate the selection and/or caret.
20603 		void findText(string text) {}
20604 		void findIndex(size_t textIndex) {}
20605 
20606 		// sample event handlers
20607 
20608 		void handleEvent(KeyEvent event) {
20609 			//if(event.type == KeyEvent.Type.KeyPressed) {
20610 
20611 			//}
20612 		}
20613 
20614 		void handleEvent(dchar ch) {
20615 
20616 		}
20617 
20618 		void handleEvent(MouseEvent event) {
20619 
20620 		}
20621 
20622 		bool contentEditable; // can it be edited?
20623 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20624 		bool contentSelectable; // selectable?
20625 
20626 		Caret caret;
20627 		Caret selectionStart;
20628 		Caret selectionEnd;
20629 
20630 		bool insertMode;
20631 	}
20632 
20633 	struct Caret {
20634 		TextLayout layout;
20635 		InlineElement inlineElement;
20636 		int offset;
20637 	}
20638 
20639 	enum TextFormat : ushort {
20640 		// decorations
20641 		underline = 1,
20642 		strikethrough = 2,
20643 
20644 		// font selectors
20645 
20646 		bold = 0x4000 | 1, // weight 700
20647 		light = 0x4000 | 2, // weight 300
20648 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20649 		// bold | light is really invalid but should give weight 500
20650 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20651 
20652 		italic = 0x4000 | 8,
20653 		smallcaps = 0x4000 | 16,
20654 	}
20655 
20656 	void* findFont(string family, int weight, TextFormat formats) {
20657 		return null;
20658 	}
20659 
20660 }
20661 
20662 /++
20663 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20664 
20665 	History:
20666 		Added February 19, 2021
20667 +/
20668 /// Group: drag_and_drop
20669 interface DropHandler {
20670 	/++
20671 		Called when the drag enters the handler's area.
20672 	+/
20673 	DragAndDropAction dragEnter(DropPackage*);
20674 	/++
20675 		Called when the drag leaves the handler's area or is
20676 		cancelled. You should free your resources when this is called.
20677 	+/
20678 	void dragLeave();
20679 	/++
20680 		Called continually as the drag moves over the handler's area.
20681 
20682 		Returns: feedback to the dragger
20683 	+/
20684 	DropParameters dragOver(Point pt);
20685 	/++
20686 		The user dropped the data and you should process it now. You can
20687 		access the data through the given [DropPackage].
20688 	+/
20689 	void drop(scope DropPackage*);
20690 	/++
20691 		Called when the drop is complete. You should free whatever temporary
20692 		resources you were using. It is often reasonable to simply forward
20693 		this call to [dragLeave].
20694 	+/
20695 	void finish();
20696 
20697 	/++
20698 		Parameters returned by [DropHandler.drop].
20699 	+/
20700 	static struct DropParameters {
20701 		/++
20702 			Acceptable action over this area.
20703 		+/
20704 		DragAndDropAction action;
20705 		/++
20706 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20707 
20708 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20709 		+/
20710 		Rectangle consistentWithin;
20711 	}
20712 }
20713 
20714 /++
20715 	History:
20716 		Added February 19, 2021
20717 +/
20718 /// Group: drag_and_drop
20719 enum DragAndDropAction {
20720 	none = 0,
20721 	copy,
20722 	move,
20723 	link,
20724 	ask,
20725 	custom
20726 }
20727 
20728 /++
20729 	An opaque structure representing dropped data. It contains
20730 	private, platform-specific data that your `drop` function
20731 	should simply forward.
20732 
20733 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20734 
20735 	History:
20736 		Added February 19, 2021
20737 +/
20738 /// Group: drag_and_drop
20739 struct DropPackage {
20740 	/++
20741 		Lists the available formats as magic numbers. You should compare these
20742 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20743 		understand the passed data.
20744 	+/
20745 	DraggableData.FormatId[] availableFormats() {
20746 		version(X11) {
20747 			return xFormats;
20748 		} else version(Windows) {
20749 			if(pDataObj is null)
20750 				return null;
20751 
20752 			typeof(return) ret;
20753 
20754 			IEnumFORMATETC ef;
20755 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20756 				FORMATETC fmt;
20757 				ULONG fetched;
20758 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20759 					if(fetched == 0)
20760 						break;
20761 
20762 					if(fmt.lindex != -1)
20763 						continue;
20764 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20765 						continue;
20766 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20767 						continue;
20768 
20769 					ret ~= fmt.cfFormat;
20770 				}
20771 			}
20772 
20773 			return ret;
20774 		} else throw new NotYetImplementedException();
20775 	}
20776 
20777 	/++
20778 		Gets data from the drop and optionally accepts it.
20779 
20780 		Returns:
20781 			void because the data is fed asynchronously through the `dg` parameter.
20782 
20783 		Params:
20784 			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.
20785 
20786 			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.
20787 
20788 			Calling `getData` again after accepting a drop is not permitted.
20789 
20790 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20791 
20792 			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.
20793 
20794 		Throws:
20795 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20796 
20797 		History:
20798 			Included in first release of [DropPackage].
20799 	+/
20800 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20801 		version(X11) {
20802 
20803 			auto display = XDisplayConnection.get();
20804 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20805 			auto best = format;
20806 
20807 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20808 
20809 				XDisplay* display;
20810 				Atom selectionAtom;
20811 				DraggableData.FormatId best;
20812 				DraggableData.FormatId format;
20813 				void delegate(scope ubyte[] data) dg;
20814 				DragAndDropAction acceptedAction;
20815 				Window sourceWindow;
20816 				SimpleWindow win;
20817 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20818 					this.display = display;
20819 					this.win = win;
20820 					this.sourceWindow = sourceWindow;
20821 					this.format = format;
20822 					this.selectionAtom = selectionAtom;
20823 					this.best = best;
20824 					this.dg = dg;
20825 					this.acceptedAction = acceptedAction;
20826 				}
20827 
20828 
20829 				mixin X11GetSelectionHandler_Basics;
20830 
20831 				void handleData(Atom target, in ubyte[] data) {
20832 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20833 
20834 					dg(cast(ubyte[]) data);
20835 
20836 					if(acceptedAction != DragAndDropAction.none) {
20837 						auto display = XDisplayConnection.get;
20838 
20839 						XClientMessageEvent xclient;
20840 
20841 						xclient.type = EventType.ClientMessage;
20842 						xclient.window = sourceWindow;
20843 						xclient.message_type = GetAtom!"XdndFinished"(display);
20844 						xclient.format = 32;
20845 						xclient.data.l[0] = win.impl.window;
20846 						xclient.data.l[1] = 1; // drop successful
20847 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20848 
20849 						XSendEvent(
20850 							display,
20851 							sourceWindow,
20852 							false,
20853 							EventMask.NoEventMask,
20854 							cast(XEvent*) &xclient
20855 						);
20856 
20857 						XFlush(display);
20858 					}
20859 				}
20860 
20861 				Atom findBestFormat(Atom[] answer) {
20862 					Atom best = None;
20863 					foreach(option; answer) {
20864 						if(option == format) {
20865 							best = option;
20866 							break;
20867 						}
20868 						/*
20869 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20870 							best = option;
20871 							break;
20872 						} else if(option == XA_STRING) {
20873 							best = option;
20874 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20875 							best = option;
20876 						}
20877 						*/
20878 					}
20879 					return best;
20880 				}
20881 			}
20882 
20883 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20884 
20885 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20886 
20887 		} else version(Windows) {
20888 
20889 			// clean up like DragLeave
20890 			// pass effect back up
20891 
20892 			FORMATETC t;
20893 			assert(format >= 0 && format <= ushort.max);
20894 			t.cfFormat = cast(ushort) format;
20895 			t.lindex = -1;
20896 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20897 			t.tymed = TYMED.TYMED_HGLOBAL;
20898 
20899 			STGMEDIUM m;
20900 
20901 			if(pDataObj.GetData(&t, &m) != S_OK) {
20902 				// fail
20903 			} else {
20904 				// succeed, take the data and clean up
20905 
20906 				// FIXME: ensure it is legit HGLOBAL
20907 				auto handle = m.hGlobal;
20908 
20909 				if(handle) {
20910 					auto sz = GlobalSize(handle);
20911 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20912 						scope(exit) GlobalUnlock(handle);
20913 						scope(exit) GlobalFree(handle);
20914 
20915 						auto data = ptr[0 .. sz];
20916 
20917 						dg(data);
20918 					}
20919 				}
20920 			}
20921 		}
20922 	}
20923 
20924 	private:
20925 
20926 	version(X11) {
20927 		SimpleWindow win;
20928 		Window sourceWindow;
20929 		Time dataTimestamp;
20930 
20931 		Atom[] xFormats;
20932 	}
20933 	version(Windows) {
20934 		IDataObject pDataObj;
20935 	}
20936 }
20937 
20938 /++
20939 	A generic helper base class for making a drop handler with a preference list of custom types.
20940 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20941 	droppers too.
20942 
20943 	It assumes the whole window it used, but you can subclass to change that.
20944 
20945 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20946 
20947 	History:
20948 		Added February 19, 2021
20949 +/
20950 /// Group: drag_and_drop
20951 class GenericDropHandlerBase : DropHandler {
20952 	// no fancy state here so no need to do anything here
20953 	void finish() { }
20954 	void dragLeave() { }
20955 
20956 	private DragAndDropAction acceptedAction;
20957 	private DraggableData.FormatId acceptedFormat;
20958 	private void delegate(scope ubyte[]) acceptedHandler;
20959 
20960 	struct FormatHandler {
20961 		DraggableData.FormatId format;
20962 		void delegate(scope ubyte[]) handler;
20963 	}
20964 
20965 	protected abstract FormatHandler[] formatHandlers();
20966 
20967 	DragAndDropAction dragEnter(DropPackage* pkg) {
20968 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20969 		foreach(fmt; formatHandlers())
20970 		foreach(f; pkg.availableFormats())
20971 			if(f == fmt.format) {
20972 				acceptedFormat = f;
20973 				acceptedHandler = fmt.handler;
20974 				return acceptedAction = DragAndDropAction.copy;
20975 			}
20976 		return acceptedAction = DragAndDropAction.none;
20977 	}
20978 	DropParameters dragOver(Point pt) {
20979 		return DropParameters(acceptedAction);
20980 	}
20981 
20982 	void drop(scope DropPackage* dropPackage) {
20983 		if(!acceptedFormat || acceptedHandler is null) {
20984 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20985 			return; // prolly shouldn't happen anyway...
20986 		}
20987 
20988 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20989 	}
20990 }
20991 
20992 /++
20993 	A simple handler for making your window accept drops of plain text.
20994 
20995 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20996 
20997 	History:
20998 		Added February 22, 2021
20999 +/
21000 /// Group: drag_and_drop
21001 class TextDropHandler : GenericDropHandlerBase {
21002 	private void delegate(in char[] text) dg;
21003 
21004 	/++
21005 
21006 	+/
21007 	this(void delegate(in char[] text) dg) {
21008 		this.dg = dg;
21009 	}
21010 
21011 	protected override FormatHandler[] formatHandlers() {
21012 		version(X11)
21013 			return [
21014 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21015 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21016 			];
21017 		else version(Windows)
21018 			return [
21019 				FormatHandler(CF_UNICODETEXT, &translator),
21020 			];
21021 		else throw new NotYetImplementedException();
21022 	}
21023 
21024 	private void translator(scope ubyte[] data) {
21025 		version(X11)
21026 			dg(cast(char[]) data);
21027 		else version(Windows)
21028 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21029 	}
21030 }
21031 
21032 /++
21033 	A simple handler for making your window accept drops of files, issued to you as file names.
21034 
21035 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21036 
21037 	History:
21038 		Added February 22, 2021
21039 +/
21040 /// Group: drag_and_drop
21041 
21042 class FilesDropHandler : GenericDropHandlerBase {
21043 	private void delegate(in char[][]) dg;
21044 
21045 	/++
21046 
21047 	+/
21048 	this(void delegate(in char[][] fileNames) dg) {
21049 		this.dg = dg;
21050 	}
21051 
21052 	protected override FormatHandler[] formatHandlers() {
21053 		version(X11)
21054 			return [
21055 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21056 			];
21057 		else version(Windows)
21058 			return [
21059 				FormatHandler(CF_HDROP, &translator),
21060 			];
21061 		else throw new NotYetImplementedException();
21062 	}
21063 
21064 	private void translator(scope ubyte[] data) {
21065 		version(X11) {
21066 			char[] listString = cast(char[]) data;
21067 			char[][16] buffer;
21068 			int count;
21069 			char[][] result = buffer[];
21070 
21071 			void commit(char[] s) {
21072 				if(count == result.length)
21073 					result.length += 16;
21074 				if(s.length > 7 && s[0 ..7] == "file://")
21075 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21076 				result[count++] = s;
21077 			}
21078 
21079 			size_t last;
21080 			foreach(idx, char c; listString) {
21081 				if(c == '\n') {
21082 					commit(listString[last .. idx - 1]); // a \r
21083 					last = idx + 1; // a \n
21084 				}
21085 			}
21086 
21087 			if(last < listString.length) {
21088 				commit(listString[last .. $]);
21089 			}
21090 
21091 			// FIXME: they are uris now, should I translate it to local file names?
21092 			// of course the host name is supposed to be there cuz of X rokking...
21093 
21094 			dg(result[0 .. count]);
21095 		} else version(Windows) {
21096 
21097 			static struct DROPFILES {
21098 				DWORD pFiles;
21099 				POINT pt;
21100 				BOOL  fNC;
21101 				BOOL  fWide;
21102 			}
21103 
21104 
21105 			const(char)[][16] buffer;
21106 			int count;
21107 			const(char)[][] result = buffer[];
21108 			size_t last;
21109 
21110 			void commitA(in char[] stuff) {
21111 				if(count == result.length)
21112 					result.length += 16;
21113 				result[count++] = stuff;
21114 			}
21115 
21116 			void commitW(in wchar[] stuff) {
21117 				commitA(makeUtf8StringFromWindowsString(stuff));
21118 			}
21119 
21120 			void magic(T)(T chars) {
21121 				size_t idx;
21122 				while(chars[idx]) {
21123 					last = idx;
21124 					while(chars[idx]) {
21125 						idx++;
21126 					}
21127 					static if(is(T == char*))
21128 						commitA(chars[last .. idx]);
21129 					else
21130 						commitW(chars[last .. idx]);
21131 					idx++;
21132 				}
21133 			}
21134 
21135 			auto df = cast(DROPFILES*) data.ptr;
21136 			if(df.fWide) {
21137 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21138 				magic(chars);
21139 			} else {
21140 				char* chars = cast(char*) (data.ptr + df.pFiles);
21141 				magic(chars);
21142 			}
21143 			dg(result[0 .. count]);
21144 		}
21145 		else throw new NotYetImplementedException();
21146 	}
21147 }
21148 
21149 /++
21150 	Interface to describe data being dragged. See also [draggable] helper function.
21151 
21152 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21153 
21154 	History:
21155 		Added February 19, 2021
21156 +/
21157 interface DraggableData {
21158 	version(X11)
21159 		alias FormatId = Atom;
21160 	else
21161 		alias FormatId = uint;
21162 	/++
21163 		Gets the platform-specific FormatId associated with the given named format.
21164 
21165 		This may be a MIME type, but may also be other various strings defined by the
21166 		programs you want to interoperate with.
21167 
21168 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
21169 		and convert it to some particular type for you.
21170 	+/
21171 	static FormatId getFormatId(string name)() {
21172 		version(X11)
21173 			return GetAtom!name(XDisplayConnection.get);
21174 		else version(Windows) {
21175 			static UINT cache;
21176 			if(!cache)
21177 				cache = RegisterClipboardFormatA(name);
21178 			return cache;
21179 		} else
21180 			throw new NotYetImplementedException();
21181 	}
21182 
21183 	/++
21184 		Looks up a string to represent the name for the given format, if there is one.
21185 
21186 		You should avoid using this function because it is slow. It is provided more for
21187 		debugging than for primary use.
21188 	+/
21189 	static string getFormatName(FormatId format) {
21190 		version(X11) {
21191 			if(format == 0)
21192 				return "None";
21193 			else
21194 				return getAtomName(format, XDisplayConnection.get);
21195 		} else version(Windows) {
21196 			switch(format) {
21197 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
21198 				case CF_DIBV5: return "CF_DIBV5";
21199 				case CF_RIFF: return "CF_RIFF";
21200 				case CF_WAVE: return "CF_WAVE";
21201 				case CF_HDROP: return "CF_HDROP";
21202 				default:
21203 					char[1024] name;
21204 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21205 					return name[0 .. count].idup;
21206 			}
21207 		} else throw new NotYetImplementedException();
21208 	}
21209 
21210 	FormatId[] availableFormats();
21211 	// Return the slice of data you filled, empty slice if done.
21212 	// this is to support the incremental thing
21213 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21214 
21215 	size_t dataLength(FormatId format);
21216 }
21217 
21218 /++
21219 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21220 
21221 	History:
21222 		Added February 19, 2021
21223 +/
21224 DraggableData draggable(string s) {
21225 	version(X11)
21226 	return new class X11SetSelectionHandler_Text, DraggableData {
21227 		this() {
21228 			super(s);
21229 		}
21230 
21231 		override FormatId[] availableFormats() {
21232 			return X11SetSelectionHandler_Text.availableFormats();
21233 		}
21234 
21235 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21236 			return X11SetSelectionHandler_Text.getData(format, data);
21237 		}
21238 
21239 		size_t dataLength(FormatId format) {
21240 			return s.length;
21241 		}
21242 	};
21243 	else version(Windows)
21244 	return new class DraggableData {
21245 		FormatId[] availableFormats() {
21246 			return [CF_UNICODETEXT];
21247 		}
21248 
21249 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21250 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21251 		}
21252 
21253 		size_t dataLength(FormatId format) {
21254 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21255 		}
21256 	};
21257 	else
21258 	throw new NotYetImplementedException();
21259 }
21260 
21261 /++
21262 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21263 
21264 	History:
21265 		Added February 19, 2021
21266 +/
21267 /// Group: drag_and_drop
21268 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21269 in {
21270 	assert(window !is null);
21271 	assert(handler !is null);
21272 }
21273 do
21274 {
21275 	version(X11) {
21276 		auto sh = cast(X11SetSelectionHandler) handler;
21277 		if(sh is null) {
21278 			// gotta make my own adapter.
21279 			sh = new class X11SetSelectionHandler {
21280 				mixin X11SetSelectionHandler_Basics;
21281 
21282 				Atom[] availableFormats() { return handler.availableFormats(); }
21283 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21284 					return handler.getData(format, data);
21285 				}
21286 
21287 				// since the drop selection is only ever used once it isn't important
21288 				// to reset it.
21289 				void done() {}
21290 			};
21291 		}
21292 		return doDragDropX11(window, sh, action);
21293 	} else version(Windows) {
21294 		return doDragDropWindows(window, handler, action);
21295 	} else throw new NotYetImplementedException();
21296 }
21297 
21298 version(Windows)
21299 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21300 	IDataObject obj = new class IDataObject {
21301 		ULONG refCount;
21302 		ULONG AddRef() {
21303 			return ++refCount;
21304 		}
21305 		ULONG Release() {
21306 			return --refCount;
21307 		}
21308 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21309 			if (IID_IUnknown == *riid) {
21310 				*ppv = cast(void*) cast(IUnknown) this;
21311 			}
21312 			else if (IID_IDataObject == *riid) {
21313 				*ppv = cast(void*) cast(IDataObject) this;
21314 			}
21315 			else {
21316 				*ppv = null;
21317 				return E_NOINTERFACE;
21318 			}
21319 
21320 			AddRef();
21321 			return NOERROR;
21322 		}
21323 
21324 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21325 			//  writeln("Advise");
21326 			return E_NOTIMPL;
21327 		}
21328 		HRESULT DUnadvise(DWORD dwConnection) {
21329 			return E_NOTIMPL;
21330 		}
21331 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21332 			//  writeln("EnumDAdvise");
21333 			return OLE_E_ADVISENOTSUPPORTED;
21334 		}
21335 		// tell what formats it supports
21336 
21337 		FORMATETC[] types;
21338 		this() {
21339 			FORMATETC t;
21340 			foreach(ty; handler.availableFormats()) {
21341 				assert(ty <= ushort.max && ty >= 0);
21342 				t.cfFormat = cast(ushort) ty;
21343 				t.lindex = -1;
21344 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21345 				t.tymed = TYMED.TYMED_HGLOBAL;
21346 			}
21347 			types ~= t;
21348 		}
21349 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21350 			if(dwDirection == DATADIR.DATADIR_GET) {
21351 				*ppenumFormatEtc = new class IEnumFORMATETC {
21352 					ULONG refCount;
21353 					ULONG AddRef() {
21354 						return ++refCount;
21355 					}
21356 					ULONG Release() {
21357 						return --refCount;
21358 					}
21359 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21360 						if (IID_IUnknown == *riid) {
21361 							*ppv = cast(void*) cast(IUnknown) this;
21362 						}
21363 						else if (IID_IEnumFORMATETC == *riid) {
21364 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21365 						}
21366 						else {
21367 							*ppv = null;
21368 							return E_NOINTERFACE;
21369 						}
21370 
21371 						AddRef();
21372 						return NOERROR;
21373 					}
21374 
21375 
21376 					int pos;
21377 					this() {
21378 						pos = 0;
21379 					}
21380 
21381 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21382 						// writeln("clone");
21383 						return E_NOTIMPL; // FIXME
21384 					}
21385 
21386 					// Caller is responsible for freeing memory
21387 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21388 						// fetched may be null if celt is one
21389 						if(celt != 1)
21390 							return E_NOTIMPL; // FIXME
21391 
21392 						if(celt + pos > types.length)
21393 							return S_FALSE;
21394 
21395 						*rgelt = types[pos++];
21396 
21397 						if(pceltFetched !is null)
21398 							*pceltFetched = 1;
21399 
21400 						// writeln("ok celt ", celt);
21401 						return S_OK;
21402 					}
21403 
21404 					HRESULT Reset() {
21405 						pos = 0;
21406 						return S_OK;
21407 					}
21408 
21409 					HRESULT Skip(ULONG celt) {
21410 						if(celt + pos <= types.length) {
21411 							pos += celt;
21412 							return S_OK;
21413 						}
21414 						return S_FALSE;
21415 					}
21416 				};
21417 
21418 				return S_OK;
21419 			} else
21420 				return E_NOTIMPL;
21421 		}
21422 		// given a format, return the format you'd prefer to use cuz it is identical
21423 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21424 			// FIXME: prolly could be better but meh
21425 			// writeln("gcf: ", *pformatectIn);
21426 			*pformatetcOut = *pformatectIn;
21427 			return S_OK;
21428 		}
21429 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21430 			foreach(ty; types) {
21431 				if(ty == *pformatetcIn) {
21432 					auto format = ty.cfFormat;
21433 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21434 					STGMEDIUM medium;
21435 					medium.tymed = TYMED.TYMED_HGLOBAL;
21436 
21437 					auto sz = handler.dataLength(format);
21438 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21439 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21440 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21441 						auto slice = data[0 .. sz];
21442 						scope(exit)
21443 							GlobalUnlock(handle);
21444 
21445 						handler.getData(format, cast(ubyte[]) slice[]);
21446 					}
21447 
21448 
21449 					medium.hGlobal = handle; // FIXME
21450 					*pmedium = medium;
21451 					return S_OK;
21452 				}
21453 			}
21454 			return DV_E_FORMATETC;
21455 		}
21456 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21457 			// writeln("GDH: ", *pformatetcIn);
21458 			return E_NOTIMPL; // FIXME
21459 		}
21460 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21461 			auto search = *pformatetc;
21462 			search.tymed &= TYMED.TYMED_HGLOBAL;
21463 			foreach(ty; types)
21464 				if(ty == search) {
21465 					// writeln("QueryGetData ", search, " ", types[0]);
21466 					return S_OK;
21467 				}
21468 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21469 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21470 			}
21471 			return S_FALSE;
21472 		}
21473 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21474 			//  writeln("SetData: ");
21475 			return E_NOTIMPL;
21476 		}
21477 	};
21478 
21479 
21480 	IDropSource src = new class IDropSource {
21481 		ULONG refCount;
21482 		ULONG AddRef() {
21483 			return ++refCount;
21484 		}
21485 		ULONG Release() {
21486 			return --refCount;
21487 		}
21488 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21489 			if (IID_IUnknown == *riid) {
21490 				*ppv = cast(void*) cast(IUnknown) this;
21491 			}
21492 			else if (IID_IDropSource == *riid) {
21493 				*ppv = cast(void*) cast(IDropSource) this;
21494 			}
21495 			else {
21496 				*ppv = null;
21497 				return E_NOINTERFACE;
21498 			}
21499 
21500 			AddRef();
21501 			return NOERROR;
21502 		}
21503 
21504 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21505 			if(fEscapePressed)
21506 				return DRAGDROP_S_CANCEL;
21507 			if(!(grfKeyState & MK_LBUTTON))
21508 				return DRAGDROP_S_DROP;
21509 			return S_OK;
21510 		}
21511 
21512 		int GiveFeedback(uint dwEffect) {
21513 			return DRAGDROP_S_USEDEFAULTCURSORS;
21514 		}
21515 	};
21516 
21517 	DWORD effect;
21518 
21519 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21520 
21521 	DROPEFFECT de = win32DragAndDropAction(action);
21522 
21523 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21524 	// but still prolly a FIXME
21525 
21526 	auto ret = DoDragDrop(obj, src, de, &effect);
21527 	/+
21528 	if(ret == DRAGDROP_S_DROP)
21529 		writeln("drop ", effect);
21530 	else if(ret == DRAGDROP_S_CANCEL)
21531 		writeln("cancel");
21532 	else if(ret == S_OK)
21533 		writeln("ok");
21534 	else writeln(ret);
21535 	+/
21536 
21537 	return ret;
21538 }
21539 
21540 version(Windows)
21541 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21542 	DROPEFFECT de;
21543 
21544 	with(DragAndDropAction)
21545 	with(DROPEFFECT)
21546 	final switch(action) {
21547 		case none: de = DROPEFFECT_NONE; break;
21548 		case copy: de = DROPEFFECT_COPY; break;
21549 		case move: de = DROPEFFECT_MOVE; break;
21550 		case link: de = DROPEFFECT_LINK; break;
21551 		case ask: throw new Exception("ask not implemented yet");
21552 		case custom: throw new Exception("custom not implemented yet");
21553 	}
21554 
21555 	return de;
21556 }
21557 
21558 
21559 /++
21560 	History:
21561 		Added February 19, 2021
21562 +/
21563 /// Group: drag_and_drop
21564 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21565 	version(X11) {
21566 		auto display = XDisplayConnection.get;
21567 
21568 		Atom atom = 5; // right???
21569 
21570 		XChangeProperty(
21571 			display,
21572 			window.impl.window,
21573 			GetAtom!"XdndAware"(display),
21574 			XA_ATOM,
21575 			32 /* bits */,
21576 			PropModeReplace,
21577 			&atom,
21578 			1);
21579 
21580 		window.dropHandler = handler;
21581 	} else version(Windows) {
21582 
21583 		initDnd();
21584 
21585 		auto dropTarget = new class (handler) IDropTarget {
21586 			DropHandler handler;
21587 			this(DropHandler handler) {
21588 				this.handler = handler;
21589 			}
21590 			ULONG refCount;
21591 			ULONG AddRef() {
21592 				return ++refCount;
21593 			}
21594 			ULONG Release() {
21595 				return --refCount;
21596 			}
21597 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21598 				if (IID_IUnknown == *riid) {
21599 					*ppv = cast(void*) cast(IUnknown) this;
21600 				}
21601 				else if (IID_IDropTarget == *riid) {
21602 					*ppv = cast(void*) cast(IDropTarget) this;
21603 				}
21604 				else {
21605 					*ppv = null;
21606 					return E_NOINTERFACE;
21607 				}
21608 
21609 				AddRef();
21610 				return NOERROR;
21611 			}
21612 
21613 
21614 			// ///////////////////
21615 
21616 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21617 				DropPackage dropPackage = DropPackage(pDataObj);
21618 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21619 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21620 			}
21621 
21622 			HRESULT DragLeave() {
21623 				handler.dragLeave();
21624 				// release the IDataObject if needed
21625 				return S_OK;
21626 			}
21627 
21628 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21629 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21630 
21631 				*pdwEffect = win32DragAndDropAction(res.action);
21632 				// same as DragEnter basically
21633 				return S_OK;
21634 			}
21635 
21636 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21637 				DropPackage pkg = DropPackage(pDataObj);
21638 				handler.drop(&pkg);
21639 
21640 				return S_OK;
21641 			}
21642 		};
21643 		// Windows can hold on to the handler and try to call it
21644 		// during which time the GC can't see it. so important to
21645 		// manually manage this. At some point i'll FIXME and make
21646 		// all my com instances manually managed since they supposed
21647 		// to respect the refcount.
21648 		import core.memory;
21649 		GC.addRoot(cast(void*) dropTarget);
21650 
21651 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21652 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21653 
21654 		window.dropHandler = handler;
21655 	} else throw new NotYetImplementedException();
21656 }
21657 
21658 
21659 
21660 static if(UsingSimpledisplayX11) {
21661 
21662 enum _NET_WM_STATE_ADD = 1;
21663 enum _NET_WM_STATE_REMOVE = 0;
21664 enum _NET_WM_STATE_TOGGLE = 2;
21665 
21666 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21667 void demandAttention(SimpleWindow window, bool needs = true) {
21668 	demandAttention(window.impl.window, needs);
21669 }
21670 
21671 /// ditto
21672 void demandAttention(Window window, bool needs = true) {
21673 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21674 }
21675 
21676 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21677 	auto display = XDisplayConnection.get();
21678 	if(atom == None)
21679 		return; // non-failure error
21680 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21681 
21682 	XClientMessageEvent xclient;
21683 
21684 	xclient.type = EventType.ClientMessage;
21685 	xclient.window = window;
21686 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21687 	xclient.format = 32;
21688 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21689 	xclient.data.l[1] = atom;
21690 	xclient.data.l[2] = atom2;
21691 	xclient.data.l[3] = 1;
21692 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21693 
21694 	XSendEvent(
21695 		display,
21696 		RootWindow(display, DefaultScreen(display)),
21697 		false,
21698 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21699 		cast(XEvent*) &xclient
21700 	);
21701 
21702 	/+
21703 	XChangeProperty(
21704 		display,
21705 		window.impl.window,
21706 		GetAtom!"_NET_WM_STATE"(display),
21707 		XA_ATOM,
21708 		32 /* bits */,
21709 		PropModeAppend,
21710 		&atom,
21711 		1);
21712 	+/
21713 }
21714 
21715 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21716 	Atom actionAtom;
21717 	with(DragAndDropAction)
21718 	final switch(action) {
21719 		case none: actionAtom = None; break;
21720 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21721 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21722 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21723 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21724 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21725 	}
21726 
21727 	return actionAtom;
21728 }
21729 
21730 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21731 	// FIXME: I need to show user feedback somehow.
21732 	auto display = XDisplayConnection.get;
21733 
21734 	auto actionAtom = dndActionAtom(display, action);
21735 	assert(actionAtom, "Don't use action none to accept a drop");
21736 
21737 	setX11Selection!"XdndSelection"(window, handler, null);
21738 
21739 	auto oldKeyHandler = window.handleKeyEvent;
21740 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21741 
21742 	auto oldCharHandler = window.handleCharEvent;
21743 	scope(exit) window.handleCharEvent = oldCharHandler;
21744 
21745 	auto oldMouseHandler = window.handleMouseEvent;
21746 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21747 
21748 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21749 
21750 	import core.sys.posix.sys.time;
21751 	timeval tv;
21752 	gettimeofday(&tv, null);
21753 
21754 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21755 
21756 	Time lastMouseTimestamp;
21757 
21758 	bool dnding = true;
21759 	Window lastIn = None;
21760 
21761 	void leave() {
21762 		if(lastIn == None)
21763 			return;
21764 
21765 		XEvent ev;
21766 		ev.xclient.type = EventType.ClientMessage;
21767 		ev.xclient.window = lastIn;
21768 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21769 		ev.xclient.format = 32;
21770 		ev.xclient.data.l[0] = window.impl.window;
21771 
21772 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21773 		XFlush(display);
21774 
21775 		lastIn = None;
21776 	}
21777 
21778 	void enter(Window w) {
21779 		assert(lastIn == None);
21780 
21781 		lastIn = w;
21782 
21783 		XEvent ev;
21784 		ev.xclient.type = EventType.ClientMessage;
21785 		ev.xclient.window = lastIn;
21786 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21787 		ev.xclient.format = 32;
21788 		ev.xclient.data.l[0] = window.impl.window;
21789 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21790 
21791 		auto types = handler.availableFormats();
21792 		assert(types.length > 0);
21793 
21794 		ev.xclient.data.l[2] = types[0];
21795 		if(types.length > 1)
21796 			ev.xclient.data.l[3] = types[1];
21797 		if(types.length > 2)
21798 			ev.xclient.data.l[4] = types[2];
21799 
21800 		// FIXME: other types?!?!? and make sure we skip TARGETS
21801 
21802 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21803 		XFlush(display);
21804 	}
21805 
21806 	void position(int rootX, int rootY) {
21807 		assert(lastIn != None);
21808 
21809 		XEvent ev;
21810 		ev.xclient.type = EventType.ClientMessage;
21811 		ev.xclient.window = lastIn;
21812 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21813 		ev.xclient.format = 32;
21814 		ev.xclient.data.l[0] = window.impl.window;
21815 		ev.xclient.data.l[1] = 0; // reserved
21816 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21817 		ev.xclient.data.l[3] = dataTimestamp;
21818 		ev.xclient.data.l[4] = actionAtom;
21819 
21820 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21821 		XFlush(display);
21822 
21823 	}
21824 
21825 	void drop() {
21826 		XEvent ev;
21827 		ev.xclient.type = EventType.ClientMessage;
21828 		ev.xclient.window = lastIn;
21829 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21830 		ev.xclient.format = 32;
21831 		ev.xclient.data.l[0] = window.impl.window;
21832 		ev.xclient.data.l[1] = 0; // reserved
21833 		ev.xclient.data.l[2] = dataTimestamp;
21834 
21835 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21836 		XFlush(display);
21837 
21838 		lastIn = None;
21839 		dnding = false;
21840 	}
21841 
21842 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21843 	// but idk if i should...
21844 
21845 	window.setEventHandlers(
21846 		delegate(KeyEvent ev) {
21847 			if(ev.pressed == true && ev.key == Key.Escape) {
21848 				// cancel
21849 				dnding = false;
21850 			}
21851 		},
21852 		delegate(MouseEvent ev) {
21853 			if(ev.timestamp < lastMouseTimestamp)
21854 				return;
21855 
21856 			lastMouseTimestamp = ev.timestamp;
21857 
21858 			if(ev.type == MouseEventType.motion) {
21859 				auto display = XDisplayConnection.get;
21860 				auto root = RootWindow(display, DefaultScreen(display));
21861 
21862 				Window topWindow;
21863 				int rootX, rootY;
21864 
21865 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21866 
21867 				if(topWindow == None)
21868 					return;
21869 
21870 				top:
21871 				if(auto result = topWindow in eligibility) {
21872 					auto dropWindow = *result;
21873 					if(dropWindow == None) {
21874 						leave();
21875 						return;
21876 					}
21877 
21878 					if(dropWindow != lastIn) {
21879 						leave();
21880 						enter(dropWindow);
21881 						position(rootX, rootY);
21882 					} else {
21883 						position(rootX, rootY);
21884 					}
21885 				} else {
21886 					// determine eligibility
21887 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21888 					if(data.length == 1) {
21889 						// in case there is no WM or it isn't reparenting
21890 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21891 					} else {
21892 
21893 						Window tryScanChildren(Window search, int maxRecurse) {
21894 							// could be reparenting window manager, so gotta check the next few children too
21895 							Window child;
21896 							int x;
21897 							int y;
21898 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21899 
21900 							if(child == None)
21901 								return None;
21902 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21903 							if(data.length == 1) {
21904 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21905 							} else {
21906 								if(maxRecurse)
21907 									return tryScanChildren(child, maxRecurse - 1);
21908 								else
21909 									return None;
21910 							}
21911 
21912 						}
21913 
21914 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21915 						auto topResult = tryScanChildren(topWindow, 3);
21916 						// it is easy to have a false negative due to the mouse going over a WM
21917 						// child window like the close button if separate from the frame... so I
21918 						// can't really cache negatives, :(
21919 						if(topResult != None) {
21920 							eligibility[topWindow] = topResult;
21921 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21922 						}
21923 					}
21924 
21925 				}
21926 
21927 			} else if(ev.type == MouseEventType.buttonReleased) {
21928 				drop();
21929 				dnding = false;
21930 			}
21931 		}
21932 	);
21933 
21934 	window.grabInput();
21935 	scope(exit)
21936 		window.releaseInputGrab();
21937 
21938 
21939 	EventLoop.get.run(() => dnding);
21940 
21941 	return 0;
21942 }
21943 
21944 /// X-specific
21945 TrueColorImage getWindowNetWmIcon(Window window) {
21946 	try {
21947 		auto display = XDisplayConnection.get;
21948 
21949 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21950 
21951 		if (data.length > arch_ulong.sizeof * 2) {
21952 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21953 			// these are an array of rgba images that we have to convert into pixmaps ourself
21954 
21955 			int width = cast(int) meta[0];
21956 			int height = cast(int) meta[1];
21957 
21958 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21959 
21960 			static if(arch_ulong.sizeof == 4) {
21961 				bytes = bytes[0 .. width * height * 4];
21962 				alias imageData = bytes;
21963 			} else static if(arch_ulong.sizeof == 8) {
21964 				bytes = bytes[0 .. width * height * 8];
21965 				auto imageData = new ubyte[](4 * width * height);
21966 			} else static assert(0);
21967 
21968 
21969 
21970 			// this returns ARGB. Remember it is little-endian so
21971 			//                                         we have BGRA
21972 			// our thing uses RGBA, which in little endian, is ABGR
21973 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21974 				auto r = bytes[idx + 2];
21975 				auto g = bytes[idx + 1];
21976 				auto b = bytes[idx + 0];
21977 				auto a = bytes[idx + 3];
21978 
21979 				imageData[idx2 + 0] = r;
21980 				imageData[idx2 + 1] = g;
21981 				imageData[idx2 + 2] = b;
21982 				imageData[idx2 + 3] = a;
21983 			}
21984 
21985 			return new TrueColorImage(width, height, imageData);
21986 		}
21987 
21988 		return null;
21989 	} catch(Exception e) {
21990 		return null;
21991 	}
21992 }
21993 
21994 } /* UsingSimpledisplayX11 */
21995 
21996 
21997 void loadBinNameToWindowClassName () {
21998 	import core.stdc.stdlib : realloc;
21999 	version(linux) {
22000 		// args[0] MAY be empty, so we'll just use this
22001 		import core.sys.posix.unistd : readlink;
22002 		char[1024] ebuf = void; // 1KB should be enough for everyone!
22003 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22004 		if (len < 1) return;
22005 	} else /*version(Windows)*/ {
22006 		import core.runtime : Runtime;
22007 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22008 		auto ebuf = Runtime.args[0];
22009 		auto len = ebuf.length;
22010 	}
22011 	auto pos = len;
22012 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22013 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22014 	if (sdpyWindowClassStr is null) return; // oops
22015 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22016 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22017 }
22018 
22019 /++
22020 	An interface representing a font that is drawn with custom facilities.
22021 
22022 	You might want [OperatingSystemFont] instead, which represents
22023 	a font loaded and drawn by functions native to the operating system.
22024 
22025 	WARNING: I might still change this.
22026 +/
22027 interface DrawableFont : MeasurableFont {
22028 	/++
22029 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22030 
22031 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22032 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22033 		fill color, but that's up to the implementation.
22034 	+/
22035 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22036 
22037 	/++
22038 		Requests that the given string is added to the image cache. You should only do this rarely, but
22039 		if you have a string that you know will be used over and over again, adding it to a cache can
22040 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22041 		to implement this as a do-nothing method).
22042 	+/
22043 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22044 }
22045 
22046 /++
22047 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22048 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22049 
22050 	You should also consider [OperatingSystemFont], which loads and draws a font with
22051 	facilities native to the user's operating system. You might also consider
22052 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22053 	of game, as they have their own ways to draw text too.
22054 
22055 	Be warned: this can be slow, especially on remote connections to the X server, since
22056 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22057 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22058 	experiment in your specific case.
22059 
22060 	Please note that the return type of [DrawableFont] also includes an implementation of
22061 	[MeasurableFont].
22062 +/
22063 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22064 	import arsd.ttf;
22065 	static class ArsdTtfFont : DrawableFont {
22066 		TtfFont font;
22067 		int size;
22068 		this(in ubyte[] data, int size) {
22069 			font = TtfFont(data);
22070 			this.size = size;
22071 
22072 
22073 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22074 			int ascent_, descent_, line_gap;
22075 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22076 
22077 			int advance, lsb;
22078 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22079 			xWidth = cast(int) (advance * scale);
22080 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22081 			MWidth = cast(int) (advance * scale);
22082 		}
22083 
22084 		private int ascent_;
22085 		private int descent_;
22086 		private int xWidth;
22087 		private int MWidth;
22088 
22089 		bool isMonospace() {
22090 			return xWidth == MWidth;
22091 		}
22092 		int averageWidth() {
22093 			return xWidth;
22094 		}
22095 		int height() {
22096 			return size;
22097 		}
22098 		int ascent() {
22099 			return ascent_;
22100 		}
22101 		int descent() {
22102 			return descent_;
22103 		}
22104 
22105 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
22106 			int width, height;
22107 			font.getStringSize(s, size, width, height);
22108 			return width;
22109 		}
22110 
22111 
22112 
22113 		Sprite[string] cache;
22114 
22115 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
22116 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
22117 			cache[text] = sprite;
22118 		}
22119 
22120 		Image stringToImage(Color fg, Color bg, in char[] text) {
22121 			int width, height;
22122 			auto data = font.renderString(text, size, width, height);
22123 			auto image = new TrueColorImage(width, height);
22124 			int pos = 0;
22125 			foreach(y; 0 .. height)
22126 			foreach(x; 0 .. width) {
22127 				fg.a = data[0];
22128 				bg.a = 255;
22129 				auto color = alphaBlend(fg, bg);
22130 				image.imageData.bytes[pos++] = color.r;
22131 				image.imageData.bytes[pos++] = color.g;
22132 				image.imageData.bytes[pos++] = color.b;
22133 				image.imageData.bytes[pos++] = data[0];
22134 				data = data[1 .. $];
22135 			}
22136 			assert(data.length == 0);
22137 
22138 			return Image.fromMemoryImage(image);
22139 		}
22140 
22141 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22142 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22143 
22144 			auto fg = painter.impl._outlineColor;
22145 			auto bg = painter.impl._fillColor;
22146 
22147 			if(sprite !is null) {
22148 				auto w = cast(SimpleWindow) painter.window;
22149 				assert(w !is null);
22150 
22151 				sprite.drawAt(painter, upperLeft);
22152 			} else {
22153 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
22154 			}
22155 		}
22156 	}
22157 
22158 	return new ArsdTtfFont(data, size);
22159 }
22160 
22161 class NotYetImplementedException : Exception {
22162 	this(string file = __FILE__, size_t line = __LINE__) {
22163 		super("Not yet implemented", file, line);
22164 	}
22165 }
22166 
22167 ///
22168 __gshared bool librariesSuccessfullyLoaded = true;
22169 ///
22170 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
22171 
22172 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
22173 	mixin(staticForeachReplacement!Iface);
22174 
22175 	void loadDynamicLibrary() @nogc {
22176 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22177 	}
22178 
22179         void loadDynamicLibraryForReal() {
22180                 foreach(name; __traits(derivedMembers, Iface)) {
22181                         mixin("alias tmp = " ~ name ~ ";");
22182                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
22183                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
22184                 }
22185         }
22186 }
22187 
22188 private const(char)[] staticForeachReplacement(Iface)() pure {
22189 /*
22190 	// just this for gdc 9....
22191 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
22192 
22193         static foreach(name; __traits(derivedMembers, Iface))
22194                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22195 */
22196 
22197 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
22198 	size_t pos;
22199 
22200 	void append(in char[] what) {
22201 		if(pos + what.length > code.length)
22202 			code.length = (code.length * 3) / 2;
22203 		code[pos .. pos + what.length] = what[];
22204 		pos += what.length;
22205 	}
22206 
22207         foreach(name; __traits(derivedMembers, Iface)) {
22208                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
22209 		append(name);
22210 		append(`")) `);
22211 		append(name);
22212 		append(";");
22213 	}
22214 
22215 	return code[0 .. pos];
22216 }
22217 
22218 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22219 	mixin(staticForeachReplacement!Iface);
22220 
22221 	private __gshared void* libHandle;
22222 	private __gshared bool attempted;
22223 
22224         void loadDynamicLibrary() @nogc {
22225 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22226 	}
22227 
22228 	bool loadAttempted() {
22229 		return attempted;
22230 	}
22231 	bool loadSuccessful() {
22232 		return libHandle !is null;
22233 	}
22234 
22235         void loadDynamicLibraryForReal() {
22236 		attempted = true;
22237                 version(Posix) {
22238                         import core.sys.posix.dlfcn;
22239 			version(OSX) {
22240 				version(X11)
22241                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22242 				else
22243                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22244 			} else {
22245                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22246 				if(libHandle is null)
22247                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22248 			}
22249 
22250 			static void* loadsym(void* l, const char* name) {
22251 				import core.stdc.stdlib;
22252 				if(l is null)
22253 					return &abort;
22254 				return dlsym(l, name);
22255 			}
22256                 } else version(Windows) {
22257                         import core.sys.windows.winbase;
22258                         libHandle = LoadLibrary(library ~ ".dll");
22259 			static void* loadsym(void* l, const char* name) {
22260 				import core.stdc.stdlib;
22261 				if(l is null)
22262 					return &abort;
22263 				return GetProcAddress(l, name);
22264 			}
22265                 }
22266                 if(libHandle is null) {
22267 			success = false;
22268                         //throw new Exception("load failure of library " ~ library);
22269 		}
22270                 foreach(name; __traits(derivedMembers, Iface)) {
22271                         mixin("alias tmp = " ~ name ~ ";");
22272                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22273                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22274                 }
22275         }
22276 
22277         void unloadDynamicLibrary() {
22278                 version(Posix) {
22279                         import core.sys.posix.dlfcn;
22280                         dlclose(libHandle);
22281                 } else version(Windows) {
22282                         import core.sys.windows.winbase;
22283                         FreeLibrary(libHandle);
22284                 }
22285                 foreach(name; __traits(derivedMembers, Iface))
22286                         mixin(name ~ " = null;");
22287         }
22288 }
22289 
22290 /+
22291 	The GC can be called from any thread, and a lot of cleanup must be done
22292 	on the gui thread. Since the GC can interrupt any locks - including being
22293 	triggered inside a critical section - it is vital to avoid deadlocks to get
22294 	these functions called from the right place.
22295 
22296 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22297 	right now.
22298 
22299 	The cleanup function is run when the event loop gets around to it, which is just
22300 	whenever there's something there after it has been woken up for other work. It does
22301 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22302 	(Well actually it might be ok but i don't wanna mess with it right now.)
22303 +/
22304 private struct CleanupQueue {
22305 	import core.stdc.stdlib;
22306 
22307 	void queue(alias func, T...)(T args) {
22308 		static struct Args {
22309 			T args;
22310 		}
22311 		static struct RealJob {
22312 			Job j;
22313 			Args a;
22314 		}
22315 		static void call(Job* data) {
22316 			auto rj = cast(RealJob*) data;
22317 			func(rj.a.args);
22318 		}
22319 
22320 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22321 		thing.j.call = &call;
22322 		thing.a.args = args;
22323 
22324 		buffer[tail++] = cast(Job*) thing;
22325 
22326 		// FIXME: set overflowed
22327 	}
22328 
22329 	void process() {
22330 		const tail = this.tail;
22331 
22332 		while(tail != head) {
22333 			Job* job = cast(Job*) buffer[head++];
22334 			job.call(job);
22335 			free(job);
22336 		}
22337 
22338 		if(overflowed)
22339 			throw new Exception("cleanup overflowed");
22340 	}
22341 
22342 	private:
22343 
22344 	ubyte tail; // must ONLY be written by queue
22345 	ubyte head; // must ONLY be written by process
22346 	bool overflowed;
22347 
22348 	static struct Job {
22349 		void function(Job*) call;
22350 	}
22351 
22352 	void*[256] buffer;
22353 }
22354 private __gshared CleanupQueue cleanupQueue;
22355 
22356 version(X11)
22357 /++
22358 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22359 
22360 	$(WARNING
22361 		This function is exempted from stability guarantees.
22362 	)
22363 +/
22364 float customScalingFactorForMonitor(int monitorNumber) {
22365 	import core.stdc.stdlib;
22366 	auto val = getenv("ARSD_SCALING_FACTOR");
22367 
22368 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
22369 	if(val is null)
22370 		return 1.0;
22371 
22372 	char[16] buffer = 0;
22373 	int pos;
22374 
22375 	const(char)* at = val;
22376 
22377 	foreach(item; 0 .. monitorNumber + 1) {
22378 		if(*at == 0)
22379 			break; // reuse the last number when we at the end of the string
22380 		pos = 0;
22381 		while(pos + 1 < buffer.length && *at && *at != ';') {
22382 			buffer[pos++] = *at;
22383 			at++;
22384 		}
22385 		if(*at)
22386 			at++; // skip the semicolon
22387 		buffer[pos] = 0;
22388 	}
22389 
22390 	//sdpyPrintDebugString(buffer[0 .. pos]);
22391 
22392 	import core.stdc.math;
22393 	auto f = atof(buffer.ptr);
22394 
22395 	if(f <= 0.0 || isnan(f) || isinf(f))
22396 		return 1.0;
22397 
22398 	return f;
22399 }
22400 
22401 void guiAbortProcess(string msg) {
22402 	import core.stdc.stdlib;
22403 	version(Windows) {
22404 		WCharzBuffer t = WCharzBuffer(msg);
22405 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22406 	} else {
22407 		import core.stdc.stdio;
22408 		fwrite(msg.ptr, 1, msg.length, stderr);
22409 		msg = "\n";
22410 		fwrite(msg.ptr, 1, msg.length, stderr);
22411 		fflush(stderr);
22412 	}
22413 
22414 	abort();
22415 }
22416 
22417 private int minInternal(int a, int b) {
22418 	return (a < b) ? a : b;
22419 }
22420 
22421 private alias scriptable = arsd_jsvar_compatible;