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 
475 	$(H3 $(ID topic-images) Displaying images)
476 		You can also load PNG images using [arsd.png].
477 
478 		---
479 		// dmd example.d simpledisplay.d color.d png.d
480 		import arsd.simpledisplay;
481 		import arsd.png;
482 
483 		void main() {
484 			auto image = Image.fromMemoryImage(readPng("image.png"));
485 			displayImage(image);
486 		}
487 		---
488 
489 		Compile with `dmd example.d simpledisplay.d png.d`.
490 
491 		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.
492 
493 	$(H3 $(ID topic-sprites) Sprites)
494 		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.
495 
496 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
497 
498 	$(H3 $(ID topic-clipboard) Clipboard)
499 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
500 
501 		It also has helpers for handling X-specific events.
502 
503 	$(H3 $(ID topic-dnd) Drag and Drop)
504 		See [enableDragAndDrop] and [draggable].
505 
506 	$(H3 $(ID topic-timers) Timers)
507 		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].
508 
509 		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.
510 
511 		---
512 			import arsd.simpledisplay;
513 
514 			void main() {
515 				auto window = new SimpleWindow(400, 400);
516 				// every 100 ms, it will draw a random line
517 				// on the window.
518 				window.eventLoop(100, {
519 					auto painter = window.draw();
520 
521 					import std.random;
522 					// random color
523 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
524 					// random line
525 					painter.drawLine(
526 						Point(uniform(0, window.width), uniform(0, window.height)),
527 						Point(uniform(0, window.width), uniform(0, window.height)));
528 
529 				});
530 			}
531 		---
532 
533 		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.
534 
535 		The pulse timer and instances of the [Timer] class may be combined at will.
536 
537 		---
538 			import arsd.simpledisplay;
539 
540 			void main() {
541 				auto window = new SimpleWindow(400, 400);
542 				auto timer = new Timer(1000, delegate {
543 					auto painter = window.draw();
544 					painter.clear();
545 				});
546 
547 				window.eventLoop(0);
548 			}
549 		---
550 
551 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
552 
553 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
554 		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.
555 
556 		See also: `xwindows.d` from my github.
557 
558 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
559 		`handleNativeEvent` and `handleNativeGlobalEvent`.
560 
561 	$(H3 $(ID topic-integration) Integration with other libraries)
562 		Integration with a third-party event loop is possible.
563 
564 		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.
565 
566 	$(H3 $(ID topic-guis) GUI widgets)
567 		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!
568 
569 		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.
570 
571 		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.)
572 
573 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
574 
575 	$(H2 Platform-specific tips and tricks)
576 
577 	X_tips:
578 
579 	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.
580 
581 	Windows_tips:
582 
583 	You can add icons or manifest files to your exe using a resource file.
584 
585 	To create a Windows .ico file, use the gimp or something. I'll write a helper
586 	program later.
587 
588 	Create `yourapp.rc`:
589 
590 	```rc
591 		1 ICON filename.ico
592 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
593 	```
594 
595 	And `yourapp.exe.manifest`:
596 
597 	```xml
598 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
599 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
600 		<assemblyIdentity
601 		    version="1.0.0.0"
602 		    processorArchitecture="*"
603 		    name="CompanyName.ProductName.YourApplication"
604 		    type="win32"
605 		/>
606 		<description>Your application description here.</description>
607 		<dependency>
608 		    <dependentAssembly>
609 			<assemblyIdentity
610 			    type="win32"
611 			    name="Microsoft.Windows.Common-Controls"
612 			    version="6.0.0.0"
613 			    processorArchitecture="*"
614 			    publicKeyToken="6595b64144ccf1df"
615 			    language="*"
616 			/>
617 		    </dependentAssembly>
618 		</dependency>
619 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
620 			<windowsSettings>
621 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
622 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
623 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
624 				<!-- to render crisply in DPI-unaware contexts -->
625 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
626 			</windowsSettings>
627 		</application>
628 		</assembly>
629 	```
630 
631 	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`.
632 
633 	Doing this lets you opt into various new things since Windows XP.
634 
635 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
636 
637 	$(H2 Tips)
638 
639 	$(H3 Name conflicts)
640 
641 	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:
642 
643 	---
644 	static import sdpy = arsd.simpledisplay;
645 	import arsd.simpledisplay : SimpleWindow;
646 
647 	void main() {
648 		auto window = new SimpleWindow();
649 		sdpy.EventLoop.get.run();
650 	}
651 	---
652 
653 	$(H2 $(ID developer-notes) Developer notes)
654 
655 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
656 	implementation though.
657 
658 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
659 	suck. If I was rewriting it, I wouldn't do it that way again.
660 
661 	This file must not have any more required dependencies. If you need bindings, add
662 	them right to this file. Once it gets into druntime and is there for a while, remove
663 	bindings from here to avoid conflicts (or put them in an appropriate version block
664 	so it continues to just work on old dmd), but wait a couple releases before making the
665 	transition so this module remains usable with older versions of dmd.
666 
667 	You may have optional dependencies if needed by putting them in version blocks or
668 	template functions. You may also extend the module with other modules with UFCS without
669 	actually editing this - that is nice to do if you can.
670 
671 	Try to make functions work the same way across operating systems. I typically make
672 	it thinly wrap Windows, then emulate that on Linux.
673 
674 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
675 	Phobos! So try to avoid it.
676 
677 	See more comments throughout the source.
678 
679 	I realize this file is fairly large, but over half that is just bindings at the bottom
680 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
681 	to understand. I suggest you jump around the source by looking for a particular
682 	declaration you're interested in, like `class SimpleWindow` using your editor's search
683 	function, then look at one piece at a time.
684 
685 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
686 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
687 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
688 
689 	I live in the eastern United States, so I will most likely not be around at night in
690 	that US east timezone.
691 
692 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
693 
694 	Building documentation: use my adrdox generator, `dub run adrdox`.
695 
696 	Examples:
697 
698 	$(DIV $(ID Event-example))
699 	$(H3 $(ID event-example) Event example)
700 	This program creates a window and draws events inside them as they
701 	happen, scrolling the text in the window as needed. Run this program
702 	and experiment to get a feel for where basic input events take place
703 	in the library.
704 
705 	---
706 	// dmd example.d simpledisplay.d color.d
707 	import arsd.simpledisplay;
708 	import std.conv;
709 
710 	void main() {
711 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
712 
713 		int y = 0;
714 
715 		void addLine(string text) {
716 			auto painter = window.draw();
717 
718 			if(y + painter.fontHeight >= window.height) {
719 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
720 				y -= painter.fontHeight;
721 			}
722 
723 			painter.outlineColor = Color.red;
724 			painter.fillColor = Color.black;
725 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
726 
727 			painter.outlineColor = Color.white;
728 
729 			painter.drawText(Point(10, y), text);
730 
731 			y += painter.fontHeight;
732 		}
733 
734 		window.eventLoop(1000,
735 		  () {
736 			addLine("Timer went off!");
737 		  },
738 		  (KeyEvent event) {
739 			addLine(to!string(event));
740 		  },
741 		  (MouseEvent event) {
742 			addLine(to!string(event));
743 		  },
744 		  (dchar ch) {
745 			addLine(to!string(ch));
746 		  }
747 		);
748 	}
749 	---
750 
751 	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.
752 
753 	$(COMMENT
754 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
755 
756 	---
757 
758 	---
759 	)
760 
761 	History:
762 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
763 
764 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
765 +/
766 module arsd.simpledisplay;
767 
768 import arsd.core;
769 
770 // FIXME: tetris demo
771 // FIXME: space invaders demo
772 // FIXME: asteroids demo
773 
774 /++ $(ID Pong-example)
775 	$(H3 Pong)
776 
777 	This program creates a little Pong-like game. Player one is controlled
778 	with the keyboard.  Player two is controlled with the mouse. It demos
779 	the pulse timer, event handling, and some basic drawing.
780 +/
781 version(demos)
782 unittest {
783 	// dmd example.d simpledisplay.d color.d
784 	import arsd.simpledisplay;
785 
786 	enum paddleMovementSpeed = 8;
787 	enum paddleHeight = 48;
788 
789 	void main() {
790 		auto window = new SimpleWindow(600, 400, "Pong game!");
791 
792 		int playerOnePosition, playerTwoPosition;
793 		int playerOneMovement, playerTwoMovement;
794 		int playerOneScore, playerTwoScore;
795 
796 		int ballX, ballY;
797 		int ballDx, ballDy;
798 
799 		void serve() {
800 			import std.random;
801 
802 			ballX = window.width / 2;
803 			ballY = window.height / 2;
804 			ballDx = uniform(-4, 4) * 3;
805 			ballDy = uniform(-4, 4) * 3;
806 			if(ballDx == 0)
807 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
808 		}
809 
810 		serve();
811 
812 		window.eventLoop(50, // set a 50 ms timer pulls
813 			// This runs once per timer pulse
814 			delegate () {
815 				auto painter = window.draw();
816 
817 				painter.clear();
818 
819 				// Update everyone's motion
820 				playerOnePosition += playerOneMovement;
821 				playerTwoPosition += playerTwoMovement;
822 
823 				ballX += ballDx;
824 				ballY += ballDy;
825 
826 				// Bounce off the top and bottom edges of the window
827 				if(ballY + 7 >= window.height)
828 					ballDy = -ballDy;
829 				if(ballY - 8 <= 0)
830 					ballDy = -ballDy;
831 
832 				// Bounce off the paddle, if it is in position
833 				if(ballX - 8 <= 16) {
834 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
835 						ballDx = -ballDx + 1; // add some speed to keep it interesting
836 						ballDy += playerOneMovement; // and y movement based on your controls too
837 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
838 					} else {
839 						// Missed it
840 						playerTwoScore ++;
841 						serve();
842 					}
843 				}
844 
845 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
846 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
847 						ballDx = -ballDx - 1;
848 						ballDy += playerTwoMovement;
849 						ballX = window.width - 24;
850 					} else {
851 						// Missed it
852 						playerOneScore ++;
853 						serve();
854 					}
855 				}
856 
857 				// Draw the paddles
858 				painter.outlineColor = Color.black;
859 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
860 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
861 
862 				// Draw the ball
863 				painter.fillColor = Color.red;
864 				painter.outlineColor = Color.yellow;
865 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
866 
867 				// Draw the score
868 				painter.outlineColor = Color.blue;
869 				import std.conv;
870 				painter.drawText(Point(64, 4), to!string(playerOneScore));
871 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
872 
873 			},
874 			delegate (KeyEvent event) {
875 				// Player 1's controls are the arrow keys on the keyboard
876 				if(event.key == Key.Down)
877 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
878 				if(event.key == Key.Up)
879 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
880 
881 			},
882 			delegate (MouseEvent event) {
883 				// Player 2's controls are mouse movement while the left button is held down
884 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
885 					if(event.dy > 0)
886 						playerTwoMovement = paddleMovementSpeed;
887 					else if(event.dy < 0)
888 						playerTwoMovement = -paddleMovementSpeed;
889 				} else {
890 					playerTwoMovement = 0;
891 				}
892 			}
893 		);
894 	}
895 }
896 
897 /++ $(H3 $(ID example-minesweeper) Minesweeper)
898 
899 	This minesweeper demo shows how we can implement another classic
900 	game with simpledisplay and shows some mouse input and basic output
901 	code.
902 +/
903 version(demos)
904 unittest {
905 	import arsd.simpledisplay;
906 
907 	enum GameSquare {
908 		mine = 0,
909 		clear,
910 		m1, m2, m3, m4, m5, m6, m7, m8
911 	}
912 
913 	enum UserSquare {
914 		unknown,
915 		revealed,
916 		flagged,
917 		questioned
918 	}
919 
920 	enum GameState {
921 		inProgress,
922 		lose,
923 		win
924 	}
925 
926 	GameSquare[] board;
927 	UserSquare[] userState;
928 	GameState gameState;
929 	int boardWidth;
930 	int boardHeight;
931 
932 	bool isMine(int x, int y) {
933 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
934 			return false;
935 		return board[y * boardWidth + x] == GameSquare.mine;
936 	}
937 
938 	GameState reveal(int x, int y) {
939 		if(board[y * boardWidth + x] == GameSquare.clear) {
940 			floodFill(userState, boardWidth, boardHeight,
941 				UserSquare.unknown, UserSquare.revealed,
942 				x, y,
943 				(x, y) {
944 					if(board[y * boardWidth + x] == GameSquare.clear)
945 						return true;
946 					else {
947 						userState[y * boardWidth + x] = UserSquare.revealed;
948 						return false;
949 					}
950 				});
951 		} else {
952 			userState[y * boardWidth + x] = UserSquare.revealed;
953 			if(isMine(x, y))
954 				return GameState.lose;
955 		}
956 
957 		foreach(state; userState) {
958 			if(state == UserSquare.unknown || state == UserSquare.questioned)
959 				return GameState.inProgress;
960 		}
961 
962 		return GameState.win;
963 	}
964 
965 	void initializeBoard(int width, int height, int numberOfMines) {
966 		boardWidth = width;
967 		boardHeight = height;
968 		board.length = width * height;
969 
970 		userState.length = width * height;
971 		userState[] = UserSquare.unknown;
972 
973 		import std.algorithm, std.random, std.range;
974 
975 		board[] = GameSquare.clear;
976 
977 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
978 			board[minePosition] = GameSquare.mine;
979 
980 		int x;
981 		int y;
982 		foreach(idx, ref square; board) {
983 			if(square == GameSquare.clear) {
984 				int danger = 0;
985 				danger += isMine(x-1, y-1)?1:0;
986 				danger += isMine(x-1, y)?1:0;
987 				danger += isMine(x-1, y+1)?1:0;
988 				danger += isMine(x, y-1)?1:0;
989 				danger += isMine(x, y+1)?1:0;
990 				danger += isMine(x+1, y-1)?1:0;
991 				danger += isMine(x+1, y)?1:0;
992 				danger += isMine(x+1, y+1)?1:0;
993 
994 				square = cast(GameSquare) (danger + 1);
995 			}
996 
997 			x++;
998 			if(x == width) {
999 				x = 0;
1000 				y++;
1001 			}
1002 		}
1003 	}
1004 
1005 	void redraw(SimpleWindow window) {
1006 		import std.conv;
1007 
1008 		auto painter = window.draw();
1009 
1010 		painter.clear();
1011 
1012 		final switch(gameState) with(GameState) {
1013 			case inProgress:
1014 				break;
1015 			case win:
1016 				painter.fillColor = Color.green;
1017 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1018 				return;
1019 			case lose:
1020 				painter.fillColor = Color.red;
1021 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1022 				return;
1023 		}
1024 
1025 		int x = 0;
1026 		int y = 0;
1027 
1028 		foreach(idx, square; board) {
1029 			auto state = userState[idx];
1030 
1031 			final switch(state) with(UserSquare) {
1032 				case unknown:
1033 					painter.outlineColor = Color.black;
1034 					painter.fillColor = Color(128,128,128);
1035 
1036 					painter.drawRectangle(
1037 						Point(x * 20, y * 20),
1038 						20, 20
1039 					);
1040 				break;
1041 				case revealed:
1042 					if(square == GameSquare.clear) {
1043 						painter.outlineColor = Color.white;
1044 						painter.fillColor = Color.white;
1045 
1046 						painter.drawRectangle(
1047 							Point(x * 20, y * 20),
1048 							20, 20
1049 						);
1050 					} else {
1051 						painter.outlineColor = Color.black;
1052 						painter.fillColor = Color.white;
1053 
1054 						painter.drawText(
1055 							Point(x * 20, y * 20),
1056 							to!string(square)[1..2],
1057 							Point(x * 20 + 20, y * 20 + 20),
1058 							TextAlignment.Center | TextAlignment.VerticalCenter);
1059 					}
1060 				break;
1061 				case flagged:
1062 					painter.outlineColor = Color.black;
1063 					painter.fillColor = Color.red;
1064 					painter.drawRectangle(
1065 						Point(x * 20, y * 20),
1066 						20, 20
1067 					);
1068 				break;
1069 				case questioned:
1070 					painter.outlineColor = Color.black;
1071 					painter.fillColor = Color.yellow;
1072 					painter.drawRectangle(
1073 						Point(x * 20, y * 20),
1074 						20, 20
1075 					);
1076 				break;
1077 			}
1078 
1079 			x++;
1080 			if(x == boardWidth) {
1081 				x = 0;
1082 				y++;
1083 			}
1084 		}
1085 
1086 	}
1087 
1088 	void main() {
1089 		auto window = new SimpleWindow(200, 200);
1090 
1091 		initializeBoard(10, 10, 10);
1092 
1093 		redraw(window);
1094 		window.eventLoop(0,
1095 			delegate (MouseEvent me) {
1096 				if(me.type != MouseEventType.buttonPressed)
1097 					return;
1098 				auto x = me.x / 20;
1099 				auto y = me.y / 20;
1100 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1101 					if(me.button == MouseButton.left) {
1102 						gameState = reveal(x, y);
1103 					} else {
1104 						userState[y*boardWidth+x] = UserSquare.flagged;
1105 					}
1106 					redraw(window);
1107 				}
1108 			}
1109 		);
1110 	}
1111 }
1112 
1113 import arsd.core;
1114 
1115 // FIXME: tetris demo
1116 // FIXME: space invaders demo
1117 // FIXME: asteroids demo
1118 
1119 
1120 /*
1121 version(OSX) {
1122 	version=without_opengl;
1123 	version=allow_unimplemented_features;
1124 	version=OSXCocoa;
1125 	pragma(linkerDirective, "-framework Cocoa");
1126 }
1127 */
1128 
1129 version(without_opengl) {
1130 	enum SdpyIsUsingIVGLBinds = false;
1131 } else /*version(Posix)*/ {
1132 	static if (__traits(compiles, (){import iv.glbinds;})) {
1133 		enum SdpyIsUsingIVGLBinds = true;
1134 		public import iv.glbinds;
1135 		//pragma(msg, "SDPY: using iv.glbinds");
1136 	} else {
1137 		enum SdpyIsUsingIVGLBinds = false;
1138 	}
1139 //} else {
1140 //	enum SdpyIsUsingIVGLBinds = false;
1141 }
1142 
1143 
1144 version(Windows) {
1145 	//import core.sys.windows.windows;
1146 	import core.sys.windows.winnls;
1147 	import core.sys.windows.windef;
1148 	import core.sys.windows.basetyps;
1149 	import core.sys.windows.winbase;
1150 	import core.sys.windows.winuser;
1151 	import core.sys.windows.shellapi;
1152 	import core.sys.windows.wingdi;
1153 	static import gdi = core.sys.windows.wingdi; // so i
1154 
1155 	pragma(lib, "gdi32");
1156 	pragma(lib, "user32");
1157 
1158 	// for AlphaBlend... a breaking change....
1159 	version(CRuntime_DigitalMars) { } else
1160 		pragma(lib, "msimg32");
1161 } else version (linux) {
1162 	//k8: this is hack for rdmd. sorry.
1163 	static import core.sys.linux.epoll;
1164 	static import core.sys.linux.timerfd;
1165 }
1166 
1167 
1168 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1169 
1170 // http://wiki.dlang.org/Simpledisplay.d
1171 
1172 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1173 
1174 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1175 // but can i control the scroll lock led
1176 
1177 
1178 // Note: if you are using Image on X, you might want to do:
1179 /*
1180 	static if(UsingSimpledisplayX11) {
1181 		if(!Image.impl.xshmAvailable) {
1182 			// the images will use the slower XPutImage, you might
1183 			// want to consider an alternative method to get better speed
1184 		}
1185 	}
1186 
1187 	If the shared memory extension is available though, simpledisplay uses it
1188 	for a significant speed boost whenever you draw large Images.
1189 */
1190 
1191 // 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.
1192 
1193 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1194 
1195 /*
1196 	Biggest FIXME:
1197 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1198 
1199 		clean up opengl contexts when their windows close
1200 
1201 		fix resizing the bitmaps/pixmaps
1202 */
1203 
1204 // BTW on Windows:
1205 // -L/SUBSYSTEM:WINDOWS:5.0
1206 // to dmd will make a nice windows binary w/o a console if you want that.
1207 
1208 /*
1209 	Stuff to add:
1210 
1211 	use multibyte functions everywhere we can
1212 
1213 	OpenGL windows
1214 	more event stuff
1215 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1216 
1217 
1218 	resizeEvent
1219 		and make the windows non-resizable by default,
1220 		or perhaps stretched (if I can find something in X like StretchBlt)
1221 
1222 	take a screenshot function!
1223 
1224 	Pens and brushes?
1225 	Maybe a global event loop?
1226 
1227 	Mouse deltas
1228 	Key items
1229 */
1230 
1231 /*
1232 From MSDN:
1233 
1234 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1235 
1236 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.
1237 
1238 */
1239 
1240 version(linux) {
1241 	version = X11;
1242 	version(without_libnotify) {
1243 		// we cool
1244 	}
1245 	else
1246 		version = libnotify;
1247 }
1248 
1249 version(libnotify) {
1250 	pragma(lib, "dl");
1251 	import core.sys.posix.dlfcn;
1252 
1253 	void delegate()[int] libnotify_action_delegates;
1254 	int libnotify_action_delegates_count;
1255 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1256 		auto idx = cast(int) user_data;
1257 		if(auto dgptr = idx in libnotify_action_delegates) {
1258 			(*dgptr)();
1259 			libnotify_action_delegates.remove(idx);
1260 		}
1261 	}
1262 
1263 	struct C_DynamicLibrary {
1264 		void* handle;
1265 		this(string name) {
1266 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1267 			if(handle is null)
1268 				throw new Exception("dlopen");
1269 		}
1270 
1271 		void close() {
1272 			dlclose(handle);
1273 		}
1274 
1275 		~this() {
1276 			// close
1277 		}
1278 
1279 		// FIXME: this looks up by name every time....
1280 		template call(string func, Ret, Args...) {
1281 			extern(C) Ret function(Args) fptr;
1282 			typeof(fptr) call() {
1283 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1284 				return fptr;
1285 			}
1286 		}
1287 	}
1288 
1289 	C_DynamicLibrary* libnotify;
1290 }
1291 
1292 version(OSX) {
1293 	version(OSXCocoa) {}
1294 	else { version = X11; }
1295 }
1296 	//version = OSXCocoa; // this was written by KennyTM
1297 version(FreeBSD)
1298 	version = X11;
1299 version(Solaris)
1300 	version = X11;
1301 
1302 version(X11) {
1303 	version(without_xft) {}
1304 	else version=with_xft;
1305 }
1306 
1307 void featureNotImplemented()() {
1308 	version(allow_unimplemented_features)
1309 		throw new NotYetImplementedException();
1310 	else
1311 		static assert(0);
1312 }
1313 
1314 // these are so the static asserts don't trigger unless you want to
1315 // add support to it for an OS
1316 version(Windows)
1317 	version = with_timer;
1318 version(linux)
1319 	version = with_timer;
1320 
1321 version(with_timer)
1322 	enum bool SimpledisplayTimerAvailable = true;
1323 else
1324 	enum bool SimpledisplayTimerAvailable = false;
1325 
1326 /// 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.
1327 version(Windows)
1328 	enum bool UsingSimpledisplayWindows = true;
1329 else
1330 	enum bool UsingSimpledisplayWindows = false;
1331 
1332 /// 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.
1333 version(X11)
1334 	enum bool UsingSimpledisplayX11 = true;
1335 else
1336 	enum bool UsingSimpledisplayX11 = false;
1337 
1338 /// 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.
1339 version(OSXCocoa)
1340 	enum bool UsingSimpledisplayCocoa = true;
1341 else
1342 	enum bool UsingSimpledisplayCocoa = false;
1343 
1344 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1345 version(Windows)
1346 	enum multipleWindowsSupported = true;
1347 else version(X11)
1348 	enum multipleWindowsSupported = true;
1349 else version(OSXCocoa)
1350 	enum multipleWindowsSupported = true;
1351 else
1352 	static assert(0);
1353 
1354 version(without_opengl)
1355 	enum bool OpenGlEnabled = false;
1356 else
1357 	enum bool OpenGlEnabled = true;
1358 
1359 /++
1360 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1361 	If you mix this in above your `main` function, you no longer need to use the linker
1362 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1363 
1364 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1365 
1366 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1367 	stderr writeln. It will fail and throw an exception.
1368 
1369 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1370 
1371 	History:
1372 		Added November 24, 2021 (dub v10.4)
1373 +/
1374 mixin template EnableWindowsSubsystem() {
1375 	version(Windows)
1376 	version(CRuntime_Microsoft) {
1377 		pragma(linkerDirective, "/subsystem:windows");
1378 		version(LDC)
1379 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1380 		else
1381 			pragma(linkerDirective, "/entry:mainCRTStartup");
1382 	}
1383 }
1384 
1385 
1386 /++
1387 	After selecting a type from [WindowTypes], you may further customize
1388 	its behavior by setting one or more of these flags.
1389 
1390 
1391 	The different window types have different meanings of `normal`. If the
1392 	window type already is a good match for what you want to do, you should
1393 	just use [WindowFlags.normal], the default, which will do the right thing
1394 	for your users.
1395 
1396 	The window flags will not always be honored by the operating system
1397 	and window managers; they are hints, not commands.
1398 +/
1399 enum WindowFlags : int {
1400 	normal = 0, ///
1401 	skipTaskbar = 1, ///
1402 	alwaysOnTop = 2, ///
1403 	alwaysOnBottom = 4, ///
1404 	cannotBeActivated = 8, ///
1405 	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.
1406 	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.
1407 	/++
1408 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1409 		it is still a top-level window. This should NOT be set separately for most window types.
1410 
1411 		A transient window will not keep the application open if its main window closes.
1412 
1413 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1414 
1415 
1416 		From the ICCM:
1417 
1418 		$(BLOCKQUOTE
1419 			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.
1420 
1421 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1422 		)
1423 
1424 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1425 
1426 		History:
1427 			Added February 23, 2021 but not yet stabilized.
1428 	+/
1429 	transient = 64,
1430 	/++
1431 		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.
1432 
1433 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1434 
1435 		History:
1436 			Added April 1, 2022
1437 	+/
1438 	managesChildWindowFocus = 128,
1439 
1440 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1441 }
1442 
1443 /++
1444 	When creating a window, you can pass a type to SimpleWindow's constructor,
1445 	then further customize the window by changing `WindowFlags`.
1446 
1447 
1448 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1449 	use. The others are there to build a foundation for a higher level GUI toolkit,
1450 	but are themselves not as high level as you might think from their names.
1451 
1452 	This list is based on the EMWH spec for X11.
1453 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1454 +/
1455 enum WindowTypes : int {
1456 	/// An ordinary application window.
1457 	normal,
1458 	/// 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.
1459 	undecorated,
1460 	/// 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.
1461 	eventOnly,
1462 	/// A drop down menu, such as from a menu bar
1463 	dropdownMenu,
1464 	/// A popup menu, such as from a right click
1465 	popupMenu,
1466 	/// A popup bubble notification
1467 	notification,
1468 	/*
1469 	menu, /// a tearable menu bar
1470 	splashScreen, /// a loading splash screen for your application
1471 	tooltip, /// A tiny window showing temporary help text or something.
1472 	comboBoxDropdown,
1473 	dialog,
1474 	toolbar
1475 	*/
1476 	/// a child nested inside the parent. You must pass a parent window to the ctor
1477 	nestedChild,
1478 
1479 	/++
1480 		The type you get when you pass in an existing browser handle, which means most
1481 		of simpledisplay's fancy things will not be done since they were never set up.
1482 
1483 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1484 		failure; you should use the existing handle constructor.
1485 
1486 		History:
1487 			Added November 17, 2022 (previously it would have type `normal`)
1488 	+/
1489 	minimallyWrapped
1490 }
1491 
1492 
1493 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1494 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1495 private __gshared char* sdpyWindowClassStr = null;
1496 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1497 
1498 /**
1499 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1500 	You may want to change context version if you want to use advanced shaders or
1501 	other modern OpenGL techinques. This setting doesn't affect already created
1502 	windows. You may use version 2.1 as your default, which should be supported
1503 	by any box since 2006, so seems to be a reasonable choice.
1504 
1505 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1506 	old context creation code without any version specified. This is the safest
1507 	way to init OpenGL, but it may not give you access to advanced features.
1508 
1509 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1510 */
1511 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1512 
1513 /**
1514 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1515 	pipeline functions, and without "compatible" mode you won't be able to use
1516 	your old non-shader-based code with such contexts. By default SimpleDisplay
1517 	creates compatible context, so you can gradually upgrade your OpenGL code if
1518 	you want to (or leave it as is, as it should "just work").
1519 */
1520 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1521 
1522 /**
1523 	Set to `true` to allow creating OpenGL context with lower version than requested
1524 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1525 	`openGLContextFallbackActivated()` will return `true`.
1526 	*/
1527 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1528 
1529 /**
1530 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1531 	*/
1532 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1533 
1534 /++
1535 	History:
1536 		Added April 24, 2023  (dub v11.0)
1537 +/
1538 auto openGLCurrentContext() {
1539 	version(Windows)
1540 		return wglGetCurrentContext();
1541 	else
1542 		return glXGetCurrentContext();
1543 }
1544 
1545 
1546 /**
1547 	Set window class name for all following `new SimpleWindow()` calls.
1548 
1549 	WARNING! For Windows, you should set your class name before creating any
1550 	window, and NEVER change it after that!
1551 */
1552 void sdpyWindowClass (const(char)[] v) {
1553 	import core.stdc.stdlib : realloc;
1554 	if (v.length == 0) v = "SimpleDisplayWindow";
1555 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1556 	if (sdpyWindowClassStr is null) return; // oops
1557 	sdpyWindowClassStr[0..v.length+1] = 0;
1558 	sdpyWindowClassStr[0..v.length] = v[];
1559 }
1560 
1561 /**
1562 	Get current window class name.
1563 */
1564 string sdpyWindowClass () {
1565 	if (sdpyWindowClassStr is null) return null;
1566 	foreach (immutable idx; 0..size_t.max-1) {
1567 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1568 	}
1569 	return null;
1570 }
1571 
1572 /++
1573 	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.
1574 
1575 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1576 +/
1577 float[2] getDpi() {
1578 	float[2] dpi;
1579 	version(Windows) {
1580 		HDC screen = GetDC(null);
1581 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1582 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1583 	} else version(X11) {
1584 		auto display = XDisplayConnection.get;
1585 		auto screen = DefaultScreen(display);
1586 
1587 		void fallback() {
1588 			/+
1589 			// 25.4 millimeters in an inch...
1590 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1591 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1592 			+/
1593 
1594 			// the physical size isn't actually as important as the logical size since this is
1595 			// all about scaling really
1596 			dpi[0] = 96;
1597 			dpi[1] = 96;
1598 		}
1599 
1600 		auto xft = getXftDpi();
1601 		if(xft is float.init)
1602 			fallback();
1603 		else {
1604 			dpi[0] = xft;
1605 			dpi[1] = xft;
1606 		}
1607 	}
1608 
1609 	return dpi;
1610 }
1611 
1612 version(X11)
1613 float getXftDpi() {
1614 	auto display = XDisplayConnection.get;
1615 
1616 	char* resourceString = XResourceManagerString(display);
1617 	XrmInitialize();
1618 
1619 	if (resourceString) {
1620 		auto db = XrmGetStringDatabase(resourceString);
1621 		XrmValue value;
1622 		char* type;
1623 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1624 			if (value.addr) {
1625 				import core.stdc.stdlib;
1626 				return atof(cast(char*) value.addr);
1627 			}
1628 		}
1629 	}
1630 
1631 	return float.init;
1632 }
1633 
1634 /++
1635 	Implementation used by [SimpleWindow.takeScreenshot].
1636 
1637 	Params:
1638 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1639 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1640 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1641 		x = the x-offset of the image to capture, from the left.
1642 		y = the y-offset of the image to capture, from the top.
1643 
1644 	History:
1645 		Added on March 14, 2021
1646 
1647 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1648 
1649 +/
1650 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1651 	TrueColorImage got;
1652 	version(X11) {
1653 		auto display = XDisplayConnection.get;
1654 		if(handle == 0)
1655 			handle = RootWindow(display, DefaultScreen(display));
1656 
1657 		if(width == 0 || height == 0) {
1658 			Window root;
1659 			int xpos, ypos;
1660 			uint widthret, heightret, borderret, depthret;
1661 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1662 
1663 			if(width == 0)
1664 				width = widthret;
1665 			if(height == 0)
1666 				height = heightret;
1667 		}
1668 
1669 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1670 
1671 		// https://github.com/adamdruppe/arsd/issues/98
1672 
1673 		auto i = new Image(image);
1674 		got = i.toTrueColorImage();
1675 
1676 		XDestroyImage(image);
1677 	} else version(Windows) {
1678 		auto hdc = GetDC(handle);
1679 		scope(exit) ReleaseDC(handle, hdc);
1680 
1681 		if(width == 0 || height == 0) {
1682 			BITMAP bmHeader;
1683 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1684 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1685 			if(width == 0)
1686 				width = bmHeader.bmWidth;
1687 			if(height == 0)
1688 				height = bmHeader.bmHeight;
1689 		}
1690 
1691 		auto i = new Image(width, height);
1692 		HDC hdcMem = CreateCompatibleDC(hdc);
1693 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1694 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1695 		SelectObject(hdcMem, hbmOld);
1696 		DeleteDC(hdcMem);
1697 
1698 		got = i.toTrueColorImage();
1699 	} else featureNotImplemented();
1700 
1701 	return got;
1702 }
1703 
1704 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1705 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1706 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1707 
1708 version(Windows)
1709 shared static this() {
1710 	auto lib = LoadLibrary("User32.dll");
1711 	if(lib is null)
1712 		return;
1713 	//scope(exit)
1714 		//FreeLibrary(lib);
1715 
1716 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1717 
1718 	if(SetProcessDpiAwarenessContext is null)
1719 		return;
1720 
1721 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1722 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1723 		//writeln(GetLastError());
1724 	}
1725 
1726 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1727 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1728 }
1729 
1730 /++
1731 	Blocking mode for event loop calls associated with a window instance.
1732 
1733 	History:
1734 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1735 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1736 		is, all would block until the application quit.
1737 
1738 		That behavior can still be achieved here with `untilApplicationQuits`,
1739 		or explicitly calling the top-level `EventLoop.get.run` function.
1740 +/
1741 enum BlockingMode {
1742 	/++
1743 		The event loop call will block until the whole application is ready
1744 		to quit if it is the only one running, but if it is nested inside
1745 		another one, it will only block until the window you're calling it on
1746 		closes.
1747 	+/
1748 	automatic             = 0x00,
1749 	/++
1750 		The event loop call will only return when the whole application
1751 		is ready to quit. This usually means all windows have been closed.
1752 
1753 		This is appropriate for your main application event loop.
1754 	+/
1755 	untilApplicationQuits = 0x01,
1756 	/++
1757 		The event loop will return when the window you're calling it on
1758 		closes. If there are other windows still open, they may be destroyed
1759 		unless you have another event loop running later.
1760 
1761 		This might be appropriate for a modal dialog box loop. Remember that
1762 		other windows are still processing input though, so you can end up
1763 		with a lengthy call stack if this happens in a loop, similar to a
1764 		recursive function (well, it literally is a recursive function, just
1765 		not an obvious looking one).
1766 	+/
1767 	untilWindowCloses     = 0x02,
1768 	/++
1769 		If an event loop is already running, this call will immediately
1770 		return, allowing the existing loop to handle it. If not, this call
1771 		will block until the condition you bitwise-or into the flag.
1772 
1773 		The default is to block until the application quits, same as with
1774 		the `automatic` setting (since if it were nested, which triggers until
1775 		window closes in automatic, this flag would instead not block at all),
1776 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1777 		it will only nest until the window closes. You might want that if you are
1778 		going to open two windows simultaneously and want closing just one of them
1779 		to trigger the event loop return.
1780 	+/
1781 	onlyIfNotNested       = 0x10,
1782 }
1783 
1784 /++
1785 	The flagship window class.
1786 
1787 
1788 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1789 	out of more advanced or complex features of the underlying windowing system.
1790 
1791 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1792 	and get a suitable window to work with.
1793 
1794 	From there, you can opt into additional features, like custom resizability and OpenGL support
1795 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1796 	and customization flags with the final two constructor arguments.
1797 
1798 	If none of that works for you, you can also create a window using native function calls, then
1799 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1800 	though, if you do this, managing the window is still your own responsibility! Notably, you
1801 	will need to destroy it yourself.
1802 +/
1803 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1804 
1805 	/++
1806 		Copies the window's current state into a [TrueColorImage].
1807 
1808 		Be warned: this can be a very slow operation
1809 
1810 		History:
1811 			Actually implemented on March 14, 2021
1812 	+/
1813 	TrueColorImage takeScreenshot() {
1814 		version(Windows)
1815 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1816 		else version(OSXCocoa)
1817 			throw new NotYetImplementedException();
1818 		else
1819 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1820 	}
1821 
1822 	/++
1823 		Returns the actual logical DPI for the window on its current display monitor. If the window
1824 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1825 
1826 		Please note this function may return zero if it doesn't know the answer!
1827 
1828 
1829 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1830 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1831 
1832 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1833 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1834 		window primarily resides on by checking the center point of the window against the monitor map.
1835 
1836 		Returns:
1837 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1838 			assumes the X and Y dpi are the same.
1839 
1840 		History:
1841 			Added November 26, 2021 (dub v10.4)
1842 
1843 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1844 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1845 			that.
1846 
1847 		Bugs:
1848 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1849 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1850 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1851 			and 1.5 on the secondary monitor.
1852 
1853 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1854 			is a historical misnomer - the real thing of interest is the scale factor and due to
1855 			compatibility concerns the scale would modify dpi values to trick applications. But since
1856 			that's the terminology common out there, I used it too.
1857 
1858 		See_Also:
1859 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1860 			as this since the window many be on a different monitor, but it is a reasonable fallback
1861 			to use if `actualDpi` returns 0.
1862 
1863 			[onDpiChanged] is changed when `actualDpi` has changed.
1864 	+/
1865 	int actualDpi() {
1866 		version(X11) bool useFallbackDpi = false;
1867 		if(!actualDpiLoadAttempted) {
1868 			// FIXME: do the actual monitor we are on
1869 			// and on X this is a good chance to load the monitor map.
1870 			version(Windows) {
1871 				if(GetDpiForWindow)
1872 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1873 			} else version(X11) {
1874 				if(!xRandrInfoLoadAttemped) {
1875 					xRandrInfoLoadAttemped = true;
1876 					if(!XRandrLibrary.attempted) {
1877 						XRandrLibrary.loadDynamicLibrary();
1878 					}
1879 
1880 					if(XRandrLibrary.loadSuccessful) {
1881 						auto display = XDisplayConnection.get;
1882 						int scratch;
1883 						int major, minor;
1884 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1885 							goto fallback;
1886 
1887 						XRRQueryVersion(display, &major, &minor);
1888 						if(major <= 1 && minor < 5)
1889 							goto fallback;
1890 
1891 						int count;
1892 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1893 						if(monitors is null)
1894 							goto fallback;
1895 						scope(exit) XRRFreeMonitors(monitors);
1896 
1897 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1898 						MonitorInfo.info.assumeSafeAppend();
1899 						foreach(idx, monitor; monitors[0 .. count]) {
1900 							MonitorInfo.info ~= MonitorInfo(
1901 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1902 								Size(monitor.mwidth, monitor.mheight),
1903 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1904 							);
1905 
1906 							/+
1907 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1908 							// unknown physical size, just guess 96 to avoid divide by zero
1909 							MonitorInfo.info ~= MonitorInfo(
1910 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1911 								Size(monitor.mwidth, monitor.mheight),
1912 								96
1913 							);
1914 							else
1915 							// and actual thing
1916 							MonitorInfo.info ~= MonitorInfo(
1917 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1918 								Size(monitor.mwidth, monitor.mheight),
1919 								minInternal(
1920 									// millimeter to int then rounding up.
1921 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1922 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1923 								)
1924 							);
1925 							+/
1926 						}
1927 					// writeln("Here", MonitorInfo.info);
1928 					}
1929 				}
1930 
1931 				if(XRandrLibrary.loadSuccessful) {
1932 					updateActualDpi(true);
1933 					// writeln("updated");
1934 
1935 					if(!requestedInput) {
1936 						// this is what requests live updates should the configuration change
1937 						// each time you select input, it sends an initial event, so very important
1938 						// to not get into a loop of selecting input, getting event, updating data,
1939 						// and reselecting input...
1940 						requestedInput = true;
1941 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1942 						// writeln("requested input");
1943 					}
1944 				} else {
1945 					fallback:
1946 					// make sure we disable events that aren't coming
1947 					xrrEventBase = -1;
1948 					// best guess... respect the custom scaling user command to some extent at least though
1949 					useFallbackDpi = true;
1950 				}
1951 			}
1952 			actualDpiLoadAttempted = true;
1953 		} else version(X11) if(MonitorInfo.info.length == 0) {
1954 			useFallbackDpi = true;
1955 		}
1956 
1957 		version(X11)
1958 		if(useFallbackDpi)
1959 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1960 
1961 		return actualDpi_;
1962 	}
1963 
1964 	private int actualDpi_;
1965 	private bool actualDpiLoadAttempted;
1966 
1967 	version(X11) private {
1968 		bool requestedInput;
1969 		static bool xRandrInfoLoadAttemped;
1970 		struct MonitorInfo {
1971 			Rectangle position;
1972 			Size size;
1973 			int dpi;
1974 
1975 			static MonitorInfo[] info;
1976 		}
1977 		bool screenPositionKnown;
1978 		int screenPositionX;
1979 		int screenPositionY;
1980 		void updateActualDpi(bool loadingNow = false) {
1981 			if(!loadingNow && !actualDpiLoadAttempted)
1982 				actualDpi(); // just to make it do the load
1983 			foreach(idx, m; MonitorInfo.info) {
1984 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1985 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1986 					actualDpi_ = m.dpi;
1987 					// writeln("monitor ", idx);
1988 					if(changed && onDpiChanged)
1989 						onDpiChanged();
1990 					break;
1991 				}
1992 			}
1993 		}
1994 	}
1995 
1996 	/++
1997 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
1998 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
1999 
2000 		History:
2001 			Added November 26, 2021 (dub v10.4)
2002 
2003 		See_Also:
2004 			[actualDpi]
2005 	+/
2006 	void delegate() onDpiChanged;
2007 
2008 	version(X11) {
2009 		void recreateAfterDisconnect() {
2010 			if(!stateDiscarded) return;
2011 
2012 			if(_parent !is null && _parent.stateDiscarded)
2013 				_parent.recreateAfterDisconnect();
2014 
2015 			bool wasHidden = hidden;
2016 
2017 			activeScreenPainter = null; // should already be done but just to confirm
2018 
2019 			actualDpi_ = 0;
2020 			actualDpiLoadAttempted = false;
2021 			xRandrInfoLoadAttemped = false;
2022 
2023 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2024 
2025 			if(auto dh = dropHandler) {
2026 				dropHandler = null;
2027 				enableDragAndDrop(this, dh);
2028 			}
2029 
2030 			if(recreateAdditionalConnectionState)
2031 				recreateAdditionalConnectionState();
2032 
2033 			hidden = wasHidden;
2034 			stateDiscarded = false;
2035 		}
2036 
2037 		bool stateDiscarded;
2038 		void discardConnectionState() {
2039 			if(XDisplayConnection.display)
2040 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2041 			if(discardAdditionalConnectionState)
2042 				discardAdditionalConnectionState();
2043 			stateDiscarded = true;
2044 		}
2045 
2046 		void delegate() discardAdditionalConnectionState;
2047 		void delegate() recreateAdditionalConnectionState;
2048 
2049 	}
2050 
2051 	private DropHandler dropHandler;
2052 
2053 	SimpleWindow _parent;
2054 	bool beingOpenKeepsAppOpen = true;
2055 	/++
2056 		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.
2057 
2058 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2059 
2060 		Params:
2061 
2062 		width = the width of the window's client area, in pixels
2063 		height = the height of the window's client area, in pixels
2064 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2065 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2066 		resizable = [Resizability] has three options:
2067 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2068 			$(P `fixedSize` will not allow the user to resize the window.)
2069 			$(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.)
2070 		windowType = The type of window you want to make.
2071 		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.
2072 		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".
2073 	+/
2074 	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) {
2075 		claimGuiThread();
2076 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2077 		this._width = this._virtualWidth = width;
2078 		this._height = this._virtualHeight = height;
2079 		this.openglMode = opengl;
2080 		version(X11) {
2081 			// auto scale not implemented except with opengl and even there it is kinda weird
2082 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2083 				resizable = Resizability.fixedSize;
2084 		}
2085 		this.resizability = resizable;
2086 		this.windowType = windowType;
2087 		this.customizationFlags = customizationFlags;
2088 		this._title = (title is null ? "D Application" : title);
2089 		this._parent = parent;
2090 		impl.createWindow(width, height, this._title, opengl, parent);
2091 
2092 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2093 			beingOpenKeepsAppOpen = false;
2094 	}
2095 
2096 	/// ditto
2097 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2098 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2099 	}
2100 
2101 	/// Same as above, except using the `Size` struct instead of separate width and height.
2102 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2103 		this(size.width, size.height, title, opengl, resizable);
2104 	}
2105 
2106 	/// ditto
2107 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2108 		this(size, title, opengl, resizable);
2109 	}
2110 
2111 
2112 	/++
2113 		Creates a window based on the given [Image]. It's client area
2114 		width and height is equal to the image. (A window's client area
2115 		is the drawable space inside; it excludes the title bar, etc.)
2116 
2117 		Windows based on images will not be resizable and do not use OpenGL.
2118 
2119 		It will draw the image in upon creation, but this will be overwritten
2120 		upon any draws, including the initial window visible event.
2121 
2122 		You probably do not want to use this and it may be removed from
2123 		the library eventually, or I might change it to be a "permanent"
2124 		background image; one that is automatically drawn on it before any
2125 		other drawing event. idk.
2126 	+/
2127 	this(Image image, string title = null) {
2128 		this(image.width, image.height, title);
2129 		this.image = image;
2130 	}
2131 
2132 	/++
2133 		Wraps a native window handle with very little additional processing - notably no destruction
2134 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2135 		windows created through the low level API (so you can use platform-specific options and
2136 		other details SimpleWindow does not expose) available to the event loop wrappers.
2137 	+/
2138 	this(NativeWindowHandle nativeWindow) {
2139 		windowType = WindowTypes.minimallyWrapped;
2140 		version(Windows)
2141 			impl.hwnd = nativeWindow;
2142 		else version(X11) {
2143 			impl.window = nativeWindow;
2144 			if(nativeWindow)
2145 				display = XDisplayConnection.get(); // get initial display to not segfault
2146 		} else version(OSXCocoa)
2147 			throw new NotYetImplementedException();
2148 		else featureNotImplemented();
2149 		// FIXME: set the size correctly
2150 		_width = 1;
2151 		_height = 1;
2152 		if(nativeWindow)
2153 			nativeMapping[nativeWindow] = this;
2154 
2155 		beingOpenKeepsAppOpen = false;
2156 
2157 		if(nativeWindow)
2158 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2159 		_suppressDestruction = true; // so it doesn't try to close
2160 	}
2161 
2162 	/++
2163 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2164 		The delegate will be called when the window manager asks you to take focus.
2165 
2166 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2167 
2168 		History:
2169 			Added April 1, 2022 (dub v10.8)
2170 	+/
2171 	SimpleWindow delegate() setRequestedInputFocus;
2172 
2173 	/// Experimental, do not use yet
2174 	/++
2175 		Grabs exclusive input from the user until you release it with
2176 		[releaseInputGrab].
2177 
2178 
2179 		Note: it is extremely rude to do this without good reason.
2180 		Reasons may include doing some kind of mouse drag operation
2181 		or popping up a temporary menu that should get events and will
2182 		be dismissed at ease by the user clicking away.
2183 
2184 		Params:
2185 			keyboard = do you want to grab keyboard input?
2186 			mouse = grab mouse input?
2187 			confine = confine the mouse cursor to inside this window?
2188 
2189 		History:
2190 			Prior to March 11, 2021, grabbing the keyboard would always also
2191 			set the X input focus. Now, it only focuses if it is a non-transient
2192 			window and otherwise manages the input direction internally.
2193 
2194 			This means spurious focus/blur events will no longer be sent and the
2195 			application will not steal focus from other applications (which the
2196 			window manager may have rejected anyway).
2197 	+/
2198 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2199 		static if(UsingSimpledisplayX11) {
2200 			XSync(XDisplayConnection.get, 0);
2201 			if(keyboard) {
2202 				if(isTransient && _parent) {
2203 					/*
2204 					FIXME:
2205 						setting the keyboard focus is not actually that helpful, what I more likely want
2206 						is the events from the parent window to be sent over here if we're transient.
2207 					*/
2208 
2209 					_parent.inputProxy = this;
2210 				} else {
2211 
2212 					SimpleWindow setTo;
2213 					if(setRequestedInputFocus !is null)
2214 						setTo = setRequestedInputFocus();
2215 					if(setTo is null)
2216 						setTo = this;
2217 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2218 				}
2219 			}
2220 			if(mouse) {
2221 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2222 				EventMask.PointerMotionMask // FIXME: not efficient
2223 				| EventMask.ButtonPressMask
2224 				| EventMask.ButtonReleaseMask
2225 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2226 				)
2227 			{
2228 				XSync(XDisplayConnection.get, 0);
2229 				import core.stdc.stdio;
2230 				printf("Grab input failed %d\n", res);
2231 				//throw new Exception("Grab input failed");
2232 			} else {
2233 				// cool
2234 			}
2235 			}
2236 
2237 		} else version(Windows) {
2238 			// FIXME: keyboard?
2239 			SetCapture(impl.hwnd);
2240 			if(confine) {
2241 				RECT rcClip;
2242 				//RECT rcOldClip;
2243 				//GetClipCursor(&rcOldClip);
2244 				GetWindowRect(hwnd, &rcClip);
2245 				ClipCursor(&rcClip);
2246 			}
2247 		} else version(OSXCocoa) {
2248 			throw new NotYetImplementedException();
2249 		} else static assert(0);
2250 	}
2251 
2252 	private Point imePopupLocation = Point(0, 0);
2253 
2254 	/++
2255 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2256 
2257 		Bugs:
2258 			Not implemented outside X11.
2259 	+/
2260 	void setIMEPopupLocation(Point location) {
2261 		static if(UsingSimpledisplayX11) {
2262 			imePopupLocation = location;
2263 			updateIMEPopupLocation();
2264 		} else {
2265 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2266 			// throw new NotYetImplementedException();
2267 		}
2268 	}
2269 
2270 	/// ditto
2271 	void setIMEPopupLocation(int x, int y) {
2272 		return setIMEPopupLocation(Point(x, y));
2273 	}
2274 
2275 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2276 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2277 	// receives a ConfigureNotify event
2278 	private void updateIMEPopupLocation() {
2279 		static if(UsingSimpledisplayX11) {
2280 			if (xic is null) {
2281 				return;
2282 			}
2283 
2284 			XPoint nspot;
2285 			nspot.x = cast(short) imePopupLocation.x;
2286 			nspot.y = cast(short) imePopupLocation.y;
2287 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2288 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2289 			XFree(preeditAttr);
2290 		}
2291 	}
2292 
2293 	private bool imeFocused = true;
2294 
2295 	/++
2296 		Tells the IME whether or not an input field is currently focused in the window.
2297 
2298 		Bugs:
2299 			Not implemented outside X11.
2300 	+/
2301 	void setIMEFocused(bool value) {
2302 		imeFocused = value;
2303 		updateIMEFocused();
2304 	}
2305 
2306 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2307 	private void updateIMEFocused() {
2308 		static if(UsingSimpledisplayX11) {
2309 			if (xic is null) {
2310 				return;
2311 			}
2312 
2313 			if (focused && imeFocused) {
2314 				XSetICFocus(xic);
2315 			} else {
2316 				XUnsetICFocus(xic);
2317 			}
2318 		}
2319 	}
2320 
2321 	/++
2322 		Returns the native window.
2323 
2324 		History:
2325 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2326 			to access it through the `impl` member (which is semi-supported
2327 			but platform specific and here it is simple enough to offer an accessor).
2328 
2329 		Bugs:
2330 			Not implemented outside Windows or X11.
2331 	+/
2332 	NativeWindowHandle nativeWindowHandle() {
2333 		version(X11)
2334 			return impl.window;
2335 		else version(Windows)
2336 			return impl.hwnd;
2337 		else
2338 			throw new NotYetImplementedException();
2339 	}
2340 
2341 	private bool isTransient() {
2342 		with(WindowTypes)
2343 		final switch(windowType) {
2344 			case normal, undecorated, eventOnly:
2345 			case nestedChild, minimallyWrapped:
2346 				return (customizationFlags & WindowFlags.transient) ? true : false;
2347 			case dropdownMenu, popupMenu, notification:
2348 				return true;
2349 		}
2350 	}
2351 
2352 	private SimpleWindow inputProxy;
2353 
2354 	/++
2355 		Releases the grab acquired by [grabInput].
2356 	+/
2357 	void releaseInputGrab() {
2358 		static if(UsingSimpledisplayX11) {
2359 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2360 			if(_parent)
2361 				_parent.inputProxy = null;
2362 		} else version(Windows) {
2363 			ReleaseCapture();
2364 			ClipCursor(null);
2365 		} else version(OSXCocoa) {
2366 			throw new NotYetImplementedException();
2367 		} else static assert(0);
2368 	}
2369 
2370 	/++
2371 		Sets the input focus to this window.
2372 
2373 		You shouldn't call this very often - please let the user control the input focus.
2374 	+/
2375 	void focus() {
2376 		static if(UsingSimpledisplayX11) {
2377 			SimpleWindow setTo;
2378 			if(setRequestedInputFocus !is null)
2379 				setTo = setRequestedInputFocus();
2380 			if(setTo is null)
2381 				setTo = this;
2382 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2383 		} else version(Windows) {
2384 			SetFocus(this.impl.hwnd);
2385 		} else version(OSXCocoa) {
2386 			throw new NotYetImplementedException();
2387 		} else static assert(0);
2388 	}
2389 
2390 	/++
2391 		Requests attention from the user for this window.
2392 
2393 
2394 		The typical result of this function is to change the color
2395 		of the taskbar icon, though it may be tweaked on specific
2396 		platforms.
2397 
2398 		It is meant to unobtrusively tell the user that something
2399 		relevant to them happened in the background and they should
2400 		check the window when they get a chance. Upon receiving the
2401 		keyboard focus, the window will automatically return to its
2402 		natural state.
2403 
2404 		If the window already has the keyboard focus, this function
2405 		may do nothing, because the user is presumed to already be
2406 		giving the window attention.
2407 
2408 		Implementation_note:
2409 
2410 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2411 		atom on X11 and the FlashWindow function on Windows.
2412 	+/
2413 	void requestAttention() {
2414 		if(_focused)
2415 			return;
2416 
2417 		version(Windows) {
2418 			FLASHWINFO info;
2419 			info.cbSize = info.sizeof;
2420 			info.hwnd = impl.hwnd;
2421 			info.dwFlags = FLASHW_TRAY;
2422 			info.uCount = 1;
2423 
2424 			FlashWindowEx(&info);
2425 
2426 		} else version(X11) {
2427 			demandingAttention = true;
2428 			demandAttention(this, true);
2429 		} else version(OSXCocoa) {
2430 			throw new NotYetImplementedException();
2431 		} else static assert(0);
2432 	}
2433 
2434 	private bool _focused;
2435 
2436 	version(X11) private bool demandingAttention;
2437 
2438 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2439 	/// You'll have to call `close()` manually if you set this delegate.
2440 	void delegate () closeQuery;
2441 
2442 	/// This will be called when window visibility was changed.
2443 	void delegate (bool becomesVisible) visibilityChanged;
2444 
2445 	/// This will be called when window becomes visible for the first time.
2446 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2447 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2448 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2449 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2450 	private bool _visibleForTheFirstTimeCalled;
2451 	void delegate () visibleForTheFirstTime;
2452 
2453 	/// Returns true if the window has been closed.
2454 	final @property bool closed() { return _closed; }
2455 
2456 	private final @property bool notClosed() { return !_closed; }
2457 
2458 	/// Returns true if the window is focused.
2459 	final @property bool focused() { return _focused; }
2460 
2461 	private bool _visible;
2462 	/// Returns true if the window is visible (mapped).
2463 	final @property bool visible() { return _visible; }
2464 
2465 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2466 	void close() {
2467 		if (!_closed) {
2468 			runInGuiThread( {
2469 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2470 				if (onClosing !is null) onClosing();
2471 				impl.closeWindow();
2472 				_closed = true;
2473 			} );
2474 		}
2475 	}
2476 
2477 	/++
2478 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2479 
2480 		History:
2481 			Overload added on March 7, 2021.
2482 	+/
2483 	void close() shared {
2484 		(cast() this).close();
2485 	}
2486 
2487 	/++
2488 
2489 	+/
2490 	void maximize() {
2491 		version(Windows)
2492 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2493 		else version(X11) {
2494 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2495 
2496 			// also note _NET_WM_STATE_FULLSCREEN
2497 		}
2498 
2499 	}
2500 
2501 	private bool _fullscreen;
2502 	version(Windows)
2503 	private WINDOWPLACEMENT g_wpPrev;
2504 
2505 	/// not fully implemented but planned for a future release
2506 	void fullscreen(bool yes) {
2507 		version(Windows) {
2508 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2509 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2510 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2511 				MONITORINFO mi;
2512 				mi.cbSize = MONITORINFO.sizeof;
2513 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2514 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2515 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2516 					SetWindowLong(hwnd, GWL_STYLE,
2517 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2518 					SetWindowPos(hwnd, HWND_TOP,
2519 						     mi.rcMonitor.left, mi.rcMonitor.top,
2520 						     mi.rcMonitor.right - mi.rcMonitor.left,
2521 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2522 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2523 				}
2524 			} else {
2525 				SetWindowLong(hwnd, GWL_STYLE,
2526 					      dwStyle | WS_OVERLAPPEDWINDOW);
2527 				SetWindowPlacement(hwnd, &g_wpPrev);
2528 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2529 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2530 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2531 			}
2532 
2533 		} else version(X11) {
2534 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2535 		}
2536 
2537 		_fullscreen = yes;
2538 
2539 	}
2540 
2541 	bool fullscreen() {
2542 		return _fullscreen;
2543 	}
2544 
2545 	/++
2546 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2547 
2548 	+/
2549 	void minimize() {
2550 		version(Windows)
2551 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2552 		//else version(X11)
2553 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2554 	}
2555 
2556 	/// Alias for `hidden = false`
2557 	void show() {
2558 		hidden = false;
2559 	}
2560 
2561 	/// Alias for `hidden = true`
2562 	void hide() {
2563 		hidden = true;
2564 	}
2565 
2566 	/// Hide cursor when it enters the window.
2567 	void hideCursor() {
2568 		version(OSXCocoa) throw new NotYetImplementedException(); else
2569 		if (!_closed) impl.hideCursor();
2570 	}
2571 
2572 	/// Don't hide cursor when it enters the window.
2573 	void showCursor() {
2574 		version(OSXCocoa) throw new NotYetImplementedException(); else
2575 		if (!_closed) impl.showCursor();
2576 	}
2577 
2578 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2579 	 *
2580 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2581 	 * control. Try to think for other approaches before using this function.
2582 	 *
2583 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2584 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2585 	 *       receive "mouse moved here" event.
2586 	 */
2587 	bool warpMouse (int x, int y) {
2588 		version(X11) {
2589 			if (!_closed) { impl.warpMouse(x, y); return true; }
2590 		} else version(Windows) {
2591 			if (!_closed) {
2592 				POINT point;
2593 				point.x = x;
2594 				point.y = y;
2595 				if(ClientToScreen(impl.hwnd, &point)) {
2596 					SetCursorPos(point.x, point.y);
2597 					return true;
2598 				}
2599 			}
2600 		}
2601 		return false;
2602 	}
2603 
2604 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2605 	void sendDummyEvent () {
2606 		version(X11) {
2607 			if (!_closed) { impl.sendDummyEvent(); }
2608 		}
2609 	}
2610 
2611 	/// Set window minimal size.
2612 	void setMinSize (int minwidth, int minheight) {
2613 		version(OSXCocoa) throw new NotYetImplementedException(); else
2614 		if (!_closed) impl.setMinSize(minwidth, minheight);
2615 	}
2616 
2617 	/// Set window maximal size.
2618 	void setMaxSize (int maxwidth, int maxheight) {
2619 		version(OSXCocoa) throw new NotYetImplementedException(); else
2620 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2621 	}
2622 
2623 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2624 	/// Currently only supported on X11.
2625 	void setResizeGranularity (int granx, int grany) {
2626 		version(OSXCocoa) throw new NotYetImplementedException(); else
2627 		if (!_closed) impl.setResizeGranularity(granx, grany);
2628 	}
2629 
2630 	/// Move window.
2631 	void move(int x, int y) {
2632 		version(OSXCocoa) throw new NotYetImplementedException(); else
2633 		if (!_closed) impl.move(x, y);
2634 	}
2635 
2636 	/// ditto
2637 	void move(Point p) {
2638 		version(OSXCocoa) throw new NotYetImplementedException(); else
2639 		if (!_closed) impl.move(p.x, p.y);
2640 	}
2641 
2642 	/++
2643 		Resize window.
2644 
2645 		Note that the width and height of the window are NOT instantly
2646 		updated - it waits for the window manager to approve the resize
2647 		request, which means you must return to the event loop before the
2648 		width and height are actually changed.
2649 	+/
2650 	void resize(int w, int h) {
2651 		if(!_closed && _fullscreen) fullscreen = false;
2652 		version(OSXCocoa) throw new NotYetImplementedException(); else
2653 		if (!_closed) impl.resize(w, h);
2654 	}
2655 
2656 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2657 	void moveResize (int x, int y, int w, int h) {
2658 		if(!_closed && _fullscreen) fullscreen = false;
2659 		version(OSXCocoa) throw new NotYetImplementedException(); else
2660 		if (!_closed) impl.moveResize(x, y, w, h);
2661 	}
2662 
2663 	private bool _hidden;
2664 
2665 	/// Returns true if the window is hidden.
2666 	final @property bool hidden() {
2667 		return _hidden;
2668 	}
2669 
2670 	/// Shows or hides the window based on the bool argument.
2671 	final @property void hidden(bool b) {
2672 		_hidden = b;
2673 		version(Windows) {
2674 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2675 		} else version(X11) {
2676 			if(b)
2677 				//XUnmapWindow(impl.display, impl.window);
2678 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2679 			else
2680 				XMapWindow(impl.display, impl.window);
2681 		} else version(OSXCocoa) {
2682 			throw new NotYetImplementedException();
2683 		} else static assert(0);
2684 	}
2685 
2686 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2687 	void opacity(double opacity) @property
2688 	in {
2689 		assert(opacity >= 0 && opacity <= 1);
2690 	} do {
2691 		version (Windows) {
2692 			impl.setOpacity(cast(ubyte)(255 * opacity));
2693 		} else version (X11) {
2694 			impl.setOpacity(cast(uint)(uint.max * opacity));
2695 		} else throw new NotYetImplementedException();
2696 	}
2697 
2698 	/++
2699 		Sets your event handlers, without entering the event loop. Useful if you
2700 		have multiple windows - set the handlers on each window, then only do
2701 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2702 
2703 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2704 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2705 		delegate signatures.
2706 	+/
2707 	void setEventHandlers(T...)(T eventHandlers) {
2708 		// FIXME: add more events
2709 		foreach(handler; eventHandlers) {
2710 			static if(__traits(compiles, handleKeyEvent = handler)) {
2711 				handleKeyEvent = handler;
2712 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2713 				handleCharEvent = handler;
2714 			} else static if(__traits(compiles, handlePulse = handler)) {
2715 				handlePulse = handler;
2716 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2717 				handleMouseEvent = handler;
2718 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2719 		}
2720 	}
2721 
2722 	/++
2723 		The event loop automatically returns when the window is closed
2724 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2725 		pulse timer is created. The event loop will block until an event
2726 		arrives or the pulse timer goes off.
2727 
2728 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2729 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2730 		[handleMouseEvent], based on the signature of delegates you provide.
2731 
2732 		Give one with no parameters to set a timer pulse handler. Give one that
2733 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2734 		and one that takes `dchar` for a char event handler. You can use as many
2735 		or as few handlers as you need for your application.
2736 
2737 		Bugs:
2738 
2739 		$(PITFALL
2740 			You should always have one event loop live for your application.
2741 			If you make two windows in sequence, the second call to eventLoop
2742 			might fail:
2743 
2744 			---
2745 			// don't do this!
2746 			auto window = new SimpleWindow();
2747 			window.eventLoop(0);
2748 
2749 			auto window2 = new SimpleWindow();
2750 			window2.eventLoop(0); // problematic! might crash
2751 			---
2752 
2753 			simpledisplay's current implementation assumes that final cleanup is
2754 			done when the event loop refcount reaches zero. So after the first
2755 			eventLoop returns, when there isn't already another one active, it assumes
2756 			the program will exit soon and cleans up.
2757 
2758 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2759 			it eventually, but in the mean time, there's an easy solution:
2760 
2761 			---
2762 			// do this
2763 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2764 
2765 			auto window = new SimpleWindow();
2766 			window.eventLoop(0);
2767 
2768 			auto window2 = new SimpleWindow();
2769 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2770 			---
2771 
2772 			By adding a top-level reference to the event loop, it ensures the final cleanup
2773 			is not performed until it goes out of scope too, letting the individual window loops
2774 			work without trouble despite the bug.
2775 		)
2776 
2777 		History:
2778 			The overload without `pulseTimeout` was added on December 8, 2021.
2779 
2780 			On December 9, 2021, the default blocking mode (which is now configurable
2781 			because [eventLoopWithBlockingMode] was added) switched from
2782 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2783 			should almost never be noticeable to you since the typical simpledisplay
2784 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2785 
2786 		See_Also:
2787 			[eventLoopWithBlockingMode]
2788 	+/
2789 	final int eventLoop(T...)(
2790 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2791 		T eventHandlers) /// delegate list like std.concurrency.receive
2792 	{
2793 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2794 	}
2795 
2796 	/// ditto
2797 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2798 	{
2799 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2800 	}
2801 
2802 	/++
2803 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2804 
2805 		History:
2806 			Added December 8, 2021 (dub v10.5)
2807 
2808 			Previously, this implementation was right inside [eventLoop], but when I wanted
2809 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2810 			just renamed it instead of adding as an overload. Besides, the new name makes it
2811 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2812 
2813 		See_Also:
2814 			[SimpleWindow.eventLoop], [EventLoop]
2815 
2816 		Bugs:
2817 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2818 	+/
2819 	final int eventLoopWithBlockingMode(T...)(
2820 		BlockingMode blockingMode, /// when you want this function to block until
2821 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2822 		T eventHandlers) /// delegate list like std.concurrency.receive
2823 	{
2824 		setEventHandlers(eventHandlers);
2825 
2826 		version(with_eventloop) {
2827 			// delegates event loop to my other module
2828 			version(X11)
2829 				XFlush(display);
2830 
2831 			import arsd.eventloop;
2832 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2833 			scope(exit) clearInterval(handle);
2834 
2835 			loop();
2836 			return 0;
2837 		} else version(OSXCocoa) {
2838 			// FIXME
2839 			if (handlePulse !is null && pulseTimeout != 0) {
2840 				timer = scheduledTimer(pulseTimeout*1e-3,
2841 					view, sel_registerName("simpledisplay_pulse"),
2842 					null, true);
2843 			}
2844 
2845             		setNeedsDisplay(view, true);
2846             		run(NSApp);
2847             		return 0;
2848         	} else {
2849 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2850 
2851 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2852 				return 0;
2853 
2854 			return el.run(
2855 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2856 					null :
2857 					&this.notClosed
2858 			);
2859 		}
2860 	}
2861 
2862 	/++
2863 		This lets you draw on the window (or its backing buffer) using basic
2864 		2D primitives.
2865 
2866 		Be sure to call this in a limited scope because your changes will not
2867 		actually appear on the window until ScreenPainter's destructor runs.
2868 
2869 		Returns: an instance of [ScreenPainter], which has the drawing methods
2870 		on it to draw on this window.
2871 
2872 		Params:
2873 			manualInvalidations = if you set this to true, you will need to
2874 			set the invalid rectangle on the painter yourself. If false, it
2875 			assumes the whole window has been redrawn each time you draw.
2876 
2877 			Only invalidated rectangles are blitted back to the window when
2878 			the destructor runs. Doing this yourself can reduce flickering
2879 			of child windows.
2880 
2881 		History:
2882 			The `manualInvalidations` parameter overload was added on
2883 			December 30, 2021 (dub v10.5)
2884 	+/
2885 	ScreenPainter draw() {
2886 		return draw(false);
2887 	}
2888 	/// ditto
2889 	ScreenPainter draw(bool manualInvalidations) {
2890 		return impl.getPainter(manualInvalidations);
2891 	}
2892 
2893 	// This is here to implement the interface we use for various native handlers.
2894 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2895 
2896 	// maps native window handles to SimpleWindow instances, if there are any
2897 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2898 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2899 
2900 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
2901 	private int _virtualWidth;
2902 	private int _virtualHeight;
2903 
2904 	/// Width of the window's drawable client area, in pixels.
2905 	@scriptable
2906 	final @property int width() const pure nothrow @safe @nogc {
2907 		if(resizability == Resizability.automaticallyScaleIfPossible)
2908 			return _virtualWidth;
2909 		else
2910 			return _width;
2911 	}
2912 
2913 	/// Height of the window's drawable client area, in pixels.
2914 	@scriptable
2915 	final @property int height() const pure nothrow @safe @nogc {
2916 		if(resizability == Resizability.automaticallyScaleIfPossible)
2917 			return _virtualHeight;
2918 		else
2919 			return _height;
2920 	}
2921 
2922 	/++
2923 		Returns the actual size of the window, bypassing the logical
2924 		illusions of [Resizability.automaticallyScaleIfPossible].
2925 
2926 		History:
2927 			Added November 11, 2022 (dub v10.10)
2928 	+/
2929 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
2930 		return Size(_width, _height);
2931 	}
2932 
2933 
2934 	private int _width;
2935 	private int _height;
2936 
2937 	// HACK: making the best of some copy constructor woes with refcounting
2938 	private ScreenPainterImplementation* activeScreenPainter_;
2939 
2940 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2941 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2942 
2943 	private OpenGlOptions openglMode;
2944 	private Resizability resizability;
2945 	private WindowTypes windowType;
2946 	private int customizationFlags;
2947 
2948 	/// `true` if OpenGL was initialized for this window.
2949 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2950 		version(without_opengl)
2951 			return false;
2952 		else
2953 			return (openglMode == OpenGlOptions.yes);
2954 	}
2955 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2956 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2957 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2958 
2959 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2960 	/// to call this, as it's not recommended to share window between threads.
2961 	void mtLock () {
2962 		version(X11) {
2963 			XLockDisplay(this.display);
2964 		}
2965 	}
2966 
2967 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2968 	/// to call this, as it's not recommended to share window between threads.
2969 	void mtUnlock () {
2970 		version(X11) {
2971 			XUnlockDisplay(this.display);
2972 		}
2973 	}
2974 
2975 	/// Emit a beep to get user's attention.
2976 	void beep () {
2977 		version(X11) {
2978 			XBell(this.display, 100);
2979 		} else version(Windows) {
2980 			MessageBeep(0xFFFFFFFF);
2981 		}
2982 	}
2983 
2984 
2985 
2986 	version(without_opengl) {} else {
2987 
2988 		/// 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`.
2989 		void delegate() redrawOpenGlScene;
2990 
2991 		/// This will allow you to change OpenGL vsync state.
2992 		final @property void vsync (bool wait) {
2993 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2994 		  version(X11) {
2995 		    setAsCurrentOpenGlContext();
2996 		    glxSetVSync(display, impl.window, wait);
2997 		  } else version(Windows) {
2998 		    setAsCurrentOpenGlContext();
2999                     wglSetVSync(wait);
3000 		  }
3001 		}
3002 
3003 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3004 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3005 		/// enough without waiting 'em to finish their frame business.
3006 		bool useGLFinish = true;
3007 
3008 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3009 		/// 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.
3010 		void redrawOpenGlSceneNow() {
3011 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3012 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3013 			if(redrawOpenGlScene is null)
3014 				return;
3015 
3016 			this.mtLock();
3017 			scope(exit) this.mtUnlock();
3018 
3019 			this.setAsCurrentOpenGlContext();
3020 
3021 			redrawOpenGlScene();
3022 
3023 			this.swapOpenGlBuffers();
3024 			// 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.
3025 			if (useGLFinish) glFinish();
3026 		}
3027 
3028 		private bool redrawOpenGlSceneSoonSet = false;
3029 		private static class RedrawOpenGlSceneEvent {
3030 			SimpleWindow w;
3031 			this(SimpleWindow w) { this.w = w; }
3032 		}
3033 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3034 		/++
3035 			Queues an opengl redraw as soon as the other pending events are cleared.
3036 		+/
3037 		void redrawOpenGlSceneSoon() {
3038 			if(redrawOpenGlScene is null)
3039 				return;
3040 
3041 			if(!redrawOpenGlSceneSoonSet) {
3042 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3043 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3044 				redrawOpenGlSceneSoonSet = true;
3045 			}
3046 			this.postEvent(redrawOpenGlSceneEvent, true);
3047 		}
3048 
3049 
3050 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3051 		void setAsCurrentOpenGlContext() {
3052 			assert(openglMode == OpenGlOptions.yes);
3053 			version(X11) {
3054 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3055 					throw new Exception("glXMakeCurrent");
3056 			} else version(Windows) {
3057 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3058 				if (!wglMakeCurrent(ghDC, ghRC))
3059 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3060 			}
3061 		}
3062 
3063 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3064 		/// This doesn't throw, returning success flag instead.
3065 		bool setAsCurrentOpenGlContextNT() nothrow {
3066 			assert(openglMode == OpenGlOptions.yes);
3067 			version(X11) {
3068 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3069 			} else version(Windows) {
3070 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3071 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3072 			}
3073 		}
3074 
3075 		/// 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.
3076 		/// This doesn't throw, returning success flag instead.
3077 		bool releaseCurrentOpenGlContext() nothrow {
3078 			assert(openglMode == OpenGlOptions.yes);
3079 			version(X11) {
3080 				return (glXMakeCurrent(display, 0, null) != 0);
3081 			} else version(Windows) {
3082 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3083 				return wglMakeCurrent(ghDC, null) ? true : false;
3084 			}
3085 		}
3086 
3087 		/++
3088 			simpledisplay always uses double buffering, usually automatically. This
3089 			manually swaps the OpenGL buffers.
3090 
3091 
3092 			You should not need to call this yourself because simpledisplay will do it
3093 			for you after calling your `redrawOpenGlScene`.
3094 
3095 			Remember that this may throw an exception, which you can catch in a multithreaded
3096 			application to keep your thread from dying from an unhandled exception.
3097 		+/
3098 		void swapOpenGlBuffers() {
3099 			assert(openglMode == OpenGlOptions.yes);
3100 			version(X11) {
3101 				if (!this._visible) return; // no need to do this if window is invisible
3102 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3103 				glXSwapBuffers(display, impl.window);
3104 			} else version(Windows) {
3105 				SwapBuffers(ghDC);
3106 			}
3107 		}
3108 	}
3109 
3110 	/++
3111 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3112 
3113 
3114 		---
3115 			auto window = new SimpleWindow(100, 100, "First title");
3116 			window.title = "A new title";
3117 		---
3118 
3119 		You may call this function at any time.
3120 	+/
3121 	@property void title(string title) {
3122 		_title = title;
3123 		version(OSXCocoa) throw new NotYetImplementedException(); else
3124 		impl.setTitle(title);
3125 	}
3126 
3127 	private string _title;
3128 
3129 	/// Gets the title
3130 	@property string title() {
3131 		if(_title is null)
3132 			_title = getRealTitle();
3133 		return _title;
3134 	}
3135 
3136 	/++
3137 		Get the title as set by the window manager.
3138 		May not match what you attempted to set.
3139 	+/
3140 	string getRealTitle() {
3141 		static if(is(typeof(impl.getTitle())))
3142 			return impl.getTitle();
3143 		else
3144 			return null;
3145 	}
3146 
3147 	// don't use this generally it is not yet really released
3148 	version(X11)
3149 	@property Image secret_icon() {
3150 		return secret_icon_inner;
3151 	}
3152 	private Image secret_icon_inner;
3153 
3154 
3155 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3156 	@property void icon(MemoryImage icon) {
3157 		if(icon is null)
3158 			return;
3159 		auto tci = icon.getAsTrueColorImage();
3160 		version(Windows) {
3161 			winIcon = new WindowsIcon(icon);
3162 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3163 		} else version(X11) {
3164 			secret_icon_inner = Image.fromMemoryImage(icon);
3165 			// FIXME: ensure this is correct
3166 			auto display = XDisplayConnection.get;
3167 			arch_ulong[] buffer;
3168 			buffer ~= icon.width;
3169 			buffer ~= icon.height;
3170 			foreach(c; tci.imageData.colors) {
3171 				arch_ulong b;
3172 				b |= c.a << 24;
3173 				b |= c.r << 16;
3174 				b |= c.g << 8;
3175 				b |= c.b;
3176 				buffer ~= b;
3177 			}
3178 
3179 			XChangeProperty(
3180 				display,
3181 				impl.window,
3182 				GetAtom!("_NET_WM_ICON", true)(display),
3183 				GetAtom!"CARDINAL"(display),
3184 				32 /* bits */,
3185 				0 /*PropModeReplace*/,
3186 				buffer.ptr,
3187 				cast(int) buffer.length);
3188 		} else version(OSXCocoa) {
3189 			throw new NotYetImplementedException();
3190 		} else static assert(0);
3191 	}
3192 
3193 	version(Windows)
3194 		private WindowsIcon winIcon;
3195 
3196 	bool _suppressDestruction;
3197 
3198 	~this() {
3199 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3200 		if(_suppressDestruction)
3201 			return;
3202 		impl.dispose();
3203 	}
3204 
3205 	private bool _closed;
3206 
3207 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3208 	/*
3209 	ScreenPainter drawTransiently() {
3210 		return impl.getPainter();
3211 	}
3212 	*/
3213 
3214 	/// Draws an image on the window. This is meant to provide quick look
3215 	/// of a static image generated elsewhere.
3216 	@property void image(Image i) {
3217 	/+
3218 		version(Windows) {
3219 			BITMAP bm;
3220 			HDC hdc = GetDC(hwnd);
3221 			HDC hdcMem = CreateCompatibleDC(hdc);
3222 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3223 
3224 			GetObject(i.handle, bm.sizeof, &bm);
3225 
3226 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3227 
3228 			SelectObject(hdcMem, hbmOld);
3229 			DeleteDC(hdcMem);
3230 			ReleaseDC(hwnd, hdc);
3231 
3232 			/*
3233 			RECT r;
3234 			r.right = i.width;
3235 			r.bottom = i.height;
3236 			InvalidateRect(hwnd, &r, false);
3237 			*/
3238 		} else
3239 		version(X11) {
3240 			if(!destroyed) {
3241 				if(i.usingXshm)
3242 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3243 				else
3244 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3245 			}
3246 		} else
3247 		version(OSXCocoa) {
3248 			draw().drawImage(Point(0, 0), i);
3249 			setNeedsDisplay(view, true);
3250 		} else static assert(0);
3251 	+/
3252 		auto painter = this.draw;
3253 		painter.drawImage(Point(0, 0), i);
3254 	}
3255 
3256 	/++
3257 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3258 
3259 		---
3260 		window.cursor = GenericCursor.Help;
3261 		// now the window mouse cursor is set to a generic help
3262 		---
3263 
3264 	+/
3265 	@property void cursor(MouseCursor cursor) {
3266 		version(OSXCocoa)
3267 			featureNotImplemented();
3268 		else
3269 		if(this.impl.curHidden <= 0) {
3270 			static if(UsingSimpledisplayX11) {
3271 				auto ch = cursor.cursorHandle;
3272 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3273 			} else version(Windows) {
3274 				auto ch = cursor.cursorHandle;
3275 				impl.currentCursor = ch;
3276 				SetCursor(ch); // redraw without waiting for mouse movement to update
3277 			} else featureNotImplemented();
3278 		}
3279 
3280 	}
3281 
3282 	/// What follows are the event handlers. These are set automatically
3283 	/// by the eventLoop function, but are still public so you can change
3284 	/// them later. wasPressed == true means key down. false == key up.
3285 
3286 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3287 	void delegate(KeyEvent ke) handleKeyEvent;
3288 
3289 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3290 	void delegate(dchar c) handleCharEvent;
3291 
3292 	/// Handles a timer pulse. Settable through setEventHandlers.
3293 	void delegate() handlePulse;
3294 
3295 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3296 	void delegate(bool) onFocusChange;
3297 
3298 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3299 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3300 	void delegate() onClosing;
3301 
3302 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3303 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3304 	 * last minute cleanup. */
3305 	void delegate() onDestroyed;
3306 
3307 	static if (UsingSimpledisplayX11)
3308 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3309 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3310 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3311 	 *
3312 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3313 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3314 
3315 	//version(Windows)
3316 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3317 
3318 	private {
3319 		int lastMouseX = int.min;
3320 		int lastMouseY = int.min;
3321 		void mdx(ref MouseEvent ev) {
3322 			if(lastMouseX == int.min || lastMouseY == int.min) {
3323 				ev.dx = 0;
3324 				ev.dy = 0;
3325 			} else {
3326 				ev.dx = ev.x - lastMouseX;
3327 				ev.dy = ev.y - lastMouseY;
3328 			}
3329 
3330 			lastMouseX = ev.x;
3331 			lastMouseY = ev.y;
3332 		}
3333 	}
3334 
3335 	/// Mouse event handler. Settable through setEventHandlers.
3336 	void delegate(MouseEvent) handleMouseEvent;
3337 
3338 	/// use to redraw child widgets if you use system apis to add stuff
3339 	void delegate() paintingFinished;
3340 
3341 	void delegate() paintingFinishedDg() {
3342 		return paintingFinished;
3343 	}
3344 
3345 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3346 	/// for this to ever happen.
3347 	void delegate(int width, int height) windowResized;
3348 
3349 	/++
3350 		Platform specific - handle any native message this window gets.
3351 
3352 		Note: this is called *in addition to* other event handlers, unless you either:
3353 
3354 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3355 
3356 		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.
3357 
3358 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3359 
3360 		On X, it takes the form of `int delegate(XEvent)`.
3361 
3362 		History:
3363 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3364 
3365 			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.
3366 	+/
3367 	NativeEventHandler handleNativeEvent_;
3368 
3369 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3370 		return handleNativeEvent_;
3371 	}
3372 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3373 		handleNativeEvent_ = neh;
3374 	}
3375 
3376 	version(Windows)
3377 	// compatibility shim with the old deprecated way
3378 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3379 	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) {
3380 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3381 			auto ret = dg(h, m, w, l);
3382 			if(ret == 0)
3383 				r = 1;
3384 			return ret;
3385 		};
3386 	}
3387 
3388 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3389 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3390 	/// this instead and it will work the same way.
3391 	__gshared NativeEventHandler handleNativeGlobalEvent;
3392 
3393 //  private:
3394 	/// The native implementation is available, but you shouldn't use it unless you are
3395 	/// familiar with the underlying operating system, don't mind depending on it, and
3396 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3397 	/// do what you need to do with handleNativeEvent instead.
3398 	///
3399 	/// This is likely to eventually change to be just a struct holding platform-specific
3400 	/// handles instead of a template mixin at some point because I'm not happy with the
3401 	/// code duplication here (ironically).
3402 	mixin NativeSimpleWindowImplementation!() impl;
3403 
3404 	/**
3405 		This is in-process one-way (from anything to window) event sending mechanics.
3406 		It is thread-safe, so it can be used in multi-threaded applications to send,
3407 		for example, "wake up and repaint" events when thread completed some operation.
3408 		This will allow to avoid using timer pulse to check events with synchronization,
3409 		'cause event handler will be called in UI thread. You can stop guessing which
3410 		pulse frequency will be enough for your app.
3411 		Note that events handlers may be called in arbitrary order, i.e. last registered
3412 		handler can be called first, and vice versa.
3413 	*/
3414 public:
3415 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3416 	 * "spamming" window with events it can't cope with.
3417 	 * It is safe to call this from non-UI threads.
3418 	 */
3419 	@property bool eventQueueEmpty() () {
3420 		synchronized(this) {
3421 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3422 		}
3423 		return true;
3424 	}
3425 
3426 	/** Does our custom event queue contains at least one with the given type?
3427 	 * Can be used in simple cases to prevent "spamming" window with events
3428 	 * it can't cope with.
3429 	 * It is safe to call this from non-UI threads.
3430 	 */
3431 	@property bool eventQueued(ET:Object) () {
3432 		synchronized(this) {
3433 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3434 				if (!o.doProcess) {
3435 					if (cast(ET)(o.evt)) return true;
3436 				}
3437 			}
3438 		}
3439 		return false;
3440 	}
3441 
3442 	/++
3443 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3444 
3445 		History:
3446 			Added May 12, 2021
3447 	+/
3448 	void delegate(Exception e) nothrow eventUncaughtException;
3449 
3450 	/** Add listener for custom event. Can be used like this:
3451 	 *
3452 	 * ---------------------
3453 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3454 	 *   ...
3455 	 *   win.removeEventListener(eid);
3456 	 * ---------------------
3457 	 *
3458 	 * Returns: 0 on failure (should never happen, so ignore it)
3459 	 *
3460 	 * $(WARNING Don't use this method in object destructors!)
3461 	 *
3462 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3463 	 *           'cause if event handler id counter will overflow, you won't be able
3464 	 *           to register any more events.)
3465 	 */
3466 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3467 		if (dg is null) return 0; // ignore empty handlers
3468 		synchronized(this) {
3469 			//FIXME: abort on overflow?
3470 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3471 			EventHandlerEntry e;
3472 			e.dg = delegate (Object o) {
3473 				if (auto co = cast(ET)o) {
3474 					try {
3475 						dg(co);
3476 					} catch (Exception e) {
3477 						// sorry!
3478 						if(eventUncaughtException)
3479 							eventUncaughtException(e);
3480 					}
3481 					return true;
3482 				}
3483 				return false;
3484 			};
3485 			e.id = lastUsedHandlerId;
3486 			auto optr = eventHandlers.ptr;
3487 			eventHandlers ~= e;
3488 			if (eventHandlers.ptr !is optr) {
3489 				import core.memory : GC;
3490 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3491 			}
3492 			return lastUsedHandlerId;
3493 		}
3494 	}
3495 
3496 	/// Remove event listener. It is safe to pass invalid event id here.
3497 	/// $(WARNING Don't use this method in object destructors!)
3498 	void removeEventListener() (uint id) {
3499 		if (id == 0 || id > lastUsedHandlerId) return;
3500 		synchronized(this) {
3501 			foreach (immutable idx; 0..eventHandlers.length) {
3502 				if (eventHandlers[idx].id == id) {
3503 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3504 					eventHandlers[$-1].dg = null;
3505 					eventHandlers.length -= 1;
3506 					eventHandlers.assumeSafeAppend;
3507 					return;
3508 				}
3509 			}
3510 		}
3511 	}
3512 
3513 	/// Post event to queue. It is safe to call this from non-UI threads.
3514 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3515 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3516 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3517 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3518 		if (this.closed) return false; // closed windows can't handle events
3519 
3520 		// remove all events of type `ET`
3521 		void removeAllET () {
3522 			uint eidx = 0, ec = eventQueueUsed;
3523 			auto eptr = eventQueue.ptr;
3524 			while (eidx < ec) {
3525 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3526 				if (cast(ET)eptr.evt !is null) {
3527 					// i found her!
3528 					if (inCustomEventProcessor) {
3529 						// if we're in custom event processing loop, processor will clear it for us
3530 						eptr.evt = null;
3531 						++eidx;
3532 						++eptr;
3533 					} else {
3534 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3535 						ec = --eventQueueUsed;
3536 						// clear last event (it is already copied)
3537 						eventQueue.ptr[ec].evt = null;
3538 					}
3539 				} else {
3540 					++eidx;
3541 					++eptr;
3542 				}
3543 			}
3544 		}
3545 
3546 		if (evt is null) {
3547 			if (replace) { synchronized(this) removeAllET(); }
3548 			// ignore empty events, they can't be handled anyway
3549 			return false;
3550 		}
3551 
3552 		// add events even if no event FD/event object created yet
3553 		synchronized(this) {
3554 			if (replace) removeAllET();
3555 			if (eventQueueUsed == uint.max) return false; // just in case
3556 			if (eventQueueUsed < eventQueue.length) {
3557 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3558 			} else {
3559 				if (eventQueue.capacity == eventQueue.length) {
3560 					// need to reallocate; do a trick to ensure that old array is cleared
3561 					auto oarr = eventQueue;
3562 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3563 					// just in case, do yet another check
3564 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3565 					import core.memory : GC;
3566 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3567 				} else {
3568 					auto optr = eventQueue.ptr;
3569 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3570 					assert(eventQueue.ptr is optr);
3571 				}
3572 				++eventQueueUsed;
3573 				assert(eventQueueUsed == eventQueue.length);
3574 			}
3575 			if (!eventWakeUp()) {
3576 				// can't wake up event processor, so there is no reason to keep the event
3577 				assert(eventQueueUsed > 0);
3578 				eventQueue[--eventQueueUsed].evt = null;
3579 				return false;
3580 			}
3581 			return true;
3582 		}
3583 	}
3584 
3585 	/// Post event to queue. It is safe to call this from non-UI threads.
3586 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3587 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3588 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3589 		return postTimeout!ET(evt, 0, replace);
3590 	}
3591 
3592 private:
3593 	private import core.time : MonoTime;
3594 
3595 	version(Posix) {
3596 		__gshared int customEventFDRead = -1;
3597 		__gshared int customEventFDWrite = -1;
3598 		__gshared int customSignalFD = -1;
3599 	} else version(Windows) {
3600 		__gshared HANDLE customEventH = null;
3601 	}
3602 
3603 	// wake up event processor
3604 	static bool eventWakeUp () {
3605 		version(X11) {
3606 			import core.sys.posix.unistd : write;
3607 			ulong n = 1;
3608 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3609 			return true;
3610 		} else version(Windows) {
3611 			if (customEventH !is null) SetEvent(customEventH);
3612 			return true;
3613 		} else {
3614 			// not implemented for other OSes
3615 			return false;
3616 		}
3617 	}
3618 
3619 	static struct QueuedEvent {
3620 		Object evt;
3621 		bool timed = false;
3622 		MonoTime hittime = MonoTime.zero;
3623 		bool doProcess = false; // process event at the current iteration (internal flag)
3624 
3625 		this (Object aevt, uint toutmsecs) {
3626 			evt = aevt;
3627 			if (toutmsecs > 0) {
3628 				import core.time : msecs;
3629 				timed = true;
3630 				hittime = MonoTime.currTime+toutmsecs.msecs;
3631 			}
3632 		}
3633 	}
3634 
3635 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3636 	static struct EventHandlerEntry {
3637 		CustomEventHandler dg;
3638 		uint id;
3639 	}
3640 
3641 	uint lastUsedHandlerId;
3642 	EventHandlerEntry[] eventHandlers;
3643 	QueuedEvent[] eventQueue = null;
3644 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3645 	bool inCustomEventProcessor = false; // required to properly remove events
3646 
3647 	// process queued events and call custom event handlers
3648 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3649 	void processCustomEvents () {
3650 		bool hasSomethingToDo = false;
3651 		uint ecount;
3652 		bool ocep;
3653 		synchronized(this) {
3654 			ocep = inCustomEventProcessor;
3655 			inCustomEventProcessor = true;
3656 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3657 			auto ctt = MonoTime.currTime;
3658 			bool hasEmpty = false;
3659 			// mark events to process (this is required for `eventQueued()`)
3660 			foreach (ref qe; eventQueue[0..ecount]) {
3661 				if (qe.evt is null) { hasEmpty = true; continue; }
3662 				if (qe.timed) {
3663 					qe.doProcess = (qe.hittime <= ctt);
3664 				} else {
3665 					qe.doProcess = true;
3666 				}
3667 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3668 			}
3669 			if (!hasSomethingToDo) {
3670 				// remove empty events
3671 				if (hasEmpty) {
3672 					uint eidx = 0, ec = eventQueueUsed;
3673 					auto eptr = eventQueue.ptr;
3674 					while (eidx < ec) {
3675 						if (eptr.evt is null) {
3676 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3677 							ec = --eventQueueUsed;
3678 							eventQueue.ptr[ec].evt = null; // make GC life easier
3679 						} else {
3680 							++eidx;
3681 							++eptr;
3682 						}
3683 					}
3684 				}
3685 				inCustomEventProcessor = ocep;
3686 				return;
3687 			}
3688 		}
3689 		// process marked events
3690 		uint efree = 0; // non-processed events will be put at this index
3691 		EventHandlerEntry[] eh;
3692 		Object evt;
3693 		foreach (immutable eidx; 0..ecount) {
3694 			synchronized(this) {
3695 				if (!eventQueue[eidx].doProcess) {
3696 					// skip this event
3697 					assert(efree <= eidx);
3698 					if (efree != eidx) {
3699 						// copy this event to queue start
3700 						eventQueue[efree] = eventQueue[eidx];
3701 						eventQueue[eidx].evt = null; // just in case
3702 					}
3703 					++efree;
3704 					continue;
3705 				}
3706 				evt = eventQueue[eidx].evt;
3707 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3708 				if (evt is null) continue; // just in case
3709 				// try all handlers; this can be slow, but meh...
3710 				eh = eventHandlers;
3711 			}
3712 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3713 			evt = null;
3714 			eh = null;
3715 		}
3716 		synchronized(this) {
3717 			// move all unprocessed events to queue top; efree holds first "free index"
3718 			foreach (immutable eidx; ecount..eventQueueUsed) {
3719 				assert(efree <= eidx);
3720 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3721 				++efree;
3722 			}
3723 			eventQueueUsed = efree;
3724 			// wake up event processor on next event loop iteration if we have more queued events
3725 			// also, remove empty events
3726 			bool awaken = false;
3727 			uint eidx = 0, ec = eventQueueUsed;
3728 			auto eptr = eventQueue.ptr;
3729 			while (eidx < ec) {
3730 				if (eptr.evt is null) {
3731 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3732 					ec = --eventQueueUsed;
3733 					eventQueue.ptr[ec].evt = null; // make GC life easier
3734 				} else {
3735 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3736 					++eidx;
3737 					++eptr;
3738 				}
3739 			}
3740 			inCustomEventProcessor = ocep;
3741 		}
3742 	}
3743 
3744 	// for all windows in nativeMapping
3745 	package static void processAllCustomEvents () {
3746 
3747 		cleanupQueue.process();
3748 
3749 		justCommunication.processCustomEvents();
3750 
3751 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3752 			if (sw is null || sw.closed) continue;
3753 			sw.processCustomEvents();
3754 		}
3755 
3756 		runPendingRunInGuiThreadDelegates();
3757 	}
3758 
3759 	// 0: infinite (i.e. no scheduled events in queue)
3760 	uint eventQueueTimeoutMSecs () {
3761 		synchronized(this) {
3762 			if (eventQueueUsed == 0) return 0;
3763 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3764 			uint res = int.max;
3765 			auto ctt = MonoTime.currTime;
3766 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3767 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3768 				if (qe.doProcess) continue; // just in case
3769 				if (!qe.timed) return 1; // minimal
3770 				if (qe.hittime <= ctt) return 1; // minimal
3771 				auto tms = (qe.hittime-ctt).total!"msecs";
3772 				if (tms < 1) tms = 1; // safety net
3773 				if (tms >= int.max) tms = int.max-1; // and another safety net
3774 				if (res > tms) res = cast(uint)tms;
3775 			}
3776 			return (res >= int.max ? 0 : res);
3777 		}
3778 	}
3779 
3780 	// for all windows in nativeMapping
3781 	static uint eventAllQueueTimeoutMSecs () {
3782 		uint res = uint.max;
3783 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3784 			if (sw is null || sw.closed) continue;
3785 			uint to = sw.eventQueueTimeoutMSecs();
3786 			if (to && to < res) {
3787 				res = to;
3788 				if (to == 1) break; // can't have less than this
3789 			}
3790 		}
3791 		return (res >= int.max ? 0 : res);
3792 	}
3793 
3794 	version(X11) {
3795 		ResizeEvent pendingResizeEvent;
3796 	}
3797 
3798 	/++
3799 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3800 
3801 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3802 		worth so you can disable it by setting this to `true`.
3803 
3804 		History:
3805 			Added November 13, 2022.
3806 	+/
3807 	public bool suppressAutoOpenglViewport = false;
3808 	private void updateOpenglViewportIfNeeded(int width, int height) {
3809 		if(suppressAutoOpenglViewport) return;
3810 
3811 		version(without_opengl) {} else
3812 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3813 		// writeln(width, " ", height);
3814 			setAsCurrentOpenGlContextNT();
3815 			glViewport(0, 0, width, height);
3816 		}
3817 	}
3818 }
3819 
3820 /++
3821 	Magic pseudo-window for just posting events to a global queue.
3822 
3823 	Not entirely supported, I might delete it at any time.
3824 
3825 	Added Nov 5, 2021.
3826 +/
3827 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init);
3828 
3829 /* Drag and drop support { */
3830 version(X11) {
3831 
3832 } else version(Windows) {
3833 	import core.sys.windows.uuid;
3834 	import core.sys.windows.ole2;
3835 	import core.sys.windows.oleidl;
3836 	import core.sys.windows.objidl;
3837 	import core.sys.windows.wtypes;
3838 
3839 	pragma(lib, "ole32");
3840 	void initDnd() {
3841 		auto err = OleInitialize(null);
3842 		if(err != S_OK && err != S_FALSE)
3843 			throw new Exception("init");//err);
3844 	}
3845 }
3846 /* } End drag and drop support */
3847 
3848 
3849 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3850 /// See [GenericCursor].
3851 class MouseCursor {
3852 	int osId;
3853 	bool isStockCursor;
3854 	private this(int osId) {
3855 		this.osId = osId;
3856 		this.isStockCursor = true;
3857 	}
3858 
3859 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3860 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3861 
3862 	version(Windows) {
3863 		HCURSOR cursor_;
3864 		HCURSOR cursorHandle() {
3865 			if(cursor_ is null)
3866 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3867 			return cursor_;
3868 		}
3869 
3870 	} else static if(UsingSimpledisplayX11) {
3871 		Cursor cursor_ = None;
3872 		int xDisplaySequence;
3873 
3874 		Cursor cursorHandle() {
3875 			if(this.osId == None)
3876 				return None;
3877 
3878 			// we need to reload if we on a new X connection
3879 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3880 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3881 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3882 			}
3883 			return cursor_;
3884 		}
3885 	}
3886 }
3887 
3888 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3889 // https://tronche.com/gui/x/xlib/appendix/b/
3890 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3891 /// 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.
3892 enum GenericCursorType {
3893 	Default, /// The default arrow pointer.
3894 	Wait, /// A cursor indicating something is loading and the user must wait.
3895 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3896 	Help, /// A cursor indicating the user can get help about the pointer location.
3897 	Cross, /// A crosshair.
3898 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3899 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3900 	UpArrow, /// An arrow pointing straight up.
3901 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3902 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3903 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3904 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3905 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3906 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3907 
3908 }
3909 
3910 /*
3911 	X_plus == css cell == Windows ?
3912 */
3913 
3914 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3915 static struct GenericCursor {
3916 	static:
3917 	///
3918 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3919 		static MouseCursor mc;
3920 
3921 		auto type = __traits(getMember, GenericCursorType, str);
3922 
3923 		if(mc is null) {
3924 
3925 			version(Windows) {
3926 				int osId;
3927 				final switch(type) {
3928 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3929 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3930 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3931 					case GenericCursorType.Help: osId = IDC_HELP; break;
3932 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3933 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3934 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3935 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3936 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3937 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3938 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3939 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3940 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3941 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3942 				}
3943 			} else static if(UsingSimpledisplayX11) {
3944 				int osId;
3945 				final switch(type) {
3946 					case GenericCursorType.Default: osId = None; break;
3947 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3948 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3949 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3950 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3951 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3952 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3953 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3954 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3955 
3956 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3957 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3958 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3959 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3960 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3961 				}
3962 
3963 			} else featureNotImplemented();
3964 
3965 			mc = new MouseCursor(osId);
3966 		}
3967 		return mc;
3968 	}
3969 }
3970 
3971 
3972 /++
3973 	If you want to get more control over the event loop, you can use this.
3974 
3975 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
3976 	to `EventLoop.get.run`.
3977 +/
3978 struct EventLoop {
3979 	@disable this();
3980 
3981 	/// Gets a reference to an existing event loop
3982 	static EventLoop get() {
3983 		return EventLoop(0, null);
3984 	}
3985 
3986 	static void quitApplication() {
3987 		EventLoop.get().exit();
3988 	}
3989 
3990 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3991 
3992 	/// Construct an application-global event loop for yourself
3993 	/// See_Also: [SimpleWindow.setEventHandlers]
3994 	this(long pulseTimeout, void delegate() handlePulse) {
3995 		synchronized(monitor) {
3996 			if(impl is null) {
3997 				claimGuiThread();
3998 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3999 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4000 			} else {
4001 				if(pulseTimeout) {
4002 					impl.pulseTimeout = pulseTimeout;
4003 					impl.handlePulse = handlePulse;
4004 				}
4005 			}
4006 			impl.refcount++;
4007 		}
4008 	}
4009 
4010 	~this() {
4011 		if(impl is null)
4012 			return;
4013 		impl.refcount--;
4014 		if(impl.refcount == 0) {
4015 			impl.dispose();
4016 			if(thisIsGuiThread)
4017 				guiThreadFinalize();
4018 		}
4019 
4020 	}
4021 
4022 	this(this) {
4023 		if(impl is null)
4024 			return;
4025 		impl.refcount++;
4026 	}
4027 
4028 	/// Runs the event loop until the whileCondition, if present, returns false
4029 	int run(bool delegate() whileCondition = null) {
4030 		assert(impl !is null);
4031 		impl.notExited = true;
4032 		return impl.run(whileCondition);
4033 	}
4034 
4035 	/// Exits the event loop
4036 	void exit() {
4037 		assert(impl !is null);
4038 		impl.notExited = false;
4039 	}
4040 
4041 	version(linux)
4042 	ref void delegate(int) signalHandler() {
4043 		assert(impl !is null);
4044 		return impl.signalHandler;
4045 	}
4046 
4047 	__gshared static EventLoopImpl* impl;
4048 }
4049 
4050 version(linux)
4051 	void delegate(int, int) globalHupHandler;
4052 
4053 version(Posix)
4054 	void makeNonBlocking(int fd) {
4055 		import fcntl = core.sys.posix.fcntl;
4056 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4057 		if(flags == -1)
4058 			throw new Exception("fcntl get");
4059 		flags |= fcntl.O_NONBLOCK;
4060 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4061 		if(s == -1)
4062 			throw new Exception("fcntl set");
4063 	}
4064 
4065 struct EventLoopImpl {
4066 	int refcount;
4067 
4068 	bool notExited = true;
4069 
4070 	version(linux) {
4071 		static import ep = core.sys.linux.epoll;
4072 		static import unix = core.sys.posix.unistd;
4073 		static import err = core.stdc.errno;
4074 		import core.sys.linux.timerfd;
4075 
4076 		void delegate(int) signalHandler;
4077 	}
4078 
4079 	version(X11) {
4080 		int pulseFd = -1;
4081 		version(linux) ep.epoll_event[16] events = void;
4082 	} else version(Windows) {
4083 		Timer pulser;
4084 		HANDLE[] handles;
4085 	}
4086 
4087 
4088 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4089 	/// to call this, as it's not recommended to share window between threads.
4090 	void mtLock () {
4091 		version(X11) {
4092 			XLockDisplay(this.display);
4093 		}
4094 	}
4095 
4096 	version(X11)
4097 	auto display() { return XDisplayConnection.get; }
4098 
4099 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4100 	/// to call this, as it's not recommended to share window between threads.
4101 	void mtUnlock () {
4102 		version(X11) {
4103 			XUnlockDisplay(this.display);
4104 		}
4105 	}
4106 
4107 	version(with_eventloop)
4108 	void initialize(long pulseTimeout) {}
4109 	else
4110 	void initialize(long pulseTimeout) {
4111 		version(Windows) {
4112 			if(pulseTimeout && handlePulse !is null)
4113 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4114 
4115 			if (customEventH is null) {
4116 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4117 				if (customEventH !is null) {
4118 					handles ~= customEventH;
4119 				} else {
4120 					// this is something that should not be; better be safe than sorry
4121 					throw new Exception("can't create eventfd for custom event processing");
4122 				}
4123 			}
4124 
4125 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4126 		}
4127 
4128 		version(linux) {
4129 			prepareEventLoop();
4130 			{
4131 				auto display = XDisplayConnection.get;
4132 				// adding Xlib file
4133 				ep.epoll_event ev = void;
4134 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4135 				ev.events = ep.EPOLLIN;
4136 				ev.data.fd = display.fd;
4137 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4138 					throw new Exception("add x fd");// ~ to!string(epollFd));
4139 				displayFd = display.fd;
4140 			}
4141 
4142 			if(pulseTimeout && handlePulse !is null) {
4143 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4144 				if(pulseFd == -1)
4145 					throw new Exception("pulse timer create failed");
4146 
4147 				itimerspec value;
4148 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4149 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4150 
4151 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4152 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4153 
4154 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4155 					throw new Exception("couldn't make pulse timer");
4156 
4157 				ep.epoll_event ev = void;
4158 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4159 				ev.events = ep.EPOLLIN;
4160 				ev.data.fd = pulseFd;
4161 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4162 			}
4163 
4164 			// eventfd for custom events
4165 			if (customEventFDWrite == -1) {
4166 				customEventFDWrite = eventfd(0, 0);
4167 				customEventFDRead = customEventFDWrite;
4168 				if (customEventFDRead >= 0) {
4169 					ep.epoll_event ev = void;
4170 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4171 					ev.events = ep.EPOLLIN;
4172 					ev.data.fd = customEventFDRead;
4173 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4174 				} else {
4175 					// this is something that should not be; better be safe than sorry
4176 					throw new Exception("can't create eventfd for custom event processing");
4177 				}
4178 			}
4179 
4180 			if (customSignalFD == -1) {
4181 				import core.sys.linux.sys.signalfd;
4182 
4183 				sigset_t sigset;
4184 				auto err = sigemptyset(&sigset);
4185 				assert(!err);
4186 				err = sigaddset(&sigset, SIGINT);
4187 				assert(!err);
4188 				err = sigaddset(&sigset, SIGHUP);
4189 				assert(!err);
4190 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4191 				assert(!err);
4192 
4193 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4194 				assert(customSignalFD != -1);
4195 
4196 				ep.epoll_event ev = void;
4197 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4198 				ev.events = ep.EPOLLIN;
4199 				ev.data.fd = customSignalFD;
4200 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4201 			}
4202 		} else version(Posix) {
4203 			prepareEventLoop();
4204 			if (customEventFDRead == -1) {
4205 				int[2] bfr;
4206 				import core.sys.posix.unistd;
4207 				auto ret = pipe(bfr);
4208 				if(ret == -1) throw new Exception("pipe");
4209 				customEventFDRead = bfr[0];
4210 				customEventFDWrite = bfr[1];
4211 			}
4212 
4213 		}
4214 
4215 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4216 
4217 		version(linux) {
4218 			this.mtLock();
4219 			scope(exit) this.mtUnlock();
4220 			XPending(display); // no, really
4221 		}
4222 
4223 		disposed = false;
4224 	}
4225 
4226 	bool disposed = true;
4227 	version(X11)
4228 		int displayFd = -1;
4229 
4230 	version(with_eventloop)
4231 	void dispose() {}
4232 	else
4233 	void dispose() {
4234 		disposed = true;
4235 		version(X11) {
4236 			if(pulseFd != -1) {
4237 				import unix = core.sys.posix.unistd;
4238 				unix.close(pulseFd);
4239 				pulseFd = -1;
4240 			}
4241 
4242 				version(linux)
4243 				if(displayFd != -1) {
4244 					// 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
4245 					ep.epoll_event ev = void;
4246 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4247 					ev.events = ep.EPOLLIN;
4248 					ev.data.fd = displayFd;
4249 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4250 					displayFd = -1;
4251 				}
4252 
4253 		} else version(Windows) {
4254 			if(pulser !is null) {
4255 				pulser.destroy();
4256 				pulser = null;
4257 			}
4258 			if (customEventH !is null) {
4259 				CloseHandle(customEventH);
4260 				customEventH = null;
4261 			}
4262 		}
4263 	}
4264 
4265 	this(long pulseTimeout, void delegate() handlePulse) {
4266 		this.pulseTimeout = pulseTimeout;
4267 		this.handlePulse = handlePulse;
4268 		initialize(pulseTimeout);
4269 	}
4270 
4271 	private long pulseTimeout;
4272 	void delegate() handlePulse;
4273 
4274 	~this() {
4275 		dispose();
4276 	}
4277 
4278 	version(Posix)
4279 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4280 	version(Posix)
4281 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4282 	version(linux)
4283 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4284 	version(Windows)
4285 	ref auto customEventH() { return SimpleWindow.customEventH; }
4286 
4287 	version(with_eventloop) {
4288 		int loopHelper(bool delegate() whileCondition) {
4289 			// FIXME: whileCondition
4290 			import arsd.eventloop;
4291 			loop();
4292 			return 0;
4293 		}
4294 	} else
4295 	int loopHelper(bool delegate() whileCondition) {
4296 		version(X11) {
4297 			bool done = false;
4298 
4299 			XFlush(display);
4300 			insideXEventLoop = true;
4301 			scope(exit) insideXEventLoop = false;
4302 
4303 			version(linux) {
4304 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4305 					bool forceXPending = false;
4306 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4307 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4308 					{
4309 						this.mtLock();
4310 						scope(exit) this.mtUnlock();
4311 						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
4312 					}
4313 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4314 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4315 					if(nfds == -1) {
4316 						if(err.errno == err.EINTR) {
4317 							//if(forceXPending) goto xpending;
4318 							continue; // interrupted by signal, just try again
4319 						}
4320 						throw new Exception("epoll wait failure");
4321 					}
4322 					// writeln(nfds, " ", events[0].data.fd);
4323 
4324 					SimpleWindow.processAllCustomEvents(); // anyway
4325 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4326 					foreach(idx; 0 .. nfds) {
4327 						if(done) break;
4328 						auto fd = events[idx].data.fd;
4329 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4330 						auto flags = events[idx].events;
4331 						if(flags & ep.EPOLLIN) {
4332 							if (fd == customSignalFD) {
4333 								version(linux) {
4334 									import core.sys.linux.sys.signalfd;
4335 									import core.sys.posix.unistd : read;
4336 									signalfd_siginfo info;
4337 									read(customSignalFD, &info, info.sizeof);
4338 
4339 									auto sig = info.ssi_signo;
4340 
4341 									if(EventLoop.get.signalHandler !is null) {
4342 										EventLoop.get.signalHandler()(sig);
4343 									} else {
4344 										EventLoop.get.exit();
4345 									}
4346 								}
4347 							} else if(fd == display.fd) {
4348 								version(sdddd) { writeln("X EVENT PENDING!"); }
4349 								this.mtLock();
4350 								scope(exit) this.mtUnlock();
4351 								while(!done && XPending(display)) {
4352 									done = doXNextEvent(this.display);
4353 								}
4354 								forceXPending = false;
4355 							} else if(fd == pulseFd) {
4356 								long expirationCount;
4357 								// 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...
4358 
4359 								handlePulse();
4360 
4361 								// read just to clear the buffer so poll doesn't trigger again
4362 								// BTW I read AFTER the pulse because if the pulse handler takes
4363 								// a lot of time to execute, we don't want the app to get stuck
4364 								// in a loop of timer hits without a chance to do anything else
4365 								//
4366 								// IOW handlePulse happens at most once per pulse interval.
4367 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4368 								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
4369 							} else if (fd == customEventFDRead) {
4370 								// we have some custom events; process 'em
4371 								import core.sys.posix.unistd : read;
4372 								ulong n;
4373 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4374 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4375 								//SimpleWindow.processAllCustomEvents();
4376 
4377 								forceXPending = true;
4378 							} else {
4379 								// some other timer
4380 								version(sdddd) { writeln("unknown fd: ", fd); }
4381 
4382 								if(Timer* t = fd in Timer.mapping)
4383 									(*t).trigger();
4384 
4385 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4386 									(*pfr).ready(flags);
4387 
4388 								// or i might add support for other FDs too
4389 								// but for now it is just timer
4390 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4391 							}
4392 						}
4393 						if(flags & ep.EPOLLHUP) {
4394 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4395 								(*pfr).hup(flags);
4396 							if(globalHupHandler)
4397 								globalHupHandler(fd, flags);
4398 						}
4399 						/+
4400 						} else {
4401 							// not interested in OUT, we are just reading here.
4402 							//
4403 							// error or hup might also be reported
4404 							// but it shouldn't here since we are only
4405 							// using a few types of FD and Xlib will report
4406 							// if it dies.
4407 							// so instead of thoughtfully handling it, I'll
4408 							// just throw. for now at least
4409 
4410 							throw new Exception("epoll did something else");
4411 						}
4412 						+/
4413 					}
4414 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4415 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4416 					xpending:
4417 					if (!done && forceXPending) {
4418 						this.mtLock();
4419 						scope(exit) this.mtUnlock();
4420 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4421 						while(!done && XPending(display)) {
4422 							done = doXNextEvent(this.display);
4423 						}
4424 					}
4425 				}
4426 			} else {
4427 				// Generic fallback: yes to simple pulse support,
4428 				// but NO timer support!
4429 
4430 				// FIXME: we could probably support the POSIX timer_create
4431 				// signal-based option, but I'm in no rush to write it since
4432 				// I prefer the fd-based functions.
4433 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4434 
4435 					import core.sys.posix.poll;
4436 
4437 					pollfd[] pfds;
4438 					pollfd[32] pfdsBuffer;
4439 					auto len = PosixFdReader.mapping.length + 2;
4440 					// FIXME: i should just reuse the buffer
4441 					if(len < pfdsBuffer.length)
4442 						pfds = pfdsBuffer[0 .. len];
4443 					else
4444 						pfds = new pollfd[](len);
4445 
4446 					pfds[0].fd = display.fd;
4447 					pfds[0].events = POLLIN;
4448 					pfds[0].revents = 0;
4449 
4450 					int slot = 1;
4451 
4452 					if(customEventFDRead != -1) {
4453 						pfds[slot].fd = customEventFDRead;
4454 						pfds[slot].events = POLLIN;
4455 						pfds[slot].revents = 0;
4456 
4457 						slot++;
4458 					}
4459 
4460 					foreach(fd, obj; PosixFdReader.mapping) {
4461 						if(!obj.enabled) continue;
4462 						pfds[slot].fd = fd;
4463 						pfds[slot].events = POLLIN;
4464 						pfds[slot].revents = 0;
4465 
4466 						slot++;
4467 					}
4468 
4469 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4470 					if(ret == -1) throw new Exception("poll");
4471 
4472 					if(ret == 0) {
4473 						// FIXME it may not necessarily time out if events keep coming
4474 						if(handlePulse !is null)
4475 							handlePulse();
4476 					} else {
4477 						foreach(s; 0 .. slot) {
4478 							if(pfds[s].revents == 0) continue;
4479 
4480 							if(pfds[s].fd == display.fd) {
4481 								while(!done && XPending(display)) {
4482 									this.mtLock();
4483 									scope(exit) this.mtUnlock();
4484 									done = doXNextEvent(this.display);
4485 								}
4486 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4487 
4488 								import core.sys.posix.unistd : read;
4489 								ulong n;
4490 								read(customEventFDRead, &n, n.sizeof);
4491 								SimpleWindow.processAllCustomEvents();
4492 							} else {
4493 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4494 								if(pfds[s].revents & POLLNVAL) {
4495 									obj.dispose();
4496 								} else {
4497 									obj.ready(pfds[s].revents);
4498 								}
4499 							}
4500 
4501 							ret--;
4502 							if(ret == 0) break;
4503 						}
4504 					}
4505 				}
4506 			}
4507 		}
4508 
4509 		version(Windows) {
4510 			int ret = -1;
4511 			MSG message;
4512 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4513 				eventLoopRound++;
4514 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4515 				auto waitResult = MsgWaitForMultipleObjectsEx(
4516 					cast(int) handles.length, handles.ptr,
4517 					(wto == 0 ? INFINITE : wto), /* timeout */
4518 					0x04FF, /* QS_ALLINPUT */
4519 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4520 
4521 				SimpleWindow.processAllCustomEvents(); // anyway
4522 				enum WAIT_OBJECT_0 = 0;
4523 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4524 					auto h = handles[waitResult - WAIT_OBJECT_0];
4525 					if(auto e = h in WindowsHandleReader.mapping) {
4526 						(*e).ready();
4527 					}
4528 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4529 					// message ready
4530 					int count;
4531 					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
4532 						ret = GetMessage(&message, null, 0, 0);
4533 						if(ret == -1)
4534 							throw new WindowsApiException("GetMessage", GetLastError());
4535 						TranslateMessage(&message);
4536 						DispatchMessage(&message);
4537 
4538 						count++;
4539 						if(count > 10)
4540 							break; // take the opportunity to catch up on other events
4541 
4542 						if(ret == 0) { // WM_QUIT
4543 							EventLoop.quitApplication();
4544 							break;
4545 						}
4546 					}
4547 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4548 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4549 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4550 					// timeout, should never happen since we aren't using it
4551 				} else if(waitResult == 0xFFFFFFFF) {
4552 						// failed
4553 						throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4554 				} else {
4555 					// idk....
4556 				}
4557 			}
4558 
4559 			// return message.wParam;
4560 			return 0;
4561 		} else {
4562 			return 0;
4563 		}
4564 	}
4565 
4566 	int run(bool delegate() whileCondition = null) {
4567 		if(disposed)
4568 			initialize(this.pulseTimeout);
4569 
4570 		version(X11) {
4571 			try {
4572 				return loopHelper(whileCondition);
4573 			} catch(XDisconnectException e) {
4574 				if(e.userRequested) {
4575 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4576 						item.discardConnectionState();
4577 					XCloseDisplay(XDisplayConnection.display);
4578 				}
4579 
4580 				XDisplayConnection.display = null;
4581 
4582 				this.dispose();
4583 
4584 				throw e;
4585 			}
4586 		} else {
4587 			return loopHelper(whileCondition);
4588 		}
4589 	}
4590 }
4591 
4592 
4593 /++
4594 	Provides an icon on the system notification area (also known as the system tray).
4595 
4596 
4597 	If a notification area is not available with the NotificationIcon object is created,
4598 	it will silently succeed and simply attempt to create one when an area becomes available.
4599 
4600 
4601 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
4602 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
4603 	use the older version.
4604 +/
4605 version(OSXCocoa) {} else // NotYetImplementedException
4606 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4607 
4608 	version(X11) {
4609 		void recreateAfterDisconnect() {
4610 			stateDiscarded = false;
4611 			clippixmap = None;
4612 			throw new Exception("NOT IMPLEMENTED");
4613 		}
4614 
4615 		bool stateDiscarded;
4616 		void discardConnectionState() {
4617 			stateDiscarded = true;
4618 		}
4619 	}
4620 
4621 
4622 	version(X11) {
4623 		Image img;
4624 
4625 		NativeEventHandler getNativeEventHandler() {
4626 			return delegate int(XEvent e) {
4627 				switch(e.type) {
4628 					case EventType.Expose:
4629 					//case EventType.VisibilityNotify:
4630 						redraw();
4631 					break;
4632 					case EventType.ClientMessage:
4633 						version(sddddd) {
4634 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4635 						writeln("\t", e.xclient.format);
4636 						writeln("\t", e.xclient.data.l);
4637 						}
4638 					break;
4639 					case EventType.ButtonPress:
4640 						auto event = e.xbutton;
4641 						if (onClick !is null || onClickEx !is null) {
4642 							MouseButton mb = cast(MouseButton)0;
4643 							switch (event.button) {
4644 								case 1: mb = MouseButton.left; break; // left
4645 								case 2: mb = MouseButton.middle; break; // middle
4646 								case 3: mb = MouseButton.right; break; // right
4647 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4648 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4649 								case 6: break; // scroll left...
4650 								case 7: break; // scroll right...
4651 								case 8: mb = MouseButton.backButton; break;
4652 								case 9: mb = MouseButton.forwardButton; break;
4653 								default:
4654 							}
4655 							if (mb) {
4656 								try { onClick()(mb); } catch (Exception) {}
4657 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4658 							}
4659 						}
4660 					break;
4661 					case EventType.EnterNotify:
4662 						if (onEnter !is null) {
4663 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4664 						}
4665 						break;
4666 					case EventType.LeaveNotify:
4667 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4668 						break;
4669 					case EventType.DestroyNotify:
4670 						active = false;
4671 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4672 					break;
4673 					case EventType.ConfigureNotify:
4674 						auto event = e.xconfigure;
4675 						this.width = event.width;
4676 						this.height = event.height;
4677 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4678 						redraw();
4679 					break;
4680 					default: return 1;
4681 				}
4682 				return 1;
4683 			};
4684 		}
4685 
4686 		/* private */ void hideBalloon() {
4687 			balloon.close();
4688 			version(with_timer)
4689 				timer.destroy();
4690 			balloon = null;
4691 			version(with_timer)
4692 				timer = null;
4693 		}
4694 
4695 		void redraw() {
4696 			if (!active) return;
4697 
4698 			auto display = XDisplayConnection.get;
4699 			auto gc = DefaultGC(display, DefaultScreen(display));
4700 			XClearWindow(display, nativeHandle);
4701 
4702 			XSetClipMask(display, gc, clippixmap);
4703 
4704 			XSetForeground(display, gc,
4705 				cast(uint) 0 << 16 |
4706 				cast(uint) 0 << 8 |
4707 				cast(uint) 0);
4708 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4709 
4710 			if (img is null) {
4711 				XSetForeground(display, gc,
4712 					cast(uint) 0 << 16 |
4713 					cast(uint) 127 << 8 |
4714 					cast(uint) 0);
4715 				XFillArc(display, nativeHandle,
4716 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4717 			} else {
4718 				int dx = 0;
4719 				int dy = 0;
4720 				if(width > img.width)
4721 					dx = (width - img.width) / 2;
4722 				if(height > img.height)
4723 					dy = (height - img.height) / 2;
4724 				XSetClipOrigin(display, gc, dx, dy);
4725 
4726 				if (img.usingXshm)
4727 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
4728 				else
4729 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
4730 			}
4731 			XSetClipMask(display, gc, None);
4732 			flushGui();
4733 		}
4734 
4735 		static Window getTrayOwner() {
4736 			auto display = XDisplayConnection.get;
4737 			auto i = cast(int) DefaultScreen(display);
4738 			if(i < 10 && i >= 0) {
4739 				static Atom atom;
4740 				if(atom == None)
4741 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4742 				return XGetSelectionOwner(display, atom);
4743 			}
4744 			return None;
4745 		}
4746 
4747 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4748 			auto to = getTrayOwner();
4749 			auto display = XDisplayConnection.get;
4750 			XEvent ev;
4751 			ev.xclient.type = EventType.ClientMessage;
4752 			ev.xclient.window = to;
4753 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4754 			ev.xclient.format = 32;
4755 			ev.xclient.data.l[0] = CurrentTime;
4756 			ev.xclient.data.l[1] = message;
4757 			ev.xclient.data.l[2] = d1;
4758 			ev.xclient.data.l[3] = d2;
4759 			ev.xclient.data.l[4] = d3;
4760 
4761 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4762 		}
4763 
4764 		private static NotificationAreaIcon[] activeIcons;
4765 
4766 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4767 		private void newManager() {
4768 			close();
4769 			createXWin();
4770 
4771 			if(this.clippixmap)
4772 				XFreePixmap(XDisplayConnection.get, clippixmap);
4773 			if(this.originalMemoryImage)
4774 				this.icon = this.originalMemoryImage;
4775 			else if(this.img)
4776 				this.icon = this.img;
4777 		}
4778 
4779 		private void createXWin () {
4780 			// create window
4781 			auto display = XDisplayConnection.get;
4782 
4783 			// to check for MANAGER on root window to catch new/changed tray owners
4784 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4785 			// so if a thing does appear, we can handle it
4786 			foreach(ai; activeIcons)
4787 				if(ai is this)
4788 					goto alreadythere;
4789 			activeIcons ~= this;
4790 			alreadythere:
4791 
4792 			// and check for an existing tray
4793 			auto trayOwner = getTrayOwner();
4794 			if(trayOwner == None)
4795 				return;
4796 				//throw new Exception("No notification area found");
4797 
4798 			Visual* v = cast(Visual*) CopyFromParent;
4799 			/+
4800 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4801 			if(visualProp !is null) {
4802 				c_ulong[] info = cast(c_ulong[]) visualProp;
4803 				if(info.length == 1) {
4804 					auto vid = info[0];
4805 					int returned;
4806 					XVisualInfo t;
4807 					t.visualid = vid;
4808 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4809 					if(got !is null) {
4810 						if(returned == 1) {
4811 							v = got.visual;
4812 							writeln("using special visual ", *got);
4813 						}
4814 						XFree(got);
4815 					}
4816 				}
4817 			}
4818 			+/
4819 
4820 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
4821 			assert(nativeWindow);
4822 
4823 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4824 
4825 			nativeHandle = nativeWindow;
4826 
4827 			///+
4828 			arch_ulong[2] info;
4829 			info[0] = 0;
4830 			info[1] = 1;
4831 
4832 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4833 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4834 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4835 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4836 
4837 			XChangeProperty(
4838 				display,
4839 				nativeWindow,
4840 				GetAtom!("_XEMBED_INFO", true)(display),
4841 				GetAtom!("_XEMBED_INFO", true)(display),
4842 				32 /* bits */,
4843 				0 /*PropModeReplace*/,
4844 				info.ptr,
4845 				2);
4846 
4847 			import core.sys.posix.unistd;
4848 			arch_ulong pid = getpid();
4849 
4850 			XChangeProperty(
4851 				display,
4852 				nativeWindow,
4853 				GetAtom!("_NET_WM_PID", true)(display),
4854 				XA_CARDINAL,
4855 				32 /* bits */,
4856 				0 /*PropModeReplace*/,
4857 				&pid,
4858 				1);
4859 
4860 			updateNetWmIcon();
4861 
4862 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4863 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4864 				XClassHint klass;
4865 				XWMHints wh;
4866 				XSizeHints size;
4867 				klass.res_name = sdpyWindowClassStr;
4868 				klass.res_class = sdpyWindowClassStr;
4869 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4870 			}
4871 
4872 				// believe it or not, THIS is what xfce needed for the 9999 issue
4873 				XSizeHints sh;
4874 					c_long spr;
4875 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4876 					sh.flags |= PMaxSize | PMinSize;
4877 				// FIXME maybe nicer resizing
4878 				sh.min_width = 16;
4879 				sh.min_height = 16;
4880 				sh.max_width = 16;
4881 				sh.max_height = 16;
4882 				XSetWMNormalHints(display, nativeWindow, &sh);
4883 
4884 
4885 			//+/
4886 
4887 
4888 			XSelectInput(display, nativeWindow,
4889 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4890 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4891 
4892 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4893 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4894 			active = true;
4895 		}
4896 
4897 		void updateNetWmIcon() {
4898 			if(img is null) return;
4899 			auto display = XDisplayConnection.get;
4900 			// FIXME: ensure this is correct
4901 			arch_ulong[] buffer;
4902 			auto imgMi = img.toTrueColorImage;
4903 			buffer ~= imgMi.width;
4904 			buffer ~= imgMi.height;
4905 			foreach(c; imgMi.imageData.colors) {
4906 				arch_ulong b;
4907 				b |= c.a << 24;
4908 				b |= c.r << 16;
4909 				b |= c.g << 8;
4910 				b |= c.b;
4911 				buffer ~= b;
4912 			}
4913 
4914 			XChangeProperty(
4915 				display,
4916 				nativeHandle,
4917 				GetAtom!"_NET_WM_ICON"(display),
4918 				GetAtom!"CARDINAL"(display),
4919 				32 /* bits */,
4920 				0 /*PropModeReplace*/,
4921 				buffer.ptr,
4922 				cast(int) buffer.length);
4923 		}
4924 
4925 
4926 
4927 		private SimpleWindow balloon;
4928 		version(with_timer)
4929 		private Timer timer;
4930 
4931 		private Window nativeHandle;
4932 		private Pixmap clippixmap = None;
4933 		private int width = 16;
4934 		private int height = 16;
4935 		private bool active = false;
4936 
4937 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4938 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4939 		void delegate () onLeave; /// X11 only.
4940 
4941 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4942 
4943 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4944 		void getWindowRect (out int x, out int y, out int width, out int height) {
4945 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4946 			Window dummyw;
4947 			auto dpy = XDisplayConnection.get;
4948 			//XWindowAttributes xwa;
4949 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4950 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4951 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4952 			width = this.width;
4953 			height = this.height;
4954 		}
4955 	}
4956 
4957 	/+
4958 		What I actually want from this:
4959 
4960 		* set / change: icon, tooltip
4961 		* handle: mouse click, right click
4962 		* show: notification bubble.
4963 	+/
4964 
4965 	version(Windows) {
4966 		WindowsIcon win32Icon;
4967 		HWND hwnd;
4968 
4969 		NOTIFYICONDATAW data;
4970 
4971 		NativeEventHandler getNativeEventHandler() {
4972 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
4973 				if(msg == WM_USER) {
4974 					auto event = LOWORD(lParam);
4975 					auto iconId = HIWORD(lParam);
4976 					//auto x = GET_X_LPARAM(wParam);
4977 					//auto y = GET_Y_LPARAM(wParam);
4978 					switch(event) {
4979 						case WM_LBUTTONDOWN:
4980 							onClick()(MouseButton.left);
4981 						break;
4982 						case WM_RBUTTONDOWN:
4983 							onClick()(MouseButton.right);
4984 						break;
4985 						case WM_MBUTTONDOWN:
4986 							onClick()(MouseButton.middle);
4987 						break;
4988 						case WM_MOUSEMOVE:
4989 							// sent, we could use it.
4990 						break;
4991 						case WM_MOUSEWHEEL:
4992 							// NOT SENT
4993 						break;
4994 						//case NIN_KEYSELECT:
4995 						//case NIN_SELECT:
4996 						//break;
4997 						default: {}
4998 					}
4999 				}
5000 				return 0;
5001 			};
5002 		}
5003 
5004 		enum NIF_SHOWTIP = 0x00000080;
5005 
5006 		private static struct NOTIFYICONDATAW {
5007 			DWORD cbSize;
5008 			HWND  hWnd;
5009 			UINT  uID;
5010 			UINT  uFlags;
5011 			UINT  uCallbackMessage;
5012 			HICON hIcon;
5013 			WCHAR[128] szTip;
5014 			DWORD dwState;
5015 			DWORD dwStateMask;
5016 			WCHAR[256] szInfo;
5017 			union {
5018 				UINT uTimeout;
5019 				UINT uVersion;
5020 			}
5021 			WCHAR[64] szInfoTitle;
5022 			DWORD dwInfoFlags;
5023 			GUID  guidItem;
5024 			HICON hBalloonIcon;
5025 		}
5026 
5027 	}
5028 
5029 	/++
5030 		Note that on Windows, only left, right, and middle buttons are sent.
5031 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5032 		program is meant to be used on Windows too.
5033 	+/
5034 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5035 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5036 		// but on X, we need an Image, so its canonical ctor is there. They should
5037 		// forward to each other though.
5038 		version(X11) {
5039 			this.name = name;
5040 			this.onClick = onClick;
5041 			createXWin();
5042 			this.icon = icon;
5043 		} else version(Windows) {
5044 			this.onClick = onClick;
5045 			this.win32Icon = new WindowsIcon(icon);
5046 
5047 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5048 
5049 			static bool registered = false;
5050 			if(!registered) {
5051 				WNDCLASSEX wc;
5052 				wc.cbSize = wc.sizeof;
5053 				wc.hInstance = hInstance;
5054 				wc.lpfnWndProc = &WndProc;
5055 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5056 				if(!RegisterClassExW(&wc))
5057 					throw new WindowsApiException("RegisterClass", GetLastError());
5058 				registered = true;
5059 			}
5060 
5061 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5062 			if(hwnd is null)
5063 				throw new WindowsApiException("CreateWindow", GetLastError());
5064 
5065 			data.cbSize = data.sizeof;
5066 			data.hWnd = hwnd;
5067 			data.uID = cast(uint) cast(void*) this;
5068 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5069 				// NIF_INFO means show balloon
5070 			data.uCallbackMessage = WM_USER;
5071 			data.hIcon = this.win32Icon.hIcon;
5072 			data.szTip = ""; // FIXME
5073 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5074 			data.dwStateMask = NIS_HIDDEN; // windows vista
5075 
5076 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5077 
5078 
5079 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5080 
5081 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5082 		} else version(OSXCocoa) {
5083 			throw new NotYetImplementedException();
5084 		} else static assert(0);
5085 	}
5086 
5087 	/// ditto
5088 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5089 		version(X11) {
5090 			this.onClick = onClick;
5091 			this.name = name;
5092 			createXWin();
5093 			this.icon = icon;
5094 		} else version(Windows) {
5095 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5096 		} else version(OSXCocoa) {
5097 			throw new NotYetImplementedException();
5098 		} else static assert(0);
5099 	}
5100 
5101 	version(X11) {
5102 		/++
5103 			X-specific extension (for now at least)
5104 		+/
5105 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5106 			this.onClickEx = onClickEx;
5107 			createXWin();
5108 			if (icon !is null) this.icon = icon;
5109 		}
5110 
5111 		/// ditto
5112 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5113 			this.onClickEx = onClickEx;
5114 			createXWin();
5115 			this.icon = icon;
5116 		}
5117 	}
5118 
5119 	private void delegate (MouseButton button) onClick_;
5120 
5121 	///
5122 	@property final void delegate(MouseButton) onClick() {
5123 		if(onClick_ is null)
5124 			onClick_ = delegate void(MouseButton) {};
5125 		return onClick_;
5126 	}
5127 
5128 	/// ditto
5129 	@property final void onClick(void delegate(MouseButton) handler) {
5130 		// I made this a property setter so we can wrap smaller arg
5131 		// delegates and just forward all to onClickEx or something.
5132 		onClick_ = handler;
5133 	}
5134 
5135 
5136 	string name_;
5137 	@property void name(string n) {
5138 		name_ = n;
5139 	}
5140 
5141 	@property string name() {
5142 		return name_;
5143 	}
5144 
5145 	private MemoryImage originalMemoryImage;
5146 
5147 	///
5148 	@property void icon(MemoryImage i) {
5149 		version(X11) {
5150 			this.originalMemoryImage = i;
5151 			if (!active) return;
5152 			if (i !is null) {
5153 				this.img = Image.fromMemoryImage(i);
5154 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5155 				// writeln("using pixmap ", clippixmap);
5156 				updateNetWmIcon();
5157 				redraw();
5158 			} else {
5159 				if (this.img !is null) {
5160 					this.img = null;
5161 					redraw();
5162 				}
5163 			}
5164 		} else version(Windows) {
5165 			this.win32Icon = new WindowsIcon(i);
5166 
5167 			data.uFlags = NIF_ICON;
5168 			data.hIcon = this.win32Icon.hIcon;
5169 
5170 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5171 		} else version(OSXCocoa) {
5172 			throw new NotYetImplementedException();
5173 		} else static assert(0);
5174 	}
5175 
5176 	/// ditto
5177 	@property void icon (Image i) {
5178 		version(X11) {
5179 			if (!active) return;
5180 			if (i !is img) {
5181 				originalMemoryImage = null;
5182 				img = i;
5183 				redraw();
5184 			}
5185 		} else version(Windows) {
5186 			this.icon(i is null ? null : i.toTrueColorImage());
5187 		} else version(OSXCocoa) {
5188 			throw new NotYetImplementedException();
5189 		} else static assert(0);
5190 	}
5191 
5192 	/++
5193 		Shows a balloon notification. You can only show one balloon at a time, if you call
5194 		it twice while one is already up, the first balloon will be replaced.
5195 
5196 
5197 		The user is free to block notifications and they will automatically disappear after
5198 		a timeout period.
5199 
5200 		Params:
5201 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5202 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5203 			icon = the icon to display with the notification. If null, it uses your existing icon.
5204 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5205 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5206 	+/
5207 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5208 		bool useCustom = true;
5209 		version(libnotify) {
5210 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5211 			try {
5212 				if(!active) return;
5213 
5214 				if(libnotify is null) {
5215 					libnotify = new C_DynamicLibrary("libnotify.so");
5216 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5217 				}
5218 
5219 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5220 
5221 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5222 
5223 				if(onclick) {
5224 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5225 					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);
5226 					libnotify_action_delegates_count++;
5227 				}
5228 
5229 				// FIXME icon
5230 
5231 				// set hint image-data
5232 				// set default action for onclick
5233 
5234 				void* error;
5235 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5236 
5237 				useCustom = false;
5238 			} catch(Exception e) {
5239 
5240 			}
5241 		}
5242 
5243 		version(X11) {
5244 		if(useCustom) {
5245 			if(!active) return;
5246 			if(balloon) {
5247 				hideBalloon();
5248 			}
5249 			// I know there are two specs for this, but one is never
5250 			// implemented by any window manager I have ever seen, and
5251 			// the other is a bloated mess and too complicated for simpledisplay...
5252 			// so doing my own little window instead.
5253 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5254 
5255 			int x, y, width, height;
5256 			getWindowRect(x, y, width, height);
5257 
5258 			int bx = x - balloon.width;
5259 			int by = y - balloon.height;
5260 			if(bx < 0)
5261 				bx = x + width + balloon.width;
5262 			if(by < 0)
5263 				by = y + height;
5264 
5265 			// just in case, make sure it is actually on scren
5266 			if(bx < 0)
5267 				bx = 0;
5268 			if(by < 0)
5269 				by = 0;
5270 
5271 			balloon.move(bx, by);
5272 			auto painter = balloon.draw();
5273 			painter.fillColor = Color(220, 220, 220);
5274 			painter.outlineColor = Color.black;
5275 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5276 			auto iconWidth = icon is null ? 0 : icon.width;
5277 			if(icon)
5278 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5279 			iconWidth += 6; // margin around the icon
5280 
5281 			// draw a close button
5282 			painter.outlineColor = Color(44, 44, 44);
5283 			painter.fillColor = Color(255, 255, 255);
5284 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5285 			painter.pen = Pen(Color.black, 3);
5286 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5287 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5288 			painter.pen = Pen(Color.black, 1);
5289 			painter.fillColor = Color(220, 220, 220);
5290 
5291 			// Draw the title and message
5292 			painter.drawText(Point(4 + iconWidth, 4), title);
5293 			painter.drawLine(
5294 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5295 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5296 			);
5297 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5298 
5299 			balloon.setEventHandlers(
5300 				(MouseEvent ev) {
5301 					if(ev.type == MouseEventType.buttonPressed) {
5302 						if(ev.x > balloon.width - 16 && ev.y < 16)
5303 							hideBalloon();
5304 						else if(onclick)
5305 							onclick();
5306 					}
5307 				}
5308 			);
5309 			balloon.show();
5310 
5311 			version(with_timer)
5312 			timer = new Timer(timeout, &hideBalloon);
5313 			else {} // FIXME
5314 		}
5315 		} else version(Windows) {
5316 			enum NIF_INFO = 0x00000010;
5317 
5318 			data.uFlags = NIF_INFO;
5319 
5320 			// FIXME: go back to the last valid unicode code point
5321 			if(title.length > 40)
5322 				title = title[0 .. 40];
5323 			if(message.length > 220)
5324 				message = message[0 .. 220];
5325 
5326 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5327 			enum NIIF_LARGE_ICON  = 0x00000020;
5328 			enum NIIF_NOSOUND = 0x00000010;
5329 			enum NIIF_USER = 0x00000004;
5330 			enum NIIF_ERROR = 0x00000003;
5331 			enum NIIF_WARNING = 0x00000002;
5332 			enum NIIF_INFO = 0x00000001;
5333 			enum NIIF_NONE = 0;
5334 
5335 			WCharzBuffer t = WCharzBuffer(title);
5336 			WCharzBuffer m = WCharzBuffer(message);
5337 
5338 			t.copyInto(data.szInfoTitle);
5339 			m.copyInto(data.szInfo);
5340 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5341 
5342 			if(icon !is null) {
5343 				auto i = new WindowsIcon(icon);
5344 				data.hBalloonIcon = i.hIcon;
5345 				data.dwInfoFlags |= NIIF_USER;
5346 			}
5347 
5348 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5349 		} else version(OSXCocoa) {
5350 			throw new NotYetImplementedException();
5351 		} else static assert(0);
5352 	}
5353 
5354 	///
5355 	//version(Windows)
5356 	void show() {
5357 		version(X11) {
5358 			if(!hidden)
5359 				return;
5360 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5361 			hidden = false;
5362 		} else version(Windows) {
5363 			data.uFlags = NIF_STATE;
5364 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5365 			data.dwStateMask = NIS_HIDDEN; // windows vista
5366 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5367 		} else version(OSXCocoa) {
5368 			throw new NotYetImplementedException();
5369 		} else static assert(0);
5370 	}
5371 
5372 	version(X11)
5373 		bool hidden = false;
5374 
5375 	///
5376 	//version(Windows)
5377 	void hide() {
5378 		version(X11) {
5379 			if(hidden)
5380 				return;
5381 			hidden = true;
5382 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5383 		} else version(Windows) {
5384 			data.uFlags = NIF_STATE;
5385 			data.dwState = NIS_HIDDEN; // windows vista
5386 			data.dwStateMask = NIS_HIDDEN; // windows vista
5387 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5388 		} else version(OSXCocoa) {
5389 			throw new NotYetImplementedException();
5390 		} else static assert(0);
5391 	}
5392 
5393 	///
5394 	void close () {
5395 		version(X11) {
5396 			if (active) {
5397 				active = false; // event handler will set this too, but meh
5398 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5399 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5400 				flushGui();
5401 			}
5402 		} else version(Windows) {
5403 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5404 		} else version(OSXCocoa) {
5405 			throw new NotYetImplementedException();
5406 		} else static assert(0);
5407 	}
5408 
5409 	~this() {
5410 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5411 		version(X11)
5412 			if(clippixmap != None)
5413 				XFreePixmap(XDisplayConnection.get, clippixmap);
5414 		close();
5415 	}
5416 }
5417 
5418 version(X11)
5419 /// Call `XFreePixmap` on the return value.
5420 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5421 	char[] data = new char[](i.width * i.height / 8 + 2);
5422 	data[] = 0;
5423 
5424 	int bitOffset = 0;
5425 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5426 		ubyte v = c.a > 128 ? 1 : 0;
5427 		data[bitOffset / 8] |= v << (bitOffset%8);
5428 		bitOffset++;
5429 	}
5430 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5431 	return handle;
5432 }
5433 
5434 
5435 // basic functions to make timers
5436 /**
5437 	A timer that will trigger your function on a given interval.
5438 
5439 
5440 	You create a timer with an interval and a callback. It will continue
5441 	to fire on the interval until it is destroyed.
5442 
5443 	There are currently no one-off timers (instead, just create one and
5444 	destroy it when it is triggered) nor are there pause/resume functions -
5445 	the timer must again be destroyed and recreated if you want to pause it.
5446 
5447 	---
5448 	auto timer = new Timer(50, { it happened!; });
5449 	timer.destroy();
5450 	---
5451 
5452 	Timers can only be expected to fire when the event loop is running and only
5453 	once per iteration through the event loop.
5454 
5455 	History:
5456 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5457 		slow could lock up the event loop. It now guarantees other things will
5458 		get a chance to run between timer calls, even if that means not keeping up
5459 		with the requested interval.
5460 */
5461 version(with_timer) {
5462 class Timer {
5463 // FIXME: needs pause and unpause
5464 	// FIXME: I might add overloads for ones that take a count of
5465 	// how many elapsed since last time (on Windows, it will divide
5466 	// the ticks thing given, on Linux it is just available) and
5467 	// maybe one that takes an instance of the Timer itself too
5468 	/// Create a timer with a callback when it triggers.
5469 	this(int intervalInMilliseconds, void delegate() onPulse) {
5470 		assert(onPulse !is null);
5471 
5472 		this.intervalInMilliseconds = intervalInMilliseconds;
5473 		this.onPulse = onPulse;
5474 
5475 		version(Windows) {
5476 			/*
5477 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5478 			if(handle == 0)
5479 				throw new WindowsApiException("SetTimer", GetLastError());
5480 			*/
5481 
5482 			// thanks to Archival 998 for the WaitableTimer blocks
5483 			handle = CreateWaitableTimer(null, false, null);
5484 			long initialTime = -intervalInMilliseconds;
5485 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5486 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5487 
5488 			mapping[handle] = this;
5489 
5490 		} else version(linux) {
5491 			static import ep = core.sys.linux.epoll;
5492 
5493 			import core.sys.linux.timerfd;
5494 
5495 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5496 			if(fd == -1)
5497 				throw new Exception("timer create failed");
5498 
5499 			mapping[fd] = this;
5500 
5501 			itimerspec value;
5502 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5503 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5504 
5505 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5506 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5507 
5508 			if(timerfd_settime(fd, 0, &value, null) == -1)
5509 				throw new Exception("couldn't make pulse timer");
5510 
5511 			version(with_eventloop) {
5512 				import arsd.eventloop;
5513 				addFileEventListeners(fd, &trigger, null, null);
5514 			} else {
5515 				prepareEventLoop();
5516 
5517 				ep.epoll_event ev = void;
5518 				ev.events = ep.EPOLLIN;
5519 				ev.data.fd = fd;
5520 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5521 			}
5522 		} else featureNotImplemented();
5523 	}
5524 
5525 	private int intervalInMilliseconds;
5526 
5527 	// just cuz I sometimes call it this.
5528 	alias dispose = destroy;
5529 
5530 	/// Stop and destroy the timer object.
5531 	void destroy() {
5532 		version(Windows) {
5533 			staticDestroy(handle);
5534 			handle = null;
5535 		} else version(linux) {
5536 			staticDestroy(fd);
5537 			fd = -1;
5538 		} else featureNotImplemented();
5539 	}
5540 
5541 	version(Windows)
5542 	static void staticDestroy(HANDLE handle) {
5543 		if(handle) {
5544 			// KillTimer(null, handle);
5545 			CancelWaitableTimer(cast(void*)handle);
5546 			mapping.remove(handle);
5547 			CloseHandle(handle);
5548 		}
5549 	}
5550 	else version(linux)
5551 	static void staticDestroy(int fd) {
5552 		if(fd != -1) {
5553 			import unix = core.sys.posix.unistd;
5554 			static import ep = core.sys.linux.epoll;
5555 
5556 			version(with_eventloop) {
5557 				import arsd.eventloop;
5558 				removeFileEventListeners(fd);
5559 			} else {
5560 				ep.epoll_event ev = void;
5561 				ev.events = ep.EPOLLIN;
5562 				ev.data.fd = fd;
5563 
5564 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5565 			}
5566 			unix.close(fd);
5567 			mapping.remove(fd);
5568 		}
5569 	}
5570 
5571 	~this() {
5572 		version(Windows) { if(handle)
5573 			cleanupQueue.queue!staticDestroy(handle);
5574 		} else version(linux) { if(fd != -1)
5575 			cleanupQueue.queue!staticDestroy(fd);
5576 		}
5577 	}
5578 
5579 
5580 	void changeTime(int intervalInMilliseconds)
5581 	{
5582 		this.intervalInMilliseconds = intervalInMilliseconds;
5583 		version(Windows)
5584 		{
5585 			if(handle)
5586 			{
5587 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5588 				long initialTime = -intervalInMilliseconds;
5589 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5590 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
5591 			}
5592 		}
5593 	}
5594 
5595 
5596 	private:
5597 
5598 	void delegate() onPulse;
5599 
5600 	int lastEventLoopRoundTriggered;
5601 
5602 	void trigger() {
5603 		version(linux) {
5604 			import unix = core.sys.posix.unistd;
5605 			long val;
5606 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5607 		} else version(Windows) {
5608 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5609 				return; // never try to actually run faster than the event loop
5610 			lastEventLoopRoundTriggered = eventLoopRound;
5611 		} else featureNotImplemented();
5612 
5613 		onPulse();
5614 	}
5615 
5616 	version(Windows)
5617 	void rearm() {
5618 
5619 	}
5620 
5621 	version(Windows)
5622 		extern(Windows)
5623 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5624 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5625 			if(Timer* t = timer in mapping) {
5626 				try
5627 				(*t).trigger();
5628 				catch(Exception e) { sdpy_abort(e); assert(0); }
5629 			}
5630 		}
5631 
5632 	version(Windows) {
5633 		//UINT_PTR handle;
5634 		//static Timer[UINT_PTR] mapping;
5635 		HANDLE handle;
5636 		__gshared Timer[HANDLE] mapping;
5637 	} else version(linux) {
5638 		int fd = -1;
5639 		__gshared Timer[int] mapping;
5640 	} else static assert(0, "timer not supported");
5641 }
5642 }
5643 
5644 version(Windows)
5645 private int eventLoopRound;
5646 
5647 version(Windows)
5648 /// 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
5649 class WindowsHandleReader {
5650 	///
5651 	this(void delegate() onReady, HANDLE handle) {
5652 		this.onReady = onReady;
5653 		this.handle = handle;
5654 
5655 		mapping[handle] = this;
5656 
5657 		enable();
5658 	}
5659 
5660 	///
5661 	void enable() {
5662 		auto el = EventLoop.get().impl;
5663 		el.handles ~= handle;
5664 	}
5665 
5666 	///
5667 	void disable() {
5668 		auto el = EventLoop.get().impl;
5669 		for(int i = 0; i < el.handles.length; i++) {
5670 			if(el.handles[i] is handle) {
5671 				el.handles[i] = el.handles[$-1];
5672 				el.handles = el.handles[0 .. $-1];
5673 				return;
5674 			}
5675 		}
5676 	}
5677 
5678 	void dispose() {
5679 		disable();
5680 		if(handle)
5681 			mapping.remove(handle);
5682 		handle = null;
5683 	}
5684 
5685 	void ready() {
5686 		if(onReady)
5687 			onReady();
5688 	}
5689 
5690 	HANDLE handle;
5691 	void delegate() onReady;
5692 
5693 	__gshared WindowsHandleReader[HANDLE] mapping;
5694 }
5695 
5696 version(Posix)
5697 /// Lets you add files to the event loop for reading. Use at your own risk.
5698 class PosixFdReader {
5699 	///
5700 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5701 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5702 	}
5703 
5704 	///
5705 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5706 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5707 	}
5708 
5709 	///
5710 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5711 		this.onReady = onReady;
5712 		this.fd = fd;
5713 		this.captureWrites = captureWrites;
5714 		this.captureReads = captureReads;
5715 
5716 		mapping[fd] = this;
5717 
5718 		version(with_eventloop) {
5719 			import arsd.eventloop;
5720 			addFileEventListeners(fd, &readyel);
5721 		} else {
5722 			enable();
5723 		}
5724 	}
5725 
5726 	bool captureReads;
5727 	bool captureWrites;
5728 
5729 	version(with_eventloop) {} else
5730 	///
5731 	void enable() {
5732 		prepareEventLoop();
5733 
5734 		enabled = true;
5735 
5736 		version(linux) {
5737 			static import ep = core.sys.linux.epoll;
5738 			ep.epoll_event ev = void;
5739 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5740 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5741 			ev.data.fd = fd;
5742 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5743 		} else {
5744 
5745 		}
5746 	}
5747 
5748 	version(with_eventloop) {} else
5749 	///
5750 	void disable() {
5751 		prepareEventLoop();
5752 
5753 		enabled = false;
5754 
5755 		version(linux) {
5756 			static import ep = core.sys.linux.epoll;
5757 			ep.epoll_event ev = void;
5758 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5759 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5760 			ev.data.fd = fd;
5761 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5762 		}
5763 	}
5764 
5765 	version(with_eventloop) {} else
5766 	///
5767 	void dispose() {
5768 		if(enabled)
5769 			disable();
5770 		if(fd != -1)
5771 			mapping.remove(fd);
5772 		fd = -1;
5773 	}
5774 
5775 	void delegate(int, bool, bool) onReady;
5776 
5777 	version(with_eventloop)
5778 	void readyel() {
5779 		onReady(fd, true, true);
5780 	}
5781 
5782 	void ready(uint flags) {
5783 		version(linux) {
5784 			static import ep = core.sys.linux.epoll;
5785 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5786 		} else {
5787 			import core.sys.posix.poll;
5788 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5789 		}
5790 	}
5791 
5792 	void hup(uint flags) {
5793 		if(onHup)
5794 			onHup();
5795 	}
5796 
5797 	void delegate() onHup;
5798 
5799 	int fd = -1;
5800 	private bool enabled;
5801 	__gshared PosixFdReader[int] mapping;
5802 }
5803 
5804 // basic functions to access the clipboard
5805 /+
5806 
5807 
5808 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5809 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5810 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5811 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5812 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5813 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5814 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5815 
5816 +/
5817 
5818 /++
5819 	this does a delegate because it is actually an async call on X...
5820 	the receiver may never be called if the clipboard is empty or unavailable
5821 	gets plain text from the clipboard.
5822 +/
5823 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5824 	version(Windows) {
5825 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5826 		if(OpenClipboard(hwndOwner) == 0)
5827 			throw new WindowsApiException("OpenClipboard", GetLastError());
5828 		scope(exit)
5829 			CloseClipboard();
5830 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5831 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5832 
5833 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5834 				scope(exit)
5835 					GlobalUnlock(dataHandle);
5836 
5837 				// FIXME: CR/LF conversions
5838 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5839 				int len = 0;
5840 				auto d = data;
5841 				while(*d) {
5842 					d++;
5843 					len++;
5844 				}
5845 				string s;
5846 				s.reserve(len);
5847 				foreach(dchar ch; data[0 .. len]) {
5848 					s ~= ch;
5849 				}
5850 				receiver(s);
5851 			}
5852 		}
5853 	} else version(X11) {
5854 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5855 	} else version(OSXCocoa) {
5856 		throw new NotYetImplementedException();
5857 	} else static assert(0);
5858 }
5859 
5860 // FIXME: a clipboard listener might be cool btw
5861 
5862 /++
5863 	this does a delegate because it is actually an async call on X...
5864 	the receiver may never be called if the clipboard is empty or unavailable
5865 	gets image from the clipboard.
5866 
5867 	templated because it introduces an optional dependency on arsd.bmp
5868 +/
5869 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5870 	version(Windows) {
5871 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5872 		if(OpenClipboard(hwndOwner) == 0)
5873 			throw new WindowsApiException("OpenClipboard", GetLastError());
5874 		scope(exit)
5875 			CloseClipboard();
5876 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5877 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5878 				scope(exit)
5879 					GlobalUnlock(dataHandle);
5880 
5881 				auto len = GlobalSize(dataHandle);
5882 
5883 				import arsd.bmp;
5884 				auto img = readBmp(data[0 .. len], false);
5885 				receiver(img);
5886 			}
5887 		}
5888 	} else version(X11) {
5889 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5890 	} else version(OSXCocoa) {
5891 		throw new NotYetImplementedException();
5892 	} else static assert(0);
5893 }
5894 
5895 /// Copies some text to the clipboard.
5896 void setClipboardText(SimpleWindow clipboardOwner, string text) {
5897 	assert(clipboardOwner !is null);
5898 	version(Windows) {
5899 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5900 			throw new WindowsApiException("OpenClipboard", GetLastError());
5901 		scope(exit)
5902 			CloseClipboard();
5903 		EmptyClipboard();
5904 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5905 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
5906 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
5907 		if(auto data = cast(wchar*) GlobalLock(handle)) {
5908 			auto slice = data[0 .. sz];
5909 			scope(failure)
5910 				GlobalUnlock(handle);
5911 
5912 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5913 
5914 			GlobalUnlock(handle);
5915 			SetClipboardData(CF_UNICODETEXT, handle);
5916 		}
5917 	} else version(X11) {
5918 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
5919 	} else version(OSXCocoa) {
5920 		throw new NotYetImplementedException();
5921 	} else static assert(0);
5922 }
5923 
5924 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
5925 	assert(clipboardOwner !is null);
5926 	version(Windows) {
5927 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5928 			throw new WindowsApiException("OpenClipboard", GetLastError());
5929 		scope(exit)
5930 			CloseClipboard();
5931 		EmptyClipboard();
5932 
5933 
5934 		import arsd.bmp;
5935 		ubyte[] mdata;
5936 		mdata.reserve(img.width * img.height);
5937 		void sink(ubyte b) {
5938 			mdata ~= b;
5939 		}
5940 		writeBmpIndirect(img, &sink, false);
5941 
5942 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
5943 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
5944 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
5945 			auto slice = data[0 .. mdata.length];
5946 			scope(failure)
5947 				GlobalUnlock(handle);
5948 
5949 			slice[] = mdata[];
5950 
5951 			GlobalUnlock(handle);
5952 			SetClipboardData(CF_DIB, handle);
5953 		}
5954 	} else version(X11) {
5955 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
5956 			mixin X11SetSelectionHandler_Basics;
5957 			private const(ubyte)[] mdata;
5958 			private const(ubyte)[] mdata_original;
5959 			this(MemoryImage img) {
5960 				import arsd.bmp;
5961 
5962 				mdata.reserve(img.width * img.height);
5963 				void sink(ubyte b) {
5964 					mdata ~= b;
5965 				}
5966 				writeBmpIndirect(img, &sink, true);
5967 
5968 				mdata_original = mdata;
5969 			}
5970 
5971 			Atom[] availableFormats() {
5972 				auto display = XDisplayConnection.get;
5973 				return [
5974 					GetAtom!"image/bmp"(display),
5975 					GetAtom!"TARGETS"(display)
5976 				];
5977 			}
5978 
5979 			ubyte[] getData(Atom format, return scope ubyte[] data) {
5980 				if(mdata.length < data.length) {
5981 					data[0 .. mdata.length] = mdata[];
5982 					auto ret = data[0 .. mdata.length];
5983 					mdata = mdata[$..$];
5984 					return ret;
5985 				} else {
5986 					data[] = mdata[0 .. data.length];
5987 					mdata = mdata[data.length .. $];
5988 					return data[];
5989 				}
5990 			}
5991 
5992 			void done() {
5993 				mdata = mdata_original;
5994 			}
5995 		}
5996 
5997 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
5998 	} else version(OSXCocoa) {
5999 		throw new NotYetImplementedException();
6000 	} else static assert(0);
6001 }
6002 
6003 
6004 version(X11) {
6005 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6006 
6007 	private Atom*[] interredAtoms; // for discardAndRecreate
6008 
6009 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6010 	/// Platform-specific for X11.
6011 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6012 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6013 		static Atom a;
6014 		if(!a) {
6015 			a = XInternAtom(display, name, !create);
6016 			interredAtoms ~= &a;
6017 		}
6018 		if(a == None)
6019 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6020 		return a;
6021 	}
6022 
6023 	/// Platform-specific for X11 - gets atom names as a string.
6024 	string getAtomName(Atom atom, Display* display) {
6025 		auto got = XGetAtomName(display, atom);
6026 		scope(exit) XFree(got);
6027 		import core.stdc.string;
6028 		string s = got[0 .. strlen(got)].idup;
6029 		return s;
6030 	}
6031 
6032 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6033 	void setPrimarySelection(SimpleWindow window, string text) {
6034 		setX11Selection!"PRIMARY"(window, text);
6035 	}
6036 
6037 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6038 	void setSecondarySelection(SimpleWindow window, string text) {
6039 		setX11Selection!"SECONDARY"(window, text);
6040 	}
6041 
6042 	interface X11SetSelectionHandler {
6043 		// should include TARGETS right now
6044 		Atom[] availableFormats();
6045 		// Return the slice of data you filled, empty slice if done.
6046 		// this is to support the incremental thing
6047 		ubyte[] getData(Atom format, return scope ubyte[] data);
6048 
6049 		void done();
6050 
6051 		void handleRequest(XEvent);
6052 
6053 		bool matchesIncr(Window, Atom);
6054 		void sendMoreIncr(XPropertyEvent*);
6055 	}
6056 
6057 	mixin template X11SetSelectionHandler_Basics() {
6058 		Window incrWindow;
6059 		Atom incrAtom;
6060 		Atom selectionAtom;
6061 		Atom formatAtom;
6062 		ubyte[] toSend;
6063 		bool matchesIncr(Window w, Atom a) {
6064 			return incrAtom && incrAtom == a && w == incrWindow;
6065 		}
6066 		void sendMoreIncr(XPropertyEvent* event) {
6067 			auto display = XDisplayConnection.get;
6068 
6069 			XChangeProperty (display,
6070 				incrWindow,
6071 				incrAtom,
6072 				formatAtom,
6073 				8 /* bits */, PropModeReplace,
6074 				toSend.ptr, cast(int) toSend.length);
6075 
6076 			if(toSend.length != 0) {
6077 				toSend = this.getData(formatAtom, toSend[]);
6078 			} else {
6079 				this.done();
6080 				incrWindow = None;
6081 				incrAtom = None;
6082 				selectionAtom = None;
6083 				formatAtom = None;
6084 				toSend = null;
6085 			}
6086 		}
6087 		void handleRequest(XEvent ev) {
6088 
6089 			auto display = XDisplayConnection.get;
6090 
6091 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6092 			XSelectionEvent selectionEvent;
6093 			selectionEvent.type = EventType.SelectionNotify;
6094 			selectionEvent.display = event.display;
6095 			selectionEvent.requestor = event.requestor;
6096 			selectionEvent.selection = event.selection;
6097 			selectionEvent.time = event.time;
6098 			selectionEvent.target = event.target;
6099 
6100 			bool supportedType() {
6101 				foreach(t; this.availableFormats())
6102 					if(t == event.target)
6103 						return true;
6104 				return false;
6105 			}
6106 
6107 			if(event.property == None) {
6108 				selectionEvent.property = event.target;
6109 
6110 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6111 				XFlush(display);
6112 			} if(event.target == GetAtom!"TARGETS"(display)) {
6113 				/* respond with the supported types */
6114 				auto tlist = this.availableFormats();
6115 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6116 				selectionEvent.property = event.property;
6117 
6118 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6119 				XFlush(display);
6120 			} else if(supportedType()) {
6121 				auto buffer = new ubyte[](1024 * 64);
6122 				auto toSend = this.getData(event.target, buffer[]);
6123 
6124 				if(toSend.length < 32 * 1024) {
6125 					// small enough to send directly...
6126 					selectionEvent.property = event.property;
6127 					XChangeProperty (display,
6128 						selectionEvent.requestor,
6129 						selectionEvent.property,
6130 						event.target,
6131 						8 /* bits */, 0 /* PropModeReplace */,
6132 						toSend.ptr, cast(int) toSend.length);
6133 
6134 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6135 					XFlush(display);
6136 				} else {
6137 					// large, let's send incrementally
6138 					arch_ulong l = toSend.length;
6139 
6140 					// if I wanted other events from this window don't want to clear that out....
6141 					XWindowAttributes xwa;
6142 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6143 
6144 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6145 
6146 					incrWindow = event.requestor;
6147 					incrAtom = event.property;
6148 					formatAtom = event.target;
6149 					selectionAtom = event.selection;
6150 					this.toSend = toSend;
6151 
6152 					selectionEvent.property = event.property;
6153 					XChangeProperty (display,
6154 						selectionEvent.requestor,
6155 						selectionEvent.property,
6156 						GetAtom!"INCR"(display),
6157 						32 /* bits */, PropModeReplace,
6158 						&l, 1);
6159 
6160 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6161 					XFlush(display);
6162 				}
6163 				//if(after)
6164 					//after();
6165 			} else {
6166 				debug(sdpy_clip) {
6167 					writeln("Unsupported data ", getAtomName(event.target, display));
6168 				}
6169 				selectionEvent.property = None; // I don't know how to handle this type...
6170 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6171 				XFlush(display);
6172 			}
6173 		}
6174 	}
6175 
6176 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6177 		mixin X11SetSelectionHandler_Basics;
6178 		private const(ubyte)[] text;
6179 		private const(ubyte)[] text_original;
6180 		this(string text) {
6181 			this.text = cast(const ubyte[]) text;
6182 			this.text_original = this.text;
6183 		}
6184 		Atom[] availableFormats() {
6185 			auto display = XDisplayConnection.get;
6186 			return [
6187 				GetAtom!"UTF8_STRING"(display),
6188 				GetAtom!"text/plain"(display),
6189 				XA_STRING,
6190 				GetAtom!"TARGETS"(display)
6191 			];
6192 		}
6193 
6194 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6195 			if(text.length < data.length) {
6196 				data[0 .. text.length] = text[];
6197 				return data[0 .. text.length];
6198 			} else {
6199 				data[] = text[0 .. data.length];
6200 				text = text[data.length .. $];
6201 				return data[];
6202 			}
6203 		}
6204 
6205 		void done() {
6206 			text = text_original;
6207 		}
6208 	}
6209 
6210 	/// 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?!)
6211 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6212 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6213 	}
6214 
6215 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6216 		assert(window !is null);
6217 
6218 		auto display = XDisplayConnection.get();
6219 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6220 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6221 		else Atom a = GetAtom!atomName(display);
6222 
6223 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6224 
6225 		window.impl.setSelectionHandlers[a] = data;
6226 	}
6227 
6228 	///
6229 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6230 		getX11Selection!"PRIMARY"(window, handler);
6231 	}
6232 
6233 	// added July 28, 2020
6234 	// undocumented as experimental tho
6235 	interface X11GetSelectionHandler {
6236 		void handleData(Atom target, in ubyte[] data);
6237 		Atom findBestFormat(Atom[] answer);
6238 
6239 		void prepareIncremental(Window, Atom);
6240 		bool matchesIncr(Window, Atom);
6241 		void handleIncrData(Atom, in ubyte[] data);
6242 	}
6243 
6244 	mixin template X11GetSelectionHandler_Basics() {
6245 		Window incrWindow;
6246 		Atom incrAtom;
6247 
6248 		void prepareIncremental(Window w, Atom a) {
6249 			incrWindow = w;
6250 			incrAtom = a;
6251 		}
6252 		bool matchesIncr(Window w, Atom a) {
6253 			return incrWindow == w && incrAtom == a;
6254 		}
6255 
6256 		Atom incrFormatAtom;
6257 		ubyte[] incrData;
6258 		void handleIncrData(Atom format, in ubyte[] data) {
6259 			incrFormatAtom = format;
6260 
6261 			if(data.length)
6262 				incrData ~= data;
6263 			else
6264 				handleData(incrFormatAtom, incrData);
6265 
6266 		}
6267 	}
6268 
6269 	///
6270 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6271 		assert(window !is null);
6272 
6273 		auto display = XDisplayConnection.get();
6274 		auto atom = GetAtom!atomName(display);
6275 
6276 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6277 			this(void delegate(in char[]) handler) {
6278 				this.handler = handler;
6279 			}
6280 
6281 			mixin X11GetSelectionHandler_Basics;
6282 
6283 			void delegate(in char[]) handler;
6284 
6285 			void handleData(Atom target, in ubyte[] data) {
6286 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6287 					handler(cast(const char[]) data);
6288 			}
6289 
6290 			Atom findBestFormat(Atom[] answer) {
6291 				Atom best = None;
6292 				foreach(option; answer) {
6293 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6294 						best = option;
6295 						break;
6296 					} else if(option == XA_STRING) {
6297 						best = option;
6298 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6299 						best = option;
6300 					}
6301 				}
6302 				return best;
6303 			}
6304 		}
6305 
6306 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6307 
6308 		auto target = GetAtom!"TARGETS"(display);
6309 
6310 		// SDD_DATA is "simpledisplay.d data"
6311 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6312 	}
6313 
6314 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6315 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6316 		assert(window !is null);
6317 
6318 		auto display = XDisplayConnection.get();
6319 		auto atom = GetAtom!atomName(display);
6320 
6321 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6322 			this(void delegate(MemoryImage) handler) {
6323 				this.handler = handler;
6324 			}
6325 
6326 			mixin X11GetSelectionHandler_Basics;
6327 
6328 			void delegate(MemoryImage) handler;
6329 
6330 			void handleData(Atom target, in ubyte[] data) {
6331 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6332 					import arsd.bmp;
6333 					handler(readBmp(data));
6334 				}
6335 			}
6336 
6337 			Atom findBestFormat(Atom[] answer) {
6338 				Atom best = None;
6339 				foreach(option; answer) {
6340 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6341 						best = option;
6342 					}
6343 				}
6344 				return best;
6345 			}
6346 
6347 		}
6348 
6349 
6350 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6351 
6352 		auto target = GetAtom!"TARGETS"(display);
6353 
6354 		// SDD_DATA is "simpledisplay.d data"
6355 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6356 	}
6357 
6358 
6359 	///
6360 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6361 		Atom actualType;
6362 		int actualFormat;
6363 		arch_ulong actualItems;
6364 		arch_ulong bytesRemaining;
6365 		void* data;
6366 
6367 		auto display = XDisplayConnection.get();
6368 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6369 			if(actualFormat == 0)
6370 				return null;
6371 			else {
6372 				int byteLength;
6373 				if(actualFormat == 32) {
6374 					// 32 means it is a C long... which is variable length
6375 					actualFormat = cast(int) arch_long.sizeof * 8;
6376 				}
6377 
6378 				// then it is just a bit count
6379 				byteLength = cast(int) (actualItems * actualFormat / 8);
6380 
6381 				auto d = new ubyte[](byteLength);
6382 				d[] = cast(ubyte[]) data[0 .. byteLength];
6383 				XFree(data);
6384 				return d;
6385 			}
6386 		}
6387 		return null;
6388 	}
6389 
6390 	/* defined in the systray spec */
6391 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6392 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6393 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6394 
6395 
6396 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6397 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6398 	public class GlobalHotkey {
6399 		KeyEvent key;
6400 		void delegate () handler;
6401 
6402 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6403 
6404 		/// Create from initialzed KeyEvent object
6405 		this (KeyEvent akey, void delegate () ahandler=null) {
6406 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6407 			key = akey;
6408 			handler = ahandler;
6409 		}
6410 
6411 		/// Create from emacs-like key name ("C-M-Y", etc.)
6412 		this (const(char)[] akey, void delegate () ahandler=null) {
6413 			key = KeyEvent.parse(akey);
6414 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6415 			handler = ahandler;
6416 		}
6417 
6418 	}
6419 
6420 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6421 		//conwriteln("failed to grab key");
6422 		GlobalHotkeyManager.ghfailed = true;
6423 		return 0;
6424 	}
6425 
6426 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6427 		Image.impl.xshmfailed = true;
6428 		return 0;
6429 	}
6430 
6431 	private __gshared int errorHappened;
6432 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6433 		import core.stdc.stdio;
6434 		char[265] buffer;
6435 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6436 		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);
6437 		errorHappened = true;
6438 		return 0;
6439 	}
6440 
6441 	/++
6442 		Global hotkey manager. It contains static methods to manage global hotkeys.
6443 
6444 		---
6445 		 try {
6446 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6447 		} catch (Exception e) {
6448 			conwriteln("ERROR registering hotkey!");
6449 		}
6450 		EventLoop.get.run();
6451 		---
6452 
6453 		The key strings are based on Emacs. In practical terms,
6454 		`M` means `alt` and `H` means the Windows logo key. `C`
6455 		is `ctrl`.
6456 
6457 		$(WARNING
6458 			This is X-specific right now. If you are on
6459 			Windows, try [registerHotKey] instead.
6460 
6461 			We will probably merge these into a single
6462 			interface later.
6463 		)
6464 	+/
6465 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6466 		version(X11) {
6467 			void recreateAfterDisconnect() {
6468 				throw new Exception("NOT IMPLEMENTED");
6469 			}
6470 			void discardConnectionState() {
6471 				throw new Exception("NOT IMPLEMENTED");
6472 			}
6473 		}
6474 
6475 		private static immutable uint[8] masklist = [ 0,
6476 			KeyOrButtonMask.LockMask,
6477 			KeyOrButtonMask.Mod2Mask,
6478 			KeyOrButtonMask.Mod3Mask,
6479 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6480 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6481 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6482 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6483 		];
6484 		private __gshared GlobalHotkeyManager ghmanager;
6485 		private __gshared bool ghfailed = false;
6486 
6487 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6488 			if (modmask == 0) return false;
6489 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6490 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6491 			return true;
6492 		}
6493 
6494 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6495 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6496 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6497 			return modmask;
6498 		}
6499 
6500 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6501 			uint keycode = cast(uint)ke.key;
6502 			auto dpy = XDisplayConnection.get;
6503 			return XKeysymToKeycode(dpy, keycode);
6504 		}
6505 
6506 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6507 
6508 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6509 
6510 		NativeEventHandler getNativeEventHandler () {
6511 			return delegate int (XEvent e) {
6512 				if (e.type != EventType.KeyPress) return 1;
6513 				auto kev = cast(const(XKeyEvent)*)&e;
6514 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6515 				if (auto ghkp = hash in globalHotkeyList) {
6516 					try {
6517 						ghkp.doHandle();
6518 					} catch (Exception e) {
6519 						import core.stdc.stdio : stderr, fprintf;
6520 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6521 					}
6522 				}
6523 				return 1;
6524 			};
6525 		}
6526 
6527 		private this () {
6528 			auto dpy = XDisplayConnection.get;
6529 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6530 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6531 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6532 		}
6533 
6534 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6535 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6536 		static void register (GlobalHotkey gh) {
6537 			if (gh is null) return;
6538 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6539 
6540 			auto dpy = XDisplayConnection.get;
6541 			immutable keycode = keyEvent2KeyCode(gh.key);
6542 
6543 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6544 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6545 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6546 			XSync(dpy, 0/*False*/);
6547 
6548 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6549 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6550 			ghfailed = false;
6551 			foreach (immutable uint ormask; masklist[]) {
6552 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6553 			}
6554 			XSync(dpy, 0/*False*/);
6555 			XSetErrorHandler(savedErrorHandler);
6556 
6557 			if (ghfailed) {
6558 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6559 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6560 				XSync(dpy, 0/*False*/);
6561 				XSetErrorHandler(savedErrorHandler);
6562 				throw new Exception("cannot register global hotkey");
6563 			}
6564 
6565 			globalHotkeyList[hash] = gh;
6566 		}
6567 
6568 		/// Ditto
6569 		static void register (const(char)[] akey, void delegate () ahandler) {
6570 			register(new GlobalHotkey(akey, ahandler));
6571 		}
6572 
6573 		private static void removeByHash (ulong hash) {
6574 			if (auto ghp = hash in globalHotkeyList) {
6575 				auto dpy = XDisplayConnection.get;
6576 				immutable keycode = keyEvent2KeyCode(ghp.key);
6577 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6578 				XSync(dpy, 0/*False*/);
6579 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6580 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6581 				XSync(dpy, 0/*False*/);
6582 				XSetErrorHandler(savedErrorHandler);
6583 				globalHotkeyList.remove(hash);
6584 			}
6585 		}
6586 
6587 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6588 		/// It is safe to unregister unknown or invalid hotkey.
6589 		static void unregister (GlobalHotkey gh) {
6590 			//TODO: add second AA for faster search? prolly doesn't worth it.
6591 			if (gh is null) return;
6592 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6593 				if (kv.value is gh) {
6594 					removeByHash(kv.key);
6595 					return;
6596 				}
6597 			}
6598 		}
6599 
6600 		/// Ditto.
6601 		static void unregister (const(char)[] key) {
6602 			auto kev = KeyEvent.parse(key);
6603 			immutable keycode = keyEvent2KeyCode(kev);
6604 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6605 		}
6606 	}
6607 }
6608 
6609 version(Windows) {
6610 	/++
6611 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6612 
6613 		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).
6614 	+/
6615 	void sendSyntheticInput(wstring s) {
6616 			INPUT[] inputs;
6617 			inputs.reserve(s.length * 2);
6618 
6619 			foreach(wchar c; s) {
6620 				INPUT input;
6621 				input.type = INPUT_KEYBOARD;
6622 				input.ki.wScan = c;
6623 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6624 				inputs ~= input;
6625 
6626 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6627 				inputs ~= input;
6628 			}
6629 
6630 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6631 				throw new WindowsApiException("SendInput", GetLastError());
6632 			}
6633 
6634 	}
6635 
6636 
6637 	// global hotkey helper function
6638 
6639 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6640 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6641 		__gshared int hotkeyId = 0;
6642 		int id = ++hotkeyId;
6643 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6644 			throw new Exception("RegisterHotKey");
6645 
6646 		__gshared void delegate()[WPARAM][HWND] handlers;
6647 
6648 		handlers[window.impl.hwnd][id] = handler;
6649 
6650 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6651 
6652 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6653 			switch(msg) {
6654 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6655 				case WM_HOTKEY:
6656 					if(auto list = hwnd in handlers) {
6657 						if(auto h = wParam in *list) {
6658 							(*h)();
6659 							return 0;
6660 						}
6661 					}
6662 				goto default;
6663 				default:
6664 			}
6665 			if(oldHandler)
6666 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6667 			return 1; // pass it on
6668 		};
6669 
6670 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6671 			oldHandler = window.handleNativeEvent;
6672 			window.handleNativeEvent = nativeEventHandler;
6673 		}
6674 
6675 		return id;
6676 	}
6677 
6678 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6679 	void unregisterHotKey(SimpleWindow window, int id) {
6680 		if(!UnregisterHotKey(window.impl.hwnd, id))
6681 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6682 	}
6683 }
6684 
6685 version (X11) {
6686 	pragma(lib, "dl");
6687 	import core.sys.posix.dlfcn;
6688 }
6689 
6690 /++
6691 	Allows for sending synthetic input to the X server via the Xtst
6692 	extension or on Windows using SendInput.
6693 
6694 	Please remember user input is meant to be user - don't use this
6695 	if you have some other alternative!
6696 
6697 	History:
6698 		Added May 17, 2020 with the X implementation.
6699 
6700 		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.)
6701 	Bugs:
6702 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6703 +/
6704 struct SyntheticInput {
6705 	@disable this();
6706 
6707 	private int* refcount;
6708 
6709 	version(X11) {
6710 		private void* lib;
6711 
6712 		private extern(C) {
6713 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6714 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6715 		}
6716 	}
6717 
6718 	/// The dummy param must be 0.
6719 	this(int dummy) {
6720 		version(X11) {
6721 			lib = dlopen("libXtst.so", RTLD_NOW);
6722 			if(lib is null)
6723 				throw new Exception("cannot load xtest lib extension");
6724 			scope(failure)
6725 				dlclose(lib);
6726 
6727 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6728 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6729 
6730 			if(XTestFakeKeyEvent is null)
6731 				throw new Exception("No XTestFakeKeyEvent");
6732 			if(XTestFakeButtonEvent is null)
6733 				throw new Exception("No XTestFakeButtonEvent");
6734 		}
6735 
6736 		refcount = new int;
6737 		*refcount = 1;
6738 	}
6739 
6740 	this(this) {
6741 		if(refcount)
6742 			*refcount += 1;
6743 	}
6744 
6745 	~this() {
6746 		if(refcount) {
6747 			*refcount -= 1;
6748 			if(*refcount == 0)
6749 				// I commented this because if I close the lib before
6750 				// XCloseDisplay, it is liable to segfault... so just
6751 				// gonna keep it loaded if it is loaded, no big deal
6752 				// anyway.
6753 				{} // dlclose(lib);
6754 		}
6755 	}
6756 
6757 	/++
6758 		Simulates typing a string into the keyboard.
6759 
6760 		Bugs:
6761 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6762 
6763 			Not implemented except on Windows and X11.
6764 	+/
6765 	void sendSyntheticInput(string s) {
6766 		version(Windows) {
6767 			INPUT[] inputs;
6768 			inputs.reserve(s.length * 2);
6769 
6770 			auto ei = GetMessageExtraInfo();
6771 
6772 			foreach(wchar c; s) {
6773 				INPUT input;
6774 				input.type = INPUT_KEYBOARD;
6775 				input.ki.wScan = c;
6776 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6777 				input.ki.dwExtraInfo = ei;
6778 				inputs ~= input;
6779 
6780 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6781 				inputs ~= input;
6782 			}
6783 
6784 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6785 				throw new WindowsApiException("SendInput", GetLastError());
6786 			}
6787 		} else version(X11) {
6788 			int delay = 0;
6789 			foreach(ch; s) {
6790 				pressKey(cast(Key) ch, true, delay);
6791 				pressKey(cast(Key) ch, false, delay);
6792 				delay += 5;
6793 			}
6794 		} else throw new NotYetImplementedException();
6795 	}
6796 
6797 	/++
6798 		Sends a fake press or release key event.
6799 
6800 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6801 
6802 		Bugs:
6803 			The `delay` parameter is not implemented yet on Windows.
6804 
6805 			Not implemented except on Windows and X11.
6806 	+/
6807 	void pressKey(Key key, bool pressed, int delay = 0) {
6808 		version(Windows) {
6809 			INPUT input;
6810 			input.type = INPUT_KEYBOARD;
6811 			input.ki.wVk = cast(ushort) key;
6812 
6813 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6814 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6815 
6816 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6817 				throw new WindowsApiException("SendInput", GetLastError());
6818 			}
6819 		} else version(X11) {
6820 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6821 		} else throw new NotYetImplementedException();
6822 	}
6823 
6824 	/++
6825 		Sends a fake mouse button press or release event.
6826 
6827 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6828 
6829 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6830 
6831 		Bugs:
6832 			The `delay` parameter is not implemented yet on Windows.
6833 
6834 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6835 
6836 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6837 	+/
6838 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6839 		version(Windows) {
6840 			INPUT input;
6841 			input.type = INPUT_MOUSE;
6842 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6843 
6844 			// input.mi.mouseData for a wheel event
6845 
6846 			switch(button) {
6847 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6848 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6849 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6850 				case MouseButton.wheelUp:
6851 				case MouseButton.wheelDown:
6852 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6853 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6854 				break;
6855 				case MouseButton.backButton: throw new NotYetImplementedException();
6856 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6857 				default:
6858 			}
6859 
6860 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6861 				throw new WindowsApiException("SendInput", GetLastError());
6862 			}
6863 		} else version(X11) {
6864 			int btn;
6865 
6866 			switch(button) {
6867 				case MouseButton.left: btn = 1; break;
6868 				case MouseButton.middle: btn = 2; break;
6869 				case MouseButton.right: btn = 3; break;
6870 				case MouseButton.wheelUp: btn = 4; break;
6871 				case MouseButton.wheelDown: btn = 5; break;
6872 				case MouseButton.backButton: btn = 8; break;
6873 				case MouseButton.forwardButton: btn = 9; break;
6874 				default:
6875 			}
6876 
6877 			assert(btn);
6878 
6879 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6880 		} else throw new NotYetImplementedException();
6881 	}
6882 
6883 	///
6884 	static void moveMouseArrowBy(int dx, int dy) {
6885 		version(Windows) {
6886 			INPUT input;
6887 			input.type = INPUT_MOUSE;
6888 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6889 			input.mi.dx = dx;
6890 			input.mi.dy = dy;
6891 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
6892 
6893 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6894 				throw new WindowsApiException("SendInput", GetLastError());
6895 			}
6896 		} else version(X11) {
6897 			auto disp = XDisplayConnection.get();
6898 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
6899 			XFlush(disp);
6900 		} else throw new NotYetImplementedException();
6901 	}
6902 
6903 	///
6904 	static void moveMouseArrowTo(int x, int y) {
6905 		version(Windows) {
6906 			INPUT input;
6907 			input.type = INPUT_MOUSE;
6908 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6909 			input.mi.dx = x;
6910 			input.mi.dy = y;
6911 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
6912 
6913 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6914 				throw new WindowsApiException("SendInput", GetLastError());
6915 			}
6916 		} else version(X11) {
6917 			auto disp = XDisplayConnection.get();
6918 			auto root = RootWindow(disp, DefaultScreen(disp));
6919 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
6920 			XFlush(disp);
6921 		} else throw new NotYetImplementedException();
6922 	}
6923 }
6924 
6925 
6926 
6927 /++
6928 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
6929 
6930 	See_Also:
6931 	$(LIST
6932 		*[ScreenPainter]
6933 		*[ScreenPainter.rasterOp]
6934 	)
6935 +/
6936 enum RasterOp {
6937 	normal, /// Replaces the pixel.
6938 	xor, /// Uses bitwise xor to draw.
6939 }
6940 
6941 // being phobos-free keeps the size WAY down
6942 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
6943 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
6944 package(arsd) const(wchar)* toWStringz(string s) {
6945 	wstring r;
6946 	foreach(dchar c; s)
6947 		r ~= c;
6948 	r ~= '\0';
6949 	return r.ptr;
6950 }
6951 private string[] split(in void[] a, char c) {
6952 		string[] ret;
6953 		size_t previous = 0;
6954 		foreach(i, char ch; cast(ubyte[]) a) {
6955 			if(ch == c) {
6956 				ret ~= cast(string) a[previous .. i];
6957 				previous = i + 1;
6958 			}
6959 		}
6960 		if(previous != a.length)
6961 			ret ~= cast(string) a[previous .. $];
6962 		return ret;
6963 	}
6964 
6965 version(without_opengl) {
6966 	enum OpenGlOptions {
6967 		no,
6968 	}
6969 } else {
6970 	/++
6971 		Determines if you want an OpenGL context created on the new window.
6972 
6973 
6974 		See more: [#topics-3d|in the 3d topic].
6975 
6976 		---
6977 		import arsd.simpledisplay;
6978 		void main() {
6979 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
6980 
6981 			// Set up the matrix
6982 			window.setAsCurrentOpenGlContext(); // make this window active
6983 
6984 			// This is called on each frame, we will draw our scene
6985 			window.redrawOpenGlScene = delegate() {
6986 
6987 			};
6988 
6989 			window.eventLoop(0);
6990 		}
6991 		---
6992 	+/
6993 	enum OpenGlOptions {
6994 		no, /// No OpenGL context is created
6995 		yes, /// Yes, create an OpenGL context
6996 	}
6997 
6998 	version(X11) {
6999 		static if (!SdpyIsUsingIVGLBinds) {
7000 
7001 
7002 			struct __GLXFBConfigRec {}
7003 			alias GLXFBConfig = __GLXFBConfigRec*;
7004 
7005 			//pragma(lib, "GL");
7006 			//pragma(lib, "GLU");
7007 			interface GLX {
7008 			extern(C) nothrow @nogc {
7009 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7010 						const int *attrib_list);
7011 
7012 				 void glXCopyContext(Display *dpy, GLXContext src,
7013 						GLXContext dst, arch_ulong mask);
7014 
7015 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7016 						GLXContext share_list, Bool direct);
7017 
7018 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7019 						Pixmap pixmap);
7020 
7021 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7022 
7023 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7024 
7025 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7026 						int attrib, int *value);
7027 
7028 				 GLXContext glXGetCurrentContext();
7029 
7030 				 GLXDrawable glXGetCurrentDrawable();
7031 
7032 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7033 
7034 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7035 						GLXContext ctx);
7036 
7037 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7038 
7039 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7040 
7041 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7042 
7043 				 void glXUseXFont(Font font, int first, int count, int list_base);
7044 
7045 				 void glXWaitGL();
7046 
7047 				 void glXWaitX();
7048 
7049 
7050 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7051 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7052 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7053 
7054 				char* glXQueryExtensionsString (Display*, int);
7055 				void* glXGetProcAddress (const(char)*);
7056 
7057 			}
7058 			}
7059 
7060 			version(OSX)
7061 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7062 			else
7063 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7064 			shared static this() {
7065 				glx.loadDynamicLibrary();
7066 			}
7067 
7068 			alias glbindGetProcAddress = glXGetProcAddress;
7069 		}
7070 	} else version(Windows) {
7071 		/* it is done below by interface GL */
7072 	} else
7073 		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.");
7074 }
7075 
7076 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7077 alias Resizablity = Resizability;
7078 
7079 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7080 enum Resizability {
7081 	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.
7082 	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.
7083 	/++
7084 		$(PITFALL
7085 			Planned for the future but not implemented.
7086 		)
7087 
7088 		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.
7089 
7090 		History:
7091 			Added November 11, 2022, but not yet implemented and may not be for some time.
7092 	+/
7093 	/*@__future*/ allowResizingMaintainingAspectRatio,
7094 	/++
7095 		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.
7096 
7097 		History:
7098 			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.
7099 
7100 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7101 	+/
7102 	automaticallyScaleIfPossible,
7103 }
7104 
7105 
7106 /++
7107 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7108 +/
7109 enum TextAlignment : uint {
7110 	Left = 0, ///
7111 	Center = 1, ///
7112 	Right = 2, ///
7113 
7114 	VerticalTop = 0, ///
7115 	VerticalCenter = 4, ///
7116 	VerticalBottom = 8, ///
7117 }
7118 
7119 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7120 alias Rectangle = arsd.color.Rectangle;
7121 
7122 
7123 /++
7124 	Keyboard press and release events.
7125 +/
7126 struct KeyEvent {
7127 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7128 	Key key;
7129 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7130 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7131 
7132 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7133 
7134 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7135 
7136 	SimpleWindow window; /// associated Window
7137 
7138 	/++
7139 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7140 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7141 		to predict if char events are actually coming..
7142 
7143 		Only available on X systems since this information is not given ahead of time elsewhere.
7144 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7145 
7146 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7147 		and potential quirks I'd recommend avoiding it.
7148 
7149 		History:
7150 			Added April 26, 2021 (dub v9.5)
7151 	+/
7152 	version(X11)
7153 		dchar[] charsPossible;
7154 
7155 	// convert key event to simplified string representation a-la emacs
7156 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7157 		uint dpos = 0;
7158 		void put (const(char)[] s...) nothrow @trusted {
7159 			static if (growdest) {
7160 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7161 			} else {
7162 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7163 			}
7164 		}
7165 
7166 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7167 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7168 		}
7169 
7170 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7171 
7172 		// put modifiers
7173 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7174 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7175 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7176 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7177 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7178 
7179 		if (this.key) {
7180 			foreach (string kn; __traits(allMembers, Key)) {
7181 				if (this.key == __traits(getMember, Key, kn)) {
7182 					// HACK!
7183 					static if (kn == "N0") put("0");
7184 					else static if (kn == "N1") put("1");
7185 					else static if (kn == "N2") put("2");
7186 					else static if (kn == "N3") put("3");
7187 					else static if (kn == "N4") put("4");
7188 					else static if (kn == "N5") put("5");
7189 					else static if (kn == "N6") put("6");
7190 					else static if (kn == "N7") put("7");
7191 					else static if (kn == "N8") put("8");
7192 					else static if (kn == "N9") put("9");
7193 					else put(kn);
7194 					return dest[0..dpos];
7195 				}
7196 			}
7197 			put("Unknown");
7198 		} else {
7199 			if (dpos && dest[dpos-1] == '+') --dpos;
7200 		}
7201 		return dest[0..dpos];
7202 	}
7203 
7204 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7205 
7206 	/** Parse string into key name with modifiers. It accepts things like:
7207 	 *
7208 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7209 	 *
7210 	 * Ctrl+Win+1 -- windows style
7211 	 *
7212 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7213 	 *
7214 	 * Ctrl Win 1 -- and space
7215 	 *
7216 	 * and even "Win + 1 + Ctrl".
7217 	 */
7218 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7219 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7220 
7221 		// remove trailing spaces
7222 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7223 
7224 		// tokens delimited by blank, '+', or '-'
7225 		// null on eol
7226 		const(char)[] getToken () nothrow @trusted @nogc {
7227 			// remove leading spaces and delimiters
7228 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7229 			if (name.length == 0) return null; // oops, no more tokens
7230 			// get token
7231 			size_t epos = 0;
7232 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7233 			assert(epos > 0 && epos <= name.length);
7234 			auto res = name[0..epos];
7235 			name = name[epos..$];
7236 			return res;
7237 		}
7238 
7239 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7240 			if (s0.length != s1.length) return false;
7241 			foreach (immutable ci, char c0; s0) {
7242 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7243 				char c1 = s1[ci];
7244 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7245 				if (c0 != c1) return false;
7246 			}
7247 			return true;
7248 		}
7249 
7250 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7251 		if (updown !is null) *updown = -1;
7252 		KeyEvent res;
7253 		res.key = cast(Key)0; // just in case
7254 		const(char)[] tk, tkn; // last token
7255 		bool allowEmascStyle = true;
7256 		bool ignoreModifiers = false;
7257 		tokenloop: for (;;) {
7258 			tk = tkn;
7259 			tkn = getToken();
7260 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7261 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7262 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7263 			if (allowEmascStyle && tkn.length != 0) {
7264 				if (tk.length == 1) {
7265 					char mdc = tk[0];
7266 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7267 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7268 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7269 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7270 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7271 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7272 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7273 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7274 				}
7275 			}
7276 			allowEmascStyle = false;
7277 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7278 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7279 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7280 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7281 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7282 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7283 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7284 			if (tk.length == 0) continue;
7285 			// try key name
7286 			if (res.key == 0) {
7287 				// little hack
7288 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7289 					final switch (tk[0]) {
7290 						case '0': tk = "N0"; break;
7291 						case '1': tk = "N1"; break;
7292 						case '2': tk = "N2"; break;
7293 						case '3': tk = "N3"; break;
7294 						case '4': tk = "N4"; break;
7295 						case '5': tk = "N5"; break;
7296 						case '6': tk = "N6"; break;
7297 						case '7': tk = "N7"; break;
7298 						case '8': tk = "N8"; break;
7299 						case '9': tk = "N9"; break;
7300 					}
7301 				}
7302 				foreach (string kn; __traits(allMembers, Key)) {
7303 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7304 				}
7305 			}
7306 			// unknown or duplicate key name, get out of here
7307 			break;
7308 		}
7309 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7310 		return res; // something
7311 	}
7312 
7313 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7314 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7315 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7316 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7317 		}
7318 		bool ignoreMods;
7319 		int updown;
7320 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7321 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7322 		if (this.key != ke.key) {
7323 			// things like "ctrl+alt" are complicated
7324 			uint tkm = this.modifierState&modmask;
7325 			uint kkm = ke.modifierState&modmask;
7326 			Key tk = this.key;
7327 			// ke
7328 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7329 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7330 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7331 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7332 			// this
7333 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7334 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7335 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7336 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7337 			return (tk == ke.key && tkm == kkm);
7338 		}
7339 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7340 	}
7341 }
7342 
7343 /// Sets the application name.
7344 @property string ApplicationName(string name) {
7345 	return _applicationName = name;
7346 }
7347 
7348 string _applicationName;
7349 
7350 /// ditto
7351 @property string ApplicationName() {
7352 	if(_applicationName is null) {
7353 		import core.runtime;
7354 		return Runtime.args[0];
7355 	}
7356 	return _applicationName;
7357 }
7358 
7359 
7360 /// Type of a [MouseEvent].
7361 enum MouseEventType : int {
7362 	motion = 0, /// The mouse moved inside the window
7363 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7364 	buttonReleased = 2, /// A mouse button was released
7365 }
7366 
7367 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7368 /++
7369 	Listen for this on your event listeners if you are interested in mouse action.
7370 
7371 	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.
7372 
7373 	Examples:
7374 
7375 	This will draw boxes on the window with the mouse as you hold the left button.
7376 	---
7377 	import arsd.simpledisplay;
7378 
7379 	void main() {
7380 		auto window = new SimpleWindow();
7381 
7382 		window.eventLoop(0,
7383 			(MouseEvent ev) {
7384 				if(ev.modifierState & ModifierState.leftButtonDown) {
7385 					auto painter = window.draw();
7386 					painter.fillColor = Color.red;
7387 					painter.outlineColor = Color.black;
7388 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7389 				}
7390 			}
7391 		);
7392 	}
7393 	---
7394 +/
7395 struct MouseEvent {
7396 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7397 
7398 	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.
7399 	int y; /// Current Y position of the cursor when the event fired.
7400 
7401 	int dx; /// Change in X position since last report
7402 	int dy; /// Change in Y position since last report
7403 
7404 	MouseButton button; /// See [MouseButton]
7405 	int modifierState; /// See [ModifierState]
7406 
7407 	version(X11)
7408 		private Time timestamp;
7409 
7410 	/// Returns a linear representation of mouse button,
7411 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7412 	///
7413 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7414 	@property ubyte buttonLinear() const {
7415 		import core.bitop;
7416 		if(button == 0)
7417 			return 0;
7418 		return (bsf(button) + 1) & 0b1111;
7419 	}
7420 
7421 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7422 
7423 	SimpleWindow window; /// The window in which the event happened.
7424 
7425 	Point globalCoordinates() {
7426 		Point p;
7427 		if(window is null)
7428 			throw new Exception("wtf");
7429 		static if(UsingSimpledisplayX11) {
7430 			Window child;
7431 			XTranslateCoordinates(
7432 				XDisplayConnection.get,
7433 				window.impl.window,
7434 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7435 				x, y, &p.x, &p.y, &child);
7436 			return p;
7437 		} else version(Windows) {
7438 			POINT[1] points;
7439 			points[0].x = x;
7440 			points[0].y = y;
7441 			MapWindowPoints(
7442 				window.impl.hwnd,
7443 				null,
7444 				points.ptr,
7445 				points.length
7446 			);
7447 			p.x = points[0].x;
7448 			p.y = points[0].y;
7449 
7450 			return p;
7451 		} else version(OSXCocoa) {
7452 			throw new NotYetImplementedException();
7453 		} else static assert(0);
7454 	}
7455 
7456 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7457 
7458 	/**
7459 	can contain emacs-like modifier prefix
7460 	case-insensitive names:
7461 		lmbX/leftX
7462 		rmbX/rightX
7463 		mmbX/middleX
7464 		wheelX
7465 		motion (no prefix allowed)
7466 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7467 	*/
7468 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7469 		if (str.length == 0) return false; // just in case
7470 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7471 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7472 		auto anchor = str;
7473 		uint mods = 0; // uint.max == any
7474 		// interesting bits in kmod
7475 		uint kmodmask =
7476 			ModifierState.shift|
7477 			ModifierState.ctrl|
7478 			ModifierState.alt|
7479 			ModifierState.windows|
7480 			ModifierState.leftButtonDown|
7481 			ModifierState.middleButtonDown|
7482 			ModifierState.rightButtonDown|
7483 			0;
7484 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7485 		bool wasButtons = false;
7486 		while (str.length) {
7487 			if (str.ptr[0] <= ' ') {
7488 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7489 				continue;
7490 			}
7491 			// one-letter modifier?
7492 			if (str.length >= 2 && str.ptr[1] == '-') {
7493 				switch (str.ptr[0]) {
7494 					case '*': // "any" modifier (cannot be undone)
7495 						mods = mods.max;
7496 						break;
7497 					case 'C': case 'c': // emacs "ctrl"
7498 						if (mods != mods.max) mods |= ModifierState.ctrl;
7499 						break;
7500 					case 'M': case 'm': // emacs "meta"
7501 						if (mods != mods.max) mods |= ModifierState.alt;
7502 						break;
7503 					case 'S': case 's': // emacs "shift"
7504 						if (mods != mods.max) mods |= ModifierState.shift;
7505 						break;
7506 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7507 						if (mods != mods.max) mods |= ModifierState.windows;
7508 						break;
7509 					default:
7510 						return false; // unknown modifier
7511 				}
7512 				str = str[2..$];
7513 				continue;
7514 			}
7515 			// word
7516 			char[16] buf = void; // locased
7517 			auto wep = 0;
7518 			while (str.length) {
7519 				immutable char ch = str.ptr[0];
7520 				if (ch <= ' ' || ch == '-') break;
7521 				str = str[1..$];
7522 				if (wep > buf.length) return false; // too long
7523 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7524 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7525 				else return false; // invalid char
7526 			}
7527 			if (wep == 0) return false; // just in case
7528 			uint bnum;
7529 			enum UpDown { None = -1, Up, Down, Any }
7530 			auto updown = UpDown.None; // 0: up; 1: down
7531 			switch (buf[0..wep]) {
7532 				// left button
7533 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7534 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7535 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7536 				case "lmb": case "left": bnum = 0; break;
7537 				// middle button
7538 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7539 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7540 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7541 				case "mmb": case "middle": bnum = 1; break;
7542 				// right button
7543 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7544 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7545 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7546 				case "rmb": case "right": bnum = 2; break;
7547 				// wheel
7548 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7549 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7550 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7551 				case "wheel": bnum = 3; break;
7552 				// motion
7553 				case "motion": bnum = 7; break;
7554 				// unknown
7555 				default: return false;
7556 			}
7557 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7558 			// parse possible "-up" or "-down"
7559 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7560 				wep = 0;
7561 				foreach (immutable idx, immutable char ch; str[1..$]) {
7562 					if (ch <= ' ' || ch == '-') break;
7563 					assert(idx == wep); // for now; trick
7564 					if (wep > buf.length) { wep = 0; break; } // too long
7565 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7566 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7567 					else { wep = 0; break; } // invalid char
7568 				}
7569 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7570 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7571 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7572 				// remove parsed part
7573 				if (updown != UpDown.None) str = str[wep+1..$];
7574 			}
7575 			if (updown == UpDown.None) {
7576 				updown = UpDown.Down;
7577 			}
7578 			wasButtons = wasButtons || (bnum <= 2);
7579 			//assert(updown != UpDown.None);
7580 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7581 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7582 			if (lastButt != lastButt.max) {
7583 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7584 				if (mods != mods.max) {
7585 					uint butbit = 0;
7586 					final switch (lastButt&0x03) {
7587 						case 0: butbit = ModifierState.leftButtonDown; break;
7588 						case 1: butbit = ModifierState.middleButtonDown; break;
7589 						case 2: butbit = ModifierState.rightButtonDown; break;
7590 					}
7591 					     if (lastButt&Flag.Down) mods |= butbit;
7592 					else if (lastButt&Flag.Up) mods &= ~butbit;
7593 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7594 				}
7595 			}
7596 			// remember last button
7597 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7598 		}
7599 		// no button -- nothing to do
7600 		if (lastButt == lastButt.max) return false;
7601 		// done parsing, check if something's left
7602 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7603 		// remove action button from mask
7604 		if ((lastButt&0xff) < 3) {
7605 			final switch (lastButt&0x03) {
7606 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7607 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7608 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7609 			}
7610 		}
7611 		// special case: "Motion" means "ignore buttons"
7612 		if ((lastButt&0xff) == 7 && !wasButtons) {
7613 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7614 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7615 		}
7616 		uint kmod = event.modifierState&kmodmask;
7617 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7618 		// check modifier state
7619 		if (mods != mods.max) {
7620 			if (kmod != mods) return false;
7621 		}
7622 		// now check type
7623 		if ((lastButt&0xff) == 7) {
7624 			// motion
7625 			if (event.type != MouseEventType.motion) return false;
7626 		} else if ((lastButt&0xff) == 3) {
7627 			// wheel
7628 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7629 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7630 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7631 			return false;
7632 		} else {
7633 			// buttons
7634 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7635 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7636 			{
7637 				return false;
7638 			}
7639 			// button number
7640 			switch (lastButt&0x03) {
7641 				case 0: if (event.button != MouseButton.left) return false; break;
7642 				case 1: if (event.button != MouseButton.middle) return false; break;
7643 				case 2: if (event.button != MouseButton.right) return false; break;
7644 				default: return false;
7645 			}
7646 		}
7647 		return true;
7648 	}
7649 }
7650 
7651 version(arsd_mevent_strcmp_test) unittest {
7652 	MouseEvent event;
7653 	event.type = MouseEventType.buttonPressed;
7654 	event.button = MouseButton.left;
7655 	event.modifierState = ModifierState.ctrl;
7656 	assert(event == "C-LMB");
7657 	assert(event != "C-LMBUP");
7658 	assert(event != "C-LMB-UP");
7659 	assert(event != "C-S-LMB");
7660 	assert(event == "*-LMB");
7661 	assert(event != "*-LMB-UP");
7662 
7663 	event.type = MouseEventType.buttonReleased;
7664 	assert(event != "C-LMB");
7665 	assert(event == "C-LMBUP");
7666 	assert(event == "C-LMB-UP");
7667 	assert(event != "C-S-LMB");
7668 	assert(event != "*-LMB");
7669 	assert(event == "*-LMB-UP");
7670 
7671 	event.button = MouseButton.right;
7672 	event.modifierState |= ModifierState.shift;
7673 	event.type = MouseEventType.buttonPressed;
7674 	assert(event != "C-LMB");
7675 	assert(event != "C-LMBUP");
7676 	assert(event != "C-LMB-UP");
7677 	assert(event != "C-S-LMB");
7678 	assert(event != "*-LMB");
7679 	assert(event != "*-LMB-UP");
7680 
7681 	assert(event != "C-RMB");
7682 	assert(event != "C-RMBUP");
7683 	assert(event != "C-RMB-UP");
7684 	assert(event == "C-S-RMB");
7685 	assert(event == "*-RMB");
7686 	assert(event != "*-RMB-UP");
7687 }
7688 
7689 /// This gives a few more options to drawing lines and such
7690 struct Pen {
7691 	Color color; /// the foreground color
7692 	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.
7693 	Style style; /// See [Style]
7694 /+
7695 // From X.h
7696 
7697 #define LineSolid		0
7698 #define LineOnOffDash		1
7699 #define LineDoubleDash		2
7700        LineDou-        The full path of the line is drawn, but the
7701        bleDash         even dashes are filled differently from the
7702                        odd dashes (see fill-style) with CapButt
7703                        style used where even and odd dashes meet.
7704 
7705 
7706 
7707 /* capStyle */
7708 
7709 #define CapNotLast		0
7710 #define CapButt			1
7711 #define CapRound		2
7712 #define CapProjecting		3
7713 
7714 /* joinStyle */
7715 
7716 #define JoinMiter		0
7717 #define JoinRound		1
7718 #define JoinBevel		2
7719 
7720 /* fillStyle */
7721 
7722 #define FillSolid		0
7723 #define FillTiled		1
7724 #define FillStippled		2
7725 #define FillOpaqueStippled	3
7726 
7727 
7728 +/
7729 	/// Style of lines drawn
7730 	enum Style {
7731 		Solid, /// a solid line
7732 		Dashed, /// a dashed line
7733 		Dotted, /// a dotted line
7734 	}
7735 }
7736 
7737 
7738 /++
7739 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7740 
7741 
7742 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7743 
7744 	$(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.)
7745 
7746 	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.
7747 
7748 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7749 
7750 	$(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.
7751 
7752 	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!
7753 
7754 	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!)
7755 
7756 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7757 
7758 	---
7759 		auto image = new Image(256, 256);
7760 		scope(exit) destroy(image);
7761 	---
7762 
7763 	As long as you don't hold on to it outside the scope.
7764 
7765 	I might change it to be an owned pointer at some point in the future.
7766 
7767 	)
7768 
7769 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7770 	you can also often get a fair amount of speedup by getting the raw data format and
7771 	writing some custom code.
7772 
7773 	FIXME INSERT EXAMPLES HERE
7774 
7775 
7776 +/
7777 final class Image {
7778 	///
7779 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7780 		this.width = width;
7781 		this.height = height;
7782 		this.enableAlpha = enableAlpha;
7783 
7784 		impl.createImage(width, height, forcexshm, enableAlpha);
7785 	}
7786 
7787 	///
7788 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7789 		this(size.width, size.height, forcexshm, enableAlpha);
7790 	}
7791 
7792 	private bool suppressDestruction;
7793 
7794 	version(X11)
7795 	this(XImage* handle) {
7796 		this.handle = handle;
7797 		this.rawData = cast(ubyte*) handle.data;
7798 		this.width = handle.width;
7799 		this.height = handle.height;
7800 		this.enableAlpha = handle.depth == 32;
7801 		suppressDestruction = true;
7802 	}
7803 
7804 	~this() {
7805 		if(suppressDestruction) return;
7806 		impl.dispose();
7807 	}
7808 
7809 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7810 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7811 	pure const @system nothrow {
7812 		/*
7813 			To use these to draw a blue rectangle with size WxH at position X,Y...
7814 
7815 			// make certain that it will fit before we proceed
7816 			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!
7817 
7818 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7819 			// (though calculating them isn't really that expensive).
7820 			auto nextLineAdjustment = img.adjustmentForNextLine();
7821 			auto offR = img.redByteOffset();
7822 			auto offB = img.blueByteOffset();
7823 			auto offG = img.greenByteOffset();
7824 			auto bpp = img.bytesPerPixel();
7825 
7826 			auto data = img.getDataPointer();
7827 
7828 			// figure out the starting byte offset
7829 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7830 
7831 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7832 
7833 			// and now our drawing loop for the rectangle
7834 			foreach(y; 0 .. H) {
7835 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7836 				foreach(x; 0 .. W) {
7837 					// write our color
7838 					data[offR] = 0;
7839 					data[offG] = 0;
7840 					data[offB] = 255;
7841 
7842 					data += bpp; // moving to the next pixel is just an addition...
7843 				}
7844 				startOfLine += nextLineAdjustment;
7845 			}
7846 
7847 
7848 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7849 
7850 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7851 			can be made into a bitmask or something so we can write them as *uint...
7852 		*/
7853 
7854 		///
7855 		int offsetForTopLeftPixel() {
7856 			version(X11) {
7857 				return 0;
7858 			} else version(Windows) {
7859 				if(enableAlpha) {
7860 					return (width * 4) * (height - 1);
7861 				} else {
7862 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7863 				}
7864 			} else version(OSXCocoa) {
7865 				return 0 ; //throw new NotYetImplementedException();
7866 			} else static assert(0, "fill in this info for other OSes");
7867 		}
7868 
7869 		///
7870 		int offsetForPixel(int x, int y) {
7871 			version(X11) {
7872 				auto offset = (y * width + x) * 4;
7873 				return offset;
7874 			} else version(Windows) {
7875 				if(enableAlpha) {
7876 					auto itemsPerLine = width * 4;
7877 					// remember, bmps are upside down
7878 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7879 					return offset;
7880 				} else {
7881 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7882 					// remember, bmps are upside down
7883 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7884 					return offset;
7885 				}
7886 			} else version(OSXCocoa) {
7887 				return 0 ; //throw new NotYetImplementedException();
7888 			} else static assert(0, "fill in this info for other OSes");
7889 		}
7890 
7891 		///
7892 		int adjustmentForNextLine() {
7893 			version(X11) {
7894 				return width * 4;
7895 			} else version(Windows) {
7896 				// windows bmps are upside down, so the adjustment is actually negative
7897 				if(enableAlpha)
7898 					return - (cast(int) width * 4);
7899 				else
7900 					return -((cast(int) width * 3 + 3) / 4) * 4;
7901 			} else version(OSXCocoa) {
7902 				return 0 ; //throw new NotYetImplementedException();
7903 			} else static assert(0, "fill in this info for other OSes");
7904 		}
7905 
7906 		/// once you have the position of a pixel, use these to get to the proper color
7907 		int redByteOffset() {
7908 			version(X11) {
7909 				return 2;
7910 			} else version(Windows) {
7911 				return 2;
7912 			} else version(OSXCocoa) {
7913 				return 0 ; //throw new NotYetImplementedException();
7914 			} else static assert(0, "fill in this info for other OSes");
7915 		}
7916 
7917 		///
7918 		int greenByteOffset() {
7919 			version(X11) {
7920 				return 1;
7921 			} else version(Windows) {
7922 				return 1;
7923 			} else version(OSXCocoa) {
7924 				return 0 ; //throw new NotYetImplementedException();
7925 			} else static assert(0, "fill in this info for other OSes");
7926 		}
7927 
7928 		///
7929 		int blueByteOffset() {
7930 			version(X11) {
7931 				return 0;
7932 			} else version(Windows) {
7933 				return 0;
7934 			} else version(OSXCocoa) {
7935 				return 0 ; //throw new NotYetImplementedException();
7936 			} else static assert(0, "fill in this info for other OSes");
7937 		}
7938 
7939 		/// Only valid if [enableAlpha] is true
7940 		int alphaByteOffset() {
7941 			version(X11) {
7942 				return 3;
7943 			} else version(Windows) {
7944 				return 3;
7945 			} else version(OSXCocoa) {
7946 				return 3; //throw new NotYetImplementedException();
7947 			} else static assert(0, "fill in this info for other OSes");
7948 		}
7949 	}
7950 
7951 	///
7952 	final void putPixel(int x, int y, Color c) {
7953 		if(x < 0 || x >= width)
7954 			return;
7955 		if(y < 0 || y >= height)
7956 			return;
7957 
7958 		impl.setPixel(x, y, c);
7959 	}
7960 
7961 	///
7962 	final Color getPixel(int x, int y) {
7963 		if(x < 0 || x >= width)
7964 			return Color.transparent;
7965 		if(y < 0 || y >= height)
7966 			return Color.transparent;
7967 
7968 		version(OSXCocoa) throw new NotYetImplementedException(); else
7969 		return impl.getPixel(x, y);
7970 	}
7971 
7972 	///
7973 	final void opIndexAssign(Color c, int x, int y) {
7974 		putPixel(x, y, c);
7975 	}
7976 
7977 	///
7978 	TrueColorImage toTrueColorImage() {
7979 		auto tci = new TrueColorImage(width, height);
7980 		convertToRgbaBytes(tci.imageData.bytes);
7981 		return tci;
7982 	}
7983 
7984 	///
7985 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) {
7986 		auto tci = i.getAsTrueColorImage();
7987 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
7988 		img.setRgbaBytes(tci.imageData.bytes);
7989 		return img;
7990 	}
7991 
7992 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
7993 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
7994 	/// if you pass null, it will allocate a new one.
7995 	ubyte[] getRgbaBytes(ubyte[] where = null) {
7996 		if(where is null)
7997 			where = new ubyte[this.width*this.height*4];
7998 		convertToRgbaBytes(where);
7999 		return where;
8000 	}
8001 
8002 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8003 	void setRgbaBytes(in ubyte[] from ) {
8004 		assert(from.length == this.width * this.height * 4);
8005 		setFromRgbaBytes(from);
8006 	}
8007 
8008 	// FIXME: make properly cross platform by getting rgba right
8009 
8010 	/// warning: this is not portable across platforms because the data format can change
8011 	ubyte* getDataPointer() {
8012 		return impl.rawData;
8013 	}
8014 
8015 	/// for use with getDataPointer
8016 	final int bytesPerLine() const pure @safe nothrow {
8017 		version(Windows)
8018 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8019 		else version(X11)
8020 			return 4 * width;
8021 		else version(OSXCocoa)
8022 			return 4 * width;
8023 		else static assert(0);
8024 	}
8025 
8026 	/// for use with getDataPointer
8027 	final int bytesPerPixel() const pure @safe nothrow {
8028 		version(Windows)
8029 			return enableAlpha ? 4 : 3;
8030 		else version(X11)
8031 			return 4;
8032 		else version(OSXCocoa)
8033 			return 4;
8034 		else static assert(0);
8035 	}
8036 
8037 	///
8038 	immutable int width;
8039 
8040 	///
8041 	immutable int height;
8042 
8043 	///
8044 	immutable bool enableAlpha;
8045     //private:
8046 	mixin NativeImageImplementation!() impl;
8047 }
8048 
8049 /++
8050 	A convenience function to pop up a window displaying the image.
8051 	If you pass a win, it will draw the image in it. Otherwise, it will
8052 	create a window with the size of the image and run its event loop, closing
8053 	when a key is pressed.
8054 
8055 	History:
8056 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8057 		always block until the application quit which could cause bizarre behavior
8058 		inside a more complex application. Now, the default is to block until
8059 		this window closes if it is the only event loop running, and otherwise,
8060 		not to block at all and just pop up the display window asynchronously.
8061 +/
8062 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8063 	if(win is null) {
8064 		win = new SimpleWindow(image);
8065 		{
8066 			auto p = win.draw;
8067 			p.drawImage(Point(0, 0), image);
8068 		}
8069 		win.eventLoopWithBlockingMode(
8070 			bm, 0,
8071 			(KeyEvent ev) {
8072 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8073 			} );
8074 	} else {
8075 		win.image = image;
8076 	}
8077 }
8078 
8079 enum FontWeight : int {
8080 	dontcare = 0,
8081 	thin = 100,
8082 	extralight = 200,
8083 	light = 300,
8084 	regular = 400,
8085 	medium = 500,
8086 	semibold = 600,
8087 	bold = 700,
8088 	extrabold = 800,
8089 	heavy = 900
8090 }
8091 
8092 /++
8093 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8094 
8095 	History:
8096 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8097 +/
8098 interface MeasurableFont {
8099 	/++
8100 		Returns true if it is a monospace font, meaning each of the
8101 		glyphs (at least the ascii characters) have matching width
8102 		and no kerning, so you can determine the display width of some
8103 		strings by simply multiplying the string width by [averageWidth].
8104 
8105 		(Please note that multiply doesn't $(I actually) work in general,
8106 		consider characters like tab and newline, but it does sometimes.)
8107 	+/
8108 	bool isMonospace();
8109 
8110 	/++
8111 		The average width of glyphs in the font, traditionally equal to the
8112 		width of the lowercase x. Can be used to estimate bounding boxes,
8113 		especially if the font [isMonospace].
8114 
8115 		Given in pixels.
8116 	+/
8117 	int averageWidth();
8118 	/++
8119 		The height of the bounding box of a line.
8120 	+/
8121 	int height();
8122 	/++
8123 		The maximum ascent of a glyph above the baseline.
8124 
8125 		Given in pixels.
8126 	+/
8127 	int ascent();
8128 	/++
8129 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8130 
8131 		Given in pixels.
8132 	+/
8133 	int descent();
8134 	/++
8135 		The display width of the given string, and if you provide a window, it will use it to
8136 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8137 
8138 		Given in pixels.
8139 	+/
8140 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8141 
8142 }
8143 
8144 // FIXME: i need a font cache and it needs to handle disconnects.
8145 
8146 /++
8147 	Represents a font loaded off the operating system or the X server.
8148 
8149 
8150 	While the api here is unified cross platform, the fonts are not necessarily
8151 	available, even across machines of the same platform, so be sure to always check
8152 	for null (using [isNull]) and have a fallback plan.
8153 
8154 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8155 
8156 	Worst case, a null font will automatically fall back to the default font loaded
8157 	for your system.
8158 +/
8159 class OperatingSystemFont : MeasurableFont {
8160 	// FIXME: when the X Connection is lost, these need to be invalidated!
8161 	// that means I need to store the original stuff again to reconstruct it too.
8162 
8163 	version(X11) {
8164 		XFontStruct* font;
8165 		XFontSet fontset;
8166 
8167 		version(with_xft) {
8168 			XftFont* xftFont;
8169 			bool isXft;
8170 		}
8171 	} else version(Windows) {
8172 		HFONT font;
8173 		int width_;
8174 		int height_;
8175 	} else version(OSXCocoa) {
8176 		// FIXME
8177 	} else static assert(0);
8178 
8179 	/++
8180 		Constructs the class and immediately calls [load].
8181 	+/
8182 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8183 		load(name, size, weight, italic);
8184 	}
8185 
8186 	/++
8187 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8188 
8189 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8190 
8191 		History:
8192 			Added January 24, 2021.
8193 	+/
8194 	this() {
8195 		// this space intentionally left blank
8196 	}
8197 
8198 	/++
8199 		Constructs a copy of the given font object.
8200 
8201 		History:
8202 			Added January 7, 2023.
8203 	+/
8204 	this(OperatingSystemFont font) {
8205 		if(font is null || font.loadedInfo is LoadedInfo.init)
8206 			loadDefault();
8207 		else
8208 			load(font.loadedInfo.tupleof);
8209 	}
8210 
8211 	/++
8212 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8213 
8214 		History:
8215 			Added November 13, 2020.
8216 	+/
8217 	version(with_xft)
8218 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8219 		unload();
8220 
8221 		if(!XftLibrary.attempted) {
8222 			XftLibrary.loadDynamicLibrary();
8223 		}
8224 
8225 		if(!XftLibrary.loadSuccessful)
8226 			return false;
8227 
8228 		auto display = XDisplayConnection.get;
8229 
8230 		char[256] nameBuffer = void;
8231 		int nbp = 0;
8232 
8233 		void add(in char[] a) {
8234 			nameBuffer[nbp .. nbp + a.length] = a[];
8235 			nbp += a.length;
8236 		}
8237 		add(name);
8238 
8239 		if(size) {
8240 			add(":size=");
8241 			add(toInternal!string(size));
8242 		}
8243 		if(weight != FontWeight.dontcare) {
8244 			add(":weight=");
8245 			add(weightToString(weight));
8246 		}
8247 		if(italic)
8248 			add(":slant=100");
8249 
8250 		nameBuffer[nbp] = 0;
8251 
8252 		this.xftFont = XftFontOpenName(
8253 			display,
8254 			DefaultScreen(display),
8255 			nameBuffer.ptr
8256 		);
8257 
8258 		this.isXft = true;
8259 
8260 		if(xftFont !is null) {
8261 			isMonospace_ = stringWidth("x") == stringWidth("M");
8262 			ascent_ = xftFont.ascent;
8263 			descent_ = xftFont.descent;
8264 		}
8265 
8266 		return !isNull();
8267 	}
8268 
8269 	/++
8270 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8271 
8272 
8273 		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.
8274 
8275 		If `pattern` is null, it returns all available font families.
8276 
8277 		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.
8278 
8279 		The format of the pattern is platform-specific.
8280 
8281 		History:
8282 			Added May 1, 2021 (dub v9.5)
8283 	+/
8284 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8285 		version(Windows) {
8286 			auto hdc = GetDC(null);
8287 			scope(exit) ReleaseDC(null, hdc);
8288 			LOGFONT logfont;
8289 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8290 				auto localHandler = *(cast(typeof(handler)*) p);
8291 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8292 			}
8293 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8294 		} else version(X11) {
8295 			//import core.stdc.stdio;
8296 			bool done = false;
8297 			version(with_xft) {
8298 				if(!XftLibrary.attempted) {
8299 					XftLibrary.loadDynamicLibrary();
8300 				}
8301 
8302 				if(!XftLibrary.loadSuccessful)
8303 					goto skipXft;
8304 
8305 				if(!FontConfigLibrary.attempted)
8306 					FontConfigLibrary.loadDynamicLibrary();
8307 				if(!FontConfigLibrary.loadSuccessful)
8308 					goto skipXft;
8309 
8310 				{
8311 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8312 					if(got is null)
8313 						goto skipXft;
8314 					scope(exit) FcFontSetDestroy(got);
8315 
8316 					auto fontPatterns = got.fonts[0 .. got.nfont];
8317 					foreach(candidate; fontPatterns) {
8318 						char* where, whereStyle;
8319 
8320 						char* pmg = FcNameUnparse(candidate);
8321 
8322 						//FcPatternGetString(candidate, "family", 0, &where);
8323 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8324 						//if(where && whereStyle) {
8325 						if(pmg) {
8326 							if(!handler(pmg.sliceCString))
8327 								return;
8328 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8329 						}
8330 					}
8331 				}
8332 			}
8333 
8334 			skipXft:
8335 
8336 			if(pattern is null)
8337 				pattern = "*";
8338 
8339 			int count;
8340 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8341 			scope(exit) XFreeFontNames(coreFontsRaw);
8342 
8343 			auto coreFonts = coreFontsRaw[0 .. count];
8344 
8345 			foreach(font; coreFonts) {
8346 				char[128] tmp;
8347 				tmp[0 ..5] = "core:";
8348 				auto cf = font.sliceCString;
8349 				if(5 + cf.length > tmp.length)
8350 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8351 				tmp[5 .. 5 + cf.length] = cf;
8352 				if(!handler(tmp[0 .. 5 + cf.length]))
8353 					return;
8354 			}
8355 		}
8356 	}
8357 
8358 	/++
8359 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8360 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8361 
8362 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8363 		underlying system doesn't support returning the raw bytes.
8364 
8365 		History:
8366 			Added September 10, 2021 (dub v10.3)
8367 	+/
8368 	ubyte[] getTtfBytes() {
8369 		if(isNull)
8370 			return null;
8371 
8372 		version(Windows) {
8373 			auto dc = GetDC(null);
8374 			auto orig = SelectObject(dc, font);
8375 
8376 			scope(exit) {
8377 				SelectObject(dc, orig);
8378 				ReleaseDC(null, dc);
8379 			}
8380 
8381 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8382 			if(res == GDI_ERROR)
8383 				return null;
8384 
8385 			ubyte[] buffer = new ubyte[](res);
8386 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8387 			if(res == GDI_ERROR)
8388 				return null; // wtf really tbh
8389 
8390 			return buffer;
8391 		} else version(with_xft) {
8392 			if(isXft && xftFont) {
8393 				if(!FontConfigLibrary.attempted)
8394 					FontConfigLibrary.loadDynamicLibrary();
8395 				if(!FontConfigLibrary.loadSuccessful)
8396 					return null;
8397 
8398 				char* file;
8399 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8400 					if (file !is null && file[0]) {
8401 						import core.stdc.stdio;
8402 						auto fp = fopen(file, "rb");
8403 						if(fp is null)
8404 							return null;
8405 						scope(exit)
8406 							fclose(fp);
8407 						fseek(fp, 0, SEEK_END);
8408 						ubyte[] buffer = new ubyte[](ftell(fp));
8409 						fseek(fp, 0, SEEK_SET);
8410 
8411 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8412 						if(got != buffer.length)
8413 							return null;
8414 
8415 						return buffer;
8416 					}
8417 				}
8418 			}
8419 			return null;
8420 		}
8421 	}
8422 
8423 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8424 
8425 	private string weightToString(FontWeight weight) {
8426 		with(FontWeight)
8427 		final switch(weight) {
8428 			case dontcare: return "*";
8429 			case thin: return "extralight";
8430 			case extralight: return "extralight";
8431 			case light: return "light";
8432 			case regular: return "regular";
8433 			case medium: return "medium";
8434 			case semibold: return "demibold";
8435 			case bold: return "bold";
8436 			case extrabold: return "demibold";
8437 			case heavy: return "black";
8438 		}
8439 	}
8440 
8441 	/++
8442 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8443 
8444 		History:
8445 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8446 	+/
8447 	version(X11)
8448 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8449 		unload();
8450 
8451 		string xfontstr;
8452 
8453 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8454 			// this is kinda a disgusting hack but if the user sends an exact
8455 			// string I'd like to honor it...
8456 			xfontstr = name;
8457 		} else {
8458 			string weightstr = weightToString(weight);
8459 			string sizestr;
8460 			if(size == 0)
8461 				sizestr = "*";
8462 			else
8463 				sizestr = toInternal!string(size);
8464 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8465 		}
8466 
8467 		// writeln(xfontstr);
8468 
8469 		auto display = XDisplayConnection.get;
8470 
8471 		font = XLoadQueryFont(display, xfontstr.ptr);
8472 		if(font is null)
8473 			return false;
8474 
8475 		char** lol;
8476 		int lol2;
8477 		char* lol3;
8478 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8479 
8480 		prepareFontInfo();
8481 
8482 		return !isNull();
8483 	}
8484 
8485 	version(X11)
8486 	private void prepareFontInfo() {
8487 		if(font !is null) {
8488 			isMonospace_ = stringWidth("l") == stringWidth("M");
8489 			ascent_ = font.max_bounds.ascent;
8490 			descent_ = font.max_bounds.descent;
8491 		}
8492 	}
8493 
8494 	/++
8495 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8496 
8497 		History:
8498 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8499 	+/
8500 	version(Windows)
8501 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8502 		unload();
8503 
8504 		WCharzBuffer buffer = WCharzBuffer(name);
8505 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8506 
8507 		prepareFontInfo(hdc);
8508 
8509 		return !isNull();
8510 	}
8511 
8512 	version(Windows)
8513 	void prepareFontInfo(HDC hdc = null) {
8514 		if(font is null)
8515 			return;
8516 
8517 		TEXTMETRIC tm;
8518 		auto dc = hdc ? hdc : GetDC(null);
8519 		auto orig = SelectObject(dc, font);
8520 		GetTextMetrics(dc, &tm);
8521 		SelectObject(dc, orig);
8522 		if(hdc is null)
8523 			ReleaseDC(null, dc);
8524 
8525 		width_ = tm.tmAveCharWidth;
8526 		height_ = tm.tmHeight;
8527 		ascent_ = tm.tmAscent;
8528 		descent_ = tm.tmDescent;
8529 		// 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.
8530 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8531 	}
8532 
8533 
8534 	/++
8535 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8536 
8537 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8538 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8539 
8540 		On Windows, it forwards directly to [loadWin32].
8541 
8542 		Params:
8543 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8544 			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.
8545 			weight = approximate boldness, results may vary.
8546 			italic = try to get a slanted version of the given font.
8547 
8548 		History:
8549 			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.
8550 	+/
8551 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8552 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8553 		version(X11) {
8554 			version(with_xft) {
8555 				if(name.length > 5 && name[0 .. 5] == "core:") {
8556 					goto core;
8557 				}
8558 
8559 				if(loadXft(name, size, weight, italic))
8560 					return true;
8561 				// if xft fails, fallback to core to avoid breaking
8562 				// code that already depended on this.
8563 			}
8564 
8565 			core:
8566 
8567 			if(name.length > 5 && name[0 .. 5] == "core:") {
8568 				name = name[5 .. $];
8569 			}
8570 
8571 			return loadCoreX(name, size, weight, italic);
8572 		} else version(Windows) {
8573 			return loadWin32(name, size, weight, italic);
8574 		} else version(OSXCocoa) {
8575 			// FIXME
8576 			return false;
8577 		} else static assert(0);
8578 	}
8579 
8580 	private struct LoadedInfo {
8581 		string name;
8582 		int size;
8583 		FontWeight weight;
8584 		bool italic;
8585 	}
8586 	private LoadedInfo loadedInfo;
8587 
8588 	///
8589 	void unload() {
8590 		if(isNull())
8591 			return;
8592 
8593 		version(X11) {
8594 			auto display = XDisplayConnection.display;
8595 
8596 			if(display is null)
8597 				return;
8598 
8599 			version(with_xft) {
8600 				if(isXft) {
8601 					if(xftFont)
8602 						XftFontClose(display, xftFont);
8603 					isXft = false;
8604 					xftFont = null;
8605 					return;
8606 				}
8607 			}
8608 
8609 			if(font && font !is ScreenPainterImplementation.defaultfont)
8610 				XFreeFont(display, font);
8611 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8612 				XFreeFontSet(display, fontset);
8613 
8614 			font = null;
8615 			fontset = null;
8616 		} else version(Windows) {
8617 			DeleteObject(font);
8618 			font = null;
8619 		} else version(OSXCocoa) {
8620 			// FIXME
8621 		} else static assert(0);
8622 	}
8623 
8624 	private bool isMonospace_;
8625 
8626 	/++
8627 		History:
8628 			Added January 16, 2021
8629 	+/
8630 	bool isMonospace() {
8631 		return isMonospace_;
8632 	}
8633 
8634 	/++
8635 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8636 
8637 		History:
8638 			Added March 26, 2020
8639 			Documented January 16, 2021
8640 	+/
8641 	int averageWidth() {
8642 		version(X11) {
8643 			return stringWidth("x");
8644 		} else version(Windows)
8645 			return width_;
8646 		else assert(0);
8647 	}
8648 
8649 	/++
8650 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8651 
8652 		History:
8653 			Added January 16, 2021
8654 	+/
8655 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8656 	// FIXME: what about tab?
8657 		if(isNull)
8658 			return 0;
8659 
8660 		version(X11) {
8661 			version(with_xft)
8662 				if(isXft && xftFont !is null) {
8663 					//return xftFont.max_advance_width;
8664 					XGlyphInfo extents;
8665 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8666 					// writeln(extents);
8667 					return extents.xOff;
8668 				}
8669 			if(font is null)
8670 				return 0;
8671 			else if(fontset) {
8672 				XRectangle rect;
8673 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8674 
8675 				return rect.width;
8676 			} else {
8677 				return XTextWidth(font, s.ptr, cast(int) s.length);
8678 			}
8679 		} else version(Windows) {
8680 			WCharzBuffer buffer = WCharzBuffer(s);
8681 
8682 			return stringWidth(buffer.slice, window);
8683 		}
8684 		else assert(0);
8685 	}
8686 
8687 	version(Windows)
8688 	/// ditto
8689 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8690 		if(isNull)
8691 			return 0;
8692 		version(Windows) {
8693 			SIZE size;
8694 
8695 			prepareContext(window);
8696 			scope(exit) releaseContext();
8697 
8698 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8699 
8700 			return size.cx;
8701 		} else {
8702 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8703 			static assert(0, "not implemented yet");
8704 			//return stringWidth(s, window);
8705 		}
8706 	}
8707 
8708 	private {
8709 		int prepRefcount;
8710 
8711 		version(Windows) {
8712 			HDC dc;
8713 			HANDLE orig;
8714 			HWND hwnd;
8715 		}
8716 	}
8717 	/++
8718 		[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.
8719 
8720 		History:
8721 			Added January 23, 2021
8722 	+/
8723 	void prepareContext(SimpleWindow window = null) {
8724 		prepRefcount++;
8725 		if(prepRefcount == 1) {
8726 			version(Windows) {
8727 				hwnd = window is null ? null : window.impl.hwnd;
8728 				dc = GetDC(hwnd);
8729 				orig = SelectObject(dc, font);
8730 			}
8731 		}
8732 	}
8733 	/// ditto
8734 	void releaseContext() {
8735 		prepRefcount--;
8736 		if(prepRefcount == 0) {
8737 			version(Windows) {
8738 				SelectObject(dc, orig);
8739 				ReleaseDC(hwnd, dc);
8740 				hwnd = null;
8741 				dc = null;
8742 				orig = null;
8743 			}
8744 		}
8745 	}
8746 
8747 	/+
8748 		FIXME: I think I need advance and kerning pair
8749 
8750 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8751 	+/
8752 
8753 	/++
8754 		Returns the height of the font.
8755 
8756 		History:
8757 			Added March 26, 2020
8758 			Documented January 16, 2021
8759 	+/
8760 	int height() {
8761 		version(X11) {
8762 			version(with_xft)
8763 				if(isXft && xftFont !is null) {
8764 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8765 				}
8766 			if(font is null)
8767 				return 0;
8768 			return font.max_bounds.ascent + font.max_bounds.descent;
8769 		} else version(Windows)
8770 			return height_;
8771 		else assert(0);
8772 	}
8773 
8774 	private int ascent_;
8775 	private int descent_;
8776 
8777 	/++
8778 		Max ascent above the baseline.
8779 
8780 		History:
8781 			Added January 22, 2021
8782 	+/
8783 	int ascent() {
8784 		return ascent_;
8785 	}
8786 
8787 	/++
8788 		Max descent below the baseline.
8789 
8790 		History:
8791 			Added January 22, 2021
8792 	+/
8793 	int descent() {
8794 		return descent_;
8795 	}
8796 
8797 	/++
8798 		Loads the default font used by [ScreenPainter] if none others are loaded.
8799 
8800 		Returns:
8801 			This method mutates the `this` object, but then returns `this` for
8802 			easy chaining like:
8803 
8804 			---
8805 			auto font = foo.isNull ? foo : foo.loadDefault
8806 			---
8807 
8808 		History:
8809 			Added previously, but left unimplemented until January 24, 2021.
8810 	+/
8811 	OperatingSystemFont loadDefault() {
8812 		unload();
8813 
8814 		loadedInfo = LoadedInfo.init;
8815 
8816 		version(X11) {
8817 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8818 			// but meh since sdpy does its own thing, this should be ok too
8819 
8820 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8821 			this.font = ScreenPainterImplementation.defaultfont;
8822 			this.fontset = ScreenPainterImplementation.defaultfontset;
8823 
8824 			prepareFontInfo();
8825 		} else version(Windows) {
8826 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8827 			this.font = ScreenPainterImplementation.defaultGuiFont;
8828 
8829 			prepareFontInfo();
8830 		} else throw new NotYetImplementedException();
8831 
8832 		return this;
8833 	}
8834 
8835 	///
8836 	bool isNull() {
8837 		version(OSXCocoa) throw new NotYetImplementedException(); else {
8838 			version(with_xft)
8839 				if(isXft)
8840 					return xftFont is null;
8841 			return font is null;
8842 		}
8843 	}
8844 
8845 	/* Metrics */
8846 	/+
8847 		GetABCWidth
8848 		GetKerningPairs
8849 
8850 		if I do it right, I can size it all here, and match
8851 		what happens when I draw the full string with the OS functions.
8852 
8853 		subclasses might do the same thing while getting the glyphs on images
8854 	struct GlyphInfo {
8855 		int glyph;
8856 
8857 		size_t stringIdxStart;
8858 		size_t stringIdxEnd;
8859 
8860 		Rectangle boundingBox;
8861 	}
8862 	GlyphInfo[] getCharBoxes() {
8863 		// XftTextExtentsUtf8
8864 		return null;
8865 
8866 	}
8867 	+/
8868 
8869 	~this() {
8870 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
8871 		unload();
8872 	}
8873 }
8874 
8875 version(Windows)
8876 private string sliceCString(const(wchar)[] w) {
8877 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
8878 }
8879 
8880 private inout(char)[] sliceCString(inout(char)* s) {
8881 	import core.stdc.string;
8882 	auto len = strlen(s);
8883 	return s[0 .. len];
8884 }
8885 
8886 /**
8887 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
8888 	than constructing it directly. Then, it is reference counted so you can pass it
8889 	at around and when the last ref goes out of scope, the buffered drawing activities
8890 	are all carried out.
8891 
8892 
8893 	Most functions use the outlineColor instead of taking a color themselves.
8894 	ScreenPainter is reference counted and draws its buffer to the screen when its
8895 	final reference goes out of scope.
8896 */
8897 struct ScreenPainter {
8898 	CapableOfBeingDrawnUpon window;
8899 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) {
8900 		this.window = window;
8901 		if(window.closed)
8902 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
8903 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
8904 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
8905 		if(window.activeScreenPainter !is null) {
8906 			impl = window.activeScreenPainter;
8907 			if(impl.referenceCount == 0) {
8908 				impl.window = window;
8909 				impl.create(handle);
8910 			}
8911 			impl.manualInvalidations = manualInvalidations;
8912 			impl.referenceCount++;
8913 		//	writeln("refcount ++ ", impl.referenceCount);
8914 		} else {
8915 			impl = new ScreenPainterImplementation;
8916 			impl.window = window;
8917 			impl.create(handle);
8918 			impl.referenceCount = 1;
8919 			impl.manualInvalidations = manualInvalidations;
8920 			window.activeScreenPainter = impl;
8921 			// writeln("constructed");
8922 		}
8923 
8924 		copyActiveOriginals();
8925 	}
8926 
8927 	/++
8928 		EXPERIMENTAL. subject to change.
8929 
8930 		When you draw a cursor, you can draw this to notify your window of where it is,
8931 		for IME systems to use.
8932 	+/
8933 	void notifyCursorPosition(int x, int y, int width, int height) {
8934 		if(auto w = cast(SimpleWindow) window) {
8935 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
8936 		}
8937 	}
8938 
8939 	/++
8940 		If you are using manual invalidations, this informs the
8941 		window system that a section needs to be redrawn.
8942 
8943 		If you didn't opt into manual invalidation, you don't
8944 		have to call this.
8945 
8946 		History:
8947 			Added December 30, 2021 (dub v10.5)
8948 	+/
8949 	void invalidateRect(Rectangle rect) {
8950 		if(impl is null) return;
8951 
8952 		// transform(rect)
8953 		rect.left += _originX;
8954 		rect.right += _originX;
8955 		rect.top += _originY;
8956 		rect.bottom += _originY;
8957 
8958 		impl.invalidateRect(rect);
8959 	}
8960 
8961 	private Pen originalPen;
8962 	private Color originalFillColor;
8963 	private arsd.color.Rectangle originalClipRectangle;
8964 	private OperatingSystemFont originalFont;
8965 	void copyActiveOriginals() {
8966 		if(impl is null) return;
8967 		originalPen = impl._activePen;
8968 		originalFillColor = impl._fillColor;
8969 		originalClipRectangle = impl._clipRectangle;
8970 		originalFont = impl._activeFont;
8971 	}
8972 
8973 	~this() {
8974 		if(impl is null) return;
8975 		impl.referenceCount--;
8976 		//writeln("refcount -- ", impl.referenceCount);
8977 		if(impl.referenceCount == 0) {
8978 			// writeln("destructed");
8979 			impl.dispose();
8980 			*window.activeScreenPainter = ScreenPainterImplementation.init;
8981 			// writeln("paint finished");
8982 		} else {
8983 			// there is still an active reference, reset stuff so the
8984 			// next user doesn't get weirdness via the reference
8985 			this.rasterOp = RasterOp.normal;
8986 			pen = originalPen;
8987 			fillColor = originalFillColor;
8988 			if(originalFont)
8989 				setFont(originalFont);
8990 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
8991 		}
8992 	}
8993 
8994 	this(this) {
8995 		if(impl is null) return;
8996 		impl.referenceCount++;
8997 		//writeln("refcount ++ ", impl.referenceCount);
8998 
8999 		copyActiveOriginals();
9000 	}
9001 
9002 	private int _originX;
9003 	private int _originY;
9004 	@property int originX() { return _originX; }
9005 	@property int originY() { return _originY; }
9006 	@property int originX(int a) {
9007 		_originX = a;
9008 		return _originX;
9009 	}
9010 	@property int originY(int a) {
9011 		_originY = a;
9012 		return _originY;
9013 	}
9014 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9015 	private void transform(ref Point p) {
9016 		if(impl is null) return;
9017 		p.x += _originX;
9018 		p.y += _originY;
9019 	}
9020 
9021 	// this needs to be checked BEFORE the originX/Y transformation
9022 	private bool isClipped(Point p) {
9023 		return !currentClipRectangle.contains(p);
9024 	}
9025 	private bool isClipped(Point p, int width, int height) {
9026 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9027 	}
9028 	private bool isClipped(Point p, Size s) {
9029 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9030 	}
9031 	private bool isClipped(Point p, Point p2) {
9032 		// need to ensure the end points are actually included inside, so the +1 does that
9033 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9034 	}
9035 
9036 
9037 	/++
9038 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9039 
9040 		Returns:
9041 			The old clip rectangle.
9042 
9043 		History:
9044 			Return value was `void` prior to May 10, 2021.
9045 
9046 	+/
9047 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9048 		if(impl is null) return currentClipRectangle;
9049 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9050 			return currentClipRectangle; // no need to do anything
9051 		auto old = currentClipRectangle;
9052 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9053 		transform(pt);
9054 
9055 		impl.setClipRectangle(pt.x, pt.y, width, height);
9056 
9057 		return old;
9058 	}
9059 
9060 	/// ditto
9061 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9062 		if(impl is null) return currentClipRectangle;
9063 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9064 	}
9065 
9066 	///
9067 	void setFont(OperatingSystemFont font) {
9068 		if(impl is null) return;
9069 		impl.setFont(font);
9070 	}
9071 
9072 	///
9073 	int fontHeight() {
9074 		if(impl is null) return 0;
9075 		return impl.fontHeight();
9076 	}
9077 
9078 	private Pen activePen;
9079 
9080 	///
9081 	@property void pen(Pen p) {
9082 		if(impl is null) return;
9083 		activePen = p;
9084 		impl.pen(p);
9085 	}
9086 
9087 	///
9088 	@scriptable
9089 	@property void outlineColor(Color c) {
9090 		if(impl is null) return;
9091 		if(activePen.color == c)
9092 			return;
9093 		activePen.color = c;
9094 		impl.pen(activePen);
9095 	}
9096 
9097 	///
9098 	@scriptable
9099 	@property void fillColor(Color c) {
9100 		if(impl is null) return;
9101 		impl.fillColor(c);
9102 	}
9103 
9104 	///
9105 	@property void rasterOp(RasterOp op) {
9106 		if(impl is null) return;
9107 		impl.rasterOp(op);
9108 	}
9109 
9110 
9111 	void updateDisplay() {
9112 		// FIXME this should do what the dtor does
9113 	}
9114 
9115 	/// 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)
9116 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9117 		if(impl is null) return;
9118 		if(isClipped(upperLeft, width, height)) return;
9119 		transform(upperLeft);
9120 		version(Windows) {
9121 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9122 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9123 			RECT clip = scroll;
9124 			RECT uncovered;
9125 			HRGN hrgn;
9126 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9127 				throw new WindowsApiException("ScrollDC", GetLastError());
9128 
9129 		} else version(X11) {
9130 			// FIXME: clip stuff outside this rectangle
9131 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9132 		} else version(OSXCocoa) {
9133 			throw new NotYetImplementedException();
9134 		} else static assert(0);
9135 	}
9136 
9137 	///
9138 	void clear(Color color = Color.white()) {
9139 		if(impl is null) return;
9140 		fillColor = color;
9141 		outlineColor = color;
9142 		drawRectangle(Point(0, 0), window.width, window.height);
9143 	}
9144 
9145 	/++
9146 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9147 
9148 		Params:
9149 			upperLeft = point on the window where the upper left corner of the image will be drawn
9150 			imageUpperLeft = point on the image to start the slice to draw
9151 			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.
9152 		History:
9153 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9154 	+/
9155 	version(OSXCocoa) {} else // NotYetImplementedException
9156 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9157 		if(impl is null) return;
9158 		if(isClipped(upperLeft, s.width, s.height)) return;
9159 		transform(upperLeft);
9160 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9161 	}
9162 
9163 	///
9164 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9165 		if(impl is null) return;
9166 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9167 		transform(upperLeft);
9168 		if(w == 0 || w > i.width)
9169 			w = i.width;
9170 		if(h == 0 || h > i.height)
9171 			h = i.height;
9172 		if(upperLeftOfImage.x < 0)
9173 			upperLeftOfImage.x = 0;
9174 		if(upperLeftOfImage.y < 0)
9175 			upperLeftOfImage.y = 0;
9176 
9177 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9178 	}
9179 
9180 	///
9181 	Size textSize(in char[] text) {
9182 		if(impl is null) return Size(0, 0);
9183 		return impl.textSize(text);
9184 	}
9185 
9186 	/++
9187 		Draws a string in the window with the set font (see [setFont] to change it).
9188 
9189 		Params:
9190 			upperLeft = the upper left point of the bounding box of the text
9191 			text = the string to draw
9192 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9193 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9194 	+/
9195 	@scriptable
9196 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9197 		if(impl is null) return;
9198 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9199 			if(isClipped(upperLeft, lowerRight)) return;
9200 			transform(lowerRight);
9201 		} else {
9202 			if(isClipped(upperLeft, textSize(text))) return;
9203 		}
9204 		transform(upperLeft);
9205 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9206 	}
9207 
9208 	/++
9209 		Draws text using a custom font.
9210 
9211 		This is still MAJOR work in progress.
9212 
9213 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9214 	+/
9215 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9216 		if(impl is null) return;
9217 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9218 		transform(upperLeft);
9219 		font.drawString(this, upperLeft, text);
9220 	}
9221 
9222 	version(Windows)
9223 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9224 		if(impl is null) return;
9225 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9226 		transform(upperLeft);
9227 
9228 		if(text.length && text[$-1] == '\n')
9229 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9230 
9231 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9232 	}
9233 
9234 	static struct TextDrawingContext {
9235 		Point boundingBoxUpperLeft;
9236 		Point boundingBoxLowerRight;
9237 
9238 		Point currentLocation;
9239 
9240 		Point lastDrewUpperLeft;
9241 		Point lastDrewLowerRight;
9242 
9243 		// how do i do right aligned rich text?
9244 		// i kinda want to do a pre-made drawing then right align
9245 		// draw the whole block.
9246 		//
9247 		// That's exactly the diff: inline vs block stuff.
9248 
9249 		// I need to get coordinates of an inline section out too,
9250 		// not just a bounding box, but a series of bounding boxes
9251 		// should be ok. Consider what's needed to detect a click
9252 		// on a link in the middle of a paragraph breaking a line.
9253 		//
9254 		// Generally, we should be able to get the rectangles of
9255 		// any portion we draw.
9256 		//
9257 		// It also needs to tell what text is left if it overflows
9258 		// out of the box, so we can do stuff like float images around
9259 		// it. It should not attempt to draw a letter that would be
9260 		// clipped.
9261 		//
9262 		// I might also turn off word wrap stuff.
9263 	}
9264 
9265 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9266 		if(impl is null) return;
9267 		// FIXME
9268 	}
9269 
9270 	/// Drawing an individual pixel is slow. Avoid it if possible.
9271 	void drawPixel(Point where) {
9272 		if(impl is null) return;
9273 		if(isClipped(where)) return;
9274 		transform(where);
9275 		impl.drawPixel(where.x, where.y);
9276 	}
9277 
9278 
9279 	/// Draws a pen using the current pen / outlineColor
9280 	@scriptable
9281 	void drawLine(Point starting, Point ending) {
9282 		if(impl is null) return;
9283 		if(isClipped(starting, ending)) return;
9284 		transform(starting);
9285 		transform(ending);
9286 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9287 	}
9288 
9289 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9290 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9291 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9292 	@scriptable
9293 	void drawRectangle(Point upperLeft, int width, int height) {
9294 		if(impl is null) return;
9295 		if(isClipped(upperLeft, width, height)) return;
9296 		transform(upperLeft);
9297 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9298 	}
9299 
9300 	/// ditto
9301 	void drawRectangle(Point upperLeft, Size size) {
9302 		if(impl is null) return;
9303 		if(isClipped(upperLeft, size.width, size.height)) return;
9304 		transform(upperLeft);
9305 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9306 	}
9307 
9308 	/// ditto
9309 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9310 		if(impl is null) return;
9311 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9312 		transform(upperLeft);
9313 		transform(lowerRightInclusive);
9314 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9315 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9316 	}
9317 
9318 	// overload added on May 12, 2021
9319 	/// ditto
9320 	void drawRectangle(Rectangle rect) {
9321 		drawRectangle(rect.upperLeft, rect.size);
9322 	}
9323 
9324 	/// Arguments are the points of the bounding rectangle
9325 	void drawEllipse(Point upperLeft, Point lowerRight) {
9326 		if(impl is null) return;
9327 		if(isClipped(upperLeft, lowerRight)) return;
9328 		transform(upperLeft);
9329 		transform(lowerRight);
9330 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9331 	}
9332 
9333 	/++
9334 		start and finish are units of degrees * 64
9335 
9336 		History:
9337 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9338 
9339 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9340 	+/
9341 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9342 		if(impl is null) return;
9343 		// FIXME: not actually implemented
9344 		if(isClipped(upperLeft, width, height)) return;
9345 		transform(upperLeft);
9346 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9347 	}
9348 
9349 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9350 	void drawCircle(Point upperLeft, int diameter) {
9351 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9352 	}
9353 
9354 	/// .
9355 	void drawPolygon(Point[] vertexes) {
9356 		if(impl is null) return;
9357 		assert(vertexes.length);
9358 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9359 		foreach(ref vertex; vertexes) {
9360 			if(vertex.x < minX)
9361 				minX = vertex.x;
9362 			if(vertex.y < minY)
9363 				minY = vertex.y;
9364 			if(vertex.x > maxX)
9365 				maxX = vertex.x;
9366 			if(vertex.y > maxY)
9367 				maxY = vertex.y;
9368 			transform(vertex);
9369 		}
9370 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9371 		impl.drawPolygon(vertexes);
9372 	}
9373 
9374 	/// ditto
9375 	void drawPolygon(Point[] vertexes...) {
9376 		if(impl is null) return;
9377 		drawPolygon(vertexes);
9378 	}
9379 
9380 
9381 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9382 
9383 	//mixin NativeScreenPainterImplementation!() impl;
9384 
9385 
9386 	// HACK: if I mixin the impl directly, it won't let me override the copy
9387 	// constructor! The linker complains about there being multiple definitions.
9388 	// I'll make the best of it and reference count it though.
9389 	ScreenPainterImplementation* impl;
9390 }
9391 
9392 	// HACK: I need a pointer to the implementation so it's separate
9393 	struct ScreenPainterImplementation {
9394 		CapableOfBeingDrawnUpon window;
9395 		int referenceCount;
9396 		mixin NativeScreenPainterImplementation!();
9397 	}
9398 
9399 // FIXME: i haven't actually tested the sprite class on MS Windows
9400 
9401 /**
9402 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9403 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9404 
9405 
9406 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9407 	though I'm not sure that's ideal and the implementation might change.
9408 
9409 	You create one by giving a window and an image. It optimizes for that window,
9410 	and copies the image into it to use as the initial picture. Creating a sprite
9411 	can be quite slow (especially over a network connection) so you should do it
9412 	as little as possible and just hold on to your sprite handles after making them.
9413 	simpledisplay does try to do its best though, using the XSHM extension if available,
9414 	but you should still write your code as if it will always be slow.
9415 
9416 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9417 	a fast operation - much faster than drawing the Image itself every time.
9418 
9419 	`Sprite` represents a scarce resource which should be freed when you
9420 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9421 	after it has been disposed. If you are unsure about this, don't take chances,
9422 	just let the garbage collector do it for you. But ideally, you can manage its
9423 	lifetime more efficiently.
9424 
9425 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9426 	support alpha blending in its drawing at this time. That might change in the
9427 	future, but if you need alpha blending right now, use OpenGL instead. See
9428 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9429 
9430 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9431 	in by setting the enableAlpha = true in the constructor.
9432 */
9433 version(OSXCocoa) {} else // NotYetImplementedException
9434 class Sprite : CapableOfBeingDrawnUpon {
9435 
9436 	///
9437 	ScreenPainter draw() {
9438 		return ScreenPainter(this, handle, false);
9439 	}
9440 
9441 	/++
9442 		Copies the sprite's current state into a [TrueColorImage].
9443 
9444 		Be warned: this can be a very slow operation
9445 
9446 		History:
9447 			Actually implemented on March 14, 2021
9448 	+/
9449 	TrueColorImage takeScreenshot() {
9450 		return trueColorImageFromNativeHandle(handle, width, height);
9451 	}
9452 
9453 	void delegate() paintingFinishedDg() { return null; }
9454 	bool closed() { return false; }
9455 	ScreenPainterImplementation* activeScreenPainter_;
9456 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9457 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9458 
9459 	version(Windows)
9460 		private ubyte* rawData;
9461 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9462 	// ditto on the XPicture stuff
9463 
9464 	version(X11) {
9465 		private static XRenderPictFormat* RGB24;
9466 		private static XRenderPictFormat* ARGB32;
9467 
9468 		private Picture xrenderPicture;
9469 	}
9470 
9471 	version(X11)
9472 	private static void requireXRender() {
9473 		if(!XRenderLibrary.loadAttempted) {
9474 			XRenderLibrary.loadDynamicLibrary();
9475 		}
9476 
9477 		if(!XRenderLibrary.loadSuccessful)
9478 			throw new Exception("XRender library load failure");
9479 
9480 		auto display = XDisplayConnection.get;
9481 
9482 		// FIXME: if we migrate X displays, these need to be changed
9483 		if(RGB24 is null)
9484 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9485 		if(ARGB32 is null)
9486 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9487 	}
9488 
9489 	protected this() {}
9490 
9491 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9492 		this._width = width;
9493 		this._height = height;
9494 		this.enableAlpha = enableAlpha;
9495 
9496 		version(X11) {
9497 			auto display = XDisplayConnection.get();
9498 
9499 			if(enableAlpha) {
9500 				requireXRender();
9501 			}
9502 
9503 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9504 
9505 			if(enableAlpha) {
9506 				XRenderPictureAttributes attrs;
9507 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9508 			}
9509 		} else version(Windows) {
9510 			version(CRuntime_DigitalMars) {
9511 				//if(enableAlpha)
9512 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9513 			}
9514 
9515 			BITMAPINFO infoheader;
9516 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9517 			infoheader.bmiHeader.biWidth = width;
9518 			infoheader.bmiHeader.biHeight = height;
9519 			infoheader.bmiHeader.biPlanes = 1;
9520 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9521 			infoheader.bmiHeader.biCompression = BI_RGB;
9522 
9523 			// FIXME: this should prolly be a device dependent bitmap...
9524 			handle = CreateDIBSection(
9525 				null,
9526 				&infoheader,
9527 				DIB_RGB_COLORS,
9528 				cast(void**) &rawData,
9529 				null,
9530 				0);
9531 
9532 			if(handle is null)
9533 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9534 		}
9535 	}
9536 
9537 	/// Makes a sprite based on the image with the initial contents from the Image
9538 	this(SimpleWindow win, Image i) {
9539 		this(win, i.width, i.height, i.enableAlpha);
9540 
9541 		version(X11) {
9542 			auto display = XDisplayConnection.get();
9543 			auto gc = XCreateGC(display, this.handle, 0, null);
9544 			scope(exit) XFreeGC(display, gc);
9545 			if(i.usingXshm)
9546 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9547 			else
9548 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9549 		} else version(Windows) {
9550 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9551 			auto arrLength = itemsPerLine * height;
9552 			rawData[0..arrLength] = i.rawData[0..arrLength];
9553 		} else version(OSXCocoa) {
9554 			// FIXME: I have no idea if this is even any good
9555 			ubyte* rawData;
9556 
9557 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9558 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
9559 				colorSpace,
9560 				kCGImageAlphaPremultipliedLast
9561 				|kCGBitmapByteOrder32Big);
9562 			CGColorSpaceRelease(colorSpace);
9563 			rawData = CGBitmapContextGetData(context);
9564 
9565 			auto rdl = (width * height * 4);
9566 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9567 		} else static assert(0);
9568 	}
9569 
9570 	/++
9571 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9572 
9573 		Params:
9574 			where = point on the window where the upper left corner of the image will be drawn
9575 			imageUpperLeft = point on the image to start the slice to draw
9576 			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.
9577 		History:
9578 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9579 	+/
9580 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9581 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9582 	}
9583 
9584 	/// Call this when you're ready to get rid of it
9585 	void dispose() {
9586 		version(X11) {
9587 			staticDispose(xrenderPicture, handle);
9588 			xrenderPicture = None;
9589 			handle = None;
9590 		} else version(Windows) {
9591 			staticDispose(handle);
9592 			handle = null;
9593 		} else version(OSXCocoa) {
9594 			staticDispose(context);
9595 			context = null;
9596 		} else static assert(0);
9597 
9598 	}
9599 
9600 	version(X11)
9601 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9602 		if(xrenderPicture)
9603 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9604 		if(handle)
9605 			XFreePixmap(XDisplayConnection.get(), handle);
9606 	}
9607 	else version(Windows)
9608 	static void staticDispose(HBITMAP handle) {
9609 		if(handle)
9610 			DeleteObject(handle);
9611 	}
9612 	else version(OSXCocoa)
9613 	static void staticDispose(CGContextRef context) {
9614 		if(context)
9615 			CGContextRelease(context);
9616 	}
9617 
9618 	~this() {
9619 		version(X11) { if(xrenderPicture || handle)
9620 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9621 		} else version(Windows) { if(handle)
9622 			cleanupQueue.queue!staticDispose(handle);
9623 		} else version(OSXCocoa) { if(context)
9624 			cleanupQueue.queue!staticDispose(context);
9625 		} else static assert(0);
9626 	}
9627 
9628 	///
9629 	final @property int width() { return _width; }
9630 
9631 	///
9632 	final @property int height() { return _height; }
9633 
9634 	///
9635 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9636 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9637 	}
9638 
9639 	auto nativeHandle() {
9640 		return handle;
9641 	}
9642 
9643 	private:
9644 
9645 	int _width;
9646 	int _height;
9647 	bool enableAlpha;
9648 	version(X11)
9649 		Pixmap handle;
9650 	else version(Windows)
9651 		HBITMAP handle;
9652 	else version(OSXCocoa)
9653 		CGContextRef context;
9654 	else static assert(0);
9655 }
9656 
9657 /++
9658 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9659 
9660 	History:
9661 		Added November 20, 2021 (dub v10.4)
9662 +/
9663 abstract class Gradient : Sprite {
9664 	protected this(int w, int h) {
9665 		version(X11) {
9666 			Sprite.requireXRender();
9667 
9668 			super();
9669 			enableAlpha = true;
9670 			_width = w;
9671 			_height = h;
9672 		} else version(Windows) {
9673 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9674 		}
9675 	}
9676 
9677 	version(Windows)
9678 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9679 		auto ptr = rawData;
9680 		foreach(j; 0 .. _height)
9681 		foreach(i; 0 .. _width) {
9682 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9683 			*rawData = (color.a * color.b) / 255; rawData++;
9684 			*rawData = (color.a * color.g) / 255; rawData++;
9685 			*rawData = (color.a * color.r) / 255; rawData++;
9686 			*rawData = color.a; rawData++;
9687 		}
9688 	}
9689 
9690 	version(X11)
9691 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9692 		assert(stops.length > 0);
9693 		assert(stops.length <= 16, "I got lazy with buffers");
9694 
9695 		XFixed[16] stopsPositions = void;
9696 		XRenderColor[16] colors = void;
9697 
9698 		foreach(idx, stop; stops) {
9699 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9700 			auto c = stop.c;
9701 			colors[idx] = XRenderColor(
9702 				cast(ushort)(c.r * ushort.max / 255),
9703 				cast(ushort)(c.g * ushort.max / 255),
9704 				cast(ushort)(c.b * ushort.max / 255),
9705 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9706 			);
9707 		}
9708 
9709 		xrenderPicture = dg(stopsPositions, colors);
9710 	}
9711 
9712 	///
9713 	static struct Stop {
9714 		float percentage; /// between 0 and 1.0
9715 		Color c;
9716 	}
9717 }
9718 
9719 /++
9720 	Creates a linear gradient between p1 and p2.
9721 
9722 	X ONLY RIGHT NOW
9723 
9724 	History:
9725 		Added November 20, 2021 (dub v10.4)
9726 
9727 	Bugs:
9728 		Not yet implemented on Windows.
9729 +/
9730 class LinearGradient : Gradient {
9731 	/++
9732 
9733 	+/
9734 	this(Point p1, Point p2, Stop[] stops...) {
9735 		super(p2.x, p2.y);
9736 
9737 		version(X11) {
9738 			XLinearGradient gradient;
9739 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9740 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9741 
9742 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9743 				return XRenderCreateLinearGradient(
9744 					XDisplayConnection.get,
9745 					&gradient,
9746 					stopsPositions.ptr,
9747 					colors.ptr,
9748 					cast(int) stops.length);
9749 			});
9750 		} else version(Windows) {
9751 			// FIXME
9752 			forEachPixel((int x, int y) {
9753 				import core.stdc.math;
9754 
9755 				//sqrtf(
9756 
9757 				return Color.transparent;
9758 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9759 			});
9760 		}
9761 	}
9762 }
9763 
9764 /++
9765 	A conical gradient goes from color to color around a circumference from a center point.
9766 
9767 	X ONLY RIGHT NOW
9768 
9769 	History:
9770 		Added November 20, 2021 (dub v10.4)
9771 
9772 	Bugs:
9773 		Not yet implemented on Windows.
9774 +/
9775 class ConicalGradient : Gradient {
9776 	/++
9777 
9778 	+/
9779 	this(Point center, float angleInDegrees, Stop[] stops...) {
9780 		super(center.x * 2, center.y * 2);
9781 
9782 		version(X11) {
9783 			XConicalGradient gradient;
9784 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9785 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9786 
9787 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9788 				return XRenderCreateConicalGradient(
9789 					XDisplayConnection.get,
9790 					&gradient,
9791 					stopsPositions.ptr,
9792 					colors.ptr,
9793 					cast(int) stops.length);
9794 			});
9795 		} else version(Windows) {
9796 			// FIXME
9797 			forEachPixel((int x, int y) {
9798 				import core.stdc.math;
9799 
9800 				//sqrtf(
9801 
9802 				return Color.transparent;
9803 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9804 			});
9805 
9806 		}
9807 	}
9808 }
9809 
9810 /++
9811 	A radial gradient goes from color to color based on distance from the center.
9812 	It is like rings of color.
9813 
9814 	X ONLY RIGHT NOW
9815 
9816 
9817 	More specifically, you create two circles: an inner circle and an outer circle.
9818 	The gradient is only drawn in the area outside the inner circle but inside the outer
9819 	circle. The closest line between those two circles forms the line for the gradient
9820 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9821 
9822 	History:
9823 		Added November 20, 2021 (dub v10.4)
9824 
9825 	Bugs:
9826 		Not yet implemented on Windows.
9827 +/
9828 class RadialGradient : Gradient {
9829 	/++
9830 
9831 	+/
9832 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
9833 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
9834 
9835 		version(X11) {
9836 			XRadialGradient gradient;
9837 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
9838 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
9839 
9840 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9841 				return XRenderCreateRadialGradient(
9842 					XDisplayConnection.get,
9843 					&gradient,
9844 					stopsPositions.ptr,
9845 					colors.ptr,
9846 					cast(int) stops.length);
9847 			});
9848 		} else version(Windows) {
9849 			// FIXME
9850 			forEachPixel((int x, int y) {
9851 				import core.stdc.math;
9852 
9853 				//sqrtf(
9854 
9855 				return Color.transparent;
9856 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9857 			});
9858 		}
9859 	}
9860 }
9861 
9862 
9863 
9864 /+
9865 	NOT IMPLEMENTED
9866 
9867 	A display-stored image optimized for relatively quick drawing, like
9868 	[Sprite], but this one supports alpha channel blending and does NOT
9869 	support direct drawing upon it with a [ScreenPainter].
9870 
9871 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
9872 	plain [ScreenPainter]... sort of.
9873 
9874 	On X11, it requires the Xrender extension and library. This is available
9875 	almost everywhere though.
9876 
9877 	History:
9878 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
9879 +/
9880 version(none)
9881 class AlphaSprite {
9882 	/++
9883 		Copies the given image into it.
9884 	+/
9885 	this(MemoryImage img) {
9886 
9887 		if(!XRenderLibrary.loadAttempted) {
9888 			XRenderLibrary.loadDynamicLibrary();
9889 
9890 			// FIXME: this needs to be reconstructed when the X server changes
9891 			repopulateX();
9892 		}
9893 		if(!XRenderLibrary.loadSuccessful)
9894 			throw new Exception("XRender library load failure");
9895 
9896 		// I probably need to put the alpha mask in a separate Picture
9897 		// ugh
9898 		// maybe the Sprite itself can have an alpha bitmask anyway
9899 
9900 
9901 		auto display = XDisplayConnection.get();
9902 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
9903 
9904 
9905 		XRenderPictureAttributes attrs;
9906 
9907 		handle = XRenderCreatePicture(
9908 			XDisplayConnection.get,
9909 			pixmap,
9910 			RGBA,
9911 			0,
9912 			&attrs
9913 		);
9914 
9915 	}
9916 
9917 	// maybe i'll use the create gradient functions too with static factories..
9918 
9919 	void drawAt(ScreenPainter painter, Point where) {
9920 		//painter.drawPixmap(this, where);
9921 
9922 		XRenderPictureAttributes attrs;
9923 
9924 		auto pic = XRenderCreatePicture(
9925 			XDisplayConnection.get,
9926 			painter.impl.d,
9927 			RGB,
9928 			0,
9929 			&attrs
9930 		);
9931 
9932 		XRenderComposite(
9933 			XDisplayConnection.get,
9934 			3, // PictOpOver
9935 			handle,
9936 			None,
9937 			pic,
9938 			0, // src
9939 			0,
9940 			0, // mask
9941 			0,
9942 			10, // dest
9943 			10,
9944 			100, // width
9945 			100
9946 		);
9947 
9948 		/+
9949 		XRenderFreePicture(
9950 			XDisplayConnection.get,
9951 			pic
9952 		);
9953 
9954 		XRenderFreePicture(
9955 			XDisplayConnection.get,
9956 			fill
9957 		);
9958 		+/
9959 		// on Windows you can stretch but Xrender still can't :(
9960 	}
9961 
9962 	static XRenderPictFormat* RGB;
9963 	static XRenderPictFormat* RGBA;
9964 	static void repopulateX() {
9965 		auto display = XDisplayConnection.get;
9966 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
9967 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
9968 	}
9969 
9970 	XPixmap pixmap;
9971 	Picture handle;
9972 }
9973 
9974 ///
9975 interface CapableOfBeingDrawnUpon {
9976 	///
9977 	ScreenPainter draw();
9978 	///
9979 	int width();
9980 	///
9981 	int height();
9982 	protected ScreenPainterImplementation* activeScreenPainter();
9983 	protected void activeScreenPainter(ScreenPainterImplementation*);
9984 	bool closed();
9985 
9986 	void delegate() paintingFinishedDg();
9987 
9988 	/// Be warned: this can be a very slow operation
9989 	TrueColorImage takeScreenshot();
9990 }
9991 
9992 /// 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].
9993 void flushGui() {
9994 	version(X11) {
9995 		auto dpy = XDisplayConnection.get();
9996 		XLockDisplay(dpy);
9997 		scope(exit) XUnlockDisplay(dpy);
9998 		XFlush(dpy);
9999 	}
10000 }
10001 
10002 /++
10003 	Runs the given code in the GUI thread when its event loop
10004 	is available, blocking until it completes. This allows you
10005 	to create and manipulate windows from another thread without
10006 	invoking undefined behavior.
10007 
10008 	If this is the gui thread, it runs the code immediately.
10009 
10010 	If no gui thread exists yet, the current thread is assumed
10011 	to be it. Attempting to create windows or run the event loop
10012 	in any other thread will cause an assertion failure.
10013 
10014 
10015 	$(TIP
10016 		Did you know you can use UFCS on delegate literals?
10017 
10018 		() {
10019 			// code here
10020 		}.runInGuiThread;
10021 	)
10022 
10023 	Returns:
10024 		`true` if the function was called, `false` if it was not.
10025 		The function may not be called because the gui thread had
10026 		already terminated by the time you called this.
10027 
10028 	History:
10029 		Added April 10, 2020 (v7.2.0)
10030 
10031 		Return value added and implementation tweaked to avoid locking
10032 		at program termination on February 24, 2021 (v9.2.1).
10033 +/
10034 bool runInGuiThread(scope void delegate() dg) @trusted {
10035 	claimGuiThread();
10036 
10037 	if(thisIsGuiThread) {
10038 		dg();
10039 		return true;
10040 	}
10041 
10042 	if(guiThreadTerminating)
10043 		return false;
10044 
10045 	import core.sync.semaphore;
10046 	static Semaphore sc;
10047 	if(sc is null)
10048 		sc = new Semaphore();
10049 
10050 	static RunQueueMember* rqm;
10051 	if(rqm is null)
10052 		rqm = new RunQueueMember;
10053 	rqm.dg = cast(typeof(rqm.dg)) dg;
10054 	rqm.signal = sc;
10055 	rqm.thrown = null;
10056 
10057 	synchronized(runInGuiThreadLock) {
10058 		runInGuiThreadQueue ~= rqm;
10059 	}
10060 
10061 	if(!SimpleWindow.eventWakeUp())
10062 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10063 
10064 	rqm.signal.wait();
10065 	auto t = rqm.thrown;
10066 
10067 	if(t)
10068 		throw t;
10069 
10070 	return true;
10071 }
10072 
10073 // note it runs sync if this is the gui thread....
10074 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10075 	claimGuiThread();
10076 
10077 	try {
10078 
10079 		if(thisIsGuiThread) {
10080 			dg();
10081 			return;
10082 		}
10083 
10084 		if(guiThreadTerminating)
10085 			return;
10086 
10087 		RunQueueMember* rqm = new RunQueueMember;
10088 		rqm.dg = cast(typeof(rqm.dg)) dg;
10089 		rqm.signal = null;
10090 		rqm.thrown = null;
10091 
10092 		synchronized(runInGuiThreadLock) {
10093 			runInGuiThreadQueue ~= rqm;
10094 		}
10095 
10096 		if(!SimpleWindow.eventWakeUp())
10097 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10098 	} catch(Exception e) {
10099 		if(handleError)
10100 			handleError(e);
10101 	}
10102 }
10103 
10104 private void runPendingRunInGuiThreadDelegates() {
10105 	more:
10106 	RunQueueMember* next;
10107 	synchronized(runInGuiThreadLock) {
10108 		if(runInGuiThreadQueue.length) {
10109 			next = runInGuiThreadQueue[0];
10110 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10111 		} else {
10112 			next = null;
10113 		}
10114 	}
10115 
10116 	if(next) {
10117 		try {
10118 			next.dg();
10119 			next.thrown = null;
10120 		} catch(Throwable t) {
10121 			next.thrown = t;
10122 		}
10123 
10124 		if(next.signal)
10125 			next.signal.notify();
10126 
10127 		goto more;
10128 	}
10129 }
10130 
10131 private void claimGuiThread() nothrow {
10132 	import core.atomic;
10133 	if(cas(&guiThreadExists_, false, true))
10134 		thisIsGuiThread = true;
10135 }
10136 
10137 private struct RunQueueMember {
10138 	void delegate() dg;
10139 	import core.sync.semaphore;
10140 	Semaphore signal;
10141 	Throwable thrown;
10142 }
10143 
10144 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10145 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10146 private bool thisIsGuiThread = false;
10147 private shared bool guiThreadExists_ = false;
10148 private shared bool guiThreadTerminating = false;
10149 
10150 /++
10151 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10152 	event loop. All windows must be exclusively created and managed by a single thread.
10153 
10154 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10155 	when you call one of its constructors.
10156 
10157 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10158 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10159 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10160 
10161 	The reason this function is available is in case you want to message pass between a gui
10162 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10163 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10164 
10165 	History:
10166 		Added December 3, 2021 (dub v10.5)
10167 +/
10168 public bool guiThreadExists() {
10169 	return guiThreadExists_;
10170 }
10171 
10172 /++
10173 	Returns `true` if this thread is either running or set to be running the
10174 	simpledisplay.d gui core event loop because it owns windows.
10175 
10176 	It is important to keep gui-related functionality in the right thread, so you will
10177 	want to `runInGuiThread` when you call them (with some specific exceptions called
10178 	out in those specific functions' documentation). Notably, all windows must be
10179 	created and managed only from the gui thread.
10180 
10181 	Will return false if simpledisplay's other functions haven't been called
10182 	yet; check [guiThreadExists] in addition to this.
10183 
10184 	History:
10185 		Added December 3, 2021 (dub v10.5)
10186 +/
10187 public bool thisThreadRunningGui() {
10188 	return thisIsGuiThread;
10189 }
10190 
10191 /++
10192 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10193 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10194 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10195 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10196 	file instead if you are in one of those situations).
10197 
10198 	It does not support outputting very many types; just strings and ints are likely to actually work.
10199 
10200 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10201 	is unspecified meaning I can change it at any time. The only point of this function is to help
10202 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10203 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10204 	in those contexts.
10205 
10206 	$(WARNING
10207 		I reserve the right to change this function at any time. You can use it if it helps you
10208 		but do not rely on it for anything permanent.
10209 	)
10210 
10211 	History:
10212 		Added December 3, 2021. Not formally supported under any stable tag.
10213 +/
10214 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10215 	try {
10216 		version(Windows) {
10217 			import core.sys.windows.wincon;
10218 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10219 				AllocConsole();
10220 			const(char)* fn = "CONOUT$";
10221 		} else version(Posix) {
10222 			const(char)* fn = "/dev/tty";
10223 		} else static assert(0, "Function not implemented for your system");
10224 
10225 		if(fileOverride.length)
10226 			fn = fileOverride.ptr;
10227 
10228 		import core.stdc.stdio;
10229 		auto fp = fopen(fn, "wt");
10230 		if(fp is null) return;
10231 		scope(exit) fclose(fp);
10232 
10233 		string str;
10234 		foreach(item; t) {
10235 			static if(is(typeof(item) : const(char)[]))
10236 				str ~= item;
10237 			else
10238 				str ~= toInternal!string(item);
10239 			str ~= " ";
10240 		}
10241 		str ~= "\n";
10242 
10243 		fwrite(str.ptr, 1, str.length, fp);
10244 		fflush(fp);
10245 	} catch(Exception e) {
10246 		// sorry no hope
10247 	}
10248 }
10249 
10250 private void guiThreadFinalize() {
10251 	assert(thisIsGuiThread);
10252 
10253 	guiThreadTerminating = true; // don't add any more from this point on
10254 	runPendingRunInGuiThreadDelegates();
10255 }
10256 
10257 /+
10258 interface IPromise {
10259 	void reportProgress(int current, int max, string message);
10260 
10261 	/+ // not formally in cuz of templates but still
10262 	IPromise Then();
10263 	IPromise Catch();
10264 	IPromise Finally();
10265 	+/
10266 }
10267 
10268 /+
10269 	auto promise = async({ ... });
10270 	promise.Then(whatever).
10271 		Then(whateverelse).
10272 		Catch((exception) { });
10273 
10274 
10275 	A promise is run inside a fiber and it looks something like:
10276 
10277 	try {
10278 		auto res = whatever();
10279 		auto res2 = whateverelse(res);
10280 	} catch(Exception e) {
10281 		{ }(e);
10282 	}
10283 
10284 	When a thing succeeds, it is passed as an arg to the next
10285 +/
10286 class Promise(T) : IPromise {
10287 	auto Then() { return null; }
10288 	auto Catch() { return null; }
10289 	auto Finally() { return null; }
10290 
10291 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10292 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10293 	T await();
10294 }
10295 
10296 interface Task {
10297 }
10298 
10299 interface Resolvable(T) : Task {
10300 	void run();
10301 
10302 	void resolve(T);
10303 
10304 	Resolvable!T then(void delegate(T)); // returns a new promise
10305 	Resolvable!T error(Throwable); // js catch
10306 	Resolvable!T completed(); // js finally
10307 
10308 }
10309 
10310 /++
10311 	Runs `work` in a helper thread and sends its return value back to the main gui
10312 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10313 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10314 	kill the program.
10315 
10316 	You can call reportProgress(position, max, message) to update your parent window
10317 	on your progress.
10318 
10319 	I should also use `shared` methods. FIXME
10320 
10321 	History:
10322 		Added March 6, 2021 (dub version 9.3).
10323 +/
10324 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10325 	uponCompletion(work(null));
10326 }
10327 
10328 +/
10329 
10330 /// Used internal to dispatch events to various classes.
10331 interface CapableOfHandlingNativeEvent {
10332 	NativeEventHandler getNativeEventHandler();
10333 
10334 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10335 
10336 	version(X11) {
10337 		// if this is impossible, you are allowed to just throw from it
10338 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10339 		void recreateAfterDisconnect();
10340 		// discard any *connection specific* state, but keep enough that you
10341 		// can be recreated if possible. discardConnectionState() is always called immediately
10342 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10343 		// you need initialization order
10344 		void discardConnectionState();
10345 	}
10346 }
10347 
10348 version(X11)
10349 /++
10350 	State of keys on mouse events, especially motion.
10351 
10352 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10353 +/
10354 enum ModifierState : uint {
10355 	shift = 1, ///
10356 	capsLock = 2, ///
10357 	ctrl = 4, ///
10358 	alt = 8, /// Not always available on Windows
10359 	windows = 64, /// ditto
10360 	numLock = 16, ///
10361 
10362 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10363 	middleButtonDown = 512, /// ditto
10364 	rightButtonDown = 1024, /// ditto
10365 }
10366 else version(Windows)
10367 /// ditto
10368 enum ModifierState : uint {
10369 	shift = 4, ///
10370 	ctrl = 8, ///
10371 
10372 	// i'm not sure if the next two are available
10373 	alt = 256, /// not always available on Windows
10374 	windows = 512, /// ditto
10375 
10376 	capsLock = 1024, ///
10377 	numLock = 2048, ///
10378 
10379 	leftButtonDown = 1, /// not available on key events
10380 	middleButtonDown = 16, /// ditto
10381 	rightButtonDown = 2, /// ditto
10382 
10383 	backButtonDown = 0x20, /// not available on X
10384 	forwardButtonDown = 0x40, /// ditto
10385 }
10386 else version(OSXCocoa)
10387 // FIXME FIXME NotYetImplementedException
10388 enum ModifierState : uint {
10389 	shift = 1, ///
10390 	capsLock = 2, ///
10391 	ctrl = 4, ///
10392 	alt = 8, /// Not always available on Windows
10393 	windows = 64, /// ditto
10394 	numLock = 16, ///
10395 
10396 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10397 	middleButtonDown = 512, /// ditto
10398 	rightButtonDown = 1024, /// ditto
10399 }
10400 
10401 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10402 enum MouseButton : int {
10403 	none = 0,
10404 	left = 1, ///
10405 	right = 2, ///
10406 	middle = 4, ///
10407 	wheelUp = 8, ///
10408 	wheelDown = 16, ///
10409 	backButton = 32, /// often found on the thumb and used for back in browsers
10410 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10411 }
10412 
10413 version(X11) {
10414 	// FIXME: match ASCII whenever we can. Most of it is already there,
10415 	// but there's a few exceptions and mismatches with Windows
10416 
10417 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10418 	enum Key {
10419 		Escape = 0xff1b, ///
10420 		F1 = 0xffbe, ///
10421 		F2 = 0xffbf, ///
10422 		F3 = 0xffc0, ///
10423 		F4 = 0xffc1, ///
10424 		F5 = 0xffc2, ///
10425 		F6 = 0xffc3, ///
10426 		F7 = 0xffc4, ///
10427 		F8 = 0xffc5, ///
10428 		F9 = 0xffc6, ///
10429 		F10 = 0xffc7, ///
10430 		F11 = 0xffc8, ///
10431 		F12 = 0xffc9, ///
10432 		PrintScreen = 0xff61, ///
10433 		ScrollLock = 0xff14, ///
10434 		Pause = 0xff13, ///
10435 		Grave = 0x60, /// The $(BACKTICK) ~ key
10436 		// number keys across the top of the keyboard
10437 		N1 = 0x31, /// Number key atop the keyboard
10438 		N2 = 0x32, ///
10439 		N3 = 0x33, ///
10440 		N4 = 0x34, ///
10441 		N5 = 0x35, ///
10442 		N6 = 0x36, ///
10443 		N7 = 0x37, ///
10444 		N8 = 0x38, ///
10445 		N9 = 0x39, ///
10446 		N0 = 0x30, ///
10447 		Dash = 0x2d, ///
10448 		Equals = 0x3d, ///
10449 		Backslash = 0x5c, /// The \ | key
10450 		Backspace = 0xff08, ///
10451 		Insert = 0xff63, ///
10452 		Home = 0xff50, ///
10453 		PageUp = 0xff55, ///
10454 		Delete = 0xffff, ///
10455 		End = 0xff57, ///
10456 		PageDown = 0xff56, ///
10457 		Up = 0xff52, ///
10458 		Down = 0xff54, ///
10459 		Left = 0xff51, ///
10460 		Right = 0xff53, ///
10461 
10462 		Tab = 0xff09, ///
10463 		Q = 0x71, ///
10464 		W = 0x77, ///
10465 		E = 0x65, ///
10466 		R = 0x72, ///
10467 		T = 0x74, ///
10468 		Y = 0x79, ///
10469 		U = 0x75, ///
10470 		I = 0x69, ///
10471 		O = 0x6f, ///
10472 		P = 0x70, ///
10473 		LeftBracket = 0x5b, /// the [ { key
10474 		RightBracket = 0x5d, /// the ] } key
10475 		CapsLock = 0xffe5, ///
10476 		A = 0x61, ///
10477 		S = 0x73, ///
10478 		D = 0x64, ///
10479 		F = 0x66, ///
10480 		G = 0x67, ///
10481 		H = 0x68, ///
10482 		J = 0x6a, ///
10483 		K = 0x6b, ///
10484 		L = 0x6c, ///
10485 		Semicolon = 0x3b, ///
10486 		Apostrophe = 0x27, ///
10487 		Enter = 0xff0d, ///
10488 		Shift = 0xffe1, ///
10489 		Z = 0x7a, ///
10490 		X = 0x78, ///
10491 		C = 0x63, ///
10492 		V = 0x76, ///
10493 		B = 0x62, ///
10494 		N = 0x6e, ///
10495 		M = 0x6d, ///
10496 		Comma = 0x2c, ///
10497 		Period = 0x2e, ///
10498 		Slash = 0x2f, /// the / ? key
10499 		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
10500 		Ctrl = 0xffe3, ///
10501 		Windows = 0xffeb, ///
10502 		Alt = 0xffe9, ///
10503 		Space = 0x20, ///
10504 		Alt_r = 0xffea, /// ditto of shift_r
10505 		Windows_r = 0xffec, ///
10506 		Menu = 0xff67, ///
10507 		Ctrl_r = 0xffe4, ///
10508 
10509 		NumLock = 0xff7f, ///
10510 		Divide = 0xffaf, /// The / key on the number pad
10511 		Multiply = 0xffaa, /// The * key on the number pad
10512 		Minus = 0xffad, /// The - key on the number pad
10513 		Plus = 0xffab, /// The + key on the number pad
10514 		PadEnter = 0xff8d, /// Numberpad enter key
10515 		Pad1 = 0xff9c, /// Numberpad keys
10516 		Pad2 = 0xff99, ///
10517 		Pad3 = 0xff9b, ///
10518 		Pad4 = 0xff96, ///
10519 		Pad5 = 0xff9d, ///
10520 		Pad6 = 0xff98, ///
10521 		Pad7 = 0xff95, ///
10522 		Pad8 = 0xff97, ///
10523 		Pad9 = 0xff9a, ///
10524 		Pad0 = 0xff9e, ///
10525 		PadDot = 0xff9f, ///
10526 	}
10527 } else version(Windows) {
10528 	// the character here is for en-us layouts and for illustration only
10529 	// if you actually want to get characters, wait for character events
10530 	// (the argument to your event handler is simply a dchar)
10531 	// those will be converted by the OS for the right locale.
10532 
10533 	enum Key {
10534 		Escape = 0x1b,
10535 		F1 = 0x70,
10536 		F2 = 0x71,
10537 		F3 = 0x72,
10538 		F4 = 0x73,
10539 		F5 = 0x74,
10540 		F6 = 0x75,
10541 		F7 = 0x76,
10542 		F8 = 0x77,
10543 		F9 = 0x78,
10544 		F10 = 0x79,
10545 		F11 = 0x7a,
10546 		F12 = 0x7b,
10547 		PrintScreen = 0x2c,
10548 		ScrollLock = 0x91,
10549 		Pause = 0x13,
10550 		Grave = 0xc0,
10551 		// number keys across the top of the keyboard
10552 		N1 = 0x31,
10553 		N2 = 0x32,
10554 		N3 = 0x33,
10555 		N4 = 0x34,
10556 		N5 = 0x35,
10557 		N6 = 0x36,
10558 		N7 = 0x37,
10559 		N8 = 0x38,
10560 		N9 = 0x39,
10561 		N0 = 0x30,
10562 		Dash = 0xbd,
10563 		Equals = 0xbb,
10564 		Backslash = 0xdc,
10565 		Backspace = 0x08,
10566 		Insert = 0x2d,
10567 		Home = 0x24,
10568 		PageUp = 0x21,
10569 		Delete = 0x2e,
10570 		End = 0x23,
10571 		PageDown = 0x22,
10572 		Up = 0x26,
10573 		Down = 0x28,
10574 		Left = 0x25,
10575 		Right = 0x27,
10576 
10577 		Tab = 0x09,
10578 		Q = 0x51,
10579 		W = 0x57,
10580 		E = 0x45,
10581 		R = 0x52,
10582 		T = 0x54,
10583 		Y = 0x59,
10584 		U = 0x55,
10585 		I = 0x49,
10586 		O = 0x4f,
10587 		P = 0x50,
10588 		LeftBracket = 0xdb,
10589 		RightBracket = 0xdd,
10590 		CapsLock = 0x14,
10591 		A = 0x41,
10592 		S = 0x53,
10593 		D = 0x44,
10594 		F = 0x46,
10595 		G = 0x47,
10596 		H = 0x48,
10597 		J = 0x4a,
10598 		K = 0x4b,
10599 		L = 0x4c,
10600 		Semicolon = 0xba,
10601 		Apostrophe = 0xde,
10602 		Enter = 0x0d,
10603 		Shift = 0x10,
10604 		Z = 0x5a,
10605 		X = 0x58,
10606 		C = 0x43,
10607 		V = 0x56,
10608 		B = 0x42,
10609 		N = 0x4e,
10610 		M = 0x4d,
10611 		Comma = 0xbc,
10612 		Period = 0xbe,
10613 		Slash = 0xbf,
10614 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10615 		Ctrl = 0x11,
10616 		Windows = 0x5b,
10617 		Alt = -5, // FIXME
10618 		Space = 0x20,
10619 		Alt_r = 0xffea, // ditto of shift_r
10620 		Windows_r = 0x5c, // ditto of shift_r
10621 		Menu = 0x5d,
10622 		Ctrl_r = 0xa3, // ditto of shift_r
10623 
10624 		NumLock = 0x90,
10625 		Divide = 0x6f,
10626 		Multiply = 0x6a,
10627 		Minus = 0x6d,
10628 		Plus = 0x6b,
10629 		PadEnter = -8, // FIXME
10630 		Pad1 = 0x61,
10631 		Pad2 = 0x62,
10632 		Pad3 = 0x63,
10633 		Pad4 = 0x64,
10634 		Pad5 = 0x65,
10635 		Pad6 = 0x66,
10636 		Pad7 = 0x67,
10637 		Pad8 = 0x68,
10638 		Pad9 = 0x69,
10639 		Pad0 = 0x60,
10640 		PadDot = 0x6e,
10641 	}
10642 
10643 	// I'm keeping this around for reference purposes
10644 	// ideally all these buttons will be listed for all platforms,
10645 	// but now now I'm just focusing on my US keyboard
10646 	version(none)
10647 	enum Key {
10648 		LBUTTON = 0x01,
10649 		RBUTTON = 0x02,
10650 		CANCEL = 0x03,
10651 		MBUTTON = 0x04,
10652 		//static if (_WIN32_WINNT > =  0x500) {
10653 		XBUTTON1 = 0x05,
10654 		XBUTTON2 = 0x06,
10655 		//}
10656 		BACK = 0x08,
10657 		TAB = 0x09,
10658 		CLEAR = 0x0C,
10659 		RETURN = 0x0D,
10660 		SHIFT = 0x10,
10661 		CONTROL = 0x11,
10662 		MENU = 0x12,
10663 		PAUSE = 0x13,
10664 		CAPITAL = 0x14,
10665 		KANA = 0x15,
10666 		HANGEUL = 0x15,
10667 		HANGUL = 0x15,
10668 		JUNJA = 0x17,
10669 		FINAL = 0x18,
10670 		HANJA = 0x19,
10671 		KANJI = 0x19,
10672 		ESCAPE = 0x1B,
10673 		CONVERT = 0x1C,
10674 		NONCONVERT = 0x1D,
10675 		ACCEPT = 0x1E,
10676 		MODECHANGE = 0x1F,
10677 		SPACE = 0x20,
10678 		PRIOR = 0x21,
10679 		NEXT = 0x22,
10680 		END = 0x23,
10681 		HOME = 0x24,
10682 		LEFT = 0x25,
10683 		UP = 0x26,
10684 		RIGHT = 0x27,
10685 		DOWN = 0x28,
10686 		SELECT = 0x29,
10687 		PRINT = 0x2A,
10688 		EXECUTE = 0x2B,
10689 		SNAPSHOT = 0x2C,
10690 		INSERT = 0x2D,
10691 		DELETE = 0x2E,
10692 		HELP = 0x2F,
10693 		LWIN = 0x5B,
10694 		RWIN = 0x5C,
10695 		APPS = 0x5D,
10696 		SLEEP = 0x5F,
10697 		NUMPAD0 = 0x60,
10698 		NUMPAD1 = 0x61,
10699 		NUMPAD2 = 0x62,
10700 		NUMPAD3 = 0x63,
10701 		NUMPAD4 = 0x64,
10702 		NUMPAD5 = 0x65,
10703 		NUMPAD6 = 0x66,
10704 		NUMPAD7 = 0x67,
10705 		NUMPAD8 = 0x68,
10706 		NUMPAD9 = 0x69,
10707 		MULTIPLY = 0x6A,
10708 		ADD = 0x6B,
10709 		SEPARATOR = 0x6C,
10710 		SUBTRACT = 0x6D,
10711 		DECIMAL = 0x6E,
10712 		DIVIDE = 0x6F,
10713 		F1 = 0x70,
10714 		F2 = 0x71,
10715 		F3 = 0x72,
10716 		F4 = 0x73,
10717 		F5 = 0x74,
10718 		F6 = 0x75,
10719 		F7 = 0x76,
10720 		F8 = 0x77,
10721 		F9 = 0x78,
10722 		F10 = 0x79,
10723 		F11 = 0x7A,
10724 		F12 = 0x7B,
10725 		F13 = 0x7C,
10726 		F14 = 0x7D,
10727 		F15 = 0x7E,
10728 		F16 = 0x7F,
10729 		F17 = 0x80,
10730 		F18 = 0x81,
10731 		F19 = 0x82,
10732 		F20 = 0x83,
10733 		F21 = 0x84,
10734 		F22 = 0x85,
10735 		F23 = 0x86,
10736 		F24 = 0x87,
10737 		NUMLOCK = 0x90,
10738 		SCROLL = 0x91,
10739 		LSHIFT = 0xA0,
10740 		RSHIFT = 0xA1,
10741 		LCONTROL = 0xA2,
10742 		RCONTROL = 0xA3,
10743 		LMENU = 0xA4,
10744 		RMENU = 0xA5,
10745 		//static if (_WIN32_WINNT > =  0x500) {
10746 		BROWSER_BACK = 0xA6,
10747 		BROWSER_FORWARD = 0xA7,
10748 		BROWSER_REFRESH = 0xA8,
10749 		BROWSER_STOP = 0xA9,
10750 		BROWSER_SEARCH = 0xAA,
10751 		BROWSER_FAVORITES = 0xAB,
10752 		BROWSER_HOME = 0xAC,
10753 		VOLUME_MUTE = 0xAD,
10754 		VOLUME_DOWN = 0xAE,
10755 		VOLUME_UP = 0xAF,
10756 		MEDIA_NEXT_TRACK = 0xB0,
10757 		MEDIA_PREV_TRACK = 0xB1,
10758 		MEDIA_STOP = 0xB2,
10759 		MEDIA_PLAY_PAUSE = 0xB3,
10760 		LAUNCH_MAIL = 0xB4,
10761 		LAUNCH_MEDIA_SELECT = 0xB5,
10762 		LAUNCH_APP1 = 0xB6,
10763 		LAUNCH_APP2 = 0xB7,
10764 		//}
10765 		OEM_1 = 0xBA,
10766 		//static if (_WIN32_WINNT > =  0x500) {
10767 		OEM_PLUS = 0xBB,
10768 		OEM_COMMA = 0xBC,
10769 		OEM_MINUS = 0xBD,
10770 		OEM_PERIOD = 0xBE,
10771 		//}
10772 		OEM_2 = 0xBF,
10773 		OEM_3 = 0xC0,
10774 		OEM_4 = 0xDB,
10775 		OEM_5 = 0xDC,
10776 		OEM_6 = 0xDD,
10777 		OEM_7 = 0xDE,
10778 		OEM_8 = 0xDF,
10779 		//static if (_WIN32_WINNT > =  0x500) {
10780 		OEM_102 = 0xE2,
10781 		//}
10782 		PROCESSKEY = 0xE5,
10783 		//static if (_WIN32_WINNT > =  0x500) {
10784 		PACKET = 0xE7,
10785 		//}
10786 		ATTN = 0xF6,
10787 		CRSEL = 0xF7,
10788 		EXSEL = 0xF8,
10789 		EREOF = 0xF9,
10790 		PLAY = 0xFA,
10791 		ZOOM = 0xFB,
10792 		NONAME = 0xFC,
10793 		PA1 = 0xFD,
10794 		OEM_CLEAR = 0xFE,
10795 	}
10796 
10797 } else version(OSXCocoa) {
10798 	// FIXME
10799 	enum Key {
10800 		Escape = 0x1b,
10801 		F1 = 0x70,
10802 		F2 = 0x71,
10803 		F3 = 0x72,
10804 		F4 = 0x73,
10805 		F5 = 0x74,
10806 		F6 = 0x75,
10807 		F7 = 0x76,
10808 		F8 = 0x77,
10809 		F9 = 0x78,
10810 		F10 = 0x79,
10811 		F11 = 0x7a,
10812 		F12 = 0x7b,
10813 		PrintScreen = 0x2c,
10814 		ScrollLock = -2, // FIXME
10815 		Pause = -3, // FIXME
10816 		Grave = 0xc0,
10817 		// number keys across the top of the keyboard
10818 		N1 = 0x31,
10819 		N2 = 0x32,
10820 		N3 = 0x33,
10821 		N4 = 0x34,
10822 		N5 = 0x35,
10823 		N6 = 0x36,
10824 		N7 = 0x37,
10825 		N8 = 0x38,
10826 		N9 = 0x39,
10827 		N0 = 0x30,
10828 		Dash = 0xbd,
10829 		Equals = 0xbb,
10830 		Backslash = 0xdc,
10831 		Backspace = 0x08,
10832 		Insert = 0x2d,
10833 		Home = 0x24,
10834 		PageUp = 0x21,
10835 		Delete = 0x2e,
10836 		End = 0x23,
10837 		PageDown = 0x22,
10838 		Up = 0x26,
10839 		Down = 0x28,
10840 		Left = 0x25,
10841 		Right = 0x27,
10842 
10843 		Tab = 0x09,
10844 		Q = 0x51,
10845 		W = 0x57,
10846 		E = 0x45,
10847 		R = 0x52,
10848 		T = 0x54,
10849 		Y = 0x59,
10850 		U = 0x55,
10851 		I = 0x49,
10852 		O = 0x4f,
10853 		P = 0x50,
10854 		LeftBracket = 0xdb,
10855 		RightBracket = 0xdd,
10856 		CapsLock = 0x14,
10857 		A = 0x41,
10858 		S = 0x53,
10859 		D = 0x44,
10860 		F = 0x46,
10861 		G = 0x47,
10862 		H = 0x48,
10863 		J = 0x4a,
10864 		K = 0x4b,
10865 		L = 0x4c,
10866 		Semicolon = 0xba,
10867 		Apostrophe = 0xde,
10868 		Enter = 0x0d,
10869 		Shift = 0x10,
10870 		Z = 0x5a,
10871 		X = 0x58,
10872 		C = 0x43,
10873 		V = 0x56,
10874 		B = 0x42,
10875 		N = 0x4e,
10876 		M = 0x4d,
10877 		Comma = 0xbc,
10878 		Period = 0xbe,
10879 		Slash = 0xbf,
10880 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10881 		Ctrl = 0x11,
10882 		Windows = 0x5b,
10883 		Alt = -5, // FIXME
10884 		Space = 0x20,
10885 		Alt_r = 0xffea, // ditto of shift_r
10886 		Windows_r = -6, // FIXME
10887 		Menu = 0x5d,
10888 		Ctrl_r = -7, // FIXME
10889 
10890 		NumLock = 0x90,
10891 		Divide = 0x6f,
10892 		Multiply = 0x6a,
10893 		Minus = 0x6d,
10894 		Plus = 0x6b,
10895 		PadEnter = -8, // FIXME
10896 		// FIXME for the rest of these:
10897 		Pad1 = 0xff9c,
10898 		Pad2 = 0xff99,
10899 		Pad3 = 0xff9b,
10900 		Pad4 = 0xff96,
10901 		Pad5 = 0xff9d,
10902 		Pad6 = 0xff98,
10903 		Pad7 = 0xff95,
10904 		Pad8 = 0xff97,
10905 		Pad9 = 0xff9a,
10906 		Pad0 = 0xff9e,
10907 		PadDot = 0xff9f,
10908 	}
10909 
10910 }
10911 
10912 /* Additional utilities */
10913 
10914 
10915 Color fromHsl(real h, real s, real l) {
10916 	return arsd.color.fromHsl([h,s,l]);
10917 }
10918 
10919 
10920 
10921 /* ********** What follows is the system-specific implementations *********/
10922 version(Windows) {
10923 
10924 
10925 	// helpers for making HICONs from MemoryImages
10926 	class WindowsIcon {
10927 		struct Win32Icon(int colorCount) {
10928 		align(1):
10929 			uint biSize;
10930 			int biWidth;
10931 			int biHeight;
10932 			ushort biPlanes;
10933 			ushort biBitCount;
10934 			uint biCompression;
10935 			uint biSizeImage;
10936 			int biXPelsPerMeter;
10937 			int biYPelsPerMeter;
10938 			uint biClrUsed;
10939 			uint biClrImportant;
10940 			RGBQUAD[colorCount] biColors;
10941 			/* Pixels:
10942 			Uint8 pixels[]
10943 			*/
10944 			/* Mask:
10945 			Uint8 mask[]
10946 			*/
10947 
10948 			// FIXME: this sux, needs to be dynamic
10949 			ubyte[/*4096*/ 256*256*4 + 256*256/8] data;
10950 
10951 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
10952 				width = mi.width;
10953 				height = mi.height;
10954 
10955 				auto indexedImage = cast(IndexedImage) mi;
10956 				if(indexedImage is null)
10957 					indexedImage = quantize(mi.getAsTrueColorImage());
10958 
10959 				assert(width <= 256, "image too wide");
10960 				assert(height <= 256, "image too tall");
10961 				assert(width %8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy
10962 				assert(height %4 == 0, "image not multiple of 4 height");
10963 
10964 				int icon_plen = height*((width+3)&~3);
10965 				int icon_mlen = height*((((width+7)/8)+3)&~3);
10966 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
10967 
10968 				biSize = 40;
10969 				biWidth = width;
10970 				biHeight = height*2;
10971 				biPlanes = 1;
10972 				biBitCount = 8;
10973 				biSizeImage = icon_plen+icon_mlen;
10974 
10975 				int offset = 0;
10976 				int andOff = icon_plen * 8; // the and offset is in bits
10977 				for(int y = height - 1; y >= 0; y--) {
10978 					int off2 = y * width;
10979 					foreach(x; 0 .. width) {
10980 						const b = indexedImage.data[off2 + x];
10981 						data[offset] = b;
10982 						offset++;
10983 
10984 						const andBit = andOff % 8;
10985 						const andIdx = andOff / 8;
10986 						assert(b < indexedImage.palette.length);
10987 						// this is anded to the destination, since and 0 means erase,
10988 						// we want that to  be opaque, and 1 for transparent
10989 						auto transparent = (indexedImage.palette[b].a <= 127);
10990 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
10991 
10992 						andOff++;
10993 					}
10994 
10995 					andOff += andOff % 32;
10996 				}
10997 
10998 				foreach(idx, entry; indexedImage.palette) {
10999 					if(entry.a > 127) {
11000 						biColors[idx].rgbBlue = entry.b;
11001 						biColors[idx].rgbGreen = entry.g;
11002 						biColors[idx].rgbRed = entry.r;
11003 					} else {
11004 						biColors[idx].rgbBlue = 255;
11005 						biColors[idx].rgbGreen = 255;
11006 						biColors[idx].rgbRed = 255;
11007 					}
11008 				}
11009 
11010 				/*
11011 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
11012 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
11013 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
11014 				auto pngMap = fetchPaletteWin32(png);
11015 				biColors[0..pngMap.length] = pngMap[];
11016 				*/
11017 			}
11018 		}
11019 
11020 
11021 		Win32Icon!(256) icon_win32;
11022 
11023 
11024 		this(MemoryImage mi) {
11025 			int icon_len, width, height;
11026 
11027 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
11028 
11029 			/*
11030 			PNG* png = readPnpngData);
11031 			PNGHeader pngh = getHeader(png);
11032 			void* icon_win32;
11033 			if(pngh.depth == 4) {
11034 				auto i = new Win32Icon!(16);
11035 				i.fromPNG(png, pngh, icon_len, width, height);
11036 				icon_win32 = i;
11037 			}
11038 			else if(pngh.depth == 8) {
11039 				auto i = new Win32Icon!(256);
11040 				i.fromPNG(png, pngh, icon_len, width, height);
11041 				icon_win32 = i;
11042 			} else assert(0);
11043 			*/
11044 
11045 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
11046 
11047 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11048 		}
11049 
11050 		~this() {
11051 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11052 			DestroyIcon(hIcon);
11053 		}
11054 
11055 		HICON hIcon;
11056 	}
11057 
11058 
11059 
11060 
11061 
11062 
11063 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11064 	alias HWND NativeWindowHandle;
11065 
11066 	extern(Windows)
11067 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11068 		try {
11069 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11070 				// it returns zero if the message is handled, so we won't do anything more there
11071 				// do I like that though?
11072 				int mustReturn;
11073 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11074 				if(mustReturn)
11075 					return ret;
11076 			}
11077 
11078 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11079 				if(window.getNativeEventHandler !is null) {
11080 					int mustReturn;
11081 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11082 					if(mustReturn)
11083 						return ret;
11084 				}
11085 				if(auto w = cast(SimpleWindow) (*window))
11086 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11087 				else
11088 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11089 			} else {
11090 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11091 			}
11092 		} catch (Exception e) {
11093 			try {
11094 				sdpy_abort(e);
11095 				return 0;
11096 			} catch(Exception e) { assert(0); }
11097 		}
11098 	}
11099 
11100 	void sdpy_abort(Throwable e) nothrow {
11101 		try
11102 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11103 		catch(Exception e)
11104 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11105 		ExitProcess(1);
11106 	}
11107 
11108 	mixin template NativeScreenPainterImplementation() {
11109 		HDC hdc;
11110 		HWND hwnd;
11111 		//HDC windowHdc;
11112 		HBITMAP oldBmp;
11113 
11114 		void create(NativeWindowHandle window) {
11115 			hwnd = window;
11116 
11117 			if(auto sw = cast(SimpleWindow) this.window) {
11118 				// drawing on a window, double buffer
11119 				auto windowHdc = GetDC(hwnd);
11120 
11121 				auto buffer = sw.impl.buffer;
11122 				if(buffer is null) {
11123 					hdc = windowHdc;
11124 					windowDc = true;
11125 				} else {
11126 					hdc = CreateCompatibleDC(windowHdc);
11127 
11128 					ReleaseDC(hwnd, windowHdc);
11129 
11130 					oldBmp = SelectObject(hdc, buffer);
11131 				}
11132 			} else {
11133 				// drawing on something else, draw directly
11134 				hdc = CreateCompatibleDC(null);
11135 				SelectObject(hdc, window);
11136 			}
11137 
11138 			// X doesn't draw a text background, so neither should we
11139 			SetBkMode(hdc, TRANSPARENT);
11140 
11141 			ensureDefaultFontLoaded();
11142 
11143 			if(defaultGuiFont) {
11144 				SelectObject(hdc, defaultGuiFont);
11145 				// DeleteObject(defaultGuiFont);
11146 			}
11147 		}
11148 
11149 		static HFONT defaultGuiFont;
11150 		static void ensureDefaultFontLoaded() {
11151 			static bool triedDefaultGuiFont = false;
11152 			if(!triedDefaultGuiFont) {
11153 				NONCLIENTMETRICS params;
11154 				params.cbSize = params.sizeof;
11155 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11156 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11157 				}
11158 				triedDefaultGuiFont = true;
11159 			}
11160 		}
11161 
11162 		private OperatingSystemFont _activeFont;
11163 
11164 		void setFont(OperatingSystemFont font) {
11165 			_activeFont = font;
11166 			if(font && font.font) {
11167 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11168 					// error... how to handle tho?
11169 				} else {
11170 
11171 				}
11172 			}
11173 			else if(defaultGuiFont)
11174 				SelectObject(hdc, defaultGuiFont);
11175 		}
11176 
11177 		arsd.color.Rectangle _clipRectangle;
11178 
11179 		void setClipRectangle(int x, int y, int width, int height) {
11180 			auto old = _clipRectangle;
11181 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11182 			if(old == _clipRectangle)
11183 				return;
11184 
11185 			if(width == 0 || height == 0) {
11186 				SelectClipRgn(hdc, null);
11187 			} else {
11188 				auto region = CreateRectRgn(x, y, x + width, y + height);
11189 				SelectClipRgn(hdc, region);
11190 				DeleteObject(region);
11191 			}
11192 		}
11193 
11194 
11195 		// just because we can on Windows...
11196 		//void create(Image image);
11197 
11198 		void invalidateRect(Rectangle invalidRect) {
11199 			RECT rect;
11200 			rect.left = invalidRect.left;
11201 			rect.right = invalidRect.right;
11202 			rect.top = invalidRect.top;
11203 			rect.bottom = invalidRect.bottom;
11204 			InvalidateRect(hwnd, &rect, false);
11205 		}
11206 		bool manualInvalidations;
11207 
11208 		void dispose() {
11209 			// FIXME: this.window.width/height is probably wrong
11210 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11211 			// ReleaseDC(hwnd, windowHdc);
11212 
11213 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11214 			if(cast(SimpleWindow) this.window) {
11215 				if(!manualInvalidations)
11216 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11217 			}
11218 
11219 			if(originalPen !is null)
11220 				SelectObject(hdc, originalPen);
11221 			if(currentPen !is null)
11222 				DeleteObject(currentPen);
11223 			if(originalBrush !is null)
11224 				SelectObject(hdc, originalBrush);
11225 			if(currentBrush !is null)
11226 				DeleteObject(currentBrush);
11227 
11228 			SelectObject(hdc, oldBmp);
11229 
11230 			if(windowDc)
11231 				ReleaseDC(hwnd, hdc);
11232 			else
11233 				DeleteDC(hdc);
11234 
11235 			if(window.paintingFinishedDg !is null)
11236 				window.paintingFinishedDg()();
11237 		}
11238 
11239 		bool windowDc;
11240 		HPEN originalPen;
11241 		HPEN currentPen;
11242 
11243 		Pen _activePen;
11244 
11245 		Color _outlineColor;
11246 
11247 		@property void pen(Pen p) {
11248 			_activePen = p;
11249 			_outlineColor = p.color;
11250 
11251 			HPEN pen;
11252 			if(p.color.a == 0) {
11253 				pen = GetStockObject(NULL_PEN);
11254 			} else {
11255 				int style = PS_SOLID;
11256 				final switch(p.style) {
11257 					case Pen.Style.Solid:
11258 						style = PS_SOLID;
11259 					break;
11260 					case Pen.Style.Dashed:
11261 						style = PS_DASH;
11262 					break;
11263 					case Pen.Style.Dotted:
11264 						style = PS_DOT;
11265 					break;
11266 				}
11267 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11268 			}
11269 			auto orig = SelectObject(hdc, pen);
11270 			if(originalPen is null)
11271 				originalPen = orig;
11272 
11273 			if(currentPen !is null)
11274 				DeleteObject(currentPen);
11275 
11276 			currentPen = pen;
11277 
11278 			// the outline is like a foreground since it's done that way on X
11279 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11280 
11281 		}
11282 
11283 		@property void rasterOp(RasterOp op) {
11284 			int mode;
11285 			final switch(op) {
11286 				case RasterOp.normal:
11287 					mode = R2_COPYPEN;
11288 				break;
11289 				case RasterOp.xor:
11290 					mode = R2_XORPEN;
11291 				break;
11292 			}
11293 			SetROP2(hdc, mode);
11294 		}
11295 
11296 		HBRUSH originalBrush;
11297 		HBRUSH currentBrush;
11298 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11299 		@property void fillColor(Color c) {
11300 			if(c == _fillColor)
11301 				return;
11302 			_fillColor = c;
11303 			HBRUSH brush;
11304 			if(c.a == 0) {
11305 				brush = GetStockObject(HOLLOW_BRUSH);
11306 			} else {
11307 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11308 			}
11309 			auto orig = SelectObject(hdc, brush);
11310 			if(originalBrush is null)
11311 				originalBrush = orig;
11312 
11313 			if(currentBrush !is null)
11314 				DeleteObject(currentBrush);
11315 
11316 			currentBrush = brush;
11317 
11318 			// background color is NOT set because X doesn't draw text backgrounds
11319 			//   SetBkColor(hdc, RGB(255, 255, 255));
11320 		}
11321 
11322 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11323 			BITMAP bm;
11324 
11325 			HDC hdcMem = CreateCompatibleDC(hdc);
11326 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11327 
11328 			GetObject(i.handle, bm.sizeof, &bm);
11329 
11330 			// or should I AlphaBlend!??!?!
11331 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11332 
11333 			SelectObject(hdcMem, hbmOld);
11334 			DeleteDC(hdcMem);
11335 		}
11336 
11337 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11338 			BITMAP bm;
11339 
11340 			HDC hdcMem = CreateCompatibleDC(hdc);
11341 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11342 
11343 			GetObject(s.handle, bm.sizeof, &bm);
11344 
11345 			version(CRuntime_DigitalMars) goto noalpha;
11346 
11347 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11348 			if(s.enableAlpha) {
11349 				auto dw = w ? w : bm.bmWidth;
11350 				auto dh = h ? h : bm.bmHeight;
11351 				BLENDFUNCTION bf;
11352 				bf.BlendOp = AC_SRC_OVER;
11353 				bf.SourceConstantAlpha = 255;
11354 				bf.AlphaFormat = AC_SRC_ALPHA;
11355 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11356 			} else {
11357 				noalpha:
11358 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11359 			}
11360 
11361 			SelectObject(hdcMem, hbmOld);
11362 			DeleteDC(hdcMem);
11363 		}
11364 
11365 		Size textSize(scope const(char)[] text) {
11366 			bool dummyX;
11367 			if(text.length == 0) {
11368 				text = " ";
11369 				dummyX = true;
11370 			}
11371 			RECT rect;
11372 			WCharzBuffer buffer = WCharzBuffer(text);
11373 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11374 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11375 		}
11376 
11377 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11378 			if(text.length && text[$-1] == '\n')
11379 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11380 			if(text.length && text[$-1] == '\r')
11381 				text = text[0 .. $-1];
11382 
11383 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11384 			if(x2 == 0 && y2 == 0) {
11385 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11386 			} else {
11387 				RECT rect;
11388 				rect.left = x;
11389 				rect.top = y;
11390 				rect.right = x2;
11391 				rect.bottom = y2;
11392 
11393 				uint mode = DT_LEFT;
11394 				if(alignment & TextAlignment.Right)
11395 					mode = DT_RIGHT;
11396 				else if(alignment & TextAlignment.Center)
11397 					mode = DT_CENTER;
11398 
11399 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11400 				if(alignment & TextAlignment.VerticalCenter)
11401 					mode |= DT_VCENTER | DT_SINGLELINE;
11402 
11403 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11404 			}
11405 
11406 			/*
11407 			uint mode;
11408 
11409 			if(alignment & TextAlignment.Center)
11410 				mode = TA_CENTER;
11411 
11412 			SetTextAlign(hdc, mode);
11413 			*/
11414 		}
11415 
11416 		int fontHeight() {
11417 			TEXTMETRIC metric;
11418 			if(GetTextMetricsW(hdc, &metric)) {
11419 				return metric.tmHeight;
11420 			}
11421 
11422 			return 16; // idk just guessing here, maybe we should throw
11423 		}
11424 
11425 		void drawPixel(int x, int y) {
11426 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11427 		}
11428 
11429 		// The basic shapes, outlined
11430 
11431 		void drawLine(int x1, int y1, int x2, int y2) {
11432 			MoveToEx(hdc, x1, y1, null);
11433 			LineTo(hdc, x2, y2);
11434 		}
11435 
11436 		void drawRectangle(int x, int y, int width, int height) {
11437 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11438 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11439 		}
11440 
11441 		/// Arguments are the points of the bounding rectangle
11442 		void drawEllipse(int x1, int y1, int x2, int y2) {
11443 			Ellipse(hdc, x1, y1, x2, y2);
11444 		}
11445 
11446 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11447 			if((start % (360*64)) == (finish % (360*64)))
11448 				drawEllipse(x1, y1, x1 + width, y1 + height);
11449 			else {
11450 				import core.stdc.math;
11451 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11452 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11453 
11454 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11455 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11456 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11457 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11458 
11459 				if(_activePen.color.a)
11460 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11461 				if(_fillColor.a)
11462 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11463 			}
11464 		}
11465 
11466 		void drawPolygon(Point[] vertexes) {
11467 			POINT[] points;
11468 			points.length = vertexes.length;
11469 
11470 			foreach(i, p; vertexes) {
11471 				points[i].x = p.x;
11472 				points[i].y = p.y;
11473 			}
11474 
11475 			Polygon(hdc, points.ptr, cast(int) points.length);
11476 		}
11477 	}
11478 
11479 
11480 	// Mix this into the SimpleWindow class
11481 	mixin template NativeSimpleWindowImplementation() {
11482 		int curHidden = 0; // counter
11483 		__gshared static bool[string] knownWinClasses;
11484 		static bool altPressed = false;
11485 
11486 		HANDLE oldCursor;
11487 
11488 		void hideCursor () {
11489 			if(curHidden == 0)
11490 				oldCursor = SetCursor(null);
11491 			++curHidden;
11492 		}
11493 
11494 		void showCursor () {
11495 			--curHidden;
11496 			if(curHidden == 0) {
11497 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11498 			}
11499 		}
11500 
11501 
11502 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11503 
11504 		void setMinSize (int minwidth, int minheight) {
11505 			minWidth = minwidth;
11506 			minHeight = minheight;
11507 		}
11508 		void setMaxSize (int maxwidth, int maxheight) {
11509 			maxWidth = maxwidth;
11510 			maxHeight = maxheight;
11511 		}
11512 
11513 		// FIXME i'm not sure that Windows has this functionality
11514 		// though it is nonessential anyway.
11515 		void setResizeGranularity (int granx, int grany) {}
11516 
11517 		ScreenPainter getPainter(bool manualInvalidations) {
11518 			return ScreenPainter(this, hwnd, manualInvalidations);
11519 		}
11520 
11521 		HBITMAP buffer;
11522 
11523 		void setTitle(string title) {
11524 			WCharzBuffer bfr = WCharzBuffer(title);
11525 			SetWindowTextW(hwnd, bfr.ptr);
11526 		}
11527 
11528 		string getTitle() {
11529 			auto len = GetWindowTextLengthW(hwnd);
11530 			if (!len)
11531 				return null;
11532 			wchar[256] tmpBuffer;
11533 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11534 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11535 			auto str = buffer[0 .. len2];
11536 			return makeUtf8StringFromWindowsString(str);
11537 		}
11538 
11539 		void move(int x, int y) {
11540 			RECT rect;
11541 			GetWindowRect(hwnd, &rect);
11542 			// move it while maintaining the same size...
11543 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11544 		}
11545 
11546 		void resize(int w, int h) {
11547 			RECT rect;
11548 			GetWindowRect(hwnd, &rect);
11549 
11550 			RECT client;
11551 			GetClientRect(hwnd, &client);
11552 
11553 			rect.right = rect.right - client.right + w;
11554 			rect.bottom = rect.bottom - client.bottom + h;
11555 
11556 			// same position, new size for the client rectangle
11557 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11558 
11559 			updateOpenglViewportIfNeeded(w, h);
11560 		}
11561 
11562 		void moveResize (int x, int y, int w, int h) {
11563 			// what's given is the client rectangle, we need to adjust
11564 
11565 			RECT rect;
11566 			rect.left = x;
11567 			rect.top = y;
11568 			rect.right = w + x;
11569 			rect.bottom = h + y;
11570 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11571 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11572 
11573 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11574 			updateOpenglViewportIfNeeded(w, h);
11575 			if (windowResized !is null) windowResized(w, h);
11576 		}
11577 
11578 		version(without_opengl) {} else {
11579 			HGLRC ghRC;
11580 			HDC ghDC;
11581 		}
11582 
11583 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11584 			string cnamec;
11585 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11586 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11587 				cnamec = "DSimpleWindow";
11588 			} else {
11589 				cnamec = sdpyWindowClass;
11590 			}
11591 
11592 			WCharzBuffer cn = WCharzBuffer(cnamec);
11593 
11594 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11595 
11596 			if(cnamec !in knownWinClasses) {
11597 				WNDCLASSEX wc;
11598 
11599 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11600 				// to the object. Maybe.
11601 				wc.cbSize = wc.sizeof;
11602 				wc.cbClsExtra = 0;
11603 				wc.cbWndExtra = 0;
11604 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11605 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11606 				wc.hIcon = LoadIcon(hInstance, null);
11607 				wc.hInstance = hInstance;
11608 				wc.lpfnWndProc = &WndProc;
11609 				wc.lpszClassName = cn.ptr;
11610 				wc.hIconSm = null;
11611 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11612 				if(!RegisterClassExW(&wc))
11613 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11614 				knownWinClasses[cnamec] = true;
11615 			}
11616 
11617 			int style;
11618 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11619 
11620 			// FIXME: windowType and customizationFlags
11621 			final switch(windowType) {
11622 				case WindowTypes.normal:
11623 					if(resizability == Resizability.fixedSize) {
11624 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11625 					} else {
11626 						style = WS_OVERLAPPEDWINDOW;
11627 					}
11628 				break;
11629 				case WindowTypes.undecorated:
11630 					style = WS_POPUP | WS_SYSMENU;
11631 				break;
11632 				case WindowTypes.eventOnly:
11633 					_hidden = true;
11634 				break;
11635 				case WindowTypes.dropdownMenu:
11636 				case WindowTypes.popupMenu:
11637 				case WindowTypes.notification:
11638 					style = WS_POPUP;
11639 					flags |= WS_EX_NOACTIVATE;
11640 				break;
11641 				case WindowTypes.nestedChild:
11642 					style = WS_CHILD;
11643 				break;
11644 				case WindowTypes.minimallyWrapped:
11645 					assert(0, "construct minimally wrapped through the other ctor overlad");
11646 			}
11647 
11648 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11649 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11650 
11651 			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
11652 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11653 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11654 
11655 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11656 				setOpacity(255);
11657 
11658 			SimpleWindow.nativeMapping[hwnd] = this;
11659 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11660 
11661 			if(windowType == WindowTypes.eventOnly)
11662 				return;
11663 
11664 			HDC hdc = GetDC(hwnd);
11665 
11666 
11667 			version(without_opengl) {}
11668 			else {
11669 				if(opengl == OpenGlOptions.yes) {
11670 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11671 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11672 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11673 					ghDC = hdc;
11674 					PIXELFORMATDESCRIPTOR pfd;
11675 
11676 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11677 					pfd.nVersion = 1;
11678 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11679 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11680 					pfd.iPixelType = PFD_TYPE_RGBA;
11681 					pfd.cColorBits = 24;
11682 					pfd.cDepthBits = 24;
11683 					pfd.cAccumBits = 0;
11684 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11685 
11686 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11687 
11688 					if (pixelformat == 0)
11689 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11690 
11691 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11692 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11693 
11694 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11695 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11696 						// so we will create fake context to get that stupid address
11697 						auto tmpcc = wglCreateContext(ghDC);
11698 						if (tmpcc !is null) {
11699 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11700 							wglMakeCurrent(ghDC, tmpcc);
11701 							wglInitOtherFunctions();
11702 						}
11703 					}
11704 
11705 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11706 						int[9] contextAttribs = [
11707 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11708 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11709 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11710 							// for modern context, set "forward compatibility" flag too
11711 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11712 							0/*None*/,
11713 						];
11714 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11715 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11716 							// activate fallback mode
11717 							// 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;
11718 							ghRC = wglCreateContext(ghDC);
11719 						}
11720 						if (ghRC is null)
11721 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11722 					} else {
11723 						// try to do at least something
11724 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11725 							sdpyOpenGLContextVersion = 0;
11726 							ghRC = wglCreateContext(ghDC);
11727 						}
11728 						if (ghRC is null)
11729 							throw new WindowsApiException("wglCreateContext", GetLastError());
11730 					}
11731 				}
11732 			}
11733 
11734 			if(opengl == OpenGlOptions.no) {
11735 				buffer = CreateCompatibleBitmap(hdc, width, height);
11736 
11737 				auto hdcBmp = CreateCompatibleDC(hdc);
11738 				// make sure it's filled with a blank slate
11739 				auto oldBmp = SelectObject(hdcBmp, buffer);
11740 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11741 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11742 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11743 				SelectObject(hdcBmp, oldBmp);
11744 				SelectObject(hdcBmp, oldBrush);
11745 				SelectObject(hdcBmp, oldPen);
11746 				DeleteDC(hdcBmp);
11747 
11748 				bmpWidth = width;
11749 				bmpHeight = height;
11750 
11751 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11752 			}
11753 
11754 			// We want the window's client area to match the image size
11755 			RECT rcClient, rcWindow;
11756 			POINT ptDiff;
11757 			GetClientRect(hwnd, &rcClient);
11758 			GetWindowRect(hwnd, &rcWindow);
11759 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11760 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11761 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11762 
11763 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11764 				ShowWindow(hwnd, SW_SHOWNORMAL);
11765 			} else {
11766 				_hidden = true;
11767 			}
11768 			this._visibleForTheFirstTimeCalled = false; // hack!
11769 		}
11770 
11771 
11772 		void dispose() {
11773 			if(buffer)
11774 				DeleteObject(buffer);
11775 		}
11776 
11777 		void closeWindow() {
11778 			if(ghRC) {
11779 				wglDeleteContext(ghRC);
11780 				ghRC = null;
11781 			}
11782 			DestroyWindow(hwnd);
11783 		}
11784 
11785 		bool setOpacity(ubyte alpha) {
11786 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11787 		}
11788 
11789 		HANDLE currentCursor;
11790 
11791 		// returns zero if it recognized the event
11792 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11793 			MouseEvent mouse;
11794 
11795 			void mouseEvent(bool isScreen, ulong mods) {
11796 				auto x = LOWORD(lParam);
11797 				auto y = HIWORD(lParam);
11798 				if(isScreen) {
11799 					POINT p;
11800 					p.x = x;
11801 					p.y = y;
11802 					ScreenToClient(hwnd, &p);
11803 					x = cast(ushort) p.x;
11804 					y = cast(ushort) p.y;
11805 				}
11806 
11807 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11808 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11809 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11810 				}
11811 
11812 				mouse.x = x + offsetX;
11813 				mouse.y = y + offsetY;
11814 
11815 				wind.mdx(mouse);
11816 				mouse.modifierState = cast(int) mods;
11817 				mouse.window = wind;
11818 
11819 				if(wind.handleMouseEvent)
11820 					wind.handleMouseEvent(mouse);
11821 			}
11822 
11823 			switch(msg) {
11824 				case WM_GETMINMAXINFO:
11825 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11826 
11827 					if(wind.minWidth > 0) {
11828 						RECT rect;
11829 						rect.left = 100;
11830 						rect.top = 100;
11831 						rect.right = wind.minWidth + 100;
11832 						rect.bottom = wind.minHeight + 100;
11833 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11834 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11835 
11836 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11837 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11838 					}
11839 
11840 					if(wind.maxWidth < int.max) {
11841 						RECT rect;
11842 						rect.left = 100;
11843 						rect.top = 100;
11844 						rect.right = wind.maxWidth + 100;
11845 						rect.bottom = wind.maxHeight + 100;
11846 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11847 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11848 
11849 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11850 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11851 					}
11852 				break;
11853 				case WM_CHAR:
11854 					wchar c = cast(wchar) wParam;
11855 					if(wind.handleCharEvent)
11856 						wind.handleCharEvent(cast(dchar) c);
11857 				break;
11858 				  case WM_SETFOCUS:
11859 				  case WM_KILLFOCUS:
11860 					wind._focused = (msg == WM_SETFOCUS);
11861 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
11862 					if(wind.onFocusChange)
11863 						wind.onFocusChange(msg == WM_SETFOCUS);
11864 				  break;
11865 
11866 				case WM_SYSKEYDOWN:
11867 					goto case;
11868 				case WM_SYSKEYUP:
11869 					if(lParam & (1 << 29)) {
11870 						goto case;
11871 					} else {
11872 						// no window has keyboard focus
11873 						goto default;
11874 					}
11875 				case WM_KEYDOWN:
11876 				case WM_KEYUP:
11877 					KeyEvent ev;
11878 					ev.key = cast(Key) wParam;
11879 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
11880 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
11881 
11882 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
11883 
11884 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
11885 						ev.modifierState |= ModifierState.shift;
11886 					//k8: this doesn't work; thanks for nothing, windows
11887 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
11888 						ev.modifierState |= ModifierState.alt;*/
11889 					// this never seems to actually be set
11890 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11891 
11892 					if (wParam == 0x12) {
11893 						altPressed = (msg == WM_SYSKEYDOWN);
11894 					}
11895 
11896 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
11897 						altPressed = false;
11898 					}
11899 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
11900 
11901 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11902 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
11903 						ev.modifierState |= ModifierState.ctrl;
11904 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
11905 						ev.modifierState |= ModifierState.windows;
11906 					if(GetKeyState(Key.NumLock))
11907 						ev.modifierState |= ModifierState.numLock;
11908 					if(GetKeyState(Key.CapsLock))
11909 						ev.modifierState |= ModifierState.capsLock;
11910 
11911 					/+
11912 					// we always want to send the character too, so let's convert it
11913 					ubyte[256] state;
11914 					wchar[16] buffer;
11915 					GetKeyboardState(state.ptr);
11916 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
11917 
11918 					foreach(dchar d; buffer) {
11919 						ev.character = d;
11920 						break;
11921 					}
11922 					+/
11923 
11924 					ev.window = wind;
11925 					if(wind.handleKeyEvent)
11926 						wind.handleKeyEvent(ev);
11927 				break;
11928 				case 0x020a /*WM_MOUSEWHEEL*/:
11929 					// send click
11930 					mouse.type = cast(MouseEventType) 1;
11931 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11932 					mouseEvent(true, LOWORD(wParam));
11933 
11934 					// also send release
11935 					mouse.type = cast(MouseEventType) 2;
11936 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11937 					mouseEvent(true, LOWORD(wParam));
11938 				break;
11939 				case WM_MOUSEMOVE:
11940 					mouse.type = cast(MouseEventType) 0;
11941 					mouseEvent(false, wParam);
11942 				break;
11943 				case WM_LBUTTONDOWN:
11944 				case WM_LBUTTONDBLCLK:
11945 					mouse.type = cast(MouseEventType) 1;
11946 					mouse.button = MouseButton.left;
11947 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
11948 					mouseEvent(false, wParam);
11949 				break;
11950 				case WM_LBUTTONUP:
11951 					mouse.type = cast(MouseEventType) 2;
11952 					mouse.button = MouseButton.left;
11953 					mouseEvent(false, wParam);
11954 				break;
11955 				case WM_RBUTTONDOWN:
11956 				case WM_RBUTTONDBLCLK:
11957 					mouse.type = cast(MouseEventType) 1;
11958 					mouse.button = MouseButton.right;
11959 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
11960 					mouseEvent(false, wParam);
11961 				break;
11962 				case WM_RBUTTONUP:
11963 					mouse.type = cast(MouseEventType) 2;
11964 					mouse.button = MouseButton.right;
11965 					mouseEvent(false, wParam);
11966 				break;
11967 				case WM_MBUTTONDOWN:
11968 				case WM_MBUTTONDBLCLK:
11969 					mouse.type = cast(MouseEventType) 1;
11970 					mouse.button = MouseButton.middle;
11971 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
11972 					mouseEvent(false, wParam);
11973 				break;
11974 				case WM_MBUTTONUP:
11975 					mouse.type = cast(MouseEventType) 2;
11976 					mouse.button = MouseButton.middle;
11977 					mouseEvent(false, wParam);
11978 				break;
11979 				case WM_XBUTTONDOWN:
11980 				case WM_XBUTTONDBLCLK:
11981 					mouse.type = cast(MouseEventType) 1;
11982 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11983 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
11984 					mouseEvent(false, wParam);
11985 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
11986 				case WM_XBUTTONUP:
11987 					mouse.type = cast(MouseEventType) 2;
11988 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11989 					mouseEvent(false, wParam);
11990 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
11991 
11992 				default: return 1;
11993 			}
11994 			return 0;
11995 		}
11996 
11997 		HWND hwnd;
11998 		private int oldWidth;
11999 		private int oldHeight;
12000 		private bool inSizeMove;
12001 
12002 		/++
12003 			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.
12004 
12005 			History:
12006 				Added November 23, 2021
12007 
12008 				Not fully stable, may be moved out of the impl struct.
12009 
12010 				Default value changed to `true` on February 15, 2021
12011 		+/
12012 		bool doLiveResizing = true;
12013 
12014 		package int bmpWidth;
12015 		package int bmpHeight;
12016 
12017 		// the extern(Windows) wndproc should just forward to this
12018 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12019 		try {
12020 			assert(hwnd is this.hwnd);
12021 
12022 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12023 			switch(msg) {
12024 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12025 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12026 					// The main things we can do are select, execute, close, or ignore
12027 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12028 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12029 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12030 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12031 
12032 					// returns the value in the *high order word* of the return value
12033 					// hence the << 16
12034 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12035 				case WM_SETCURSOR:
12036 					if(cast(HWND) wParam !is hwnd)
12037 						return 0; // further processing elsewhere
12038 
12039 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12040 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12041 						return 1;
12042 					} else {
12043 						return DefWindowProc(hwnd, msg, wParam, lParam);
12044 					}
12045 				//break;
12046 
12047 				case WM_CLOSE:
12048 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12049 				break;
12050 				case WM_DESTROY:
12051 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12052 					SimpleWindow.nativeMapping.remove(hwnd);
12053 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12054 
12055 					bool anyImportant = false;
12056 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12057 						if(w.beingOpenKeepsAppOpen) {
12058 							anyImportant = true;
12059 							break;
12060 						}
12061 					if(!anyImportant) {
12062 						PostQuitMessage(0);
12063 					}
12064 				break;
12065 				case 0x02E0 /*WM_DPICHANGED*/:
12066 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12067 
12068 					RECT* prcNewWindow = cast(RECT*)lParam;
12069 					// docs say this is the recommended position and we should honor it
12070 					SetWindowPos(hwnd,
12071 							null,
12072 							prcNewWindow.left,
12073 							prcNewWindow.top,
12074 							prcNewWindow.right - prcNewWindow.left,
12075 							prcNewWindow.bottom - prcNewWindow.top,
12076 							SWP_NOZORDER | SWP_NOACTIVATE);
12077 
12078 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12079 					// im not sure it is completely correct
12080 					// but without it the tabs and such do look weird as things change.
12081 					if(SystemParametersInfoForDpi) {
12082 						LOGFONT lfText;
12083 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12084 						HFONT hFontNew = CreateFontIndirect(&lfText);
12085 						if (hFontNew)
12086 						{
12087 							//DeleteObject(hFontOld);
12088 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12089 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12090 								return TRUE;
12091 							}
12092 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12093 						}
12094 					}
12095 
12096 					if(this.onDpiChanged)
12097 						this.onDpiChanged();
12098 				break;
12099 				case WM_ENTERIDLE:
12100 					// when a menu is up, it stops normal event processing (modal message loop)
12101 					// but this at least gives us a chance to SOMETIMES catch up
12102 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12103 					SimpleWindow.processAllCustomEvents;
12104 					SimpleWindow.processAllCustomEvents;
12105 					SleepEx(0, true);
12106 					break;
12107 				case WM_SIZE:
12108 					if(wParam == 1 /* SIZE_MINIMIZED */)
12109 						break;
12110 					_width = LOWORD(lParam);
12111 					_height = HIWORD(lParam);
12112 
12113 					// I want to avoid tearing in the windows (my code is inefficient
12114 					// so this is a hack around that) so while sizing, we don't trigger,
12115 					// but we do want to trigger on events like mazimize.
12116 					if(!inSizeMove || doLiveResizing)
12117 						goto size_changed;
12118 				break;
12119 				/+
12120 				case WM_SIZING:
12121 					writeln("size");
12122 				break;
12123 				+/
12124 				// I don't like the tearing I get when redrawing on WM_SIZE
12125 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12126 				// so instead it is going to redraw only at the end of a size.
12127 				case 0x0231: /* WM_ENTERSIZEMOVE */
12128 					inSizeMove = true;
12129 				break;
12130 				case 0x0232: /* WM_EXITSIZEMOVE */
12131 					inSizeMove = false;
12132 
12133 					size_changed:
12134 
12135 					// nothing relevant changed, don't bother redrawing
12136 					if(oldWidth == _width && oldHeight == _height) {
12137 						break;
12138 					}
12139 
12140 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12141 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12142 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12143 						// gotta get the double buffer bmp to match the window
12144 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12145 
12146 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12147 						if(resizability != Resizability.automaticallyScaleIfPossible)
12148 						if(_width > bmpWidth || _height > bmpHeight) {
12149 							auto hdc = GetDC(hwnd);
12150 							auto oldBuffer = buffer;
12151 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12152 
12153 							auto hdcBmp = CreateCompatibleDC(hdc);
12154 							auto oldBmp = SelectObject(hdcBmp, buffer);
12155 
12156 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12157 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12158 
12159 							/+
12160 							RECT r;
12161 							r.left = 0;
12162 							r.top = 0;
12163 							r.right = width;
12164 							r.bottom = height;
12165 							auto c = Color.green;
12166 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12167 							FillRect(hdcBmp, &r, brush);
12168 							DeleteObject(brush);
12169 							+/
12170 
12171 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12172 
12173 							bmpWidth = _width;
12174 							bmpHeight = _height;
12175 
12176 							SelectObject(hdcOldBmp, oldOldBmp);
12177 							DeleteDC(hdcOldBmp);
12178 
12179 							SelectObject(hdcBmp, oldBmp);
12180 							DeleteDC(hdcBmp);
12181 
12182 							ReleaseDC(hwnd, hdc);
12183 
12184 							DeleteObject(oldBuffer);
12185 						}
12186 					}
12187 
12188 					updateOpenglViewportIfNeeded(_width, _height);
12189 
12190 					if(resizability != Resizability.automaticallyScaleIfPossible)
12191 					if(windowResized !is null)
12192 						windowResized(_width, _height);
12193 
12194 					if(inSizeMove) {
12195 						SimpleWindow.processAllCustomEvents();
12196 						SimpleWindow.processAllCustomEvents();
12197 					} else {
12198 						// when it is all done, make sure everything is freshly drawn or there might be
12199 						// weird bugs left.
12200 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12201 					}
12202 
12203 					oldWidth = this._width;
12204 					oldHeight = this._height;
12205 				break;
12206 				case WM_ERASEBKGND:
12207 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12208 					if (!this._visibleForTheFirstTimeCalled) {
12209 						this._visibleForTheFirstTimeCalled = true;
12210 						if (this.visibleForTheFirstTime !is null) {
12211 							this.visibleForTheFirstTime();
12212 						}
12213 					}
12214 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12215 					version(without_opengl) {} else {
12216 						if (openglMode == OpenGlOptions.yes) return 1;
12217 					}
12218 					// call windows default handler, so it can paint standard controls
12219 					goto default;
12220 				case WM_CTLCOLORBTN:
12221 				case WM_CTLCOLORSTATIC:
12222 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12223 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12224 					GetSysColorBrush(COLOR_3DFACE);
12225 				//break;
12226 				case WM_SHOWWINDOW:
12227 					this._visible = (wParam != 0);
12228 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12229 						this._visibleForTheFirstTimeCalled = true;
12230 						if (this.visibleForTheFirstTime !is null) {
12231 							this.visibleForTheFirstTime();
12232 						}
12233 					}
12234 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12235 					break;
12236 				case WM_PAINT: {
12237 					if (!this._visibleForTheFirstTimeCalled) {
12238 						this._visibleForTheFirstTimeCalled = true;
12239 						if (this.visibleForTheFirstTime !is null) {
12240 							this.visibleForTheFirstTime();
12241 						}
12242 					}
12243 
12244 					BITMAP bm;
12245 					PAINTSTRUCT ps;
12246 
12247 					HDC hdc = BeginPaint(hwnd, &ps);
12248 
12249 					if(openglMode == OpenGlOptions.no) {
12250 
12251 						HDC hdcMem = CreateCompatibleDC(hdc);
12252 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12253 
12254 						GetObject(buffer, bm.sizeof, &bm);
12255 
12256 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12257 						if(resizability == Resizability.automaticallyScaleIfPossible)
12258 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12259 						else
12260 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12261 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12262 
12263 						SelectObject(hdcMem, hbmOld);
12264 						DeleteDC(hdcMem);
12265 						EndPaint(hwnd, &ps);
12266 					} else {
12267 						EndPaint(hwnd, &ps);
12268 						version(without_opengl) {} else
12269 							redrawOpenGlSceneSoon();
12270 					}
12271 				} break;
12272 				  default:
12273 					return DefWindowProc(hwnd, msg, wParam, lParam);
12274 			}
12275 			 return 0;
12276 
12277 		}
12278 		catch(Throwable t) {
12279 			sdpyPrintDebugString(t.toString);
12280 			return 0;
12281 		}
12282 		}
12283 	}
12284 
12285 	mixin template NativeImageImplementation() {
12286 		HBITMAP handle;
12287 		ubyte* rawData;
12288 
12289 	final:
12290 
12291 		Color getPixel(int x, int y) {
12292 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12293 			// remember, bmps are upside down
12294 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12295 
12296 			Color c;
12297 			if(enableAlpha)
12298 				c.a = rawData[offset + 3];
12299 			else
12300 				c.a = 255;
12301 			c.b = rawData[offset + 0];
12302 			c.g = rawData[offset + 1];
12303 			c.r = rawData[offset + 2];
12304 			c.unPremultiply();
12305 			return c;
12306 		}
12307 
12308 		void setPixel(int x, int y, Color c) {
12309 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12310 			// remember, bmps are upside down
12311 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12312 
12313 			if(enableAlpha)
12314 				c.premultiply();
12315 
12316 			rawData[offset + 0] = c.b;
12317 			rawData[offset + 1] = c.g;
12318 			rawData[offset + 2] = c.r;
12319 			if(enableAlpha)
12320 				rawData[offset + 3] = c.a;
12321 		}
12322 
12323 		void convertToRgbaBytes(ubyte[] where) {
12324 			assert(where.length == this.width * this.height * 4);
12325 
12326 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12327 			int idx = 0;
12328 			int offset = itemsPerLine * (height - 1);
12329 			// remember, bmps are upside down
12330 			for(int y = height - 1; y >= 0; y--) {
12331 				auto offsetStart = offset;
12332 				for(int x = 0; x < width; x++) {
12333 					where[idx + 0] = rawData[offset + 2]; // r
12334 					where[idx + 1] = rawData[offset + 1]; // g
12335 					where[idx + 2] = rawData[offset + 0]; // b
12336 					if(enableAlpha) {
12337 						where[idx + 3] = rawData[offset + 3]; // a
12338 						unPremultiplyRgba(where[idx .. idx + 4]);
12339 						offset++;
12340 					} else
12341 						where[idx + 3] = 255; // a
12342 					idx += 4;
12343 					offset += 3;
12344 				}
12345 
12346 				offset = offsetStart - itemsPerLine;
12347 			}
12348 		}
12349 
12350 		void setFromRgbaBytes(in ubyte[] what) {
12351 			assert(what.length == this.width * this.height * 4);
12352 
12353 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12354 			int idx = 0;
12355 			int offset = itemsPerLine * (height - 1);
12356 			// remember, bmps are upside down
12357 			for(int y = height - 1; y >= 0; y--) {
12358 				auto offsetStart = offset;
12359 				for(int x = 0; x < width; x++) {
12360 					if(enableAlpha) {
12361 						auto a = what[idx + 3];
12362 
12363 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12364 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12365 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12366 						rawData[offset + 3] = a; // a
12367 						//premultiplyBgra(rawData[offset .. offset + 4]);
12368 						offset++;
12369 					} else {
12370 						rawData[offset + 2] = what[idx + 0]; // r
12371 						rawData[offset + 1] = what[idx + 1]; // g
12372 						rawData[offset + 0] = what[idx + 2]; // b
12373 					}
12374 					idx += 4;
12375 					offset += 3;
12376 				}
12377 
12378 				offset = offsetStart - itemsPerLine;
12379 			}
12380 		}
12381 
12382 
12383 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12384 			BITMAPINFO infoheader;
12385 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12386 			infoheader.bmiHeader.biWidth = width;
12387 			infoheader.bmiHeader.biHeight = height;
12388 			infoheader.bmiHeader.biPlanes = 1;
12389 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12390 			infoheader.bmiHeader.biCompression = BI_RGB;
12391 
12392 			handle = CreateDIBSection(
12393 				null,
12394 				&infoheader,
12395 				DIB_RGB_COLORS,
12396 				cast(void**) &rawData,
12397 				null,
12398 				0);
12399 			if(handle is null)
12400 				throw new WindowsApiException("create image failed", GetLastError());
12401 
12402 		}
12403 
12404 		void dispose() {
12405 			DeleteObject(handle);
12406 		}
12407 	}
12408 
12409 	enum KEY_ESCAPE = 27;
12410 }
12411 version(X11) {
12412 	/// This is the default font used. You might change this before doing anything else with
12413 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12414 	/// for cross-platform compatibility.
12415 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12416 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12417 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12418 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12419 
12420 	alias int delegate(XEvent) NativeEventHandler;
12421 	alias Window NativeWindowHandle;
12422 
12423 	enum KEY_ESCAPE = 9;
12424 
12425 	mixin template NativeScreenPainterImplementation() {
12426 		Display* display;
12427 		Drawable d;
12428 		Drawable destiny;
12429 
12430 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12431 		GC gc;
12432 
12433 		__gshared bool fontAttempted;
12434 
12435 		__gshared XFontStruct* defaultfont;
12436 		__gshared XFontSet defaultfontset;
12437 
12438 		XFontStruct* font;
12439 		XFontSet fontset;
12440 
12441 		void create(NativeWindowHandle window) {
12442 			this.display = XDisplayConnection.get();
12443 
12444 			Drawable buffer = None;
12445 			if(auto sw = cast(SimpleWindow) this.window) {
12446 				buffer = sw.impl.buffer;
12447 				this.destiny = cast(Drawable) window;
12448 			} else {
12449 				buffer = cast(Drawable) window;
12450 				this.destiny = None;
12451 			}
12452 
12453 			this.d = cast(Drawable) buffer;
12454 
12455 			auto dgc = DefaultGC(display, DefaultScreen(display));
12456 
12457 			this.gc = XCreateGC(display, d, 0, null);
12458 
12459 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12460 
12461 			ensureDefaultFontLoaded();
12462 
12463 			font = defaultfont;
12464 			fontset = defaultfontset;
12465 
12466 			if(font) {
12467 				XSetFont(display, gc, font.fid);
12468 			}
12469 		}
12470 
12471 		static void ensureDefaultFontLoaded() {
12472 			if(!fontAttempted) {
12473 				auto display = XDisplayConnection.get;
12474 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12475 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12476 				if(font is null) {
12477 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12478 					font = XLoadQueryFont(display, xfontstr.ptr);
12479 				}
12480 
12481 				char** lol;
12482 				int lol2;
12483 				char* lol3;
12484 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12485 
12486 				fontAttempted = true;
12487 
12488 				defaultfont = font;
12489 				defaultfontset = fontset;
12490 			}
12491 		}
12492 
12493 		arsd.color.Rectangle _clipRectangle;
12494 		void setClipRectangle(int x, int y, int width, int height) {
12495 			auto old = _clipRectangle;
12496 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12497 			if(old == _clipRectangle)
12498 				return;
12499 
12500 			if(width == 0 || height == 0) {
12501 				XSetClipMask(display, gc, None);
12502 
12503 				if(xrenderPicturePainter) {
12504 
12505 					XRectangle[1] rects;
12506 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12507 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12508 				}
12509 
12510 				version(with_xft) {
12511 					if(xftFont is null || xftDraw is null)
12512 						return;
12513 					XftDrawSetClip(xftDraw, null);
12514 				}
12515 			} else {
12516 				XRectangle[1] rects;
12517 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12518 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12519 
12520 				if(xrenderPicturePainter)
12521 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12522 
12523 				version(with_xft) {
12524 					if(xftFont is null || xftDraw is null)
12525 						return;
12526 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12527 				}
12528 			}
12529 		}
12530 
12531 		version(with_xft) {
12532 			XftFont* xftFont;
12533 			XftDraw* xftDraw;
12534 
12535 			XftColor xftColor;
12536 
12537 			void updateXftColor() {
12538 				if(xftFont is null)
12539 					return;
12540 
12541 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12542 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12543 
12544 				XftColorAllocValue(
12545 					display,
12546 					DefaultVisual(display, DefaultScreen(display)),
12547 					DefaultColormap(display, 0),
12548 					&colorIn,
12549 					&xftColor
12550 				);
12551 			}
12552 		}
12553 
12554 		private OperatingSystemFont _activeFont;
12555 		void setFont(OperatingSystemFont font) {
12556 			_activeFont = font;
12557 			version(with_xft) {
12558 				if(font && font.isXft && font.xftFont)
12559 					this.xftFont = font.xftFont;
12560 				else
12561 					this.xftFont = null;
12562 
12563 				if(this.xftFont) {
12564 					if(xftDraw is null) {
12565 						xftDraw = XftDrawCreate(
12566 							display,
12567 							d,
12568 							DefaultVisual(display, DefaultScreen(display)),
12569 							DefaultColormap(display, 0)
12570 						);
12571 
12572 						updateXftColor();
12573 					}
12574 
12575 					return;
12576 				}
12577 			}
12578 
12579 			if(font && font.font) {
12580 				this.font = font.font;
12581 				this.fontset = font.fontset;
12582 				XSetFont(display, gc, font.font.fid);
12583 			} else {
12584 				this.font = defaultfont;
12585 				this.fontset = defaultfontset;
12586 			}
12587 
12588 		}
12589 
12590 		private Picture xrenderPicturePainter;
12591 
12592 		bool manualInvalidations;
12593 		void invalidateRect(Rectangle invalidRect) {
12594 			// FIXME if manualInvalidations
12595 		}
12596 
12597 		void dispose() {
12598 			this.rasterOp = RasterOp.normal;
12599 
12600 			if(xrenderPicturePainter) {
12601 				XRenderFreePicture(display, xrenderPicturePainter);
12602 				xrenderPicturePainter = None;
12603 			}
12604 
12605 			// FIXME: this.window.width/height is probably wrong
12606 
12607 			// src x,y     then dest x, y
12608 			if(destiny != None) {
12609 				// FIXME: if manual invalidations we can actually only copy some of the area.
12610 				// if(manualInvalidations)
12611 				XSetClipMask(display, gc, None);
12612 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12613 			}
12614 
12615 			XFreeGC(display, gc);
12616 
12617 			version(with_xft)
12618 			if(xftDraw) {
12619 				XftDrawDestroy(xftDraw);
12620 				xftDraw = null;
12621 			}
12622 
12623 			/+
12624 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12625 			if(font && font !is defaultfont) {
12626 				XFreeFont(display, font);
12627 				font = null;
12628 			}
12629 			if(fontset && fontset !is defaultfontset) {
12630 				XFreeFontSet(display, fontset);
12631 				fontset = null;
12632 			}
12633 			+/
12634 			XFlush(display);
12635 
12636 			if(window.paintingFinishedDg !is null)
12637 				window.paintingFinishedDg()();
12638 		}
12639 
12640 		bool backgroundIsNotTransparent = true;
12641 		bool foregroundIsNotTransparent = true;
12642 
12643 		bool _penInitialized = false;
12644 		Pen _activePen;
12645 
12646 		Color _outlineColor;
12647 		Color _fillColor;
12648 
12649 		@property void pen(Pen p) {
12650 			if(_penInitialized && p == _activePen) {
12651 				return;
12652 			}
12653 			_penInitialized = true;
12654 			_activePen = p;
12655 			_outlineColor = p.color;
12656 
12657 			int style;
12658 
12659 			byte dashLength;
12660 
12661 			final switch(p.style) {
12662 				case Pen.Style.Solid:
12663 					style = 0 /*LineSolid*/;
12664 				break;
12665 				case Pen.Style.Dashed:
12666 					style = 1 /*LineOnOffDash*/;
12667 					dashLength = 4;
12668 				break;
12669 				case Pen.Style.Dotted:
12670 					style = 1 /*LineOnOffDash*/;
12671 					dashLength = 1;
12672 				break;
12673 			}
12674 
12675 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
12676 			if(dashLength)
12677 				XSetDashes(display, gc, 0, &dashLength, 1);
12678 
12679 			if(p.color.a == 0) {
12680 				foregroundIsNotTransparent = false;
12681 				return;
12682 			}
12683 
12684 			foregroundIsNotTransparent = true;
12685 
12686 			XSetForeground(display, gc, colorToX(p.color, display));
12687 
12688 			version(with_xft)
12689 				updateXftColor();
12690 		}
12691 
12692 		RasterOp _currentRasterOp;
12693 		bool _currentRasterOpInitialized = false;
12694 		@property void rasterOp(RasterOp op) {
12695 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12696 				return;
12697 			_currentRasterOp = op;
12698 			_currentRasterOpInitialized = true;
12699 			int mode;
12700 			final switch(op) {
12701 				case RasterOp.normal:
12702 					mode = GXcopy;
12703 				break;
12704 				case RasterOp.xor:
12705 					mode = GXxor;
12706 				break;
12707 			}
12708 			XSetFunction(display, gc, mode);
12709 		}
12710 
12711 
12712 		bool _fillColorInitialized = false;
12713 
12714 		@property void fillColor(Color c) {
12715 			if(_fillColorInitialized && _fillColor == c)
12716 				return; // already good, no need to waste time calling it
12717 			_fillColor = c;
12718 			_fillColorInitialized = true;
12719 			if(c.a == 0) {
12720 				backgroundIsNotTransparent = false;
12721 				return;
12722 			}
12723 
12724 			backgroundIsNotTransparent = true;
12725 
12726 			XSetBackground(display, gc, colorToX(c, display));
12727 
12728 		}
12729 
12730 		void swapColors() {
12731 			auto tmp = _fillColor;
12732 			fillColor = _outlineColor;
12733 			auto newPen = _activePen;
12734 			newPen.color = tmp;
12735 			pen(newPen);
12736 		}
12737 
12738 		uint colorToX(Color c, Display* display) {
12739 			auto visual = DefaultVisual(display, DefaultScreen(display));
12740 			import core.bitop;
12741 			uint color = 0;
12742 			{
12743 			auto startBit = bsf(visual.red_mask);
12744 			auto lastBit = bsr(visual.red_mask);
12745 			auto r = cast(uint) c.r;
12746 			r >>= 7 - (lastBit - startBit);
12747 			r <<= startBit;
12748 			color |= r;
12749 			}
12750 			{
12751 			auto startBit = bsf(visual.green_mask);
12752 			auto lastBit = bsr(visual.green_mask);
12753 			auto g = cast(uint) c.g;
12754 			g >>= 7 - (lastBit - startBit);
12755 			g <<= startBit;
12756 			color |= g;
12757 			}
12758 			{
12759 			auto startBit = bsf(visual.blue_mask);
12760 			auto lastBit = bsr(visual.blue_mask);
12761 			auto b = cast(uint) c.b;
12762 			b >>= 7 - (lastBit - startBit);
12763 			b <<= startBit;
12764 			color |= b;
12765 			}
12766 
12767 
12768 
12769 			return color;
12770 		}
12771 
12772 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12773 			// source x, source y
12774 			if(ix >= i.width) return;
12775 			if(iy >= i.height) return;
12776 			if(ix + w > i.width) w = i.width - ix;
12777 			if(iy + h > i.height) h = i.height - iy;
12778 			if(i.usingXshm)
12779 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12780 			else
12781 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12782 		}
12783 
12784 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12785 			if(s.enableAlpha) {
12786 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12787 				if(this.xrenderPicturePainter == None) {
12788 					XRenderPictureAttributes attrs;
12789 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12790 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12791 
12792 					// need to initialize the clip
12793 					XRectangle[1] rects;
12794 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12795 
12796 					if(_clipRectangle != Rectangle.init)
12797 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12798 				}
12799 
12800 				XRenderComposite(
12801 					display,
12802 					3, // PicOpOver
12803 					s.xrenderPicture,
12804 					None,
12805 					this.xrenderPicturePainter,
12806 					ix,
12807 					iy,
12808 					0,
12809 					0,
12810 					x,
12811 					y,
12812 					w ? w : s.width,
12813 					h ? h : s.height
12814 				);
12815 			} else {
12816 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12817 			}
12818 		}
12819 
12820 		int fontHeight() {
12821 			version(with_xft)
12822 				if(xftFont !is null)
12823 					return xftFont.height;
12824 			if(font)
12825 				return font.max_bounds.ascent + font.max_bounds.descent;
12826 			return 12; // pretty common default...
12827 		}
12828 
12829 		int textWidth(in char[] line) {
12830 			version(with_xft)
12831 			if(xftFont) {
12832 				if(line.length == 0)
12833 					return 0;
12834 				XGlyphInfo extents;
12835 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12836 				return extents.width;
12837 			}
12838 
12839 			if(fontset) {
12840 				if(line.length == 0)
12841 					return 0;
12842 				XRectangle rect;
12843 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12844 
12845 				return rect.width;
12846 			}
12847 
12848 			if(font)
12849 				// FIXME: unicode
12850 				return XTextWidth( font, line.ptr, cast(int) line.length);
12851 			else
12852 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
12853 		}
12854 
12855 		Size textSize(in char[] text) {
12856 			auto maxWidth = 0;
12857 			auto lineHeight = fontHeight;
12858 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
12859 			foreach(line; text.split('\n')) {
12860 				int textWidth = this.textWidth(line);
12861 				if(textWidth > maxWidth)
12862 					maxWidth = textWidth;
12863 				h += lineHeight + 4;
12864 			}
12865 			return Size(maxWidth, h);
12866 		}
12867 
12868 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
12869 			const(char)[] text;
12870 			version(with_xft)
12871 			if(xftFont) {
12872 				text = originalText;
12873 				goto loaded;
12874 			}
12875 
12876 			if(fontset)
12877 				text = originalText;
12878 			else {
12879 				text.reserve(originalText.length);
12880 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
12881 				// then strip the rest so there isn't garbage
12882 				foreach(dchar ch; originalText)
12883 					if(ch < 256)
12884 						text ~= cast(ubyte) ch;
12885 					else
12886 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
12887 			}
12888 			loaded:
12889 			if(text.length == 0)
12890 				return;
12891 
12892 			// FIXME: should we clip it to the bounding box?
12893 			int textHeight = fontHeight;
12894 
12895 			auto lines = text.split('\n');
12896 
12897 			const lineHeight = textHeight;
12898 			textHeight *= lines.length;
12899 
12900 			int cy = y;
12901 
12902 			if(alignment & TextAlignment.VerticalBottom) {
12903 				if(y2 <= 0)
12904 					return;
12905 				auto h = y2 - y;
12906 				if(h > textHeight) {
12907 					cy += h - textHeight;
12908 					cy -= lineHeight / 2;
12909 				}
12910 			} else if(alignment & TextAlignment.VerticalCenter) {
12911 				if(y2 <= 0)
12912 					return;
12913 				auto h = y2 - y;
12914 				if(textHeight < h) {
12915 					cy += (h - textHeight) / 2;
12916 					//cy -= lineHeight / 4;
12917 				}
12918 			}
12919 
12920 			foreach(line; text.split('\n')) {
12921 				int textWidth = this.textWidth(line);
12922 
12923 				int px = x, py = cy;
12924 
12925 				if(alignment & TextAlignment.Center) {
12926 					if(x2 <= 0)
12927 						return;
12928 					auto w = x2 - x;
12929 					if(w > textWidth)
12930 						px += (w - textWidth) / 2;
12931 				} else if(alignment & TextAlignment.Right) {
12932 					if(x2 <= 0)
12933 						return;
12934 					auto pos = x2 - textWidth;
12935 					if(pos > x)
12936 						px = pos;
12937 				}
12938 
12939 				version(with_xft)
12940 				if(xftFont) {
12941 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
12942 
12943 					goto carry_on;
12944 				}
12945 
12946 				if(fontset)
12947 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12948 				else
12949 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12950 				carry_on:
12951 				cy += lineHeight + 4;
12952 			}
12953 		}
12954 
12955 		void drawPixel(int x, int y) {
12956 			XDrawPoint(display, d, gc, x, y);
12957 		}
12958 
12959 		// The basic shapes, outlined
12960 
12961 		void drawLine(int x1, int y1, int x2, int y2) {
12962 			if(foregroundIsNotTransparent)
12963 				XDrawLine(display, d, gc, x1, y1, x2, y2);
12964 		}
12965 
12966 		void drawRectangle(int x, int y, int width, int height) {
12967 			if(backgroundIsNotTransparent) {
12968 				swapColors();
12969 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
12970 				swapColors();
12971 			}
12972 			// 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
12973 			if(foregroundIsNotTransparent)
12974 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
12975 		}
12976 
12977 		/// Arguments are the points of the bounding rectangle
12978 		void drawEllipse(int x1, int y1, int x2, int y2) {
12979 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
12980 		}
12981 
12982 		// NOTE: start and finish are in units of degrees * 64
12983 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
12984 			if(backgroundIsNotTransparent) {
12985 				swapColors();
12986 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
12987 				swapColors();
12988 			}
12989 			if(foregroundIsNotTransparent) {
12990 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
12991 
12992 				// Windows draws the straight lines on the edges too so FIXME sort of
12993 			}
12994 		}
12995 
12996 		void drawPolygon(Point[] vertexes) {
12997 			XPoint[16] pointsBuffer;
12998 			XPoint[] points;
12999 			if(vertexes.length <= pointsBuffer.length)
13000 				points = pointsBuffer[0 .. vertexes.length];
13001 			else
13002 				points.length = vertexes.length;
13003 
13004 			foreach(i, p; vertexes) {
13005 				points[i].x = cast(short) p.x;
13006 				points[i].y = cast(short) p.y;
13007 			}
13008 
13009 			if(backgroundIsNotTransparent) {
13010 				swapColors();
13011 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13012 				swapColors();
13013 			}
13014 			if(foregroundIsNotTransparent) {
13015 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13016 			}
13017 		}
13018 	}
13019 
13020 	/* XRender { */
13021 
13022 	struct XRenderColor {
13023 		ushort red;
13024 		ushort green;
13025 		ushort blue;
13026 		ushort alpha;
13027 	}
13028 
13029 	alias Picture = XID;
13030 	alias PictFormat = XID;
13031 
13032 	struct XGlyphInfo {
13033 		ushort width;
13034 		ushort height;
13035 		short x;
13036 		short y;
13037 		short xOff;
13038 		short yOff;
13039 	}
13040 
13041 struct XRenderDirectFormat {
13042     short   red;
13043     short   redMask;
13044     short   green;
13045     short   greenMask;
13046     short   blue;
13047     short   blueMask;
13048     short   alpha;
13049     short   alphaMask;
13050 }
13051 
13052 struct XRenderPictFormat {
13053     PictFormat		id;
13054     int			type;
13055     int			depth;
13056     XRenderDirectFormat	direct;
13057     Colormap		colormap;
13058 }
13059 
13060 enum PictFormatID	=   (1 << 0);
13061 enum PictFormatType	=   (1 << 1);
13062 enum PictFormatDepth	=   (1 << 2);
13063 enum PictFormatRed	=   (1 << 3);
13064 enum PictFormatRedMask  =(1 << 4);
13065 enum PictFormatGreen	=   (1 << 5);
13066 enum PictFormatGreenMask=(1 << 6);
13067 enum PictFormatBlue	=   (1 << 7);
13068 enum PictFormatBlueMask =(1 << 8);
13069 enum PictFormatAlpha	=   (1 << 9);
13070 enum PictFormatAlphaMask=(1 << 10);
13071 enum PictFormatColormap =(1 << 11);
13072 
13073 struct XRenderPictureAttributes {
13074 	int 		repeat;
13075 	Picture		alpha_map;
13076 	int			alpha_x_origin;
13077 	int			alpha_y_origin;
13078 	int			clip_x_origin;
13079 	int			clip_y_origin;
13080 	Pixmap		clip_mask;
13081 	Bool		graphics_exposures;
13082 	int			subwindow_mode;
13083 	int			poly_edge;
13084 	int			poly_mode;
13085 	Atom		dither;
13086 	Bool		component_alpha;
13087 }
13088 
13089 alias int XFixed;
13090 
13091 struct XPointFixed {
13092     XFixed  x, y;
13093 }
13094 
13095 struct XCircle {
13096     XFixed x;
13097     XFixed y;
13098     XFixed radius;
13099 }
13100 
13101 struct XTransform {
13102     XFixed[3][3]  matrix;
13103 }
13104 
13105 struct XFilters {
13106     int	    nfilter;
13107     char    **filter;
13108     int	    nalias;
13109     short   *alias_;
13110 }
13111 
13112 struct XIndexValue {
13113     c_ulong    pixel;
13114     ushort   red, green, blue, alpha;
13115 }
13116 
13117 struct XAnimCursor {
13118     Cursor	    cursor;
13119     c_ulong   delay;
13120 }
13121 
13122 struct XLinearGradient {
13123     XPointFixed p1;
13124     XPointFixed p2;
13125 }
13126 
13127 struct XRadialGradient {
13128     XCircle inner;
13129     XCircle outer;
13130 }
13131 
13132 struct XConicalGradient {
13133     XPointFixed center;
13134     XFixed angle; /* in degrees */
13135 }
13136 
13137 enum PictStandardARGB32  = 0;
13138 enum PictStandardRGB24   = 1;
13139 enum PictStandardA8	 =  2;
13140 enum PictStandardA4	 =  3;
13141 enum PictStandardA1	 =  4;
13142 enum PictStandardNUM	 =  5;
13143 
13144 interface XRender {
13145 extern(C) @nogc:
13146 
13147 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13148 
13149 	Status XRenderQueryVersion (Display *dpy,
13150 			int     *major_versionp,
13151 			int     *minor_versionp);
13152 
13153 	Status XRenderQueryFormats (Display *dpy);
13154 
13155 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13156 
13157 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13158 
13159 	XRenderPictFormat *
13160 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13161 
13162 	XRenderPictFormat *
13163 		XRenderFindFormat (Display			*dpy,
13164 				c_ulong		mask,
13165 				const XRenderPictFormat	*templ,
13166 				int				count);
13167 	XRenderPictFormat *
13168 		XRenderFindStandardFormat (Display		*dpy,
13169 				int			format);
13170 
13171 	XIndexValue *
13172 		XRenderQueryPictIndexValues(Display			*dpy,
13173 				const XRenderPictFormat	*format,
13174 				int				*num);
13175 
13176 	Picture XRenderCreatePicture(
13177 		Display *dpy,
13178 		Drawable drawable,
13179 		const XRenderPictFormat *format,
13180 		c_ulong valuemask,
13181 		const XRenderPictureAttributes *attributes);
13182 
13183 	void XRenderChangePicture (Display				*dpy,
13184 				Picture				picture,
13185 				c_ulong			valuemask,
13186 				const XRenderPictureAttributes  *attributes);
13187 
13188 	void
13189 		XRenderSetPictureClipRectangles (Display	    *dpy,
13190 				Picture	    picture,
13191 				int		    xOrigin,
13192 				int		    yOrigin,
13193 				const XRectangle *rects,
13194 				int		    n);
13195 
13196 	void
13197 		XRenderSetPictureClipRegion (Display	    *dpy,
13198 				Picture	    picture,
13199 				Region	    r);
13200 
13201 	void
13202 		XRenderSetPictureTransform (Display	    *dpy,
13203 				Picture	    picture,
13204 				XTransform	    *transform);
13205 
13206 	void
13207 		XRenderFreePicture (Display                   *dpy,
13208 				Picture                   picture);
13209 
13210 	void
13211 		XRenderComposite (Display   *dpy,
13212 				int	    op,
13213 				Picture   src,
13214 				Picture   mask,
13215 				Picture   dst,
13216 				int	    src_x,
13217 				int	    src_y,
13218 				int	    mask_x,
13219 				int	    mask_y,
13220 				int	    dst_x,
13221 				int	    dst_y,
13222 				uint	width,
13223 				uint	height);
13224 
13225 
13226 	Picture XRenderCreateSolidFill (Display *dpy,
13227 			const XRenderColor *color);
13228 
13229 	Picture XRenderCreateLinearGradient (Display *dpy,
13230 			const XLinearGradient *gradient,
13231 			const XFixed *stops,
13232 			const XRenderColor *colors,
13233 			int nstops);
13234 
13235 	Picture XRenderCreateRadialGradient (Display *dpy,
13236 			const XRadialGradient *gradient,
13237 			const XFixed *stops,
13238 			const XRenderColor *colors,
13239 			int nstops);
13240 
13241 	Picture XRenderCreateConicalGradient (Display *dpy,
13242 			const XConicalGradient *gradient,
13243 			const XFixed *stops,
13244 			const XRenderColor *colors,
13245 			int nstops);
13246 
13247 
13248 
13249 	Cursor
13250 		XRenderCreateCursor (Display	    *dpy,
13251 				Picture	    source,
13252 				uint   x,
13253 				uint   y);
13254 
13255 	XFilters *
13256 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13257 
13258 	void
13259 		XRenderSetPictureFilter (Display    *dpy,
13260 				Picture    picture,
13261 				const char *filter,
13262 				XFixed	    *params,
13263 				int	    nparams);
13264 
13265 	Cursor
13266 		XRenderCreateAnimCursor (Display	*dpy,
13267 				int		ncursor,
13268 				XAnimCursor	*cursors);
13269 }
13270 
13271 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13272 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13273 
13274 	/* XRender } */
13275 
13276 	/* Xrandr { */
13277 
13278 struct XRRMonitorInfo {
13279     Atom name;
13280     Bool primary;
13281     Bool automatic;
13282     int noutput;
13283     int x;
13284     int y;
13285     int width;
13286     int height;
13287     int mwidth;
13288     int mheight;
13289     /*RROutput*/ void *outputs;
13290 }
13291 
13292 struct XRRScreenChangeNotifyEvent {
13293     int type;                   /* event base */
13294     c_ulong serial;       /* # of last request processed by server */
13295     Bool send_event;            /* true if this came from a SendEvent request */
13296     Display *display;           /* Display the event was read from */
13297     Window window;              /* window which selected for this event */
13298     Window root;                /* Root window for changed screen */
13299     Time timestamp;             /* when the screen change occurred */
13300     Time config_timestamp;      /* when the last configuration change */
13301     ushort/*SizeID*/ size_index;
13302     ushort/*SubpixelOrder*/ subpixel_order;
13303     ushort/*Rotation*/ rotation;
13304     int width;
13305     int height;
13306     int mwidth;
13307     int mheight;
13308 }
13309 
13310 enum RRScreenChangeNotify = 0;
13311 
13312 enum RRScreenChangeNotifyMask = 1;
13313 
13314 __gshared int xrrEventBase = -1;
13315 
13316 
13317 interface XRandr {
13318 extern(C) @nogc:
13319 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13320 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13321 
13322 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13323 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13324 
13325 	void XRRSelectInput(Display *dpy, Window window, int mask);
13326 }
13327 
13328 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13329 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13330 	/* Xrandr } */
13331 
13332 	/* Xft { */
13333 
13334 	// actually freetype
13335 	alias void FT_Face;
13336 
13337 	// actually fontconfig
13338 	private alias FcBool = int;
13339 	alias void FcCharSet;
13340 	alias void FcPattern;
13341 	alias void FcResult;
13342 	enum FcEndian { FcEndianBig, FcEndianLittle }
13343 	struct FcFontSet {
13344 		int nfont;
13345 		int sfont;
13346 		FcPattern** fonts;
13347 	}
13348 
13349 	// actually XRegion
13350 	struct BOX {
13351 		short x1, x2, y1, y2;
13352 	}
13353 	struct _XRegion {
13354 		c_long size;
13355 		c_long numRects;
13356 		BOX* rects;
13357 		BOX extents;
13358 	}
13359 
13360 	alias Region = _XRegion*;
13361 
13362 	// ok actually Xft
13363 
13364 	struct XftFontInfo;
13365 
13366 	struct XftFont {
13367 		int         ascent;
13368 		int         descent;
13369 		int         height;
13370 		int         max_advance_width;
13371 		FcCharSet*  charset;
13372 		FcPattern*  pattern;
13373 	}
13374 
13375 	struct XftDraw;
13376 
13377 	struct XftColor {
13378 		c_ulong pixel;
13379 		XRenderColor color;
13380 	}
13381 
13382 	struct XftCharSpec {
13383 		dchar           ucs4;
13384 		short           x;
13385 		short           y;
13386 	}
13387 
13388 	struct XftCharFontSpec {
13389 		XftFont         *font;
13390 		dchar           ucs4;
13391 		short           x;
13392 		short           y;
13393 	}
13394 
13395 	struct XftGlyphSpec {
13396 		uint            glyph;
13397 		short           x;
13398 		short           y;
13399 	}
13400 
13401 	struct XftGlyphFontSpec {
13402 		XftFont         *font;
13403 		uint            glyph;
13404 		short           x;
13405 		short           y;
13406 	}
13407 
13408 	interface Xft {
13409 	extern(C) @nogc pure:
13410 
13411 	Bool XftColorAllocName (Display  *dpy,
13412 				const Visual   *visual,
13413 				Colormap cmap,
13414 				const char     *name,
13415 				XftColor *result);
13416 
13417 	Bool XftColorAllocValue (Display         *dpy,
13418 				Visual          *visual,
13419 				Colormap        cmap,
13420 				const XRenderColor    *color,
13421 				XftColor        *result);
13422 
13423 	void XftColorFree (Display   *dpy,
13424 				Visual    *visual,
13425 				Colormap  cmap,
13426 				XftColor  *color);
13427 
13428 	Bool XftDefaultHasRender (Display *dpy);
13429 
13430 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13431 
13432 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13433 
13434 	XftDraw * XftDrawCreate (Display   *dpy,
13435 		       Drawable  drawable,
13436 		       Visual    *visual,
13437 		       Colormap  colormap);
13438 
13439 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13440 			     Pixmap   bitmap);
13441 
13442 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13443 			    Pixmap  pixmap,
13444 			    int     depth);
13445 
13446 	void XftDrawChange (XftDraw  *draw,
13447 		       Drawable drawable);
13448 
13449 	Display * XftDrawDisplay (XftDraw *draw);
13450 
13451 	Drawable XftDrawDrawable (XftDraw *draw);
13452 
13453 	Colormap XftDrawColormap (XftDraw *draw);
13454 
13455 	Visual * XftDrawVisual (XftDraw *draw);
13456 
13457 	void XftDrawDestroy (XftDraw *draw);
13458 
13459 	Picture XftDrawPicture (XftDraw *draw);
13460 
13461 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13462 
13463 	void XftDrawGlyphs (XftDraw          *draw,
13464 				const XftColor *color,
13465 				XftFont          *pub,
13466 				int              x,
13467 				int              y,
13468 				const uint  *glyphs,
13469 				int              nglyphs);
13470 
13471 	void XftDrawString8 (XftDraw             *draw,
13472 				const XftColor    *color,
13473 				XftFont             *pub,
13474 				int                 x,
13475 				int                 y,
13476 				const char     *string,
13477 				int                 len);
13478 
13479 	void XftDrawString16 (XftDraw            *draw,
13480 				const XftColor   *color,
13481 				XftFont            *pub,
13482 				int                x,
13483 				int                y,
13484 				const wchar   *string,
13485 				int                len);
13486 
13487 	void XftDrawString32 (XftDraw            *draw,
13488 				const XftColor   *color,
13489 				XftFont            *pub,
13490 				int                x,
13491 				int                y,
13492 				const dchar   *string,
13493 				int                len);
13494 
13495 	void XftDrawStringUtf8 (XftDraw          *draw,
13496 				const XftColor *color,
13497 				XftFont          *pub,
13498 				int              x,
13499 				int              y,
13500 				const char  *string,
13501 				int              len);
13502 	void XftDrawStringUtf16 (XftDraw             *draw,
13503 				const XftColor    *color,
13504 				XftFont             *pub,
13505 				int                 x,
13506 				int                 y,
13507 				const char     *string,
13508 				FcEndian            endian,
13509 				int                 len);
13510 
13511 	void XftDrawCharSpec (XftDraw                *draw,
13512 				const XftColor       *color,
13513 				XftFont                *pub,
13514 				const XftCharSpec    *chars,
13515 				int                    len);
13516 
13517 	void XftDrawCharFontSpec (XftDraw                    *draw,
13518 				const XftColor           *color,
13519 				const XftCharFontSpec    *chars,
13520 				int                        len);
13521 
13522 	void XftDrawGlyphSpec (XftDraw               *draw,
13523 				const XftColor      *color,
13524 				XftFont               *pub,
13525 				const XftGlyphSpec  *glyphs,
13526 				int                   len);
13527 
13528 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13529 				const XftColor          *color,
13530 				const XftGlyphFontSpec  *glyphs,
13531 				int                       len);
13532 
13533 	void XftDrawRect (XftDraw            *draw,
13534 				const XftColor   *color,
13535 				int                x,
13536 				int                y,
13537 				uint       width,
13538 				uint       height);
13539 
13540 	Bool XftDrawSetClip (XftDraw     *draw,
13541 				Region      r);
13542 
13543 
13544 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13545 				int                   xOrigin,
13546 				int                   yOrigin,
13547 				const XRectangle    *rects,
13548 				int                   n);
13549 
13550 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13551 				int        mode);
13552 
13553 	void XftGlyphExtents (Display            *dpy,
13554 				XftFont            *pub,
13555 				const uint    *glyphs,
13556 				int                nglyphs,
13557 				XGlyphInfo         *extents);
13558 
13559 	void XftTextExtents8 (Display            *dpy,
13560 				XftFont            *pub,
13561 				const char    *string,
13562 				int                len,
13563 				XGlyphInfo         *extents);
13564 
13565 	void XftTextExtents16 (Display           *dpy,
13566 				XftFont           *pub,
13567 				const wchar  *string,
13568 				int               len,
13569 				XGlyphInfo        *extents);
13570 
13571 	void XftTextExtents32 (Display           *dpy,
13572 				XftFont           *pub,
13573 				const dchar  *string,
13574 				int               len,
13575 				XGlyphInfo        *extents);
13576 
13577 	void XftTextExtentsUtf8 (Display         *dpy,
13578 				XftFont         *pub,
13579 				const char *string,
13580 				int             len,
13581 				XGlyphInfo      *extents);
13582 
13583 	void XftTextExtentsUtf16 (Display            *dpy,
13584 				XftFont            *pub,
13585 				const char    *string,
13586 				FcEndian           endian,
13587 				int                len,
13588 				XGlyphInfo         *extents);
13589 
13590 	FcPattern * XftFontMatch (Display           *dpy,
13591 				int               screen,
13592 				const FcPattern *pattern,
13593 				FcResult          *result);
13594 
13595 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13596 
13597 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13598 
13599 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13600 
13601 	FT_Face XftLockFace (XftFont *pub);
13602 
13603 	void XftUnlockFace (XftFont *pub);
13604 
13605 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13606 
13607 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13608 
13609 	dchar XftFontInfoHash (const XftFontInfo *fi);
13610 
13611 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13612 
13613 	XftFont * XftFontOpenInfo (Display        *dpy,
13614 				FcPattern      *pattern,
13615 				XftFontInfo    *fi);
13616 
13617 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13618 
13619 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13620 
13621 	void XftFontClose (Display *dpy, XftFont *pub);
13622 
13623 	FcBool XftInitFtLibrary();
13624 	void XftFontLoadGlyphs (Display          *dpy,
13625 				XftFont          *pub,
13626 				FcBool           need_bitmaps,
13627 				const uint  *glyphs,
13628 				int              nglyph);
13629 
13630 	void XftFontUnloadGlyphs (Display            *dpy,
13631 				XftFont            *pub,
13632 				const uint    *glyphs,
13633 				int                nglyph);
13634 
13635 	FcBool XftFontCheckGlyph (Display  *dpy,
13636 				XftFont  *pub,
13637 				FcBool   need_bitmaps,
13638 				uint  glyph,
13639 				uint  *missing,
13640 				int      *nmissing);
13641 
13642 	FcBool XftCharExists (Display      *dpy,
13643 				XftFont      *pub,
13644 				dchar    ucs4);
13645 
13646 	uint XftCharIndex (Display       *dpy,
13647 				XftFont       *pub,
13648 				dchar      ucs4);
13649 	FcBool XftInit (const char *config);
13650 
13651 	int XftGetVersion ();
13652 
13653 	FcFontSet * XftListFonts (Display   *dpy,
13654 				int       screen,
13655 				...);
13656 
13657 	FcPattern *XftNameParse (const char *name);
13658 
13659 	void XftGlyphRender (Display         *dpy,
13660 				int             op,
13661 				Picture         src,
13662 				XftFont         *pub,
13663 				Picture         dst,
13664 				int             srcx,
13665 				int             srcy,
13666 				int             x,
13667 				int             y,
13668 				const uint *glyphs,
13669 				int             nglyphs);
13670 
13671 	void XftGlyphSpecRender (Display                 *dpy,
13672 				int                     op,
13673 				Picture                 src,
13674 				XftFont                 *pub,
13675 				Picture                 dst,
13676 				int                     srcx,
13677 				int                     srcy,
13678 				const XftGlyphSpec    *glyphs,
13679 				int                     nglyphs);
13680 
13681 	void XftCharSpecRender (Display              *dpy,
13682 				int                  op,
13683 				Picture              src,
13684 				XftFont              *pub,
13685 				Picture              dst,
13686 				int                  srcx,
13687 				int                  srcy,
13688 				const XftCharSpec  *chars,
13689 				int                  len);
13690 	void XftGlyphFontSpecRender (Display                     *dpy,
13691 				int                         op,
13692 				Picture                     src,
13693 				Picture                     dst,
13694 				int                         srcx,
13695 				int                         srcy,
13696 				const XftGlyphFontSpec    *glyphs,
13697 				int                         nglyphs);
13698 
13699 	void XftCharFontSpecRender (Display                  *dpy,
13700 				int                      op,
13701 				Picture                  src,
13702 				Picture                  dst,
13703 				int                      srcx,
13704 				int                      srcy,
13705 				const XftCharFontSpec  *chars,
13706 				int                      len);
13707 
13708 	void XftTextRender8 (Display         *dpy,
13709 				int             op,
13710 				Picture         src,
13711 				XftFont         *pub,
13712 				Picture         dst,
13713 				int             srcx,
13714 				int             srcy,
13715 				int             x,
13716 				int             y,
13717 				const char *string,
13718 				int             len);
13719 	void XftTextRender16 (Display            *dpy,
13720 				int                op,
13721 				Picture            src,
13722 				XftFont            *pub,
13723 				Picture            dst,
13724 				int                srcx,
13725 				int                srcy,
13726 				int                x,
13727 				int                y,
13728 				const wchar   *string,
13729 				int                len);
13730 
13731 	void XftTextRender16BE (Display          *dpy,
13732 				int              op,
13733 				Picture          src,
13734 				XftFont          *pub,
13735 				Picture          dst,
13736 				int              srcx,
13737 				int              srcy,
13738 				int              x,
13739 				int              y,
13740 				const char  *string,
13741 				int              len);
13742 
13743 	void XftTextRender16LE (Display          *dpy,
13744 				int              op,
13745 				Picture          src,
13746 				XftFont          *pub,
13747 				Picture          dst,
13748 				int              srcx,
13749 				int              srcy,
13750 				int              x,
13751 				int              y,
13752 				const char  *string,
13753 				int              len);
13754 
13755 	void XftTextRender32 (Display            *dpy,
13756 				int                op,
13757 				Picture            src,
13758 				XftFont            *pub,
13759 				Picture            dst,
13760 				int                srcx,
13761 				int                srcy,
13762 				int                x,
13763 				int                y,
13764 				const dchar   *string,
13765 				int                len);
13766 
13767 	void XftTextRender32BE (Display          *dpy,
13768 				int              op,
13769 				Picture          src,
13770 				XftFont          *pub,
13771 				Picture          dst,
13772 				int              srcx,
13773 				int              srcy,
13774 				int              x,
13775 				int              y,
13776 				const char  *string,
13777 				int              len);
13778 
13779 	void XftTextRender32LE (Display          *dpy,
13780 				int              op,
13781 				Picture          src,
13782 				XftFont          *pub,
13783 				Picture          dst,
13784 				int              srcx,
13785 				int              srcy,
13786 				int              x,
13787 				int              y,
13788 				const char  *string,
13789 				int              len);
13790 
13791 	void XftTextRenderUtf8 (Display          *dpy,
13792 				int              op,
13793 				Picture          src,
13794 				XftFont          *pub,
13795 				Picture          dst,
13796 				int              srcx,
13797 				int              srcy,
13798 				int              x,
13799 				int              y,
13800 				const char  *string,
13801 				int              len);
13802 
13803 	void XftTextRenderUtf16 (Display         *dpy,
13804 				int             op,
13805 				Picture         src,
13806 				XftFont         *pub,
13807 				Picture         dst,
13808 				int             srcx,
13809 				int             srcy,
13810 				int             x,
13811 				int             y,
13812 				const char *string,
13813 				FcEndian        endian,
13814 				int             len);
13815 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13816 
13817 	}
13818 
13819 	interface FontConfig {
13820 	extern(C) @nogc pure:
13821 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13822 		void FcFontSetDestroy(FcFontSet*);
13823 		char* FcNameUnparse(const FcPattern *);
13824 	}
13825 
13826 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13827 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13828 
13829 
13830 	/* Xft } */
13831 
13832 	class XDisconnectException : Exception {
13833 		bool userRequested;
13834 		this(bool userRequested = true) {
13835 			this.userRequested = userRequested;
13836 			super("X disconnected");
13837 		}
13838 	}
13839 
13840 	/++
13841 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
13842 
13843 		Please note that it returns
13844 	+/
13845 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
13846 
13847 		static XErrorEvent[] errorBuffer;
13848 
13849 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
13850 			errorBuffer ~= *evt;
13851 			return 0;
13852 		}
13853 
13854 		auto savedErrorHandler = XSetErrorHandler(&handler);
13855 
13856 		try {
13857 			dg();
13858 		} finally {
13859 			XSync(XDisplayConnection.get, 0/*False*/);
13860 			XSetErrorHandler(savedErrorHandler);
13861 		}
13862 
13863 		auto bfr = errorBuffer;
13864 		errorBuffer = null;
13865 
13866 		return bfr;
13867 	}
13868 
13869 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
13870 	class XDisplayConnection {
13871 		private __gshared Display* display;
13872 		private __gshared XIM xim;
13873 		private __gshared char* displayName;
13874 
13875 		private __gshared int connectionSequence_;
13876 		private __gshared bool isLocal_;
13877 
13878 		/// use this for lazy caching when reconnection
13879 		static int connectionSequenceNumber() { return connectionSequence_; }
13880 
13881 		/++
13882 			Guesses if the connection appears to be local.
13883 
13884 			History:
13885 				Added June 3, 2021
13886 		+/
13887 		static @property bool isLocal() nothrow @trusted @nogc {
13888 			return isLocal_;
13889 		}
13890 
13891 		/// Attempts recreation of state, may require application assistance
13892 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
13893 		/// then call this, and if successful, reenter the loop.
13894 		static void discardAndRecreate(string newDisplayString = null) {
13895 			if(insideXEventLoop)
13896 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
13897 
13898 			// 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
13899 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
13900 
13901 			foreach(handle; chnenhm) {
13902 				handle.discardConnectionState();
13903 			}
13904 
13905 			discardState();
13906 
13907 			if(newDisplayString !is null)
13908 				setDisplayName(newDisplayString);
13909 
13910 			auto display = get();
13911 
13912 			foreach(handle; chnenhm) {
13913 				handle.recreateAfterDisconnect();
13914 			}
13915 		}
13916 
13917 		private __gshared EventMask rootEventMask;
13918 
13919 		/++
13920 			Requests the specified input from the root window on the connection, in addition to any other request.
13921 
13922 
13923 			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.
13924 
13925 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
13926 		+/
13927 		static void addRootInput(EventMask mask) {
13928 			auto old = rootEventMask;
13929 			rootEventMask |= mask;
13930 			get(); // to ensure display connected
13931 			if(display !is null && rootEventMask != old)
13932 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
13933 		}
13934 
13935 		static void discardState() {
13936 			freeImages();
13937 
13938 			foreach(atomPtr; interredAtoms)
13939 				*atomPtr = 0;
13940 			interredAtoms = null;
13941 			interredAtoms.assumeSafeAppend();
13942 
13943 			ScreenPainterImplementation.fontAttempted = false;
13944 			ScreenPainterImplementation.defaultfont = null;
13945 			ScreenPainterImplementation.defaultfontset = null;
13946 
13947 			Image.impl.xshmQueryCompleted = false;
13948 			Image.impl._xshmAvailable = false;
13949 
13950 			SimpleWindow.nativeMapping = null;
13951 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
13952 			// GlobalHotkeyManager
13953 
13954 			display = null;
13955 			xim = null;
13956 		}
13957 
13958 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
13959 		private static void createXIM () {
13960 			import core.stdc.locale : setlocale, LC_ALL;
13961 			import core.stdc.stdio : stderr, fprintf;
13962 			import core.stdc.stdlib : free;
13963 			import core.stdc.string : strdup;
13964 
13965 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
13966 
13967 			auto olocale = strdup(setlocale(LC_ALL, null));
13968 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
13969 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
13970 
13971 			//fprintf(stderr, "opening IM...\n");
13972 			foreach (string s; mtry) {
13973 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
13974 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
13975 			}
13976 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
13977 		}
13978 
13979 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
13980 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
13981 		static struct ImgList {
13982 			size_t img; // class; hide it from GC
13983 			ImgList* next;
13984 		}
13985 
13986 		static __gshared ImgList* imglist = null;
13987 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
13988 
13989 		static void registerImage (Image img) {
13990 			if (!imglistLocked && img !is null) {
13991 				import core.stdc.stdlib : malloc;
13992 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
13993 				assert(it !is null); // do proper checks
13994 				it.img = cast(size_t)cast(void*)img;
13995 				it.next = imglist;
13996 				imglist = it;
13997 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
13998 			}
13999 		}
14000 
14001 		static void unregisterImage (Image img) {
14002 			if (!imglistLocked && img !is null) {
14003 				import core.stdc.stdlib : free;
14004 				ImgList* prev = null;
14005 				ImgList* cur = imglist;
14006 				while (cur !is null) {
14007 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14008 					prev = cur;
14009 					cur = cur.next;
14010 				}
14011 				if (cur !is null) {
14012 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14013 					free(cur);
14014 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14015 				} else {
14016 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14017 				}
14018 			}
14019 		}
14020 
14021 		static void freeImages () { // needed for discardAndRecreate
14022 			imglistLocked = true;
14023 			scope(exit) imglistLocked = false;
14024 			ImgList* cur = imglist;
14025 			ImgList* next = null;
14026 			while (cur !is null) {
14027 				import core.stdc.stdlib : free;
14028 				next = cur.next;
14029 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14030 				(cast(Image)cast(void*)cur.img).dispose();
14031 				free(cur);
14032 				cur = next;
14033 			}
14034 			imglist = null;
14035 		}
14036 
14037 		/// can be used to override normal handling of display name
14038 		/// from environment and/or command line
14039 		static setDisplayName(string newDisplayName) {
14040 			displayName = cast(char*) (newDisplayName ~ '\0');
14041 		}
14042 
14043 		/// resets to the default display string
14044 		static resetDisplayName() {
14045 			displayName = null;
14046 		}
14047 
14048 		///
14049 		static Display* get() {
14050 			if(display is null) {
14051 				if(!librariesSuccessfullyLoaded)
14052 					throw new Exception("Unable to load X11 client libraries");
14053 				display = XOpenDisplay(displayName);
14054 
14055 				isLocal_ = false;
14056 
14057 				connectionSequence_++;
14058 				if(display is null)
14059 					throw new Exception("Unable to open X display");
14060 
14061 				auto str = display.display_name;
14062 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14063 				// and otherwise it probably isn't
14064 				if(str is null || (str[0] != ':' && str[0] != '/'))
14065 					isLocal_ = false;
14066 				else
14067 					isLocal_ = true;
14068 
14069 				debug(sdpy_x_errors) {
14070 					XSetErrorHandler(&adrlogger);
14071 					XSynchronize(display, true);
14072 
14073 					extern(C) int wtf() {
14074 						if(errorHappened) {
14075 							asm { int 3; }
14076 							errorHappened = false;
14077 						}
14078 						return 0;
14079 					}
14080 					XSetAfterFunction(display, &wtf);
14081 				}
14082 
14083 
14084 				XSetIOErrorHandler(&x11ioerrCB);
14085 				Bool sup;
14086 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14087 				createXIM();
14088 				version(with_eventloop) {
14089 					import arsd.eventloop;
14090 					addFileEventListeners(display.fd, &eventListener, null, null);
14091 				}
14092 			}
14093 
14094 			return display;
14095 		}
14096 
14097 		extern(C)
14098 		static int x11ioerrCB(Display* dpy) {
14099 			throw new XDisconnectException(false);
14100 		}
14101 
14102 		version(with_eventloop) {
14103 			import arsd.eventloop;
14104 			static void eventListener(OsFileHandle fd) {
14105 				//this.mtLock();
14106 				//scope(exit) this.mtUnlock();
14107 				while(XPending(display))
14108 					doXNextEvent(display);
14109 			}
14110 		}
14111 
14112 		// close connection on program exit -- we need this to properly free all images
14113 		static ~this () {
14114 			// the gui thread must clean up after itself or else Xlib might deadlock
14115 			// using this flag on any thread destruction is the easiest way i know of
14116 			// (shared static this is run by the LAST thread to exit, which may not be
14117 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14118 			if(thisIsGuiThread)
14119 				close();
14120 		}
14121 
14122 		///
14123 		static void close() {
14124 			if(display is null)
14125 				return;
14126 
14127 			version(with_eventloop) {
14128 				import arsd.eventloop;
14129 				removeFileEventListeners(display.fd);
14130 			}
14131 
14132 			// now remove all registered images to prevent shared memory leaks
14133 			freeImages();
14134 
14135 			// tbh I don't know why it is doing this but like if this happens to run
14136 			// from the other thread there's frequent hanging inside here.
14137 			if(thisIsGuiThread)
14138 				XCloseDisplay(display);
14139 			display = null;
14140 		}
14141 	}
14142 
14143 	mixin template NativeImageImplementation() {
14144 		XImage* handle;
14145 		ubyte* rawData;
14146 
14147 		XShmSegmentInfo shminfo;
14148 
14149 		__gshared bool xshmQueryCompleted;
14150 		__gshared bool _xshmAvailable;
14151 		public static @property bool xshmAvailable() {
14152 			if(!xshmQueryCompleted) {
14153 				int i1, i2, i3;
14154 				xshmQueryCompleted = true;
14155 
14156 				if(!XDisplayConnection.isLocal)
14157 					_xshmAvailable = false;
14158 				else
14159 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14160 			}
14161 			return _xshmAvailable;
14162 		}
14163 
14164 		bool usingXshm;
14165 	final:
14166 
14167 		private __gshared bool xshmfailed;
14168 
14169 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14170 			auto display = XDisplayConnection.get();
14171 			assert(display !is null);
14172 			auto screen = DefaultScreen(display);
14173 
14174 			// it will only use shared memory for somewhat largish images,
14175 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14176 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14177 
14178 
14179 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14180 				// the actual use still fails. For example, if the program is in a container and permission denied
14181 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14182 				//
14183 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14184 
14185 
14186 				// synchronize so preexisting buffers are clear
14187 				XSync(display, false);
14188 				xshmfailed = false;
14189 
14190 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14191 
14192 
14193 				usingXshm = true;
14194 				handle = XShmCreateImage(
14195 					display,
14196 					DefaultVisual(display, screen),
14197 					enableAlpha ? 32: 24,
14198 					ImageFormat.ZPixmap,
14199 					null,
14200 					&shminfo,
14201 					width, height);
14202 				if(handle is null)
14203 					goto abortXshm1;
14204 
14205 				if(handle.bytes_per_line != 4 * width)
14206 					goto abortXshm2;
14207 
14208 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14209 				if(shminfo.shmid < 0)
14210 					goto abortXshm3;
14211 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14212 				if(rawData == cast(ubyte*) -1)
14213 					goto abortXshm4;
14214 				shminfo.readOnly = 0;
14215 				XShmAttach(display, &shminfo);
14216 
14217 				// and now to the final error check to ensure it actually worked.
14218 				XSync(display, false);
14219 				if(xshmfailed)
14220 					goto abortXshm5;
14221 
14222 				XSetErrorHandler(oldErrorHandler);
14223 
14224 				XDisplayConnection.registerImage(this);
14225 				// if I don't flush here there's a chance the dtor will run before the
14226 				// ctor and lead to a bad value X error. While this hurts the efficiency
14227 				// it is local anyway so prolly better to keep it simple
14228 				XFlush(display);
14229 
14230 				return;
14231 
14232 				abortXshm5:
14233 					shmdt(shminfo.shmaddr);
14234 					rawData = null;
14235 
14236 				abortXshm4:
14237 					shmctl(shminfo.shmid, IPC_RMID, null);
14238 
14239 				abortXshm3:
14240 					// nothing needed, the shmget failed so there's nothing to free
14241 
14242 				abortXshm2:
14243 					XDestroyImage(handle);
14244 					handle = null;
14245 
14246 				abortXshm1:
14247 					XSetErrorHandler(oldErrorHandler);
14248 					usingXshm = false;
14249 					handle = null;
14250 
14251 					shminfo = typeof(shminfo).init;
14252 
14253 					_xshmAvailable = false; // don't try again in the future
14254 
14255 					// writeln("fallingback");
14256 
14257 					goto fallback;
14258 
14259 			} else {
14260 				fallback:
14261 
14262 				if (forcexshm) throw new Exception("can't create XShm Image");
14263 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14264 				import core.stdc.stdlib : malloc;
14265 				rawData = cast(ubyte*) malloc(width * height * 4);
14266 
14267 				handle = XCreateImage(
14268 					display,
14269 					DefaultVisual(display, screen),
14270 					enableAlpha ? 32 : 24, // bpp
14271 					ImageFormat.ZPixmap,
14272 					0, // offset
14273 					rawData,
14274 					width, height,
14275 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14276 			}
14277 		}
14278 
14279 		void dispose() {
14280 			// note: this calls free(rawData) for us
14281 			if(handle) {
14282 				if (usingXshm) {
14283 					XDisplayConnection.unregisterImage(this);
14284 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14285 				}
14286 				XDestroyImage(handle);
14287 				if(usingXshm) {
14288 					shmdt(shminfo.shmaddr);
14289 					shmctl(shminfo.shmid, IPC_RMID, null);
14290 				}
14291 				handle = null;
14292 			}
14293 		}
14294 
14295 		Color getPixel(int x, int y) {
14296 			auto offset = (y * width + x) * 4;
14297 			Color c;
14298 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14299 			c.b = rawData[offset + 0];
14300 			c.g = rawData[offset + 1];
14301 			c.r = rawData[offset + 2];
14302 			if(enableAlpha)
14303 				c.unPremultiply;
14304 			return c;
14305 		}
14306 
14307 		void setPixel(int x, int y, Color c) {
14308 			if(enableAlpha)
14309 				c.premultiply();
14310 			auto offset = (y * width + x) * 4;
14311 			rawData[offset + 0] = c.b;
14312 			rawData[offset + 1] = c.g;
14313 			rawData[offset + 2] = c.r;
14314 			if(enableAlpha)
14315 				rawData[offset + 3] = c.a;
14316 		}
14317 
14318 		void convertToRgbaBytes(ubyte[] where) {
14319 			assert(where.length == this.width * this.height * 4);
14320 
14321 			// if rawData had a length....
14322 			//assert(rawData.length == where.length);
14323 			for(int idx = 0; idx < where.length; idx += 4) {
14324 				where[idx + 0] = rawData[idx + 2]; // r
14325 				where[idx + 1] = rawData[idx + 1]; // g
14326 				where[idx + 2] = rawData[idx + 0]; // b
14327 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14328 
14329 				if(enableAlpha)
14330 					unPremultiplyRgba(where[idx .. idx + 4]);
14331 			}
14332 		}
14333 
14334 		void setFromRgbaBytes(in ubyte[] where) {
14335 			assert(where.length == this.width * this.height * 4);
14336 
14337 			// if rawData had a length....
14338 			//assert(rawData.length == where.length);
14339 			for(int idx = 0; idx < where.length; idx += 4) {
14340 				rawData[idx + 2] = where[idx + 0]; // r
14341 				rawData[idx + 1] = where[idx + 1]; // g
14342 				rawData[idx + 0] = where[idx + 2]; // b
14343 				if(enableAlpha) {
14344 					rawData[idx + 3] = where[idx + 3]; // a
14345 					premultiplyBgra(rawData[idx .. idx + 4]);
14346 				}
14347 			}
14348 		}
14349 
14350 	}
14351 
14352 	mixin template NativeSimpleWindowImplementation() {
14353 		GC gc;
14354 		Window window;
14355 		Display* display;
14356 
14357 		Pixmap buffer;
14358 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14359 		XIC xic; // input context
14360 		int curHidden = 0; // counter
14361 		Cursor blankCurPtr = 0;
14362 		int cursorSequenceNumber = 0;
14363 		int warpEventCount = 0; // number of mouse movement events to eat
14364 
14365 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14366 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14367 
14368 		version(without_opengl) {} else
14369 		GLXContext glc;
14370 
14371 		private void fixFixedSize(bool forced=false) (int width, int height) {
14372 			if (forced || this.resizability == Resizability.fixedSize) {
14373 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14374 				XSizeHints sh;
14375 				static if (!forced) {
14376 					c_long spr;
14377 					XGetWMNormalHints(display, window, &sh, &spr);
14378 					sh.flags |= PMaxSize | PMinSize;
14379 				} else {
14380 					sh.flags = PMaxSize | PMinSize;
14381 				}
14382 				sh.min_width = width;
14383 				sh.min_height = height;
14384 				sh.max_width = width;
14385 				sh.max_height = height;
14386 				XSetWMNormalHints(display, window, &sh);
14387 				//XFlush(display);
14388 			}
14389 		}
14390 
14391 		ScreenPainter getPainter(bool manualInvalidations) {
14392 			return ScreenPainter(this, window, manualInvalidations);
14393 		}
14394 
14395 		void move(int x, int y) {
14396 			XMoveWindow(display, window, x, y);
14397 		}
14398 
14399 		void resize(int w, int h) {
14400 			if (w < 1) w = 1;
14401 			if (h < 1) h = 1;
14402 			XResizeWindow(display, window, w, h);
14403 
14404 			// calling this now to avoid waiting for the server to
14405 			// acknowledge the resize; draws without returning to the
14406 			// event loop will thus actually work. the server's event
14407 			// btw might overrule this and resize it again
14408 			recordX11Resize(display, this, w, h);
14409 
14410 			updateOpenglViewportIfNeeded(w, h);
14411 		}
14412 
14413 		void moveResize (int x, int y, int w, int h) {
14414 			if (w < 1) w = 1;
14415 			if (h < 1) h = 1;
14416 			XMoveResizeWindow(display, window, x, y, w, h);
14417 			updateOpenglViewportIfNeeded(w, h);
14418 		}
14419 
14420 		void hideCursor () {
14421 			if (curHidden++ == 0) {
14422 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14423 					static const(char)[1] cmbmp = 0;
14424 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14425 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14426 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14427 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14428 					XFreePixmap(display, pm);
14429 				}
14430 				XDefineCursor(display, window, blankCurPtr);
14431 			}
14432 		}
14433 
14434 		void showCursor () {
14435 			if (--curHidden == 0) XUndefineCursor(display, window);
14436 		}
14437 
14438 		void warpMouse (int x, int y) {
14439 			// here i will send dummy "ignore next mouse motion" event,
14440 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14441 			// and we don't need to report it to the user (as warping is
14442 			// used when the user needs movement deltas).
14443 			//XClientMessageEvent xclient;
14444 			XEvent e;
14445 			e.xclient.type = EventType.ClientMessage;
14446 			e.xclient.window = window;
14447 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14448 			e.xclient.format = 32;
14449 			e.xclient.data.l[0] = 0;
14450 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14451 			//{ 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]); }
14452 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14453 			// now warp pointer...
14454 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14455 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14456 			// ...and flush
14457 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14458 			XFlush(display);
14459 		}
14460 
14461 		void sendDummyEvent () {
14462 			// here i will send dummy event to ping event queue
14463 			XEvent e;
14464 			e.xclient.type = EventType.ClientMessage;
14465 			e.xclient.window = window;
14466 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14467 			e.xclient.format = 32;
14468 			e.xclient.data.l[0] = 0;
14469 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14470 			XFlush(display);
14471 		}
14472 
14473 		void setTitle(string title) {
14474 			if (title.ptr is null) title = "";
14475 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14476 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14477 			XTextProperty windowName;
14478 			windowName.value = title.ptr;
14479 			windowName.encoding = XA_UTF8; //XA_STRING;
14480 			windowName.format = 8;
14481 			windowName.nitems = cast(uint)title.length;
14482 			XSetWMName(display, window, &windowName);
14483 			char[1024] namebuf = 0;
14484 			auto maxlen = namebuf.length-1;
14485 			if (maxlen > title.length) maxlen = title.length;
14486 			namebuf[0..maxlen] = title[0..maxlen];
14487 			XStoreName(display, window, namebuf.ptr);
14488 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14489 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14490 		}
14491 
14492 		string[] getTitles() {
14493 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14494 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14495 			XTextProperty textProp;
14496 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14497 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14498 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14499 				} else
14500 					return [];
14501 			} else
14502 				return null;
14503 		}
14504 
14505 		string getTitle() {
14506 			auto titles = getTitles();
14507 			return titles.length ? titles[0] : null;
14508 		}
14509 
14510 		void setMinSize (int minwidth, int minheight) {
14511 			import core.stdc.config : c_long;
14512 			if (minwidth < 1) minwidth = 1;
14513 			if (minheight < 1) minheight = 1;
14514 			XSizeHints sh;
14515 			c_long spr;
14516 			XGetWMNormalHints(display, window, &sh, &spr);
14517 			sh.min_width = minwidth;
14518 			sh.min_height = minheight;
14519 			sh.flags |= PMinSize;
14520 			XSetWMNormalHints(display, window, &sh);
14521 			flushGui();
14522 		}
14523 
14524 		void setMaxSize (int maxwidth, int maxheight) {
14525 			import core.stdc.config : c_long;
14526 			if (maxwidth < 1) maxwidth = 1;
14527 			if (maxheight < 1) maxheight = 1;
14528 			XSizeHints sh;
14529 			c_long spr;
14530 			XGetWMNormalHints(display, window, &sh, &spr);
14531 			sh.max_width = maxwidth;
14532 			sh.max_height = maxheight;
14533 			sh.flags |= PMaxSize;
14534 			XSetWMNormalHints(display, window, &sh);
14535 			flushGui();
14536 		}
14537 
14538 		void setResizeGranularity (int granx, int grany) {
14539 			import core.stdc.config : c_long;
14540 			if (granx < 1) granx = 1;
14541 			if (grany < 1) grany = 1;
14542 			XSizeHints sh;
14543 			c_long spr;
14544 			XGetWMNormalHints(display, window, &sh, &spr);
14545 			sh.width_inc = granx;
14546 			sh.height_inc = grany;
14547 			sh.flags |= PResizeInc;
14548 			XSetWMNormalHints(display, window, &sh);
14549 			flushGui();
14550 		}
14551 
14552 		void setOpacity (uint opacity) {
14553 			arch_ulong o = opacity;
14554 			if (opacity == uint.max)
14555 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14556 			else
14557 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14558 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14559 		}
14560 
14561 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14562 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14563 			display = XDisplayConnection.get();
14564 			auto screen = DefaultScreen(display);
14565 
14566 			bool overrideRedirect = false;
14567 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14568 				overrideRedirect = true;
14569 
14570 			version(without_opengl) {}
14571 			else {
14572 				if(opengl == OpenGlOptions.yes) {
14573 					GLXFBConfig fbconf = null;
14574 					XVisualInfo* vi = null;
14575 					bool useLegacy = false;
14576 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14577 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14578 						int[23] visualAttribs = [
14579 							GLX_X_RENDERABLE , 1/*True*/,
14580 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14581 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14582 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14583 							GLX_RED_SIZE     , 8,
14584 							GLX_GREEN_SIZE   , 8,
14585 							GLX_BLUE_SIZE    , 8,
14586 							GLX_ALPHA_SIZE   , 8,
14587 							GLX_DEPTH_SIZE   , 24,
14588 							GLX_STENCIL_SIZE , 8,
14589 							GLX_DOUBLEBUFFER , 1/*True*/,
14590 							0/*None*/,
14591 						];
14592 						int fbcount;
14593 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14594 						if (fbcount == 0) {
14595 							useLegacy = true; // try to do at least something
14596 						} else {
14597 							// pick the FB config/visual with the most samples per pixel
14598 							int bestidx = -1, bestns = -1;
14599 							foreach (int fbi; 0..fbcount) {
14600 								int sb, samples;
14601 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14602 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14603 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14604 							}
14605 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14606 							fbconf = fbc[bestidx];
14607 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14608 							XFree(fbc);
14609 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14610 						}
14611 					}
14612 					if (vi is null || useLegacy) {
14613 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14614 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14615 						useLegacy = true;
14616 					}
14617 					if (vi is null) throw new Exception("no open gl visual found");
14618 
14619 					XSetWindowAttributes swa;
14620 					auto root = RootWindow(display, screen);
14621 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14622 
14623 					swa.override_redirect = overrideRedirect;
14624 
14625 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14626 						0, 0, width, height,
14627 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14628 
14629 					// now try to use `glXCreateContextAttribsARB()` if it's here
14630 					if (!useLegacy) {
14631 						// request fairly advanced context, even with stencil buffer!
14632 						int[9] contextAttribs = [
14633 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14634 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14635 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14636 							// for modern context, set "forward compatibility" flag too
14637 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14638 							0/*None*/,
14639 						];
14640 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14641 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14642 							sdpyOpenGLContextVersion = 0;
14643 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14644 						}
14645 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14646 					} else {
14647 						// fallback to old GLX call
14648 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14649 							sdpyOpenGLContextVersion = 0;
14650 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14651 						}
14652 					}
14653 					// sync to ensure any errors generated are processed
14654 					XSync(display, 0/*False*/);
14655 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14656 					if(glc is null)
14657 						throw new Exception("glc");
14658 				}
14659 			}
14660 
14661 			if(opengl == OpenGlOptions.no) {
14662 
14663 				XSetWindowAttributes swa;
14664 				swa.background_pixel = WhitePixel(display, screen);
14665 				swa.border_pixel = BlackPixel(display, screen);
14666 				swa.override_redirect = overrideRedirect;
14667 				auto root = RootWindow(display, screen);
14668 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14669 
14670 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14671 					0, 0, width, height,
14672 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14673 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14674 
14675 
14676 
14677 				/*
14678 				window = XCreateSimpleWindow(
14679 					display,
14680 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14681 					0, 0, // x, y
14682 					width, height,
14683 					1, // border width
14684 					BlackPixel(display, screen), // border
14685 					WhitePixel(display, screen)); // background
14686 				*/
14687 
14688 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14689 				bufferw = width;
14690 				bufferh = height;
14691 
14692 				gc = DefaultGC(display, screen);
14693 
14694 				// clear out the buffer to get us started...
14695 				XSetForeground(display, gc, WhitePixel(display, screen));
14696 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14697 				XSetForeground(display, gc, BlackPixel(display, screen));
14698 			}
14699 
14700 			// input context
14701 			//TODO: create this only for top-level windows, and reuse that?
14702 			populateXic();
14703 
14704 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14705 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14706 			// window class
14707 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14708 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14709 				XClassHint klass;
14710 				XWMHints wh;
14711 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14712 					wh.input = true;
14713 					wh.flags |= InputHint;
14714 				}
14715 				XSizeHints size;
14716 				klass.res_name = sdpyWindowClassStr;
14717 				klass.res_class = sdpyWindowClassStr;
14718 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14719 			}
14720 
14721 			setTitle(title);
14722 			SimpleWindow.nativeMapping[window] = this;
14723 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14724 
14725 			// This gives our window a close button
14726 			if (windowType != WindowTypes.eventOnly) {
14727 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14728 				int useAtoms;
14729 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14730 					useAtoms = 2;
14731 				} else {
14732 					useAtoms = 1;
14733 				}
14734 				assert(useAtoms <= atoms.length);
14735 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14736 			}
14737 
14738 			// FIXME: windowType and customizationFlags
14739 			Atom[8] wsatoms; // here, due to goto
14740 			int wmsacount = 0; // here, due to goto
14741 
14742 			try
14743 			final switch(windowType) {
14744 				case WindowTypes.normal:
14745 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14746 				break;
14747 				case WindowTypes.undecorated:
14748 					motifHideDecorations();
14749 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14750 				break;
14751 				case WindowTypes.eventOnly:
14752 					_hidden = true;
14753 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14754 					goto hiddenWindow;
14755 				//break;
14756 				case WindowTypes.nestedChild:
14757 					// handled in XCreateWindow calls
14758 				break;
14759 
14760 				case WindowTypes.dropdownMenu:
14761 					motifHideDecorations();
14762 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14763 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14764 				break;
14765 				case WindowTypes.popupMenu:
14766 					motifHideDecorations();
14767 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14768 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14769 				break;
14770 				case WindowTypes.notification:
14771 					motifHideDecorations();
14772 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14773 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14774 				break;
14775 				case WindowTypes.minimallyWrapped:
14776 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14777 				/+
14778 				case WindowTypes.menu:
14779 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14780 					motifHideDecorations();
14781 				break;
14782 				case WindowTypes.desktop:
14783 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14784 				break;
14785 				case WindowTypes.dock:
14786 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14787 				break;
14788 				case WindowTypes.toolbar:
14789 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14790 				break;
14791 				case WindowTypes.menu:
14792 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14793 				break;
14794 				case WindowTypes.utility:
14795 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14796 				break;
14797 				case WindowTypes.splash:
14798 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14799 				break;
14800 				case WindowTypes.dialog:
14801 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14802 				break;
14803 				case WindowTypes.tooltip:
14804 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14805 				break;
14806 				case WindowTypes.notification:
14807 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14808 				break;
14809 				case WindowTypes.combo:
14810 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14811 				break;
14812 				case WindowTypes.dnd:
14813 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14814 				break;
14815 				+/
14816 			}
14817 			catch(Exception e) {
14818 				// XInternAtom failed, prolly a WM
14819 				// that doesn't support these things
14820 			}
14821 
14822 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14823 			// the two following flags may be ignored by WM
14824 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14825 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14826 
14827 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14828 
14829 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14830 
14831 			// What would be ideal here is if they only were
14832 			// selected if there was actually an event handler
14833 			// for them...
14834 
14835 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14836 
14837 			hiddenWindow:
14838 
14839 			// set the pid property for lookup later by window managers
14840 			// a standard convenience
14841 			import core.sys.posix.unistd;
14842 			arch_ulong pid = getpid();
14843 
14844 			XChangeProperty(
14845 				display,
14846 				impl.window,
14847 				GetAtom!("_NET_WM_PID", true)(display),
14848 				XA_CARDINAL,
14849 				32 /* bits */,
14850 				0 /*PropModeReplace*/,
14851 				&pid,
14852 				1);
14853 
14854 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
14855 				if(parent is null) assert(0);
14856 				XChangeProperty(
14857 					display,
14858 					impl.window,
14859 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
14860 					XA_WINDOW,
14861 					32 /* bits */,
14862 					0 /*PropModeReplace*/,
14863 					&parent.impl.window,
14864 					1);
14865 
14866 			}
14867 
14868 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
14869 				XMapWindow(display, window);
14870 			} else {
14871 				_hidden = true;
14872 			}
14873 		}
14874 
14875 		void populateXic() {
14876 			if (XDisplayConnection.xim !is null) {
14877 				xic = XCreateIC(XDisplayConnection.xim,
14878 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
14879 						/*XNClientWindow*/"clientWindow".ptr, window,
14880 						/*XNFocusWindow*/"focusWindow".ptr, window,
14881 						null);
14882 				if (xic is null) {
14883 					import core.stdc.stdio : stderr, fprintf;
14884 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
14885 				}
14886 			}
14887 		}
14888 
14889 		void selectDefaultInput(bool forceIncludeMouseMotion) {
14890 			auto mask = EventMask.ExposureMask |
14891 				EventMask.KeyPressMask |
14892 				EventMask.KeyReleaseMask |
14893 				EventMask.PropertyChangeMask |
14894 				EventMask.FocusChangeMask |
14895 				EventMask.StructureNotifyMask |
14896 				EventMask.SubstructureNotifyMask |
14897 				EventMask.VisibilityChangeMask
14898 				| EventMask.ButtonPressMask
14899 				| EventMask.ButtonReleaseMask
14900 			;
14901 
14902 			// xshm is our shortcut for local connections
14903 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
14904 				mask |= EventMask.PointerMotionMask;
14905 			else
14906 				mask |= EventMask.ButtonMotionMask;
14907 
14908 			XSelectInput(display, window, mask);
14909 		}
14910 
14911 
14912 		void setNetWMWindowType(Atom type) {
14913 			Atom[2] atoms;
14914 
14915 			atoms[0] = type;
14916 			// generic fallback
14917 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
14918 
14919 			XChangeProperty(
14920 				display,
14921 				impl.window,
14922 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
14923 				XA_ATOM,
14924 				32 /* bits */,
14925 				0 /*PropModeReplace*/,
14926 				atoms.ptr,
14927 				cast(int) atoms.length);
14928 		}
14929 
14930 		void motifHideDecorations(bool hide = true) {
14931 			MwmHints hints;
14932 			hints.flags = MWM_HINTS_DECORATIONS;
14933 			hints.decorations = hide ? 0 : 1;
14934 
14935 			XChangeProperty(
14936 				display,
14937 				impl.window,
14938 				GetAtom!"_MOTIF_WM_HINTS"(display),
14939 				GetAtom!"_MOTIF_WM_HINTS"(display),
14940 				32 /* bits */,
14941 				0 /*PropModeReplace*/,
14942 				&hints,
14943 				hints.sizeof / 4);
14944 		}
14945 
14946 		/*k8: unused
14947 		void createOpenGlContext() {
14948 
14949 		}
14950 		*/
14951 
14952 		void closeWindow() {
14953 			// I can't close this or a child window closing will
14954 			// break events for everyone. So I'm just leaking it right
14955 			// now and that is probably perfectly fine...
14956 			version(none)
14957 			if (customEventFDRead != -1) {
14958 				import core.sys.posix.unistd : close;
14959 				auto same = customEventFDRead == customEventFDWrite;
14960 
14961 				close(customEventFDRead);
14962 				if(!same)
14963 					close(customEventFDWrite);
14964 				customEventFDRead = -1;
14965 				customEventFDWrite = -1;
14966 			}
14967 
14968 			if(glc !is null) {
14969 				glXDestroyContext(display, glc);
14970 				glc = null;
14971 			}
14972 
14973 			if(buffer)
14974 				XFreePixmap(display, buffer);
14975 			bufferw = bufferh = 0;
14976 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
14977 			XDestroyWindow(display, window);
14978 			XFlush(display);
14979 		}
14980 
14981 		void dispose() {
14982 		}
14983 
14984 		bool destroyed = false;
14985 	}
14986 
14987 	bool insideXEventLoop;
14988 }
14989 
14990 version(X11) {
14991 
14992 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
14993 
14994 	private class ResizeEvent {
14995 		int width, height;
14996 	}
14997 
14998 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
14999 		if(win.windowType == WindowTypes.minimallyWrapped)
15000 			return;
15001 
15002 		if(win.pendingResizeEvent is null) {
15003 			win.pendingResizeEvent = new ResizeEvent();
15004 			win.addEventListener((ResizeEvent re) {
15005 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15006 			});
15007 		}
15008 		win.pendingResizeEvent.width = width;
15009 		win.pendingResizeEvent.height = height;
15010 		if(!win.eventQueued!ResizeEvent) {
15011 			win.postEvent(win.pendingResizeEvent);
15012 		}
15013 	}
15014 
15015 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15016 		if(win.windowType == WindowTypes.minimallyWrapped)
15017 			return;
15018 		if(win.closed)
15019 			return;
15020 
15021 		if(width != win.width || height != win.height) {
15022 
15023 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15024 			win._width = width;
15025 			win._height = height;
15026 
15027 			if(win.openglMode == OpenGlOptions.no) {
15028 				// FIXME: could this be more efficient?
15029 
15030 				if (win.bufferw < width || win.bufferh < height) {
15031 					//{ 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); }
15032 					// grow the internal buffer to match the window...
15033 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15034 					{
15035 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15036 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15037 						scope(exit) XFreeGC(win.display, xgc);
15038 						XSetClipMask(win.display, xgc, None);
15039 						XSetForeground(win.display, xgc, 0);
15040 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15041 					}
15042 					XCopyArea(display,
15043 						cast(Drawable) win.buffer,
15044 						cast(Drawable) newPixmap,
15045 						win.gc, 0, 0,
15046 						win.bufferw < width ? win.bufferw : win.width,
15047 						win.bufferh < height ? win.bufferh : win.height,
15048 						0, 0);
15049 
15050 					XFreePixmap(display, win.buffer);
15051 					win.buffer = newPixmap;
15052 					win.bufferw = width;
15053 					win.bufferh = height;
15054 				}
15055 
15056 				// clear unused parts of the buffer
15057 				if (win.bufferw > width || win.bufferh > height) {
15058 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15059 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15060 					scope(exit) XFreeGC(win.display, xgc);
15061 					XSetClipMask(win.display, xgc, None);
15062 					XSetForeground(win.display, xgc, 0);
15063 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15064 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15065 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15066 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15067 				}
15068 
15069 			}
15070 
15071 			win.updateOpenglViewportIfNeeded(width, height);
15072 
15073 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15074 
15075 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15076 			if(win.windowResized !is null) {
15077 				XUnlockDisplay(display);
15078 				scope(exit) XLockDisplay(display);
15079 				win.windowResized(width, height);
15080 			}
15081 		}
15082 	}
15083 
15084 
15085 	/// Platform-specific, you might use it when doing a custom event loop.
15086 	bool doXNextEvent(Display* display) {
15087 		bool done;
15088 		XEvent e;
15089 		XNextEvent(display, &e);
15090 		version(sddddd) {
15091 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15092 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15093 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15094 			}
15095 		}
15096 
15097 		// filter out compose events
15098 		if (XFilterEvent(&e, None)) {
15099 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15100 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15101 			return false;
15102 		}
15103 		// process keyboard mapping changes
15104 		if (e.type == EventType.KeymapNotify) {
15105 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15106 			XRefreshKeyboardMapping(&e.xmapping);
15107 			return false;
15108 		}
15109 
15110 		version(with_eventloop)
15111 			import arsd.eventloop;
15112 
15113 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15114 			// see windows impl's comments
15115 			XUnlockDisplay(display);
15116 			scope(exit) XLockDisplay(display);
15117 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15118 			if(ret == 0)
15119 				return done;
15120 		}
15121 
15122 
15123 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15124 			if(win.getNativeEventHandler !is null) {
15125 				XUnlockDisplay(display);
15126 				scope(exit) XLockDisplay(display);
15127 				auto ret = win.getNativeEventHandler()(e);
15128 				if(ret == 0)
15129 					return done;
15130 			}
15131 		}
15132 
15133 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15134 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15135 				// we get this because of the RRScreenChangeNotifyMask
15136 
15137 				// this isn't actually an ideal way to do it since it wastes time
15138 				// but meh it is simple and it works.
15139 				win.actualDpiLoadAttempted = false;
15140 				SimpleWindow.xRandrInfoLoadAttemped = false;
15141 				win.updateActualDpi(); // trigger a reload
15142 			}
15143 		}
15144 
15145 		switch(e.type) {
15146 		  case EventType.SelectionClear:
15147 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15148 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15149 				// writeln("SelectionClear");
15150 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15151 			}
15152 		  break;
15153 		  case EventType.SelectionRequest:
15154 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15155 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15156 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15157 				XUnlockDisplay(display);
15158 				scope(exit) XLockDisplay(display);
15159 				(*ssh).handleRequest(e);
15160 			}
15161 		  break;
15162 		  case EventType.PropertyNotify:
15163 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15164 
15165 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15166 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15167 					ssh.sendMoreIncr(&e.xproperty);
15168 			}
15169 
15170 
15171 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15172 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15173 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15174 					Atom target;
15175 					int format;
15176 					arch_ulong bytesafter, length;
15177 					void* value;
15178 
15179 					ubyte[] s;
15180 					Atom targetToKeep;
15181 
15182 					XGetWindowProperty(
15183 						e.xproperty.display,
15184 						e.xproperty.window,
15185 						e.xproperty.atom,
15186 						0,
15187 						100000 /* length */,
15188 						true, /* erase it to signal we got it and want more */
15189 						0 /*AnyPropertyType*/,
15190 						&target, &format, &length, &bytesafter, &value);
15191 
15192 					if(!targetToKeep)
15193 						targetToKeep = target;
15194 
15195 					auto id = (cast(ubyte*) value)[0 .. length];
15196 
15197 					handler.handleIncrData(targetToKeep, id);
15198 
15199 					XFree(value);
15200 				}
15201 			}
15202 		  break;
15203 		  case EventType.SelectionNotify:
15204 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15205 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15206 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15207 					XUnlockDisplay(display);
15208 					scope(exit) XLockDisplay(display);
15209 					handler.handleData(None, null);
15210 				} else {
15211 					Atom target;
15212 					int format;
15213 					arch_ulong bytesafter, length;
15214 					void* value;
15215 					XGetWindowProperty(
15216 						e.xselection.display,
15217 						e.xselection.requestor,
15218 						e.xselection.property,
15219 						0,
15220 						100000 /* length */,
15221 						//false, /* don't erase it */
15222 						true, /* do erase it lol */
15223 						0 /*AnyPropertyType*/,
15224 						&target, &format, &length, &bytesafter, &value);
15225 
15226 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15227 
15228 					{
15229 						XUnlockDisplay(display);
15230 						scope(exit) XLockDisplay(display);
15231 
15232 						if(target == XA_ATOM) {
15233 							// initial request, see what they are able to work with and request the best one
15234 							// we can handle, if available
15235 
15236 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15237 							Atom best = handler.findBestFormat(answer);
15238 
15239 							/+
15240 							writeln("got ", answer);
15241 							foreach(a; answer)
15242 								printf("%s\n", XGetAtomName(display, a));
15243 							writeln("best ", best);
15244 							+/
15245 
15246 							if(best != None) {
15247 								// actually request the best format
15248 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15249 							}
15250 						} else if(target == GetAtom!"INCR"(display)) {
15251 							// incremental
15252 
15253 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15254 
15255 							// signal the sending program that we see
15256 							// the incr and are ready to receive more.
15257 							XDeleteProperty(
15258 								e.xselection.display,
15259 								e.xselection.requestor,
15260 								e.xselection.property);
15261 						} else {
15262 							// unsupported type... maybe, forward
15263 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15264 						}
15265 					}
15266 					XFree(value);
15267 					/*
15268 					XDeleteProperty(
15269 						e.xselection.display,
15270 						e.xselection.requestor,
15271 						e.xselection.property);
15272 					*/
15273 				}
15274 			}
15275 		  break;
15276 		  case EventType.ConfigureNotify:
15277 			auto event = e.xconfigure;
15278 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15279 				if(win.windowType == WindowTypes.minimallyWrapped)
15280 					break;
15281 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15282 
15283 				/+
15284 					The ICCCM says window managers must send a synthetic event when the window
15285 					is moved but NOT when it is resized. In the resize case, an event is sent
15286 					with position (0, 0) which can be wrong and break the dpi calculations.
15287 
15288 					So we only consider the synthetic events from the WM and otherwise
15289 					need to wait for some other event to get the position which... sucks.
15290 
15291 					I'd rather not have windows changing their layout on mouse motion after
15292 					switching monitors... might be forced to but for now just ignoring it.
15293 
15294 					Easiest way to switch monitors without sending a size position is by
15295 					maximize or fullscreen in a setup like mine, but on most setups those
15296 					work on the monitor it is already living on, so it should be ok most the
15297 					time.
15298 				+/
15299 				if(event.send_event) {
15300 					win.screenPositionKnown = true;
15301 					win.screenPositionX = event.x;
15302 					win.screenPositionY = event.y;
15303 					win.updateActualDpi();
15304 				}
15305 
15306 				win.updateIMEPopupLocation();
15307 				recordX11ResizeAsync(display, *win, event.width, event.height);
15308 			}
15309 		  break;
15310 		  case EventType.Expose:
15311 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15312 				if(win.windowType == WindowTypes.minimallyWrapped)
15313 					break;
15314 				// if it is closing from a popup menu, it can get
15315 				// an Expose event right by the end and trigger a
15316 				// BadDrawable error ... we'll just check
15317 				// closed to handle that.
15318 				if((*win).closed) break;
15319 				if((*win).openglMode == OpenGlOptions.no) {
15320 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15321 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15322 					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);
15323 				} else {
15324 					// need to redraw the scene somehow
15325 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15326 						XUnlockDisplay(display);
15327 						scope(exit) XLockDisplay(display);
15328 						version(without_opengl) {} else
15329 						win.redrawOpenGlSceneSoon();
15330 					}
15331 				}
15332 			}
15333 		  break;
15334 		  case EventType.FocusIn:
15335 		  case EventType.FocusOut:
15336 
15337 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15338 				/+
15339 
15340 				void info(string detail) {
15341 					string s;
15342 					// import std.conv;
15343 					// import std.datetime;
15344 					s ~= to!string(Clock.currTime);
15345 					s ~= " ";
15346 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15347 					s ~= " ";
15348 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15349 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15350 					s ~= detail;
15351 					s ~= " ";
15352 
15353 					sdpyPrintDebugString(s);
15354 
15355 				}
15356 
15357 				switch(e.xfocus.detail) {
15358 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15359 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15360 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15361 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15362 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15363 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15364 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15365 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15366 					default:
15367 
15368 				}
15369 				+/
15370 
15371 
15372 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15373 					break; // just ignore these they seem irrelevant
15374 
15375 				auto old = win._focused;
15376 				win._focused = e.type == EventType.FocusIn;
15377 
15378 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15379 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15380 					win._focused = true;
15381 
15382 				if(win.demandingAttention)
15383 					demandAttention(*win, false);
15384 
15385 				win.updateIMEFocused();
15386 
15387 				if(old != win._focused && win.onFocusChange) {
15388 					XUnlockDisplay(display);
15389 					scope(exit) XLockDisplay(display);
15390 					win.onFocusChange(win._focused);
15391 				}
15392 			}
15393 		  break;
15394 		  case EventType.VisibilityNotify:
15395 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15396 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15397 						if (win.visibilityChanged !is null) {
15398 								XUnlockDisplay(display);
15399 								scope(exit) XLockDisplay(display);
15400 								win.visibilityChanged(false);
15401 							}
15402 					} else {
15403 						if (win.visibilityChanged !is null) {
15404 							XUnlockDisplay(display);
15405 							scope(exit) XLockDisplay(display);
15406 							win.visibilityChanged(true);
15407 						}
15408 					}
15409 				}
15410 				break;
15411 		  case EventType.ClientMessage:
15412 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15413 					// "ignore next mouse motion" event, increment ignore counter for teh window
15414 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15415 						++(*win).warpEventCount;
15416 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15417 					} else {
15418 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15419 					}
15420 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15421 					// user clicked the close button on the window manager
15422 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15423 						XUnlockDisplay(display);
15424 						scope(exit) XLockDisplay(display);
15425 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15426 					}
15427 
15428 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15429 					// writeln("HAPPENED");
15430 					// user clicked the close button on the window manager
15431 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15432 						XUnlockDisplay(display);
15433 						scope(exit) XLockDisplay(display);
15434 
15435 						auto setTo = *win;
15436 
15437 						if(win.setRequestedInputFocus !is null) {
15438 							auto s = win.setRequestedInputFocus();
15439 							if(s !is null) {
15440 								setTo = s;
15441 							}
15442 						}
15443 
15444 						assert(setTo !is null);
15445 
15446 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15447 
15448 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15449 					}
15450 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15451 					foreach(nai; NotificationAreaIcon.activeIcons)
15452 						nai.newManager();
15453 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15454 
15455 					bool xDragWindow = true;
15456 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15457 						//XDefineCursor(display, xDragWindow.impl.window,
15458 							//writeln("XdndStatus ", e.xclient.data.l);
15459 					}
15460 					if(auto dh = win.dropHandler) {
15461 
15462 						static Atom[3] xFormatsBuffer;
15463 						static Atom[] xFormats;
15464 
15465 						void resetXFormats() {
15466 							xFormatsBuffer[] = 0;
15467 							xFormats = xFormatsBuffer[];
15468 						}
15469 
15470 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15471 							// on Windows it is supposed to return the effect you actually do FIXME
15472 
15473 							auto sourceWindow =  e.xclient.data.l[0];
15474 
15475 							xFormatsBuffer[0] = e.xclient.data.l[2];
15476 							xFormatsBuffer[1] = e.xclient.data.l[3];
15477 							xFormatsBuffer[2] = e.xclient.data.l[4];
15478 
15479 							if(e.xclient.data.l[1] & 1) {
15480 								// can just grab it all but like we don't necessarily need them...
15481 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15482 							} else {
15483 								int len;
15484 								foreach(fmt; xFormatsBuffer)
15485 									if(fmt) len++;
15486 								xFormats = xFormatsBuffer[0 .. len];
15487 							}
15488 
15489 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15490 
15491 							dh.dragEnter(&pkg);
15492 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15493 
15494 							auto pack = e.xclient.data.l[2];
15495 
15496 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15497 
15498 
15499 							XClientMessageEvent xclient;
15500 
15501 							xclient.type = EventType.ClientMessage;
15502 							xclient.window = e.xclient.data.l[0];
15503 							xclient.message_type = GetAtom!"XdndStatus"(display);
15504 							xclient.format = 32;
15505 							xclient.data.l[0] = win.impl.window;
15506 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15507 							auto r = result.consistentWithin;
15508 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15509 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15510 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15511 
15512 							XSendEvent(
15513 								display,
15514 								e.xclient.data.l[0],
15515 								false,
15516 								EventMask.NoEventMask,
15517 								cast(XEvent*) &xclient
15518 							);
15519 
15520 
15521 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15522 							//writeln("XdndLeave");
15523 							// drop cancelled.
15524 							// data.l[0] is the source window
15525 							dh.dragLeave();
15526 
15527 							resetXFormats();
15528 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15529 							// drop happening, should fetch data, then send finished
15530 							// writeln("XdndDrop");
15531 
15532 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15533 
15534 							dh.drop(&pkg);
15535 
15536 							resetXFormats();
15537 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15538 							// writeln("XdndFinished");
15539 
15540 							dh.finish();
15541 						}
15542 
15543 					}
15544 				}
15545 		  break;
15546 		  case EventType.MapNotify:
15547 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15548 					(*win)._visible = true;
15549 					if (!(*win)._visibleForTheFirstTimeCalled) {
15550 						(*win)._visibleForTheFirstTimeCalled = true;
15551 						if ((*win).visibleForTheFirstTime !is null) {
15552 							XUnlockDisplay(display);
15553 							scope(exit) XLockDisplay(display);
15554 							(*win).visibleForTheFirstTime();
15555 						}
15556 					}
15557 					if ((*win).visibilityChanged !is null) {
15558 						XUnlockDisplay(display);
15559 						scope(exit) XLockDisplay(display);
15560 						(*win).visibilityChanged(true);
15561 					}
15562 				}
15563 		  break;
15564 		  case EventType.UnmapNotify:
15565 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15566 					win._visible = false;
15567 					if (win.visibilityChanged !is null) {
15568 						XUnlockDisplay(display);
15569 						scope(exit) XLockDisplay(display);
15570 						win.visibilityChanged(false);
15571 					}
15572 			}
15573 		  break;
15574 		  case EventType.DestroyNotify:
15575 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15576 				if(win.destroyed)
15577 					break; // might get a notification both for itself and from its parent
15578 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15579 				win._closed = true; // just in case
15580 				win.destroyed = true;
15581 				if (win.xic !is null) {
15582 					XDestroyIC(win.xic);
15583 					win.xic = null; // just in case
15584 				}
15585 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15586 				bool anyImportant = false;
15587 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15588 					if(w.beingOpenKeepsAppOpen) {
15589 						anyImportant = true;
15590 						break;
15591 					}
15592 				if(!anyImportant) {
15593 					EventLoop.quitApplication();
15594 					done = true;
15595 				}
15596 			}
15597 			auto window = e.xdestroywindow.window;
15598 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15599 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15600 
15601 			version(with_eventloop) {
15602 				if(done) exit();
15603 			}
15604 		  break;
15605 
15606 		  case EventType.MotionNotify:
15607 			MouseEvent mouse;
15608 			auto event = e.xmotion;
15609 
15610 			mouse.type = MouseEventType.motion;
15611 			mouse.x = event.x;
15612 			mouse.y = event.y;
15613 			mouse.modifierState = event.state;
15614 
15615 			mouse.timestamp = event.time;
15616 
15617 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15618 				mouse.window = *win;
15619 				if (win.warpEventCount > 0) {
15620 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15621 					--(*win).warpEventCount;
15622 					(*win).mdx(mouse); // so deltas will be correctly updated
15623 				} else {
15624 					win.warpEventCount = 0; // just in case
15625 					(*win).mdx(mouse);
15626 					if((*win).handleMouseEvent) {
15627 						XUnlockDisplay(display);
15628 						scope(exit) XLockDisplay(display);
15629 						(*win).handleMouseEvent(mouse);
15630 					}
15631 				}
15632 			}
15633 
15634 		  	version(with_eventloop)
15635 				send(mouse);
15636 		  break;
15637 		  case EventType.ButtonPress:
15638 		  case EventType.ButtonRelease:
15639 			MouseEvent mouse;
15640 			auto event = e.xbutton;
15641 
15642 			mouse.timestamp = event.time;
15643 
15644 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15645 			mouse.x = event.x;
15646 			mouse.y = event.y;
15647 
15648 			static Time lastMouseDownTime = 0;
15649 			static int lastMouseDownButton = -1;
15650 
15651 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15652 			if(e.type == EventType.ButtonPress) {
15653 				lastMouseDownTime = event.time;
15654 				lastMouseDownButton = event.button;
15655 			}
15656 
15657 			switch(event.button) {
15658 				case 1: mouse.button = MouseButton.left; break; // left
15659 				case 2: mouse.button = MouseButton.middle; break; // middle
15660 				case 3: mouse.button = MouseButton.right; break; // right
15661 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15662 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15663 				case 6: break; // idk
15664 				case 7: break; // idk
15665 				case 8: mouse.button = MouseButton.backButton; break;
15666 				case 9: mouse.button = MouseButton.forwardButton; break;
15667 				default:
15668 			}
15669 
15670 			// FIXME: double check this
15671 			mouse.modifierState = event.state;
15672 
15673 			//mouse.modifierState = event.detail;
15674 
15675 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15676 				mouse.window = *win;
15677 				(*win).mdx(mouse);
15678 				if((*win).handleMouseEvent) {
15679 					XUnlockDisplay(display);
15680 					scope(exit) XLockDisplay(display);
15681 					(*win).handleMouseEvent(mouse);
15682 				}
15683 			}
15684 			version(with_eventloop)
15685 				send(mouse);
15686 		  break;
15687 
15688 		  case EventType.KeyPress:
15689 		  case EventType.KeyRelease:
15690 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15691 			KeyEvent ke;
15692 			ke.pressed = e.type == EventType.KeyPress;
15693 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15694 
15695 			auto sym = XKeycodeToKeysym(
15696 				XDisplayConnection.get(),
15697 				e.xkey.keycode,
15698 				0);
15699 
15700 			ke.key = cast(Key) sym;//e.xkey.keycode;
15701 
15702 			ke.modifierState = e.xkey.state;
15703 
15704 			// writefln("%x", sym);
15705 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15706 			int charbuflen = 0; // return value of XwcLookupString
15707 			if (ke.pressed) {
15708 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15709 				if (win !is null && win.xic !is null) {
15710 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15711 					Status status;
15712 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15713 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15714 				} else {
15715 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15716 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15717 					char[16] buffer;
15718 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15719 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15720 				}
15721 			}
15722 
15723 			// if there's no char, subst one
15724 			if (charbuflen == 0) {
15725 				switch (sym) {
15726 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15727 					case 0xff8d: // keypad enter
15728 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15729 					default : // ignore
15730 				}
15731 			}
15732 
15733 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15734 				ke.window = *win;
15735 
15736 
15737 				if(win.inputProxy)
15738 					win = &win.inputProxy;
15739 
15740 				// char events are separate since they are on Windows too
15741 				// also, xcompose can generate long char sequences
15742 				// don't send char events if Meta and/or Hyper is pressed
15743 				// TODO: ctrl+char should only send control chars; not yet
15744 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15745 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15746 				}
15747 
15748 				dchar[32] charsComingBuffer;
15749 				int charsComingPosition;
15750 				dchar[] charsComing = charsComingBuffer[];
15751 
15752 				if (ke.pressed && charbuflen > 0) {
15753 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15754 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15755 						if(charsComingPosition >= charsComing.length)
15756 							charsComing.length = charsComingPosition + 8;
15757 
15758 						charsComing[charsComingPosition++] = ch;
15759 					}
15760 
15761 					charsComing = charsComing[0 .. charsComingPosition];
15762 				} else {
15763 					charsComing = null;
15764 				}
15765 
15766 				ke.charsPossible = charsComing;
15767 
15768 				if (win.handleKeyEvent) {
15769 					XUnlockDisplay(display);
15770 					scope(exit) XLockDisplay(display);
15771 					win.handleKeyEvent(ke);
15772 				}
15773 
15774 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15775 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15776 					XUnlockDisplay(display);
15777 					scope(exit) XLockDisplay(display);
15778 					foreach(ch; charsComing)
15779 						win.handleCharEvent(ch);
15780 				}
15781 			}
15782 
15783 			version(with_eventloop)
15784 				send(ke);
15785 		  break;
15786 		  default:
15787 		}
15788 
15789 		return done;
15790 	}
15791 }
15792 
15793 /* *************************************** */
15794 /*      Done with simpledisplay stuff      */
15795 /* *************************************** */
15796 
15797 // Necessary C library bindings follow
15798 version(Windows) {} else
15799 version(X11) {
15800 
15801 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15802 
15803 // X11 bindings needed here
15804 /*
15805 	A little of this is from the bindings project on
15806 	D Source and some of it is copy/paste from the C
15807 	header.
15808 
15809 	The DSource listing consistently used D's long
15810 	where C used long. That's wrong - C long is 32 bit, so
15811 	it should be int in D. I changed that here.
15812 
15813 	Note:
15814 	This isn't complete, just took what I needed for myself.
15815 */
15816 
15817 import core.stdc.stddef : wchar_t;
15818 
15819 interface XLib {
15820 extern(C) nothrow @nogc {
15821 	char* XResourceManagerString(Display*);
15822 	void XrmInitialize();
15823 	XrmDatabase XrmGetStringDatabase(char* data);
15824 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15825 
15826 	Cursor XCreateFontCursor(Display*, uint shape);
15827 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15828 	int XUndefineCursor(Display* display, Window w);
15829 
15830 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15831 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15832 	int XFreeCursor(Display* display, Cursor cursor);
15833 
15834 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
15835 
15836 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
15837 
15838 	XVaNestedList XVaCreateNestedList(int unused, ...);
15839 
15840 	char *XKeysymToString(KeySym keysym);
15841 	KeySym XKeycodeToKeysym(
15842 		Display*		/* display */,
15843 		KeyCode		/* keycode */,
15844 		int			/* index */
15845 	);
15846 
15847 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
15848 
15849 	int XFree(void*);
15850 	int XDeleteProperty(Display *display, Window w, Atom property);
15851 
15852 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
15853 
15854 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
15855 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
15856 		*actual_type_return, int *actual_format_return, arch_ulong
15857 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
15858 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
15859 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
15860 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
15861 
15862 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
15863 
15864 	Window XGetSelectionOwner(Display *display, Atom selection);
15865 
15866 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
15867 
15868 	char** XListFonts(Display*, const char*, int, int*);
15869 	void XFreeFontNames(char**);
15870 
15871 	Display* XOpenDisplay(const char*);
15872 	int XCloseDisplay(Display*);
15873 
15874 	int function() XSynchronize(Display*, bool);
15875 	int function() XSetAfterFunction(Display*, int function() proc);
15876 
15877 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
15878 
15879 	Bool XSupportsLocale();
15880 	char* XSetLocaleModifiers(const(char)* modifier_list);
15881 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15882 	Status XCloseOM(XOM om);
15883 
15884 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15885 	Status XCloseIM(XIM im);
15886 
15887 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15888 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15889 	Display* XDisplayOfIM(XIM im);
15890 	char* XLocaleOfIM(XIM im);
15891 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
15892 	void XDestroyIC(XIC ic);
15893 	void XSetICFocus(XIC ic);
15894 	void XUnsetICFocus(XIC ic);
15895 	//wchar_t* XwcResetIC(XIC ic);
15896 	char* XmbResetIC(XIC ic);
15897 	char* Xutf8ResetIC(XIC ic);
15898 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15899 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15900 	XIM XIMOfIC(XIC ic);
15901 
15902 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
15903 
15904 
15905 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
15906 	int XFreeFont(Display *display, XFontStruct *font_struct);
15907 	int XSetFont(Display* display, GC gc, Font font);
15908 	int XTextWidth(XFontStruct*, scope const char*, int);
15909 
15910 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
15911 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
15912 
15913 	Window XCreateSimpleWindow(
15914 		Display*	/* display */,
15915 		Window		/* parent */,
15916 		int			/* x */,
15917 		int			/* y */,
15918 		uint		/* width */,
15919 		uint		/* height */,
15920 		uint		/* border_width */,
15921 		uint		/* border */,
15922 		uint		/* background */
15923 	);
15924 	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);
15925 
15926 	int XReparentWindow(Display*, Window, Window, int, int);
15927 	int XClearWindow(Display*, Window);
15928 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
15929 	int XMoveWindow(Display*, Window, int, int);
15930 	int XResizeWindow(Display *display, Window w, uint width, uint height);
15931 
15932 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
15933 
15934 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
15935 
15936 	XImage *XCreateImage(
15937 		Display*		/* display */,
15938 		Visual*		/* visual */,
15939 		uint	/* depth */,
15940 		int			/* format */,
15941 		int			/* offset */,
15942 		ubyte*		/* data */,
15943 		uint	/* width */,
15944 		uint	/* height */,
15945 		int			/* bitmap_pad */,
15946 		int			/* bytes_per_line */
15947 	);
15948 
15949 	Status XInitImage (XImage* image);
15950 
15951 	Atom XInternAtom(
15952 		Display*		/* display */,
15953 		const char*	/* atom_name */,
15954 		Bool		/* only_if_exists */
15955 	);
15956 
15957 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
15958 	char* XGetAtomName(Display*, Atom);
15959 	Status XGetAtomNames(Display*, Atom*, int count, char**);
15960 
15961 	int XPutImage(
15962 		Display*	/* display */,
15963 		Drawable	/* d */,
15964 		GC			/* gc */,
15965 		XImage*	/* image */,
15966 		int			/* src_x */,
15967 		int			/* src_y */,
15968 		int			/* dest_x */,
15969 		int			/* dest_y */,
15970 		uint		/* width */,
15971 		uint		/* height */
15972 	);
15973 
15974 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
15975 
15976 
15977 	int XDestroyWindow(
15978 		Display*	/* display */,
15979 		Window		/* w */
15980 	);
15981 
15982 	int XDestroyImage(XImage*);
15983 
15984 	int XSelectInput(
15985 		Display*	/* display */,
15986 		Window		/* w */,
15987 		EventMask	/* event_mask */
15988 	);
15989 
15990 	int XMapWindow(
15991 		Display*	/* display */,
15992 		Window		/* w */
15993 	);
15994 
15995 	Status XIconifyWindow(Display*, Window, int);
15996 	int XMapRaised(Display*, Window);
15997 	int XMapSubwindows(Display*, Window);
15998 
15999 	int XNextEvent(
16000 		Display*	/* display */,
16001 		XEvent*		/* event_return */
16002 	);
16003 
16004 	int XMaskEvent(Display*, arch_long, XEvent*);
16005 
16006 	Bool XFilterEvent(XEvent *event, Window window);
16007 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16008 
16009 	Status XSetWMProtocols(
16010 		Display*	/* display */,
16011 		Window		/* w */,
16012 		Atom*		/* protocols */,
16013 		int			/* count */
16014 	);
16015 
16016 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16017 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16018 
16019 
16020 	Status XInitThreads();
16021 	void XLockDisplay (Display* display);
16022 	void XUnlockDisplay (Display* display);
16023 
16024 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16025 
16026 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16027 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16028 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16029 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16030 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16031 
16032 
16033 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16034 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16035 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16036 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16037 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16038 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16039 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16040 	int XDrawPoint(Display*, Drawable, GC, int, int);
16041 	int XSetForeground(Display*, GC, uint);
16042 	int XSetBackground(Display*, GC, uint);
16043 
16044 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16045 	void XFreeFontSet(Display*, XFontSet);
16046 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16047 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16048 
16049 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16050 
16051 
16052 //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);
16053 
16054 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16055 	int XSetFunction(Display*, GC, int);
16056 
16057 	GC XCreateGC(Display*, Drawable, uint, void*);
16058 	int XCopyGC(Display*, GC, uint, GC);
16059 	int XFreeGC(Display*, GC);
16060 
16061 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16062 	bool XCheckMaskEvent(Display*, int, XEvent*);
16063 
16064 	int XPending(Display*);
16065 	int XEventsQueued(Display* display, int mode);
16066 
16067 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16068 	int XFreePixmap(Display*, Pixmap);
16069 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16070 	int XFlush(Display*);
16071 	int XBell(Display*, int);
16072 	int XSync(Display*, bool);
16073 
16074 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16075 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16076 
16077 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16078 	int XUngrabKeyboard(Display*, Time);
16079 
16080 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16081 
16082 	KeySym XStringToKeysym(const char *string);
16083 
16084 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16085 
16086 	Window XDefaultRootWindow(Display*);
16087 
16088 	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);
16089 
16090 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16091 
16092 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16093 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16094 
16095 	Status XAllocColor(Display*, Colormap, XColor*);
16096 
16097 	int XWithdrawWindow(Display*, Window, int);
16098 	int XUnmapWindow(Display*, Window);
16099 	int XLowerWindow(Display*, Window);
16100 	int XRaiseWindow(Display*, Window);
16101 
16102 	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);
16103 	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);
16104 
16105 	int XGetInputFocus(Display*, Window*, int*);
16106 	int XSetInputFocus(Display*, Window, int, Time);
16107 
16108 	XErrorHandler XSetErrorHandler(XErrorHandler);
16109 
16110 	int XGetErrorText(Display*, int, char*, int);
16111 
16112 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16113 
16114 
16115 	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);
16116 	int XUngrabPointer(Display *display, Time time);
16117 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16118 
16119 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16120 
16121 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16122 	int XSetClipMask(Display*, GC, Pixmap);
16123 	int XSetClipOrigin(Display*, GC, int, int);
16124 
16125 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16126 
16127 	void XSetWMName(Display*, Window, XTextProperty*);
16128 	Status XGetWMName(Display*, Window, XTextProperty*);
16129 	int XStoreName(Display* display, Window w, const(char)* window_name);
16130 
16131 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16132 
16133 }
16134 }
16135 
16136 interface Xext {
16137 extern(C) nothrow @nogc {
16138 	Status XShmAttach(Display*, XShmSegmentInfo*);
16139 	Status XShmDetach(Display*, XShmSegmentInfo*);
16140 	Status XShmPutImage(
16141 		Display*            /* dpy */,
16142 		Drawable            /* d */,
16143 		GC                  /* gc */,
16144 		XImage*             /* image */,
16145 		int                 /* src_x */,
16146 		int                 /* src_y */,
16147 		int                 /* dst_x */,
16148 		int                 /* dst_y */,
16149 		uint        /* src_width */,
16150 		uint        /* src_height */,
16151 		Bool                /* send_event */
16152 	);
16153 
16154 	Status XShmQueryExtension(Display*);
16155 
16156 	XImage *XShmCreateImage(
16157 		Display*            /* dpy */,
16158 		Visual*             /* visual */,
16159 		uint        /* depth */,
16160 		int                 /* format */,
16161 		char*               /* data */,
16162 		XShmSegmentInfo*    /* shminfo */,
16163 		uint        /* width */,
16164 		uint        /* height */
16165 	);
16166 
16167 	Pixmap XShmCreatePixmap(
16168 		Display*            /* dpy */,
16169 		Drawable            /* d */,
16170 		char*               /* data */,
16171 		XShmSegmentInfo*    /* shminfo */,
16172 		uint        /* width */,
16173 		uint        /* height */,
16174 		uint        /* depth */
16175 	);
16176 
16177 }
16178 }
16179 
16180 	// this requires -lXpm
16181 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16182 
16183 
16184 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16185 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16186 shared static this() {
16187 	xlib.loadDynamicLibrary();
16188 	xext.loadDynamicLibrary();
16189 }
16190 
16191 
16192 extern(C) nothrow @nogc {
16193 
16194 alias XrmDatabase = void*;
16195 struct XrmValue {
16196 	uint size;
16197 	void* addr;
16198 }
16199 
16200 struct XVisualInfo {
16201 	Visual* visual;
16202 	VisualID visualid;
16203 	int screen;
16204 	uint depth;
16205 	int c_class;
16206 	c_ulong red_mask;
16207 	c_ulong green_mask;
16208 	c_ulong blue_mask;
16209 	int colormap_size;
16210 	int bits_per_rgb;
16211 }
16212 
16213 enum VisualNoMask=	0x0;
16214 enum VisualIDMask=	0x1;
16215 enum VisualScreenMask=0x2;
16216 enum VisualDepthMask=	0x4;
16217 enum VisualClassMask=	0x8;
16218 enum VisualRedMaskMask=0x10;
16219 enum VisualGreenMaskMask=0x20;
16220 enum VisualBlueMaskMask=0x40;
16221 enum VisualColormapSizeMask=0x80;
16222 enum VisualBitsPerRGBMask=0x100;
16223 enum VisualAllMask=	0x1FF;
16224 
16225 enum AnyKey = 0;
16226 enum AnyModifier = 1 << 15;
16227 
16228 // XIM and other crap
16229 struct _XOM {}
16230 struct _XIM {}
16231 struct _XIC {}
16232 alias XOM = _XOM*;
16233 alias XIM = _XIM*;
16234 alias XIC = _XIC*;
16235 
16236 alias XVaNestedList = void*;
16237 
16238 alias XIMStyle = arch_ulong;
16239 enum : arch_ulong {
16240 	XIMPreeditArea      = 0x0001,
16241 	XIMPreeditCallbacks = 0x0002,
16242 	XIMPreeditPosition  = 0x0004,
16243 	XIMPreeditNothing   = 0x0008,
16244 	XIMPreeditNone      = 0x0010,
16245 	XIMStatusArea       = 0x0100,
16246 	XIMStatusCallbacks  = 0x0200,
16247 	XIMStatusNothing    = 0x0400,
16248 	XIMStatusNone       = 0x0800,
16249 }
16250 
16251 
16252 /* X Shared Memory Extension functions */
16253 	//pragma(lib, "Xshm");
16254 	alias arch_ulong ShmSeg;
16255 	struct XShmSegmentInfo {
16256 		ShmSeg shmseg;
16257 		int shmid;
16258 		ubyte* shmaddr;
16259 		Bool readOnly;
16260 	}
16261 
16262 	// and the necessary OS functions
16263 	int shmget(int, size_t, int);
16264 	void* shmat(int, scope const void*, int);
16265 	int shmdt(scope const void*);
16266 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16267 
16268 	enum IPC_PRIVATE = 0;
16269 	enum IPC_CREAT = 512;
16270 	enum IPC_RMID = 0;
16271 
16272 /* MIT-SHM end */
16273 
16274 
16275 enum MappingType:int {
16276 	MappingModifier		=0,
16277 	MappingKeyboard		=1,
16278 	MappingPointer		=2
16279 }
16280 
16281 /* ImageFormat -- PutImage, GetImage */
16282 enum ImageFormat:int {
16283 	XYBitmap	=0,	/* depth 1, XYFormat */
16284 	XYPixmap	=1,	/* depth == drawable depth */
16285 	ZPixmap	=2	/* depth == drawable depth */
16286 }
16287 
16288 enum ModifierName:int {
16289 	ShiftMapIndex	=0,
16290 	LockMapIndex	=1,
16291 	ControlMapIndex	=2,
16292 	Mod1MapIndex	=3,
16293 	Mod2MapIndex	=4,
16294 	Mod3MapIndex	=5,
16295 	Mod4MapIndex	=6,
16296 	Mod5MapIndex	=7
16297 }
16298 
16299 enum ButtonMask:int {
16300 	Button1Mask	=1<<8,
16301 	Button2Mask	=1<<9,
16302 	Button3Mask	=1<<10,
16303 	Button4Mask	=1<<11,
16304 	Button5Mask	=1<<12,
16305 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16306 }
16307 
16308 enum KeyOrButtonMask:uint {
16309 	ShiftMask	=1<<0,
16310 	LockMask	=1<<1,
16311 	ControlMask	=1<<2,
16312 	Mod1Mask	=1<<3,
16313 	Mod2Mask	=1<<4,
16314 	Mod3Mask	=1<<5,
16315 	Mod4Mask	=1<<6,
16316 	Mod5Mask	=1<<7,
16317 	Button1Mask	=1<<8,
16318 	Button2Mask	=1<<9,
16319 	Button3Mask	=1<<10,
16320 	Button4Mask	=1<<11,
16321 	Button5Mask	=1<<12,
16322 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16323 }
16324 
16325 enum ButtonName:int {
16326 	Button1	=1,
16327 	Button2	=2,
16328 	Button3	=3,
16329 	Button4	=4,
16330 	Button5	=5
16331 }
16332 
16333 /* Notify modes */
16334 enum NotifyModes:int
16335 {
16336 	NotifyNormal		=0,
16337 	NotifyGrab			=1,
16338 	NotifyUngrab		=2,
16339 	NotifyWhileGrabbed	=3
16340 }
16341 enum NotifyHint = 1;	/* for MotionNotify events */
16342 
16343 /* Notify detail */
16344 enum NotifyDetail:int
16345 {
16346 	NotifyAncestor			=0,
16347 	NotifyVirtual			=1,
16348 	NotifyInferior			=2,
16349 	NotifyNonlinear			=3,
16350 	NotifyNonlinearVirtual	=4,
16351 	NotifyPointer			=5,
16352 	NotifyPointerRoot		=6,
16353 	NotifyDetailNone		=7
16354 }
16355 
16356 /* Visibility notify */
16357 
16358 enum VisibilityNotify:int
16359 {
16360 VisibilityUnobscured		=0,
16361 VisibilityPartiallyObscured	=1,
16362 VisibilityFullyObscured		=2
16363 }
16364 
16365 
16366 enum WindowStackingMethod:int
16367 {
16368 	Above		=0,
16369 	Below		=1,
16370 	TopIf		=2,
16371 	BottomIf	=3,
16372 	Opposite	=4
16373 }
16374 
16375 /* Circulation request */
16376 enum CirculationRequest:int
16377 {
16378 	PlaceOnTop		=0,
16379 	PlaceOnBottom	=1
16380 }
16381 
16382 enum PropertyNotification:int
16383 {
16384 	PropertyNewValue	=0,
16385 	PropertyDelete		=1
16386 }
16387 
16388 enum ColorMapNotification:int
16389 {
16390 	ColormapUninstalled	=0,
16391 	ColormapInstalled		=1
16392 }
16393 
16394 
16395 	struct _XPrivate {}
16396 	struct _XrmHashBucketRec {}
16397 
16398 	alias void* XPointer;
16399 	alias void* XExtData;
16400 
16401 	version( X86_64 ) {
16402 		alias ulong XID;
16403 		alias ulong arch_ulong;
16404 		alias long arch_long;
16405 	} else version (AArch64) {
16406 		alias ulong XID;
16407 		alias ulong arch_ulong;
16408 		alias long arch_long;
16409 	} else {
16410 		alias uint XID;
16411 		alias uint arch_ulong;
16412 		alias int arch_long;
16413 	}
16414 
16415 	alias XID Window;
16416 	alias XID Drawable;
16417 	alias XID Pixmap;
16418 
16419 	alias arch_ulong Atom;
16420 	alias int Bool;
16421 	alias Display XDisplay;
16422 
16423 	alias int ByteOrder;
16424 	alias arch_ulong Time;
16425 	alias void ScreenFormat;
16426 
16427 	struct XImage {
16428 		int width, height;			/* size of image */
16429 		int xoffset;				/* number of pixels offset in X direction */
16430 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16431 		void *data;					/* pointer to image data */
16432 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16433 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16434 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16435 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16436 		int depth;					/* depth of image */
16437 		int bytes_per_line;			/* accelarator to next line */
16438 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16439 		arch_ulong red_mask;	/* bits in z arrangment */
16440 		arch_ulong green_mask;
16441 		arch_ulong blue_mask;
16442 		XPointer obdata;			/* hook for the object routines to hang on */
16443 		static struct F {				/* image manipulation routines */
16444 			XImage* function(
16445 				XDisplay* 			/* display */,
16446 				Visual*				/* visual */,
16447 				uint				/* depth */,
16448 				int					/* format */,
16449 				int					/* offset */,
16450 				ubyte*				/* data */,
16451 				uint				/* width */,
16452 				uint				/* height */,
16453 				int					/* bitmap_pad */,
16454 				int					/* bytes_per_line */) create_image;
16455 			int function(XImage *) destroy_image;
16456 			arch_ulong function(XImage *, int, int) get_pixel;
16457 			int function(XImage *, int, int, arch_ulong) put_pixel;
16458 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16459 			int function(XImage *, arch_long) add_pixel;
16460 		}
16461 		F f;
16462 	}
16463 	version(X86_64) static assert(XImage.sizeof == 136);
16464 	else version(X86) static assert(XImage.sizeof == 88);
16465 
16466 struct XCharStruct {
16467 	short       lbearing;       /* origin to left edge of raster */
16468 	short       rbearing;       /* origin to right edge of raster */
16469 	short       width;          /* advance to next char's origin */
16470 	short       ascent;         /* baseline to top edge of raster */
16471 	short       descent;        /* baseline to bottom edge of raster */
16472 	ushort attributes;  /* per char flags (not predefined) */
16473 }
16474 
16475 /*
16476  * To allow arbitrary information with fonts, there are additional properties
16477  * returned.
16478  */
16479 struct XFontProp {
16480 	Atom name;
16481 	arch_ulong card32;
16482 }
16483 
16484 alias Atom Font;
16485 
16486 struct XFontStruct {
16487 	XExtData *ext_data;           /* Hook for extension to hang data */
16488 	Font fid;                     /* Font ID for this font */
16489 	uint direction;           /* Direction the font is painted */
16490 	uint min_char_or_byte2;   /* First character */
16491 	uint max_char_or_byte2;   /* Last character */
16492 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16493 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16494 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16495 	uint default_char;        /* Char to print for undefined character */
16496 	int n_properties;             /* How many properties there are */
16497 	XFontProp *properties;        /* Pointer to array of additional properties*/
16498 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16499 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16500 	XCharStruct *per_char;        /* first_char to last_char information */
16501 	int ascent;                   /* Max extent above baseline for spacing */
16502 	int descent;                  /* Max descent below baseline for spacing */
16503 }
16504 
16505 
16506 /*
16507  * Definitions of specific events.
16508  */
16509 struct XKeyEvent
16510 {
16511 	int type;			/* of event */
16512 	arch_ulong serial;		/* # of last request processed by server */
16513 	Bool send_event;	/* true if this came from a SendEvent request */
16514 	Display *display;	/* Display the event was read from */
16515 	Window window;	        /* "event" window it is reported relative to */
16516 	Window root;	        /* root window that the event occurred on */
16517 	Window subwindow;	/* child window */
16518 	Time time;		/* milliseconds */
16519 	int x, y;		/* pointer x, y coordinates in event window */
16520 	int x_root, y_root;	/* coordinates relative to root */
16521 	KeyOrButtonMask state;	/* key or button mask */
16522 	uint keycode;	/* detail */
16523 	Bool same_screen;	/* same screen flag */
16524 }
16525 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16526 alias XKeyEvent XKeyPressedEvent;
16527 alias XKeyEvent XKeyReleasedEvent;
16528 
16529 struct XButtonEvent
16530 {
16531 	int type;		/* of event */
16532 	arch_ulong serial;	/* # of last request processed by server */
16533 	Bool send_event;	/* true if this came from a SendEvent request */
16534 	Display *display;	/* Display the event was read from */
16535 	Window window;	        /* "event" window it is reported relative to */
16536 	Window root;	        /* root window that the event occurred on */
16537 	Window subwindow;	/* child window */
16538 	Time time;		/* milliseconds */
16539 	int x, y;		/* pointer x, y coordinates in event window */
16540 	int x_root, y_root;	/* coordinates relative to root */
16541 	KeyOrButtonMask state;	/* key or button mask */
16542 	uint button;	/* detail */
16543 	Bool same_screen;	/* same screen flag */
16544 }
16545 alias XButtonEvent XButtonPressedEvent;
16546 alias XButtonEvent XButtonReleasedEvent;
16547 
16548 struct XMotionEvent{
16549 	int type;		/* of event */
16550 	arch_ulong serial;	/* # of last request processed by server */
16551 	Bool send_event;	/* true if this came from a SendEvent request */
16552 	Display *display;	/* Display the event was read from */
16553 	Window window;	        /* "event" window reported relative to */
16554 	Window root;	        /* root window that the event occurred on */
16555 	Window subwindow;	/* child window */
16556 	Time time;		/* milliseconds */
16557 	int x, y;		/* pointer x, y coordinates in event window */
16558 	int x_root, y_root;	/* coordinates relative to root */
16559 	KeyOrButtonMask state;	/* key or button mask */
16560 	byte is_hint;		/* detail */
16561 	Bool same_screen;	/* same screen flag */
16562 }
16563 alias XMotionEvent XPointerMovedEvent;
16564 
16565 struct XCrossingEvent{
16566 	int type;		/* of event */
16567 	arch_ulong serial;	/* # of last request processed by server */
16568 	Bool send_event;	/* true if this came from a SendEvent request */
16569 	Display *display;	/* Display the event was read from */
16570 	Window window;	        /* "event" window reported relative to */
16571 	Window root;	        /* root window that the event occurred on */
16572 	Window subwindow;	/* child window */
16573 	Time time;		/* milliseconds */
16574 	int x, y;		/* pointer x, y coordinates in event window */
16575 	int x_root, y_root;	/* coordinates relative to root */
16576 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16577 	NotifyDetail detail;
16578 	/*
16579 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16580 	 * NotifyNonlinear,NotifyNonlinearVirtual
16581 	 */
16582 	Bool same_screen;	/* same screen flag */
16583 	Bool focus;		/* Boolean focus */
16584 	KeyOrButtonMask state;	/* key or button mask */
16585 }
16586 alias XCrossingEvent XEnterWindowEvent;
16587 alias XCrossingEvent XLeaveWindowEvent;
16588 
16589 struct XFocusChangeEvent{
16590 	int type;		/* FocusIn or FocusOut */
16591 	arch_ulong serial;	/* # of last request processed by server */
16592 	Bool send_event;	/* true if this came from a SendEvent request */
16593 	Display *display;	/* Display the event was read from */
16594 	Window window;		/* window of event */
16595 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16596 				   NotifyGrab, NotifyUngrab */
16597 	NotifyDetail detail;
16598 	/*
16599 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16600 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16601 	 * NotifyPointerRoot, NotifyDetailNone
16602 	 */
16603 }
16604 alias XFocusChangeEvent XFocusInEvent;
16605 alias XFocusChangeEvent XFocusOutEvent;
16606 
16607 enum CWBackPixmap              = (1L<<0);
16608 enum CWBackPixel               = (1L<<1);
16609 enum CWBorderPixmap            = (1L<<2);
16610 enum CWBorderPixel             = (1L<<3);
16611 enum CWBitGravity              = (1L<<4);
16612 enum CWWinGravity              = (1L<<5);
16613 enum CWBackingStore            = (1L<<6);
16614 enum CWBackingPlanes           = (1L<<7);
16615 enum CWBackingPixel            = (1L<<8);
16616 enum CWOverrideRedirect        = (1L<<9);
16617 enum CWSaveUnder               = (1L<<10);
16618 enum CWEventMask               = (1L<<11);
16619 enum CWDontPropagate           = (1L<<12);
16620 enum CWColormap                = (1L<<13);
16621 enum CWCursor                  = (1L<<14);
16622 
16623 struct XWindowAttributes {
16624 	int x, y;			/* location of window */
16625 	int width, height;		/* width and height of window */
16626 	int border_width;		/* border width of window */
16627 	int depth;			/* depth of window */
16628 	Visual *visual;			/* the associated visual structure */
16629 	Window root;			/* root of screen containing window */
16630 	int class_;			/* InputOutput, InputOnly*/
16631 	int bit_gravity;		/* one of the bit gravity values */
16632 	int win_gravity;		/* one of the window gravity values */
16633 	int backing_store;		/* NotUseful, WhenMapped, Always */
16634 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16635 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16636 	Bool save_under;		/* boolean, should bits under be saved? */
16637 	Colormap colormap;		/* color map to be associated with window */
16638 	Bool map_installed;		/* boolean, is color map currently installed*/
16639 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16640 	arch_long all_event_masks;		/* set of events all people have interest in*/
16641 	arch_long your_event_mask;		/* my event mask */
16642 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16643 	Bool override_redirect;		/* boolean value for override-redirect */
16644 	Screen *screen;			/* back pointer to correct screen */
16645 }
16646 
16647 enum IsUnmapped = 0;
16648 enum IsUnviewable = 1;
16649 enum IsViewable = 2;
16650 
16651 struct XSetWindowAttributes {
16652 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16653 	arch_ulong background_pixel;/* background pixel */
16654 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16655 	arch_ulong border_pixel;/* border pixel value */
16656 	int bit_gravity;         /* one of bit gravity values */
16657 	int win_gravity;         /* one of the window gravity values */
16658 	int backing_store;       /* NotUseful, WhenMapped, Always */
16659 	arch_ulong backing_planes;/* planes to be preserved if possible */
16660 	arch_ulong backing_pixel;/* value to use in restoring planes */
16661 	Bool save_under;         /* should bits under be saved? (popups) */
16662 	arch_long event_mask;         /* set of events that should be saved */
16663 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16664 	Bool override_redirect;  /* boolean value for override_redirect */
16665 	Colormap colormap;       /* color map to be associated with window */
16666 	Cursor cursor;           /* cursor to be displayed (or None) */
16667 }
16668 
16669 
16670 alias int Status;
16671 
16672 
16673 enum EventMask:int
16674 {
16675 	NoEventMask				=0,
16676 	KeyPressMask			=1<<0,
16677 	KeyReleaseMask			=1<<1,
16678 	ButtonPressMask			=1<<2,
16679 	ButtonReleaseMask		=1<<3,
16680 	EnterWindowMask			=1<<4,
16681 	LeaveWindowMask			=1<<5,
16682 	PointerMotionMask		=1<<6,
16683 	PointerMotionHintMask	=1<<7,
16684 	Button1MotionMask		=1<<8,
16685 	Button2MotionMask		=1<<9,
16686 	Button3MotionMask		=1<<10,
16687 	Button4MotionMask		=1<<11,
16688 	Button5MotionMask		=1<<12,
16689 	ButtonMotionMask		=1<<13,
16690 	KeymapStateMask		=1<<14,
16691 	ExposureMask			=1<<15,
16692 	VisibilityChangeMask	=1<<16,
16693 	StructureNotifyMask		=1<<17,
16694 	ResizeRedirectMask		=1<<18,
16695 	SubstructureNotifyMask	=1<<19,
16696 	SubstructureRedirectMask=1<<20,
16697 	FocusChangeMask			=1<<21,
16698 	PropertyChangeMask		=1<<22,
16699 	ColormapChangeMask		=1<<23,
16700 	OwnerGrabButtonMask		=1<<24
16701 }
16702 
16703 struct MwmHints {
16704 	c_ulong flags;
16705 	c_ulong functions;
16706 	c_ulong decorations;
16707 	c_long input_mode;
16708 	c_ulong status;
16709 }
16710 
16711 enum {
16712 	MWM_HINTS_FUNCTIONS = (1L << 0),
16713 	MWM_HINTS_DECORATIONS =  (1L << 1),
16714 
16715 	MWM_FUNC_ALL = (1L << 0),
16716 	MWM_FUNC_RESIZE = (1L << 1),
16717 	MWM_FUNC_MOVE = (1L << 2),
16718 	MWM_FUNC_MINIMIZE = (1L << 3),
16719 	MWM_FUNC_MAXIMIZE = (1L << 4),
16720 	MWM_FUNC_CLOSE = (1L << 5),
16721 
16722 	MWM_DECOR_ALL = (1L << 0),
16723 	MWM_DECOR_BORDER = (1L << 1),
16724 	MWM_DECOR_RESIZEH = (1L << 2),
16725 	MWM_DECOR_TITLE = (1L << 3),
16726 	MWM_DECOR_MENU = (1L << 4),
16727 	MWM_DECOR_MINIMIZE = (1L << 5),
16728 	MWM_DECOR_MAXIMIZE = (1L << 6),
16729 }
16730 
16731 import core.stdc.config : c_long, c_ulong;
16732 
16733 	/* Size hints mask bits */
16734 
16735 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16736 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16737 	enum   PPosition   = (1L << 2)          /* program specified position */;
16738 	enum   PSize       = (1L << 3)          /* program specified size */;
16739 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16740 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16741 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16742 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16743 	enum   PBaseSize   = (1L << 8);
16744 	enum   PWinGravity = (1L << 9);
16745 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16746 	struct XSizeHints {
16747 		arch_long flags;         /* marks which fields in this structure are defined */
16748 		int x, y;           /* Obsolete */
16749 		int width, height;  /* Obsolete */
16750 		int min_width, min_height;
16751 		int max_width, max_height;
16752 		int width_inc, height_inc;
16753 		struct Aspect {
16754 			int x;       /* numerator */
16755 			int y;       /* denominator */
16756 		}
16757 
16758 		Aspect min_aspect;
16759 		Aspect max_aspect;
16760 		int base_width, base_height;
16761 		int win_gravity;
16762 		/* this structure may be extended in the future */
16763 	}
16764 
16765 
16766 
16767 enum EventType:int
16768 {
16769 	KeyPress			=2,
16770 	KeyRelease			=3,
16771 	ButtonPress			=4,
16772 	ButtonRelease		=5,
16773 	MotionNotify		=6,
16774 	EnterNotify			=7,
16775 	LeaveNotify			=8,
16776 	FocusIn				=9,
16777 	FocusOut			=10,
16778 	KeymapNotify		=11,
16779 	Expose				=12,
16780 	GraphicsExpose		=13,
16781 	NoExpose			=14,
16782 	VisibilityNotify	=15,
16783 	CreateNotify		=16,
16784 	DestroyNotify		=17,
16785 	UnmapNotify		=18,
16786 	MapNotify			=19,
16787 	MapRequest			=20,
16788 	ReparentNotify		=21,
16789 	ConfigureNotify		=22,
16790 	ConfigureRequest	=23,
16791 	GravityNotify		=24,
16792 	ResizeRequest		=25,
16793 	CirculateNotify		=26,
16794 	CirculateRequest	=27,
16795 	PropertyNotify		=28,
16796 	SelectionClear		=29,
16797 	SelectionRequest	=30,
16798 	SelectionNotify		=31,
16799 	ColormapNotify		=32,
16800 	ClientMessage		=33,
16801 	MappingNotify		=34,
16802 	LASTEvent			=35	/* must be bigger than any event # */
16803 }
16804 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16805 struct XKeymapEvent
16806 {
16807 	int type;
16808 	arch_ulong serial;	/* # of last request processed by server */
16809 	Bool send_event;	/* true if this came from a SendEvent request */
16810 	Display *display;	/* Display the event was read from */
16811 	Window window;
16812 	byte[32] key_vector;
16813 }
16814 
16815 struct XExposeEvent
16816 {
16817 	int type;
16818 	arch_ulong serial;	/* # of last request processed by server */
16819 	Bool send_event;	/* true if this came from a SendEvent request */
16820 	Display *display;	/* Display the event was read from */
16821 	Window window;
16822 	int x, y;
16823 	int width, height;
16824 	int count;		/* if non-zero, at least this many more */
16825 }
16826 
16827 struct XGraphicsExposeEvent{
16828 	int type;
16829 	arch_ulong serial;	/* # of last request processed by server */
16830 	Bool send_event;	/* true if this came from a SendEvent request */
16831 	Display *display;	/* Display the event was read from */
16832 	Drawable drawable;
16833 	int x, y;
16834 	int width, height;
16835 	int count;		/* if non-zero, at least this many more */
16836 	int major_code;		/* core is CopyArea or CopyPlane */
16837 	int minor_code;		/* not defined in the core */
16838 }
16839 
16840 struct XNoExposeEvent{
16841 	int type;
16842 	arch_ulong serial;	/* # of last request processed by server */
16843 	Bool send_event;	/* true if this came from a SendEvent request */
16844 	Display *display;	/* Display the event was read from */
16845 	Drawable drawable;
16846 	int major_code;		/* core is CopyArea or CopyPlane */
16847 	int minor_code;		/* not defined in the core */
16848 }
16849 
16850 struct XVisibilityEvent{
16851 	int type;
16852 	arch_ulong serial;	/* # of last request processed by server */
16853 	Bool send_event;	/* true if this came from a SendEvent request */
16854 	Display *display;	/* Display the event was read from */
16855 	Window window;
16856 	VisibilityNotify state;		/* Visibility state */
16857 }
16858 
16859 struct XCreateWindowEvent{
16860 	int type;
16861 	arch_ulong serial;	/* # of last request processed by server */
16862 	Bool send_event;	/* true if this came from a SendEvent request */
16863 	Display *display;	/* Display the event was read from */
16864 	Window parent;		/* parent of the window */
16865 	Window window;		/* window id of window created */
16866 	int x, y;		/* window location */
16867 	int width, height;	/* size of window */
16868 	int border_width;	/* border width */
16869 	Bool override_redirect;	/* creation should be overridden */
16870 }
16871 
16872 struct XDestroyWindowEvent
16873 {
16874 	int type;
16875 	arch_ulong serial;		/* # of last request processed by server */
16876 	Bool send_event;	/* true if this came from a SendEvent request */
16877 	Display *display;	/* Display the event was read from */
16878 	Window event;
16879 	Window window;
16880 }
16881 
16882 struct XUnmapEvent
16883 {
16884 	int type;
16885 	arch_ulong serial;		/* # of last request processed by server */
16886 	Bool send_event;	/* true if this came from a SendEvent request */
16887 	Display *display;	/* Display the event was read from */
16888 	Window event;
16889 	Window window;
16890 	Bool from_configure;
16891 }
16892 
16893 struct XMapEvent
16894 {
16895 	int type;
16896 	arch_ulong serial;		/* # of last request processed by server */
16897 	Bool send_event;	/* true if this came from a SendEvent request */
16898 	Display *display;	/* Display the event was read from */
16899 	Window event;
16900 	Window window;
16901 	Bool override_redirect;	/* Boolean, is override set... */
16902 }
16903 
16904 struct XMapRequestEvent
16905 {
16906 	int type;
16907 	arch_ulong serial;	/* # of last request processed by server */
16908 	Bool send_event;	/* true if this came from a SendEvent request */
16909 	Display *display;	/* Display the event was read from */
16910 	Window parent;
16911 	Window window;
16912 }
16913 
16914 struct XReparentEvent
16915 {
16916 	int type;
16917 	arch_ulong serial;	/* # of last request processed by server */
16918 	Bool send_event;	/* true if this came from a SendEvent request */
16919 	Display *display;	/* Display the event was read from */
16920 	Window event;
16921 	Window window;
16922 	Window parent;
16923 	int x, y;
16924 	Bool override_redirect;
16925 }
16926 
16927 struct XConfigureEvent
16928 {
16929 	int type;
16930 	arch_ulong serial;	/* # of last request processed by server */
16931 	Bool send_event;	/* true if this came from a SendEvent request */
16932 	Display *display;	/* Display the event was read from */
16933 	Window event;
16934 	Window window;
16935 	int x, y;
16936 	int width, height;
16937 	int border_width;
16938 	Window above;
16939 	Bool override_redirect;
16940 }
16941 
16942 struct XGravityEvent
16943 {
16944 	int type;
16945 	arch_ulong serial;	/* # of last request processed by server */
16946 	Bool send_event;	/* true if this came from a SendEvent request */
16947 	Display *display;	/* Display the event was read from */
16948 	Window event;
16949 	Window window;
16950 	int x, y;
16951 }
16952 
16953 struct XResizeRequestEvent
16954 {
16955 	int type;
16956 	arch_ulong serial;	/* # of last request processed by server */
16957 	Bool send_event;	/* true if this came from a SendEvent request */
16958 	Display *display;	/* Display the event was read from */
16959 	Window window;
16960 	int width, height;
16961 }
16962 
16963 struct  XConfigureRequestEvent
16964 {
16965 	int type;
16966 	arch_ulong serial;	/* # of last request processed by server */
16967 	Bool send_event;	/* true if this came from a SendEvent request */
16968 	Display *display;	/* Display the event was read from */
16969 	Window parent;
16970 	Window window;
16971 	int x, y;
16972 	int width, height;
16973 	int border_width;
16974 	Window above;
16975 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
16976 	arch_ulong value_mask;
16977 }
16978 
16979 struct XCirculateEvent
16980 {
16981 	int type;
16982 	arch_ulong serial;	/* # of last request processed by server */
16983 	Bool send_event;	/* true if this came from a SendEvent request */
16984 	Display *display;	/* Display the event was read from */
16985 	Window event;
16986 	Window window;
16987 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16988 }
16989 
16990 struct XCirculateRequestEvent
16991 {
16992 	int type;
16993 	arch_ulong serial;	/* # of last request processed by server */
16994 	Bool send_event;	/* true if this came from a SendEvent request */
16995 	Display *display;	/* Display the event was read from */
16996 	Window parent;
16997 	Window window;
16998 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16999 }
17000 
17001 struct XPropertyEvent
17002 {
17003 	int type;
17004 	arch_ulong serial;	/* # of last request processed by server */
17005 	Bool send_event;	/* true if this came from a SendEvent request */
17006 	Display *display;	/* Display the event was read from */
17007 	Window window;
17008 	Atom atom;
17009 	Time time;
17010 	PropertyNotification state;		/* NewValue, Deleted */
17011 }
17012 
17013 struct XSelectionClearEvent
17014 {
17015 	int type;
17016 	arch_ulong serial;	/* # of last request processed by server */
17017 	Bool send_event;	/* true if this came from a SendEvent request */
17018 	Display *display;	/* Display the event was read from */
17019 	Window window;
17020 	Atom selection;
17021 	Time time;
17022 }
17023 
17024 struct XSelectionRequestEvent
17025 {
17026 	int type;
17027 	arch_ulong serial;	/* # of last request processed by server */
17028 	Bool send_event;	/* true if this came from a SendEvent request */
17029 	Display *display;	/* Display the event was read from */
17030 	Window owner;
17031 	Window requestor;
17032 	Atom selection;
17033 	Atom target;
17034 	Atom property;
17035 	Time time;
17036 }
17037 
17038 struct XSelectionEvent
17039 {
17040 	int type;
17041 	arch_ulong serial;	/* # of last request processed by server */
17042 	Bool send_event;	/* true if this came from a SendEvent request */
17043 	Display *display;	/* Display the event was read from */
17044 	Window requestor;
17045 	Atom selection;
17046 	Atom target;
17047 	Atom property;		/* ATOM or None */
17048 	Time time;
17049 }
17050 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17051 
17052 struct XColormapEvent
17053 {
17054 	int type;
17055 	arch_ulong serial;	/* # of last request processed by server */
17056 	Bool send_event;	/* true if this came from a SendEvent request */
17057 	Display *display;	/* Display the event was read from */
17058 	Window window;
17059 	Colormap colormap;	/* COLORMAP or None */
17060 	Bool new_;		/* C++ */
17061 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17062 }
17063 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17064 
17065 struct XClientMessageEvent
17066 {
17067 	int type;
17068 	arch_ulong serial;	/* # of last request processed by server */
17069 	Bool send_event;	/* true if this came from a SendEvent request */
17070 	Display *display;	/* Display the event was read from */
17071 	Window window;
17072 	Atom message_type;
17073 	int format;
17074 	union Data{
17075 		byte[20] b;
17076 		short[10] s;
17077 		arch_ulong[5] l;
17078 	}
17079 	Data data;
17080 
17081 }
17082 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17083 
17084 struct XMappingEvent
17085 {
17086 	int type;
17087 	arch_ulong serial;	/* # of last request processed by server */
17088 	Bool send_event;	/* true if this came from a SendEvent request */
17089 	Display *display;	/* Display the event was read from */
17090 	Window window;		/* unused */
17091 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17092 				   MappingPointer */
17093 	int first_keycode;	/* first keycode */
17094 	int count;		/* defines range of change w. first_keycode*/
17095 }
17096 
17097 struct XErrorEvent
17098 {
17099 	int type;
17100 	Display *display;	/* Display the event was read from */
17101 	XID resourceid;		/* resource id */
17102 	arch_ulong serial;	/* serial number of failed request */
17103 	ubyte error_code;	/* error code of failed request */
17104 	ubyte request_code;	/* Major op-code of failed request */
17105 	ubyte minor_code;	/* Minor op-code of failed request */
17106 }
17107 
17108 struct XAnyEvent
17109 {
17110 	int type;
17111 	arch_ulong serial;	/* # of last request processed by server */
17112 	Bool send_event;	/* true if this came from a SendEvent request */
17113 	Display *display;/* Display the event was read from */
17114 	Window window;	/* window on which event was requested in event mask */
17115 }
17116 
17117 union XEvent{
17118 	int type;		/* must not be changed; first element */
17119 	XAnyEvent xany;
17120 	XKeyEvent xkey;
17121 	XButtonEvent xbutton;
17122 	XMotionEvent xmotion;
17123 	XCrossingEvent xcrossing;
17124 	XFocusChangeEvent xfocus;
17125 	XExposeEvent xexpose;
17126 	XGraphicsExposeEvent xgraphicsexpose;
17127 	XNoExposeEvent xnoexpose;
17128 	XVisibilityEvent xvisibility;
17129 	XCreateWindowEvent xcreatewindow;
17130 	XDestroyWindowEvent xdestroywindow;
17131 	XUnmapEvent xunmap;
17132 	XMapEvent xmap;
17133 	XMapRequestEvent xmaprequest;
17134 	XReparentEvent xreparent;
17135 	XConfigureEvent xconfigure;
17136 	XGravityEvent xgravity;
17137 	XResizeRequestEvent xresizerequest;
17138 	XConfigureRequestEvent xconfigurerequest;
17139 	XCirculateEvent xcirculate;
17140 	XCirculateRequestEvent xcirculaterequest;
17141 	XPropertyEvent xproperty;
17142 	XSelectionClearEvent xselectionclear;
17143 	XSelectionRequestEvent xselectionrequest;
17144 	XSelectionEvent xselection;
17145 	XColormapEvent xcolormap;
17146 	XClientMessageEvent xclient;
17147 	XMappingEvent xmapping;
17148 	XErrorEvent xerror;
17149 	XKeymapEvent xkeymap;
17150 	arch_ulong[24] pad;
17151 }
17152 
17153 
17154 	struct Display {
17155 		XExtData *ext_data;	/* hook for extension to hang data */
17156 		_XPrivate *private1;
17157 		int fd;			/* Network socket. */
17158 		int private2;
17159 		int proto_major_version;/* major version of server's X protocol */
17160 		int proto_minor_version;/* minor version of servers X protocol */
17161 		char *vendor;		/* vendor of the server hardware */
17162 	    	XID private3;
17163 		XID private4;
17164 		XID private5;
17165 		int private6;
17166 		XID function(Display*)resource_alloc;/* allocator function */
17167 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17168 		int bitmap_unit;	/* padding and data requirements */
17169 		int bitmap_pad;		/* padding requirements on bitmaps */
17170 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17171 		int nformats;		/* number of pixmap formats in list */
17172 		ScreenFormat *pixmap_format;	/* pixmap format list */
17173 		int private8;
17174 		int release;		/* release of the server */
17175 		_XPrivate *private9;
17176 		_XPrivate *private10;
17177 		int qlen;		/* Length of input event queue */
17178 		arch_ulong last_request_read; /* seq number of last event read */
17179 		arch_ulong request;	/* sequence number of last request. */
17180 		XPointer private11;
17181 		XPointer private12;
17182 		XPointer private13;
17183 		XPointer private14;
17184 		uint max_request_size; /* maximum number 32 bit words in request*/
17185 		_XrmHashBucketRec *db;
17186 		int function  (Display*)private15;
17187 		char *display_name;	/* "host:display" string used on this connect*/
17188 		int default_screen;	/* default screen for operations */
17189 		int nscreens;		/* number of screens on this server*/
17190 		Screen *screens;	/* pointer to list of screens */
17191 		arch_ulong motion_buffer;	/* size of motion buffer */
17192 		arch_ulong private16;
17193 		int min_keycode;	/* minimum defined keycode */
17194 		int max_keycode;	/* maximum defined keycode */
17195 		XPointer private17;
17196 		XPointer private18;
17197 		int private19;
17198 		byte *xdefaults;	/* contents of defaults from server */
17199 		/* there is more to this structure, but it is private to Xlib */
17200 	}
17201 
17202 	// I got these numbers from a C program as a sanity test
17203 	version(X86_64) {
17204 		static assert(Display.sizeof == 296);
17205 		static assert(XPointer.sizeof == 8);
17206 		static assert(XErrorEvent.sizeof == 40);
17207 		static assert(XAnyEvent.sizeof == 40);
17208 		static assert(XMappingEvent.sizeof == 56);
17209 		static assert(XEvent.sizeof == 192);
17210     	} else version (AArch64) {
17211         	// omit check for aarch64
17212 	} else {
17213 		static assert(Display.sizeof == 176);
17214 		static assert(XPointer.sizeof == 4);
17215 		static assert(XEvent.sizeof == 96);
17216 	}
17217 
17218 struct Depth
17219 {
17220 	int depth;		/* this depth (Z) of the depth */
17221 	int nvisuals;		/* number of Visual types at this depth */
17222 	Visual *visuals;	/* list of visuals possible at this depth */
17223 }
17224 
17225 alias void* GC;
17226 alias c_ulong VisualID;
17227 alias XID Colormap;
17228 alias XID Cursor;
17229 alias XID KeySym;
17230 alias uint KeyCode;
17231 enum None = 0;
17232 }
17233 
17234 version(without_opengl) {}
17235 else {
17236 extern(C) nothrow @nogc {
17237 
17238 
17239 static if(!SdpyIsUsingIVGLBinds) {
17240 enum GLX_USE_GL=            1;       /* support GLX rendering */
17241 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17242 enum GLX_LEVEL=             3;       /* level in plane stacking */
17243 enum GLX_RGBA=              4;       /* true if RGBA mode */
17244 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17245 enum GLX_STEREO=            6;       /* stereo buffering supported */
17246 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17247 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17248 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17249 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17250 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17251 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17252 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17253 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17254 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17255 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17256 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17257 
17258 
17259 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17260 
17261 
17262 
17263 enum GL_TRUE = 1;
17264 enum GL_FALSE = 0;
17265 alias int GLint;
17266 }
17267 
17268 alias XID GLXContextID;
17269 alias XID GLXPixmap;
17270 alias XID GLXDrawable;
17271 alias XID GLXPbuffer;
17272 alias XID GLXWindow;
17273 alias XID GLXFBConfigID;
17274 alias void* GLXContext;
17275 
17276 }
17277 }
17278 
17279 enum AllocNone = 0;
17280 
17281 extern(C) {
17282 	/* WARNING, this type not in Xlib spec */
17283 	extern(C) alias XIOErrorHandler = int function (Display* display);
17284 }
17285 
17286 extern(C) nothrow
17287 alias XErrorHandler = int function(Display*, XErrorEvent*);
17288 
17289 extern(C) nothrow @nogc {
17290 struct Screen{
17291 	XExtData *ext_data;		/* hook for extension to hang data */
17292 	Display *display;		/* back pointer to display structure */
17293 	Window root;			/* Root window id. */
17294 	int width, height;		/* width and height of screen */
17295 	int mwidth, mheight;	/* width and height of  in millimeters */
17296 	int ndepths;			/* number of depths possible */
17297 	Depth *depths;			/* list of allowable depths on the screen */
17298 	int root_depth;			/* bits per pixel */
17299 	Visual *root_visual;	/* root visual */
17300 	GC default_gc;			/* GC for the root root visual */
17301 	Colormap cmap;			/* default color map */
17302 	uint white_pixel;
17303 	uint black_pixel;		/* White and Black pixel values */
17304 	int max_maps, min_maps;	/* max and min color maps */
17305 	int backing_store;		/* Never, WhenMapped, Always */
17306 	bool save_unders;
17307 	int root_input_mask;	/* initial root input mask */
17308 }
17309 
17310 struct Visual
17311 {
17312 	XExtData *ext_data;	/* hook for extension to hang data */
17313 	VisualID visualid;	/* visual id of this visual */
17314 	int class_;			/* class of screen (monochrome, etc.) */
17315 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17316 	int bits_per_rgb;	/* log base 2 of distinct color values */
17317 	int map_entries;	/* color map entries */
17318 }
17319 
17320 	alias Display* _XPrivDisplay;
17321 
17322 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17323 		assert(dpy !is null);
17324 		return &dpy.screens[scr];
17325 	}
17326 
17327 	extern(D) Window RootWindow(Display *dpy,int scr) {
17328 		return ScreenOfDisplay(dpy,scr).root;
17329 	}
17330 
17331 	struct XWMHints {
17332 		arch_long flags;
17333 		Bool input;
17334 		int initial_state;
17335 		Pixmap icon_pixmap;
17336 		Window icon_window;
17337 		int icon_x, icon_y;
17338 		Pixmap icon_mask;
17339 		XID window_group;
17340 	}
17341 
17342 	struct XClassHint {
17343 		char* res_name;
17344 		char* res_class;
17345 	}
17346 
17347 	extern(D) int DefaultScreen(Display *dpy) {
17348 		return dpy.default_screen;
17349 	}
17350 
17351 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17352 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17353 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17354 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17355 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17356 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17357 
17358 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17359 
17360 	enum int AnyPropertyType = 0;
17361 	enum int Success = 0;
17362 
17363 	enum int RevertToNone = None;
17364 	enum int PointerRoot = 1;
17365 	enum Time CurrentTime = 0;
17366 	enum int RevertToPointerRoot = PointerRoot;
17367 	enum int RevertToParent = 2;
17368 
17369 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17370 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17371 	}
17372 
17373 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17374 		return ScreenOfDisplay(dpy,scr).root_visual;
17375 	}
17376 
17377 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17378 		return ScreenOfDisplay(dpy,scr).default_gc;
17379 	}
17380 
17381 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17382 		return ScreenOfDisplay(dpy,scr).black_pixel;
17383 	}
17384 
17385 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17386 		return ScreenOfDisplay(dpy,scr).white_pixel;
17387 	}
17388 
17389 	alias void* XFontSet; // i think
17390 	struct XmbTextItem {
17391 		char* chars;
17392 		int nchars;
17393 		int delta;
17394 		XFontSet font_set;
17395 	}
17396 
17397 	struct XTextItem {
17398 		char* chars;
17399 		int nchars;
17400 		int delta;
17401 		Font font;
17402 	}
17403 
17404 	enum {
17405 		GXclear        = 0x0, /* 0 */
17406 		GXand          = 0x1, /* src AND dst */
17407 		GXandReverse   = 0x2, /* src AND NOT dst */
17408 		GXcopy         = 0x3, /* src */
17409 		GXandInverted  = 0x4, /* NOT src AND dst */
17410 		GXnoop         = 0x5, /* dst */
17411 		GXxor          = 0x6, /* src XOR dst */
17412 		GXor           = 0x7, /* src OR dst */
17413 		GXnor          = 0x8, /* NOT src AND NOT dst */
17414 		GXequiv        = 0x9, /* NOT src XOR dst */
17415 		GXinvert       = 0xa, /* NOT dst */
17416 		GXorReverse    = 0xb, /* src OR NOT dst */
17417 		GXcopyInverted = 0xc, /* NOT src */
17418 		GXorInverted   = 0xd, /* NOT src OR dst */
17419 		GXnand         = 0xe, /* NOT src OR NOT dst */
17420 		GXset          = 0xf, /* 1 */
17421 	}
17422 	enum QueueMode : int {
17423 		QueuedAlready,
17424 		QueuedAfterReading,
17425 		QueuedAfterFlush
17426 	}
17427 
17428 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17429 
17430 	struct XPoint {
17431 		short x;
17432 		short y;
17433 	}
17434 
17435 	enum CoordMode:int {
17436 		CoordModeOrigin = 0,
17437 		CoordModePrevious = 1
17438 	}
17439 
17440 	enum PolygonShape:int {
17441 		Complex = 0,
17442 		Nonconvex = 1,
17443 		Convex = 2
17444 	}
17445 
17446 	struct XTextProperty {
17447 		const(char)* value;		/* same as Property routines */
17448 		Atom encoding;			/* prop type */
17449 		int format;				/* prop data format: 8, 16, or 32 */
17450 		arch_ulong nitems;		/* number of data items in value */
17451 	}
17452 
17453 	version( X86_64 ) {
17454 		static assert(XTextProperty.sizeof == 32);
17455 	}
17456 
17457 
17458 	struct XGCValues {
17459 		int function_;           /* logical operation */
17460 		arch_ulong plane_mask;/* plane mask */
17461 		arch_ulong foreground;/* foreground pixel */
17462 		arch_ulong background;/* background pixel */
17463 		int line_width;         /* line width */
17464 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17465 		int cap_style;          /* CapNotLast, CapButt,
17466 					   CapRound, CapProjecting */
17467 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17468 		int fill_style;         /* FillSolid, FillTiled,
17469 					   FillStippled, FillOpaeueStippled */
17470 		int fill_rule;          /* EvenOddRule, WindingRule */
17471 		int arc_mode;           /* ArcChord, ArcPieSlice */
17472 		Pixmap tile;            /* tile pixmap for tiling operations */
17473 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17474 		int ts_x_origin;        /* offset for tile or stipple operations */
17475 		int ts_y_origin;
17476 		Font font;              /* default text font for text operations */
17477 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17478 		Bool graphics_exposures;/* boolean, should exposures be generated */
17479 		int clip_x_origin;      /* origin for clipping */
17480 		int clip_y_origin;
17481 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17482 		int dash_offset;        /* patterned/dashed line information */
17483 		char dashes;
17484 	}
17485 
17486 	struct XColor {
17487 		arch_ulong pixel;
17488 		ushort red, green, blue;
17489 		byte flags;
17490 		byte pad;
17491 	}
17492 
17493 	struct XRectangle {
17494 		short x;
17495 		short y;
17496 		ushort width;
17497 		ushort height;
17498 	}
17499 
17500 	enum ClipByChildren = 0;
17501 	enum IncludeInferiors = 1;
17502 
17503 	enum Atom XA_PRIMARY = 1;
17504 	enum Atom XA_SECONDARY = 2;
17505 	enum Atom XA_STRING = 31;
17506 	enum Atom XA_CARDINAL = 6;
17507 	enum Atom XA_WM_NAME = 39;
17508 	enum Atom XA_ATOM = 4;
17509 	enum Atom XA_WINDOW = 33;
17510 	enum Atom XA_WM_HINTS = 35;
17511 	enum int PropModeAppend = 2;
17512 	enum int PropModeReplace = 0;
17513 	enum int PropModePrepend = 1;
17514 
17515 	enum int CopyFromParent = 0;
17516 	enum int InputOutput = 1;
17517 
17518 	// XWMHints
17519 	enum InputHint = 1 << 0;
17520 	enum StateHint = 1 << 1;
17521 	enum IconPixmapHint = (1L << 2);
17522 	enum IconWindowHint = (1L << 3);
17523 	enum IconPositionHint = (1L << 4);
17524 	enum IconMaskHint = (1L << 5);
17525 	enum WindowGroupHint = (1L << 6);
17526 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17527 	enum XUrgencyHint = (1L << 8);
17528 
17529 	// GC Components
17530 	enum GCFunction           =   (1L<<0);
17531 	enum GCPlaneMask         =    (1L<<1);
17532 	enum GCForeground       =     (1L<<2);
17533 	enum GCBackground      =      (1L<<3);
17534 	enum GCLineWidth      =       (1L<<4);
17535 	enum GCLineStyle     =        (1L<<5);
17536 	enum GCCapStyle     =         (1L<<6);
17537 	enum GCJoinStyle   =          (1L<<7);
17538 	enum GCFillStyle  =           (1L<<8);
17539 	enum GCFillRule  =            (1L<<9);
17540 	enum GCTile     =             (1L<<10);
17541 	enum GCStipple           =    (1L<<11);
17542 	enum GCTileStipXOrigin  =     (1L<<12);
17543 	enum GCTileStipYOrigin =      (1L<<13);
17544 	enum GCFont               =   (1L<<14);
17545 	enum GCSubwindowMode     =    (1L<<15);
17546 	enum GCGraphicsExposures=     (1L<<16);
17547 	enum GCClipXOrigin     =      (1L<<17);
17548 	enum GCClipYOrigin    =       (1L<<18);
17549 	enum GCClipMask      =        (1L<<19);
17550 	enum GCDashOffset   =         (1L<<20);
17551 	enum GCDashList    =          (1L<<21);
17552 	enum GCArcMode    =           (1L<<22);
17553 	enum GCLastBit   =            22;
17554 
17555 
17556 	enum int WithdrawnState = 0;
17557 	enum int NormalState = 1;
17558 	enum int IconicState = 3;
17559 
17560 }
17561 } else version (OSXCocoa) {
17562 private:
17563 	alias void* id;
17564 	alias void* Class;
17565 	alias void* SEL;
17566 	alias void* IMP;
17567 	alias void* Ivar;
17568 	alias byte BOOL;
17569 	alias const(void)* CFStringRef;
17570 	alias const(void)* CFAllocatorRef;
17571 	alias const(void)* CFTypeRef;
17572 	alias const(void)* CGContextRef;
17573 	alias const(void)* CGColorSpaceRef;
17574 	alias const(void)* CGImageRef;
17575 	alias ulong CGBitmapInfo;
17576 
17577 	struct objc_super {
17578 		id self;
17579 		Class superclass;
17580 	}
17581 
17582 	struct CFRange {
17583 		long location, length;
17584 	}
17585 
17586 	struct NSPoint {
17587 		double x, y;
17588 
17589 		static fromTuple(T)(T tupl) {
17590 			return NSPoint(tupl.tupleof);
17591 		}
17592 	}
17593 	struct NSSize {
17594 		double width, height;
17595 	}
17596 	struct NSRect {
17597 		NSPoint origin;
17598 		NSSize size;
17599 	}
17600 	alias NSPoint CGPoint;
17601 	alias NSSize CGSize;
17602 	alias NSRect CGRect;
17603 
17604 	struct CGAffineTransform {
17605 		double a, b, c, d, tx, ty;
17606 	}
17607 
17608 	enum NSApplicationActivationPolicyRegular = 0;
17609 	enum NSBackingStoreBuffered = 2;
17610 	enum kCFStringEncodingUTF8 = 0x08000100;
17611 
17612 	enum : size_t {
17613 		NSBorderlessWindowMask = 0,
17614 		NSTitledWindowMask = 1 << 0,
17615 		NSClosableWindowMask = 1 << 1,
17616 		NSMiniaturizableWindowMask = 1 << 2,
17617 		NSResizableWindowMask = 1 << 3,
17618 		NSTexturedBackgroundWindowMask = 1 << 8
17619 	}
17620 
17621 	enum : ulong {
17622 		kCGImageAlphaNone,
17623 		kCGImageAlphaPremultipliedLast,
17624 		kCGImageAlphaPremultipliedFirst,
17625 		kCGImageAlphaLast,
17626 		kCGImageAlphaFirst,
17627 		kCGImageAlphaNoneSkipLast,
17628 		kCGImageAlphaNoneSkipFirst
17629 	}
17630 	enum : ulong {
17631 		kCGBitmapAlphaInfoMask = 0x1F,
17632 		kCGBitmapFloatComponents = (1 << 8),
17633 		kCGBitmapByteOrderMask = 0x7000,
17634 		kCGBitmapByteOrderDefault = (0 << 12),
17635 		kCGBitmapByteOrder16Little = (1 << 12),
17636 		kCGBitmapByteOrder32Little = (2 << 12),
17637 		kCGBitmapByteOrder16Big = (3 << 12),
17638 		kCGBitmapByteOrder32Big = (4 << 12)
17639 	}
17640 	enum CGPathDrawingMode {
17641 		kCGPathFill,
17642 		kCGPathEOFill,
17643 		kCGPathStroke,
17644 		kCGPathFillStroke,
17645 		kCGPathEOFillStroke
17646 	}
17647 	enum objc_AssociationPolicy : size_t {
17648 		OBJC_ASSOCIATION_ASSIGN = 0,
17649 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
17650 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
17651 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
17652 		OBJC_ASSOCIATION_COPY = 0x303 //01403
17653 	}
17654 
17655 	extern(C) {
17656 		id objc_msgSend(id receiver, SEL selector, ...);
17657 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
17658 		id objc_getClass(const(char)* name);
17659 		SEL sel_registerName(const(char)* str);
17660 		Class objc_allocateClassPair(Class superclass, const(char)* name,
17661 									 size_t extra_bytes);
17662 		void objc_registerClassPair(Class cls);
17663 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
17664 		id objc_getAssociatedObject(id object, void* key);
17665 		void objc_setAssociatedObject(id object, void* key, id value,
17666 									  objc_AssociationPolicy policy);
17667 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
17668 		id object_getIvar(id object, Ivar ivar);
17669 		void object_setIvar(id object, Ivar ivar, id value);
17670 		BOOL class_addIvar(Class cls, const(char)* name,
17671 						   size_t size, ubyte alignment, const(char)* types);
17672 
17673 		extern __gshared id NSApp;
17674 
17675 		void CFRelease(CFTypeRef obj);
17676 
17677 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
17678 											const(char)* bytes, long numBytes,
17679 											long encoding,
17680 											BOOL isExternalRepresentation);
17681 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
17682 							 char lossByte, bool isExternalRepresentation,
17683 							 char* buffer, long maxBufLen, long* usedBufLen);
17684 		long CFStringGetLength(CFStringRef theString);
17685 
17686 		CGContextRef CGBitmapContextCreate(void* data,
17687 										   size_t width, size_t height,
17688 										   size_t bitsPerComponent,
17689 										   size_t bytesPerRow,
17690 										   CGColorSpaceRef colorspace,
17691 										   CGBitmapInfo bitmapInfo);
17692 		void CGContextRelease(CGContextRef c);
17693 		ubyte* CGBitmapContextGetData(CGContextRef c);
17694 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
17695 		size_t CGBitmapContextGetWidth(CGContextRef c);
17696 		size_t CGBitmapContextGetHeight(CGContextRef c);
17697 
17698 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
17699 		void CGColorSpaceRelease(CGColorSpaceRef cs);
17700 
17701 		void CGContextSetRGBStrokeColor(CGContextRef c,
17702 										double red, double green, double blue,
17703 										double alpha);
17704 		void CGContextSetRGBFillColor(CGContextRef c,
17705 									  double red, double green, double blue,
17706 									  double alpha);
17707 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
17708 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
17709 									  const(char)* str, size_t length);
17710 		void CGContextStrokeLineSegments(CGContextRef c,
17711 										 const(CGPoint)* points, size_t count);
17712 
17713 		void CGContextBeginPath(CGContextRef c);
17714 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
17715 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
17716 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
17717 							 double startAngle, double endAngle, long clockwise);
17718 		void CGContextAddRect(CGContextRef c, CGRect rect);
17719 		void CGContextAddLines(CGContextRef c,
17720 							   const(CGPoint)* points, size_t count);
17721 		void CGContextSaveGState(CGContextRef c);
17722 		void CGContextRestoreGState(CGContextRef c);
17723 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
17724 								 ulong textEncoding);
17725 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
17726 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
17727 
17728 		void CGImageRelease(CGImageRef image);
17729 	}
17730 
17731 private:
17732     // A convenient method to create a CFString (=NSString) from a D string.
17733     CFStringRef createCFString(string str) {
17734         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
17735                                              kCFStringEncodingUTF8, false);
17736     }
17737 
17738     // Objective-C calls.
17739     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
17740         auto _cmd = sel_registerName(selector.ptr);
17741         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17742         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
17743     }
17744     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
17745         auto _cmd = sel_registerName(selector.ptr);
17746         auto cls = objc_getClass(className);
17747         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17748         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
17749     }
17750     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
17751         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
17752     }
17753 
17754     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
17755     alias objc_msgSend_classMethod!("alloc", id) alloc;
17756     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
17757                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
17758     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
17759     alias objc_msgSend_specialized!("center", void) center;
17760     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
17761     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
17762     alias objc_msgSend_specialized!("release", void) release;
17763     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
17764     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
17765     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
17766     alias objc_msgSend_specialized!("invalidate", void) invalidate;
17767     alias objc_msgSend_specialized!("close", void) close;
17768     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
17769                                     id, double, id, SEL, id, BOOL) scheduledTimer;
17770     alias objc_msgSend_specialized!("run", void) run;
17771     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
17772                                     id) currentNSGraphicsContext;
17773     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
17774     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
17775     alias objc_msgSend_specialized!("superclass", Class) superclass;
17776     alias objc_msgSend_specialized!("init", id) init;
17777     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
17778     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
17779     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
17780                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
17781     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
17782     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
17783     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
17784                                     void, BOOL) activateIgnoringOtherApps;
17785     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
17786                                     id) sharedNSApplication;
17787     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
17788 } else static assert(0, "Unsupported operating system");
17789 
17790 
17791 version(OSXCocoa) {
17792 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
17793 	//
17794 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
17795 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
17796 	//
17797 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
17798 	// Probably won't even fully compile right now
17799 
17800     import std.math : PI; // OSX Only
17801     import std.algorithm : map; // OSX Only
17802     import std.array : array; // OSX Only
17803 
17804     alias SimpleWindow NativeWindowHandle;
17805     alias void delegate(id) NativeEventHandler;
17806 
17807     __gshared Ivar simpleWindowIvar;
17808 
17809     enum KEY_ESCAPE = 27;
17810 
17811     mixin template NativeImageImplementation() {
17812         CGContextRef context;
17813         ubyte* rawData;
17814     final:
17815 
17816 	void convertToRgbaBytes(ubyte[] where) {
17817 		assert(where.length == this.width * this.height * 4);
17818 
17819 		// if rawData had a length....
17820 		//assert(rawData.length == where.length);
17821 		for(long idx = 0; idx < where.length; idx += 4) {
17822 			auto alpha = rawData[idx + 3];
17823 			if(alpha == 255) {
17824 				where[idx + 0] = rawData[idx + 0]; // r
17825 				where[idx + 1] = rawData[idx + 1]; // g
17826 				where[idx + 2] = rawData[idx + 2]; // b
17827 				where[idx + 3] = rawData[idx + 3]; // a
17828 			} else {
17829 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
17830 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
17831 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
17832 				where[idx + 3] = rawData[idx + 3]; // a
17833 
17834 			}
17835 		}
17836 	}
17837 
17838 	void setFromRgbaBytes(in ubyte[] where) {
17839 		// FIXME: this is probably wrong
17840 		assert(where.length == this.width * this.height * 4);
17841 
17842 		// if rawData had a length....
17843 		//assert(rawData.length == where.length);
17844 		for(long idx = 0; idx < where.length; idx += 4) {
17845 			auto alpha = rawData[idx + 3];
17846 			if(alpha == 255) {
17847 				rawData[idx + 0] = where[idx + 0]; // r
17848 				rawData[idx + 1] = where[idx + 1]; // g
17849 				rawData[idx + 2] = where[idx + 2]; // b
17850 				rawData[idx + 3] = where[idx + 3]; // a
17851 			} else {
17852 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
17853 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
17854 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
17855 				rawData[idx + 3] = where[idx + 3]; // a
17856 
17857 			}
17858 		}
17859 	}
17860 
17861 
17862         void createImage(int width, int height, bool forcexshm=false) {
17863             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17864             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
17865                                             colorSpace,
17866                                             kCGImageAlphaPremultipliedLast
17867                                                    |kCGBitmapByteOrder32Big);
17868             CGColorSpaceRelease(colorSpace);
17869             rawData = CGBitmapContextGetData(context);
17870         }
17871         void dispose() {
17872             CGContextRelease(context);
17873         }
17874 
17875         void setPixel(int x, int y, Color c) {
17876             auto offset = (y * width + x) * 4;
17877             if (c.a == 255) {
17878                 rawData[offset + 0] = c.r;
17879                 rawData[offset + 1] = c.g;
17880                 rawData[offset + 2] = c.b;
17881                 rawData[offset + 3] = c.a;
17882             } else {
17883                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
17884                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
17885                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
17886                 rawData[offset + 3] = c.a;
17887             }
17888         }
17889     }
17890 
17891     mixin template NativeScreenPainterImplementation() {
17892         CGContextRef context;
17893         ubyte[4] _outlineComponents;
17894 	id view;
17895 
17896         void create(NativeWindowHandle window) {
17897             context = window.drawingContext;
17898 	    view = window.view;
17899         }
17900 
17901         void dispose() {
17902             	setNeedsDisplay(view, true);
17903         }
17904 
17905 	bool manualInvalidations;
17906 	void invalidateRect(Rectangle invalidRect) { }
17907 
17908 	// NotYetImplementedException
17909 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
17910 	void rasterOp(RasterOp op) {}
17911 	Pen _activePen;
17912 	Color _fillColor;
17913 	Rectangle _clipRectangle;
17914 	void setClipRectangle(int, int, int, int) {}
17915 	void setFont(OperatingSystemFont) {}
17916 	int fontHeight() { return 14; }
17917 
17918 	// end
17919 
17920         void pen(Pen pen) {
17921 	    _activePen = pen;
17922 	    auto color = pen.color; // FIXME
17923             double alphaComponent = color.a/255.0f;
17924             CGContextSetRGBStrokeColor(context,
17925                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
17926 
17927             if (color.a != 255) {
17928                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
17929                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
17930                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
17931                 _outlineComponents[3] = color.a;
17932             } else {
17933                 _outlineComponents[0] = color.r;
17934                 _outlineComponents[1] = color.g;
17935                 _outlineComponents[2] = color.b;
17936                 _outlineComponents[3] = color.a;
17937             }
17938         }
17939 
17940         @property void fillColor(Color color) {
17941             CGContextSetRGBFillColor(context,
17942                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
17943         }
17944 
17945         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
17946 		// NotYetImplementedException for upper left/width/height
17947             auto cgImage = CGBitmapContextCreateImage(image.context);
17948             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17949                                CGBitmapContextGetHeight(image.context));
17950             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17951             CGImageRelease(cgImage);
17952         }
17953 
17954 	version(OSXCocoa) {} else // NotYetImplementedException
17955         void drawPixmap(Sprite image, int x, int y) {
17956 		// FIXME: is this efficient?
17957             auto cgImage = CGBitmapContextCreateImage(image.context);
17958             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17959                                CGBitmapContextGetHeight(image.context));
17960             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17961             CGImageRelease(cgImage);
17962         }
17963 
17964 
17965         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
17966 		// FIXME: alignment
17967             if (_outlineComponents[3] != 0) {
17968                 CGContextSaveGState(context);
17969                 auto invAlpha = 1.0f/_outlineComponents[3];
17970                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
17971                                                   _outlineComponents[1]*invAlpha,
17972                                                   _outlineComponents[2]*invAlpha,
17973                                                   _outlineComponents[3]/255.0f);
17974                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
17975 // auto cfstr = cast(id)createCFString(text);
17976 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
17977 // NSPoint(x, y), null);
17978 // CFRelease(cfstr);
17979                 CGContextRestoreGState(context);
17980             }
17981         }
17982 
17983         void drawPixel(int x, int y) {
17984             auto rawData = CGBitmapContextGetData(context);
17985             auto width = CGBitmapContextGetWidth(context);
17986             auto height = CGBitmapContextGetHeight(context);
17987             auto offset = ((height - y - 1) * width + x) * 4;
17988             rawData[offset .. offset+4] = _outlineComponents;
17989         }
17990 
17991         void drawLine(int x1, int y1, int x2, int y2) {
17992             CGPoint[2] linePoints;
17993             linePoints[0] = CGPoint(x1, y1);
17994             linePoints[1] = CGPoint(x2, y2);
17995             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
17996         }
17997 
17998         void drawRectangle(int x, int y, int width, int height) {
17999             CGContextBeginPath(context);
18000             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18001             CGContextAddRect(context, rect);
18002             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18003         }
18004 
18005         void drawEllipse(int x1, int y1, int x2, int y2) {
18006             CGContextBeginPath(context);
18007             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18008             CGContextAddEllipseInRect(context, rect);
18009             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18010         }
18011 
18012         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18013             // @@@BUG@@@ Does not support elliptic arc (width != height).
18014             CGContextBeginPath(context);
18015             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18016                             start*PI/(180*64), finish*PI/(180*64), 0);
18017             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18018         }
18019 
18020         void drawPolygon(Point[] intPoints) {
18021             CGContextBeginPath(context);
18022             auto points = array(map!(CGPoint.fromTuple)(intPoints));
18023             CGContextAddLines(context, points.ptr, points.length);
18024             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18025         }
18026     }
18027 
18028     mixin template NativeSimpleWindowImplementation() {
18029         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18030             synchronized {
18031                 if (NSApp == null) initializeApp();
18032             }
18033 
18034             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18035 
18036             // create the window.
18037             window = initWithContentRect(alloc("NSWindow"),
18038                                          contentRect,
18039                                          NSTitledWindowMask
18040                                             |NSClosableWindowMask
18041                                             |NSMiniaturizableWindowMask
18042                                             |NSResizableWindowMask,
18043                                          NSBackingStoreBuffered,
18044                                          true);
18045 
18046             // set the title & move the window to center.
18047             auto windowTitle = createCFString(title);
18048             setTitle(window, windowTitle);
18049             CFRelease(windowTitle);
18050             center(window);
18051 
18052             // create area to draw on.
18053             auto colorSpace = CGColorSpaceCreateDeviceRGB();
18054             drawingContext = CGBitmapContextCreate(null, width, height,
18055                                                    8, 4*width, colorSpace,
18056                                                    kCGImageAlphaPremultipliedLast
18057                                                       |kCGBitmapByteOrder32Big);
18058             CGColorSpaceRelease(colorSpace);
18059             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18060             auto matrix = CGContextGetTextMatrix(drawingContext);
18061             matrix.c = -matrix.c;
18062             matrix.d = -matrix.d;
18063             CGContextSetTextMatrix(drawingContext, matrix);
18064 
18065             // create the subview that things will be drawn on.
18066             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
18067             setContentView(window, view);
18068             object_setIvar(view, simpleWindowIvar, cast(id)this);
18069             release(view);
18070 
18071             setBackgroundColor(window, whiteNSColor);
18072             makeKeyAndOrderFront(window, null);
18073         }
18074         void dispose() {
18075             closeWindow();
18076             release(window);
18077         }
18078         void closeWindow() {
18079             invalidate(timer);
18080             .close(window);
18081         }
18082 
18083         ScreenPainter getPainter(bool manualInvalidations) {
18084 		return ScreenPainter(this, this, manualInvalidations);
18085 	}
18086 
18087         id window;
18088         id timer;
18089         id view;
18090         CGContextRef drawingContext;
18091     }
18092 
18093     extern(C) {
18094     private:
18095         BOOL returnTrue3(id self, SEL _cmd, id app) {
18096             return true;
18097         }
18098         BOOL returnTrue2(id self, SEL _cmd) {
18099             return true;
18100         }
18101 
18102         void pulse(id self, SEL _cmd) {
18103             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18104             simpleWindow.handlePulse();
18105             setNeedsDisplay(self, true);
18106         }
18107         void drawRect(id self, SEL _cmd, NSRect rect) {
18108             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18109             auto curCtx = graphicsPort(currentNSGraphicsContext);
18110             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18111             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
18112                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
18113             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18114             CGImageRelease(cgImage);
18115         }
18116         void keyDown(id self, SEL _cmd, id event) {
18117             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18118 
18119             // the event may have multiple characters, and we send them all at
18120             // once.
18121             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
18122                 auto chars = characters(event);
18123                 auto range = CFRange(0, CFStringGetLength(chars));
18124                 auto buffer = new char[range.length*3];
18125                 long actualLength;
18126                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
18127                                  buffer.ptr, cast(int) buffer.length, &actualLength);
18128                 foreach (dchar dc; buffer[0..actualLength]) {
18129                     if (simpleWindow.handleCharEvent)
18130                         simpleWindow.handleCharEvent(dc);
18131 		    // NotYetImplementedException
18132                     //if (simpleWindow.handleKeyEvent)
18133                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
18134                 }
18135             }
18136 
18137             // the event's 'keyCode' is hardware-dependent. I don't think people
18138             // will like it. Let's leave it to the native handler.
18139 
18140             // perform the default action.
18141 
18142 	    // so the default action is to make a bomp sound and i dont want that
18143 	    // sooooooooo yeah not gonna do that.
18144 
18145             //auto superData = objc_super(self, superclass(self));
18146             //alias extern(C) void function(objc_super*, SEL, id) T;
18147             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
18148         }
18149     }
18150 
18151     // initialize the app so that it can be interacted with the user.
18152     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
18153     private void initializeApp() {
18154         // push an autorelease pool to avoid leaking.
18155         init(alloc("NSAutoreleasePool"));
18156 
18157         // create a new NSApp instance
18158         sharedNSApplication;
18159         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
18160 
18161         // create the "Quit" menu.
18162         auto menuBar = init(alloc("NSMenu"));
18163         auto appMenuItem = init(alloc("NSMenuItem"));
18164         addItem(menuBar, appMenuItem);
18165         setMainMenu(NSApp, menuBar);
18166         release(appMenuItem);
18167         release(menuBar);
18168 
18169         auto appMenu = init(alloc("NSMenu"));
18170         auto quitTitle = createCFString("Quit");
18171         auto q = createCFString("q");
18172         auto quitItem = initWithTitle(alloc("NSMenuItem"),
18173                                       quitTitle, sel_registerName("terminate:"), q);
18174         addItem(appMenu, quitItem);
18175         setSubmenu(appMenuItem, appMenu);
18176         release(quitItem);
18177         release(appMenu);
18178         CFRelease(q);
18179         CFRelease(quitTitle);
18180 
18181         // assign a delegate for the application, allow it to quit when the last
18182         // window is closed.
18183         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
18184                                                     "SDWindowCloseDelegate", 0);
18185         class_addMethod(delegateClass,
18186                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
18187                         &returnTrue3, "c@:@");
18188         objc_registerClassPair(delegateClass);
18189 
18190         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
18191         setDelegate(NSApp, appDelegate);
18192         activateIgnoringOtherApps(NSApp, true);
18193 
18194         // create a new view that draws the graphics and respond to keyDown
18195         // events.
18196         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
18197                                                 "SDGraphicsView", (void*).sizeof);
18198         class_addIvar(viewClass, "simpledisplay_simpleWindow",
18199                       (void*).sizeof, (void*).alignof, "^v");
18200         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
18201                         &pulse, "v@:");
18202         class_addMethod(viewClass, sel_registerName("drawRect:"),
18203                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
18204         class_addMethod(viewClass, sel_registerName("isFlipped"),
18205                         &returnTrue2, "c@:");
18206         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
18207                         &returnTrue2, "c@:");
18208         class_addMethod(viewClass, sel_registerName("keyDown:"),
18209                         &keyDown, "v@:@");
18210         objc_registerClassPair(viewClass);
18211         simpleWindowIvar = class_getInstanceVariable(viewClass,
18212                                                      "simpledisplay_simpleWindow");
18213     }
18214 }
18215 
18216 version(without_opengl) {} else
18217 extern(System) nothrow @nogc {
18218 	//enum uint GL_VERSION = 0x1F02;
18219 	//const(char)* glGetString (/*GLenum*/uint);
18220 	version(X11) {
18221 	static if (!SdpyIsUsingIVGLBinds) {
18222 
18223 		enum GLX_X_RENDERABLE = 0x8012;
18224 		enum GLX_DRAWABLE_TYPE = 0x8010;
18225 		enum GLX_RENDER_TYPE = 0x8011;
18226 		enum GLX_X_VISUAL_TYPE = 0x22;
18227 		enum GLX_TRUE_COLOR = 0x8002;
18228 		enum GLX_WINDOW_BIT = 0x00000001;
18229 		enum GLX_RGBA_BIT = 0x00000001;
18230 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18231 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18232 		enum GLX_SAMPLES = 0x186a1;
18233 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18234 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18235 	}
18236 
18237 		// GLX_EXT_swap_control
18238 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18239 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18240 
18241 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18242 		extern(System) {
18243 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18244 		}
18245 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18246 
18247 		// this made public so we don't have to get it again and again
18248 		public bool glXCreateContextAttribsARB_present () {
18249 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18250 				// get it
18251 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18252 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18253 			}
18254 			return (glXCreateContextAttribsARBFn !is null);
18255 		}
18256 
18257 		// this made public so we don't have to get it again and again
18258 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18259 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18260 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18261 		}
18262 
18263 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18264 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18265 
18266 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18267 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18268 			if (_glx_swapInterval_fn is null) {
18269 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18270 				if (_glx_swapInterval_fn is null) {
18271 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18272 					return;
18273 				}
18274 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
18275 			}
18276 
18277 			if(glXSwapIntervalMESA is null) {
18278 				// it seems to require both to actually take effect on many computers
18279 				// idk why
18280 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18281 				if(glXSwapIntervalMESA is null)
18282 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18283 			}
18284 
18285 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18286 				glXSwapIntervalMESA(wait ? 1 : 0);
18287 
18288 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18289 		}
18290 	} else version(Windows) {
18291 	static if (!SdpyIsUsingIVGLBinds) {
18292 	enum GL_TRUE = 1;
18293 	enum GL_FALSE = 0;
18294 	alias int GLint;
18295 
18296 	public void* glbindGetProcAddress (const(char)* name) {
18297 		void* res = wglGetProcAddress(name);
18298 		if (res is null) {
18299 			/+
18300 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18301 			import core.sys.windows.windef, core.sys.windows.winbase;
18302 			__gshared HINSTANCE dll = null;
18303 			if (dll is null) {
18304 				dll = LoadLibraryA("opengl32.dll");
18305 				if (dll is null) return null; // <32, but idc
18306 			}
18307 			res = GetProcAddress(dll, name);
18308 			+/
18309 			res = GetProcAddress(gl.libHandle, name);
18310 		}
18311 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18312 		return res;
18313 	}
18314 	}
18315 
18316 
18317  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18318         void wglSetVSync(bool wait) {
18319 		if(wglSwapIntervalEXT is null) {
18320 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18321 			if(wglSwapIntervalEXT is null)
18322 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18323 		}
18324 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18325 			return;
18326 
18327 		wglSwapIntervalEXT(wait ? 1 : 0);
18328 	}
18329 
18330 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18331 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18332 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18333 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18334 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18335 
18336 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18337 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18338 
18339 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18340 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18341 
18342 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18343 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18344 
18345 		void wglInitOtherFunctions () {
18346 			if (wglCreateContextAttribsARB is null) {
18347 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18348 			}
18349 		}
18350 	}
18351 
18352 	static if (!SdpyIsUsingIVGLBinds) {
18353 
18354 	interface GL {
18355 		extern(System) @nogc nothrow:
18356 
18357 		void glGetIntegerv(int, void*);
18358 		void glMatrixMode(int);
18359 		void glPushMatrix();
18360 		void glLoadIdentity();
18361 		void glOrtho(double, double, double, double, double, double);
18362 		void glFrustum(double, double, double, double, double, double);
18363 
18364 		void glPopMatrix();
18365 		void glEnable(int);
18366 		void glDisable(int);
18367 		void glClear(int);
18368 		void glBegin(int);
18369 		void glVertex2f(float, float);
18370 		void glVertex3f(float, float, float);
18371 		void glEnd();
18372 		void glColor3b(byte, byte, byte);
18373 		void glColor3ub(ubyte, ubyte, ubyte);
18374 		void glColor4b(byte, byte, byte, byte);
18375 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18376 		void glColor3i(int, int, int);
18377 		void glColor3ui(uint, uint, uint);
18378 		void glColor4i(int, int, int, int);
18379 		void glColor4ui(uint, uint, uint, uint);
18380 		void glColor3f(float, float, float);
18381 		void glColor4f(float, float, float, float);
18382 		void glTranslatef(float, float, float);
18383 		void glScalef(float, float, float);
18384 		version(X11) {
18385 			void glSecondaryColor3b(byte, byte, byte);
18386 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18387 			void glSecondaryColor3i(int, int, int);
18388 			void glSecondaryColor3ui(uint, uint, uint);
18389 			void glSecondaryColor3f(float, float, float);
18390 		}
18391 
18392 		void glDrawElements(int, int, int, void*);
18393 
18394 		void glRotatef(float, float, float, float);
18395 
18396 		uint glGetError();
18397 
18398 		void glDeleteTextures(int, uint*);
18399 
18400 
18401 		void glRasterPos2i(int, int);
18402 		void glDrawPixels(int, int, uint, uint, void*);
18403 		void glClearColor(float, float, float, float);
18404 
18405 
18406 		void glPixelStorei(uint, int);
18407 
18408 		void glGenTextures(uint, uint*);
18409 		void glBindTexture(int, int);
18410 		void glTexParameteri(uint, uint, int);
18411 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18412 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
18413 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18414 			/*GLsizei*/int width, /*GLsizei*/int height,
18415 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18416 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18417 
18418 		void glLineWidth(int);
18419 
18420 
18421 		void glTexCoord2f(float, float);
18422 		void glVertex2i(int, int);
18423 		void glBlendFunc (int, int);
18424 		void glDepthFunc (int);
18425 		void glViewport(int, int, int, int);
18426 
18427 		void glClearDepth(double);
18428 
18429 		void glReadBuffer(uint);
18430 		void glReadPixels(int, int, int, int, int, int, void*);
18431 
18432 		void glFlush();
18433 		void glFinish();
18434 
18435 		version(Windows) {
18436 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18437 			HGLRC wglCreateContext(HDC);
18438 			HGLRC wglCreateLayerContext(HDC, int);
18439 			BOOL wglDeleteContext(HGLRC);
18440 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18441 			HGLRC wglGetCurrentContext();
18442 			HDC wglGetCurrentDC();
18443 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18444 			PROC wglGetProcAddress(LPCSTR);
18445 			BOOL wglMakeCurrent(HDC, HGLRC);
18446 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18447 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18448 			BOOL wglShareLists(HGLRC, HGLRC);
18449 			BOOL wglSwapLayerBuffers(HDC, UINT);
18450 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18451 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18452 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18453 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18454 		}
18455 
18456 	}
18457 
18458 	interface GL3 {
18459 		extern(System) @nogc nothrow:
18460 
18461 		void glGenVertexArrays(GLsizei, GLuint*);
18462 		void glBindVertexArray(GLuint);
18463 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18464 		void glGenerateMipmap(GLenum);
18465 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18466 		void glStencilMask(GLuint);
18467 		void glStencilFunc(GLenum, GLint, GLuint);
18468 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18469 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18470 		GLuint glCreateProgram();
18471 		GLuint glCreateShader(GLenum);
18472 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18473 		void glCompileShader(GLuint);
18474 		void glGetShaderiv(GLuint, GLenum, GLint*);
18475 		void glAttachShader(GLuint, GLuint);
18476 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18477 		void glLinkProgram(GLuint);
18478 		void glGetProgramiv(GLuint, GLenum, GLint*);
18479 		void glDeleteProgram(GLuint);
18480 		void glDeleteShader(GLuint);
18481 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18482 		void glGenBuffers(GLsizei, GLuint*);
18483 
18484 		void glUniform1f(GLint location, GLfloat v0);
18485 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18486 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18487 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18488 		void glUniform1i(GLint location, GLint v0);
18489 		void glUniform2i(GLint location, GLint v0, GLint v1);
18490 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18491 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18492 		void glUniform1ui(GLint location, GLuint v0);
18493 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18494 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18495 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18496 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18497 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18498 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18499 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18500 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18501 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18502 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18503 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18504 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18505 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18506 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18507 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18508 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18509 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18510 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18511 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18512 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18513 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18514 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18515 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18516 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18517 
18518 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18519 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18520 		void glDrawArrays(GLenum, GLint, GLsizei);
18521 		void glStencilOp(GLenum, GLenum, GLenum);
18522 		void glUseProgram(GLuint);
18523 		void glCullFace(GLenum);
18524 		void glFrontFace(GLenum);
18525 		void glActiveTexture(GLenum);
18526 		void glBindBuffer(GLenum, GLuint);
18527 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18528 		void glEnableVertexAttribArray(GLuint);
18529 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18530 		void glUniform1i(GLint, GLint);
18531 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18532 		void glDisableVertexAttribArray(GLuint);
18533 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18534 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18535 		void glLogicOp (GLenum opcode);
18536 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18537 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18538 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18539 		GLenum glCheckFramebufferStatus (GLenum target);
18540 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18541 	}
18542 
18543 	interface GL4 {
18544 		extern(System) @nogc nothrow:
18545 
18546 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18547 			/*GLsizei*/int width, /*GLsizei*/int height,
18548 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18549 	}
18550 
18551 	interface GLU {
18552 		extern(System) @nogc nothrow:
18553 
18554 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18555 		void gluPerspective(double, double, double, double);
18556 
18557 		char* gluErrorString(uint);
18558 	}
18559 
18560 
18561 	enum GL_RED = 0x1903;
18562 	enum GL_ALPHA = 0x1906;
18563 
18564 	enum uint GL_FRONT = 0x0404;
18565 
18566 	enum uint GL_BLEND = 0x0be2;
18567 	enum uint GL_LEQUAL = 0x0203;
18568 
18569 
18570 	enum uint GL_RGB = 0x1907;
18571 	enum uint GL_BGRA = 0x80e1;
18572 	enum uint GL_RGBA = 0x1908;
18573 	enum uint GL_TEXTURE_2D =   0x0DE1;
18574 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18575 	enum uint GL_NEAREST = 0x2600;
18576 	enum uint GL_LINEAR = 0x2601;
18577 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18578 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18579 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18580 	enum uint GL_REPEAT = 0x2901;
18581 	enum uint GL_CLAMP = 0x2900;
18582 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18583 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18584 	enum uint GL_DECAL = 0x2101;
18585 	enum uint GL_MODULATE = 0x2100;
18586 	enum uint GL_TEXTURE_ENV = 0x2300;
18587 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18588 	enum uint GL_REPLACE = 0x1E01;
18589 	enum uint GL_LIGHTING = 0x0B50;
18590 	enum uint GL_DITHER = 0x0BD0;
18591 
18592 	enum uint GL_NO_ERROR = 0;
18593 
18594 
18595 
18596 	enum int GL_VIEWPORT = 0x0BA2;
18597 	enum int GL_MODELVIEW = 0x1700;
18598 	enum int GL_TEXTURE = 0x1702;
18599 	enum int GL_PROJECTION = 0x1701;
18600 	enum int GL_DEPTH_TEST = 0x0B71;
18601 
18602 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18603 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18604 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18605 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18606 
18607 	enum int GL_POINTS = 0x0000;
18608 	enum int GL_LINES =  0x0001;
18609 	enum int GL_LINE_LOOP = 0x0002;
18610 	enum int GL_LINE_STRIP = 0x0003;
18611 	enum int GL_TRIANGLES = 0x0004;
18612 	enum int GL_TRIANGLE_STRIP = 5;
18613 	enum int GL_TRIANGLE_FAN = 6;
18614 	enum int GL_QUADS = 7;
18615 	enum int GL_QUAD_STRIP = 8;
18616 	enum int GL_POLYGON = 9;
18617 
18618 	alias GLvoid = void;
18619 	alias GLboolean = ubyte;
18620 	alias GLuint = uint;
18621 	alias GLenum = uint;
18622 	alias GLchar = char;
18623 	alias GLsizei = int;
18624 	alias GLfloat = float;
18625 	alias GLintptr = size_t;
18626 	alias GLsizeiptr = ptrdiff_t;
18627 
18628 
18629 	enum uint GL_INVALID_ENUM = 0x0500;
18630 
18631 	enum uint GL_ZERO = 0;
18632 	enum uint GL_ONE = 1;
18633 
18634 	enum uint GL_BYTE = 0x1400;
18635 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18636 	enum uint GL_SHORT = 0x1402;
18637 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18638 	enum uint GL_INT = 0x1404;
18639 	enum uint GL_UNSIGNED_INT = 0x1405;
18640 	enum uint GL_FLOAT = 0x1406;
18641 	enum uint GL_2_BYTES = 0x1407;
18642 	enum uint GL_3_BYTES = 0x1408;
18643 	enum uint GL_4_BYTES = 0x1409;
18644 	enum uint GL_DOUBLE = 0x140A;
18645 
18646 	enum uint GL_STREAM_DRAW = 0x88E0;
18647 
18648 	enum uint GL_CCW = 0x0901;
18649 
18650 	enum uint GL_STENCIL_TEST = 0x0B90;
18651 	enum uint GL_SCISSOR_TEST = 0x0C11;
18652 
18653 	enum uint GL_EQUAL = 0x0202;
18654 	enum uint GL_NOTEQUAL = 0x0205;
18655 
18656 	enum uint GL_ALWAYS = 0x0207;
18657 	enum uint GL_KEEP = 0x1E00;
18658 
18659 	enum uint GL_INCR = 0x1E02;
18660 
18661 	enum uint GL_INCR_WRAP = 0x8507;
18662 	enum uint GL_DECR_WRAP = 0x8508;
18663 
18664 	enum uint GL_CULL_FACE = 0x0B44;
18665 	enum uint GL_BACK = 0x0405;
18666 
18667 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18668 	enum uint GL_VERTEX_SHADER = 0x8B31;
18669 
18670 	enum uint GL_COMPILE_STATUS = 0x8B81;
18671 	enum uint GL_LINK_STATUS = 0x8B82;
18672 
18673 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18674 
18675 	enum uint GL_STATIC_DRAW = 0x88E4;
18676 
18677 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18678 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18679 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18680 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18681 
18682 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18683 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18684 
18685 	enum uint GL_TEXTURE0 = 0x84C0U;
18686 	enum uint GL_TEXTURE1 = 0x84C1U;
18687 
18688 	enum uint GL_ARRAY_BUFFER = 0x8892;
18689 
18690 	enum uint GL_SRC_COLOR = 0x0300;
18691 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18692 	enum uint GL_SRC_ALPHA = 0x0302;
18693 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18694 	enum uint GL_DST_ALPHA = 0x0304;
18695 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18696 	enum uint GL_DST_COLOR = 0x0306;
18697 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18698 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18699 
18700 	enum uint GL_INVERT = 0x150AU;
18701 
18702 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18703 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18704 
18705 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18706 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18707 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18708 
18709 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18710 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18711 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18712 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18713 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18714 
18715 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18716 	enum uint GL_CLEAR = 0x1500U;
18717 	enum uint GL_COPY = 0x1503U;
18718 	enum uint GL_XOR = 0x1506U;
18719 
18720 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18721 
18722 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18723 
18724 	}
18725 }
18726 
18727 /++
18728 	History:
18729 		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.
18730 +/
18731 __gshared bool gluSuccessfullyLoaded = true;
18732 
18733 version(without_opengl) {} else {
18734 static if(!SdpyIsUsingIVGLBinds) {
18735 	version(Windows) {
18736 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18737 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18738 	} else {
18739 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18740 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18741 	}
18742 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18743 
18744 
18745 	shared static this() {
18746 		gl.loadDynamicLibrary();
18747 
18748 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18749 		// unless those functions are actually used
18750 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18751 		glu.loadDynamicLibrary();
18752 	}
18753 }
18754 }
18755 
18756 /++
18757 	Convenience method for converting D arrays to opengl buffer data
18758 
18759 	I would LOVE to overload it with the original glBufferData, but D won't
18760 	let me since glBufferData is a function pointer :(
18761 
18762 	Added: August 25, 2020 (version 8.5)
18763 +/
18764 version(without_opengl) {} else
18765 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18766 	glBufferData(target, data.length, data.ptr, usage);
18767 }
18768 
18769 /+
18770 /++
18771 	A matrix for simple uses that easily integrates with [OpenGlShader].
18772 
18773 	Might not be useful to you since it only as some simple functions and
18774 	probably isn't that fast.
18775 
18776 	Note it uses an inline static array for its storage, so copying it
18777 	may be expensive.
18778 +/
18779 struct BasicMatrix(int columns, int rows, T = float) {
18780 	import core.stdc.math;
18781 
18782 	T[columns * rows] data = 0.0;
18783 
18784 	/++
18785 		Basic operations that operate *in place*.
18786 	+/
18787 	void translate() {
18788 
18789 	}
18790 
18791 	/// ditto
18792 	void scale() {
18793 
18794 	}
18795 
18796 	/// ditto
18797 	void rotate() {
18798 
18799 	}
18800 
18801 	/++
18802 
18803 	+/
18804 	static if(columns == rows)
18805 	static BasicMatrix identity() {
18806 		BasicMatrix m;
18807 		foreach(i; 0 .. columns)
18808 			data[0 + i + i * columns] = 1.0;
18809 		return m;
18810 	}
18811 
18812 	static BasicMatrix ortho() {
18813 		return BasicMatrix.init;
18814 	}
18815 }
18816 +/
18817 
18818 /++
18819 	Convenience class for using opengl shaders.
18820 
18821 	Ensure that you've loaded opengl 3+ and set your active
18822 	context before trying to use this.
18823 
18824 	Added: August 25, 2020 (version 8.5)
18825 +/
18826 version(without_opengl) {} else
18827 final class OpenGlShader {
18828 	private int shaderProgram_;
18829 	private @property void shaderProgram(int a) {
18830 		shaderProgram_ = a;
18831 	}
18832 	/// Get the program ID for use in OpenGL functions.
18833 	public @property int shaderProgram() {
18834 		return shaderProgram_;
18835 	}
18836 
18837 	/++
18838 
18839 	+/
18840 	static struct Source {
18841 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
18842 		string code; ///
18843 	}
18844 
18845 	/++
18846 		Helper method to just compile some shader code and check for errors
18847 		while you do glCreateShader, etc. on the outside yourself.
18848 
18849 		This just does `glShaderSource` and `glCompileShader` for the given code.
18850 
18851 		If you the OpenGlShader class constructor, you never need to call this yourself.
18852 	+/
18853 	static void compile(int sid, Source code) {
18854 		const(char)*[1] buffer;
18855 		int[1] lengthBuffer;
18856 
18857 		buffer[0] = code.code.ptr;
18858 		lengthBuffer[0] = cast(int) code.code.length;
18859 
18860 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
18861 		glCompileShader(sid);
18862 
18863 		int success;
18864 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
18865 		if(!success) {
18866 			char[512] info;
18867 			int len;
18868 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
18869 
18870 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
18871 		}
18872 	}
18873 
18874 	/++
18875 		Calls `glLinkProgram` and throws if error a occurs.
18876 
18877 		If you the OpenGlShader class constructor, you never need to call this yourself.
18878 	+/
18879 	static void link(int shaderProgram) {
18880 		glLinkProgram(shaderProgram);
18881 		int success;
18882 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
18883 		if(!success) {
18884 			char[512] info;
18885 			int len;
18886 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
18887 
18888 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
18889 		}
18890 	}
18891 
18892 	/++
18893 		Constructs the shader object by calling `glCreateProgram`, then
18894 		compiling each given [Source], and finally, linking them together.
18895 
18896 		Throws: on compile or link failure.
18897 	+/
18898 	this(Source[] codes...) {
18899 		shaderProgram = glCreateProgram();
18900 
18901 		int[16] shadersBufferStack;
18902 
18903 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
18904 			shadersBufferStack[0 .. codes.length] :
18905 			new int[](codes.length);
18906 
18907 		foreach(idx, code; codes) {
18908 			shadersBuffer[idx] = glCreateShader(code.type);
18909 
18910 			compile(shadersBuffer[idx], code);
18911 
18912 			glAttachShader(shaderProgram, shadersBuffer[idx]);
18913 		}
18914 
18915 		link(shaderProgram);
18916 
18917 		foreach(s; shadersBuffer)
18918 			glDeleteShader(s);
18919 	}
18920 
18921 	/// Calls `glUseProgram(this.shaderProgram)`
18922 	void use() {
18923 		glUseProgram(this.shaderProgram);
18924 	}
18925 
18926 	/// Deletes the program.
18927 	void delete_() {
18928 		glDeleteProgram(shaderProgram);
18929 		shaderProgram = 0;
18930 	}
18931 
18932 	/++
18933 		[OpenGlShader.uniforms].name gives you one of these.
18934 
18935 		You can get the id out of it or just assign
18936 	+/
18937 	static struct Uniform {
18938 		/// the id passed to glUniform*
18939 		int id;
18940 
18941 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
18942 		void opAssign(float x, float y, float z, float w) {
18943 			if(id != -1)
18944 			glUniform4f(id, x, y, z, w);
18945 		}
18946 
18947 		void opAssign(float x) {
18948 			if(id != -1)
18949 			glUniform1f(id, x);
18950 		}
18951 
18952 		void opAssign(float x, float y) {
18953 			if(id != -1)
18954 			glUniform2f(id, x, y);
18955 		}
18956 
18957 		void opAssign(T)(T t) {
18958 			t.glUniform(id);
18959 		}
18960 	}
18961 
18962 	static struct UniformsHelper {
18963 		OpenGlShader _shader;
18964 
18965 		@property Uniform opDispatch(string name)() {
18966 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
18967 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
18968 			//if(i == -1)
18969 				//throw new Exception("Could not find uniform " ~ name);
18970 			return Uniform(i);
18971 		}
18972 
18973 		@property void opDispatch(string name, T)(T t) {
18974 			Uniform f = this.opDispatch!name;
18975 			t.glUniform(f);
18976 		}
18977 	}
18978 
18979 	/++
18980 		Gives access to the uniforms through dot access.
18981 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
18982 	+/
18983 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
18984 }
18985 
18986 version(without_opengl) {} else {
18987 /++
18988 	A static container of experimental types and value constructors for opengl 3+ shaders.
18989 
18990 
18991 	You can declare variables like:
18992 
18993 	```
18994 	OGL.vec3f something;
18995 	```
18996 
18997 	But generally it would be used with [OpenGlShader]'s uniform helpers like
18998 
18999 	```
19000 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19001 	```
19002 
19003 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19004 
19005 
19006 	History:
19007 		Added December 7, 2021. Not yet stable.
19008 +/
19009 final class OGL {
19010 	static:
19011 
19012 	private template typeFromSpecifier(string specifier) {
19013 		static if(specifier == "f")
19014 			alias typeFromSpecifier = GLfloat;
19015 		else static if(specifier == "i")
19016 			alias typeFromSpecifier = GLint;
19017 		else static if(specifier == "ui")
19018 			alias typeFromSpecifier = GLuint;
19019 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19020 	}
19021 
19022 	private template CommonType(T...) {
19023 		static if(T.length == 1)
19024 			alias CommonType = T[0];
19025 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19026 			alias CommonType = CommonType!(C, T[2 .. $]);
19027 	}
19028 
19029 	private template typesToSpecifier(T...) {
19030 		static if(is(CommonType!T == float))
19031 			enum typesToSpecifier = "f";
19032 		else static if(is(CommonType!T == int))
19033 			enum typesToSpecifier = "i";
19034 		else static if(is(CommonType!T == uint))
19035 			enum typesToSpecifier = "ui";
19036 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19037 	}
19038 
19039 	private template genNames(size_t dim, size_t dim2 = 0) {
19040 		string helper() {
19041 			string s;
19042 			if(dim2) {
19043 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19044 			} else {
19045 				if(dim > 0) s ~= "type x = 0;";
19046 				if(dim > 1) s ~= "type y = 0;";
19047 				if(dim > 2) s ~= "type z = 0;";
19048 				if(dim > 3) s ~= "type w = 0;";
19049 			}
19050 			return s;
19051 		}
19052 
19053 		enum genNames = helper();
19054 	}
19055 
19056 	// there's vec, arrays of vec, mat, and arrays of mat
19057 	template opDispatch(string name)
19058 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19059 	{
19060 		static if(name[4] == 'x') {
19061 			enum dimX = cast(int) (name[3] - '0');
19062 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19063 
19064 			enum dimY = cast(int) (name[5] - '0');
19065 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19066 
19067 			enum isArray = name[$ - 1] == 'v';
19068 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19069 			alias type = typeFromSpecifier!typeSpecifier;
19070 		} else {
19071 			enum dim = cast(int) (name[3] - '0');
19072 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19073 			enum isArray = name[$ - 1] == 'v';
19074 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19075 			alias type = typeFromSpecifier!typeSpecifier;
19076 		}
19077 
19078 		align(1)
19079 		struct opDispatch {
19080 			align(1):
19081 			static if(name[4] == 'x')
19082 				mixin(genNames!(dimX, dimY));
19083 			else
19084 				mixin(genNames!dim);
19085 
19086 			private void glUniform(OpenGlShader.Uniform assignTo) {
19087 				glUniform(assignTo.id);
19088 			}
19089 			private void glUniform(int assignTo) {
19090 				static if(name[4] == 'x') {
19091 					// FIXME
19092 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19093 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19094 				} else
19095 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19096 			}
19097 		}
19098 	}
19099 
19100 	auto vec(T...)(T members) {
19101 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19102 	}
19103 }
19104 }
19105 
19106 version(linux) {
19107 	version(with_eventloop) {} else {
19108 		private int epollFd = -1;
19109 		void prepareEventLoop() {
19110 			if(epollFd != -1)
19111 				return; // already initialized, no need to do it again
19112 			import ep = core.sys.linux.epoll;
19113 
19114 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19115 			if(epollFd == -1)
19116 				throw new Exception("epoll create failure");
19117 		}
19118 	}
19119 } else version(Posix) {
19120 	void prepareEventLoop() {}
19121 }
19122 
19123 version(X11) {
19124 	import core.stdc.locale : LC_ALL; // rdmd fix
19125 	__gshared bool sdx_isUTF8Locale;
19126 
19127 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19128 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19129 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19130 	// anal magic is here. I (Ketmar) hope you like it.
19131 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19132 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19133 	// later.
19134 
19135 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19136 	shared static this () {
19137 		if(!librariesSuccessfullyLoaded)
19138 			return;
19139 
19140 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19141 
19142 		// this doesn't hurt; it may add some locking, but the speed is still
19143 		// allows doing 60 FPS videogames; also, ignore the result, as most
19144 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19145 		// never seen this failing).
19146 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19147 
19148 		setlocale(LC_ALL, "");
19149 		// check if out locale is UTF-8
19150 		auto lct = setlocale(LC_CTYPE, null);
19151 		if (lct is null) {
19152 			sdx_isUTF8Locale = false;
19153 		} else {
19154 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19155 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19156 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19157 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19158 				{
19159 					sdx_isUTF8Locale = true;
19160 					break;
19161 				}
19162 			}
19163 		}
19164 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19165 	}
19166 }
19167 
19168 class ExperimentalTextComponent2 {
19169 	/+
19170 		Stage 1: get it working monospace
19171 		Stage 2: use proportional font
19172 		Stage 3: allow changes in inline style
19173 		Stage 4: allow new fonts and sizes in the middle
19174 		Stage 5: optimize gap buffer
19175 		Stage 6: optimize layout
19176 		Stage 7: word wrap
19177 		Stage 8: justification
19178 		Stage 9: editing, selection, etc.
19179 
19180 			Operations:
19181 				insert text
19182 				overstrike text
19183 				select
19184 				cut
19185 				modify
19186 	+/
19187 
19188 	/++
19189 		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.
19190 	+/
19191 	this(SimpleWindow window) {
19192 		this.window = window;
19193 	}
19194 
19195 	private SimpleWindow window;
19196 
19197 
19198 	/++
19199 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19200 		representing the internal parts. The first pass is focused on the x parameter, then the
19201 		renderer is responsible for going back to the parts in the current line and calling
19202 		adjustDownForAscent to change the y params.
19203 	+/
19204 	static interface ComponentRenderHelper {
19205 
19206 		/+
19207 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19208 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19209 			to move (adjust y to make room for new line) until you get back to the same position,
19210 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19211 
19212 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19213 			once you reach something that is unchanged, you can stop.
19214 		+/
19215 
19216 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19217 
19218 		int ascent() const;
19219 		int descent() const;
19220 
19221 		int advance() const;
19222 
19223 		bool endsWithExplititLineBreak() const;
19224 	}
19225 
19226 	static interface RenderResult {
19227 		/++
19228 			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.
19229 		+/
19230 		void popFront();
19231 		@property bool empty() const;
19232 		@property ComponentRenderHelper front() const;
19233 
19234 		void repositionForNextLine(Point baseline, int availableWidth);
19235 	}
19236 
19237 	static interface ComponentInFlow {
19238 		void draw(ScreenPainter painter);
19239 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19240 
19241 		bool startsWithExplicitLineBreak() const;
19242 	}
19243 
19244 	static class TextFlowComponent : ComponentInFlow {
19245 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19246 
19247 		Color foreground;
19248 		Color background;
19249 
19250 		OperatingSystemFont font; // should NEVER be null
19251 
19252 		ubyte attributes; // underline, strike through, display on new block
19253 
19254 		version(Windows)
19255 			const(wchar)[] content;
19256 		else
19257 			const(char)[] content; // this should NEVER have a newline, except at the end
19258 
19259 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19260 
19261 		// could prolly put some spacing around it too like margin / padding
19262 
19263 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19264 			in { assert(font !is null);
19265 			     assert(!font.isNull); }
19266 			do
19267 		{
19268 			this.foreground = f;
19269 			this.background = b;
19270 			this.font = font;
19271 
19272 			this.attributes = attr;
19273 			version(Windows) {
19274 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19275 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19276 				auto buffer = new wchar[](sz);
19277 				this.content = makeWindowsString(c, buffer, conversionFlags);
19278 			} else {
19279 				this.content = c.dup;
19280 			}
19281 		}
19282 
19283 		void draw(ScreenPainter painter) {
19284 			painter.setFont(this.font);
19285 			painter.outlineColor = this.foreground;
19286 			painter.fillColor = Color.transparent;
19287 			foreach(rendered; this.rendered) {
19288 				// the component works in term of baseline,
19289 				// but the painter works in term of upper left bounding box
19290 				// so need to translate that
19291 
19292 				if(this.background.a) {
19293 					painter.fillColor = this.background;
19294 					painter.outlineColor = this.background;
19295 
19296 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19297 
19298 					painter.outlineColor = this.foreground;
19299 					painter.fillColor = Color.transparent;
19300 				}
19301 
19302 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19303 
19304 				// FIXME: strike through, underline, highlight selection, etc.
19305 			}
19306 		}
19307 	}
19308 
19309 	// I could split the parts into words on render
19310 	// for easier word-wrap, each one being an unbreakable "inline-block"
19311 	private TextFlowComponent[] parts;
19312 	private int needsRerenderFrom;
19313 
19314 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19315 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19316 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19317 	}
19318 
19319 	static struct RenderedComponent {
19320 		int startX;
19321 		int startY;
19322 		short width;
19323 		// 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!
19324 		// for individual chars in here you've gotta process on demand
19325 		version(Windows)
19326 			const(wchar)[] slice;
19327 		else
19328 			const(char)[] slice;
19329 	}
19330 
19331 
19332 	void rerender(Rectangle boundingBox) {
19333 		Point baseline = boundingBox.upperLeft;
19334 
19335 		this.boundingBox.left = boundingBox.left;
19336 		this.boundingBox.top = boundingBox.top;
19337 
19338 		auto remainingParts = parts;
19339 
19340 		int largestX;
19341 
19342 
19343 		foreach(part; parts)
19344 			part.font.prepareContext(window);
19345 		scope(exit)
19346 		foreach(part; parts)
19347 			part.font.releaseContext();
19348 
19349 		calculateNextLine:
19350 
19351 		int nextLineHeight = 0;
19352 		int nextBiggestDescent = 0;
19353 
19354 		foreach(part; remainingParts) {
19355 			auto height = part.font.ascent;
19356 			if(height > nextLineHeight)
19357 				nextLineHeight = height;
19358 			if(part.font.descent > nextBiggestDescent)
19359 				nextBiggestDescent = part.font.descent;
19360 			if(part.content.length && part.content[$-1] == '\n')
19361 				break;
19362 		}
19363 
19364 		baseline.y += nextLineHeight;
19365 		auto lineStart = baseline;
19366 
19367 		while(remainingParts.length) {
19368 			remainingParts[0].rendered = null;
19369 
19370 			bool eol;
19371 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19372 				eol = true;
19373 
19374 			// FIXME: word wrap
19375 			auto font = remainingParts[0].font;
19376 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19377 			auto width = font.stringWidth(slice, window);
19378 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19379 
19380 			remainingParts = remainingParts[1 .. $];
19381 			baseline.x += width;
19382 
19383 			if(eol) {
19384 				baseline.y += nextBiggestDescent;
19385 				if(baseline.x > largestX)
19386 					largestX = baseline.x;
19387 				baseline.x = lineStart.x;
19388 				goto calculateNextLine;
19389 			}
19390 		}
19391 
19392 		if(baseline.x > largestX)
19393 			largestX = baseline.x;
19394 
19395 		this.boundingBox.right = largestX;
19396 		this.boundingBox.bottom = baseline.y;
19397 	}
19398 
19399 	// you must call rerender first!
19400 	void draw(ScreenPainter painter) {
19401 		foreach(part; parts) {
19402 			part.draw(painter);
19403 		}
19404 	}
19405 
19406 	struct IdentifyResult {
19407 		TextFlowComponent part;
19408 		int charIndexInPart;
19409 		int totalCharIndex = -1; // if this is -1, it just means the end
19410 
19411 		Rectangle boundingBox;
19412 	}
19413 
19414 	IdentifyResult identify(Point pt, bool exact = false) {
19415 		if(parts.length == 0)
19416 			return IdentifyResult(null, 0);
19417 
19418 		if(pt.y < boundingBox.top) {
19419 			if(exact)
19420 				return IdentifyResult(null, 1);
19421 			return IdentifyResult(parts[0], 0);
19422 		}
19423 		if(pt.y > boundingBox.bottom) {
19424 			if(exact)
19425 				return IdentifyResult(null, 2);
19426 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19427 		}
19428 
19429 		int tci = 0;
19430 
19431 		// I should probably like binary search this or something...
19432 		foreach(ref part; parts) {
19433 			foreach(rendered; part.rendered) {
19434 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19435 				if(rect.contains(pt)) {
19436 					auto x = pt.x - rendered.startX;
19437 					auto estimatedIdx = x / part.font.averageWidth;
19438 
19439 					if(estimatedIdx < 0)
19440 						estimatedIdx = 0;
19441 
19442 					if(estimatedIdx > rendered.slice.length)
19443 						estimatedIdx = cast(int) rendered.slice.length;
19444 
19445 					int idx;
19446 					int x1, x2;
19447 					if(part.font.isMonospace) {
19448 						auto w = part.font.averageWidth;
19449 						if(!exact && x > (estimatedIdx + 1) * w)
19450 							return IdentifyResult(null, 4);
19451 						idx = estimatedIdx;
19452 						x1 = idx * w;
19453 						x2 = (idx + 1) * w;
19454 					} else {
19455 						idx = estimatedIdx;
19456 
19457 						part.font.prepareContext(window);
19458 						scope(exit) part.font.releaseContext();
19459 
19460 						// int iterations;
19461 
19462 						while(true) {
19463 							// iterations++;
19464 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19465 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19466 
19467 							x1 += rendered.startX;
19468 							x2 += rendered.startX;
19469 
19470 							if(pt.x < x1) {
19471 								if(idx == 0) {
19472 									if(exact)
19473 										return IdentifyResult(null, 6);
19474 									else
19475 										break;
19476 								}
19477 								idx--;
19478 							} else if(pt.x > x2) {
19479 								idx++;
19480 								if(idx > rendered.slice.length) {
19481 									if(exact)
19482 										return IdentifyResult(null, 5);
19483 									else
19484 										break;
19485 								}
19486 							} else if(pt.x >= x1 && pt.x <= x2) {
19487 								if(idx)
19488 									idx--; // point it at the original index
19489 								break; // we fit
19490 							}
19491 						}
19492 
19493 						// writeln(iterations)
19494 					}
19495 
19496 
19497 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19498 				}
19499 			}
19500 			tci += cast(int) part.content.length; // FIXME: utf-8?
19501 		}
19502 		return IdentifyResult(null, 3);
19503 	}
19504 
19505 	Rectangle boundingBox; // only set after [rerender]
19506 
19507 	// text will be positioned around the exclusion zone
19508 	static struct ExclusionZone {
19509 
19510 	}
19511 
19512 	ExclusionZone[] exclusionZones;
19513 }
19514 
19515 
19516 // Don't use this yet. When I'm happy with it, I will move it to the
19517 // regular module namespace.
19518 mixin template ExperimentalTextComponent() {
19519 
19520 static:
19521 
19522 	alias Rectangle = arsd.color.Rectangle;
19523 
19524 	struct ForegroundColor {
19525 		Color color;
19526 		alias color this;
19527 
19528 		this(Color c) {
19529 			color = c;
19530 		}
19531 
19532 		this(int r, int g, int b, int a = 255) {
19533 			color = Color(r, g, b, a);
19534 		}
19535 
19536 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19537 			return ForegroundColor(mixin("Color." ~ s));
19538 		}
19539 	}
19540 
19541 	struct BackgroundColor {
19542 		Color color;
19543 		alias color this;
19544 
19545 		this(Color c) {
19546 			color = c;
19547 		}
19548 
19549 		this(int r, int g, int b, int a = 255) {
19550 			color = Color(r, g, b, a);
19551 		}
19552 
19553 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19554 			return BackgroundColor(mixin("Color." ~ s));
19555 		}
19556 	}
19557 
19558 	static class InlineElement {
19559 		string text;
19560 
19561 		BlockElement containingBlock;
19562 
19563 		Color color = Color.black;
19564 		Color backgroundColor = Color.transparent;
19565 		ushort styles;
19566 
19567 		string font;
19568 		int fontSize;
19569 
19570 		int lineHeight;
19571 
19572 		void* identifier;
19573 
19574 		Rectangle boundingBox;
19575 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19576 
19577 		bool isMergeCompatible(InlineElement other) {
19578 			return
19579 				containingBlock is other.containingBlock &&
19580 				color == other.color &&
19581 				backgroundColor == other.backgroundColor &&
19582 				styles == other.styles &&
19583 				font == other.font &&
19584 				fontSize == other.fontSize &&
19585 				lineHeight == other.lineHeight &&
19586 				true;
19587 		}
19588 
19589 		int xOfIndex(size_t index) {
19590 			if(index < letterXs.length)
19591 				return letterXs[index];
19592 			else
19593 				return boundingBox.right;
19594 		}
19595 
19596 		InlineElement clone() {
19597 			auto ie = new InlineElement();
19598 			ie.tupleof = this.tupleof;
19599 			return ie;
19600 		}
19601 
19602 		InlineElement getPreviousInlineElement() {
19603 			InlineElement prev = null;
19604 			foreach(ie; this.containingBlock.parts) {
19605 				if(ie is this)
19606 					break;
19607 				prev = ie;
19608 			}
19609 			if(prev is null) {
19610 				BlockElement pb;
19611 				BlockElement cb = this.containingBlock;
19612 				moar:
19613 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19614 					if(ie is cb)
19615 						break;
19616 					pb = ie;
19617 				}
19618 				if(pb is null)
19619 					return null;
19620 				if(pb.parts.length == 0) {
19621 					cb = pb;
19622 					goto moar;
19623 				}
19624 
19625 				prev = pb.parts[$-1];
19626 
19627 			}
19628 			return prev;
19629 		}
19630 
19631 		InlineElement getNextInlineElement() {
19632 			InlineElement next = null;
19633 			foreach(idx, ie; this.containingBlock.parts) {
19634 				if(ie is this) {
19635 					if(idx + 1 < this.containingBlock.parts.length)
19636 						next = this.containingBlock.parts[idx + 1];
19637 					break;
19638 				}
19639 			}
19640 			if(next is null) {
19641 				BlockElement n;
19642 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19643 					if(ie is this.containingBlock) {
19644 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19645 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19646 						break;
19647 					}
19648 				}
19649 				if(n is null)
19650 					return null;
19651 
19652 				if(n.parts.length)
19653 					next = n.parts[0];
19654 				else {} // FIXME
19655 
19656 			}
19657 			return next;
19658 		}
19659 
19660 	}
19661 
19662 	// Block elements are used entirely for positioning inline elements,
19663 	// which are the things that are actually drawn.
19664 	class BlockElement {
19665 		InlineElement[] parts;
19666 		uint alignment;
19667 
19668 		int whiteSpace; // pre, pre-wrap, wrap
19669 
19670 		TextLayout containingLayout;
19671 
19672 		// inputs
19673 		Point where;
19674 		Size minimumSize;
19675 		Size maximumSize;
19676 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19677 		void* identifier;
19678 
19679 		Rectangle margin;
19680 		Rectangle padding;
19681 
19682 		// outputs
19683 		Rectangle[] boundingBoxes;
19684 	}
19685 
19686 	struct TextIdentifyResult {
19687 		InlineElement element;
19688 		int offset;
19689 
19690 		private TextIdentifyResult fixupNewline() {
19691 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19692 				offset--;
19693 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19694 				offset--;
19695 			}
19696 			return this;
19697 		}
19698 	}
19699 
19700 	class TextLayout {
19701 		BlockElement[] blocks;
19702 		Rectangle boundingBox_;
19703 		Rectangle boundingBox() { return boundingBox_; }
19704 		void boundingBox(Rectangle r) {
19705 			if(r != boundingBox_) {
19706 				boundingBox_ = r;
19707 				layoutInvalidated = true;
19708 			}
19709 		}
19710 
19711 		Rectangle contentBoundingBox() {
19712 			Rectangle r;
19713 			foreach(block; blocks)
19714 			foreach(ie; block.parts) {
19715 				if(ie.boundingBox.right > r.right)
19716 					r.right = ie.boundingBox.right;
19717 				if(ie.boundingBox.bottom > r.bottom)
19718 					r.bottom = ie.boundingBox.bottom;
19719 			}
19720 			return r;
19721 		}
19722 
19723 		BlockElement[] getBlocks() {
19724 			return blocks;
19725 		}
19726 
19727 		InlineElement[] getTexts() {
19728 			InlineElement[] elements;
19729 			foreach(block; blocks)
19730 				elements ~= block.parts;
19731 			return elements;
19732 		}
19733 
19734 		string getPlainText() {
19735 			string text;
19736 			foreach(block; blocks)
19737 				foreach(part; block.parts)
19738 					text ~= part.text;
19739 			return text;
19740 		}
19741 
19742 		string getHtml() {
19743 			return null; // FIXME
19744 		}
19745 
19746 		this(Rectangle boundingBox) {
19747 			this.boundingBox = boundingBox;
19748 		}
19749 
19750 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19751 			auto be = new BlockElement();
19752 			be.containingLayout = this;
19753 			if(after is null)
19754 				blocks ~= be;
19755 			else {
19756 				foreach(idx, b; blocks) {
19757 					if(b is after.containingBlock) {
19758 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19759 						break;
19760 					}
19761 				}
19762 			}
19763 			return be;
19764 		}
19765 
19766 		void clear() {
19767 			blocks = null;
19768 			selectionStart = selectionEnd = caret = Caret.init;
19769 		}
19770 
19771 		void addText(Args...)(Args args) {
19772 			if(blocks.length == 0)
19773 				addBlock();
19774 
19775 			InlineElement ie = new InlineElement();
19776 			foreach(idx, arg; args) {
19777 				static if(is(typeof(arg) == ForegroundColor))
19778 					ie.color = arg;
19779 				else static if(is(typeof(arg) == TextFormat)) {
19780 					if(arg & 0x8000) // ~TextFormat.something turns it off
19781 						ie.styles &= arg;
19782 					else
19783 						ie.styles |= arg;
19784 				} else static if(is(typeof(arg) == string)) {
19785 					static if(idx == 0 && args.length > 1)
19786 						static assert(0, "Put styles before the string.");
19787 					size_t lastLineIndex;
19788 					foreach(cidx, char a; arg) {
19789 						if(a == '\n') {
19790 							ie.text = arg[lastLineIndex .. cidx + 1];
19791 							lastLineIndex = cidx + 1;
19792 							ie.containingBlock = blocks[$-1];
19793 							blocks[$-1].parts ~= ie.clone;
19794 							ie.text = null;
19795 						} else {
19796 
19797 						}
19798 					}
19799 
19800 					ie.text = arg[lastLineIndex .. $];
19801 					ie.containingBlock = blocks[$-1];
19802 					blocks[$-1].parts ~= ie.clone;
19803 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
19804 				}
19805 			}
19806 
19807 			invalidateLayout();
19808 		}
19809 
19810 		void tryMerge(InlineElement into, InlineElement what) {
19811 			if(!into.isMergeCompatible(what)) {
19812 				return; // cannot merge, different configs
19813 			}
19814 
19815 			// cool, can merge, bring text together...
19816 			into.text ~= what.text;
19817 
19818 			// and remove what
19819 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
19820 				if(what.containingBlock.parts[a] is what) {
19821 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
19822 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
19823 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
19824 
19825 				}
19826 			}
19827 
19828 			// FIXME: ensure no other carets have a reference to it
19829 		}
19830 
19831 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
19832 		TextIdentifyResult identify(int x, int y, bool exact = false) {
19833 			TextIdentifyResult inexactMatch;
19834 			foreach(block; blocks) {
19835 				foreach(part; block.parts) {
19836 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
19837 
19838 						// FIXME binary search
19839 						int tidx;
19840 						int lastX;
19841 						foreach_reverse(idxo, lx; part.letterXs) {
19842 							int idx = cast(int) idxo;
19843 							if(lx <= x) {
19844 								if(lastX && lastX - x < x - lx)
19845 									tidx = idx + 1;
19846 								else
19847 									tidx = idx;
19848 								break;
19849 							}
19850 							lastX = lx;
19851 						}
19852 
19853 						return TextIdentifyResult(part, tidx).fixupNewline;
19854 					} else if(!exact) {
19855 						// we're not in the box, but are we on the same line?
19856 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
19857 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
19858 					}
19859 				}
19860 			}
19861 
19862 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
19863 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
19864 
19865 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
19866 		}
19867 
19868 		void moveCaretToPixelCoordinates(int x, int y) {
19869 			auto result = identify(x, y);
19870 			caret.inlineElement = result.element;
19871 			caret.offset = result.offset;
19872 		}
19873 
19874 		void selectToPixelCoordinates(int x, int y) {
19875 			auto result = identify(x, y);
19876 
19877 			if(y < caretLastDrawnY1) {
19878 				// on a previous line, carat is selectionEnd
19879 				selectionEnd = caret;
19880 
19881 				selectionStart = Caret(this, result.element, result.offset);
19882 			} else if(y > caretLastDrawnY2) {
19883 				// on a later line
19884 				selectionStart = caret;
19885 
19886 				selectionEnd = Caret(this, result.element, result.offset);
19887 			} else {
19888 				// on the same line...
19889 				if(x <= caretLastDrawnX) {
19890 					selectionEnd = caret;
19891 					selectionStart = Caret(this, result.element, result.offset);
19892 				} else {
19893 					selectionStart = caret;
19894 					selectionEnd = Caret(this, result.element, result.offset);
19895 				}
19896 
19897 			}
19898 		}
19899 
19900 
19901 		/// Call this if the inputs change. It will reflow everything
19902 		void redoLayout(ScreenPainter painter) {
19903 			//painter.setClipRectangle(boundingBox);
19904 			auto pos = Point(boundingBox.left, boundingBox.top);
19905 
19906 			int lastHeight;
19907 			void nl() {
19908 				pos.x = boundingBox.left;
19909 				pos.y += lastHeight;
19910 			}
19911 			foreach(block; blocks) {
19912 				nl();
19913 				foreach(part; block.parts) {
19914 					part.letterXs = null;
19915 
19916 					auto size = painter.textSize(part.text);
19917 					version(Windows)
19918 						if(part.text.length && part.text[$-1] == '\n')
19919 							size.height /= 2; // windows counts the new line at the end, but we don't want that
19920 
19921 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
19922 
19923 					foreach(idx, char c; part.text) {
19924 							// FIXME: unicode
19925 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
19926 					}
19927 
19928 					pos.x += size.width;
19929 					if(pos.x >= boundingBox.right) {
19930 						pos.y += size.height;
19931 						pos.x = boundingBox.left;
19932 						lastHeight = 0;
19933 					} else {
19934 						lastHeight = size.height;
19935 					}
19936 
19937 					if(part.text.length && part.text[$-1] == '\n')
19938 						nl();
19939 				}
19940 			}
19941 
19942 			layoutInvalidated = false;
19943 		}
19944 
19945 		bool layoutInvalidated = true;
19946 		void invalidateLayout() {
19947 			layoutInvalidated = true;
19948 		}
19949 
19950 // FIXME: caret can remain sometimes when inserting
19951 // FIXME: inserting at the beginning once you already have something can eff it up.
19952 		void drawInto(ScreenPainter painter, bool focused = false) {
19953 			if(layoutInvalidated)
19954 				redoLayout(painter);
19955 			foreach(block; blocks) {
19956 				foreach(part; block.parts) {
19957 					painter.outlineColor = part.color;
19958 					painter.fillColor = part.backgroundColor;
19959 
19960 					auto pos = part.boundingBox.upperLeft;
19961 					auto size = part.boundingBox.size;
19962 
19963 					painter.drawText(pos, part.text);
19964 					if(part.styles & TextFormat.underline)
19965 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
19966 					if(part.styles & TextFormat.strikethrough)
19967 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
19968 				}
19969 			}
19970 
19971 			// on every redraw, I will force the caret to be
19972 			// redrawn too, in order to eliminate perceived lag
19973 			// when moving around with the mouse.
19974 			eraseCaret(painter);
19975 
19976 			if(focused) {
19977 				highlightSelection(painter);
19978 				drawCaret(painter);
19979 			}
19980 		}
19981 
19982 		Color selectionXorColor = Color(255, 255, 127);
19983 
19984 		void highlightSelection(ScreenPainter painter) {
19985 			if(selectionStart is selectionEnd)
19986 				return; // no selection
19987 
19988 			if(selectionStart.inlineElement is null) return;
19989 			if(selectionEnd.inlineElement is null) return;
19990 
19991 			assert(selectionStart.inlineElement !is null);
19992 			assert(selectionEnd.inlineElement !is null);
19993 
19994 			painter.rasterOp = RasterOp.xor;
19995 			painter.outlineColor = Color.transparent;
19996 			painter.fillColor = selectionXorColor;
19997 
19998 			auto at = selectionStart.inlineElement;
19999 			auto atOffset = selectionStart.offset;
20000 			bool done;
20001 			while(at) {
20002 				auto box = at.boundingBox;
20003 				if(atOffset < at.letterXs.length)
20004 					box.left = at.letterXs[atOffset];
20005 
20006 				if(at is selectionEnd.inlineElement) {
20007 					if(selectionEnd.offset < at.letterXs.length)
20008 						box.right = at.letterXs[selectionEnd.offset];
20009 					done = true;
20010 				}
20011 
20012 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20013 
20014 				if(done)
20015 					break;
20016 
20017 				at = at.getNextInlineElement();
20018 				atOffset = 0;
20019 			}
20020 		}
20021 
20022 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20023 		bool caretShowingOnScreen = false;
20024 		void drawCaret(ScreenPainter painter) {
20025 			//painter.setClipRectangle(boundingBox);
20026 			int x, y1, y2;
20027 			if(caret.inlineElement is null) {
20028 				x = boundingBox.left;
20029 				y1 = boundingBox.top + 2;
20030 				y2 = boundingBox.top + painter.fontHeight;
20031 			} else {
20032 				x = caret.inlineElement.xOfIndex(caret.offset);
20033 				y1 = caret.inlineElement.boundingBox.top + 2;
20034 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20035 			}
20036 
20037 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20038 				eraseCaret(painter);
20039 
20040 			painter.pen = Pen(Color.white, 1);
20041 			painter.rasterOp = RasterOp.xor;
20042 			painter.drawLine(
20043 				Point(x, y1),
20044 				Point(x, y2)
20045 			);
20046 			painter.rasterOp = RasterOp.normal;
20047 			caretShowingOnScreen = !caretShowingOnScreen;
20048 
20049 			if(caretShowingOnScreen) {
20050 				caretLastDrawnX = x;
20051 				caretLastDrawnY1 = y1;
20052 				caretLastDrawnY2 = y2;
20053 			}
20054 		}
20055 
20056 		Rectangle caretBoundingBox() {
20057 			int x, y1, y2;
20058 			if(caret.inlineElement is null) {
20059 				x = boundingBox.left;
20060 				y1 = boundingBox.top + 2;
20061 				y2 = boundingBox.top + 16;
20062 			} else {
20063 				x = caret.inlineElement.xOfIndex(caret.offset);
20064 				y1 = caret.inlineElement.boundingBox.top + 2;
20065 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20066 			}
20067 
20068 			return Rectangle(x, y1, x + 1, y2);
20069 		}
20070 
20071 		void eraseCaret(ScreenPainter painter) {
20072 			//painter.setClipRectangle(boundingBox);
20073 			if(!caretShowingOnScreen) return;
20074 			painter.pen = Pen(Color.white, 1);
20075 			painter.rasterOp = RasterOp.xor;
20076 			painter.drawLine(
20077 				Point(caretLastDrawnX, caretLastDrawnY1),
20078 				Point(caretLastDrawnX, caretLastDrawnY2)
20079 			);
20080 
20081 			caretShowingOnScreen = false;
20082 			painter.rasterOp = RasterOp.normal;
20083 		}
20084 
20085 		/// Caret movement api
20086 		/// These should give the user a logical result based on what they see on screen...
20087 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20088 		void moveUp() {
20089 			if(caret.inlineElement is null) return;
20090 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20091 			auto y = caret.inlineElement.boundingBox.top + 2;
20092 
20093 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20094 			if(y < 0)
20095 				return;
20096 
20097 			auto i = identify(x, y);
20098 
20099 			if(i.element) {
20100 				caret.inlineElement = i.element;
20101 				caret.offset = i.offset;
20102 			}
20103 		}
20104 		void moveDown() {
20105 			if(caret.inlineElement is null) return;
20106 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20107 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20108 
20109 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20110 
20111 			auto i = identify(x, y);
20112 			if(i.element) {
20113 				caret.inlineElement = i.element;
20114 				caret.offset = i.offset;
20115 			}
20116 		}
20117 		void moveLeft() {
20118 			if(caret.inlineElement is null) return;
20119 			if(caret.offset)
20120 				caret.offset--;
20121 			else {
20122 				auto p = caret.inlineElement.getPreviousInlineElement();
20123 				if(p) {
20124 					caret.inlineElement = p;
20125 					if(p.text.length && p.text[$-1] == '\n')
20126 						caret.offset = cast(int) p.text.length - 1;
20127 					else
20128 						caret.offset = cast(int) p.text.length;
20129 				}
20130 			}
20131 		}
20132 		void moveRight() {
20133 			if(caret.inlineElement is null) return;
20134 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20135 				caret.offset++;
20136 			} else {
20137 				auto p = caret.inlineElement.getNextInlineElement();
20138 				if(p) {
20139 					caret.inlineElement = p;
20140 					caret.offset = 0;
20141 				}
20142 			}
20143 		}
20144 		void moveHome() {
20145 			if(caret.inlineElement is null) return;
20146 			auto x = 0;
20147 			auto y = caret.inlineElement.boundingBox.top + 2;
20148 
20149 			auto i = identify(x, y);
20150 
20151 			if(i.element) {
20152 				caret.inlineElement = i.element;
20153 				caret.offset = i.offset;
20154 			}
20155 		}
20156 		void moveEnd() {
20157 			if(caret.inlineElement is null) return;
20158 			auto x = int.max;
20159 			auto y = caret.inlineElement.boundingBox.top + 2;
20160 
20161 			auto i = identify(x, y);
20162 
20163 			if(i.element) {
20164 				caret.inlineElement = i.element;
20165 				caret.offset = i.offset;
20166 			}
20167 
20168 		}
20169 		void movePageUp(ref Caret caret) {}
20170 		void movePageDown(ref Caret caret) {}
20171 
20172 		void moveDocumentStart(ref Caret caret) {
20173 			if(blocks.length && blocks[0].parts.length)
20174 				caret = Caret(this, blocks[0].parts[0], 0);
20175 			else
20176 				caret = Caret.init;
20177 		}
20178 
20179 		void moveDocumentEnd(ref Caret caret) {
20180 			if(blocks.length) {
20181 				auto parts = blocks[$-1].parts;
20182 				if(parts.length) {
20183 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20184 				} else {
20185 					caret = Caret.init;
20186 				}
20187 			} else
20188 				caret = Caret.init;
20189 		}
20190 
20191 		void deleteSelection() {
20192 			if(selectionStart is selectionEnd)
20193 				return;
20194 
20195 			if(selectionStart.inlineElement is null) return;
20196 			if(selectionEnd.inlineElement is null) return;
20197 
20198 			assert(selectionStart.inlineElement !is null);
20199 			assert(selectionEnd.inlineElement !is null);
20200 
20201 			auto at = selectionStart.inlineElement;
20202 
20203 			if(selectionEnd.inlineElement is at) {
20204 				// same element, need to chop out
20205 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20206 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20207 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20208 			} else {
20209 				// different elements, we can do it with slicing
20210 				at.text = at.text[0 .. selectionStart.offset];
20211 				if(selectionStart.offset < at.letterXs.length)
20212 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20213 
20214 				at = at.getNextInlineElement();
20215 
20216 				while(at) {
20217 					if(at is selectionEnd.inlineElement) {
20218 						at.text = at.text[selectionEnd.offset .. $];
20219 						if(selectionEnd.offset < at.letterXs.length)
20220 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20221 						selectionEnd.offset = 0;
20222 						break;
20223 					} else {
20224 						auto cfd = at;
20225 						cfd.text = null; // delete the whole thing
20226 
20227 						at = at.getNextInlineElement();
20228 
20229 						if(cfd.text.length == 0) {
20230 							// and remove cfd
20231 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20232 								if(cfd.containingBlock.parts[a] is cfd) {
20233 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20234 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20235 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20236 
20237 								}
20238 							}
20239 						}
20240 					}
20241 				}
20242 			}
20243 
20244 			caret = selectionEnd;
20245 			selectNone();
20246 
20247 			invalidateLayout();
20248 
20249 		}
20250 
20251 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20252 		void insert(in char[] text) {
20253 			foreach(dchar ch; text)
20254 				insert(ch);
20255 		}
20256 		/// ditto
20257 		void insert(dchar ch) {
20258 
20259 			bool selectionDeleted = false;
20260 			if(selectionStart !is selectionEnd) {
20261 				deleteSelection();
20262 				selectionDeleted = true;
20263 			}
20264 
20265 			if(ch == 127) {
20266 				delete_();
20267 				return;
20268 			}
20269 			if(ch == 8) {
20270 				if(!selectionDeleted)
20271 					backspace();
20272 				return;
20273 			}
20274 
20275 			invalidateLayout();
20276 
20277 			if(ch == 13) ch = 10;
20278 			auto e = caret.inlineElement;
20279 			if(e is null) {
20280 				addText("" ~ cast(char) ch) ; // FIXME
20281 				return;
20282 			}
20283 
20284 			if(caret.offset == e.text.length) {
20285 				e.text ~= cast(char) ch; // FIXME
20286 				caret.offset++;
20287 				if(ch == 10) {
20288 					auto c = caret.inlineElement.clone;
20289 					c.text = null;
20290 					c.letterXs = null;
20291 					insertPartAfter(c,e);
20292 					caret = Caret(this, c, 0);
20293 				}
20294 			} else {
20295 				// FIXME cast char sucks
20296 				if(ch == 10) {
20297 					auto c = caret.inlineElement.clone;
20298 					c.text = e.text[caret.offset .. $];
20299 					if(caret.offset < c.letterXs.length)
20300 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20301 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20302 					if(caret.offset <= e.letterXs.length) {
20303 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20304 					}
20305 					insertPartAfter(c,e);
20306 					caret = Caret(this, c, 0);
20307 				} else {
20308 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20309 					caret.offset++;
20310 				}
20311 			}
20312 		}
20313 
20314 		void insertPartAfter(InlineElement what, InlineElement where) {
20315 			foreach(idx, p; where.containingBlock.parts) {
20316 				if(p is where) {
20317 					if(idx + 1 == where.containingBlock.parts.length)
20318 						where.containingBlock.parts ~= what;
20319 					else
20320 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20321 					return;
20322 				}
20323 			}
20324 		}
20325 
20326 		void cleanupStructures() {
20327 			for(size_t i = 0; i < blocks.length; i++) {
20328 				auto block = blocks[i];
20329 				for(size_t a = 0; a < block.parts.length; a++) {
20330 					auto part = block.parts[a];
20331 					if(part.text.length == 0) {
20332 						for(size_t b = a; b < block.parts.length - 1; b++)
20333 							block.parts[b] = block.parts[b+1];
20334 						block.parts = block.parts[0 .. $-1];
20335 					}
20336 				}
20337 				if(block.parts.length == 0) {
20338 					for(size_t a = i; a < blocks.length - 1; a++)
20339 						blocks[a] = blocks[a+1];
20340 					blocks = blocks[0 .. $-1];
20341 				}
20342 			}
20343 		}
20344 
20345 		void backspace() {
20346 			try_again:
20347 			auto e = caret.inlineElement;
20348 			if(e is null)
20349 				return;
20350 			if(caret.offset == 0) {
20351 				auto prev = e.getPreviousInlineElement();
20352 				if(prev is null)
20353 					return;
20354 				auto newOffset = cast(int) prev.text.length;
20355 				tryMerge(prev, e);
20356 				caret.inlineElement = prev;
20357 				caret.offset = prev is null ? 0 : newOffset;
20358 
20359 				goto try_again;
20360 			} else if(caret.offset == e.text.length) {
20361 				e.text = e.text[0 .. $-1];
20362 				caret.offset--;
20363 			} else {
20364 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20365 				caret.offset--;
20366 			}
20367 			//cleanupStructures();
20368 
20369 			invalidateLayout();
20370 		}
20371 		void delete_() {
20372 			if(selectionStart !is selectionEnd)
20373 				deleteSelection();
20374 			else {
20375 				auto before = caret;
20376 				moveRight();
20377 				if(caret != before) {
20378 					backspace();
20379 				}
20380 			}
20381 
20382 			invalidateLayout();
20383 		}
20384 		void overstrike() {}
20385 
20386 		/// Selection API. See also: caret movement.
20387 		void selectAll() {
20388 			moveDocumentStart(selectionStart);
20389 			moveDocumentEnd(selectionEnd);
20390 		}
20391 		bool selectNone() {
20392 			if(selectionStart != selectionEnd) {
20393 				selectionStart = selectionEnd = Caret.init;
20394 				return true;
20395 			}
20396 			return false;
20397 		}
20398 
20399 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20400 		/// They will modify the current selection if there is one and will splice one in if needed.
20401 		void changeAttributes() {}
20402 
20403 
20404 		/// Text search api. They manipulate the selection and/or caret.
20405 		void findText(string text) {}
20406 		void findIndex(size_t textIndex) {}
20407 
20408 		// sample event handlers
20409 
20410 		void handleEvent(KeyEvent event) {
20411 			//if(event.type == KeyEvent.Type.KeyPressed) {
20412 
20413 			//}
20414 		}
20415 
20416 		void handleEvent(dchar ch) {
20417 
20418 		}
20419 
20420 		void handleEvent(MouseEvent event) {
20421 
20422 		}
20423 
20424 		bool contentEditable; // can it be edited?
20425 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20426 		bool contentSelectable; // selectable?
20427 
20428 		Caret caret;
20429 		Caret selectionStart;
20430 		Caret selectionEnd;
20431 
20432 		bool insertMode;
20433 	}
20434 
20435 	struct Caret {
20436 		TextLayout layout;
20437 		InlineElement inlineElement;
20438 		int offset;
20439 	}
20440 
20441 	enum TextFormat : ushort {
20442 		// decorations
20443 		underline = 1,
20444 		strikethrough = 2,
20445 
20446 		// font selectors
20447 
20448 		bold = 0x4000 | 1, // weight 700
20449 		light = 0x4000 | 2, // weight 300
20450 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20451 		// bold | light is really invalid but should give weight 500
20452 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20453 
20454 		italic = 0x4000 | 8,
20455 		smallcaps = 0x4000 | 16,
20456 	}
20457 
20458 	void* findFont(string family, int weight, TextFormat formats) {
20459 		return null;
20460 	}
20461 
20462 }
20463 
20464 /++
20465 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20466 
20467 	History:
20468 		Added February 19, 2021
20469 +/
20470 /// Group: drag_and_drop
20471 interface DropHandler {
20472 	/++
20473 		Called when the drag enters the handler's area.
20474 	+/
20475 	DragAndDropAction dragEnter(DropPackage*);
20476 	/++
20477 		Called when the drag leaves the handler's area or is
20478 		cancelled. You should free your resources when this is called.
20479 	+/
20480 	void dragLeave();
20481 	/++
20482 		Called continually as the drag moves over the handler's area.
20483 
20484 		Returns: feedback to the dragger
20485 	+/
20486 	DropParameters dragOver(Point pt);
20487 	/++
20488 		The user dropped the data and you should process it now. You can
20489 		access the data through the given [DropPackage].
20490 	+/
20491 	void drop(scope DropPackage*);
20492 	/++
20493 		Called when the drop is complete. You should free whatever temporary
20494 		resources you were using. It is often reasonable to simply forward
20495 		this call to [dragLeave].
20496 	+/
20497 	void finish();
20498 
20499 	/++
20500 		Parameters returned by [DropHandler.drop].
20501 	+/
20502 	static struct DropParameters {
20503 		/++
20504 			Acceptable action over this area.
20505 		+/
20506 		DragAndDropAction action;
20507 		/++
20508 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20509 
20510 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20511 		+/
20512 		Rectangle consistentWithin;
20513 	}
20514 }
20515 
20516 /++
20517 	History:
20518 		Added February 19, 2021
20519 +/
20520 /// Group: drag_and_drop
20521 enum DragAndDropAction {
20522 	none = 0,
20523 	copy,
20524 	move,
20525 	link,
20526 	ask,
20527 	custom
20528 }
20529 
20530 /++
20531 	An opaque structure representing dropped data. It contains
20532 	private, platform-specific data that your `drop` function
20533 	should simply forward.
20534 
20535 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20536 
20537 	History:
20538 		Added February 19, 2021
20539 +/
20540 /// Group: drag_and_drop
20541 struct DropPackage {
20542 	/++
20543 		Lists the available formats as magic numbers. You should compare these
20544 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20545 		understand the passed data.
20546 	+/
20547 	DraggableData.FormatId[] availableFormats() {
20548 		version(X11) {
20549 			return xFormats;
20550 		} else version(Windows) {
20551 			if(pDataObj is null)
20552 				return null;
20553 
20554 			typeof(return) ret;
20555 
20556 			IEnumFORMATETC ef;
20557 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20558 				FORMATETC fmt;
20559 				ULONG fetched;
20560 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20561 					if(fetched == 0)
20562 						break;
20563 
20564 					if(fmt.lindex != -1)
20565 						continue;
20566 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20567 						continue;
20568 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20569 						continue;
20570 
20571 					ret ~= fmt.cfFormat;
20572 				}
20573 			}
20574 
20575 			return ret;
20576 		}
20577 	}
20578 
20579 	/++
20580 		Gets data from the drop and optionally accepts it.
20581 
20582 		Returns:
20583 			void because the data is fed asynchronously through the `dg` parameter.
20584 
20585 		Params:
20586 			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.
20587 
20588 			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.
20589 
20590 			Calling `getData` again after accepting a drop is not permitted.
20591 
20592 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20593 
20594 			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.
20595 
20596 		Throws:
20597 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20598 
20599 		History:
20600 			Included in first release of [DropPackage].
20601 	+/
20602 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20603 		version(X11) {
20604 
20605 			auto display = XDisplayConnection.get();
20606 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20607 			auto best = format;
20608 
20609 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20610 
20611 				XDisplay* display;
20612 				Atom selectionAtom;
20613 				DraggableData.FormatId best;
20614 				DraggableData.FormatId format;
20615 				void delegate(scope ubyte[] data) dg;
20616 				DragAndDropAction acceptedAction;
20617 				Window sourceWindow;
20618 				SimpleWindow win;
20619 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20620 					this.display = display;
20621 					this.win = win;
20622 					this.sourceWindow = sourceWindow;
20623 					this.format = format;
20624 					this.selectionAtom = selectionAtom;
20625 					this.best = best;
20626 					this.dg = dg;
20627 					this.acceptedAction = acceptedAction;
20628 				}
20629 
20630 
20631 				mixin X11GetSelectionHandler_Basics;
20632 
20633 				void handleData(Atom target, in ubyte[] data) {
20634 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20635 
20636 					dg(cast(ubyte[]) data);
20637 
20638 					if(acceptedAction != DragAndDropAction.none) {
20639 						auto display = XDisplayConnection.get;
20640 
20641 						XClientMessageEvent xclient;
20642 
20643 						xclient.type = EventType.ClientMessage;
20644 						xclient.window = sourceWindow;
20645 						xclient.message_type = GetAtom!"XdndFinished"(display);
20646 						xclient.format = 32;
20647 						xclient.data.l[0] = win.impl.window;
20648 						xclient.data.l[1] = 1; // drop successful
20649 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20650 
20651 						XSendEvent(
20652 							display,
20653 							sourceWindow,
20654 							false,
20655 							EventMask.NoEventMask,
20656 							cast(XEvent*) &xclient
20657 						);
20658 
20659 						XFlush(display);
20660 					}
20661 				}
20662 
20663 				Atom findBestFormat(Atom[] answer) {
20664 					Atom best = None;
20665 					foreach(option; answer) {
20666 						if(option == format) {
20667 							best = option;
20668 							break;
20669 						}
20670 						/*
20671 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20672 							best = option;
20673 							break;
20674 						} else if(option == XA_STRING) {
20675 							best = option;
20676 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20677 							best = option;
20678 						}
20679 						*/
20680 					}
20681 					return best;
20682 				}
20683 			}
20684 
20685 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20686 
20687 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20688 
20689 		} else version(Windows) {
20690 
20691 			// clean up like DragLeave
20692 			// pass effect back up
20693 
20694 			FORMATETC t;
20695 			assert(format >= 0 && format <= ushort.max);
20696 			t.cfFormat = cast(ushort) format;
20697 			t.lindex = -1;
20698 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20699 			t.tymed = TYMED.TYMED_HGLOBAL;
20700 
20701 			STGMEDIUM m;
20702 
20703 			if(pDataObj.GetData(&t, &m) != S_OK) {
20704 				// fail
20705 			} else {
20706 				// succeed, take the data and clean up
20707 
20708 				// FIXME: ensure it is legit HGLOBAL
20709 				auto handle = m.hGlobal;
20710 
20711 				if(handle) {
20712 					auto sz = GlobalSize(handle);
20713 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20714 						scope(exit) GlobalUnlock(handle);
20715 						scope(exit) GlobalFree(handle);
20716 
20717 						auto data = ptr[0 .. sz];
20718 
20719 						dg(data);
20720 					}
20721 				}
20722 			}
20723 		}
20724 	}
20725 
20726 	private:
20727 
20728 	version(X11) {
20729 		SimpleWindow win;
20730 		Window sourceWindow;
20731 		Time dataTimestamp;
20732 
20733 		Atom[] xFormats;
20734 	}
20735 	version(Windows) {
20736 		IDataObject pDataObj;
20737 	}
20738 }
20739 
20740 /++
20741 	A generic helper base class for making a drop handler with a preference list of custom types.
20742 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20743 	droppers too.
20744 
20745 	It assumes the whole window it used, but you can subclass to change that.
20746 
20747 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20748 
20749 	History:
20750 		Added February 19, 2021
20751 +/
20752 /// Group: drag_and_drop
20753 class GenericDropHandlerBase : DropHandler {
20754 	// no fancy state here so no need to do anything here
20755 	void finish() { }
20756 	void dragLeave() { }
20757 
20758 	private DragAndDropAction acceptedAction;
20759 	private DraggableData.FormatId acceptedFormat;
20760 	private void delegate(scope ubyte[]) acceptedHandler;
20761 
20762 	struct FormatHandler {
20763 		DraggableData.FormatId format;
20764 		void delegate(scope ubyte[]) handler;
20765 	}
20766 
20767 	protected abstract FormatHandler[] formatHandlers();
20768 
20769 	DragAndDropAction dragEnter(DropPackage* pkg) {
20770 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20771 		foreach(fmt; formatHandlers())
20772 		foreach(f; pkg.availableFormats())
20773 			if(f == fmt.format) {
20774 				acceptedFormat = f;
20775 				acceptedHandler = fmt.handler;
20776 				return acceptedAction = DragAndDropAction.copy;
20777 			}
20778 		return acceptedAction = DragAndDropAction.none;
20779 	}
20780 	DropParameters dragOver(Point pt) {
20781 		return DropParameters(acceptedAction);
20782 	}
20783 
20784 	void drop(scope DropPackage* dropPackage) {
20785 		if(!acceptedFormat || acceptedHandler is null) {
20786 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20787 			return; // prolly shouldn't happen anyway...
20788 		}
20789 
20790 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20791 	}
20792 }
20793 
20794 /++
20795 	A simple handler for making your window accept drops of plain text.
20796 
20797 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20798 
20799 	History:
20800 		Added February 22, 2021
20801 +/
20802 /// Group: drag_and_drop
20803 class TextDropHandler : GenericDropHandlerBase {
20804 	private void delegate(in char[] text) dg;
20805 
20806 	/++
20807 
20808 	+/
20809 	this(void delegate(in char[] text) dg) {
20810 		this.dg = dg;
20811 	}
20812 
20813 	protected override FormatHandler[] formatHandlers() {
20814 		version(X11)
20815 			return [
20816 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
20817 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
20818 			];
20819 		else version(Windows)
20820 			return [
20821 				FormatHandler(CF_UNICODETEXT, &translator),
20822 			];
20823 	}
20824 
20825 	private void translator(scope ubyte[] data) {
20826 		version(X11)
20827 			dg(cast(char[]) data);
20828 		else version(Windows)
20829 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
20830 	}
20831 }
20832 
20833 /++
20834 	A simple handler for making your window accept drops of files, issued to you as file names.
20835 
20836 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20837 
20838 	History:
20839 		Added February 22, 2021
20840 +/
20841 /// Group: drag_and_drop
20842 
20843 class FilesDropHandler : GenericDropHandlerBase {
20844 	private void delegate(in char[][]) dg;
20845 
20846 	/++
20847 
20848 	+/
20849 	this(void delegate(in char[][] fileNames) dg) {
20850 		this.dg = dg;
20851 	}
20852 
20853 	protected override FormatHandler[] formatHandlers() {
20854 		version(X11)
20855 			return [
20856 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
20857 			];
20858 		else version(Windows)
20859 			return [
20860 				FormatHandler(CF_HDROP, &translator),
20861 			];
20862 	}
20863 
20864 	private void translator(scope ubyte[] data) {
20865 		version(X11) {
20866 			char[] listString = cast(char[]) data;
20867 			char[][16] buffer;
20868 			int count;
20869 			char[][] result = buffer[];
20870 
20871 			void commit(char[] s) {
20872 				if(count == result.length)
20873 					result.length += 16;
20874 				if(s.length > 7 && s[0 ..7] == "file://")
20875 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
20876 				result[count++] = s;
20877 			}
20878 
20879 			size_t last;
20880 			foreach(idx, char c; listString) {
20881 				if(c == '\n') {
20882 					commit(listString[last .. idx - 1]); // a \r
20883 					last = idx + 1; // a \n
20884 				}
20885 			}
20886 
20887 			if(last < listString.length) {
20888 				commit(listString[last .. $]);
20889 			}
20890 
20891 			// FIXME: they are uris now, should I translate it to local file names?
20892 			// of course the host name is supposed to be there cuz of X rokking...
20893 
20894 			dg(result[0 .. count]);
20895 		} else version(Windows) {
20896 
20897 			static struct DROPFILES {
20898 				DWORD pFiles;
20899 				POINT pt;
20900 				BOOL  fNC;
20901 				BOOL  fWide;
20902 			}
20903 
20904 
20905 			const(char)[][16] buffer;
20906 			int count;
20907 			const(char)[][] result = buffer[];
20908 			size_t last;
20909 
20910 			void commitA(in char[] stuff) {
20911 				if(count == result.length)
20912 					result.length += 16;
20913 				result[count++] = stuff;
20914 			}
20915 
20916 			void commitW(in wchar[] stuff) {
20917 				commitA(makeUtf8StringFromWindowsString(stuff));
20918 			}
20919 
20920 			void magic(T)(T chars) {
20921 				size_t idx;
20922 				while(chars[idx]) {
20923 					last = idx;
20924 					while(chars[idx]) {
20925 						idx++;
20926 					}
20927 					static if(is(T == char*))
20928 						commitA(chars[last .. idx]);
20929 					else
20930 						commitW(chars[last .. idx]);
20931 					idx++;
20932 				}
20933 			}
20934 
20935 			auto df = cast(DROPFILES*) data.ptr;
20936 			if(df.fWide) {
20937 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
20938 				magic(chars);
20939 			} else {
20940 				char* chars = cast(char*) (data.ptr + df.pFiles);
20941 				magic(chars);
20942 			}
20943 			dg(result[0 .. count]);
20944 		}
20945 	}
20946 }
20947 
20948 /++
20949 	Interface to describe data being dragged. See also [draggable] helper function.
20950 
20951 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20952 
20953 	History:
20954 		Added February 19, 2021
20955 +/
20956 interface DraggableData {
20957 	version(X11)
20958 		alias FormatId = Atom;
20959 	else
20960 		alias FormatId = uint;
20961 	/++
20962 		Gets the platform-specific FormatId associated with the given named format.
20963 
20964 		This may be a MIME type, but may also be other various strings defined by the
20965 		programs you want to interoperate with.
20966 
20967 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
20968 		and convert it to some particular type for you.
20969 	+/
20970 	static FormatId getFormatId(string name)() {
20971 		version(X11)
20972 			return GetAtom!name(XDisplayConnection.get);
20973 		else version(Windows) {
20974 			static UINT cache;
20975 			if(!cache)
20976 				cache = RegisterClipboardFormatA(name);
20977 			return cache;
20978 		} else
20979 			throw new NotYetImplementedException();
20980 	}
20981 
20982 	/++
20983 		Looks up a string to represent the name for the given format, if there is one.
20984 
20985 		You should avoid using this function because it is slow. It is provided more for
20986 		debugging than for primary use.
20987 	+/
20988 	static string getFormatName(FormatId format) {
20989 		version(X11) {
20990 			if(format == 0)
20991 				return "None";
20992 			else
20993 				return getAtomName(format, XDisplayConnection.get);
20994 		} else version(Windows) {
20995 			switch(format) {
20996 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
20997 				case CF_DIBV5: return "CF_DIBV5";
20998 				case CF_RIFF: return "CF_RIFF";
20999 				case CF_WAVE: return "CF_WAVE";
21000 				case CF_HDROP: return "CF_HDROP";
21001 				default:
21002 					char[1024] name;
21003 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21004 					return name[0 .. count].idup;
21005 			}
21006 		}
21007 	}
21008 
21009 	FormatId[] availableFormats();
21010 	// Return the slice of data you filled, empty slice if done.
21011 	// this is to support the incremental thing
21012 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21013 
21014 	size_t dataLength(FormatId format);
21015 }
21016 
21017 /++
21018 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21019 
21020 	History:
21021 		Added February 19, 2021
21022 +/
21023 DraggableData draggable(string s) {
21024 	version(X11)
21025 	return new class X11SetSelectionHandler_Text, DraggableData {
21026 		this() {
21027 			super(s);
21028 		}
21029 
21030 		override FormatId[] availableFormats() {
21031 			return X11SetSelectionHandler_Text.availableFormats();
21032 		}
21033 
21034 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21035 			return X11SetSelectionHandler_Text.getData(format, data);
21036 		}
21037 
21038 		size_t dataLength(FormatId format) {
21039 			return s.length;
21040 		}
21041 	};
21042 	version(Windows)
21043 	return new class DraggableData {
21044 		FormatId[] availableFormats() {
21045 			return [CF_UNICODETEXT];
21046 		}
21047 
21048 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21049 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21050 		}
21051 
21052 		size_t dataLength(FormatId format) {
21053 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21054 		}
21055 	};
21056 }
21057 
21058 /++
21059 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21060 
21061 	History:
21062 		Added February 19, 2021
21063 +/
21064 /// Group: drag_and_drop
21065 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21066 in {
21067 	assert(window !is null);
21068 	assert(handler !is null);
21069 }
21070 do
21071 {
21072 	version(X11) {
21073 		auto sh = cast(X11SetSelectionHandler) handler;
21074 		if(sh is null) {
21075 			// gotta make my own adapter.
21076 			sh = new class X11SetSelectionHandler {
21077 				mixin X11SetSelectionHandler_Basics;
21078 
21079 				Atom[] availableFormats() { return handler.availableFormats(); }
21080 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21081 					return handler.getData(format, data);
21082 				}
21083 
21084 				// since the drop selection is only ever used once it isn't important
21085 				// to reset it.
21086 				void done() {}
21087 			};
21088 		}
21089 		return doDragDropX11(window, sh, action);
21090 	} else version(Windows) {
21091 		return doDragDropWindows(window, handler, action);
21092 	} else throw new NotYetImplementedException();
21093 }
21094 
21095 version(Windows)
21096 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21097 	IDataObject obj = new class IDataObject {
21098 		ULONG refCount;
21099 		ULONG AddRef() {
21100 			return ++refCount;
21101 		}
21102 		ULONG Release() {
21103 			return --refCount;
21104 		}
21105 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21106 			if (IID_IUnknown == *riid) {
21107 				*ppv = cast(void*) cast(IUnknown) this;
21108 			}
21109 			else if (IID_IDataObject == *riid) {
21110 				*ppv = cast(void*) cast(IDataObject) this;
21111 			}
21112 			else {
21113 				*ppv = null;
21114 				return E_NOINTERFACE;
21115 			}
21116 
21117 			AddRef();
21118 			return NOERROR;
21119 		}
21120 
21121 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21122 			//  writeln("Advise");
21123 			return E_NOTIMPL;
21124 		}
21125 		HRESULT DUnadvise(DWORD dwConnection) {
21126 			return E_NOTIMPL;
21127 		}
21128 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21129 			//  writeln("EnumDAdvise");
21130 			return OLE_E_ADVISENOTSUPPORTED;
21131 		}
21132 		// tell what formats it supports
21133 
21134 		FORMATETC[] types;
21135 		this() {
21136 			FORMATETC t;
21137 			foreach(ty; handler.availableFormats()) {
21138 				assert(ty <= ushort.max && ty >= 0);
21139 				t.cfFormat = cast(ushort) ty;
21140 				t.lindex = -1;
21141 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21142 				t.tymed = TYMED.TYMED_HGLOBAL;
21143 			}
21144 			types ~= t;
21145 		}
21146 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21147 			if(dwDirection == DATADIR.DATADIR_GET) {
21148 				*ppenumFormatEtc = new class IEnumFORMATETC {
21149 					ULONG refCount;
21150 					ULONG AddRef() {
21151 						return ++refCount;
21152 					}
21153 					ULONG Release() {
21154 						return --refCount;
21155 					}
21156 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21157 						if (IID_IUnknown == *riid) {
21158 							*ppv = cast(void*) cast(IUnknown) this;
21159 						}
21160 						else if (IID_IEnumFORMATETC == *riid) {
21161 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21162 						}
21163 						else {
21164 							*ppv = null;
21165 							return E_NOINTERFACE;
21166 						}
21167 
21168 						AddRef();
21169 						return NOERROR;
21170 					}
21171 
21172 
21173 					int pos;
21174 					this() {
21175 						pos = 0;
21176 					}
21177 
21178 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21179 						// writeln("clone");
21180 						return E_NOTIMPL; // FIXME
21181 					}
21182 
21183 					// Caller is responsible for freeing memory
21184 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21185 						// fetched may be null if celt is one
21186 						if(celt != 1)
21187 							return E_NOTIMPL; // FIXME
21188 
21189 						if(celt + pos > types.length)
21190 							return S_FALSE;
21191 
21192 						*rgelt = types[pos++];
21193 
21194 						if(pceltFetched !is null)
21195 							*pceltFetched = 1;
21196 
21197 						// writeln("ok celt ", celt);
21198 						return S_OK;
21199 					}
21200 
21201 					HRESULT Reset() {
21202 						pos = 0;
21203 						return S_OK;
21204 					}
21205 
21206 					HRESULT Skip(ULONG celt) {
21207 						if(celt + pos <= types.length) {
21208 							pos += celt;
21209 							return S_OK;
21210 						}
21211 						return S_FALSE;
21212 					}
21213 				};
21214 
21215 				return S_OK;
21216 			} else
21217 				return E_NOTIMPL;
21218 		}
21219 		// given a format, return the format you'd prefer to use cuz it is identical
21220 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21221 			// FIXME: prolly could be better but meh
21222 			// writeln("gcf: ", *pformatectIn);
21223 			*pformatetcOut = *pformatectIn;
21224 			return S_OK;
21225 		}
21226 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21227 			foreach(ty; types) {
21228 				if(ty == *pformatetcIn) {
21229 					auto format = ty.cfFormat;
21230 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21231 					STGMEDIUM medium;
21232 					medium.tymed = TYMED.TYMED_HGLOBAL;
21233 
21234 					auto sz = handler.dataLength(format);
21235 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21236 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21237 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21238 						auto slice = data[0 .. sz];
21239 						scope(exit)
21240 							GlobalUnlock(handle);
21241 
21242 						handler.getData(format, cast(ubyte[]) slice[]);
21243 					}
21244 
21245 
21246 					medium.hGlobal = handle; // FIXME
21247 					*pmedium = medium;
21248 					return S_OK;
21249 				}
21250 			}
21251 			return DV_E_FORMATETC;
21252 		}
21253 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21254 			// writeln("GDH: ", *pformatetcIn);
21255 			return E_NOTIMPL; // FIXME
21256 		}
21257 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21258 			auto search = *pformatetc;
21259 			search.tymed &= TYMED.TYMED_HGLOBAL;
21260 			foreach(ty; types)
21261 				if(ty == search) {
21262 					// writeln("QueryGetData ", search, " ", types[0]);
21263 					return S_OK;
21264 				}
21265 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21266 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21267 			}
21268 			return S_FALSE;
21269 		}
21270 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21271 			//  writeln("SetData: ");
21272 			return E_NOTIMPL;
21273 		}
21274 	};
21275 
21276 
21277 	IDropSource src = new class IDropSource {
21278 		ULONG refCount;
21279 		ULONG AddRef() {
21280 			return ++refCount;
21281 		}
21282 		ULONG Release() {
21283 			return --refCount;
21284 		}
21285 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21286 			if (IID_IUnknown == *riid) {
21287 				*ppv = cast(void*) cast(IUnknown) this;
21288 			}
21289 			else if (IID_IDropSource == *riid) {
21290 				*ppv = cast(void*) cast(IDropSource) this;
21291 			}
21292 			else {
21293 				*ppv = null;
21294 				return E_NOINTERFACE;
21295 			}
21296 
21297 			AddRef();
21298 			return NOERROR;
21299 		}
21300 
21301 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21302 			if(fEscapePressed)
21303 				return DRAGDROP_S_CANCEL;
21304 			if(!(grfKeyState & MK_LBUTTON))
21305 				return DRAGDROP_S_DROP;
21306 			return S_OK;
21307 		}
21308 
21309 		int GiveFeedback(uint dwEffect) {
21310 			return DRAGDROP_S_USEDEFAULTCURSORS;
21311 		}
21312 	};
21313 
21314 	DWORD effect;
21315 
21316 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21317 
21318 	DROPEFFECT de = win32DragAndDropAction(action);
21319 
21320 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21321 	// but still prolly a FIXME
21322 
21323 	auto ret = DoDragDrop(obj, src, de, &effect);
21324 	/+
21325 	if(ret == DRAGDROP_S_DROP)
21326 		writeln("drop ", effect);
21327 	else if(ret == DRAGDROP_S_CANCEL)
21328 		writeln("cancel");
21329 	else if(ret == S_OK)
21330 		writeln("ok");
21331 	else writeln(ret);
21332 	+/
21333 
21334 	return ret;
21335 }
21336 
21337 version(Windows)
21338 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21339 	DROPEFFECT de;
21340 
21341 	with(DragAndDropAction)
21342 	with(DROPEFFECT)
21343 	final switch(action) {
21344 		case none: de = DROPEFFECT_NONE; break;
21345 		case copy: de = DROPEFFECT_COPY; break;
21346 		case move: de = DROPEFFECT_MOVE; break;
21347 		case link: de = DROPEFFECT_LINK; break;
21348 		case ask: throw new Exception("ask not implemented yet");
21349 		case custom: throw new Exception("custom not implemented yet");
21350 	}
21351 
21352 	return de;
21353 }
21354 
21355 
21356 /++
21357 	History:
21358 		Added February 19, 2021
21359 +/
21360 /// Group: drag_and_drop
21361 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21362 	version(X11) {
21363 		auto display = XDisplayConnection.get;
21364 
21365 		Atom atom = 5; // right???
21366 
21367 		XChangeProperty(
21368 			display,
21369 			window.impl.window,
21370 			GetAtom!"XdndAware"(display),
21371 			XA_ATOM,
21372 			32 /* bits */,
21373 			PropModeReplace,
21374 			&atom,
21375 			1);
21376 
21377 		window.dropHandler = handler;
21378 	} else version(Windows) {
21379 
21380 		initDnd();
21381 
21382 		auto dropTarget = new class (handler) IDropTarget {
21383 			DropHandler handler;
21384 			this(DropHandler handler) {
21385 				this.handler = handler;
21386 			}
21387 			ULONG refCount;
21388 			ULONG AddRef() {
21389 				return ++refCount;
21390 			}
21391 			ULONG Release() {
21392 				return --refCount;
21393 			}
21394 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21395 				if (IID_IUnknown == *riid) {
21396 					*ppv = cast(void*) cast(IUnknown) this;
21397 				}
21398 				else if (IID_IDropTarget == *riid) {
21399 					*ppv = cast(void*) cast(IDropTarget) this;
21400 				}
21401 				else {
21402 					*ppv = null;
21403 					return E_NOINTERFACE;
21404 				}
21405 
21406 				AddRef();
21407 				return NOERROR;
21408 			}
21409 
21410 
21411 			// ///////////////////
21412 
21413 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21414 				DropPackage dropPackage = DropPackage(pDataObj);
21415 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21416 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21417 			}
21418 
21419 			HRESULT DragLeave() {
21420 				handler.dragLeave();
21421 				// release the IDataObject if needed
21422 				return S_OK;
21423 			}
21424 
21425 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21426 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21427 
21428 				*pdwEffect = win32DragAndDropAction(res.action);
21429 				// same as DragEnter basically
21430 				return S_OK;
21431 			}
21432 
21433 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21434 				DropPackage pkg = DropPackage(pDataObj);
21435 				handler.drop(&pkg);
21436 
21437 				return S_OK;
21438 			}
21439 		};
21440 		// Windows can hold on to the handler and try to call it
21441 		// during which time the GC can't see it. so important to
21442 		// manually manage this. At some point i'll FIXME and make
21443 		// all my com instances manually managed since they supposed
21444 		// to respect the refcount.
21445 		import core.memory;
21446 		GC.addRoot(cast(void*) dropTarget);
21447 
21448 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21449 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21450 
21451 		window.dropHandler = handler;
21452 	} else throw new NotYetImplementedException();
21453 }
21454 
21455 
21456 
21457 static if(UsingSimpledisplayX11) {
21458 
21459 enum _NET_WM_STATE_ADD = 1;
21460 enum _NET_WM_STATE_REMOVE = 0;
21461 enum _NET_WM_STATE_TOGGLE = 2;
21462 
21463 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21464 void demandAttention(SimpleWindow window, bool needs = true) {
21465 	demandAttention(window.impl.window, needs);
21466 }
21467 
21468 /// ditto
21469 void demandAttention(Window window, bool needs = true) {
21470 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21471 }
21472 
21473 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21474 	auto display = XDisplayConnection.get();
21475 	if(atom == None)
21476 		return; // non-failure error
21477 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21478 
21479 	XClientMessageEvent xclient;
21480 
21481 	xclient.type = EventType.ClientMessage;
21482 	xclient.window = window;
21483 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21484 	xclient.format = 32;
21485 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21486 	xclient.data.l[1] = atom;
21487 	xclient.data.l[2] = atom2;
21488 	xclient.data.l[3] = 1;
21489 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21490 
21491 	XSendEvent(
21492 		display,
21493 		RootWindow(display, DefaultScreen(display)),
21494 		false,
21495 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21496 		cast(XEvent*) &xclient
21497 	);
21498 
21499 	/+
21500 	XChangeProperty(
21501 		display,
21502 		window.impl.window,
21503 		GetAtom!"_NET_WM_STATE"(display),
21504 		XA_ATOM,
21505 		32 /* bits */,
21506 		PropModeAppend,
21507 		&atom,
21508 		1);
21509 	+/
21510 }
21511 
21512 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21513 	Atom actionAtom;
21514 	with(DragAndDropAction)
21515 	final switch(action) {
21516 		case none: actionAtom = None; break;
21517 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21518 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21519 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21520 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21521 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21522 	}
21523 
21524 	return actionAtom;
21525 }
21526 
21527 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21528 	// FIXME: I need to show user feedback somehow.
21529 	auto display = XDisplayConnection.get;
21530 
21531 	auto actionAtom = dndActionAtom(display, action);
21532 	assert(actionAtom, "Don't use action none to accept a drop");
21533 
21534 	setX11Selection!"XdndSelection"(window, handler, null);
21535 
21536 	auto oldKeyHandler = window.handleKeyEvent;
21537 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21538 
21539 	auto oldCharHandler = window.handleCharEvent;
21540 	scope(exit) window.handleCharEvent = oldCharHandler;
21541 
21542 	auto oldMouseHandler = window.handleMouseEvent;
21543 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21544 
21545 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21546 
21547 	import core.sys.posix.sys.time;
21548 	timeval tv;
21549 	gettimeofday(&tv, null);
21550 
21551 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21552 
21553 	Time lastMouseTimestamp;
21554 
21555 	bool dnding = true;
21556 	Window lastIn = None;
21557 
21558 	void leave() {
21559 		if(lastIn == None)
21560 			return;
21561 
21562 		XEvent ev;
21563 		ev.xclient.type = EventType.ClientMessage;
21564 		ev.xclient.window = lastIn;
21565 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21566 		ev.xclient.format = 32;
21567 		ev.xclient.data.l[0] = window.impl.window;
21568 
21569 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21570 		XFlush(display);
21571 
21572 		lastIn = None;
21573 	}
21574 
21575 	void enter(Window w) {
21576 		assert(lastIn == None);
21577 
21578 		lastIn = w;
21579 
21580 		XEvent ev;
21581 		ev.xclient.type = EventType.ClientMessage;
21582 		ev.xclient.window = lastIn;
21583 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21584 		ev.xclient.format = 32;
21585 		ev.xclient.data.l[0] = window.impl.window;
21586 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21587 
21588 		auto types = handler.availableFormats();
21589 		assert(types.length > 0);
21590 
21591 		ev.xclient.data.l[2] = types[0];
21592 		if(types.length > 1)
21593 			ev.xclient.data.l[3] = types[1];
21594 		if(types.length > 2)
21595 			ev.xclient.data.l[4] = types[2];
21596 
21597 		// FIXME: other types?!?!? and make sure we skip TARGETS
21598 
21599 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21600 		XFlush(display);
21601 	}
21602 
21603 	void position(int rootX, int rootY) {
21604 		assert(lastIn != None);
21605 
21606 		XEvent ev;
21607 		ev.xclient.type = EventType.ClientMessage;
21608 		ev.xclient.window = lastIn;
21609 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21610 		ev.xclient.format = 32;
21611 		ev.xclient.data.l[0] = window.impl.window;
21612 		ev.xclient.data.l[1] = 0; // reserved
21613 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21614 		ev.xclient.data.l[3] = dataTimestamp;
21615 		ev.xclient.data.l[4] = actionAtom;
21616 
21617 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21618 		XFlush(display);
21619 
21620 	}
21621 
21622 	void drop() {
21623 		XEvent ev;
21624 		ev.xclient.type = EventType.ClientMessage;
21625 		ev.xclient.window = lastIn;
21626 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21627 		ev.xclient.format = 32;
21628 		ev.xclient.data.l[0] = window.impl.window;
21629 		ev.xclient.data.l[1] = 0; // reserved
21630 		ev.xclient.data.l[2] = dataTimestamp;
21631 
21632 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21633 		XFlush(display);
21634 
21635 		lastIn = None;
21636 		dnding = false;
21637 	}
21638 
21639 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21640 	// but idk if i should...
21641 
21642 	window.setEventHandlers(
21643 		delegate(KeyEvent ev) {
21644 			if(ev.pressed == true && ev.key == Key.Escape) {
21645 				// cancel
21646 				dnding = false;
21647 			}
21648 		},
21649 		delegate(MouseEvent ev) {
21650 			if(ev.timestamp < lastMouseTimestamp)
21651 				return;
21652 
21653 			lastMouseTimestamp = ev.timestamp;
21654 
21655 			if(ev.type == MouseEventType.motion) {
21656 				auto display = XDisplayConnection.get;
21657 				auto root = RootWindow(display, DefaultScreen(display));
21658 
21659 				Window topWindow;
21660 				int rootX, rootY;
21661 
21662 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21663 
21664 				if(topWindow == None)
21665 					return;
21666 
21667 				top:
21668 				if(auto result = topWindow in eligibility) {
21669 					auto dropWindow = *result;
21670 					if(dropWindow == None) {
21671 						leave();
21672 						return;
21673 					}
21674 
21675 					if(dropWindow != lastIn) {
21676 						leave();
21677 						enter(dropWindow);
21678 						position(rootX, rootY);
21679 					} else {
21680 						position(rootX, rootY);
21681 					}
21682 				} else {
21683 					// determine eligibility
21684 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21685 					if(data.length == 1) {
21686 						// in case there is no WM or it isn't reparenting
21687 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21688 					} else {
21689 
21690 						Window tryScanChildren(Window search, int maxRecurse) {
21691 							// could be reparenting window manager, so gotta check the next few children too
21692 							Window child;
21693 							int x;
21694 							int y;
21695 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21696 
21697 							if(child == None)
21698 								return None;
21699 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21700 							if(data.length == 1) {
21701 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21702 							} else {
21703 								if(maxRecurse)
21704 									return tryScanChildren(child, maxRecurse - 1);
21705 								else
21706 									return None;
21707 							}
21708 
21709 						}
21710 
21711 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21712 						auto topResult = tryScanChildren(topWindow, 3);
21713 						// it is easy to have a false negative due to the mouse going over a WM
21714 						// child window like the close button if separate from the frame... so I
21715 						// can't really cache negatives, :(
21716 						if(topResult != None) {
21717 							eligibility[topWindow] = topResult;
21718 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21719 						}
21720 					}
21721 
21722 				}
21723 
21724 			} else if(ev.type == MouseEventType.buttonReleased) {
21725 				drop();
21726 				dnding = false;
21727 			}
21728 		}
21729 	);
21730 
21731 	window.grabInput();
21732 	scope(exit)
21733 		window.releaseInputGrab();
21734 
21735 
21736 	EventLoop.get.run(() => dnding);
21737 
21738 	return 0;
21739 }
21740 
21741 /// X-specific
21742 TrueColorImage getWindowNetWmIcon(Window window) {
21743 	try {
21744 		auto display = XDisplayConnection.get;
21745 
21746 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21747 
21748 		if (data.length > arch_ulong.sizeof * 2) {
21749 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21750 			// these are an array of rgba images that we have to convert into pixmaps ourself
21751 
21752 			int width = cast(int) meta[0];
21753 			int height = cast(int) meta[1];
21754 
21755 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21756 
21757 			static if(arch_ulong.sizeof == 4) {
21758 				bytes = bytes[0 .. width * height * 4];
21759 				alias imageData = bytes;
21760 			} else static if(arch_ulong.sizeof == 8) {
21761 				bytes = bytes[0 .. width * height * 8];
21762 				auto imageData = new ubyte[](4 * width * height);
21763 			} else static assert(0);
21764 
21765 
21766 
21767 			// this returns ARGB. Remember it is little-endian so
21768 			//                                         we have BGRA
21769 			// our thing uses RGBA, which in little endian, is ABGR
21770 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21771 				auto r = bytes[idx + 2];
21772 				auto g = bytes[idx + 1];
21773 				auto b = bytes[idx + 0];
21774 				auto a = bytes[idx + 3];
21775 
21776 				imageData[idx2 + 0] = r;
21777 				imageData[idx2 + 1] = g;
21778 				imageData[idx2 + 2] = b;
21779 				imageData[idx2 + 3] = a;
21780 			}
21781 
21782 			return new TrueColorImage(width, height, imageData);
21783 		}
21784 
21785 		return null;
21786 	} catch(Exception e) {
21787 		return null;
21788 	}
21789 }
21790 
21791 } /* UsingSimpledisplayX11 */
21792 
21793 
21794 void loadBinNameToWindowClassName () {
21795 	import core.stdc.stdlib : realloc;
21796 	version(linux) {
21797 		// args[0] MAY be empty, so we'll just use this
21798 		import core.sys.posix.unistd : readlink;
21799 		char[1024] ebuf = void; // 1KB should be enough for everyone!
21800 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
21801 		if (len < 1) return;
21802 	} else /*version(Windows)*/ {
21803 		import core.runtime : Runtime;
21804 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
21805 		auto ebuf = Runtime.args[0];
21806 		auto len = ebuf.length;
21807 	}
21808 	auto pos = len;
21809 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
21810 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
21811 	if (sdpyWindowClassStr is null) return; // oops
21812 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
21813 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
21814 }
21815 
21816 /++
21817 	An interface representing a font that is drawn with custom facilities.
21818 
21819 	You might want [OperatingSystemFont] instead, which represents
21820 	a font loaded and drawn by functions native to the operating system.
21821 
21822 	WARNING: I might still change this.
21823 +/
21824 interface DrawableFont : MeasurableFont {
21825 	/++
21826 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
21827 
21828 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
21829 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
21830 		fill color, but that's up to the implementation.
21831 	+/
21832 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
21833 
21834 	/++
21835 		Requests that the given string is added to the image cache. You should only do this rarely, but
21836 		if you have a string that you know will be used over and over again, adding it to a cache can
21837 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
21838 		to implement this as a do-nothing method).
21839 	+/
21840 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
21841 }
21842 
21843 /++
21844 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
21845 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
21846 
21847 	You should also consider [OperatingSystemFont], which loads and draws a font with
21848 	facilities native to the user's operating system. You might also consider
21849 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
21850 	of game, as they have their own ways to draw text too.
21851 
21852 	Be warned: this can be slow, especially on remote connections to the X server, since
21853 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
21854 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
21855 	experiment in your specific case.
21856 
21857 	Please note that the return type of [DrawableFont] also includes an implementation of
21858 	[MeasurableFont].
21859 +/
21860 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
21861 	import arsd.ttf;
21862 	static class ArsdTtfFont : DrawableFont {
21863 		TtfFont font;
21864 		int size;
21865 		this(in ubyte[] data, int size) {
21866 			font = TtfFont(data);
21867 			this.size = size;
21868 
21869 
21870 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
21871 			int ascent_, descent_, line_gap;
21872 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
21873 
21874 			int advance, lsb;
21875 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
21876 			xWidth = cast(int) (advance * scale);
21877 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
21878 			MWidth = cast(int) (advance * scale);
21879 		}
21880 
21881 		private int ascent_;
21882 		private int descent_;
21883 		private int xWidth;
21884 		private int MWidth;
21885 
21886 		bool isMonospace() {
21887 			return xWidth == MWidth;
21888 		}
21889 		int averageWidth() {
21890 			return xWidth;
21891 		}
21892 		int height() {
21893 			return size;
21894 		}
21895 		int ascent() {
21896 			return ascent_;
21897 		}
21898 		int descent() {
21899 			return descent_;
21900 		}
21901 
21902 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
21903 			int width, height;
21904 			font.getStringSize(s, size, width, height);
21905 			return width;
21906 		}
21907 
21908 
21909 
21910 		Sprite[string] cache;
21911 
21912 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
21913 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
21914 			cache[text] = sprite;
21915 		}
21916 
21917 		Image stringToImage(Color fg, Color bg, in char[] text) {
21918 			int width, height;
21919 			auto data = font.renderString(text, size, width, height);
21920 			auto image = new TrueColorImage(width, height);
21921 			int pos = 0;
21922 			foreach(y; 0 .. height)
21923 			foreach(x; 0 .. width) {
21924 				fg.a = data[0];
21925 				bg.a = 255;
21926 				auto color = alphaBlend(fg, bg);
21927 				image.imageData.bytes[pos++] = color.r;
21928 				image.imageData.bytes[pos++] = color.g;
21929 				image.imageData.bytes[pos++] = color.b;
21930 				image.imageData.bytes[pos++] = data[0];
21931 				data = data[1 .. $];
21932 			}
21933 			assert(data.length == 0);
21934 
21935 			return Image.fromMemoryImage(image);
21936 		}
21937 
21938 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
21939 			Sprite sprite = (text in cache) ? *(text in cache) : null;
21940 
21941 			auto fg = painter.impl._outlineColor;
21942 			auto bg = painter.impl._fillColor;
21943 
21944 			if(sprite !is null) {
21945 				auto w = cast(SimpleWindow) painter.window;
21946 				assert(w !is null);
21947 
21948 				sprite.drawAt(painter, upperLeft);
21949 			} else {
21950 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
21951 			}
21952 		}
21953 	}
21954 
21955 	return new ArsdTtfFont(data, size);
21956 }
21957 
21958 class NotYetImplementedException : Exception {
21959 	this(string file = __FILE__, size_t line = __LINE__) {
21960 		super("Not yet implemented", file, line);
21961 	}
21962 }
21963 
21964 ///
21965 __gshared bool librariesSuccessfullyLoaded = true;
21966 ///
21967 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
21968 
21969 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
21970 	mixin(staticForeachReplacement!Iface);
21971 
21972 	void loadDynamicLibrary() @nogc {
21973 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
21974 	}
21975 
21976         void loadDynamicLibraryForReal() {
21977                 foreach(name; __traits(derivedMembers, Iface)) {
21978                         mixin("alias tmp = " ~ name ~ ";");
21979                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
21980                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
21981                 }
21982         }
21983 }
21984 
21985 private const(char)[] staticForeachReplacement(Iface)() pure {
21986 /*
21987 	// just this for gdc 9....
21988 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
21989 
21990         static foreach(name; __traits(derivedMembers, Iface))
21991                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
21992 */
21993 
21994 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
21995 	size_t pos;
21996 
21997 	void append(in char[] what) {
21998 		if(pos + what.length > code.length)
21999 			code.length = (code.length * 3) / 2;
22000 		code[pos .. pos + what.length] = what[];
22001 		pos += what.length;
22002 	}
22003 
22004         foreach(name; __traits(derivedMembers, Iface)) {
22005                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
22006 		append(name);
22007 		append(`")) `);
22008 		append(name);
22009 		append(";");
22010 	}
22011 
22012 	return code[0 .. pos];
22013 }
22014 
22015 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22016 	mixin(staticForeachReplacement!Iface);
22017 
22018 	private __gshared void* libHandle;
22019 	private __gshared bool attempted;
22020 
22021         void loadDynamicLibrary() @nogc {
22022 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22023 	}
22024 
22025 	bool loadAttempted() {
22026 		return attempted;
22027 	}
22028 	bool loadSuccessful() {
22029 		return libHandle !is null;
22030 	}
22031 
22032         void loadDynamicLibraryForReal() {
22033 		attempted = true;
22034                 version(Posix) {
22035                         import core.sys.posix.dlfcn;
22036 			version(OSX) {
22037 				version(X11)
22038                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22039 				else
22040                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22041 			} else {
22042                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22043 				if(libHandle is null)
22044                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22045 			}
22046 
22047 			static void* loadsym(void* l, const char* name) {
22048 				import core.stdc.stdlib;
22049 				if(l is null)
22050 					return &abort;
22051 				return dlsym(l, name);
22052 			}
22053                 } else version(Windows) {
22054                         import core.sys.windows.winbase;
22055                         libHandle = LoadLibrary(library ~ ".dll");
22056 			static void* loadsym(void* l, const char* name) {
22057 				import core.stdc.stdlib;
22058 				if(l is null)
22059 					return &abort;
22060 				return GetProcAddress(l, name);
22061 			}
22062                 }
22063                 if(libHandle is null) {
22064 			success = false;
22065                         //throw new Exception("load failure of library " ~ library);
22066 		}
22067                 foreach(name; __traits(derivedMembers, Iface)) {
22068                         mixin("alias tmp = " ~ name ~ ";");
22069                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22070                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22071                 }
22072         }
22073 
22074         void unloadDynamicLibrary() {
22075                 version(Posix) {
22076                         import core.sys.posix.dlfcn;
22077                         dlclose(libHandle);
22078                 } else version(Windows) {
22079                         import core.sys.windows.winbase;
22080                         FreeLibrary(libHandle);
22081                 }
22082                 foreach(name; __traits(derivedMembers, Iface))
22083                         mixin(name ~ " = null;");
22084         }
22085 }
22086 
22087 /+
22088 	The GC can be called from any thread, and a lot of cleanup must be done
22089 	on the gui thread. Since the GC can interrupt any locks - including being
22090 	triggered inside a critical section - it is vital to avoid deadlocks to get
22091 	these functions called from the right place.
22092 
22093 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22094 	right now.
22095 
22096 	The cleanup function is run when the event loop gets around to it, which is just
22097 	whenever there's something there after it has been woken up for other work. It does
22098 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22099 	(Well actually it might be ok but i don't wanna mess with it right now.)
22100 +/
22101 private struct CleanupQueue {
22102 	import core.stdc.stdlib;
22103 
22104 	void queue(alias func, T...)(T args) {
22105 		static struct Args {
22106 			T args;
22107 		}
22108 		static struct RealJob {
22109 			Job j;
22110 			Args a;
22111 		}
22112 		static void call(Job* data) {
22113 			auto rj = cast(RealJob*) data;
22114 			func(rj.a.args);
22115 		}
22116 
22117 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22118 		thing.j.call = &call;
22119 		thing.a.args = args;
22120 
22121 		buffer[tail++] = cast(Job*) thing;
22122 
22123 		// FIXME: set overflowed
22124 	}
22125 
22126 	void process() {
22127 		const tail = this.tail;
22128 
22129 		while(tail != head) {
22130 			Job* job = cast(Job*) buffer[head++];
22131 			job.call(job);
22132 			free(job);
22133 		}
22134 
22135 		if(overflowed)
22136 			throw new Exception("cleanup overflowed");
22137 	}
22138 
22139 	private:
22140 
22141 	ubyte tail; // must ONLY be written by queue
22142 	ubyte head; // must ONLY be written by process
22143 	bool overflowed;
22144 
22145 	static struct Job {
22146 		void function(Job*) call;
22147 	}
22148 
22149 	void*[256] buffer;
22150 }
22151 private __gshared CleanupQueue cleanupQueue;
22152 
22153 version(X11)
22154 /++
22155 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22156 
22157 	$(WARNING
22158 		This function is exempted from stability guarantees.
22159 	)
22160 +/
22161 float customScalingFactorForMonitor(int monitorNumber) {
22162 	import core.stdc.stdlib;
22163 	auto val = getenv("ARSD_SCALING_FACTOR");
22164 
22165 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
22166 	if(val is null)
22167 		return 1.0;
22168 
22169 	char[16] buffer = 0;
22170 	int pos;
22171 
22172 	const(char)* at = val;
22173 
22174 	foreach(item; 0 .. monitorNumber + 1) {
22175 		if(*at == 0)
22176 			break; // reuse the last number when we at the end of the string
22177 		pos = 0;
22178 		while(pos + 1 < buffer.length && *at && *at != ';') {
22179 			buffer[pos++] = *at;
22180 			at++;
22181 		}
22182 		if(*at)
22183 			at++; // skip the semicolon
22184 		buffer[pos] = 0;
22185 	}
22186 
22187 	//sdpyPrintDebugString(buffer[0 .. pos]);
22188 
22189 	import core.stdc.math;
22190 	auto f = atof(buffer.ptr);
22191 
22192 	if(f <= 0.0 || isnan(f) || isinf(f))
22193 		return 1.0;
22194 
22195 	return f;
22196 }
22197 
22198 void guiAbortProcess(string msg) {
22199 	import core.stdc.stdlib;
22200 	version(Windows) {
22201 		WCharzBuffer t = WCharzBuffer(msg);
22202 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22203 	} else {
22204 		import core.stdc.stdio;
22205 		fwrite(msg.ptr, 1, msg.length, stderr);
22206 		msg = "\n";
22207 		fwrite(msg.ptr, 1, msg.length, stderr);
22208 		fflush(stderr);
22209 	}
22210 
22211 	abort();
22212 }
22213 
22214 private int minInternal(int a, int b) {
22215 	return (a < b) ? a : b;
22216 }
22217 
22218 private alias scriptable = arsd_jsvar_compatible;