1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 // Search for: FIXME: leaks if multithreaded gc
4 
5 // https://freedesktop.org/wiki/Specifications/XDND/
6 
7 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
8 
9 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
10 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
11 
12 
13 // on Mac with X11: -L-L/usr/X11/lib
14 
15 /+
16 
17 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
18 
19 	Progress bar in taskbar
20 		- i can probably just set a property on the window...
21 		  it sets that prop to an integer 0 .. 100. Taskbar
22 		  deletes it or window deletes it when it is handled.
23 		- prolly display it as a nice little line at the bottom.
24 
25 
26 from gtk:
27 
28 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
29 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
30 
31 >+  if (cardinal > 0)
32 >+  {
33 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
34 >+                     xid,
35 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
36 >+                     XA_CARDINAL, 32,
37 >+                     PropModeReplace,
38 >+                     (guchar *) &cardinal, 1);
39 >+  }
40 >+  else
41 >+  {
42 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
45 >+  }
46 
47 from Windows:
48 
49 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
50 
51 interface
52 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
53 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
54 listen for msg, return TRUE
55 interface->SetProgressState(hwnd, TBPF_NORMAL);
56 interface->SetProgressValue(hwnd, 40, 100);
57 
58 
59 	My new notification system.
60 		- use a unix socket? or a x property? or a udp port?
61 		- could of course also get on the dbus train but ugh.
62 		- it could also reply with the info as a string for easy remote examination.
63 
64 +/
65 
66 /*
67 	Event Loop would be nices:
68 
69 	* add on idle - runs when nothing else happens
70 		* which can specify how long to yield for
71 	* send messages without a recipient window
72 	* setTimeout
73 	* setInterval
74 */
75 
76 /*
77 	Classic games I want to add:
78 		* my tetris clone
79 		* pac man
80 */
81 
82 /*
83 	Text layout needs a lot of work. Plain drawText is useful but too
84 	limited. It will need some kind of text context thing which it will
85 	update and you can pass it on and get more details out of it.
86 
87 	It will need a bounding box, a current cursor location that is updated
88 	as drawing continues, and various changable facts (which can also be
89 	changed on the painter i guess) like font, color, size, background,
90 	etc.
91 
92 	We can also fetch the caret location from it somehow.
93 
94 	Should prolly be an overload of drawText
95 
96 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
97 
98 		WS_EX_NOACTIVATE
99 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
100 		full screen windows. Can just set the atom on X. Windows will be harder.
101 
102 		moving windows. resizing windows.
103 
104 		hide cursor, capture cursor, change cursor.
105 
106 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
107 	sure the pieces are there to do its job easily and make other jobs possible.
108 */
109 
110 /++
111 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
112 	including creating windows, drawing on them, working with the clipboard,
113 	timers, OpenGL, and more. However, it does NOT provide high level GUI
114 	widgets. See my minigui.d, an extension to this module, for that
115 	functionality.
116 
117 	simpledisplay provides cross-platform wrapping for Windows and Linux
118 	(and perhaps other OSes that use X11), but also does not prevent you
119 	from using the underlying facilities if you need them. It has a goal
120 	of working efficiently over a remote X link (at least as far as Xlib
121 	reasonably allows.)
122 
123 	simpledisplay depends on [arsd.color|color.d], which should be available from the
124 	same place where you got this file. Other than that, however, it has
125 	very few dependencies and ones that don't come with the OS and/or the
126 	compiler are all opt-in.
127 
128 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
129 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
130 
131 	simpledisplay is basically stable. I plan to refactor the internals,
132 	and may add new features and fix bugs, but It do not expect to
133 	significantly change the API. It has been stable a few years already now.
134 
135 	Installation_instructions:
136 
137 	`simpledisplay.d` does not have any dependencies outside the
138 	operating system and `color.d`, so it should just work most the
139 	time, but there are a few caveats on some systems:
140 
141 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
142 	console to be automatically allocated.
143 
144 	Please note when compiling on Win64, you need to explicitly list
145 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
146 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
147 
148 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
149 	note the "w".
150 
151 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
152 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
153 	See [EnableWindowsSubsystem] for more information.
154 
155 	$(PITFALL
156 		With the Windows subsystem, there is no console, so standard writeln will throw!
157 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
158 		create a console as needed.
159 	)
160 
161 	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.
162 
163 	On Ubuntu, you might need to install X11 development libraries to
164 	successfully link.
165 
166 	$(CONSOLE
167 		$ sudo apt-get install libglc-dev
168 		$ sudo apt-get install libx11-dev
169 	)
170 
171 
172 	Jump_list:
173 
174 	Don't worry, you don't have to read this whole documentation file!
175 
176 	Check out the [#event-example] and [#Pong-example] to get started quickly.
177 
178 	The main classes you may want to create are [SimpleWindow], [Timer],
179 	[Image], and [Sprite].
180 
181 	The main functions you'll want are [setClipboardText] and [getClipboardText].
182 
183 	There are also platform-specific functions available such as [XDisplayConnection]
184 	and [GetAtom] for X11, among others.
185 
186 	See the examples and topics list below to learn more.
187 
188 	$(WARNING
189 		There should only be one GUI thread per application,
190 		and all windows should be created in it and your
191 		event loop should run there.
192 
193 		To do otherwise is undefined behavior and has no
194 		cross platform guarantees.
195 	)
196 
197 	$(H2 About this documentation)
198 
199 	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.
200 
201 	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!
202 
203 	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.
204 
205 	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.
206 
207 	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.
208 
209 	At points, I will talk about implementation details in the documentation. These are sometimes
210 	subject to change, but nevertheless useful to understand what is really going on. You can learn
211 	more about some of the referenced things by searching the web for info about using them from C.
212 	You can always look at the source of simpledisplay.d too for the most authoritative source on
213 	its specific implementation. If you disagree with how I did something, please contact me so we
214 	can discuss it!
215 
216 	$(H2 Using with fibers)
217 
218 	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).
219 
220 	$(H2 Topics)
221 
222 	$(H3 $(ID topic-windows) Windows)
223 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
224 		window on the user's screen.
225 
226 		You may create multiple windows, if the underlying platform supports it. You may check
227 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
228 		SimpleWindow's constructor at runtime to handle those cases.
229 
230 		A single running event loop will handle as many windows as needed.
231 
232 	$(H3 $(ID topic-event-loops) Event loops)
233 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
234 
235 		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:
236 
237 		---
238 		// dmd example.d simpledisplay.d color.d
239 		import arsd.simpledisplay;
240 		void main() {
241 			auto window = new SimpleWindow(200, 200);
242 			window.eventLoop(0,
243 			  delegate (dchar) { /* got a character key press */ }
244 			);
245 		}
246 		---
247 
248 		$(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.)
249 
250 		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.
251 
252 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
253 
254 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
255 
256 		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.
257 
258 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
259 		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.
260 
261 		See the [NotificationAreaIcon] class.
262 
263 	$(H3 $(ID topic-input-handling) Input handling)
264 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
265 
266 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
267 
268 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
269 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
270 
271 		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:
272 
273 		---
274 		// dmd example.d simpledisplay.d color.d
275 		import arsd.simpledisplay;
276 		void main() {
277 			auto window = new SimpleWindow(200, 200);
278 			{ // introduce sub-scope
279 				auto painter = window.draw(); // begin drawing
280 				/* draw here */
281 				painter.outlineColor = Color.red;
282 				painter.fillColor = Color.black;
283 				painter.drawRectangle(Point(0, 0), 200, 200);
284 			} // end scope, calling `painter`'s destructor, drawing to the screen.
285 			window.eventLoop(0); // handle events
286 		}
287 		---
288 
289 		Painting is done based on two color properties, a pen and a brush.
290 
291 		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.
292 
293 		FIXME Add example of 2d opengl drawing here.
294 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
295 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
296 
297 		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.
298 
299 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
300 
301 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
302 
303 		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].
304 
305 		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.
306 
307 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
308 
309 		---
310 		import arsd.simpledisplay;
311 
312 		void main() {
313 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
314 
315 			float otherColor = 0.0;
316 			float colorDelta = 0.05;
317 
318 			window.redrawOpenGlScene = delegate() {
319 				glLoadIdentity();
320 				glBegin(GL_QUADS);
321 
322 				glColor3f(1.0, otherColor, 0);
323 				glVertex3f(-0.8, -0.8, 0);
324 
325 				glColor3f(1.0, otherColor, 1.0);
326 				glVertex3f(0.8, -0.8, 0);
327 
328 				glColor3f(0, 1.0, otherColor);
329 				glVertex3f(0.8, 0.8, 0);
330 
331 				glColor3f(otherColor, 0, 1.0);
332 				glVertex3f(-0.8, 0.8, 0);
333 
334 				glEnd();
335 			};
336 
337 			window.eventLoop(50, () {
338 				otherColor += colorDelta;
339 				if(otherColor > 1.0) {
340 					otherColor = 1.0;
341 					colorDelta = -0.05;
342 				}
343 				if(otherColor < 0) {
344 					otherColor = 0;
345 					colorDelta = 0.05;
346 				}
347 				// at the end of the timer, we have to request a redraw
348 				// or we won't see the changes.
349 				window.redrawOpenGlSceneSoon();
350 			});
351 		}
352 		---
353 
354 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
355 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
356 		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.
357 
358 		This example program shows how you can set up a shader to draw a rectangle:
359 
360 		---
361 		module opengl3test;
362 		import arsd.simpledisplay;
363 
364 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
365 
366 		void main() {
367 			// First thing we do, before creating the window, is declare what version we want.
368 			setOpenGLContextVersion(3, 3);
369 			// turning off legacy compat is required to use version 3.3 and newer
370 			openGLContextCompatible = false;
371 
372 			uint VAO;
373 			OpenGlShader shader;
374 
375 			// then we can create the window.
376 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
377 
378 			// additional setup needs to be done when it is visible, simpledisplay offers a property
379 			// for exactly that:
380 			window.visibleForTheFirstTime = delegate() {
381 				// now with the window loaded, we can start loading the modern opengl functions.
382 
383 				// you MUST set the context first.
384 				window.setAsCurrentOpenGlContext;
385 				// then load the remainder of the library
386 				gl3.loadDynamicLibrary();
387 
388 				// now you can create the shaders, etc.
389 				shader = new OpenGlShader(
390 					OpenGlShader.Source(GL_VERTEX_SHADER, `
391 						#version 330 core
392 						layout (location = 0) in vec3 aPos;
393 						void main() {
394 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
395 						}
396 					`),
397 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
398 						#version 330 core
399 						out vec4 FragColor;
400 						uniform vec4 mycolor;
401 						void main() {
402 							FragColor = mycolor;
403 						}
404 					`),
405 				);
406 
407 				// and do whatever other setup you want.
408 
409 				float[] vertices = [
410 					0.5f,  0.5f, 0.0f,  // top right
411 					0.5f, -0.5f, 0.0f,  // bottom right
412 					-0.5f, -0.5f, 0.0f,  // bottom left
413 					-0.5f,  0.5f, 0.0f   // top left
414 				];
415 				uint[] indices = [  // note that we start from 0!
416 					0, 1, 3,  // first Triangle
417 					1, 2, 3   // second Triangle
418 				];
419 				uint VBO, EBO;
420 				glGenVertexArrays(1, &VAO);
421 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
422 				glBindVertexArray(VAO);
423 
424 				glGenBuffers(1, &VBO);
425 				glGenBuffers(1, &EBO);
426 
427 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
428 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
429 
430 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
431 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
432 
433 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
434 				glEnableVertexAttribArray(0);
435 
436 				// the library will set the initial viewport and trigger our first draw,
437 				// so these next two lines are NOT needed. they are just here as comments
438 				// to show what would happen next.
439 
440 				// glViewport(0, 0, window.width, window.height);
441 				// window.redrawOpenGlSceneNow();
442 			};
443 
444 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
445 			// it is our render method.
446 			window.redrawOpenGlScene = delegate() {
447 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
448 				glClear(GL_COLOR_BUFFER_BIT);
449 
450 				glUseProgram(shader.shaderProgram);
451 
452 				// the shader helper class has methods to set uniforms too
453 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
454 
455 				glBindVertexArray(VAO);
456 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
457 			};
458 
459 			window.eventLoop(0);
460 		}
461 		---
462 
463 	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.
464 
465 
466 	$(H3 $(ID topic-images) Displaying images)
467 		You can also load PNG images using [arsd.png].
468 
469 		---
470 		// dmd example.d simpledisplay.d color.d png.d
471 		import arsd.simpledisplay;
472 		import arsd.png;
473 
474 		void main() {
475 			auto image = Image.fromMemoryImage(readPng("image.png"));
476 			displayImage(image);
477 		}
478 		---
479 
480 		Compile with `dmd example.d simpledisplay.d png.d`.
481 
482 		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.
483 
484 	$(H3 $(ID topic-sprites) Sprites)
485 		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.
486 
487 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
488 
489 	$(H3 $(ID topic-clipboard) Clipboard)
490 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
491 
492 		It also has helpers for handling X-specific events.
493 
494 	$(H3 $(ID topic-dnd) Drag and Drop)
495 		See [enableDragAndDrop] and [draggable].
496 
497 	$(H3 $(ID topic-timers) Timers)
498 		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].
499 
500 		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.
501 
502 		---
503 			import arsd.simpledisplay;
504 
505 			void main() {
506 				auto window = new SimpleWindow(400, 400);
507 				// every 100 ms, it will draw a random line
508 				// on the window.
509 				window.eventLoop(100, {
510 					auto painter = window.draw();
511 
512 					import std.random;
513 					// random color
514 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
515 					// random line
516 					painter.drawLine(
517 						Point(uniform(0, window.width), uniform(0, window.height)),
518 						Point(uniform(0, window.width), uniform(0, window.height)));
519 
520 				});
521 			}
522 		---
523 
524 		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.
525 
526 		The pulse timer and instances of the [Timer] class may be combined at will.
527 
528 		---
529 			import arsd.simpledisplay;
530 
531 			void main() {
532 				auto window = new SimpleWindow(400, 400);
533 				auto timer = new Timer(1000, delegate {
534 					auto painter = window.draw();
535 					painter.clear();
536 				});
537 
538 				window.eventLoop(0);
539 			}
540 		---
541 
542 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
543 
544 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
545 		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.
546 
547 		See also: `xwindows.d` from my github.
548 
549 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
550 		`handleNativeEvent` and `handleNativeGlobalEvent`.
551 
552 	$(H3 $(ID topic-integration) Integration with other libraries)
553 		Integration with a third-party event loop is possible.
554 
555 		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.
556 
557 	$(H3 $(ID topic-guis) GUI widgets)
558 		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!
559 
560 		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.
561 
562 		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.)
563 
564 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
565 
566 	$(H2 Platform-specific tips and tricks)
567 
568 	X_tips:
569 
570 	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.
571 
572 	Windows_tips:
573 
574 	You can add icons or manifest files to your exe using a resource file.
575 
576 	To create a Windows .ico file, use the gimp or something. I'll write a helper
577 	program later.
578 
579 	Create `yourapp.rc`:
580 
581 	```rc
582 		1 ICON filename.ico
583 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
584 	```
585 
586 	And `yourapp.exe.manifest`:
587 
588 	```xml
589 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
590 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
591 		<assemblyIdentity
592 		    version="1.0.0.0"
593 		    processorArchitecture="*"
594 		    name="CompanyName.ProductName.YourApplication"
595 		    type="win32"
596 		/>
597 		<description>Your application description here.</description>
598 		<dependency>
599 		    <dependentAssembly>
600 			<assemblyIdentity
601 			    type="win32"
602 			    name="Microsoft.Windows.Common-Controls"
603 			    version="6.0.0.0"
604 			    processorArchitecture="*"
605 			    publicKeyToken="6595b64144ccf1df"
606 			    language="*"
607 			/>
608 		    </dependentAssembly>
609 		</dependency>
610 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
611 			<windowsSettings>
612 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
613 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
614 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
615 				<!-- to render crisply in DPI-unaware contexts -->
616 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
617 			</windowsSettings>
618 		</application>
619 		</assembly>
620 	```
621 
622 	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`.
623 
624 	Doing this lets you opt into various new things since Windows XP.
625 
626 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
627 
628 	$(H2 Tips)
629 
630 	$(H3 Name conflicts)
631 
632 	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:
633 
634 	---
635 	static import sdpy = arsd.simpledisplay;
636 	import arsd.simpledisplay : SimpleWindow;
637 
638 	void main() {
639 		auto window = new SimpleWindow();
640 		sdpy.EventLoop.get.run();
641 	}
642 	---
643 
644 	$(H2 $(ID developer-notes) Developer notes)
645 
646 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
647 	implementation though.
648 
649 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
650 	suck. If I was rewriting it, I wouldn't do it that way again.
651 
652 	This file must not have any more required dependencies. If you need bindings, add
653 	them right to this file. Once it gets into druntime and is there for a while, remove
654 	bindings from here to avoid conflicts (or put them in an appropriate version block
655 	so it continues to just work on old dmd), but wait a couple releases before making the
656 	transition so this module remains usable with older versions of dmd.
657 
658 	You may have optional dependencies if needed by putting them in version blocks or
659 	template functions. You may also extend the module with other modules with UFCS without
660 	actually editing this - that is nice to do if you can.
661 
662 	Try to make functions work the same way across operating systems. I typically make
663 	it thinly wrap Windows, then emulate that on Linux.
664 
665 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
666 	Phobos! So try to avoid it.
667 
668 	See more comments throughout the source.
669 
670 	I realize this file is fairly large, but over half that is just bindings at the bottom
671 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
672 	to understand. I suggest you jump around the source by looking for a particular
673 	declaration you're interested in, like `class SimpleWindow` using your editor's search
674 	function, then look at one piece at a time.
675 
676 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
677 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
678 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
679 
680 	I live in the eastern United States, so I will most likely not be around at night in
681 	that US east timezone.
682 
683 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
684 
685 	Building documentation: use my adrdox generator, `dub run adrdox`.
686 
687 	Examples:
688 
689 	$(DIV $(ID Event-example))
690 	$(H3 $(ID event-example) Event example)
691 	This program creates a window and draws events inside them as they
692 	happen, scrolling the text in the window as needed. Run this program
693 	and experiment to get a feel for where basic input events take place
694 	in the library.
695 
696 	---
697 	// dmd example.d simpledisplay.d color.d
698 	import arsd.simpledisplay;
699 	import std.conv;
700 
701 	void main() {
702 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
703 
704 		int y = 0;
705 
706 		void addLine(string text) {
707 			auto painter = window.draw();
708 
709 			if(y + painter.fontHeight >= window.height) {
710 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
711 				y -= painter.fontHeight;
712 			}
713 
714 			painter.outlineColor = Color.red;
715 			painter.fillColor = Color.black;
716 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
717 
718 			painter.outlineColor = Color.white;
719 
720 			painter.drawText(Point(10, y), text);
721 
722 			y += painter.fontHeight;
723 		}
724 
725 		window.eventLoop(1000,
726 		  () {
727 			addLine("Timer went off!");
728 		  },
729 		  (KeyEvent event) {
730 			addLine(to!string(event));
731 		  },
732 		  (MouseEvent event) {
733 			addLine(to!string(event));
734 		  },
735 		  (dchar ch) {
736 			addLine(to!string(ch));
737 		  }
738 		);
739 	}
740 	---
741 
742 	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.
743 
744 	$(COMMENT
745 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
746 
747 	---
748 
749 	---
750 	)
751 
752 
753 +/
754 module arsd.simpledisplay;
755 
756 // FIXME: tetris demo
757 // FIXME: space invaders demo
758 // FIXME: asteroids demo
759 
760 /++ $(ID Pong-example)
761 	$(H3 Pong)
762 
763 	This program creates a little Pong-like game. Player one is controlled
764 	with the keyboard.  Player two is controlled with the mouse. It demos
765 	the pulse timer, event handling, and some basic drawing.
766 +/
767 version(demos)
768 unittest {
769 	// dmd example.d simpledisplay.d color.d
770 	import arsd.simpledisplay;
771 
772 	enum paddleMovementSpeed = 8;
773 	enum paddleHeight = 48;
774 
775 	void main() {
776 		auto window = new SimpleWindow(600, 400, "Pong game!");
777 
778 		int playerOnePosition, playerTwoPosition;
779 		int playerOneMovement, playerTwoMovement;
780 		int playerOneScore, playerTwoScore;
781 
782 		int ballX, ballY;
783 		int ballDx, ballDy;
784 
785 		void serve() {
786 			import std.random;
787 
788 			ballX = window.width / 2;
789 			ballY = window.height / 2;
790 			ballDx = uniform(-4, 4) * 3;
791 			ballDy = uniform(-4, 4) * 3;
792 			if(ballDx == 0)
793 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
794 		}
795 
796 		serve();
797 
798 		window.eventLoop(50, // set a 50 ms timer pulls
799 			// This runs once per timer pulse
800 			delegate () {
801 				auto painter = window.draw();
802 
803 				painter.clear();
804 
805 				// Update everyone's motion
806 				playerOnePosition += playerOneMovement;
807 				playerTwoPosition += playerTwoMovement;
808 
809 				ballX += ballDx;
810 				ballY += ballDy;
811 
812 				// Bounce off the top and bottom edges of the window
813 				if(ballY + 7 >= window.height)
814 					ballDy = -ballDy;
815 				if(ballY - 8 <= 0)
816 					ballDy = -ballDy;
817 
818 				// Bounce off the paddle, if it is in position
819 				if(ballX - 8 <= 16) {
820 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
821 						ballDx = -ballDx + 1; // add some speed to keep it interesting
822 						ballDy += playerOneMovement; // and y movement based on your controls too
823 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
824 					} else {
825 						// Missed it
826 						playerTwoScore ++;
827 						serve();
828 					}
829 				}
830 
831 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
832 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
833 						ballDx = -ballDx - 1;
834 						ballDy += playerTwoMovement;
835 						ballX = window.width - 24;
836 					} else {
837 						// Missed it
838 						playerOneScore ++;
839 						serve();
840 					}
841 				}
842 
843 				// Draw the paddles
844 				painter.outlineColor = Color.black;
845 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
846 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
847 
848 				// Draw the ball
849 				painter.fillColor = Color.red;
850 				painter.outlineColor = Color.yellow;
851 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
852 
853 				// Draw the score
854 				painter.outlineColor = Color.blue;
855 				import std.conv;
856 				painter.drawText(Point(64, 4), to!string(playerOneScore));
857 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
858 
859 			},
860 			delegate (KeyEvent event) {
861 				// Player 1's controls are the arrow keys on the keyboard
862 				if(event.key == Key.Down)
863 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
864 				if(event.key == Key.Up)
865 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
866 
867 			},
868 			delegate (MouseEvent event) {
869 				// Player 2's controls are mouse movement while the left button is held down
870 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
871 					if(event.dy > 0)
872 						playerTwoMovement = paddleMovementSpeed;
873 					else if(event.dy < 0)
874 						playerTwoMovement = -paddleMovementSpeed;
875 				} else {
876 					playerTwoMovement = 0;
877 				}
878 			}
879 		);
880 	}
881 }
882 
883 /++ $(H3 $(ID example-minesweeper) Minesweeper)
884 
885 	This minesweeper demo shows how we can implement another classic
886 	game with simpledisplay and shows some mouse input and basic output
887 	code.
888 +/
889 version(demos)
890 unittest {
891 	import arsd.simpledisplay;
892 
893 	enum GameSquare {
894 		mine = 0,
895 		clear,
896 		m1, m2, m3, m4, m5, m6, m7, m8
897 	}
898 
899 	enum UserSquare {
900 		unknown,
901 		revealed,
902 		flagged,
903 		questioned
904 	}
905 
906 	enum GameState {
907 		inProgress,
908 		lose,
909 		win
910 	}
911 
912 	GameSquare[] board;
913 	UserSquare[] userState;
914 	GameState gameState;
915 	int boardWidth;
916 	int boardHeight;
917 
918 	bool isMine(int x, int y) {
919 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
920 			return false;
921 		return board[y * boardWidth + x] == GameSquare.mine;
922 	}
923 
924 	GameState reveal(int x, int y) {
925 		if(board[y * boardWidth + x] == GameSquare.clear) {
926 			floodFill(userState, boardWidth, boardHeight,
927 				UserSquare.unknown, UserSquare.revealed,
928 				x, y,
929 				(x, y) {
930 					if(board[y * boardWidth + x] == GameSquare.clear)
931 						return true;
932 					else {
933 						userState[y * boardWidth + x] = UserSquare.revealed;
934 						return false;
935 					}
936 				});
937 		} else {
938 			userState[y * boardWidth + x] = UserSquare.revealed;
939 			if(isMine(x, y))
940 				return GameState.lose;
941 		}
942 
943 		foreach(state; userState) {
944 			if(state == UserSquare.unknown || state == UserSquare.questioned)
945 				return GameState.inProgress;
946 		}
947 
948 		return GameState.win;
949 	}
950 
951 	void initializeBoard(int width, int height, int numberOfMines) {
952 		boardWidth = width;
953 		boardHeight = height;
954 		board.length = width * height;
955 
956 		userState.length = width * height;
957 		userState[] = UserSquare.unknown;
958 
959 		import std.algorithm, std.random, std.range;
960 
961 		board[] = GameSquare.clear;
962 
963 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
964 			board[minePosition] = GameSquare.mine;
965 
966 		int x;
967 		int y;
968 		foreach(idx, ref square; board) {
969 			if(square == GameSquare.clear) {
970 				int danger = 0;
971 				danger += isMine(x-1, y-1)?1:0;
972 				danger += isMine(x-1, y)?1:0;
973 				danger += isMine(x-1, y+1)?1:0;
974 				danger += isMine(x, y-1)?1:0;
975 				danger += isMine(x, y+1)?1:0;
976 				danger += isMine(x+1, y-1)?1:0;
977 				danger += isMine(x+1, y)?1:0;
978 				danger += isMine(x+1, y+1)?1:0;
979 
980 				square = cast(GameSquare) (danger + 1);
981 			}
982 
983 			x++;
984 			if(x == width) {
985 				x = 0;
986 				y++;
987 			}
988 		}
989 	}
990 
991 	void redraw(SimpleWindow window) {
992 		import std.conv;
993 
994 		auto painter = window.draw();
995 
996 		painter.clear();
997 
998 		final switch(gameState) with(GameState) {
999 			case inProgress:
1000 				break;
1001 			case win:
1002 				painter.fillColor = Color.green;
1003 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1004 				return;
1005 			case lose:
1006 				painter.fillColor = Color.red;
1007 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1008 				return;
1009 		}
1010 
1011 		int x = 0;
1012 		int y = 0;
1013 
1014 		foreach(idx, square; board) {
1015 			auto state = userState[idx];
1016 
1017 			final switch(state) with(UserSquare) {
1018 				case unknown:
1019 					painter.outlineColor = Color.black;
1020 					painter.fillColor = Color(128,128,128);
1021 
1022 					painter.drawRectangle(
1023 						Point(x * 20, y * 20),
1024 						20, 20
1025 					);
1026 				break;
1027 				case revealed:
1028 					if(square == GameSquare.clear) {
1029 						painter.outlineColor = Color.white;
1030 						painter.fillColor = Color.white;
1031 
1032 						painter.drawRectangle(
1033 							Point(x * 20, y * 20),
1034 							20, 20
1035 						);
1036 					} else {
1037 						painter.outlineColor = Color.black;
1038 						painter.fillColor = Color.white;
1039 
1040 						painter.drawText(
1041 							Point(x * 20, y * 20),
1042 							to!string(square)[1..2],
1043 							Point(x * 20 + 20, y * 20 + 20),
1044 							TextAlignment.Center | TextAlignment.VerticalCenter);
1045 					}
1046 				break;
1047 				case flagged:
1048 					painter.outlineColor = Color.black;
1049 					painter.fillColor = Color.red;
1050 					painter.drawRectangle(
1051 						Point(x * 20, y * 20),
1052 						20, 20
1053 					);
1054 				break;
1055 				case questioned:
1056 					painter.outlineColor = Color.black;
1057 					painter.fillColor = Color.yellow;
1058 					painter.drawRectangle(
1059 						Point(x * 20, y * 20),
1060 						20, 20
1061 					);
1062 				break;
1063 			}
1064 
1065 			x++;
1066 			if(x == boardWidth) {
1067 				x = 0;
1068 				y++;
1069 			}
1070 		}
1071 
1072 	}
1073 
1074 	void main() {
1075 		auto window = new SimpleWindow(200, 200);
1076 
1077 		initializeBoard(10, 10, 10);
1078 
1079 		redraw(window);
1080 		window.eventLoop(0,
1081 			delegate (MouseEvent me) {
1082 				if(me.type != MouseEventType.buttonPressed)
1083 					return;
1084 				auto x = me.x / 20;
1085 				auto y = me.y / 20;
1086 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1087 					if(me.button == MouseButton.left) {
1088 						gameState = reveal(x, y);
1089 					} else {
1090 						userState[y*boardWidth+x] = UserSquare.flagged;
1091 					}
1092 					redraw(window);
1093 				}
1094 			}
1095 		);
1096 	}
1097 }
1098 
1099 /*
1100 version(OSX) {
1101 	version=without_opengl;
1102 	version=allow_unimplemented_features;
1103 	version=OSXCocoa;
1104 	pragma(linkerDirective, "-framework Cocoa");
1105 }
1106 */
1107 
1108 version(without_opengl) {
1109 	enum SdpyIsUsingIVGLBinds = false;
1110 } else /*version(Posix)*/ {
1111 	static if (__traits(compiles, (){import iv.glbinds;})) {
1112 		enum SdpyIsUsingIVGLBinds = true;
1113 		public import iv.glbinds;
1114 		//pragma(msg, "SDPY: using iv.glbinds");
1115 	} else {
1116 		enum SdpyIsUsingIVGLBinds = false;
1117 	}
1118 //} else {
1119 //	enum SdpyIsUsingIVGLBinds = false;
1120 }
1121 
1122 
1123 version(Windows) {
1124 	//import core.sys.windows.windows;
1125 	import core.sys.windows.winnls;
1126 	import core.sys.windows.windef;
1127 	import core.sys.windows.basetyps;
1128 	import core.sys.windows.winbase;
1129 	import core.sys.windows.winuser;
1130 	import core.sys.windows.shellapi;
1131 	import core.sys.windows.wingdi;
1132 	static import gdi = core.sys.windows.wingdi; // so i
1133 
1134 	pragma(lib, "gdi32");
1135 	pragma(lib, "user32");
1136 
1137 	// for AlphaBlend... a breaking change....
1138 	version(CRuntime_DigitalMars) { } else
1139 		pragma(lib, "msimg32");
1140 } else version (linux) {
1141 	//k8: this is hack for rdmd. sorry.
1142 	static import core.sys.linux.epoll;
1143 	static import core.sys.linux.timerfd;
1144 }
1145 
1146 
1147 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1148 
1149 // http://wiki.dlang.org/Simpledisplay.d
1150 
1151 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1152 
1153 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1154 // but can i control the scroll lock led
1155 
1156 
1157 // Note: if you are using Image on X, you might want to do:
1158 /*
1159 	static if(UsingSimpledisplayX11) {
1160 		if(!Image.impl.xshmAvailable) {
1161 			// the images will use the slower XPutImage, you might
1162 			// want to consider an alternative method to get better speed
1163 		}
1164 	}
1165 
1166 	If the shared memory extension is available though, simpledisplay uses it
1167 	for a significant speed boost whenever you draw large Images.
1168 */
1169 
1170 // 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.
1171 
1172 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1173 
1174 /*
1175 	Biggest FIXME:
1176 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1177 
1178 		clean up opengl contexts when their windows close
1179 
1180 		fix resizing the bitmaps/pixmaps
1181 */
1182 
1183 // BTW on Windows:
1184 // -L/SUBSYSTEM:WINDOWS:5.0
1185 // to dmd will make a nice windows binary w/o a console if you want that.
1186 
1187 /*
1188 	Stuff to add:
1189 
1190 	use multibyte functions everywhere we can
1191 
1192 	OpenGL windows
1193 	more event stuff
1194 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1195 
1196 
1197 	resizeEvent
1198 		and make the windows non-resizable by default,
1199 		or perhaps stretched (if I can find something in X like StretchBlt)
1200 
1201 	take a screenshot function!
1202 
1203 	Pens and brushes?
1204 	Maybe a global event loop?
1205 
1206 	Mouse deltas
1207 	Key items
1208 */
1209 
1210 /*
1211 From MSDN:
1212 
1213 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1214 
1215 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.
1216 
1217 */
1218 
1219 version(linux) {
1220 	version = X11;
1221 	version(without_libnotify) {
1222 		// we cool
1223 	}
1224 	else
1225 		version = libnotify;
1226 }
1227 
1228 version(libnotify) {
1229 	pragma(lib, "dl");
1230 	import core.sys.posix.dlfcn;
1231 
1232 	void delegate()[int] libnotify_action_delegates;
1233 	int libnotify_action_delegates_count;
1234 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1235 		auto idx = cast(int) user_data;
1236 		if(auto dgptr = idx in libnotify_action_delegates) {
1237 			(*dgptr)();
1238 			libnotify_action_delegates.remove(idx);
1239 		}
1240 	}
1241 
1242 	struct C_DynamicLibrary {
1243 		void* handle;
1244 		this(string name) {
1245 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1246 			if(handle is null)
1247 				throw new Exception("dlopen");
1248 		}
1249 
1250 		void close() {
1251 			dlclose(handle);
1252 		}
1253 
1254 		~this() {
1255 			// close
1256 		}
1257 
1258 		// FIXME: this looks up by name every time....
1259 		template call(string func, Ret, Args...) {
1260 			extern(C) Ret function(Args) fptr;
1261 			typeof(fptr) call() {
1262 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1263 				return fptr;
1264 			}
1265 		}
1266 	}
1267 
1268 	C_DynamicLibrary* libnotify;
1269 }
1270 
1271 version(OSX) {
1272 	version(OSXCocoa) {}
1273 	else { version = X11; }
1274 }
1275 	//version = OSXCocoa; // this was written by KennyTM
1276 version(FreeBSD)
1277 	version = X11;
1278 version(Solaris)
1279 	version = X11;
1280 
1281 version(X11) {
1282 	version(without_xft) {}
1283 	else version=with_xft;
1284 }
1285 
1286 void featureNotImplemented()() {
1287 	version(allow_unimplemented_features)
1288 		throw new NotYetImplementedException();
1289 	else
1290 		static assert(0);
1291 }
1292 
1293 // these are so the static asserts don't trigger unless you want to
1294 // add support to it for an OS
1295 version(Windows)
1296 	version = with_timer;
1297 version(linux)
1298 	version = with_timer;
1299 
1300 version(with_timer)
1301 	enum bool SimpledisplayTimerAvailable = true;
1302 else
1303 	enum bool SimpledisplayTimerAvailable = false;
1304 
1305 /// 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.
1306 version(Windows)
1307 	enum bool UsingSimpledisplayWindows = true;
1308 else
1309 	enum bool UsingSimpledisplayWindows = false;
1310 
1311 /// 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.
1312 version(X11)
1313 	enum bool UsingSimpledisplayX11 = true;
1314 else
1315 	enum bool UsingSimpledisplayX11 = false;
1316 
1317 /// 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.
1318 version(OSXCocoa)
1319 	enum bool UsingSimpledisplayCocoa = true;
1320 else
1321 	enum bool UsingSimpledisplayCocoa = false;
1322 
1323 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1324 version(Windows)
1325 	enum multipleWindowsSupported = true;
1326 else version(X11)
1327 	enum multipleWindowsSupported = true;
1328 else version(OSXCocoa)
1329 	enum multipleWindowsSupported = true;
1330 else
1331 	static assert(0);
1332 
1333 version(without_opengl)
1334 	enum bool OpenGlEnabled = false;
1335 else
1336 	enum bool OpenGlEnabled = true;
1337 
1338 /++
1339 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1340 	If you mix this in above your `main` function, you no longer need to use the linker
1341 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1342 
1343 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1344 
1345 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1346 	stderr writeln. It will fail and throw an exception.
1347 
1348 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1349 
1350 	History:
1351 		Added November 24, 2021 (dub v10.4)
1352 +/
1353 mixin template EnableWindowsSubsystem() {
1354 	version(Windows)
1355 	version(CRuntime_Microsoft) {
1356 		pragma(linkerDirective, "/subsystem:windows");
1357 		version(LDC)
1358 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1359 		else
1360 			pragma(linkerDirective, "/entry:mainCRTStartup");
1361 	}
1362 }
1363 
1364 
1365 /++
1366 	After selecting a type from [WindowTypes], you may further customize
1367 	its behavior by setting one or more of these flags.
1368 
1369 
1370 	The different window types have different meanings of `normal`. If the
1371 	window type already is a good match for what you want to do, you should
1372 	just use [WindowFlags.normal], the default, which will do the right thing
1373 	for your users.
1374 
1375 	The window flags will not always be honored by the operating system
1376 	and window managers; they are hints, not commands.
1377 +/
1378 enum WindowFlags : int {
1379 	normal = 0, ///
1380 	skipTaskbar = 1, ///
1381 	alwaysOnTop = 2, ///
1382 	alwaysOnBottom = 4, ///
1383 	cannotBeActivated = 8, ///
1384 	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.
1385 	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.
1386 	/++
1387 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1388 		it is still a top-level window. This should NOT be set separately for most window types.
1389 
1390 		A transient window will not keep the application open if its main window closes.
1391 
1392 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1393 
1394 
1395 		From the ICCM:
1396 
1397 		$(BLOCKQUOTE
1398 			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.
1399 
1400 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1401 		)
1402 
1403 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1404 
1405 		History:
1406 			Added February 23, 2021 but not yet stabilized.
1407 	+/
1408 	transient = 64,
1409 	/++
1410 		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.
1411 
1412 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1413 
1414 		History:
1415 			Added April 1, 2022
1416 	+/
1417 	managesChildWindowFocus = 128,
1418 
1419 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1420 }
1421 
1422 /++
1423 	When creating a window, you can pass a type to SimpleWindow's constructor,
1424 	then further customize the window by changing `WindowFlags`.
1425 
1426 
1427 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1428 	use. The others are there to build a foundation for a higher level GUI toolkit,
1429 	but are themselves not as high level as you might think from their names.
1430 
1431 	This list is based on the EMWH spec for X11.
1432 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1433 +/
1434 enum WindowTypes : int {
1435 	/// An ordinary application window.
1436 	normal,
1437 	/// 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.
1438 	undecorated,
1439 	/// 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.
1440 	eventOnly,
1441 	/// A drop down menu, such as from a menu bar
1442 	dropdownMenu,
1443 	/// A popup menu, such as from a right click
1444 	popupMenu,
1445 	/// A popup bubble notification
1446 	notification,
1447 	/*
1448 	menu, /// a tearable menu bar
1449 	splashScreen, /// a loading splash screen for your application
1450 	tooltip, /// A tiny window showing temporary help text or something.
1451 	comboBoxDropdown,
1452 	dialog,
1453 	toolbar
1454 	*/
1455 	/// a child nested inside the parent. You must pass a parent window to the ctor
1456 	nestedChild,
1457 
1458 	/++
1459 		The type you get when you pass in an existing browser handle, which means most
1460 		of simpledisplay's fancy things will not be done since they were never set up.
1461 
1462 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1463 		failure; you should use the existing handle constructor.
1464 
1465 		History:
1466 			Added November 17, 2022 (previously it would have type `normal`)
1467 	+/
1468 	minimallyWrapped
1469 }
1470 
1471 
1472 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1473 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1474 private __gshared char* sdpyWindowClassStr = null;
1475 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1476 
1477 /**
1478 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1479 	You may want to change context version if you want to use advanced shaders or
1480 	other modern OpenGL techinques. This setting doesn't affect already created
1481 	windows. You may use version 2.1 as your default, which should be supported
1482 	by any box since 2006, so seems to be a reasonable choice.
1483 
1484 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1485 	old context creation code without any version specified. This is the safest
1486 	way to init OpenGL, but it may not give you access to advanced features.
1487 
1488 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1489 */
1490 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1491 
1492 /**
1493 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1494 	pipeline functions, and without "compatible" mode you won't be able to use
1495 	your old non-shader-based code with such contexts. By default SimpleDisplay
1496 	creates compatible context, so you can gradually upgrade your OpenGL code if
1497 	you want to (or leave it as is, as it should "just work").
1498 */
1499 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1500 
1501 /**
1502 	Set to `true` to allow creating OpenGL context with lower version than requested
1503 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1504 	`openGLContextFallbackActivated()` will return `true`.
1505 	*/
1506 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1507 
1508 /**
1509 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1510 	*/
1511 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1512 
1513 
1514 /**
1515 	Set window class name for all following `new SimpleWindow()` calls.
1516 
1517 	WARNING! For Windows, you should set your class name before creating any
1518 	window, and NEVER change it after that!
1519 */
1520 void sdpyWindowClass (const(char)[] v) {
1521 	import core.stdc.stdlib : realloc;
1522 	if (v.length == 0) v = "SimpleDisplayWindow";
1523 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1524 	if (sdpyWindowClassStr is null) return; // oops
1525 	sdpyWindowClassStr[0..v.length+1] = 0;
1526 	sdpyWindowClassStr[0..v.length] = v[];
1527 }
1528 
1529 /**
1530 	Get current window class name.
1531 */
1532 string sdpyWindowClass () {
1533 	if (sdpyWindowClassStr is null) return null;
1534 	foreach (immutable idx; 0..size_t.max-1) {
1535 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1536 	}
1537 	return null;
1538 }
1539 
1540 /++
1541 	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.
1542 
1543 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1544 +/
1545 float[2] getDpi() {
1546 	float[2] dpi;
1547 	version(Windows) {
1548 		HDC screen = GetDC(null);
1549 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1550 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1551 	} else version(X11) {
1552 		auto display = XDisplayConnection.get;
1553 		auto screen = DefaultScreen(display);
1554 
1555 		void fallback() {
1556 			/+
1557 			// 25.4 millimeters in an inch...
1558 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1559 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1560 			+/
1561 
1562 			// the physical size isn't actually as important as the logical size since this is
1563 			// all about scaling really
1564 			dpi[0] = 96;
1565 			dpi[1] = 96;
1566 		}
1567 
1568 		auto xft = getXftDpi();
1569 		if(xft is float.init)
1570 			fallback();
1571 		else {
1572 			dpi[0] = xft;
1573 			dpi[1] = xft;
1574 		}
1575 	}
1576 
1577 	return dpi;
1578 }
1579 
1580 version(X11)
1581 float getXftDpi() {
1582 	auto display = XDisplayConnection.get;
1583 
1584 	char* resourceString = XResourceManagerString(display);
1585 	XrmInitialize();
1586 
1587 	if (resourceString) {
1588 		auto db = XrmGetStringDatabase(resourceString);
1589 		XrmValue value;
1590 		char* type;
1591 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1592 			if (value.addr) {
1593 				import core.stdc.stdlib;
1594 				return atof(cast(char*) value.addr);
1595 			}
1596 		}
1597 	}
1598 
1599 	return float.init;
1600 }
1601 
1602 /++
1603 	Implementation used by [SimpleWindow.takeScreenshot].
1604 
1605 	Params:
1606 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1607 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1608 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1609 		x = the x-offset of the image to capture, from the left.
1610 		y = the y-offset of the image to capture, from the top.
1611 
1612 	History:
1613 		Added on March 14, 2021
1614 
1615 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1616 
1617 +/
1618 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1619 	TrueColorImage got;
1620 	version(X11) {
1621 		auto display = XDisplayConnection.get;
1622 		if(handle == 0)
1623 			handle = RootWindow(display, DefaultScreen(display));
1624 
1625 		if(width == 0 || height == 0) {
1626 			Window root;
1627 			int xpos, ypos;
1628 			uint widthret, heightret, borderret, depthret;
1629 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1630 
1631 			if(width == 0)
1632 				width = widthret;
1633 			if(height == 0)
1634 				height = heightret;
1635 		}
1636 
1637 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1638 
1639 		// https://github.com/adamdruppe/arsd/issues/98
1640 
1641 		auto i = new Image(image);
1642 		got = i.toTrueColorImage();
1643 
1644 		XDestroyImage(image);
1645 	} else version(Windows) {
1646 		auto hdc = GetDC(handle);
1647 		scope(exit) ReleaseDC(handle, hdc);
1648 
1649 		if(width == 0 || height == 0) {
1650 			BITMAP bmHeader;
1651 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1652 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1653 			if(width == 0)
1654 				width = bmHeader.bmWidth;
1655 			if(height == 0)
1656 				height = bmHeader.bmHeight;
1657 		}
1658 
1659 		auto i = new Image(width, height);
1660 		HDC hdcMem = CreateCompatibleDC(hdc);
1661 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1662 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1663 		SelectObject(hdcMem, hbmOld);
1664 		DeleteDC(hdcMem);
1665 
1666 		got = i.toTrueColorImage();
1667 	} else featureNotImplemented();
1668 
1669 	return got;
1670 }
1671 
1672 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1673 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1674 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1675 
1676 version(Windows)
1677 shared static this() {
1678 	auto lib = LoadLibrary("User32.dll");
1679 	if(lib is null)
1680 		return;
1681 	//scope(exit)
1682 		//FreeLibrary(lib);
1683 
1684 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1685 
1686 	if(SetProcessDpiAwarenessContext is null)
1687 		return;
1688 
1689 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1690 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1691 		//writeln(GetLastError());
1692 	}
1693 
1694 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1695 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1696 }
1697 
1698 /++
1699 	Blocking mode for event loop calls associated with a window instance.
1700 
1701 	History:
1702 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1703 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1704 		is, all would block until the application quit.
1705 
1706 		That behavior can still be achieved here with `untilApplicationQuits`,
1707 		or explicitly calling the top-level `EventLoop.get.run` function.
1708 +/
1709 enum BlockingMode {
1710 	/++
1711 		The event loop call will block until the whole application is ready
1712 		to quit if it is the only one running, but if it is nested inside
1713 		another one, it will only block until the window you're calling it on
1714 		closes.
1715 	+/
1716 	automatic             = 0x00,
1717 	/++
1718 		The event loop call will only return when the whole application
1719 		is ready to quit. This usually means all windows have been closed.
1720 
1721 		This is appropriate for your main application event loop.
1722 	+/
1723 	untilApplicationQuits = 0x01,
1724 	/++
1725 		The event loop will return when the window you're calling it on
1726 		closes. If there are other windows still open, they may be destroyed
1727 		unless you have another event loop running later.
1728 
1729 		This might be appropriate for a modal dialog box loop. Remember that
1730 		other windows are still processing input though, so you can end up
1731 		with a lengthy call stack if this happens in a loop, similar to a
1732 		recursive function (well, it literally is a recursive function, just
1733 		not an obvious looking one).
1734 	+/
1735 	untilWindowCloses     = 0x02,
1736 	/++
1737 		If an event loop is already running, this call will immediately
1738 		return, allowing the existing loop to handle it. If not, this call
1739 		will block until the condition you bitwise-or into the flag.
1740 
1741 		The default is to block until the application quits, same as with
1742 		the `automatic` setting (since if it were nested, which triggers until
1743 		window closes in automatic, this flag would instead not block at all),
1744 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1745 		it will only nest until the window closes. You might want that if you are
1746 		going to open two windows simultaneously and want closing just one of them
1747 		to trigger the event loop return.
1748 	+/
1749 	onlyIfNotNested       = 0x10,
1750 }
1751 
1752 /++
1753 	The flagship window class.
1754 
1755 
1756 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1757 	out of more advanced or complex features of the underlying windowing system.
1758 
1759 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1760 	and get a suitable window to work with.
1761 
1762 	From there, you can opt into additional features, like custom resizability and OpenGL support
1763 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1764 	and customization flags with the final two constructor arguments.
1765 
1766 	If none of that works for you, you can also create a window using native function calls, then
1767 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1768 	though, if you do this, managing the window is still your own responsibility! Notably, you
1769 	will need to destroy it yourself.
1770 +/
1771 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1772 
1773 	/++
1774 		Copies the window's current state into a [TrueColorImage].
1775 
1776 		Be warned: this can be a very slow operation
1777 
1778 		History:
1779 			Actually implemented on March 14, 2021
1780 	+/
1781 	TrueColorImage takeScreenshot() {
1782 		version(Windows)
1783 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1784 		else version(OSXCocoa)
1785 			throw new NotYetImplementedException();
1786 		else
1787 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1788 	}
1789 
1790 	/++
1791 		Returns the actual logical DPI for the window on its current display monitor. If the window
1792 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1793 
1794 		Please note this function may return zero if it doesn't know the answer!
1795 
1796 
1797 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1798 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1799 
1800 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1801 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1802 		window primarily resides on by checking the center point of the window against the monitor map.
1803 
1804 		Returns:
1805 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1806 			assumes the X and Y dpi are the same.
1807 
1808 		History:
1809 			Added November 26, 2021 (dub v10.4)
1810 
1811 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1812 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1813 			that.
1814 
1815 		Bugs:
1816 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1817 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1818 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1819 			and 1.5 on the secondary monitor.
1820 
1821 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1822 			is a historical misnomer - the real thing of interest is the scale factor and due to
1823 			compatibility concerns the scale would modify dpi values to trick applications. But since
1824 			that's the terminology common out there, I used it too.
1825 
1826 		See_Also:
1827 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1828 			as this since the window many be on a different monitor, but it is a reasonable fallback
1829 			to use if `actualDpi` returns 0.
1830 
1831 			[onDpiChanged] is changed when `actualDpi` has changed.
1832 	+/
1833 	int actualDpi() {
1834 		if(!actualDpiLoadAttempted) {
1835 			// FIXME: do the actual monitor we are on
1836 			// and on X this is a good chance to load the monitor map.
1837 			version(Windows) {
1838 				if(GetDpiForWindow)
1839 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1840 			} else version(X11) {
1841 				if(!xRandrInfoLoadAttemped) {
1842 					xRandrInfoLoadAttemped = true;
1843 					if(!XRandrLibrary.attempted) {
1844 						XRandrLibrary.loadDynamicLibrary();
1845 					}
1846 
1847 					if(XRandrLibrary.loadSuccessful) {
1848 						auto display = XDisplayConnection.get;
1849 						int scratch;
1850 						int major, minor;
1851 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1852 							goto fallback;
1853 
1854 						XRRQueryVersion(display, &major, &minor);
1855 						if(major <= 1 && minor < 5)
1856 							goto fallback;
1857 
1858 						int count;
1859 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1860 						if(monitors is null)
1861 							goto fallback;
1862 						scope(exit) XRRFreeMonitors(monitors);
1863 
1864 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1865 						MonitorInfo.info.assumeSafeAppend();
1866 						foreach(idx, monitor; monitors[0 .. count]) {
1867 							MonitorInfo.info ~= MonitorInfo(
1868 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1869 								Size(monitor.mwidth, monitor.mheight),
1870 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1871 							);
1872 
1873 							/+
1874 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1875 							// unknown physical size, just guess 96 to avoid divide by zero
1876 							MonitorInfo.info ~= MonitorInfo(
1877 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1878 								Size(monitor.mwidth, monitor.mheight),
1879 								96
1880 							);
1881 							else
1882 							// and actual thing
1883 							MonitorInfo.info ~= MonitorInfo(
1884 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1885 								Size(monitor.mwidth, monitor.mheight),
1886 								minInternal(
1887 									// millimeter to int then rounding up.
1888 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1889 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1890 								)
1891 							);
1892 							+/
1893 						}
1894 					// import std.stdio; writeln("Here", MonitorInfo.info);
1895 					}
1896 				}
1897 
1898 				if(XRandrLibrary.loadSuccessful) {
1899 					updateActualDpi(true);
1900 					//import std.stdio; writeln("updated");
1901 
1902 					if(!requestedInput) {
1903 						// this is what requests live updates should the configuration change
1904 						// each time you select input, it sends an initial event, so very important
1905 						// to not get into a loop of selecting input, getting event, updating data,
1906 						// and reselecting input...
1907 						requestedInput = true;
1908 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1909 						//import std.stdio; writeln("requested input");
1910 					}
1911 				} else {
1912 					fallback:
1913 					// make sure we disable events that aren't coming
1914 					xrrEventBase = -1;
1915 					// best guess... respect the custom scaling user command to some extent at least though
1916 					actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1917 				}
1918 			}
1919 			actualDpiLoadAttempted = true;
1920 		}
1921 		return actualDpi_;
1922 	}
1923 
1924 	private int actualDpi_;
1925 	private bool actualDpiLoadAttempted;
1926 
1927 	version(X11) private {
1928 		bool requestedInput;
1929 		static bool xRandrInfoLoadAttemped;
1930 		struct MonitorInfo {
1931 			Rectangle position;
1932 			Size size;
1933 			int dpi;
1934 
1935 			static MonitorInfo[] info;
1936 		}
1937 		bool screenPositionKnown;
1938 		int screenPositionX;
1939 		int screenPositionY;
1940 		void updateActualDpi(bool loadingNow = false) {
1941 			if(!loadingNow && !actualDpiLoadAttempted)
1942 				actualDpi(); // just to make it do the load
1943 			foreach(idx, m; MonitorInfo.info) {
1944 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1945 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1946 					actualDpi_ = m.dpi;
1947 					//import std.stdio; writeln("monitor ", idx);
1948 					if(changed && onDpiChanged)
1949 						onDpiChanged();
1950 					break;
1951 				}
1952 			}
1953 		}
1954 	}
1955 
1956 	/++
1957 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
1958 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
1959 
1960 		History:
1961 			Added November 26, 2021 (dub v10.4)
1962 
1963 		See_Also:
1964 			[actualDpi]
1965 	+/
1966 	void delegate() onDpiChanged;
1967 
1968 	version(X11) {
1969 		void recreateAfterDisconnect() {
1970 			if(!stateDiscarded) return;
1971 
1972 			if(_parent !is null && _parent.stateDiscarded)
1973 				_parent.recreateAfterDisconnect();
1974 
1975 			bool wasHidden = hidden;
1976 
1977 			activeScreenPainter = null; // should already be done but just to confirm
1978 
1979 			actualDpi_ = 0;
1980 			actualDpiLoadAttempted = false;
1981 			xRandrInfoLoadAttemped = false;
1982 
1983 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1984 
1985 			if(auto dh = dropHandler) {
1986 				dropHandler = null;
1987 				enableDragAndDrop(this, dh);
1988 			}
1989 
1990 			if(recreateAdditionalConnectionState)
1991 				recreateAdditionalConnectionState();
1992 
1993 			hidden = wasHidden;
1994 			stateDiscarded = false;
1995 		}
1996 
1997 		bool stateDiscarded;
1998 		void discardConnectionState() {
1999 			if(XDisplayConnection.display)
2000 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2001 			if(discardAdditionalConnectionState)
2002 				discardAdditionalConnectionState();
2003 			stateDiscarded = true;
2004 		}
2005 
2006 		void delegate() discardAdditionalConnectionState;
2007 		void delegate() recreateAdditionalConnectionState;
2008 
2009 	}
2010 
2011 	private DropHandler dropHandler;
2012 
2013 	SimpleWindow _parent;
2014 	bool beingOpenKeepsAppOpen = true;
2015 	/++
2016 		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.
2017 
2018 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2019 
2020 		Params:
2021 
2022 		width = the width of the window's client area, in pixels
2023 		height = the height of the window's client area, in pixels
2024 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2025 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2026 		resizable = [Resizability] has three options:
2027 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2028 			$(P `fixedSize` will not allow the user to resize the window.)
2029 			$(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.)
2030 		windowType = The type of window you want to make.
2031 		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.
2032 		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".
2033 	+/
2034 	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) {
2035 		claimGuiThread();
2036 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2037 		this._width = this._virtualWidth = width;
2038 		this._height = this._virtualHeight = height;
2039 		this.openglMode = opengl;
2040 		version(X11) {
2041 			// auto scale not implemented except with opengl and even there it is kinda weird
2042 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2043 				resizable = Resizability.fixedSize;
2044 		}
2045 		this.resizability = resizable;
2046 		this.windowType = windowType;
2047 		this.customizationFlags = customizationFlags;
2048 		this._title = (title is null ? "D Application" : title);
2049 		this._parent = parent;
2050 		impl.createWindow(width, height, this._title, opengl, parent);
2051 
2052 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2053 			beingOpenKeepsAppOpen = false;
2054 	}
2055 
2056 	/// ditto
2057 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2058 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2059 	}
2060 
2061 	/// Same as above, except using the `Size` struct instead of separate width and height.
2062 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2063 		this(size.width, size.height, title, opengl, resizable);
2064 	}
2065 
2066 	/// ditto
2067 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2068 		this(size, title, opengl, resizable);
2069 	}
2070 
2071 
2072 	/++
2073 		Creates a window based on the given [Image]. It's client area
2074 		width and height is equal to the image. (A window's client area
2075 		is the drawable space inside; it excludes the title bar, etc.)
2076 
2077 		Windows based on images will not be resizable and do not use OpenGL.
2078 
2079 		It will draw the image in upon creation, but this will be overwritten
2080 		upon any draws, including the initial window visible event.
2081 
2082 		You probably do not want to use this and it may be removed from
2083 		the library eventually, or I might change it to be a "permanent"
2084 		background image; one that is automatically drawn on it before any
2085 		other drawing event. idk.
2086 	+/
2087 	this(Image image, string title = null) {
2088 		this(image.width, image.height, title);
2089 		this.image = image;
2090 	}
2091 
2092 	/++
2093 		Wraps a native window handle with very little additional processing - notably no destruction
2094 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2095 		windows created through the low level API (so you can use platform-specific options and
2096 		other details SimpleWindow does not expose) available to the event loop wrappers.
2097 	+/
2098 	this(NativeWindowHandle nativeWindow) {
2099 		windowType = WindowTypes.minimallyWrapped;
2100 		version(Windows)
2101 			impl.hwnd = nativeWindow;
2102 		else version(X11) {
2103 			impl.window = nativeWindow;
2104 			if(nativeWindow)
2105 				display = XDisplayConnection.get(); // get initial display to not segfault
2106 		} else version(OSXCocoa)
2107 			throw new NotYetImplementedException();
2108 		else featureNotImplemented();
2109 		// FIXME: set the size correctly
2110 		_width = 1;
2111 		_height = 1;
2112 		if(nativeWindow)
2113 			nativeMapping[nativeWindow] = this;
2114 
2115 		beingOpenKeepsAppOpen = false;
2116 
2117 		if(nativeWindow)
2118 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2119 		_suppressDestruction = true; // so it doesn't try to close
2120 	}
2121 
2122 	/++
2123 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2124 		The delegate will be called when the window manager asks you to take focus.
2125 
2126 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2127 
2128 		History:
2129 			Added April 1, 2022 (dub v10.8)
2130 	+/
2131 	SimpleWindow delegate() setRequestedInputFocus;
2132 
2133 	/// Experimental, do not use yet
2134 	/++
2135 		Grabs exclusive input from the user until you release it with
2136 		[releaseInputGrab].
2137 
2138 
2139 		Note: it is extremely rude to do this without good reason.
2140 		Reasons may include doing some kind of mouse drag operation
2141 		or popping up a temporary menu that should get events and will
2142 		be dismissed at ease by the user clicking away.
2143 
2144 		Params:
2145 			keyboard = do you want to grab keyboard input?
2146 			mouse = grab mouse input?
2147 			confine = confine the mouse cursor to inside this window?
2148 
2149 		History:
2150 			Prior to March 11, 2021, grabbing the keyboard would always also
2151 			set the X input focus. Now, it only focuses if it is a non-transient
2152 			window and otherwise manages the input direction internally.
2153 
2154 			This means spurious focus/blur events will no longer be sent and the
2155 			application will not steal focus from other applications (which the
2156 			window manager may have rejected anyway).
2157 	+/
2158 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2159 		static if(UsingSimpledisplayX11) {
2160 			XSync(XDisplayConnection.get, 0);
2161 			if(keyboard) {
2162 				if(isTransient && _parent) {
2163 					/*
2164 					FIXME:
2165 						setting the keyboard focus is not actually that helpful, what I more likely want
2166 						is the events from the parent window to be sent over here if we're transient.
2167 					*/
2168 
2169 					_parent.inputProxy = this;
2170 				} else {
2171 					XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
2172 				}
2173 			}
2174 			if(mouse) {
2175 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2176 				EventMask.PointerMotionMask // FIXME: not efficient
2177 				| EventMask.ButtonPressMask
2178 				| EventMask.ButtonReleaseMask
2179 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2180 				)
2181 			{
2182 				XSync(XDisplayConnection.get, 0);
2183 				import core.stdc.stdio;
2184 				printf("Grab input failed %d\n", res);
2185 				//throw new Exception("Grab input failed");
2186 			} else {
2187 				// cool
2188 			}
2189 			}
2190 
2191 		} else version(Windows) {
2192 			// FIXME: keyboard?
2193 			SetCapture(impl.hwnd);
2194 			if(confine) {
2195 				RECT rcClip;
2196 				//RECT rcOldClip;
2197 				//GetClipCursor(&rcOldClip);
2198 				GetWindowRect(hwnd, &rcClip);
2199 				ClipCursor(&rcClip);
2200 			}
2201 		} else version(OSXCocoa) {
2202 			throw new NotYetImplementedException();
2203 		} else static assert(0);
2204 	}
2205 
2206 	private Point imePopupLocation = Point(0, 0);
2207 
2208 	/++
2209 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2210 
2211 		Bugs:
2212 			Not implemented outside X11.
2213 	+/
2214 	void setIMEPopupLocation(Point location) {
2215 		static if(UsingSimpledisplayX11) {
2216 			imePopupLocation = location;
2217 			updateIMEPopupLocation();
2218 		} else {
2219 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2220 			// throw new NotYetImplementedException();
2221 		}
2222 	}
2223 
2224 	/// ditto
2225 	void setIMEPopupLocation(int x, int y) {
2226 		return setIMEPopupLocation(Point(x, y));
2227 	}
2228 
2229 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2230 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2231 	// receives a ConfigureNotify event
2232 	private void updateIMEPopupLocation() {
2233 		static if(UsingSimpledisplayX11) {
2234 			if (xic is null) {
2235 				return;
2236 			}
2237 
2238 			XPoint nspot;
2239 			nspot.x = cast(short) imePopupLocation.x;
2240 			nspot.y = cast(short) imePopupLocation.y;
2241 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2242 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2243 			XFree(preeditAttr);
2244 		}
2245 	}
2246 
2247 	private bool imeFocused = true;
2248 
2249 	/++
2250 		Tells the IME whether or not an input field is currently focused in the window.
2251 
2252 		Bugs:
2253 			Not implemented outside X11.
2254 	+/
2255 	void setIMEFocused(bool value) {
2256 		imeFocused = value;
2257 		updateIMEFocused();
2258 	}
2259 
2260 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2261 	private void updateIMEFocused() {
2262 		static if(UsingSimpledisplayX11) {
2263 			if (xic is null) {
2264 				return;
2265 			}
2266 
2267 			if (focused && imeFocused) {
2268 				XSetICFocus(xic);
2269 			} else {
2270 				XUnsetICFocus(xic);
2271 			}
2272 		}
2273 	}
2274 
2275 	/++
2276 		Returns the native window.
2277 
2278 		History:
2279 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2280 			to access it through the `impl` member (which is semi-supported
2281 			but platform specific and here it is simple enough to offer an accessor).
2282 
2283 		Bugs:
2284 			Not implemented outside Windows or X11.
2285 	+/
2286 	NativeWindowHandle nativeWindowHandle() {
2287 		version(X11)
2288 			return impl.window;
2289 		else version(Windows)
2290 			return impl.hwnd;
2291 		else
2292 			throw new NotYetImplementedException();
2293 	}
2294 
2295 	private bool isTransient() {
2296 		with(WindowTypes)
2297 		final switch(windowType) {
2298 			case normal, undecorated, eventOnly:
2299 			case nestedChild, minimallyWrapped:
2300 				return (customizationFlags & WindowFlags.transient) ? true : false;
2301 			case dropdownMenu, popupMenu, notification:
2302 				return true;
2303 		}
2304 	}
2305 
2306 	private SimpleWindow inputProxy;
2307 
2308 	/++
2309 		Releases the grab acquired by [grabInput].
2310 	+/
2311 	void releaseInputGrab() {
2312 		static if(UsingSimpledisplayX11) {
2313 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2314 			if(_parent)
2315 				_parent.inputProxy = null;
2316 		} else version(Windows) {
2317 			ReleaseCapture();
2318 			ClipCursor(null);
2319 		} else version(OSXCocoa) {
2320 			throw new NotYetImplementedException();
2321 		} else static assert(0);
2322 	}
2323 
2324 	/++
2325 		Sets the input focus to this window.
2326 
2327 		You shouldn't call this very often - please let the user control the input focus.
2328 	+/
2329 	void focus() {
2330 		static if(UsingSimpledisplayX11) {
2331 			XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
2332 		} else version(Windows) {
2333 			SetFocus(this.impl.hwnd);
2334 		} else version(OSXCocoa) {
2335 			throw new NotYetImplementedException();
2336 		} else static assert(0);
2337 	}
2338 
2339 	/++
2340 		Requests attention from the user for this window.
2341 
2342 
2343 		The typical result of this function is to change the color
2344 		of the taskbar icon, though it may be tweaked on specific
2345 		platforms.
2346 
2347 		It is meant to unobtrusively tell the user that something
2348 		relevant to them happened in the background and they should
2349 		check the window when they get a chance. Upon receiving the
2350 		keyboard focus, the window will automatically return to its
2351 		natural state.
2352 
2353 		If the window already has the keyboard focus, this function
2354 		may do nothing, because the user is presumed to already be
2355 		giving the window attention.
2356 
2357 		Implementation_note:
2358 
2359 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2360 		atom on X11 and the FlashWindow function on Windows.
2361 	+/
2362 	void requestAttention() {
2363 		if(_focused)
2364 			return;
2365 
2366 		version(Windows) {
2367 			FLASHWINFO info;
2368 			info.cbSize = info.sizeof;
2369 			info.hwnd = impl.hwnd;
2370 			info.dwFlags = FLASHW_TRAY;
2371 			info.uCount = 1;
2372 
2373 			FlashWindowEx(&info);
2374 
2375 		} else version(X11) {
2376 			demandingAttention = true;
2377 			demandAttention(this, true);
2378 		} else version(OSXCocoa) {
2379 			throw new NotYetImplementedException();
2380 		} else static assert(0);
2381 	}
2382 
2383 	private bool _focused;
2384 
2385 	version(X11) private bool demandingAttention;
2386 
2387 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2388 	/// You'll have to call `close()` manually if you set this delegate.
2389 	void delegate () closeQuery;
2390 
2391 	/// This will be called when window visibility was changed.
2392 	void delegate (bool becomesVisible) visibilityChanged;
2393 
2394 	/// This will be called when window becomes visible for the first time.
2395 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2396 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2397 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2398 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2399 	private bool _visibleForTheFirstTimeCalled;
2400 	void delegate () visibleForTheFirstTime;
2401 
2402 	/// Returns true if the window has been closed.
2403 	final @property bool closed() { return _closed; }
2404 
2405 	private final @property bool notClosed() { return !_closed; }
2406 
2407 	/// Returns true if the window is focused.
2408 	final @property bool focused() { return _focused; }
2409 
2410 	private bool _visible;
2411 	/// Returns true if the window is visible (mapped).
2412 	final @property bool visible() { return _visible; }
2413 
2414 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2415 	void close() {
2416 		if (!_closed) {
2417 			runInGuiThread( {
2418 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2419 				if (onClosing !is null) onClosing();
2420 				impl.closeWindow();
2421 				_closed = true;
2422 			} );
2423 		}
2424 	}
2425 
2426 	/++
2427 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2428 
2429 		History:
2430 			Overload added on March 7, 2021.
2431 	+/
2432 	void close() shared {
2433 		(cast() this).close();
2434 	}
2435 
2436 	/++
2437 
2438 	+/
2439 	void maximize() {
2440 		version(Windows)
2441 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2442 		else version(X11) {
2443 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2444 
2445 			// also note _NET_WM_STATE_FULLSCREEN
2446 		}
2447 
2448 	}
2449 
2450 	private bool _fullscreen;
2451 	version(Windows)
2452 	private WINDOWPLACEMENT g_wpPrev;
2453 
2454 	/// not fully implemented but planned for a future release
2455 	void fullscreen(bool yes) {
2456 		version(Windows) {
2457 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2458 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2459 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2460 				MONITORINFO mi;
2461 				mi.cbSize = MONITORINFO.sizeof;
2462 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2463 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2464 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2465 					SetWindowLong(hwnd, GWL_STYLE,
2466 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2467 					SetWindowPos(hwnd, HWND_TOP,
2468 						     mi.rcMonitor.left, mi.rcMonitor.top,
2469 						     mi.rcMonitor.right - mi.rcMonitor.left,
2470 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2471 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2472 				}
2473 			} else {
2474 				SetWindowLong(hwnd, GWL_STYLE,
2475 					      dwStyle | WS_OVERLAPPEDWINDOW);
2476 				SetWindowPlacement(hwnd, &g_wpPrev);
2477 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2478 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2479 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2480 			}
2481 
2482 		} else version(X11) {
2483 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2484 		}
2485 
2486 		_fullscreen = yes;
2487 
2488 	}
2489 
2490 	bool fullscreen() {
2491 		return _fullscreen;
2492 	}
2493 
2494 	/++
2495 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2496 
2497 	+/
2498 	void minimize() {
2499 		version(Windows)
2500 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2501 		//else version(X11)
2502 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2503 	}
2504 
2505 	/// Alias for `hidden = false`
2506 	void show() {
2507 		hidden = false;
2508 	}
2509 
2510 	/// Alias for `hidden = true`
2511 	void hide() {
2512 		hidden = true;
2513 	}
2514 
2515 	/// Hide cursor when it enters the window.
2516 	void hideCursor() {
2517 		version(OSXCocoa) throw new NotYetImplementedException(); else
2518 		if (!_closed) impl.hideCursor();
2519 	}
2520 
2521 	/// Don't hide cursor when it enters the window.
2522 	void showCursor() {
2523 		version(OSXCocoa) throw new NotYetImplementedException(); else
2524 		if (!_closed) impl.showCursor();
2525 	}
2526 
2527 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2528 	 *
2529 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2530 	 * control. Try to think for other approaches before using this function.
2531 	 *
2532 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2533 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2534 	 *       receive "mouse moved here" event.
2535 	 */
2536 	bool warpMouse (int x, int y) {
2537 		version(X11) {
2538 			if (!_closed) { impl.warpMouse(x, y); return true; }
2539 		} else version(Windows) {
2540 			if (!_closed) {
2541 				POINT point;
2542 				point.x = x;
2543 				point.y = y;
2544 				if(ClientToScreen(impl.hwnd, &point)) {
2545 					SetCursorPos(point.x, point.y);
2546 					return true;
2547 				}
2548 			}
2549 		}
2550 		return false;
2551 	}
2552 
2553 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2554 	void sendDummyEvent () {
2555 		version(X11) {
2556 			if (!_closed) { impl.sendDummyEvent(); }
2557 		}
2558 	}
2559 
2560 	/// Set window minimal size.
2561 	void setMinSize (int minwidth, int minheight) {
2562 		version(OSXCocoa) throw new NotYetImplementedException(); else
2563 		if (!_closed) impl.setMinSize(minwidth, minheight);
2564 	}
2565 
2566 	/// Set window maximal size.
2567 	void setMaxSize (int maxwidth, int maxheight) {
2568 		version(OSXCocoa) throw new NotYetImplementedException(); else
2569 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2570 	}
2571 
2572 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2573 	/// Currently only supported on X11.
2574 	void setResizeGranularity (int granx, int grany) {
2575 		version(OSXCocoa) throw new NotYetImplementedException(); else
2576 		if (!_closed) impl.setResizeGranularity(granx, grany);
2577 	}
2578 
2579 	/// Move window.
2580 	void move(int x, int y) {
2581 		version(OSXCocoa) throw new NotYetImplementedException(); else
2582 		if (!_closed) impl.move(x, y);
2583 	}
2584 
2585 	/// ditto
2586 	void move(Point p) {
2587 		version(OSXCocoa) throw new NotYetImplementedException(); else
2588 		if (!_closed) impl.move(p.x, p.y);
2589 	}
2590 
2591 	/++
2592 		Resize window.
2593 
2594 		Note that the width and height of the window are NOT instantly
2595 		updated - it waits for the window manager to approve the resize
2596 		request, which means you must return to the event loop before the
2597 		width and height are actually changed.
2598 	+/
2599 	void resize(int w, int h) {
2600 		if(!_closed && _fullscreen) fullscreen = false;
2601 		version(OSXCocoa) throw new NotYetImplementedException(); else
2602 		if (!_closed) impl.resize(w, h);
2603 	}
2604 
2605 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2606 	void moveResize (int x, int y, int w, int h) {
2607 		if(!_closed && _fullscreen) fullscreen = false;
2608 		version(OSXCocoa) throw new NotYetImplementedException(); else
2609 		if (!_closed) impl.moveResize(x, y, w, h);
2610 	}
2611 
2612 	private bool _hidden;
2613 
2614 	/// Returns true if the window is hidden.
2615 	final @property bool hidden() {
2616 		return _hidden;
2617 	}
2618 
2619 	/// Shows or hides the window based on the bool argument.
2620 	final @property void hidden(bool b) {
2621 		_hidden = b;
2622 		version(Windows) {
2623 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2624 		} else version(X11) {
2625 			if(b)
2626 				//XUnmapWindow(impl.display, impl.window);
2627 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2628 			else
2629 				XMapWindow(impl.display, impl.window);
2630 		} else version(OSXCocoa) {
2631 			throw new NotYetImplementedException();
2632 		} else static assert(0);
2633 	}
2634 
2635 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2636 	void opacity(double opacity) @property
2637 	in {
2638 		assert(opacity >= 0 && opacity <= 1);
2639 	} do {
2640 		version (Windows) {
2641 			impl.setOpacity(cast(ubyte)(255 * opacity));
2642 		} else version (X11) {
2643 			impl.setOpacity(cast(uint)(uint.max * opacity));
2644 		} else throw new NotYetImplementedException();
2645 	}
2646 
2647 	/++
2648 		Sets your event handlers, without entering the event loop. Useful if you
2649 		have multiple windows - set the handlers on each window, then only do
2650 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2651 
2652 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2653 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2654 		delegate signatures.
2655 	+/
2656 	void setEventHandlers(T...)(T eventHandlers) {
2657 		// FIXME: add more events
2658 		foreach(handler; eventHandlers) {
2659 			static if(__traits(compiles, handleKeyEvent = handler)) {
2660 				handleKeyEvent = handler;
2661 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2662 				handleCharEvent = handler;
2663 			} else static if(__traits(compiles, handlePulse = handler)) {
2664 				handlePulse = handler;
2665 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2666 				handleMouseEvent = handler;
2667 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2668 		}
2669 	}
2670 
2671 	/++
2672 		The event loop automatically returns when the window is closed
2673 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2674 		pulse timer is created. The event loop will block until an event
2675 		arrives or the pulse timer goes off.
2676 
2677 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2678 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2679 		[handleMouseEvent], based on the signature of delegates you provide.
2680 
2681 		Give one with no parameters to set a timer pulse handler. Give one that
2682 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2683 		and one that takes `dchar` for a char event handler. You can use as many
2684 		or as few handlers as you need for your application.
2685 
2686 		History:
2687 			The overload without `pulseTimeout` was added on December 8, 2021.
2688 
2689 			On December 9, 2021, the default blocking mode (which is now configurable
2690 			because [eventLoopWithBlockingMode] was added) switched from
2691 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2692 			should almost never be noticeable to you since the typical simpledisplay
2693 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2694 
2695 		See_Also:
2696 			[eventLoopWithBlockingMode]
2697 	+/
2698 	final int eventLoop(T...)(
2699 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2700 		T eventHandlers) /// delegate list like std.concurrency.receive
2701 	{
2702 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2703 	}
2704 
2705 	/// ditto
2706 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2707 	{
2708 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2709 	}
2710 
2711 	/++
2712 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2713 
2714 		History:
2715 			Added December 8, 2021 (dub v10.5)
2716 
2717 			Previously, this implementation was right inside [eventLoop], but when I wanted
2718 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2719 			just renamed it instead of adding as an overload. Besides, the new name makes it
2720 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2721 
2722 		See_Also:
2723 			[SimpleWindow.eventLoop], [EventLoop]
2724 
2725 		Bugs:
2726 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2727 	+/
2728 	final int eventLoopWithBlockingMode(T...)(
2729 		BlockingMode blockingMode, /// when you want this function to block until
2730 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2731 		T eventHandlers) /// delegate list like std.concurrency.receive
2732 	{
2733 		setEventHandlers(eventHandlers);
2734 
2735 		version(with_eventloop) {
2736 			// delegates event loop to my other module
2737 			version(X11)
2738 				XFlush(display);
2739 
2740 			import arsd.eventloop;
2741 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2742 			scope(exit) clearInterval(handle);
2743 
2744 			loop();
2745 			return 0;
2746 		} else version(OSXCocoa) {
2747 			// FIXME
2748 			if (handlePulse !is null && pulseTimeout != 0) {
2749 				timer = scheduledTimer(pulseTimeout*1e-3,
2750 					view, sel_registerName("simpledisplay_pulse"),
2751 					null, true);
2752 			}
2753 
2754             		setNeedsDisplay(view, true);
2755             		run(NSApp);
2756             		return 0;
2757         	} else {
2758 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2759 
2760 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2761 				return 0;
2762 
2763 			return el.run(
2764 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2765 					null :
2766 					&this.notClosed
2767 			);
2768 		}
2769 	}
2770 
2771 	/++
2772 		This lets you draw on the window (or its backing buffer) using basic
2773 		2D primitives.
2774 
2775 		Be sure to call this in a limited scope because your changes will not
2776 		actually appear on the window until ScreenPainter's destructor runs.
2777 
2778 		Returns: an instance of [ScreenPainter], which has the drawing methods
2779 		on it to draw on this window.
2780 
2781 		Params:
2782 			manualInvalidations = if you set this to true, you will need to
2783 			set the invalid rectangle on the painter yourself. If false, it
2784 			assumes the whole window has been redrawn each time you draw.
2785 
2786 			Only invalidated rectangles are blitted back to the window when
2787 			the destructor runs. Doing this yourself can reduce flickering
2788 			of child windows.
2789 
2790 		History:
2791 			The `manualInvalidations` parameter overload was added on
2792 			December 30, 2021 (dub v10.5)
2793 	+/
2794 	ScreenPainter draw() {
2795 		return draw(false);
2796 	}
2797 	/// ditto
2798 	ScreenPainter draw(bool manualInvalidations) {
2799 		return impl.getPainter(manualInvalidations);
2800 	}
2801 
2802 	// This is here to implement the interface we use for various native handlers.
2803 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2804 
2805 	// maps native window handles to SimpleWindow instances, if there are any
2806 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2807 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2808 
2809 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
2810 	private int _virtualWidth;
2811 	private int _virtualHeight;
2812 
2813 	/// Width of the window's drawable client area, in pixels.
2814 	@scriptable
2815 	final @property int width() const pure nothrow @safe @nogc {
2816 		if(resizability == Resizability.automaticallyScaleIfPossible)
2817 			return _virtualWidth;
2818 		else
2819 			return _width;
2820 	}
2821 
2822 	/// Height of the window's drawable client area, in pixels.
2823 	@scriptable
2824 	final @property int height() const pure nothrow @safe @nogc {
2825 		if(resizability == Resizability.automaticallyScaleIfPossible)
2826 			return _virtualHeight;
2827 		else
2828 			return _height;
2829 	}
2830 
2831 	/++
2832 		Returns the actual size of the window, bypassing the logical
2833 		illusions of [Resizability.automaticallyScaleIfPossible].
2834 
2835 		History:
2836 			Added November 11, 2022 (dub v10.10)
2837 	+/
2838 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
2839 		return Size(_width, _height);
2840 	}
2841 
2842 
2843 	private int _width;
2844 	private int _height;
2845 
2846 	// HACK: making the best of some copy constructor woes with refcounting
2847 	private ScreenPainterImplementation* activeScreenPainter_;
2848 
2849 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2850 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2851 
2852 	private OpenGlOptions openglMode;
2853 	private Resizability resizability;
2854 	private WindowTypes windowType;
2855 	private int customizationFlags;
2856 
2857 	/// `true` if OpenGL was initialized for this window.
2858 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2859 		version(without_opengl)
2860 			return false;
2861 		else
2862 			return (openglMode == OpenGlOptions.yes);
2863 	}
2864 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2865 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2866 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2867 
2868 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2869 	/// to call this, as it's not recommended to share window between threads.
2870 	void mtLock () {
2871 		version(X11) {
2872 			XLockDisplay(this.display);
2873 		}
2874 	}
2875 
2876 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2877 	/// to call this, as it's not recommended to share window between threads.
2878 	void mtUnlock () {
2879 		version(X11) {
2880 			XUnlockDisplay(this.display);
2881 		}
2882 	}
2883 
2884 	/// Emit a beep to get user's attention.
2885 	void beep () {
2886 		version(X11) {
2887 			XBell(this.display, 100);
2888 		} else version(Windows) {
2889 			MessageBeep(0xFFFFFFFF);
2890 		}
2891 	}
2892 
2893 
2894 
2895 	version(without_opengl) {} else {
2896 
2897 		/// 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`.
2898 		void delegate() redrawOpenGlScene;
2899 
2900 		/// This will allow you to change OpenGL vsync state.
2901 		final @property void vsync (bool wait) {
2902 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2903 		  version(X11) {
2904 		    setAsCurrentOpenGlContext();
2905 		    glxSetVSync(display, impl.window, wait);
2906 		  } else version(Windows) {
2907 		    setAsCurrentOpenGlContext();
2908                     wglSetVSync(wait);
2909 		  }
2910 		}
2911 
2912 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
2913 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
2914 		/// enough without waiting 'em to finish their frame business.
2915 		bool useGLFinish = true;
2916 
2917 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
2918 		/// 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.
2919 		void redrawOpenGlSceneNow() {
2920 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
2921 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2922 			if(redrawOpenGlScene is null)
2923 				return;
2924 
2925 			this.mtLock();
2926 			scope(exit) this.mtUnlock();
2927 
2928 			this.setAsCurrentOpenGlContext();
2929 
2930 			redrawOpenGlScene();
2931 
2932 			this.swapOpenGlBuffers();
2933 			// 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.
2934 			if (useGLFinish) glFinish();
2935 		}
2936 
2937 		private bool redrawOpenGlSceneSoonSet = false;
2938 		private static class RedrawOpenGlSceneEvent {
2939 			SimpleWindow w;
2940 			this(SimpleWindow w) { this.w = w; }
2941 		}
2942 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
2943 		/++
2944 			Queues an opengl redraw as soon as the other pending events are cleared.
2945 		+/
2946 		void redrawOpenGlSceneSoon() {
2947 			if(!redrawOpenGlSceneSoonSet) {
2948 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
2949 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
2950 				redrawOpenGlSceneSoonSet = true;
2951 			}
2952 			this.postEvent(redrawOpenGlSceneEvent, true);
2953 		}
2954 
2955 
2956 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2957 		void setAsCurrentOpenGlContext() {
2958 			assert(openglMode == OpenGlOptions.yes);
2959 			version(X11) {
2960 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
2961 					throw new Exception("glXMakeCurrent");
2962 			} else version(Windows) {
2963 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2964 				if (!wglMakeCurrent(ghDC, ghRC))
2965 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
2966 			}
2967 		}
2968 
2969 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2970 		/// This doesn't throw, returning success flag instead.
2971 		bool setAsCurrentOpenGlContextNT() nothrow {
2972 			assert(openglMode == OpenGlOptions.yes);
2973 			version(X11) {
2974 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
2975 			} else version(Windows) {
2976 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2977 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
2978 			}
2979 		}
2980 
2981 		/// 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.
2982 		/// This doesn't throw, returning success flag instead.
2983 		bool releaseCurrentOpenGlContext() nothrow {
2984 			assert(openglMode == OpenGlOptions.yes);
2985 			version(X11) {
2986 				return (glXMakeCurrent(display, 0, null) != 0);
2987 			} else version(Windows) {
2988 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2989 				return wglMakeCurrent(ghDC, null) ? true : false;
2990 			}
2991 		}
2992 
2993 		/++
2994 			simpledisplay always uses double buffering, usually automatically. This
2995 			manually swaps the OpenGL buffers.
2996 
2997 
2998 			You should not need to call this yourself because simpledisplay will do it
2999 			for you after calling your `redrawOpenGlScene`.
3000 
3001 			Remember that this may throw an exception, which you can catch in a multithreaded
3002 			application to keep your thread from dying from an unhandled exception.
3003 		+/
3004 		void swapOpenGlBuffers() {
3005 			assert(openglMode == OpenGlOptions.yes);
3006 			version(X11) {
3007 				if (!this._visible) return; // no need to do this if window is invisible
3008 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3009 				glXSwapBuffers(display, impl.window);
3010 			} else version(Windows) {
3011 				SwapBuffers(ghDC);
3012 			}
3013 		}
3014 	}
3015 
3016 	/++
3017 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3018 
3019 
3020 		---
3021 			auto window = new SimpleWindow(100, 100, "First title");
3022 			window.title = "A new title";
3023 		---
3024 
3025 		You may call this function at any time.
3026 	+/
3027 	@property void title(string title) {
3028 		_title = title;
3029 		version(OSXCocoa) throw new NotYetImplementedException(); else
3030 		impl.setTitle(title);
3031 	}
3032 
3033 	private string _title;
3034 
3035 	/// Gets the title
3036 	@property string title() {
3037 		if(_title is null)
3038 			_title = getRealTitle();
3039 		return _title;
3040 	}
3041 
3042 	/++
3043 		Get the title as set by the window manager.
3044 		May not match what you attempted to set.
3045 	+/
3046 	string getRealTitle() {
3047 		static if(is(typeof(impl.getTitle())))
3048 			return impl.getTitle();
3049 		else
3050 			return null;
3051 	}
3052 
3053 	// don't use this generally it is not yet really released
3054 	version(X11)
3055 	@property Image secret_icon() {
3056 		return secret_icon_inner;
3057 	}
3058 	private Image secret_icon_inner;
3059 
3060 
3061 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3062 	@property void icon(MemoryImage icon) {
3063 		if(icon is null)
3064 			return;
3065 		auto tci = icon.getAsTrueColorImage();
3066 		version(Windows) {
3067 			winIcon = new WindowsIcon(icon);
3068 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3069 		} else version(X11) {
3070 			secret_icon_inner = Image.fromMemoryImage(icon);
3071 			// FIXME: ensure this is correct
3072 			auto display = XDisplayConnection.get;
3073 			arch_ulong[] buffer;
3074 			buffer ~= icon.width;
3075 			buffer ~= icon.height;
3076 			foreach(c; tci.imageData.colors) {
3077 				arch_ulong b;
3078 				b |= c.a << 24;
3079 				b |= c.r << 16;
3080 				b |= c.g << 8;
3081 				b |= c.b;
3082 				buffer ~= b;
3083 			}
3084 
3085 			XChangeProperty(
3086 				display,
3087 				impl.window,
3088 				GetAtom!("_NET_WM_ICON", true)(display),
3089 				GetAtom!"CARDINAL"(display),
3090 				32 /* bits */,
3091 				0 /*PropModeReplace*/,
3092 				buffer.ptr,
3093 				cast(int) buffer.length);
3094 		} else version(OSXCocoa) {
3095 			throw new NotYetImplementedException();
3096 		} else static assert(0);
3097 	}
3098 
3099 	version(Windows)
3100 		private WindowsIcon winIcon;
3101 
3102 	bool _suppressDestruction;
3103 
3104 	~this() {
3105 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3106 		if(_suppressDestruction)
3107 			return;
3108 		impl.dispose();
3109 	}
3110 
3111 	private bool _closed;
3112 
3113 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3114 	/*
3115 	ScreenPainter drawTransiently() {
3116 		return impl.getPainter();
3117 	}
3118 	*/
3119 
3120 	/// Draws an image on the window. This is meant to provide quick look
3121 	/// of a static image generated elsewhere.
3122 	@property void image(Image i) {
3123 	/+
3124 		version(Windows) {
3125 			BITMAP bm;
3126 			HDC hdc = GetDC(hwnd);
3127 			HDC hdcMem = CreateCompatibleDC(hdc);
3128 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3129 
3130 			GetObject(i.handle, bm.sizeof, &bm);
3131 
3132 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3133 
3134 			SelectObject(hdcMem, hbmOld);
3135 			DeleteDC(hdcMem);
3136 			ReleaseDC(hwnd, hdc);
3137 
3138 			/*
3139 			RECT r;
3140 			r.right = i.width;
3141 			r.bottom = i.height;
3142 			InvalidateRect(hwnd, &r, false);
3143 			*/
3144 		} else
3145 		version(X11) {
3146 			if(!destroyed) {
3147 				if(i.usingXshm)
3148 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3149 				else
3150 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3151 			}
3152 		} else
3153 		version(OSXCocoa) {
3154 			draw().drawImage(Point(0, 0), i);
3155 			setNeedsDisplay(view, true);
3156 		} else static assert(0);
3157 	+/
3158 		auto painter = this.draw;
3159 		painter.drawImage(Point(0, 0), i);
3160 	}
3161 
3162 	/++
3163 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3164 
3165 		---
3166 		window.cursor = GenericCursor.Help;
3167 		// now the window mouse cursor is set to a generic help
3168 		---
3169 
3170 	+/
3171 	@property void cursor(MouseCursor cursor) {
3172 		version(OSXCocoa)
3173 			featureNotImplemented();
3174 		else
3175 		if(this.impl.curHidden <= 0) {
3176 			static if(UsingSimpledisplayX11) {
3177 				auto ch = cursor.cursorHandle;
3178 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3179 			} else version(Windows) {
3180 				auto ch = cursor.cursorHandle;
3181 				impl.currentCursor = ch;
3182 				SetCursor(ch); // redraw without waiting for mouse movement to update
3183 			} else featureNotImplemented();
3184 		}
3185 
3186 	}
3187 
3188 	/// What follows are the event handlers. These are set automatically
3189 	/// by the eventLoop function, but are still public so you can change
3190 	/// them later. wasPressed == true means key down. false == key up.
3191 
3192 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3193 	void delegate(KeyEvent ke) handleKeyEvent;
3194 
3195 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3196 	void delegate(dchar c) handleCharEvent;
3197 
3198 	/// Handles a timer pulse. Settable through setEventHandlers.
3199 	void delegate() handlePulse;
3200 
3201 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3202 	void delegate(bool) onFocusChange;
3203 
3204 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3205 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3206 	void delegate() onClosing;
3207 
3208 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3209 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3210 	 * last minute cleanup. */
3211 	void delegate() onDestroyed;
3212 
3213 	static if (UsingSimpledisplayX11)
3214 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3215 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3216 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3217 	 *
3218 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3219 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3220 
3221 	//version(Windows)
3222 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3223 
3224 	private {
3225 		int lastMouseX = int.min;
3226 		int lastMouseY = int.min;
3227 		void mdx(ref MouseEvent ev) {
3228 			if(lastMouseX == int.min || lastMouseY == int.min) {
3229 				ev.dx = 0;
3230 				ev.dy = 0;
3231 			} else {
3232 				ev.dx = ev.x - lastMouseX;
3233 				ev.dy = ev.y - lastMouseY;
3234 			}
3235 
3236 			lastMouseX = ev.x;
3237 			lastMouseY = ev.y;
3238 		}
3239 	}
3240 
3241 	/// Mouse event handler. Settable through setEventHandlers.
3242 	void delegate(MouseEvent) handleMouseEvent;
3243 
3244 	/// use to redraw child widgets if you use system apis to add stuff
3245 	void delegate() paintingFinished;
3246 
3247 	void delegate() paintingFinishedDg() {
3248 		return paintingFinished;
3249 	}
3250 
3251 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3252 	/// for this to ever happen.
3253 	void delegate(int width, int height) windowResized;
3254 
3255 	/++
3256 		Platform specific - handle any native message this window gets.
3257 
3258 		Note: this is called *in addition to* other event handlers, unless you either:
3259 
3260 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3261 
3262 		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.
3263 
3264 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3265 
3266 		On X, it takes the form of `int delegate(XEvent)`.
3267 
3268 		History:
3269 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3270 
3271 			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.
3272 	+/
3273 	NativeEventHandler handleNativeEvent_;
3274 
3275 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3276 		return handleNativeEvent_;
3277 	}
3278 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3279 		handleNativeEvent_ = neh;
3280 	}
3281 
3282 	version(Windows)
3283 	// compatibility shim with the old deprecated way
3284 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3285 	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) {
3286 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3287 			auto ret = dg(h, m, w, l);
3288 			if(ret == 0)
3289 				r = 1;
3290 			return ret;
3291 		};
3292 	}
3293 
3294 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3295 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3296 	/// this instead and it will work the same way.
3297 	__gshared NativeEventHandler handleNativeGlobalEvent;
3298 
3299 //  private:
3300 	/// The native implementation is available, but you shouldn't use it unless you are
3301 	/// familiar with the underlying operating system, don't mind depending on it, and
3302 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3303 	/// do what you need to do with handleNativeEvent instead.
3304 	///
3305 	/// This is likely to eventually change to be just a struct holding platform-specific
3306 	/// handles instead of a template mixin at some point because I'm not happy with the
3307 	/// code duplication here (ironically).
3308 	mixin NativeSimpleWindowImplementation!() impl;
3309 
3310 	/**
3311 		This is in-process one-way (from anything to window) event sending mechanics.
3312 		It is thread-safe, so it can be used in multi-threaded applications to send,
3313 		for example, "wake up and repaint" events when thread completed some operation.
3314 		This will allow to avoid using timer pulse to check events with synchronization,
3315 		'cause event handler will be called in UI thread. You can stop guessing which
3316 		pulse frequency will be enough for your app.
3317 		Note that events handlers may be called in arbitrary order, i.e. last registered
3318 		handler can be called first, and vice versa.
3319 	*/
3320 public:
3321 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3322 	 * "spamming" window with events it can't cope with.
3323 	 * It is safe to call this from non-UI threads.
3324 	 */
3325 	@property bool eventQueueEmpty() () {
3326 		synchronized(this) {
3327 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3328 		}
3329 		return true;
3330 	}
3331 
3332 	/** Does our custom event queue contains at least one with the given type?
3333 	 * Can be used in simple cases to prevent "spamming" window with events
3334 	 * it can't cope with.
3335 	 * It is safe to call this from non-UI threads.
3336 	 */
3337 	@property bool eventQueued(ET:Object) () {
3338 		synchronized(this) {
3339 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3340 				if (!o.doProcess) {
3341 					if (cast(ET)(o.evt)) return true;
3342 				}
3343 			}
3344 		}
3345 		return false;
3346 	}
3347 
3348 	/++
3349 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3350 
3351 		History:
3352 			Added May 12, 2021
3353 	+/
3354 	void delegate(Exception e) nothrow eventUncaughtException;
3355 
3356 	/** Add listener for custom event. Can be used like this:
3357 	 *
3358 	 * ---------------------
3359 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3360 	 *   ...
3361 	 *   win.removeEventListener(eid);
3362 	 * ---------------------
3363 	 *
3364 	 * Returns: 0 on failure (should never happen, so ignore it)
3365 	 *
3366 	 * $(WARNING Don't use this method in object destructors!)
3367 	 *
3368 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3369 	 *           'cause if event handler id counter will overflow, you won't be able
3370 	 *           to register any more events.)
3371 	 */
3372 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3373 		if (dg is null) return 0; // ignore empty handlers
3374 		synchronized(this) {
3375 			//FIXME: abort on overflow?
3376 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3377 			EventHandlerEntry e;
3378 			e.dg = delegate (Object o) {
3379 				if (auto co = cast(ET)o) {
3380 					try {
3381 						dg(co);
3382 					} catch (Exception e) {
3383 						// sorry!
3384 						if(eventUncaughtException)
3385 							eventUncaughtException(e);
3386 					}
3387 					return true;
3388 				}
3389 				return false;
3390 			};
3391 			e.id = lastUsedHandlerId;
3392 			auto optr = eventHandlers.ptr;
3393 			eventHandlers ~= e;
3394 			if (eventHandlers.ptr !is optr) {
3395 				import core.memory : GC;
3396 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3397 			}
3398 			return lastUsedHandlerId;
3399 		}
3400 	}
3401 
3402 	/// Remove event listener. It is safe to pass invalid event id here.
3403 	/// $(WARNING Don't use this method in object destructors!)
3404 	void removeEventListener() (uint id) {
3405 		if (id == 0 || id > lastUsedHandlerId) return;
3406 		synchronized(this) {
3407 			foreach (immutable idx; 0..eventHandlers.length) {
3408 				if (eventHandlers[idx].id == id) {
3409 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3410 					eventHandlers[$-1].dg = null;
3411 					eventHandlers.length -= 1;
3412 					eventHandlers.assumeSafeAppend;
3413 					return;
3414 				}
3415 			}
3416 		}
3417 	}
3418 
3419 	/// Post event to queue. It is safe to call this from non-UI threads.
3420 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3421 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3422 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3423 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3424 		if (this.closed) return false; // closed windows can't handle events
3425 
3426 		// remove all events of type `ET`
3427 		void removeAllET () {
3428 			uint eidx = 0, ec = eventQueueUsed;
3429 			auto eptr = eventQueue.ptr;
3430 			while (eidx < ec) {
3431 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3432 				if (cast(ET)eptr.evt !is null) {
3433 					// i found her!
3434 					if (inCustomEventProcessor) {
3435 						// if we're in custom event processing loop, processor will clear it for us
3436 						eptr.evt = null;
3437 						++eidx;
3438 						++eptr;
3439 					} else {
3440 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3441 						ec = --eventQueueUsed;
3442 						// clear last event (it is already copied)
3443 						eventQueue.ptr[ec].evt = null;
3444 					}
3445 				} else {
3446 					++eidx;
3447 					++eptr;
3448 				}
3449 			}
3450 		}
3451 
3452 		if (evt is null) {
3453 			if (replace) { synchronized(this) removeAllET(); }
3454 			// ignore empty events, they can't be handled anyway
3455 			return false;
3456 		}
3457 
3458 		// add events even if no event FD/event object created yet
3459 		synchronized(this) {
3460 			if (replace) removeAllET();
3461 			if (eventQueueUsed == uint.max) return false; // just in case
3462 			if (eventQueueUsed < eventQueue.length) {
3463 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3464 			} else {
3465 				if (eventQueue.capacity == eventQueue.length) {
3466 					// need to reallocate; do a trick to ensure that old array is cleared
3467 					auto oarr = eventQueue;
3468 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3469 					// just in case, do yet another check
3470 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3471 					import core.memory : GC;
3472 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3473 				} else {
3474 					auto optr = eventQueue.ptr;
3475 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3476 					assert(eventQueue.ptr is optr);
3477 				}
3478 				++eventQueueUsed;
3479 				assert(eventQueueUsed == eventQueue.length);
3480 			}
3481 			if (!eventWakeUp()) {
3482 				// can't wake up event processor, so there is no reason to keep the event
3483 				assert(eventQueueUsed > 0);
3484 				eventQueue[--eventQueueUsed].evt = null;
3485 				return false;
3486 			}
3487 			return true;
3488 		}
3489 	}
3490 
3491 	/// Post event to queue. It is safe to call this from non-UI threads.
3492 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3493 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3494 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3495 		return postTimeout!ET(evt, 0, replace);
3496 	}
3497 
3498 private:
3499 	private import core.time : MonoTime;
3500 
3501 	version(Posix) {
3502 		__gshared int customEventFDRead = -1;
3503 		__gshared int customEventFDWrite = -1;
3504 		__gshared int customSignalFD = -1;
3505 	} else version(Windows) {
3506 		__gshared HANDLE customEventH = null;
3507 	}
3508 
3509 	// wake up event processor
3510 	static bool eventWakeUp () {
3511 		version(X11) {
3512 			import core.sys.posix.unistd : write;
3513 			ulong n = 1;
3514 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3515 			return true;
3516 		} else version(Windows) {
3517 			if (customEventH !is null) SetEvent(customEventH);
3518 			return true;
3519 		} else {
3520 			// not implemented for other OSes
3521 			return false;
3522 		}
3523 	}
3524 
3525 	static struct QueuedEvent {
3526 		Object evt;
3527 		bool timed = false;
3528 		MonoTime hittime = MonoTime.zero;
3529 		bool doProcess = false; // process event at the current iteration (internal flag)
3530 
3531 		this (Object aevt, uint toutmsecs) {
3532 			evt = aevt;
3533 			if (toutmsecs > 0) {
3534 				import core.time : msecs;
3535 				timed = true;
3536 				hittime = MonoTime.currTime+toutmsecs.msecs;
3537 			}
3538 		}
3539 	}
3540 
3541 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3542 	static struct EventHandlerEntry {
3543 		CustomEventHandler dg;
3544 		uint id;
3545 	}
3546 
3547 	uint lastUsedHandlerId;
3548 	EventHandlerEntry[] eventHandlers;
3549 	QueuedEvent[] eventQueue = null;
3550 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3551 	bool inCustomEventProcessor = false; // required to properly remove events
3552 
3553 	// process queued events and call custom event handlers
3554 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3555 	void processCustomEvents () {
3556 		bool hasSomethingToDo = false;
3557 		uint ecount;
3558 		bool ocep;
3559 		synchronized(this) {
3560 			ocep = inCustomEventProcessor;
3561 			inCustomEventProcessor = true;
3562 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3563 			auto ctt = MonoTime.currTime;
3564 			bool hasEmpty = false;
3565 			// mark events to process (this is required for `eventQueued()`)
3566 			foreach (ref qe; eventQueue[0..ecount]) {
3567 				if (qe.evt is null) { hasEmpty = true; continue; }
3568 				if (qe.timed) {
3569 					qe.doProcess = (qe.hittime <= ctt);
3570 				} else {
3571 					qe.doProcess = true;
3572 				}
3573 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3574 			}
3575 			if (!hasSomethingToDo) {
3576 				// remove empty events
3577 				if (hasEmpty) {
3578 					uint eidx = 0, ec = eventQueueUsed;
3579 					auto eptr = eventQueue.ptr;
3580 					while (eidx < ec) {
3581 						if (eptr.evt is null) {
3582 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3583 							ec = --eventQueueUsed;
3584 							eventQueue.ptr[ec].evt = null; // make GC life easier
3585 						} else {
3586 							++eidx;
3587 							++eptr;
3588 						}
3589 					}
3590 				}
3591 				inCustomEventProcessor = ocep;
3592 				return;
3593 			}
3594 		}
3595 		// process marked events
3596 		uint efree = 0; // non-processed events will be put at this index
3597 		EventHandlerEntry[] eh;
3598 		Object evt;
3599 		foreach (immutable eidx; 0..ecount) {
3600 			synchronized(this) {
3601 				if (!eventQueue[eidx].doProcess) {
3602 					// skip this event
3603 					assert(efree <= eidx);
3604 					if (efree != eidx) {
3605 						// copy this event to queue start
3606 						eventQueue[efree] = eventQueue[eidx];
3607 						eventQueue[eidx].evt = null; // just in case
3608 					}
3609 					++efree;
3610 					continue;
3611 				}
3612 				evt = eventQueue[eidx].evt;
3613 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3614 				if (evt is null) continue; // just in case
3615 				// try all handlers; this can be slow, but meh...
3616 				eh = eventHandlers;
3617 			}
3618 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3619 			evt = null;
3620 			eh = null;
3621 		}
3622 		synchronized(this) {
3623 			// move all unprocessed events to queue top; efree holds first "free index"
3624 			foreach (immutable eidx; ecount..eventQueueUsed) {
3625 				assert(efree <= eidx);
3626 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3627 				++efree;
3628 			}
3629 			eventQueueUsed = efree;
3630 			// wake up event processor on next event loop iteration if we have more queued events
3631 			// also, remove empty events
3632 			bool awaken = false;
3633 			uint eidx = 0, ec = eventQueueUsed;
3634 			auto eptr = eventQueue.ptr;
3635 			while (eidx < ec) {
3636 				if (eptr.evt is null) {
3637 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3638 					ec = --eventQueueUsed;
3639 					eventQueue.ptr[ec].evt = null; // make GC life easier
3640 				} else {
3641 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3642 					++eidx;
3643 					++eptr;
3644 				}
3645 			}
3646 			inCustomEventProcessor = ocep;
3647 		}
3648 	}
3649 
3650 	// for all windows in nativeMapping
3651 	package static void processAllCustomEvents () {
3652 
3653 		cleanupQueue.process();
3654 
3655 		justCommunication.processCustomEvents();
3656 
3657 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3658 			if (sw is null || sw.closed) continue;
3659 			sw.processCustomEvents();
3660 		}
3661 
3662 		runPendingRunInGuiThreadDelegates();
3663 	}
3664 
3665 	// 0: infinite (i.e. no scheduled events in queue)
3666 	uint eventQueueTimeoutMSecs () {
3667 		synchronized(this) {
3668 			if (eventQueueUsed == 0) return 0;
3669 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3670 			uint res = int.max;
3671 			auto ctt = MonoTime.currTime;
3672 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3673 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3674 				if (qe.doProcess) continue; // just in case
3675 				if (!qe.timed) return 1; // minimal
3676 				if (qe.hittime <= ctt) return 1; // minimal
3677 				auto tms = (qe.hittime-ctt).total!"msecs";
3678 				if (tms < 1) tms = 1; // safety net
3679 				if (tms >= int.max) tms = int.max-1; // and another safety net
3680 				if (res > tms) res = cast(uint)tms;
3681 			}
3682 			return (res >= int.max ? 0 : res);
3683 		}
3684 	}
3685 
3686 	// for all windows in nativeMapping
3687 	static uint eventAllQueueTimeoutMSecs () {
3688 		uint res = uint.max;
3689 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3690 			if (sw is null || sw.closed) continue;
3691 			uint to = sw.eventQueueTimeoutMSecs();
3692 			if (to && to < res) {
3693 				res = to;
3694 				if (to == 1) break; // can't have less than this
3695 			}
3696 		}
3697 		return (res >= int.max ? 0 : res);
3698 	}
3699 
3700 	version(X11) {
3701 		ResizeEvent pendingResizeEvent;
3702 	}
3703 
3704 	/++
3705 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3706 
3707 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3708 		worth so you can disable it by setting this to `true`.
3709 
3710 		History:
3711 			Added November 13, 2022.
3712 	+/
3713 	public bool suppressAutoOpenglViewport = false;
3714 	private void updateOpenglViewportIfNeeded(int width, int height) {
3715 		if(suppressAutoOpenglViewport) return;
3716 
3717 		version(without_opengl) {} else
3718 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3719 			setAsCurrentOpenGlContextNT();
3720 			glViewport(0, 0, width, height);
3721 		}
3722 	}
3723 }
3724 
3725 /++
3726 	Magic pseudo-window for just posting events to a global queue.
3727 
3728 	Not entirely supported, I might delete it at any time.
3729 
3730 	Added Nov 5, 2021.
3731 +/
3732 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init);
3733 
3734 /* Drag and drop support { */
3735 version(X11) {
3736 
3737 } else version(Windows) {
3738 	import core.sys.windows.uuid;
3739 	import core.sys.windows.ole2;
3740 	import core.sys.windows.oleidl;
3741 	import core.sys.windows.objidl;
3742 	import core.sys.windows.wtypes;
3743 
3744 	pragma(lib, "ole32");
3745 	void initDnd() {
3746 		auto err = OleInitialize(null);
3747 		if(err != S_OK && err != S_FALSE)
3748 			throw new Exception("init");//err);
3749 	}
3750 }
3751 /* } End drag and drop support */
3752 
3753 
3754 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3755 /// See [GenericCursor].
3756 class MouseCursor {
3757 	int osId;
3758 	bool isStockCursor;
3759 	private this(int osId) {
3760 		this.osId = osId;
3761 		this.isStockCursor = true;
3762 	}
3763 
3764 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3765 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3766 
3767 	version(Windows) {
3768 		HCURSOR cursor_;
3769 		HCURSOR cursorHandle() {
3770 			if(cursor_ is null)
3771 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3772 			return cursor_;
3773 		}
3774 
3775 	} else static if(UsingSimpledisplayX11) {
3776 		Cursor cursor_ = None;
3777 		int xDisplaySequence;
3778 
3779 		Cursor cursorHandle() {
3780 			if(this.osId == None)
3781 				return None;
3782 
3783 			// we need to reload if we on a new X connection
3784 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3785 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3786 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3787 			}
3788 			return cursor_;
3789 		}
3790 	}
3791 }
3792 
3793 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3794 // https://tronche.com/gui/x/xlib/appendix/b/
3795 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3796 /// 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.
3797 enum GenericCursorType {
3798 	Default, /// The default arrow pointer.
3799 	Wait, /// A cursor indicating something is loading and the user must wait.
3800 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3801 	Help, /// A cursor indicating the user can get help about the pointer location.
3802 	Cross, /// A crosshair.
3803 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3804 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3805 	UpArrow, /// An arrow pointing straight up.
3806 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3807 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3808 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3809 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3810 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3811 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3812 
3813 }
3814 
3815 /*
3816 	X_plus == css cell == Windows ?
3817 */
3818 
3819 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3820 static struct GenericCursor {
3821 	static:
3822 	///
3823 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3824 		static MouseCursor mc;
3825 
3826 		auto type = __traits(getMember, GenericCursorType, str);
3827 
3828 		if(mc is null) {
3829 
3830 			version(Windows) {
3831 				int osId;
3832 				final switch(type) {
3833 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3834 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3835 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3836 					case GenericCursorType.Help: osId = IDC_HELP; break;
3837 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3838 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3839 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3840 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3841 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3842 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3843 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3844 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3845 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3846 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3847 				}
3848 			} else static if(UsingSimpledisplayX11) {
3849 				int osId;
3850 				final switch(type) {
3851 					case GenericCursorType.Default: osId = None; break;
3852 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3853 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3854 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3855 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3856 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3857 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3858 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3859 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3860 
3861 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3862 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3863 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3864 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3865 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3866 				}
3867 
3868 			} else featureNotImplemented();
3869 
3870 			mc = new MouseCursor(osId);
3871 		}
3872 		return mc;
3873 	}
3874 }
3875 
3876 
3877 /++
3878 	If you want to get more control over the event loop, you can use this.
3879 
3880 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
3881 	to `EventLoop.get.run`.
3882 +/
3883 struct EventLoop {
3884 	@disable this();
3885 
3886 	/// Gets a reference to an existing event loop
3887 	static EventLoop get() {
3888 		return EventLoop(0, null);
3889 	}
3890 
3891 	static void quitApplication() {
3892 		EventLoop.get().exit();
3893 	}
3894 
3895 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3896 
3897 	/// Construct an application-global event loop for yourself
3898 	/// See_Also: [SimpleWindow.setEventHandlers]
3899 	this(long pulseTimeout, void delegate() handlePulse) {
3900 		synchronized(monitor) {
3901 			if(impl is null) {
3902 				claimGuiThread();
3903 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3904 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
3905 			} else {
3906 				if(pulseTimeout) {
3907 					impl.pulseTimeout = pulseTimeout;
3908 					impl.handlePulse = handlePulse;
3909 				}
3910 			}
3911 			impl.refcount++;
3912 		}
3913 	}
3914 
3915 	~this() {
3916 		if(impl is null)
3917 			return;
3918 		impl.refcount--;
3919 		if(impl.refcount == 0) {
3920 			impl.dispose();
3921 			if(thisIsGuiThread)
3922 				guiThreadFinalize();
3923 		}
3924 
3925 	}
3926 
3927 	this(this) {
3928 		if(impl is null)
3929 			return;
3930 		impl.refcount++;
3931 	}
3932 
3933 	/// Runs the event loop until the whileCondition, if present, returns false
3934 	int run(bool delegate() whileCondition = null) {
3935 		assert(impl !is null);
3936 		impl.notExited = true;
3937 		return impl.run(whileCondition);
3938 	}
3939 
3940 	/// Exits the event loop
3941 	void exit() {
3942 		assert(impl !is null);
3943 		impl.notExited = false;
3944 	}
3945 
3946 	version(linux)
3947 	ref void delegate(int) signalHandler() {
3948 		assert(impl !is null);
3949 		return impl.signalHandler;
3950 	}
3951 
3952 	__gshared static EventLoopImpl* impl;
3953 }
3954 
3955 version(linux)
3956 	void delegate(int, int) globalHupHandler;
3957 
3958 version(Posix)
3959 	void makeNonBlocking(int fd) {
3960 		import fcntl = core.sys.posix.fcntl;
3961 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
3962 		if(flags == -1)
3963 			throw new Exception("fcntl get");
3964 		flags |= fcntl.O_NONBLOCK;
3965 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
3966 		if(s == -1)
3967 			throw new Exception("fcntl set");
3968 	}
3969 
3970 struct EventLoopImpl {
3971 	int refcount;
3972 
3973 	bool notExited = true;
3974 
3975 	version(linux) {
3976 		static import ep = core.sys.linux.epoll;
3977 		static import unix = core.sys.posix.unistd;
3978 		static import err = core.stdc.errno;
3979 		import core.sys.linux.timerfd;
3980 
3981 		void delegate(int) signalHandler;
3982 	}
3983 
3984 	version(X11) {
3985 		int pulseFd = -1;
3986 		version(linux) ep.epoll_event[16] events = void;
3987 	} else version(Windows) {
3988 		Timer pulser;
3989 		HANDLE[] handles;
3990 	}
3991 
3992 
3993 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3994 	/// to call this, as it's not recommended to share window between threads.
3995 	void mtLock () {
3996 		version(X11) {
3997 			XLockDisplay(this.display);
3998 		}
3999 	}
4000 
4001 	version(X11)
4002 	auto display() { return XDisplayConnection.get; }
4003 
4004 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4005 	/// to call this, as it's not recommended to share window between threads.
4006 	void mtUnlock () {
4007 		version(X11) {
4008 			XUnlockDisplay(this.display);
4009 		}
4010 	}
4011 
4012 	version(with_eventloop)
4013 	void initialize(long pulseTimeout) {}
4014 	else
4015 	void initialize(long pulseTimeout) {
4016 		version(Windows) {
4017 			if(pulseTimeout && handlePulse !is null)
4018 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4019 
4020 			if (customEventH is null) {
4021 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4022 				if (customEventH !is null) {
4023 					handles ~= customEventH;
4024 				} else {
4025 					// this is something that should not be; better be safe than sorry
4026 					throw new Exception("can't create eventfd for custom event processing");
4027 				}
4028 			}
4029 
4030 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4031 		}
4032 
4033 		version(linux) {
4034 			prepareEventLoop();
4035 			{
4036 				auto display = XDisplayConnection.get;
4037 				// adding Xlib file
4038 				ep.epoll_event ev = void;
4039 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4040 				ev.events = ep.EPOLLIN;
4041 				ev.data.fd = display.fd;
4042 				//import std.conv;
4043 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4044 					throw new Exception("add x fd");// ~ to!string(epollFd));
4045 				displayFd = display.fd;
4046 			}
4047 
4048 			if(pulseTimeout && handlePulse !is null) {
4049 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4050 				if(pulseFd == -1)
4051 					throw new Exception("pulse timer create failed");
4052 
4053 				itimerspec value;
4054 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4055 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4056 
4057 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4058 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4059 
4060 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4061 					throw new Exception("couldn't make pulse timer");
4062 
4063 				ep.epoll_event ev = void;
4064 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4065 				ev.events = ep.EPOLLIN;
4066 				ev.data.fd = pulseFd;
4067 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4068 			}
4069 
4070 			// eventfd for custom events
4071 			if (customEventFDWrite == -1) {
4072 				customEventFDWrite = eventfd(0, 0);
4073 				customEventFDRead = customEventFDWrite;
4074 				if (customEventFDRead >= 0) {
4075 					ep.epoll_event ev = void;
4076 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4077 					ev.events = ep.EPOLLIN;
4078 					ev.data.fd = customEventFDRead;
4079 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4080 				} else {
4081 					// this is something that should not be; better be safe than sorry
4082 					throw new Exception("can't create eventfd for custom event processing");
4083 				}
4084 			}
4085 
4086 			if (customSignalFD == -1) {
4087 				import core.sys.linux.sys.signalfd;
4088 
4089 				sigset_t sigset;
4090 				auto err = sigemptyset(&sigset);
4091 				assert(!err);
4092 				err = sigaddset(&sigset, SIGINT);
4093 				assert(!err);
4094 				err = sigaddset(&sigset, SIGHUP);
4095 				assert(!err);
4096 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4097 				assert(!err);
4098 
4099 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4100 				assert(customSignalFD != -1);
4101 
4102 				ep.epoll_event ev = void;
4103 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4104 				ev.events = ep.EPOLLIN;
4105 				ev.data.fd = customSignalFD;
4106 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4107 			}
4108 		} else version(Posix) {
4109 			prepareEventLoop();
4110 			if (customEventFDRead == -1) {
4111 				int[2] bfr;
4112 				import core.sys.posix.unistd;
4113 				auto ret = pipe(bfr);
4114 				if(ret == -1) throw new Exception("pipe");
4115 				customEventFDRead = bfr[0];
4116 				customEventFDWrite = bfr[1];
4117 			}
4118 
4119 		}
4120 
4121 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4122 
4123 		version(linux) {
4124 			this.mtLock();
4125 			scope(exit) this.mtUnlock();
4126 			XPending(display); // no, really
4127 		}
4128 
4129 		disposed = false;
4130 	}
4131 
4132 	bool disposed = true;
4133 	version(X11)
4134 		int displayFd = -1;
4135 
4136 	version(with_eventloop)
4137 	void dispose() {}
4138 	else
4139 	void dispose() {
4140 		disposed = true;
4141 		version(X11) {
4142 			if(pulseFd != -1) {
4143 				import unix = core.sys.posix.unistd;
4144 				unix.close(pulseFd);
4145 				pulseFd = -1;
4146 			}
4147 
4148 				version(linux)
4149 				if(displayFd != -1) {
4150 					// 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
4151 					ep.epoll_event ev = void;
4152 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4153 					ev.events = ep.EPOLLIN;
4154 					ev.data.fd = displayFd;
4155 					//import std.conv;
4156 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4157 					displayFd = -1;
4158 				}
4159 
4160 		} else version(Windows) {
4161 			if(pulser !is null) {
4162 				pulser.destroy();
4163 				pulser = null;
4164 			}
4165 			if (customEventH !is null) {
4166 				CloseHandle(customEventH);
4167 				customEventH = null;
4168 			}
4169 		}
4170 	}
4171 
4172 	this(long pulseTimeout, void delegate() handlePulse) {
4173 		this.pulseTimeout = pulseTimeout;
4174 		this.handlePulse = handlePulse;
4175 		initialize(pulseTimeout);
4176 	}
4177 
4178 	private long pulseTimeout;
4179 	void delegate() handlePulse;
4180 
4181 	~this() {
4182 		dispose();
4183 	}
4184 
4185 	version(Posix)
4186 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4187 	version(Posix)
4188 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4189 	version(linux)
4190 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4191 	version(Windows)
4192 	ref auto customEventH() { return SimpleWindow.customEventH; }
4193 
4194 	version(with_eventloop) {
4195 		int loopHelper(bool delegate() whileCondition) {
4196 			// FIXME: whileCondition
4197 			import arsd.eventloop;
4198 			loop();
4199 			return 0;
4200 		}
4201 	} else
4202 	int loopHelper(bool delegate() whileCondition) {
4203 		version(X11) {
4204 			bool done = false;
4205 
4206 			XFlush(display);
4207 			insideXEventLoop = true;
4208 			scope(exit) insideXEventLoop = false;
4209 
4210 			version(linux) {
4211 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4212 					bool forceXPending = false;
4213 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4214 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4215 					{
4216 						this.mtLock();
4217 						scope(exit) this.mtUnlock();
4218 						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
4219 					}
4220 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4221 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4222 					if(nfds == -1) {
4223 						if(err.errno == err.EINTR) {
4224 							//if(forceXPending) goto xpending;
4225 							continue; // interrupted by signal, just try again
4226 						}
4227 						throw new Exception("epoll wait failure");
4228 					}
4229 
4230 					SimpleWindow.processAllCustomEvents(); // anyway
4231 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4232 					foreach(idx; 0 .. nfds) {
4233 						if(done) break;
4234 						auto fd = events[idx].data.fd;
4235 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4236 						auto flags = events[idx].events;
4237 						if(flags & ep.EPOLLIN) {
4238 							if (fd == customSignalFD) {
4239 								version(linux) {
4240 									import core.sys.linux.sys.signalfd;
4241 									import core.sys.posix.unistd : read;
4242 									signalfd_siginfo info;
4243 									read(customSignalFD, &info, info.sizeof);
4244 
4245 									auto sig = info.ssi_signo;
4246 
4247 									if(EventLoop.get.signalHandler !is null) {
4248 										EventLoop.get.signalHandler()(sig);
4249 									} else {
4250 										EventLoop.get.exit();
4251 									}
4252 								}
4253 							} else if(fd == display.fd) {
4254 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
4255 								this.mtLock();
4256 								scope(exit) this.mtUnlock();
4257 								while(!done && XPending(display)) {
4258 									done = doXNextEvent(this.display);
4259 								}
4260 								forceXPending = false;
4261 							} else if(fd == pulseFd) {
4262 								long expirationCount;
4263 								// 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...
4264 
4265 								handlePulse();
4266 
4267 								// read just to clear the buffer so poll doesn't trigger again
4268 								// BTW I read AFTER the pulse because if the pulse handler takes
4269 								// a lot of time to execute, we don't want the app to get stuck
4270 								// in a loop of timer hits without a chance to do anything else
4271 								//
4272 								// IOW handlePulse happens at most once per pulse interval.
4273 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4274 								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
4275 							} else if (fd == customEventFDRead) {
4276 								// we have some custom events; process 'em
4277 								import core.sys.posix.unistd : read;
4278 								ulong n;
4279 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4280 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4281 								//SimpleWindow.processAllCustomEvents();
4282 							} else {
4283 								// some other timer
4284 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
4285 
4286 								if(Timer* t = fd in Timer.mapping)
4287 									(*t).trigger();
4288 
4289 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4290 									(*pfr).ready(flags);
4291 
4292 								// or i might add support for other FDs too
4293 								// but for now it is just timer
4294 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4295 							}
4296 						}
4297 						if(flags & ep.EPOLLHUP) {
4298 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4299 								(*pfr).hup(flags);
4300 							if(globalHupHandler)
4301 								globalHupHandler(fd, flags);
4302 						}
4303 						/+
4304 						} else {
4305 							// not interested in OUT, we are just reading here.
4306 							//
4307 							// error or hup might also be reported
4308 							// but it shouldn't here since we are only
4309 							// using a few types of FD and Xlib will report
4310 							// if it dies.
4311 							// so instead of thoughtfully handling it, I'll
4312 							// just throw. for now at least
4313 
4314 							throw new Exception("epoll did something else");
4315 						}
4316 						+/
4317 					}
4318 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4319 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4320 					xpending:
4321 					if (!done && forceXPending) {
4322 						this.mtLock();
4323 						scope(exit) this.mtUnlock();
4324 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4325 						while(!done && XPending(display)) {
4326 							done = doXNextEvent(this.display);
4327 						}
4328 					}
4329 				}
4330 			} else {
4331 				// Generic fallback: yes to simple pulse support,
4332 				// but NO timer support!
4333 
4334 				// FIXME: we could probably support the POSIX timer_create
4335 				// signal-based option, but I'm in no rush to write it since
4336 				// I prefer the fd-based functions.
4337 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4338 
4339 					import core.sys.posix.poll;
4340 
4341 					pollfd[] pfds;
4342 					pollfd[32] pfdsBuffer;
4343 					auto len = PosixFdReader.mapping.length + 2;
4344 					// FIXME: i should just reuse the buffer
4345 					if(len < pfdsBuffer.length)
4346 						pfds = pfdsBuffer[0 .. len];
4347 					else
4348 						pfds = new pollfd[](len);
4349 
4350 					pfds[0].fd = display.fd;
4351 					pfds[0].events = POLLIN;
4352 					pfds[0].revents = 0;
4353 
4354 					int slot = 1;
4355 
4356 					if(customEventFDRead != -1) {
4357 						pfds[slot].fd = customEventFDRead;
4358 						pfds[slot].events = POLLIN;
4359 						pfds[slot].revents = 0;
4360 
4361 						slot++;
4362 					}
4363 
4364 					foreach(fd, obj; PosixFdReader.mapping) {
4365 						if(!obj.enabled) continue;
4366 						pfds[slot].fd = fd;
4367 						pfds[slot].events = POLLIN;
4368 						pfds[slot].revents = 0;
4369 
4370 						slot++;
4371 					}
4372 
4373 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4374 					if(ret == -1) throw new Exception("poll");
4375 
4376 					if(ret == 0) {
4377 						// FIXME it may not necessarily time out if events keep coming
4378 						if(handlePulse !is null)
4379 							handlePulse();
4380 					} else {
4381 						foreach(s; 0 .. slot) {
4382 							if(pfds[s].revents == 0) continue;
4383 
4384 							if(pfds[s].fd == display.fd) {
4385 								while(!done && XPending(display)) {
4386 									this.mtLock();
4387 									scope(exit) this.mtUnlock();
4388 									done = doXNextEvent(this.display);
4389 								}
4390 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4391 
4392 								import core.sys.posix.unistd : read;
4393 								ulong n;
4394 								read(customEventFDRead, &n, n.sizeof);
4395 								SimpleWindow.processAllCustomEvents();
4396 							} else {
4397 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4398 								if(pfds[s].revents & POLLNVAL) {
4399 									obj.dispose();
4400 								} else {
4401 									obj.ready(pfds[s].revents);
4402 								}
4403 							}
4404 
4405 							ret--;
4406 							if(ret == 0) break;
4407 						}
4408 					}
4409 				}
4410 			}
4411 		}
4412 
4413 		version(Windows) {
4414 			int ret = -1;
4415 			MSG message;
4416 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4417 				eventLoopRound++;
4418 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4419 				auto waitResult = MsgWaitForMultipleObjectsEx(
4420 					cast(int) handles.length, handles.ptr,
4421 					(wto == 0 ? INFINITE : wto), /* timeout */
4422 					0x04FF, /* QS_ALLINPUT */
4423 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4424 
4425 				SimpleWindow.processAllCustomEvents(); // anyway
4426 				enum WAIT_OBJECT_0 = 0;
4427 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4428 					auto h = handles[waitResult - WAIT_OBJECT_0];
4429 					if(auto e = h in WindowsHandleReader.mapping) {
4430 						(*e).ready();
4431 					}
4432 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4433 					// message ready
4434 					int count;
4435 					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
4436 						ret = GetMessage(&message, null, 0, 0);
4437 						if(ret == -1)
4438 							throw new Exception("GetMessage failed");
4439 						TranslateMessage(&message);
4440 						DispatchMessage(&message);
4441 
4442 						count++;
4443 						if(count > 10)
4444 							break; // take the opportunity to catch up on other events
4445 
4446 						if(ret == 0) { // WM_QUIT
4447 							EventLoop.quitApplication();
4448 							break;
4449 						}
4450 					}
4451 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4452 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4453 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4454 					// timeout, should never happen since we aren't using it
4455 				} else if(waitResult == 0xFFFFFFFF) {
4456 						// failed
4457 						throw new Exception("MsgWaitForMultipleObjectsEx failed");
4458 				} else {
4459 					// idk....
4460 				}
4461 			}
4462 
4463 			// return message.wParam;
4464 			return 0;
4465 		} else {
4466 			return 0;
4467 		}
4468 	}
4469 
4470 	int run(bool delegate() whileCondition = null) {
4471 		if(disposed)
4472 			initialize(this.pulseTimeout);
4473 
4474 		version(X11) {
4475 			try {
4476 				return loopHelper(whileCondition);
4477 			} catch(XDisconnectException e) {
4478 				if(e.userRequested) {
4479 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4480 						item.discardConnectionState();
4481 					XCloseDisplay(XDisplayConnection.display);
4482 				}
4483 
4484 				XDisplayConnection.display = null;
4485 
4486 				this.dispose();
4487 
4488 				throw e;
4489 			}
4490 		} else {
4491 			return loopHelper(whileCondition);
4492 		}
4493 	}
4494 }
4495 
4496 
4497 /++
4498 	Provides an icon on the system notification area (also known as the system tray).
4499 
4500 
4501 	If a notification area is not available with the NotificationIcon object is created,
4502 	it will silently succeed and simply attempt to create one when an area becomes available.
4503 
4504 
4505 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
4506 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
4507 	use the older version.
4508 +/
4509 version(OSXCocoa) {} else // NotYetImplementedException
4510 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4511 
4512 	version(X11) {
4513 		void recreateAfterDisconnect() {
4514 			stateDiscarded = false;
4515 			clippixmap = None;
4516 			throw new Exception("NOT IMPLEMENTED");
4517 		}
4518 
4519 		bool stateDiscarded;
4520 		void discardConnectionState() {
4521 			stateDiscarded = true;
4522 		}
4523 	}
4524 
4525 
4526 	version(X11) {
4527 		Image img;
4528 
4529 		NativeEventHandler getNativeEventHandler() {
4530 			return delegate int(XEvent e) {
4531 				switch(e.type) {
4532 					case EventType.Expose:
4533 					//case EventType.VisibilityNotify:
4534 						redraw();
4535 					break;
4536 					case EventType.ClientMessage:
4537 						version(sddddd) {
4538 						import std.stdio;
4539 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4540 						writeln("\t", e.xclient.format);
4541 						writeln("\t", e.xclient.data.l);
4542 						}
4543 					break;
4544 					case EventType.ButtonPress:
4545 						auto event = e.xbutton;
4546 						if (onClick !is null || onClickEx !is null) {
4547 							MouseButton mb = cast(MouseButton)0;
4548 							switch (event.button) {
4549 								case 1: mb = MouseButton.left; break; // left
4550 								case 2: mb = MouseButton.middle; break; // middle
4551 								case 3: mb = MouseButton.right; break; // right
4552 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4553 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4554 								case 6: break; // scroll left...
4555 								case 7: break; // scroll right...
4556 								case 8: mb = MouseButton.backButton; break;
4557 								case 9: mb = MouseButton.forwardButton; break;
4558 								default:
4559 							}
4560 							if (mb) {
4561 								try { onClick()(mb); } catch (Exception) {}
4562 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4563 							}
4564 						}
4565 					break;
4566 					case EventType.EnterNotify:
4567 						if (onEnter !is null) {
4568 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4569 						}
4570 						break;
4571 					case EventType.LeaveNotify:
4572 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4573 						break;
4574 					case EventType.DestroyNotify:
4575 						active = false;
4576 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4577 					break;
4578 					case EventType.ConfigureNotify:
4579 						auto event = e.xconfigure;
4580 						this.width = event.width;
4581 						this.height = event.height;
4582 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4583 						redraw();
4584 					break;
4585 					default: return 1;
4586 				}
4587 				return 1;
4588 			};
4589 		}
4590 
4591 		/* private */ void hideBalloon() {
4592 			balloon.close();
4593 			version(with_timer)
4594 				timer.destroy();
4595 			balloon = null;
4596 			version(with_timer)
4597 				timer = null;
4598 		}
4599 
4600 		void redraw() {
4601 			if (!active) return;
4602 
4603 			auto display = XDisplayConnection.get;
4604 			auto gc = DefaultGC(display, DefaultScreen(display));
4605 			XClearWindow(display, nativeHandle);
4606 
4607 			XSetClipMask(display, gc, clippixmap);
4608 
4609 			XSetForeground(display, gc,
4610 				cast(uint) 0 << 16 |
4611 				cast(uint) 0 << 8 |
4612 				cast(uint) 0);
4613 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4614 
4615 			if (img is null) {
4616 				XSetForeground(display, gc,
4617 					cast(uint) 0 << 16 |
4618 					cast(uint) 127 << 8 |
4619 					cast(uint) 0);
4620 				XFillArc(display, nativeHandle,
4621 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4622 			} else {
4623 				int dx = 0;
4624 				int dy = 0;
4625 				if(width > img.width)
4626 					dx = (width - img.width) / 2;
4627 				if(height > img.height)
4628 					dy = (height - img.height) / 2;
4629 				XSetClipOrigin(display, gc, dx, dy);
4630 
4631 				if (img.usingXshm)
4632 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
4633 				else
4634 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
4635 			}
4636 			XSetClipMask(display, gc, None);
4637 			flushGui();
4638 		}
4639 
4640 		static Window getTrayOwner() {
4641 			auto display = XDisplayConnection.get;
4642 			auto i = cast(int) DefaultScreen(display);
4643 			if(i < 10 && i >= 0) {
4644 				static Atom atom;
4645 				if(atom == None)
4646 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4647 				return XGetSelectionOwner(display, atom);
4648 			}
4649 			return None;
4650 		}
4651 
4652 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4653 			auto to = getTrayOwner();
4654 			auto display = XDisplayConnection.get;
4655 			XEvent ev;
4656 			ev.xclient.type = EventType.ClientMessage;
4657 			ev.xclient.window = to;
4658 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4659 			ev.xclient.format = 32;
4660 			ev.xclient.data.l[0] = CurrentTime;
4661 			ev.xclient.data.l[1] = message;
4662 			ev.xclient.data.l[2] = d1;
4663 			ev.xclient.data.l[3] = d2;
4664 			ev.xclient.data.l[4] = d3;
4665 
4666 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4667 		}
4668 
4669 		private static NotificationAreaIcon[] activeIcons;
4670 
4671 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4672 		private void newManager() {
4673 			close();
4674 			createXWin();
4675 
4676 			if(this.clippixmap)
4677 				XFreePixmap(XDisplayConnection.get, clippixmap);
4678 			if(this.originalMemoryImage)
4679 				this.icon = this.originalMemoryImage;
4680 			else if(this.img)
4681 				this.icon = this.img;
4682 		}
4683 
4684 		private void createXWin () {
4685 			// create window
4686 			auto display = XDisplayConnection.get;
4687 
4688 			// to check for MANAGER on root window to catch new/changed tray owners
4689 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4690 			// so if a thing does appear, we can handle it
4691 			foreach(ai; activeIcons)
4692 				if(ai is this)
4693 					goto alreadythere;
4694 			activeIcons ~= this;
4695 			alreadythere:
4696 
4697 			// and check for an existing tray
4698 			auto trayOwner = getTrayOwner();
4699 			if(trayOwner == None)
4700 				return;
4701 				//throw new Exception("No notification area found");
4702 
4703 			Visual* v = cast(Visual*) CopyFromParent;
4704 			/+
4705 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4706 			if(visualProp !is null) {
4707 				c_ulong[] info = cast(c_ulong[]) visualProp;
4708 				if(info.length == 1) {
4709 					auto vid = info[0];
4710 					int returned;
4711 					XVisualInfo t;
4712 					t.visualid = vid;
4713 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4714 					if(got !is null) {
4715 						if(returned == 1) {
4716 							v = got.visual;
4717 							import std.stdio;
4718 							writeln("using special visual ", *got);
4719 						}
4720 						XFree(got);
4721 					}
4722 				}
4723 			}
4724 			+/
4725 
4726 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
4727 			assert(nativeWindow);
4728 
4729 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4730 
4731 			nativeHandle = nativeWindow;
4732 
4733 			///+
4734 			arch_ulong[2] info;
4735 			info[0] = 0;
4736 			info[1] = 1;
4737 
4738 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4739 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4740 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4741 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4742 
4743 			XChangeProperty(
4744 				display,
4745 				nativeWindow,
4746 				GetAtom!("_XEMBED_INFO", true)(display),
4747 				GetAtom!("_XEMBED_INFO", true)(display),
4748 				32 /* bits */,
4749 				0 /*PropModeReplace*/,
4750 				info.ptr,
4751 				2);
4752 
4753 			import core.sys.posix.unistd;
4754 			arch_ulong pid = getpid();
4755 
4756 			XChangeProperty(
4757 				display,
4758 				nativeWindow,
4759 				GetAtom!("_NET_WM_PID", true)(display),
4760 				XA_CARDINAL,
4761 				32 /* bits */,
4762 				0 /*PropModeReplace*/,
4763 				&pid,
4764 				1);
4765 
4766 			updateNetWmIcon();
4767 
4768 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4769 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4770 				XClassHint klass;
4771 				XWMHints wh;
4772 				XSizeHints size;
4773 				klass.res_name = sdpyWindowClassStr;
4774 				klass.res_class = sdpyWindowClassStr;
4775 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4776 			}
4777 
4778 				// believe it or not, THIS is what xfce needed for the 9999 issue
4779 				XSizeHints sh;
4780 					c_long spr;
4781 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4782 					sh.flags |= PMaxSize | PMinSize;
4783 				// FIXME maybe nicer resizing
4784 				sh.min_width = 16;
4785 				sh.min_height = 16;
4786 				sh.max_width = 16;
4787 				sh.max_height = 16;
4788 				XSetWMNormalHints(display, nativeWindow, &sh);
4789 
4790 
4791 			//+/
4792 
4793 
4794 			XSelectInput(display, nativeWindow,
4795 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4796 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4797 
4798 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4799 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4800 			active = true;
4801 		}
4802 
4803 		void updateNetWmIcon() {
4804 			if(img is null) return;
4805 			auto display = XDisplayConnection.get;
4806 			// FIXME: ensure this is correct
4807 			arch_ulong[] buffer;
4808 			auto imgMi = img.toTrueColorImage;
4809 			buffer ~= imgMi.width;
4810 			buffer ~= imgMi.height;
4811 			foreach(c; imgMi.imageData.colors) {
4812 				arch_ulong b;
4813 				b |= c.a << 24;
4814 				b |= c.r << 16;
4815 				b |= c.g << 8;
4816 				b |= c.b;
4817 				buffer ~= b;
4818 			}
4819 
4820 			XChangeProperty(
4821 				display,
4822 				nativeHandle,
4823 				GetAtom!"_NET_WM_ICON"(display),
4824 				GetAtom!"CARDINAL"(display),
4825 				32 /* bits */,
4826 				0 /*PropModeReplace*/,
4827 				buffer.ptr,
4828 				cast(int) buffer.length);
4829 		}
4830 
4831 
4832 
4833 		private SimpleWindow balloon;
4834 		version(with_timer)
4835 		private Timer timer;
4836 
4837 		private Window nativeHandle;
4838 		private Pixmap clippixmap = None;
4839 		private int width = 16;
4840 		private int height = 16;
4841 		private bool active = false;
4842 
4843 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4844 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4845 		void delegate () onLeave; /// X11 only.
4846 
4847 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4848 
4849 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4850 		void getWindowRect (out int x, out int y, out int width, out int height) {
4851 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4852 			Window dummyw;
4853 			auto dpy = XDisplayConnection.get;
4854 			//XWindowAttributes xwa;
4855 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4856 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4857 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4858 			width = this.width;
4859 			height = this.height;
4860 		}
4861 	}
4862 
4863 	/+
4864 		What I actually want from this:
4865 
4866 		* set / change: icon, tooltip
4867 		* handle: mouse click, right click
4868 		* show: notification bubble.
4869 	+/
4870 
4871 	version(Windows) {
4872 		WindowsIcon win32Icon;
4873 		HWND hwnd;
4874 
4875 		NOTIFYICONDATAW data;
4876 
4877 		NativeEventHandler getNativeEventHandler() {
4878 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
4879 				if(msg == WM_USER) {
4880 					auto event = LOWORD(lParam);
4881 					auto iconId = HIWORD(lParam);
4882 					//auto x = GET_X_LPARAM(wParam);
4883 					//auto y = GET_Y_LPARAM(wParam);
4884 					switch(event) {
4885 						case WM_LBUTTONDOWN:
4886 							onClick()(MouseButton.left);
4887 						break;
4888 						case WM_RBUTTONDOWN:
4889 							onClick()(MouseButton.right);
4890 						break;
4891 						case WM_MBUTTONDOWN:
4892 							onClick()(MouseButton.middle);
4893 						break;
4894 						case WM_MOUSEMOVE:
4895 							// sent, we could use it.
4896 						break;
4897 						case WM_MOUSEWHEEL:
4898 							// NOT SENT
4899 						break;
4900 						//case NIN_KEYSELECT:
4901 						//case NIN_SELECT:
4902 						//break;
4903 						default: {}
4904 					}
4905 				}
4906 				return 0;
4907 			};
4908 		}
4909 
4910 		enum NIF_SHOWTIP = 0x00000080;
4911 
4912 		private static struct NOTIFYICONDATAW {
4913 			DWORD cbSize;
4914 			HWND  hWnd;
4915 			UINT  uID;
4916 			UINT  uFlags;
4917 			UINT  uCallbackMessage;
4918 			HICON hIcon;
4919 			WCHAR[128] szTip;
4920 			DWORD dwState;
4921 			DWORD dwStateMask;
4922 			WCHAR[256] szInfo;
4923 			union {
4924 				UINT uTimeout;
4925 				UINT uVersion;
4926 			}
4927 			WCHAR[64] szInfoTitle;
4928 			DWORD dwInfoFlags;
4929 			GUID  guidItem;
4930 			HICON hBalloonIcon;
4931 		}
4932 
4933 	}
4934 
4935 	/++
4936 		Note that on Windows, only left, right, and middle buttons are sent.
4937 		Mouse wheel buttons are NOT set, so don't rely on those events if your
4938 		program is meant to be used on Windows too.
4939 	+/
4940 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
4941 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
4942 		// but on X, we need an Image, so its canonical ctor is there. They should
4943 		// forward to each other though.
4944 		version(X11) {
4945 			this.name = name;
4946 			this.onClick = onClick;
4947 			createXWin();
4948 			this.icon = icon;
4949 		} else version(Windows) {
4950 			this.onClick = onClick;
4951 			this.win32Icon = new WindowsIcon(icon);
4952 
4953 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
4954 
4955 			static bool registered = false;
4956 			if(!registered) {
4957 				WNDCLASSEX wc;
4958 				wc.cbSize = wc.sizeof;
4959 				wc.hInstance = hInstance;
4960 				wc.lpfnWndProc = &WndProc;
4961 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
4962 				if(!RegisterClassExW(&wc))
4963 					throw new WindowsApiException("RegisterClass");
4964 				registered = true;
4965 			}
4966 
4967 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
4968 			if(hwnd is null)
4969 				throw new Exception("CreateWindow");
4970 
4971 			data.cbSize = data.sizeof;
4972 			data.hWnd = hwnd;
4973 			data.uID = cast(uint) cast(void*) this;
4974 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
4975 				// NIF_INFO means show balloon
4976 			data.uCallbackMessage = WM_USER;
4977 			data.hIcon = this.win32Icon.hIcon;
4978 			data.szTip = ""; // FIXME
4979 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4980 			data.dwStateMask = NIS_HIDDEN; // windows vista
4981 
4982 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
4983 
4984 
4985 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
4986 
4987 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
4988 		} else version(OSXCocoa) {
4989 			throw new NotYetImplementedException();
4990 		} else static assert(0);
4991 	}
4992 
4993 	/// ditto
4994 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
4995 		version(X11) {
4996 			this.onClick = onClick;
4997 			this.name = name;
4998 			createXWin();
4999 			this.icon = icon;
5000 		} else version(Windows) {
5001 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5002 		} else version(OSXCocoa) {
5003 			throw new NotYetImplementedException();
5004 		} else static assert(0);
5005 	}
5006 
5007 	version(X11) {
5008 		/++
5009 			X-specific extension (for now at least)
5010 		+/
5011 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5012 			this.onClickEx = onClickEx;
5013 			createXWin();
5014 			if (icon !is null) this.icon = icon;
5015 		}
5016 
5017 		/// ditto
5018 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5019 			this.onClickEx = onClickEx;
5020 			createXWin();
5021 			this.icon = icon;
5022 		}
5023 	}
5024 
5025 	private void delegate (MouseButton button) onClick_;
5026 
5027 	///
5028 	@property final void delegate(MouseButton) onClick() {
5029 		if(onClick_ is null)
5030 			onClick_ = delegate void(MouseButton) {};
5031 		return onClick_;
5032 	}
5033 
5034 	/// ditto
5035 	@property final void onClick(void delegate(MouseButton) handler) {
5036 		// I made this a property setter so we can wrap smaller arg
5037 		// delegates and just forward all to onClickEx or something.
5038 		onClick_ = handler;
5039 	}
5040 
5041 
5042 	string name_;
5043 	@property void name(string n) {
5044 		name_ = n;
5045 	}
5046 
5047 	@property string name() {
5048 		return name_;
5049 	}
5050 
5051 	private MemoryImage originalMemoryImage;
5052 
5053 	///
5054 	@property void icon(MemoryImage i) {
5055 		version(X11) {
5056 			this.originalMemoryImage = i;
5057 			if (!active) return;
5058 			if (i !is null) {
5059 				this.img = Image.fromMemoryImage(i);
5060 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5061 				//import std.stdio; writeln("using pixmap ", clippixmap);
5062 				updateNetWmIcon();
5063 				redraw();
5064 			} else {
5065 				if (this.img !is null) {
5066 					this.img = null;
5067 					redraw();
5068 				}
5069 			}
5070 		} else version(Windows) {
5071 			this.win32Icon = new WindowsIcon(i);
5072 
5073 			data.uFlags = NIF_ICON;
5074 			data.hIcon = this.win32Icon.hIcon;
5075 
5076 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5077 		} else version(OSXCocoa) {
5078 			throw new NotYetImplementedException();
5079 		} else static assert(0);
5080 	}
5081 
5082 	/// ditto
5083 	@property void icon (Image i) {
5084 		version(X11) {
5085 			if (!active) return;
5086 			if (i !is img) {
5087 				originalMemoryImage = null;
5088 				img = i;
5089 				redraw();
5090 			}
5091 		} else version(Windows) {
5092 			this.icon(i is null ? null : i.toTrueColorImage());
5093 		} else version(OSXCocoa) {
5094 			throw new NotYetImplementedException();
5095 		} else static assert(0);
5096 	}
5097 
5098 	/++
5099 		Shows a balloon notification. You can only show one balloon at a time, if you call
5100 		it twice while one is already up, the first balloon will be replaced.
5101 
5102 
5103 		The user is free to block notifications and they will automatically disappear after
5104 		a timeout period.
5105 
5106 		Params:
5107 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5108 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5109 			icon = the icon to display with the notification. If null, it uses your existing icon.
5110 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5111 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5112 	+/
5113 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5114 		bool useCustom = true;
5115 		version(libnotify) {
5116 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5117 			try {
5118 				if(!active) return;
5119 
5120 				if(libnotify is null) {
5121 					libnotify = new C_DynamicLibrary("libnotify.so");
5122 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5123 				}
5124 
5125 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5126 
5127 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5128 
5129 				if(onclick) {
5130 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5131 					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);
5132 					libnotify_action_delegates_count++;
5133 				}
5134 
5135 				// FIXME icon
5136 
5137 				// set hint image-data
5138 				// set default action for onclick
5139 
5140 				void* error;
5141 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5142 
5143 				useCustom = false;
5144 			} catch(Exception e) {
5145 
5146 			}
5147 		}
5148 
5149 		version(X11) {
5150 		if(useCustom) {
5151 			if(!active) return;
5152 			if(balloon) {
5153 				hideBalloon();
5154 			}
5155 			// I know there are two specs for this, but one is never
5156 			// implemented by any window manager I have ever seen, and
5157 			// the other is a bloated mess and too complicated for simpledisplay...
5158 			// so doing my own little window instead.
5159 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5160 
5161 			int x, y, width, height;
5162 			getWindowRect(x, y, width, height);
5163 
5164 			int bx = x - balloon.width;
5165 			int by = y - balloon.height;
5166 			if(bx < 0)
5167 				bx = x + width + balloon.width;
5168 			if(by < 0)
5169 				by = y + height;
5170 
5171 			// just in case, make sure it is actually on scren
5172 			if(bx < 0)
5173 				bx = 0;
5174 			if(by < 0)
5175 				by = 0;
5176 
5177 			balloon.move(bx, by);
5178 			auto painter = balloon.draw();
5179 			painter.fillColor = Color(220, 220, 220);
5180 			painter.outlineColor = Color.black;
5181 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5182 			auto iconWidth = icon is null ? 0 : icon.width;
5183 			if(icon)
5184 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5185 			iconWidth += 6; // margin around the icon
5186 
5187 			// draw a close button
5188 			painter.outlineColor = Color(44, 44, 44);
5189 			painter.fillColor = Color(255, 255, 255);
5190 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5191 			painter.pen = Pen(Color.black, 3);
5192 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5193 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5194 			painter.pen = Pen(Color.black, 1);
5195 			painter.fillColor = Color(220, 220, 220);
5196 
5197 			// Draw the title and message
5198 			painter.drawText(Point(4 + iconWidth, 4), title);
5199 			painter.drawLine(
5200 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5201 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5202 			);
5203 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5204 
5205 			balloon.setEventHandlers(
5206 				(MouseEvent ev) {
5207 					if(ev.type == MouseEventType.buttonPressed) {
5208 						if(ev.x > balloon.width - 16 && ev.y < 16)
5209 							hideBalloon();
5210 						else if(onclick)
5211 							onclick();
5212 					}
5213 				}
5214 			);
5215 			balloon.show();
5216 
5217 			version(with_timer)
5218 			timer = new Timer(timeout, &hideBalloon);
5219 			else {} // FIXME
5220 		}
5221 		} else version(Windows) {
5222 			enum NIF_INFO = 0x00000010;
5223 
5224 			data.uFlags = NIF_INFO;
5225 
5226 			// FIXME: go back to the last valid unicode code point
5227 			if(title.length > 40)
5228 				title = title[0 .. 40];
5229 			if(message.length > 220)
5230 				message = message[0 .. 220];
5231 
5232 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5233 			enum NIIF_LARGE_ICON  = 0x00000020;
5234 			enum NIIF_NOSOUND = 0x00000010;
5235 			enum NIIF_USER = 0x00000004;
5236 			enum NIIF_ERROR = 0x00000003;
5237 			enum NIIF_WARNING = 0x00000002;
5238 			enum NIIF_INFO = 0x00000001;
5239 			enum NIIF_NONE = 0;
5240 
5241 			WCharzBuffer t = WCharzBuffer(title);
5242 			WCharzBuffer m = WCharzBuffer(message);
5243 
5244 			t.copyInto(data.szInfoTitle);
5245 			m.copyInto(data.szInfo);
5246 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5247 
5248 			if(icon !is null) {
5249 				auto i = new WindowsIcon(icon);
5250 				data.hBalloonIcon = i.hIcon;
5251 				data.dwInfoFlags |= NIIF_USER;
5252 			}
5253 
5254 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5255 		} else version(OSXCocoa) {
5256 			throw new NotYetImplementedException();
5257 		} else static assert(0);
5258 	}
5259 
5260 	///
5261 	//version(Windows)
5262 	void show() {
5263 		version(X11) {
5264 			if(!hidden)
5265 				return;
5266 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5267 			hidden = false;
5268 		} else version(Windows) {
5269 			data.uFlags = NIF_STATE;
5270 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5271 			data.dwStateMask = NIS_HIDDEN; // windows vista
5272 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5273 		} else version(OSXCocoa) {
5274 			throw new NotYetImplementedException();
5275 		} else static assert(0);
5276 	}
5277 
5278 	version(X11)
5279 		bool hidden = false;
5280 
5281 	///
5282 	//version(Windows)
5283 	void hide() {
5284 		version(X11) {
5285 			if(hidden)
5286 				return;
5287 			hidden = true;
5288 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5289 		} else version(Windows) {
5290 			data.uFlags = NIF_STATE;
5291 			data.dwState = NIS_HIDDEN; // windows vista
5292 			data.dwStateMask = NIS_HIDDEN; // windows vista
5293 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5294 		} else version(OSXCocoa) {
5295 			throw new NotYetImplementedException();
5296 		} else static assert(0);
5297 	}
5298 
5299 	///
5300 	void close () {
5301 		version(X11) {
5302 			if (active) {
5303 				active = false; // event handler will set this too, but meh
5304 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5305 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5306 				flushGui();
5307 			}
5308 		} else version(Windows) {
5309 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5310 		} else version(OSXCocoa) {
5311 			throw new NotYetImplementedException();
5312 		} else static assert(0);
5313 	}
5314 
5315 	~this() {
5316 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5317 		version(X11)
5318 			if(clippixmap != None)
5319 				XFreePixmap(XDisplayConnection.get, clippixmap);
5320 		close();
5321 	}
5322 }
5323 
5324 version(X11)
5325 /// Call `XFreePixmap` on the return value.
5326 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5327 	char[] data = new char[](i.width * i.height / 8 + 2);
5328 	data[] = 0;
5329 
5330 	int bitOffset = 0;
5331 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5332 		ubyte v = c.a > 128 ? 1 : 0;
5333 		data[bitOffset / 8] |= v << (bitOffset%8);
5334 		bitOffset++;
5335 	}
5336 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5337 	return handle;
5338 }
5339 
5340 
5341 // basic functions to make timers
5342 /**
5343 	A timer that will trigger your function on a given interval.
5344 
5345 
5346 	You create a timer with an interval and a callback. It will continue
5347 	to fire on the interval until it is destroyed.
5348 
5349 	There are currently no one-off timers (instead, just create one and
5350 	destroy it when it is triggered) nor are there pause/resume functions -
5351 	the timer must again be destroyed and recreated if you want to pause it.
5352 
5353 	auto timer = new Timer(50, { it happened!; });
5354 	timer.destroy();
5355 
5356 	Timers can only be expected to fire when the event loop is running and only
5357 	once per iteration through the event loop.
5358 
5359 	History:
5360 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5361 		slow could lock up the event loop. It now guarantees other things will
5362 		get a chance to run between timer calls, even if that means not keeping up
5363 		with the requested interval.
5364 */
5365 version(with_timer) {
5366 class Timer {
5367 // FIXME: needs pause and unpause
5368 	// FIXME: I might add overloads for ones that take a count of
5369 	// how many elapsed since last time (on Windows, it will divide
5370 	// the ticks thing given, on Linux it is just available) and
5371 	// maybe one that takes an instance of the Timer itself too
5372 	/// Create a timer with a callback when it triggers.
5373 	this(int intervalInMilliseconds, void delegate() onPulse) {
5374 		assert(onPulse !is null);
5375 
5376 		this.intervalInMilliseconds = intervalInMilliseconds;
5377 		this.onPulse = onPulse;
5378 
5379 		version(Windows) {
5380 			/*
5381 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5382 			if(handle == 0)
5383 				throw new Exception("SetTimer fail");
5384 			*/
5385 
5386 			// thanks to Archival 998 for the WaitableTimer blocks
5387 			handle = CreateWaitableTimer(null, false, null);
5388 			long initialTime = -intervalInMilliseconds;
5389 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5390 				throw new Exception("SetWaitableTimer Failed");
5391 
5392 			mapping[handle] = this;
5393 
5394 		} else version(linux) {
5395 			static import ep = core.sys.linux.epoll;
5396 
5397 			import core.sys.linux.timerfd;
5398 
5399 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5400 			if(fd == -1)
5401 				throw new Exception("timer create failed");
5402 
5403 			mapping[fd] = this;
5404 
5405 			itimerspec value;
5406 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5407 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5408 
5409 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5410 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5411 
5412 			if(timerfd_settime(fd, 0, &value, null) == -1)
5413 				throw new Exception("couldn't make pulse timer");
5414 
5415 			version(with_eventloop) {
5416 				import arsd.eventloop;
5417 				addFileEventListeners(fd, &trigger, null, null);
5418 			} else {
5419 				prepareEventLoop();
5420 
5421 				ep.epoll_event ev = void;
5422 				ev.events = ep.EPOLLIN;
5423 				ev.data.fd = fd;
5424 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5425 			}
5426 		} else featureNotImplemented();
5427 	}
5428 
5429 	private int intervalInMilliseconds;
5430 
5431 	// just cuz I sometimes call it this.
5432 	alias dispose = destroy;
5433 
5434 	/// Stop and destroy the timer object.
5435 	void destroy() {
5436 		version(Windows) {
5437 			staticDestroy(handle);
5438 			handle = null;
5439 		} else version(linux) {
5440 			staticDestroy(fd);
5441 			fd = -1;
5442 		} else featureNotImplemented();
5443 	}
5444 
5445 	version(Windows)
5446 	static void staticDestroy(HANDLE handle) {
5447 		if(handle) {
5448 			// KillTimer(null, handle);
5449 			CancelWaitableTimer(cast(void*)handle);
5450 			mapping.remove(handle);
5451 			CloseHandle(handle);
5452 		}
5453 	}
5454 	else version(linux)
5455 	static void staticDestroy(int fd) {
5456 		if(fd != -1) {
5457 			import unix = core.sys.posix.unistd;
5458 			static import ep = core.sys.linux.epoll;
5459 
5460 			version(with_eventloop) {
5461 				import arsd.eventloop;
5462 				removeFileEventListeners(fd);
5463 			} else {
5464 				ep.epoll_event ev = void;
5465 				ev.events = ep.EPOLLIN;
5466 				ev.data.fd = fd;
5467 
5468 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5469 			}
5470 			unix.close(fd);
5471 			mapping.remove(fd);
5472 		}
5473 	}
5474 
5475 	~this() {
5476 		version(Windows) { if(handle)
5477 			cleanupQueue.queue!staticDestroy(handle);
5478 		} else version(linux) { if(fd != -1)
5479 			cleanupQueue.queue!staticDestroy(fd);
5480 		}
5481 	}
5482 
5483 
5484 	void changeTime(int intervalInMilliseconds)
5485 	{
5486 		this.intervalInMilliseconds = intervalInMilliseconds;
5487 		version(Windows)
5488 		{
5489 			if(handle)
5490 			{
5491 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5492 				long initialTime = -intervalInMilliseconds;
5493 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5494 					throw new Exception("couldn't change pulse timer");
5495 			}
5496 		}
5497 	}
5498 
5499 
5500 	private:
5501 
5502 	void delegate() onPulse;
5503 
5504 	int lastEventLoopRoundTriggered;
5505 
5506 	void trigger() {
5507 		version(linux) {
5508 			import unix = core.sys.posix.unistd;
5509 			long val;
5510 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5511 		} else version(Windows) {
5512 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5513 				return; // never try to actually run faster than the event loop
5514 			lastEventLoopRoundTriggered = eventLoopRound;
5515 		} else featureNotImplemented();
5516 
5517 		onPulse();
5518 	}
5519 
5520 	version(Windows)
5521 	void rearm() {
5522 
5523 	}
5524 
5525 	version(Windows)
5526 		extern(Windows)
5527 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5528 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5529 			if(Timer* t = timer in mapping) {
5530 				try
5531 				(*t).trigger();
5532 				catch(Exception e) { sdpy_abort(e); assert(0); }
5533 			}
5534 		}
5535 
5536 	version(Windows) {
5537 		//UINT_PTR handle;
5538 		//static Timer[UINT_PTR] mapping;
5539 		HANDLE handle;
5540 		__gshared Timer[HANDLE] mapping;
5541 	} else version(linux) {
5542 		int fd = -1;
5543 		__gshared Timer[int] mapping;
5544 	} else static assert(0, "timer not supported");
5545 }
5546 }
5547 
5548 version(Windows)
5549 private int eventLoopRound;
5550 
5551 version(Windows)
5552 /// 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
5553 class WindowsHandleReader {
5554 	///
5555 	this(void delegate() onReady, HANDLE handle) {
5556 		this.onReady = onReady;
5557 		this.handle = handle;
5558 
5559 		mapping[handle] = this;
5560 
5561 		enable();
5562 	}
5563 
5564 	///
5565 	void enable() {
5566 		auto el = EventLoop.get().impl;
5567 		el.handles ~= handle;
5568 	}
5569 
5570 	///
5571 	void disable() {
5572 		auto el = EventLoop.get().impl;
5573 		for(int i = 0; i < el.handles.length; i++) {
5574 			if(el.handles[i] is handle) {
5575 				el.handles[i] = el.handles[$-1];
5576 				el.handles = el.handles[0 .. $-1];
5577 				return;
5578 			}
5579 		}
5580 	}
5581 
5582 	void dispose() {
5583 		disable();
5584 		if(handle)
5585 			mapping.remove(handle);
5586 		handle = null;
5587 	}
5588 
5589 	void ready() {
5590 		if(onReady)
5591 			onReady();
5592 	}
5593 
5594 	HANDLE handle;
5595 	void delegate() onReady;
5596 
5597 	__gshared WindowsHandleReader[HANDLE] mapping;
5598 }
5599 
5600 version(Posix)
5601 /// Lets you add files to the event loop for reading. Use at your own risk.
5602 class PosixFdReader {
5603 	///
5604 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5605 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5606 	}
5607 
5608 	///
5609 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5610 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5611 	}
5612 
5613 	///
5614 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5615 		this.onReady = onReady;
5616 		this.fd = fd;
5617 		this.captureWrites = captureWrites;
5618 		this.captureReads = captureReads;
5619 
5620 		mapping[fd] = this;
5621 
5622 		version(with_eventloop) {
5623 			import arsd.eventloop;
5624 			addFileEventListeners(fd, &readyel);
5625 		} else {
5626 			enable();
5627 		}
5628 	}
5629 
5630 	bool captureReads;
5631 	bool captureWrites;
5632 
5633 	version(with_eventloop) {} else
5634 	///
5635 	void enable() {
5636 		prepareEventLoop();
5637 
5638 		enabled = true;
5639 
5640 		version(linux) {
5641 			static import ep = core.sys.linux.epoll;
5642 			ep.epoll_event ev = void;
5643 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5644 			//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5645 			ev.data.fd = fd;
5646 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5647 		} else {
5648 
5649 		}
5650 	}
5651 
5652 	version(with_eventloop) {} else
5653 	///
5654 	void disable() {
5655 		prepareEventLoop();
5656 
5657 		enabled = false;
5658 
5659 		version(linux) {
5660 			static import ep = core.sys.linux.epoll;
5661 			ep.epoll_event ev = void;
5662 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5663 			//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5664 			ev.data.fd = fd;
5665 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5666 		}
5667 	}
5668 
5669 	version(with_eventloop) {} else
5670 	///
5671 	void dispose() {
5672 		if(enabled)
5673 			disable();
5674 		if(fd != -1)
5675 			mapping.remove(fd);
5676 		fd = -1;
5677 	}
5678 
5679 	void delegate(int, bool, bool) onReady;
5680 
5681 	version(with_eventloop)
5682 	void readyel() {
5683 		onReady(fd, true, true);
5684 	}
5685 
5686 	void ready(uint flags) {
5687 		version(linux) {
5688 			static import ep = core.sys.linux.epoll;
5689 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5690 		} else {
5691 			import core.sys.posix.poll;
5692 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5693 		}
5694 	}
5695 
5696 	void hup(uint flags) {
5697 		if(onHup)
5698 			onHup();
5699 	}
5700 
5701 	void delegate() onHup;
5702 
5703 	int fd = -1;
5704 	private bool enabled;
5705 	__gshared PosixFdReader[int] mapping;
5706 }
5707 
5708 // basic functions to access the clipboard
5709 /+
5710 
5711 
5712 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5713 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5714 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5715 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5716 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5717 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5718 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5719 
5720 +/
5721 
5722 /++
5723 	this does a delegate because it is actually an async call on X...
5724 	the receiver may never be called if the clipboard is empty or unavailable
5725 	gets plain text from the clipboard.
5726 +/
5727 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5728 	version(Windows) {
5729 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5730 		if(OpenClipboard(hwndOwner) == 0)
5731 			throw new Exception("OpenClipboard");
5732 		scope(exit)
5733 			CloseClipboard();
5734 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5735 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5736 
5737 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5738 				scope(exit)
5739 					GlobalUnlock(dataHandle);
5740 
5741 				// FIXME: CR/LF conversions
5742 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5743 				int len = 0;
5744 				auto d = data;
5745 				while(*d) {
5746 					d++;
5747 					len++;
5748 				}
5749 				string s;
5750 				s.reserve(len);
5751 				foreach(dchar ch; data[0 .. len]) {
5752 					s ~= ch;
5753 				}
5754 				receiver(s);
5755 			}
5756 		}
5757 	} else version(X11) {
5758 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5759 	} else version(OSXCocoa) {
5760 		throw new NotYetImplementedException();
5761 	} else static assert(0);
5762 }
5763 
5764 // FIXME: a clipboard listener might be cool btw
5765 
5766 /++
5767 	this does a delegate because it is actually an async call on X...
5768 	the receiver may never be called if the clipboard is empty or unavailable
5769 	gets image from the clipboard.
5770 
5771 	templated because it introduces an optional dependency on arsd.bmp
5772 +/
5773 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5774 	version(Windows) {
5775 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5776 		if(OpenClipboard(hwndOwner) == 0)
5777 			throw new Exception("OpenClipboard");
5778 		scope(exit)
5779 			CloseClipboard();
5780 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5781 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5782 				scope(exit)
5783 					GlobalUnlock(dataHandle);
5784 
5785 				auto len = GlobalSize(dataHandle);
5786 
5787 				import arsd.bmp;
5788 				auto img = readBmp(data[0 .. len], false);
5789 				receiver(img);
5790 			}
5791 		}
5792 	} else version(X11) {
5793 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5794 	} else version(OSXCocoa) {
5795 		throw new NotYetImplementedException();
5796 	} else static assert(0);
5797 }
5798 
5799 version(Windows)
5800 struct WCharzBuffer {
5801 	wchar[] buffer;
5802 	wchar[256] staticBuffer = void;
5803 
5804 	size_t length() {
5805 		return buffer.length;
5806 	}
5807 
5808 	wchar* ptr() {
5809 		return buffer.ptr;
5810 	}
5811 
5812 	wchar[] slice() {
5813 		return buffer;
5814 	}
5815 
5816 	void copyInto(R)(ref R r) {
5817 		static if(is(R == wchar[N], size_t N)) {
5818 			r[0 .. this.length] = slice[];
5819 			r[this.length] = 0;
5820 		} else static assert(0, "can only copy into wchar[n], not " ~ R.stringof);
5821 	}
5822 
5823 	/++
5824 		conversionFlags = [WindowsStringConversionFlags]
5825 	+/
5826 	this(in char[] data, int conversionFlags = 0) {
5827 		conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name
5828 		auto sz = sizeOfConvertedWstring(data, conversionFlags);
5829 		if(sz > staticBuffer.length)
5830 			buffer = new wchar[](sz);
5831 		else
5832 			buffer = staticBuffer[];
5833 
5834 		buffer = makeWindowsString(data, buffer, conversionFlags);
5835 	}
5836 }
5837 
5838 version(Windows)
5839 int sizeOfConvertedWstring(in char[] s, int conversionFlags) {
5840 	int size = 0;
5841 
5842 	if(conversionFlags & WindowsStringConversionFlags.convertNewLines) {
5843 		// need to convert line endings, which means the length will get bigger.
5844 
5845 		// BTW I betcha this could be faster with some simd stuff.
5846 		char last;
5847 		foreach(char ch; s) {
5848 			if(ch == 10 && last != 13)
5849 				size++; // will add a 13 before it...
5850 			size++;
5851 			last = ch;
5852 		}
5853 	} else {
5854 		// no conversion necessary, just estimate based on length
5855 		/*
5856 			I don't think there's any string with a longer length
5857 			in code units when encoded in UTF-16 than it has in UTF-8.
5858 			This will probably over allocate, but that's OK.
5859 		*/
5860 		size = cast(int) s.length;
5861 	}
5862 
5863 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate)
5864 		size++;
5865 
5866 	return size;
5867 }
5868 
5869 version(Windows)
5870 enum WindowsStringConversionFlags : int {
5871 	zeroTerminate = 1,
5872 	convertNewLines = 2,
5873 }
5874 
5875 version(Windows)
5876 class WindowsApiException : Exception {
5877 	char[256] buffer;
5878 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5879 		assert(msg.length < 100);
5880 
5881 		auto error = GetLastError();
5882 		buffer[0 .. msg.length] = msg;
5883 		buffer[msg.length] = ' ';
5884 
5885 		int pos = cast(int) msg.length + 1;
5886 
5887 		if(error == 0)
5888 			buffer[pos++] = '0';
5889 		else {
5890 
5891 			auto ec = error;
5892 			auto init = pos;
5893 			while(ec) {
5894 				buffer[pos++] = (ec % 10) + '0';
5895 				ec /= 10;
5896 			}
5897 
5898 			buffer[pos++] = ' ';
5899 
5900 			size_t size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &(buffer[pos]), cast(DWORD) buffer.length - pos, null);
5901 
5902 			pos += size;
5903 		}
5904 
5905 
5906 		super(cast(string) buffer[0 .. pos], file, line, next);
5907 	}
5908 }
5909 
5910 class ErrnoApiException : Exception {
5911 	char[256] buffer;
5912 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5913 		assert(msg.length < 100);
5914 
5915 		import core.stdc.errno;
5916 		auto error = errno;
5917 		buffer[0 .. msg.length] = msg;
5918 		buffer[msg.length] = ' ';
5919 
5920 		int pos = cast(int) msg.length + 1;
5921 
5922 		if(error == 0)
5923 			buffer[pos++] = '0';
5924 		else {
5925 			auto init = pos;
5926 			while(error) {
5927 				buffer[pos++] = (error % 10) + '0';
5928 				error /= 10;
5929 			}
5930 			for(int i = 0; i < (pos - init) / 2; i++) {
5931 				char c = buffer[i + init];
5932 				buffer[i + init] = buffer[pos - (i + init) - 1];
5933 				buffer[pos - (i + init) - 1] = c;
5934 			}
5935 		}
5936 
5937 
5938 		super(cast(string) buffer[0 .. pos], file, line, next);
5939 	}
5940 
5941 }
5942 
5943 version(Windows)
5944 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) {
5945 	if(str.length == 0)
5946 		return null;
5947 
5948 	int pos = 0;
5949 	dchar last;
5950 	foreach(dchar c; str) {
5951 		if(c <= 0xFFFF) {
5952 			if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13)
5953 				buffer[pos++] = 13;
5954 			buffer[pos++] = cast(wchar) c;
5955 		} else if(c <= 0x10FFFF) {
5956 			buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
5957 			buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
5958 		}
5959 
5960 		last = c;
5961 	}
5962 
5963 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) {
5964 		buffer[pos] = 0;
5965 	}
5966 
5967 	return buffer[0 .. pos];
5968 }
5969 
5970 version(Windows)
5971 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
5972 	if(str.length == 0)
5973 		return null;
5974 
5975 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
5976 	if(got == 0) {
5977 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5978 			throw new Exception("not enough buffer");
5979 		else
5980 			throw new Exception("conversion"); // FIXME: GetLastError
5981 	}
5982 	return buffer[0 .. got];
5983 }
5984 
5985 version(Windows)
5986 string makeUtf8StringFromWindowsString(in wchar[] str) {
5987 	char[] buffer;
5988 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
5989 	buffer.length = got;
5990 
5991 	// it is unique because we just allocated it above!
5992 	return cast(string) makeUtf8StringFromWindowsString(str, buffer);
5993 }
5994 
5995 version(Windows)
5996 string makeUtf8StringFromWindowsString(wchar* str) {
5997 	char[] buffer;
5998 	auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
5999 	buffer.length = got;
6000 
6001 	got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
6002 	if(got == 0) {
6003 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
6004 			throw new Exception("not enough buffer");
6005 		else
6006 			throw new Exception("conversion"); // FIXME: GetLastError
6007 	}
6008 	return cast(string) buffer[0 .. got];
6009 }
6010 
6011 int findIndexOfZero(in wchar[] str) {
6012 	foreach(idx, wchar ch; str)
6013 		if(ch == 0)
6014 			return cast(int) idx;
6015 	return cast(int) str.length;
6016 }
6017 int findIndexOfZero(in char[] str) {
6018 	foreach(idx, char ch; str)
6019 		if(ch == 0)
6020 			return cast(int) idx;
6021 	return cast(int) str.length;
6022 }
6023 
6024 /// Copies some text to the clipboard.
6025 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6026 	assert(clipboardOwner !is null);
6027 	version(Windows) {
6028 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6029 			throw new Exception("OpenClipboard");
6030 		scope(exit)
6031 			CloseClipboard();
6032 		EmptyClipboard();
6033 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6034 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6035 		if(handle is null) throw new Exception("GlobalAlloc");
6036 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6037 			auto slice = data[0 .. sz];
6038 			scope(failure)
6039 				GlobalUnlock(handle);
6040 
6041 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6042 
6043 			GlobalUnlock(handle);
6044 			SetClipboardData(CF_UNICODETEXT, handle);
6045 		}
6046 	} else version(X11) {
6047 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6048 	} else version(OSXCocoa) {
6049 		throw new NotYetImplementedException();
6050 	} else static assert(0);
6051 }
6052 
6053 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6054 	assert(clipboardOwner !is null);
6055 	version(Windows) {
6056 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6057 			throw new Exception("OpenClipboard");
6058 		scope(exit)
6059 			CloseClipboard();
6060 		EmptyClipboard();
6061 
6062 
6063 		import arsd.bmp;
6064 		ubyte[] mdata;
6065 		mdata.reserve(img.width * img.height);
6066 		void sink(ubyte b) {
6067 			mdata ~= b;
6068 		}
6069 		writeBmpIndirect(img, &sink, false);
6070 
6071 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6072 		if(handle is null) throw new Exception("GlobalAlloc");
6073 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6074 			auto slice = data[0 .. mdata.length];
6075 			scope(failure)
6076 				GlobalUnlock(handle);
6077 
6078 			slice[] = mdata[];
6079 
6080 			GlobalUnlock(handle);
6081 			SetClipboardData(CF_DIB, handle);
6082 		}
6083 	} else version(X11) {
6084 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6085 			mixin X11SetSelectionHandler_Basics;
6086 			private const(ubyte)[] mdata;
6087 			private const(ubyte)[] mdata_original;
6088 			this(MemoryImage img) {
6089 				import arsd.bmp;
6090 
6091 				mdata.reserve(img.width * img.height);
6092 				void sink(ubyte b) {
6093 					mdata ~= b;
6094 				}
6095 				writeBmpIndirect(img, &sink, true);
6096 
6097 				mdata_original = mdata;
6098 			}
6099 
6100 			Atom[] availableFormats() {
6101 				auto display = XDisplayConnection.get;
6102 				return [
6103 					GetAtom!"image/bmp"(display),
6104 					GetAtom!"TARGETS"(display)
6105 				];
6106 			}
6107 
6108 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6109 				if(mdata.length < data.length) {
6110 					data[0 .. mdata.length] = mdata[];
6111 					auto ret = data[0 .. mdata.length];
6112 					mdata = mdata[$..$];
6113 					return ret;
6114 				} else {
6115 					data[] = mdata[0 .. data.length];
6116 					mdata = mdata[data.length .. $];
6117 					return data[];
6118 				}
6119 			}
6120 
6121 			void done() {
6122 				mdata = mdata_original;
6123 			}
6124 		}
6125 
6126 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6127 	} else version(OSXCocoa) {
6128 		throw new NotYetImplementedException();
6129 	} else static assert(0);
6130 }
6131 
6132 
6133 version(X11) {
6134 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6135 
6136 	private Atom*[] interredAtoms; // for discardAndRecreate
6137 
6138 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6139 	/// Platform-specific for X11.
6140 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6141 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6142 		static Atom a;
6143 		if(!a) {
6144 			a = XInternAtom(display, name, !create);
6145 			interredAtoms ~= &a;
6146 		}
6147 		if(a == None)
6148 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6149 		return a;
6150 	}
6151 
6152 	/// Platform-specific for X11 - gets atom names as a string.
6153 	string getAtomName(Atom atom, Display* display) {
6154 		auto got = XGetAtomName(display, atom);
6155 		scope(exit) XFree(got);
6156 		import core.stdc.string;
6157 		string s = got[0 .. strlen(got)].idup;
6158 		return s;
6159 	}
6160 
6161 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6162 	void setPrimarySelection(SimpleWindow window, string text) {
6163 		setX11Selection!"PRIMARY"(window, text);
6164 	}
6165 
6166 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6167 	void setSecondarySelection(SimpleWindow window, string text) {
6168 		setX11Selection!"SECONDARY"(window, text);
6169 	}
6170 
6171 	interface X11SetSelectionHandler {
6172 		// should include TARGETS right now
6173 		Atom[] availableFormats();
6174 		// Return the slice of data you filled, empty slice if done.
6175 		// this is to support the incremental thing
6176 		ubyte[] getData(Atom format, return scope ubyte[] data);
6177 
6178 		void done();
6179 
6180 		void handleRequest(XEvent);
6181 
6182 		bool matchesIncr(Window, Atom);
6183 		void sendMoreIncr(XPropertyEvent*);
6184 	}
6185 
6186 	mixin template X11SetSelectionHandler_Basics() {
6187 		Window incrWindow;
6188 		Atom incrAtom;
6189 		Atom selectionAtom;
6190 		Atom formatAtom;
6191 		ubyte[] toSend;
6192 		bool matchesIncr(Window w, Atom a) {
6193 			return incrAtom && incrAtom == a && w == incrWindow;
6194 		}
6195 		void sendMoreIncr(XPropertyEvent* event) {
6196 			auto display = XDisplayConnection.get;
6197 
6198 			XChangeProperty (display,
6199 				incrWindow,
6200 				incrAtom,
6201 				formatAtom,
6202 				8 /* bits */, PropModeReplace,
6203 				toSend.ptr, cast(int) toSend.length);
6204 
6205 			if(toSend.length != 0) {
6206 				toSend = this.getData(formatAtom, toSend[]);
6207 			} else {
6208 				this.done();
6209 				incrWindow = None;
6210 				incrAtom = None;
6211 				selectionAtom = None;
6212 				formatAtom = None;
6213 				toSend = null;
6214 			}
6215 		}
6216 		void handleRequest(XEvent ev) {
6217 
6218 			auto display = XDisplayConnection.get;
6219 
6220 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6221 			XSelectionEvent selectionEvent;
6222 			selectionEvent.type = EventType.SelectionNotify;
6223 			selectionEvent.display = event.display;
6224 			selectionEvent.requestor = event.requestor;
6225 			selectionEvent.selection = event.selection;
6226 			selectionEvent.time = event.time;
6227 			selectionEvent.target = event.target;
6228 
6229 			bool supportedType() {
6230 				foreach(t; this.availableFormats())
6231 					if(t == event.target)
6232 						return true;
6233 				return false;
6234 			}
6235 
6236 			if(event.property == None) {
6237 				selectionEvent.property = event.target;
6238 
6239 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6240 				XFlush(display);
6241 			} if(event.target == GetAtom!"TARGETS"(display)) {
6242 				/* respond with the supported types */
6243 				auto tlist = this.availableFormats();
6244 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6245 				selectionEvent.property = event.property;
6246 
6247 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6248 				XFlush(display);
6249 			} else if(supportedType()) {
6250 				auto buffer = new ubyte[](1024 * 64);
6251 				auto toSend = this.getData(event.target, buffer[]);
6252 
6253 				if(toSend.length < 32 * 1024) {
6254 					// small enough to send directly...
6255 					selectionEvent.property = event.property;
6256 					XChangeProperty (display,
6257 						selectionEvent.requestor,
6258 						selectionEvent.property,
6259 						event.target,
6260 						8 /* bits */, 0 /* PropModeReplace */,
6261 						toSend.ptr, cast(int) toSend.length);
6262 
6263 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6264 					XFlush(display);
6265 				} else {
6266 					// large, let's send incrementally
6267 					arch_ulong l = toSend.length;
6268 
6269 					// if I wanted other events from this window don't want to clear that out....
6270 					XWindowAttributes xwa;
6271 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6272 
6273 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6274 
6275 					incrWindow = event.requestor;
6276 					incrAtom = event.property;
6277 					formatAtom = event.target;
6278 					selectionAtom = event.selection;
6279 					this.toSend = toSend;
6280 
6281 					selectionEvent.property = event.property;
6282 					XChangeProperty (display,
6283 						selectionEvent.requestor,
6284 						selectionEvent.property,
6285 						GetAtom!"INCR"(display),
6286 						32 /* bits */, PropModeReplace,
6287 						&l, 1);
6288 
6289 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6290 					XFlush(display);
6291 				}
6292 				//if(after)
6293 					//after();
6294 			} else {
6295 				debug(sdpy_clip) {
6296 					import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display));
6297 				}
6298 				selectionEvent.property = None; // I don't know how to handle this type...
6299 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6300 				XFlush(display);
6301 			}
6302 		}
6303 	}
6304 
6305 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6306 		mixin X11SetSelectionHandler_Basics;
6307 		private const(ubyte)[] text;
6308 		private const(ubyte)[] text_original;
6309 		this(string text) {
6310 			this.text = cast(const ubyte[]) text;
6311 			this.text_original = this.text;
6312 		}
6313 		Atom[] availableFormats() {
6314 			auto display = XDisplayConnection.get;
6315 			return [
6316 				GetAtom!"UTF8_STRING"(display),
6317 				GetAtom!"text/plain"(display),
6318 				XA_STRING,
6319 				GetAtom!"TARGETS"(display)
6320 			];
6321 		}
6322 
6323 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6324 			if(text.length < data.length) {
6325 				data[0 .. text.length] = text[];
6326 				return data[0 .. text.length];
6327 			} else {
6328 				data[] = text[0 .. data.length];
6329 				text = text[data.length .. $];
6330 				return data[];
6331 			}
6332 		}
6333 
6334 		void done() {
6335 			text = text_original;
6336 		}
6337 	}
6338 
6339 	/// 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?!)
6340 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6341 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6342 	}
6343 
6344 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6345 		assert(window !is null);
6346 
6347 		auto display = XDisplayConnection.get();
6348 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6349 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6350 		else Atom a = GetAtom!atomName(display);
6351 
6352 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6353 
6354 		window.impl.setSelectionHandlers[a] = data;
6355 	}
6356 
6357 	///
6358 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6359 		getX11Selection!"PRIMARY"(window, handler);
6360 	}
6361 
6362 	// added July 28, 2020
6363 	// undocumented as experimental tho
6364 	interface X11GetSelectionHandler {
6365 		void handleData(Atom target, in ubyte[] data);
6366 		Atom findBestFormat(Atom[] answer);
6367 
6368 		void prepareIncremental(Window, Atom);
6369 		bool matchesIncr(Window, Atom);
6370 		void handleIncrData(Atom, in ubyte[] data);
6371 	}
6372 
6373 	mixin template X11GetSelectionHandler_Basics() {
6374 		Window incrWindow;
6375 		Atom incrAtom;
6376 
6377 		void prepareIncremental(Window w, Atom a) {
6378 			incrWindow = w;
6379 			incrAtom = a;
6380 		}
6381 		bool matchesIncr(Window w, Atom a) {
6382 			return incrWindow == w && incrAtom == a;
6383 		}
6384 
6385 		Atom incrFormatAtom;
6386 		ubyte[] incrData;
6387 		void handleIncrData(Atom format, in ubyte[] data) {
6388 			incrFormatAtom = format;
6389 
6390 			if(data.length)
6391 				incrData ~= data;
6392 			else
6393 				handleData(incrFormatAtom, incrData);
6394 
6395 		}
6396 	}
6397 
6398 	///
6399 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6400 		assert(window !is null);
6401 
6402 		auto display = XDisplayConnection.get();
6403 		auto atom = GetAtom!atomName(display);
6404 
6405 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6406 			this(void delegate(in char[]) handler) {
6407 				this.handler = handler;
6408 			}
6409 
6410 			mixin X11GetSelectionHandler_Basics;
6411 
6412 			void delegate(in char[]) handler;
6413 
6414 			void handleData(Atom target, in ubyte[] data) {
6415 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6416 					handler(cast(const char[]) data);
6417 			}
6418 
6419 			Atom findBestFormat(Atom[] answer) {
6420 				Atom best = None;
6421 				foreach(option; answer) {
6422 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6423 						best = option;
6424 						break;
6425 					} else if(option == XA_STRING) {
6426 						best = option;
6427 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6428 						best = option;
6429 					}
6430 				}
6431 				return best;
6432 			}
6433 		}
6434 
6435 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6436 
6437 		auto target = GetAtom!"TARGETS"(display);
6438 
6439 		// SDD_DATA is "simpledisplay.d data"
6440 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6441 	}
6442 
6443 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6444 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6445 		assert(window !is null);
6446 
6447 		auto display = XDisplayConnection.get();
6448 		auto atom = GetAtom!atomName(display);
6449 
6450 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6451 			this(void delegate(MemoryImage) handler) {
6452 				this.handler = handler;
6453 			}
6454 
6455 			mixin X11GetSelectionHandler_Basics;
6456 
6457 			void delegate(MemoryImage) handler;
6458 
6459 			void handleData(Atom target, in ubyte[] data) {
6460 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6461 					import arsd.bmp;
6462 					handler(readBmp(data));
6463 				}
6464 			}
6465 
6466 			Atom findBestFormat(Atom[] answer) {
6467 				Atom best = None;
6468 				foreach(option; answer) {
6469 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6470 						best = option;
6471 					}
6472 				}
6473 				return best;
6474 			}
6475 
6476 		}
6477 
6478 
6479 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6480 
6481 		auto target = GetAtom!"TARGETS"(display);
6482 
6483 		// SDD_DATA is "simpledisplay.d data"
6484 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6485 	}
6486 
6487 
6488 	///
6489 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6490 		Atom actualType;
6491 		int actualFormat;
6492 		arch_ulong actualItems;
6493 		arch_ulong bytesRemaining;
6494 		void* data;
6495 
6496 		auto display = XDisplayConnection.get();
6497 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6498 			if(actualFormat == 0)
6499 				return null;
6500 			else {
6501 				int byteLength;
6502 				if(actualFormat == 32) {
6503 					// 32 means it is a C long... which is variable length
6504 					actualFormat = cast(int) arch_long.sizeof * 8;
6505 				}
6506 
6507 				// then it is just a bit count
6508 				byteLength = cast(int) (actualItems * actualFormat / 8);
6509 
6510 				auto d = new ubyte[](byteLength);
6511 				d[] = cast(ubyte[]) data[0 .. byteLength];
6512 				XFree(data);
6513 				return d;
6514 			}
6515 		}
6516 		return null;
6517 	}
6518 
6519 	/* defined in the systray spec */
6520 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6521 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6522 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6523 
6524 
6525 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6526 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6527 	public class GlobalHotkey {
6528 		KeyEvent key;
6529 		void delegate () handler;
6530 
6531 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6532 
6533 		/// Create from initialzed KeyEvent object
6534 		this (KeyEvent akey, void delegate () ahandler=null) {
6535 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6536 			key = akey;
6537 			handler = ahandler;
6538 		}
6539 
6540 		/// Create from emacs-like key name ("C-M-Y", etc.)
6541 		this (const(char)[] akey, void delegate () ahandler=null) {
6542 			key = KeyEvent.parse(akey);
6543 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6544 			handler = ahandler;
6545 		}
6546 
6547 	}
6548 
6549 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6550 		//conwriteln("failed to grab key");
6551 		GlobalHotkeyManager.ghfailed = true;
6552 		return 0;
6553 	}
6554 
6555 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6556 		Image.impl.xshmfailed = true;
6557 		return 0;
6558 	}
6559 
6560 	private __gshared int errorHappened;
6561 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6562 		import core.stdc.stdio;
6563 		char[265] buffer;
6564 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6565 		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);
6566 		errorHappened = true;
6567 		return 0;
6568 	}
6569 
6570 	/++
6571 		Global hotkey manager. It contains static methods to manage global hotkeys.
6572 
6573 		---
6574 		 try {
6575 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6576 		} catch (Exception e) {
6577 			conwriteln("ERROR registering hotkey!");
6578 		}
6579 		EventLoop.get.run();
6580 		---
6581 
6582 		The key strings are based on Emacs. In practical terms,
6583 		`M` means `alt` and `H` means the Windows logo key. `C`
6584 		is `ctrl`.
6585 
6586 		$(WARNING
6587 			This is X-specific right now. If you are on
6588 			Windows, try [registerHotKey] instead.
6589 
6590 			We will probably merge these into a single
6591 			interface later.
6592 		)
6593 	+/
6594 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6595 		version(X11) {
6596 			void recreateAfterDisconnect() {
6597 				throw new Exception("NOT IMPLEMENTED");
6598 			}
6599 			void discardConnectionState() {
6600 				throw new Exception("NOT IMPLEMENTED");
6601 			}
6602 		}
6603 
6604 		private static immutable uint[8] masklist = [ 0,
6605 			KeyOrButtonMask.LockMask,
6606 			KeyOrButtonMask.Mod2Mask,
6607 			KeyOrButtonMask.Mod3Mask,
6608 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6609 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6610 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6611 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6612 		];
6613 		private __gshared GlobalHotkeyManager ghmanager;
6614 		private __gshared bool ghfailed = false;
6615 
6616 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6617 			if (modmask == 0) return false;
6618 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6619 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6620 			return true;
6621 		}
6622 
6623 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6624 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6625 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6626 			return modmask;
6627 		}
6628 
6629 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6630 			uint keycode = cast(uint)ke.key;
6631 			auto dpy = XDisplayConnection.get;
6632 			return XKeysymToKeycode(dpy, keycode);
6633 		}
6634 
6635 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6636 
6637 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6638 
6639 		NativeEventHandler getNativeEventHandler () {
6640 			return delegate int (XEvent e) {
6641 				if (e.type != EventType.KeyPress) return 1;
6642 				auto kev = cast(const(XKeyEvent)*)&e;
6643 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6644 				if (auto ghkp = hash in globalHotkeyList) {
6645 					try {
6646 						ghkp.doHandle();
6647 					} catch (Exception e) {
6648 						import core.stdc.stdio : stderr, fprintf;
6649 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6650 					}
6651 				}
6652 				return 1;
6653 			};
6654 		}
6655 
6656 		private this () {
6657 			auto dpy = XDisplayConnection.get;
6658 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6659 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6660 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6661 		}
6662 
6663 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6664 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6665 		static void register (GlobalHotkey gh) {
6666 			if (gh is null) return;
6667 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6668 
6669 			auto dpy = XDisplayConnection.get;
6670 			immutable keycode = keyEvent2KeyCode(gh.key);
6671 
6672 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6673 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6674 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6675 			XSync(dpy, 0/*False*/);
6676 
6677 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6678 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6679 			ghfailed = false;
6680 			foreach (immutable uint ormask; masklist[]) {
6681 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6682 			}
6683 			XSync(dpy, 0/*False*/);
6684 			XSetErrorHandler(savedErrorHandler);
6685 
6686 			if (ghfailed) {
6687 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6688 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6689 				XSync(dpy, 0/*False*/);
6690 				XSetErrorHandler(savedErrorHandler);
6691 				throw new Exception("cannot register global hotkey");
6692 			}
6693 
6694 			globalHotkeyList[hash] = gh;
6695 		}
6696 
6697 		/// Ditto
6698 		static void register (const(char)[] akey, void delegate () ahandler) {
6699 			register(new GlobalHotkey(akey, ahandler));
6700 		}
6701 
6702 		private static void removeByHash (ulong hash) {
6703 			if (auto ghp = hash in globalHotkeyList) {
6704 				auto dpy = XDisplayConnection.get;
6705 				immutable keycode = keyEvent2KeyCode(ghp.key);
6706 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6707 				XSync(dpy, 0/*False*/);
6708 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6709 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6710 				XSync(dpy, 0/*False*/);
6711 				XSetErrorHandler(savedErrorHandler);
6712 				globalHotkeyList.remove(hash);
6713 			}
6714 		}
6715 
6716 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6717 		/// It is safe to unregister unknown or invalid hotkey.
6718 		static void unregister (GlobalHotkey gh) {
6719 			//TODO: add second AA for faster search? prolly doesn't worth it.
6720 			if (gh is null) return;
6721 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6722 				if (kv.value is gh) {
6723 					removeByHash(kv.key);
6724 					return;
6725 				}
6726 			}
6727 		}
6728 
6729 		/// Ditto.
6730 		static void unregister (const(char)[] key) {
6731 			auto kev = KeyEvent.parse(key);
6732 			immutable keycode = keyEvent2KeyCode(kev);
6733 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6734 		}
6735 	}
6736 }
6737 
6738 version(Windows) {
6739 	/++
6740 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6741 
6742 		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).
6743 	+/
6744 	void sendSyntheticInput(wstring s) {
6745 			INPUT[] inputs;
6746 			inputs.reserve(s.length * 2);
6747 
6748 			foreach(wchar c; s) {
6749 				INPUT input;
6750 				input.type = INPUT_KEYBOARD;
6751 				input.ki.wScan = c;
6752 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6753 				inputs ~= input;
6754 
6755 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6756 				inputs ~= input;
6757 			}
6758 
6759 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6760 				throw new Exception("SendInput failed");
6761 			}
6762 
6763 	}
6764 
6765 
6766 	// global hotkey helper function
6767 
6768 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6769 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6770 		__gshared int hotkeyId = 0;
6771 		int id = ++hotkeyId;
6772 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6773 			throw new Exception("RegisterHotKey failed");
6774 
6775 		__gshared void delegate()[WPARAM][HWND] handlers;
6776 
6777 		handlers[window.impl.hwnd][id] = handler;
6778 
6779 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6780 
6781 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6782 			switch(msg) {
6783 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6784 				case WM_HOTKEY:
6785 					if(auto list = hwnd in handlers) {
6786 						if(auto h = wParam in *list) {
6787 							(*h)();
6788 							return 0;
6789 						}
6790 					}
6791 				goto default;
6792 				default:
6793 			}
6794 			if(oldHandler)
6795 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6796 			return 1; // pass it on
6797 		};
6798 
6799 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6800 			oldHandler = window.handleNativeEvent;
6801 			window.handleNativeEvent = nativeEventHandler;
6802 		}
6803 
6804 		return id;
6805 	}
6806 
6807 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6808 	void unregisterHotKey(SimpleWindow window, int id) {
6809 		if(!UnregisterHotKey(window.impl.hwnd, id))
6810 			throw new Exception("UnregisterHotKey");
6811 	}
6812 }
6813 
6814 version (X11) {
6815 	pragma(lib, "dl");
6816 	import core.sys.posix.dlfcn;
6817 }
6818 
6819 /++
6820 	Allows for sending synthetic input to the X server via the Xtst
6821 	extension or on Windows using SendInput.
6822 
6823 	Please remember user input is meant to be user - don't use this
6824 	if you have some other alternative!
6825 
6826 	History:
6827 		Added May 17, 2020 with the X implementation.
6828 
6829 		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.)
6830 	Bugs:
6831 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6832 +/
6833 struct SyntheticInput {
6834 	@disable this();
6835 
6836 	private int* refcount;
6837 
6838 	version(X11) {
6839 		private void* lib;
6840 
6841 		private extern(C) {
6842 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6843 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6844 		}
6845 	}
6846 
6847 	/// The dummy param must be 0.
6848 	this(int dummy) {
6849 		version(X11) {
6850 			lib = dlopen("libXtst.so", RTLD_NOW);
6851 			if(lib is null)
6852 				throw new Exception("cannot load xtest lib extension");
6853 			scope(failure)
6854 				dlclose(lib);
6855 
6856 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6857 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6858 
6859 			if(XTestFakeKeyEvent is null)
6860 				throw new Exception("No XTestFakeKeyEvent");
6861 			if(XTestFakeButtonEvent is null)
6862 				throw new Exception("No XTestFakeButtonEvent");
6863 		}
6864 
6865 		refcount = new int;
6866 		*refcount = 1;
6867 	}
6868 
6869 	this(this) {
6870 		if(refcount)
6871 			*refcount += 1;
6872 	}
6873 
6874 	~this() {
6875 		if(refcount) {
6876 			*refcount -= 1;
6877 			if(*refcount == 0)
6878 				// I commented this because if I close the lib before
6879 				// XCloseDisplay, it is liable to segfault... so just
6880 				// gonna keep it loaded if it is loaded, no big deal
6881 				// anyway.
6882 				{} // dlclose(lib);
6883 		}
6884 	}
6885 
6886 	/++
6887 		Simulates typing a string into the keyboard.
6888 
6889 		Bugs:
6890 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6891 
6892 			Not implemented except on Windows and X11.
6893 	+/
6894 	void sendSyntheticInput(string s) {
6895 		version(Windows) {
6896 			INPUT[] inputs;
6897 			inputs.reserve(s.length * 2);
6898 
6899 			auto ei = GetMessageExtraInfo();
6900 
6901 			foreach(wchar c; s) {
6902 				INPUT input;
6903 				input.type = INPUT_KEYBOARD;
6904 				input.ki.wScan = c;
6905 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6906 				input.ki.dwExtraInfo = ei;
6907 				inputs ~= input;
6908 
6909 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6910 				inputs ~= input;
6911 			}
6912 
6913 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6914 				throw new Exception("SendInput failed");
6915 			}
6916 		} else version(X11) {
6917 			int delay = 0;
6918 			foreach(ch; s) {
6919 				pressKey(cast(Key) ch, true, delay);
6920 				pressKey(cast(Key) ch, false, delay);
6921 				delay += 5;
6922 			}
6923 		} else throw new NotYetImplementedException();
6924 	}
6925 
6926 	/++
6927 		Sends a fake press or release key event.
6928 
6929 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6930 
6931 		Bugs:
6932 			The `delay` parameter is not implemented yet on Windows.
6933 
6934 			Not implemented except on Windows and X11.
6935 	+/
6936 	void pressKey(Key key, bool pressed, int delay = 0) {
6937 		version(Windows) {
6938 			INPUT input;
6939 			input.type = INPUT_KEYBOARD;
6940 			input.ki.wVk = cast(ushort) key;
6941 
6942 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6943 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6944 
6945 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6946 				throw new Exception("SendInput failed");
6947 			}
6948 		} else version(X11) {
6949 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6950 		} else throw new NotYetImplementedException();
6951 	}
6952 
6953 	/++
6954 		Sends a fake mouse button press or release event.
6955 
6956 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6957 
6958 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6959 
6960 		Bugs:
6961 			The `delay` parameter is not implemented yet on Windows.
6962 
6963 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6964 
6965 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6966 	+/
6967 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6968 		version(Windows) {
6969 			INPUT input;
6970 			input.type = INPUT_MOUSE;
6971 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6972 
6973 			// input.mi.mouseData for a wheel event
6974 
6975 			switch(button) {
6976 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6977 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6978 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6979 				case MouseButton.wheelUp:
6980 				case MouseButton.wheelDown:
6981 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6982 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6983 				break;
6984 				case MouseButton.backButton: throw new NotYetImplementedException();
6985 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6986 				default:
6987 			}
6988 
6989 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6990 				throw new Exception("SendInput failed");
6991 			}
6992 		} else version(X11) {
6993 			int btn;
6994 
6995 			switch(button) {
6996 				case MouseButton.left: btn = 1; break;
6997 				case MouseButton.middle: btn = 2; break;
6998 				case MouseButton.right: btn = 3; break;
6999 				case MouseButton.wheelUp: btn = 4; break;
7000 				case MouseButton.wheelDown: btn = 5; break;
7001 				case MouseButton.backButton: btn = 8; break;
7002 				case MouseButton.forwardButton: btn = 9; break;
7003 				default:
7004 			}
7005 
7006 			assert(btn);
7007 
7008 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7009 		} else throw new NotYetImplementedException();
7010 	}
7011 
7012 	///
7013 	static void moveMouseArrowBy(int dx, int dy) {
7014 		version(Windows) {
7015 			INPUT input;
7016 			input.type = INPUT_MOUSE;
7017 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7018 			input.mi.dx = dx;
7019 			input.mi.dy = dy;
7020 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7021 
7022 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7023 				throw new Exception("SendInput failed");
7024 			}
7025 		} else version(X11) {
7026 			auto disp = XDisplayConnection.get();
7027 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7028 			XFlush(disp);
7029 		} else throw new NotYetImplementedException();
7030 	}
7031 
7032 	///
7033 	static void moveMouseArrowTo(int x, int y) {
7034 		version(Windows) {
7035 			INPUT input;
7036 			input.type = INPUT_MOUSE;
7037 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7038 			input.mi.dx = x;
7039 			input.mi.dy = y;
7040 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7041 
7042 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7043 				throw new Exception("SendInput failed");
7044 			}
7045 		} else version(X11) {
7046 			auto disp = XDisplayConnection.get();
7047 			auto root = RootWindow(disp, DefaultScreen(disp));
7048 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7049 			XFlush(disp);
7050 		} else throw new NotYetImplementedException();
7051 	}
7052 }
7053 
7054 
7055 
7056 /++
7057 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7058 
7059 	See_Also:
7060 	$(LIST
7061 		*[ScreenPainter]
7062 		*[ScreenPainter.rasterOp]
7063 	)
7064 +/
7065 enum RasterOp {
7066 	normal, /// Replaces the pixel.
7067 	xor, /// Uses bitwise xor to draw.
7068 }
7069 
7070 // being phobos-free keeps the size WAY down
7071 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7072 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7073 package(arsd) const(wchar)* toWStringz(string s) {
7074 	wstring r;
7075 	foreach(dchar c; s)
7076 		r ~= c;
7077 	r ~= '\0';
7078 	return r.ptr;
7079 }
7080 private string[] split(in void[] a, char c) {
7081 		string[] ret;
7082 		size_t previous = 0;
7083 		foreach(i, char ch; cast(ubyte[]) a) {
7084 			if(ch == c) {
7085 				ret ~= cast(string) a[previous .. i];
7086 				previous = i + 1;
7087 			}
7088 		}
7089 		if(previous != a.length)
7090 			ret ~= cast(string) a[previous .. $];
7091 		return ret;
7092 	}
7093 
7094 version(without_opengl) {
7095 	enum OpenGlOptions {
7096 		no,
7097 	}
7098 } else {
7099 	/++
7100 		Determines if you want an OpenGL context created on the new window.
7101 
7102 
7103 		See more: [#topics-3d|in the 3d topic].
7104 
7105 		---
7106 		import arsd.simpledisplay;
7107 		void main() {
7108 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7109 
7110 			// Set up the matrix
7111 			window.setAsCurrentOpenGlContext(); // make this window active
7112 
7113 			// This is called on each frame, we will draw our scene
7114 			window.redrawOpenGlScene = delegate() {
7115 
7116 			};
7117 
7118 			window.eventLoop(0);
7119 		}
7120 		---
7121 	+/
7122 	enum OpenGlOptions {
7123 		no, /// No OpenGL context is created
7124 		yes, /// Yes, create an OpenGL context
7125 	}
7126 
7127 	version(X11) {
7128 		static if (!SdpyIsUsingIVGLBinds) {
7129 
7130 
7131 			struct __GLXFBConfigRec {}
7132 			alias GLXFBConfig = __GLXFBConfigRec*;
7133 
7134 			//pragma(lib, "GL");
7135 			//pragma(lib, "GLU");
7136 			interface GLX {
7137 			extern(C) nothrow @nogc {
7138 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7139 						const int *attrib_list);
7140 
7141 				 void glXCopyContext(Display *dpy, GLXContext src,
7142 						GLXContext dst, arch_ulong mask);
7143 
7144 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7145 						GLXContext share_list, Bool direct);
7146 
7147 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7148 						Pixmap pixmap);
7149 
7150 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7151 
7152 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7153 
7154 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7155 						int attrib, int *value);
7156 
7157 				 GLXContext glXGetCurrentContext();
7158 
7159 				 GLXDrawable glXGetCurrentDrawable();
7160 
7161 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7162 
7163 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7164 						GLXContext ctx);
7165 
7166 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7167 
7168 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7169 
7170 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7171 
7172 				 void glXUseXFont(Font font, int first, int count, int list_base);
7173 
7174 				 void glXWaitGL();
7175 
7176 				 void glXWaitX();
7177 
7178 
7179 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7180 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7181 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7182 
7183 				char* glXQueryExtensionsString (Display*, int);
7184 				void* glXGetProcAddress (const(char)*);
7185 
7186 			}
7187 			}
7188 
7189 			version(OSX)
7190 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7191 			else
7192 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7193 			shared static this() {
7194 				glx.loadDynamicLibrary();
7195 			}
7196 
7197 			alias glbindGetProcAddress = glXGetProcAddress;
7198 		}
7199 	} else version(Windows) {
7200 		/* it is done below by interface GL */
7201 	} else
7202 		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.");
7203 }
7204 
7205 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7206 alias Resizablity = Resizability;
7207 
7208 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7209 enum Resizability {
7210 	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.
7211 	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.
7212 	/++
7213 		$(PITFALL
7214 			Planned for the future but not implemented.
7215 		)
7216 
7217 		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.
7218 
7219 		History:
7220 			Added November 11, 2022, but not yet implemented and may not be for some time.
7221 	+/
7222 	allowResizingMaintainingAspectRatio,
7223 	/++
7224 		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.
7225 
7226 		History:
7227 			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.
7228 
7229 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7230 	+/
7231 	automaticallyScaleIfPossible,
7232 }
7233 
7234 
7235 /++
7236 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7237 +/
7238 enum TextAlignment : uint {
7239 	Left = 0, ///
7240 	Center = 1, ///
7241 	Right = 2, ///
7242 
7243 	VerticalTop = 0, ///
7244 	VerticalCenter = 4, ///
7245 	VerticalBottom = 8, ///
7246 }
7247 
7248 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7249 alias Rectangle = arsd.color.Rectangle;
7250 
7251 
7252 /++
7253 	Keyboard press and release events.
7254 +/
7255 struct KeyEvent {
7256 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7257 	Key key;
7258 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7259 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7260 
7261 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7262 
7263 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7264 
7265 	SimpleWindow window; /// associated Window
7266 
7267 	/++
7268 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7269 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7270 		to predict if char events are actually coming..
7271 
7272 		Only available on X systems since this information is not given ahead of time elsewhere.
7273 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7274 
7275 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7276 		and potential quirks I'd recommend avoiding it.
7277 
7278 		History:
7279 			Added April 26, 2021 (dub v9.5)
7280 	+/
7281 	version(X11)
7282 		dchar[] charsPossible;
7283 
7284 	// convert key event to simplified string representation a-la emacs
7285 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7286 		uint dpos = 0;
7287 		void put (const(char)[] s...) nothrow @trusted {
7288 			static if (growdest) {
7289 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7290 			} else {
7291 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7292 			}
7293 		}
7294 
7295 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7296 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7297 		}
7298 
7299 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7300 
7301 		// put modifiers
7302 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7303 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7304 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7305 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7306 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7307 
7308 		if (this.key) {
7309 			foreach (string kn; __traits(allMembers, Key)) {
7310 				if (this.key == __traits(getMember, Key, kn)) {
7311 					// HACK!
7312 					static if (kn == "N0") put("0");
7313 					else static if (kn == "N1") put("1");
7314 					else static if (kn == "N2") put("2");
7315 					else static if (kn == "N3") put("3");
7316 					else static if (kn == "N4") put("4");
7317 					else static if (kn == "N5") put("5");
7318 					else static if (kn == "N6") put("6");
7319 					else static if (kn == "N7") put("7");
7320 					else static if (kn == "N8") put("8");
7321 					else static if (kn == "N9") put("9");
7322 					else put(kn);
7323 					return dest[0..dpos];
7324 				}
7325 			}
7326 			put("Unknown");
7327 		} else {
7328 			if (dpos && dest[dpos-1] == '+') --dpos;
7329 		}
7330 		return dest[0..dpos];
7331 	}
7332 
7333 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7334 
7335 	/** Parse string into key name with modifiers. It accepts things like:
7336 	 *
7337 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7338 	 *
7339 	 * Ctrl+Win+1 -- windows style
7340 	 *
7341 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7342 	 *
7343 	 * Ctrl Win 1 -- and space
7344 	 *
7345 	 * and even "Win + 1 + Ctrl".
7346 	 */
7347 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7348 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7349 
7350 		// remove trailing spaces
7351 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7352 
7353 		// tokens delimited by blank, '+', or '-'
7354 		// null on eol
7355 		const(char)[] getToken () nothrow @trusted @nogc {
7356 			// remove leading spaces and delimiters
7357 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7358 			if (name.length == 0) return null; // oops, no more tokens
7359 			// get token
7360 			size_t epos = 0;
7361 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7362 			assert(epos > 0 && epos <= name.length);
7363 			auto res = name[0..epos];
7364 			name = name[epos..$];
7365 			return res;
7366 		}
7367 
7368 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7369 			if (s0.length != s1.length) return false;
7370 			foreach (immutable ci, char c0; s0) {
7371 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7372 				char c1 = s1[ci];
7373 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7374 				if (c0 != c1) return false;
7375 			}
7376 			return true;
7377 		}
7378 
7379 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7380 		if (updown !is null) *updown = -1;
7381 		KeyEvent res;
7382 		res.key = cast(Key)0; // just in case
7383 		const(char)[] tk, tkn; // last token
7384 		bool allowEmascStyle = true;
7385 		bool ignoreModifiers = false;
7386 		tokenloop: for (;;) {
7387 			tk = tkn;
7388 			tkn = getToken();
7389 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7390 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7391 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7392 			if (allowEmascStyle && tkn.length != 0) {
7393 				if (tk.length == 1) {
7394 					char mdc = tk[0];
7395 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7396 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7397 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7398 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7399 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7400 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7401 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7402 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7403 				}
7404 			}
7405 			allowEmascStyle = false;
7406 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7407 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7408 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7409 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7410 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7411 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7412 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7413 			if (tk.length == 0) continue;
7414 			// try key name
7415 			if (res.key == 0) {
7416 				// little hack
7417 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7418 					final switch (tk[0]) {
7419 						case '0': tk = "N0"; break;
7420 						case '1': tk = "N1"; break;
7421 						case '2': tk = "N2"; break;
7422 						case '3': tk = "N3"; break;
7423 						case '4': tk = "N4"; break;
7424 						case '5': tk = "N5"; break;
7425 						case '6': tk = "N6"; break;
7426 						case '7': tk = "N7"; break;
7427 						case '8': tk = "N8"; break;
7428 						case '9': tk = "N9"; break;
7429 					}
7430 				}
7431 				foreach (string kn; __traits(allMembers, Key)) {
7432 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7433 				}
7434 			}
7435 			// unknown or duplicate key name, get out of here
7436 			break;
7437 		}
7438 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7439 		return res; // something
7440 	}
7441 
7442 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7443 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7444 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7445 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7446 		}
7447 		bool ignoreMods;
7448 		int updown;
7449 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7450 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7451 		if (this.key != ke.key) {
7452 			// things like "ctrl+alt" are complicated
7453 			uint tkm = this.modifierState&modmask;
7454 			uint kkm = ke.modifierState&modmask;
7455 			Key tk = this.key;
7456 			// ke
7457 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7458 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7459 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7460 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7461 			// this
7462 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7463 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7464 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7465 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7466 			return (tk == ke.key && tkm == kkm);
7467 		}
7468 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7469 	}
7470 }
7471 
7472 /// Sets the application name.
7473 @property string ApplicationName(string name) {
7474 	return _applicationName = name;
7475 }
7476 
7477 string _applicationName;
7478 
7479 /// ditto
7480 @property string ApplicationName() {
7481 	if(_applicationName is null) {
7482 		import core.runtime;
7483 		return Runtime.args[0];
7484 	}
7485 	return _applicationName;
7486 }
7487 
7488 
7489 /// Type of a [MouseEvent].
7490 enum MouseEventType : int {
7491 	motion = 0, /// The mouse moved inside the window
7492 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7493 	buttonReleased = 2, /// A mouse button was released
7494 }
7495 
7496 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7497 /++
7498 	Listen for this on your event listeners if you are interested in mouse action.
7499 
7500 	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.
7501 
7502 	Examples:
7503 
7504 	This will draw boxes on the window with the mouse as you hold the left button.
7505 	---
7506 	import arsd.simpledisplay;
7507 
7508 	void main() {
7509 		auto window = new SimpleWindow();
7510 
7511 		window.eventLoop(0,
7512 			(MouseEvent ev) {
7513 				if(ev.modifierState & ModifierState.leftButtonDown) {
7514 					auto painter = window.draw();
7515 					painter.fillColor = Color.red;
7516 					painter.outlineColor = Color.black;
7517 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7518 				}
7519 			}
7520 		);
7521 	}
7522 	---
7523 +/
7524 struct MouseEvent {
7525 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7526 
7527 	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.
7528 	int y; /// Current Y position of the cursor when the event fired.
7529 
7530 	int dx; /// Change in X position since last report
7531 	int dy; /// Change in Y position since last report
7532 
7533 	MouseButton button; /// See [MouseButton]
7534 	int modifierState; /// See [ModifierState]
7535 
7536 	version(X11)
7537 		private Time timestamp;
7538 
7539 	/// Returns a linear representation of mouse button,
7540 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7541 	///
7542 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7543 	@property ubyte buttonLinear() const {
7544 		import core.bitop;
7545 		if(button == 0)
7546 			return 0;
7547 		return (bsf(button) + 1) & 0b1111;
7548 	}
7549 
7550 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7551 
7552 	SimpleWindow window; /// The window in which the event happened.
7553 
7554 	Point globalCoordinates() {
7555 		Point p;
7556 		if(window is null)
7557 			throw new Exception("wtf");
7558 		static if(UsingSimpledisplayX11) {
7559 			Window child;
7560 			XTranslateCoordinates(
7561 				XDisplayConnection.get,
7562 				window.impl.window,
7563 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7564 				x, y, &p.x, &p.y, &child);
7565 			return p;
7566 		} else version(Windows) {
7567 			POINT[1] points;
7568 			points[0].x = x;
7569 			points[0].y = y;
7570 			MapWindowPoints(
7571 				window.impl.hwnd,
7572 				null,
7573 				points.ptr,
7574 				points.length
7575 			);
7576 			p.x = points[0].x;
7577 			p.y = points[0].y;
7578 
7579 			return p;
7580 		} else version(OSXCocoa) {
7581 			throw new NotYetImplementedException();
7582 		} else static assert(0);
7583 	}
7584 
7585 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7586 
7587 	/**
7588 	can contain emacs-like modifier prefix
7589 	case-insensitive names:
7590 		lmbX/leftX
7591 		rmbX/rightX
7592 		mmbX/middleX
7593 		wheelX
7594 		motion (no prefix allowed)
7595 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7596 	*/
7597 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7598 		if (str.length == 0) return false; // just in case
7599 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7600 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7601 		auto anchor = str;
7602 		uint mods = 0; // uint.max == any
7603 		// interesting bits in kmod
7604 		uint kmodmask =
7605 			ModifierState.shift|
7606 			ModifierState.ctrl|
7607 			ModifierState.alt|
7608 			ModifierState.windows|
7609 			ModifierState.leftButtonDown|
7610 			ModifierState.middleButtonDown|
7611 			ModifierState.rightButtonDown|
7612 			0;
7613 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7614 		bool wasButtons = false;
7615 		while (str.length) {
7616 			if (str.ptr[0] <= ' ') {
7617 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7618 				continue;
7619 			}
7620 			// one-letter modifier?
7621 			if (str.length >= 2 && str.ptr[1] == '-') {
7622 				switch (str.ptr[0]) {
7623 					case '*': // "any" modifier (cannot be undone)
7624 						mods = mods.max;
7625 						break;
7626 					case 'C': case 'c': // emacs "ctrl"
7627 						if (mods != mods.max) mods |= ModifierState.ctrl;
7628 						break;
7629 					case 'M': case 'm': // emacs "meta"
7630 						if (mods != mods.max) mods |= ModifierState.alt;
7631 						break;
7632 					case 'S': case 's': // emacs "shift"
7633 						if (mods != mods.max) mods |= ModifierState.shift;
7634 						break;
7635 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7636 						if (mods != mods.max) mods |= ModifierState.windows;
7637 						break;
7638 					default:
7639 						return false; // unknown modifier
7640 				}
7641 				str = str[2..$];
7642 				continue;
7643 			}
7644 			// word
7645 			char[16] buf = void; // locased
7646 			auto wep = 0;
7647 			while (str.length) {
7648 				immutable char ch = str.ptr[0];
7649 				if (ch <= ' ' || ch == '-') break;
7650 				str = str[1..$];
7651 				if (wep > buf.length) return false; // too long
7652 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7653 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7654 				else return false; // invalid char
7655 			}
7656 			if (wep == 0) return false; // just in case
7657 			uint bnum;
7658 			enum UpDown { None = -1, Up, Down, Any }
7659 			auto updown = UpDown.None; // 0: up; 1: down
7660 			switch (buf[0..wep]) {
7661 				// left button
7662 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7663 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7664 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7665 				case "lmb": case "left": bnum = 0; break;
7666 				// middle button
7667 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7668 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7669 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7670 				case "mmb": case "middle": bnum = 1; break;
7671 				// right button
7672 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7673 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7674 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7675 				case "rmb": case "right": bnum = 2; break;
7676 				// wheel
7677 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7678 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7679 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7680 				case "wheel": bnum = 3; break;
7681 				// motion
7682 				case "motion": bnum = 7; break;
7683 				// unknown
7684 				default: return false;
7685 			}
7686 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7687 			// parse possible "-up" or "-down"
7688 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7689 				wep = 0;
7690 				foreach (immutable idx, immutable char ch; str[1..$]) {
7691 					if (ch <= ' ' || ch == '-') break;
7692 					assert(idx == wep); // for now; trick
7693 					if (wep > buf.length) { wep = 0; break; } // too long
7694 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7695 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7696 					else { wep = 0; break; } // invalid char
7697 				}
7698 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7699 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7700 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7701 				// remove parsed part
7702 				if (updown != UpDown.None) str = str[wep+1..$];
7703 			}
7704 			if (updown == UpDown.None) {
7705 				updown = UpDown.Down;
7706 			}
7707 			wasButtons = wasButtons || (bnum <= 2);
7708 			//assert(updown != UpDown.None);
7709 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7710 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7711 			if (lastButt != lastButt.max) {
7712 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7713 				if (mods != mods.max) {
7714 					uint butbit = 0;
7715 					final switch (lastButt&0x03) {
7716 						case 0: butbit = ModifierState.leftButtonDown; break;
7717 						case 1: butbit = ModifierState.middleButtonDown; break;
7718 						case 2: butbit = ModifierState.rightButtonDown; break;
7719 					}
7720 					     if (lastButt&Flag.Down) mods |= butbit;
7721 					else if (lastButt&Flag.Up) mods &= ~butbit;
7722 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7723 				}
7724 			}
7725 			// remember last button
7726 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7727 		}
7728 		// no button -- nothing to do
7729 		if (lastButt == lastButt.max) return false;
7730 		// done parsing, check if something's left
7731 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7732 		// remove action button from mask
7733 		if ((lastButt&0xff) < 3) {
7734 			final switch (lastButt&0x03) {
7735 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7736 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7737 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7738 			}
7739 		}
7740 		// special case: "Motion" means "ignore buttons"
7741 		if ((lastButt&0xff) == 7 && !wasButtons) {
7742 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7743 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7744 		}
7745 		uint kmod = event.modifierState&kmodmask;
7746 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7747 		// check modifier state
7748 		if (mods != mods.max) {
7749 			if (kmod != mods) return false;
7750 		}
7751 		// now check type
7752 		if ((lastButt&0xff) == 7) {
7753 			// motion
7754 			if (event.type != MouseEventType.motion) return false;
7755 		} else if ((lastButt&0xff) == 3) {
7756 			// wheel
7757 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7758 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7759 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7760 			return false;
7761 		} else {
7762 			// buttons
7763 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7764 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7765 			{
7766 				return false;
7767 			}
7768 			// button number
7769 			switch (lastButt&0x03) {
7770 				case 0: if (event.button != MouseButton.left) return false; break;
7771 				case 1: if (event.button != MouseButton.middle) return false; break;
7772 				case 2: if (event.button != MouseButton.right) return false; break;
7773 				default: return false;
7774 			}
7775 		}
7776 		return true;
7777 	}
7778 }
7779 
7780 version(arsd_mevent_strcmp_test) unittest {
7781 	MouseEvent event;
7782 	event.type = MouseEventType.buttonPressed;
7783 	event.button = MouseButton.left;
7784 	event.modifierState = ModifierState.ctrl;
7785 	assert(event == "C-LMB");
7786 	assert(event != "C-LMBUP");
7787 	assert(event != "C-LMB-UP");
7788 	assert(event != "C-S-LMB");
7789 	assert(event == "*-LMB");
7790 	assert(event != "*-LMB-UP");
7791 
7792 	event.type = MouseEventType.buttonReleased;
7793 	assert(event != "C-LMB");
7794 	assert(event == "C-LMBUP");
7795 	assert(event == "C-LMB-UP");
7796 	assert(event != "C-S-LMB");
7797 	assert(event != "*-LMB");
7798 	assert(event == "*-LMB-UP");
7799 
7800 	event.button = MouseButton.right;
7801 	event.modifierState |= ModifierState.shift;
7802 	event.type = MouseEventType.buttonPressed;
7803 	assert(event != "C-LMB");
7804 	assert(event != "C-LMBUP");
7805 	assert(event != "C-LMB-UP");
7806 	assert(event != "C-S-LMB");
7807 	assert(event != "*-LMB");
7808 	assert(event != "*-LMB-UP");
7809 
7810 	assert(event != "C-RMB");
7811 	assert(event != "C-RMBUP");
7812 	assert(event != "C-RMB-UP");
7813 	assert(event == "C-S-RMB");
7814 	assert(event == "*-RMB");
7815 	assert(event != "*-RMB-UP");
7816 }
7817 
7818 /// This gives a few more options to drawing lines and such
7819 struct Pen {
7820 	Color color; /// the foreground color
7821 	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.
7822 	Style style; /// See [Style]
7823 /+
7824 // From X.h
7825 
7826 #define LineSolid		0
7827 #define LineOnOffDash		1
7828 #define LineDoubleDash		2
7829        LineDou-        The full path of the line is drawn, but the
7830        bleDash         even dashes are filled differently from the
7831                        odd dashes (see fill-style) with CapButt
7832                        style used where even and odd dashes meet.
7833 
7834 
7835 
7836 /* capStyle */
7837 
7838 #define CapNotLast		0
7839 #define CapButt			1
7840 #define CapRound		2
7841 #define CapProjecting		3
7842 
7843 /* joinStyle */
7844 
7845 #define JoinMiter		0
7846 #define JoinRound		1
7847 #define JoinBevel		2
7848 
7849 /* fillStyle */
7850 
7851 #define FillSolid		0
7852 #define FillTiled		1
7853 #define FillStippled		2
7854 #define FillOpaqueStippled	3
7855 
7856 
7857 +/
7858 	/// Style of lines drawn
7859 	enum Style {
7860 		Solid, /// a solid line
7861 		Dashed, /// a dashed line
7862 		Dotted, /// a dotted line
7863 	}
7864 }
7865 
7866 
7867 /++
7868 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7869 
7870 
7871 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7872 
7873 	$(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.)
7874 
7875 	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.
7876 
7877 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7878 
7879 	$(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.
7880 
7881 	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!
7882 
7883 	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!)
7884 
7885 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7886 
7887 	---
7888 		auto image = new Image(256, 256);
7889 		scope(exit) destroy(image);
7890 	---
7891 
7892 	As long as you don't hold on to it outside the scope.
7893 
7894 	I might change it to be an owned pointer at some point in the future.
7895 
7896 	)
7897 
7898 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7899 	you can also often get a fair amount of speedup by getting the raw data format and
7900 	writing some custom code.
7901 
7902 	FIXME INSERT EXAMPLES HERE
7903 
7904 
7905 +/
7906 final class Image {
7907 	///
7908 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7909 		this.width = width;
7910 		this.height = height;
7911 		this.enableAlpha = enableAlpha;
7912 
7913 		impl.createImage(width, height, forcexshm, enableAlpha);
7914 	}
7915 
7916 	///
7917 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7918 		this(size.width, size.height, forcexshm, enableAlpha);
7919 	}
7920 
7921 	private bool suppressDestruction;
7922 
7923 	version(X11)
7924 	this(XImage* handle) {
7925 		this.handle = handle;
7926 		this.rawData = cast(ubyte*) handle.data;
7927 		this.width = handle.width;
7928 		this.height = handle.height;
7929 		this.enableAlpha = handle.depth == 32;
7930 		suppressDestruction = true;
7931 	}
7932 
7933 	~this() {
7934 		if(suppressDestruction) return;
7935 		impl.dispose();
7936 	}
7937 
7938 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7939 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7940 	pure const @system nothrow {
7941 		/*
7942 			To use these to draw a blue rectangle with size WxH at position X,Y...
7943 
7944 			// make certain that it will fit before we proceed
7945 			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!
7946 
7947 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7948 			// (though calculating them isn't really that expensive).
7949 			auto nextLineAdjustment = img.adjustmentForNextLine();
7950 			auto offR = img.redByteOffset();
7951 			auto offB = img.blueByteOffset();
7952 			auto offG = img.greenByteOffset();
7953 			auto bpp = img.bytesPerPixel();
7954 
7955 			auto data = img.getDataPointer();
7956 
7957 			// figure out the starting byte offset
7958 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7959 
7960 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7961 
7962 			// and now our drawing loop for the rectangle
7963 			foreach(y; 0 .. H) {
7964 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7965 				foreach(x; 0 .. W) {
7966 					// write our color
7967 					data[offR] = 0;
7968 					data[offG] = 0;
7969 					data[offB] = 255;
7970 
7971 					data += bpp; // moving to the next pixel is just an addition...
7972 				}
7973 				startOfLine += nextLineAdjustment;
7974 			}
7975 
7976 
7977 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7978 
7979 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7980 			can be made into a bitmask or something so we can write them as *uint...
7981 		*/
7982 
7983 		///
7984 		int offsetForTopLeftPixel() {
7985 			version(X11) {
7986 				return 0;
7987 			} else version(Windows) {
7988 				if(enableAlpha) {
7989 					return (width * 4) * (height - 1);
7990 				} else {
7991 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7992 				}
7993 			} else version(OSXCocoa) {
7994 				return 0 ; //throw new NotYetImplementedException();
7995 			} else static assert(0, "fill in this info for other OSes");
7996 		}
7997 
7998 		///
7999 		int offsetForPixel(int x, int y) {
8000 			version(X11) {
8001 				auto offset = (y * width + x) * 4;
8002 				return offset;
8003 			} else version(Windows) {
8004 				if(enableAlpha) {
8005 					auto itemsPerLine = width * 4;
8006 					// remember, bmps are upside down
8007 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8008 					return offset;
8009 				} else {
8010 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8011 					// remember, bmps are upside down
8012 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8013 					return offset;
8014 				}
8015 			} else version(OSXCocoa) {
8016 				return 0 ; //throw new NotYetImplementedException();
8017 			} else static assert(0, "fill in this info for other OSes");
8018 		}
8019 
8020 		///
8021 		int adjustmentForNextLine() {
8022 			version(X11) {
8023 				return width * 4;
8024 			} else version(Windows) {
8025 				// windows bmps are upside down, so the adjustment is actually negative
8026 				if(enableAlpha)
8027 					return - (cast(int) width * 4);
8028 				else
8029 					return -((cast(int) width * 3 + 3) / 4) * 4;
8030 			} else version(OSXCocoa) {
8031 				return 0 ; //throw new NotYetImplementedException();
8032 			} else static assert(0, "fill in this info for other OSes");
8033 		}
8034 
8035 		/// once you have the position of a pixel, use these to get to the proper color
8036 		int redByteOffset() {
8037 			version(X11) {
8038 				return 2;
8039 			} else version(Windows) {
8040 				return 2;
8041 			} else version(OSXCocoa) {
8042 				return 0 ; //throw new NotYetImplementedException();
8043 			} else static assert(0, "fill in this info for other OSes");
8044 		}
8045 
8046 		///
8047 		int greenByteOffset() {
8048 			version(X11) {
8049 				return 1;
8050 			} else version(Windows) {
8051 				return 1;
8052 			} else version(OSXCocoa) {
8053 				return 0 ; //throw new NotYetImplementedException();
8054 			} else static assert(0, "fill in this info for other OSes");
8055 		}
8056 
8057 		///
8058 		int blueByteOffset() {
8059 			version(X11) {
8060 				return 0;
8061 			} else version(Windows) {
8062 				return 0;
8063 			} else version(OSXCocoa) {
8064 				return 0 ; //throw new NotYetImplementedException();
8065 			} else static assert(0, "fill in this info for other OSes");
8066 		}
8067 
8068 		/// Only valid if [enableAlpha] is true
8069 		int alphaByteOffset() {
8070 			version(X11) {
8071 				return 3;
8072 			} else version(Windows) {
8073 				return 3;
8074 			} else version(OSXCocoa) {
8075 				return 3; //throw new NotYetImplementedException();
8076 			} else static assert(0, "fill in this info for other OSes");
8077 		}
8078 	}
8079 
8080 	///
8081 	final void putPixel(int x, int y, Color c) {
8082 		if(x < 0 || x >= width)
8083 			return;
8084 		if(y < 0 || y >= height)
8085 			return;
8086 
8087 		impl.setPixel(x, y, c);
8088 	}
8089 
8090 	///
8091 	final Color getPixel(int x, int y) {
8092 		if(x < 0 || x >= width)
8093 			return Color.transparent;
8094 		if(y < 0 || y >= height)
8095 			return Color.transparent;
8096 
8097 		version(OSXCocoa) throw new NotYetImplementedException(); else
8098 		return impl.getPixel(x, y);
8099 	}
8100 
8101 	///
8102 	final void opIndexAssign(Color c, int x, int y) {
8103 		putPixel(x, y, c);
8104 	}
8105 
8106 	///
8107 	TrueColorImage toTrueColorImage() {
8108 		auto tci = new TrueColorImage(width, height);
8109 		convertToRgbaBytes(tci.imageData.bytes);
8110 		return tci;
8111 	}
8112 
8113 	///
8114 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) {
8115 		auto tci = i.getAsTrueColorImage();
8116 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8117 		img.setRgbaBytes(tci.imageData.bytes);
8118 		return img;
8119 	}
8120 
8121 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8122 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8123 	/// if you pass null, it will allocate a new one.
8124 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8125 		if(where is null)
8126 			where = new ubyte[this.width*this.height*4];
8127 		convertToRgbaBytes(where);
8128 		return where;
8129 	}
8130 
8131 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8132 	void setRgbaBytes(in ubyte[] from ) {
8133 		assert(from.length == this.width * this.height * 4);
8134 		setFromRgbaBytes(from);
8135 	}
8136 
8137 	// FIXME: make properly cross platform by getting rgba right
8138 
8139 	/// warning: this is not portable across platforms because the data format can change
8140 	ubyte* getDataPointer() {
8141 		return impl.rawData;
8142 	}
8143 
8144 	/// for use with getDataPointer
8145 	final int bytesPerLine() const pure @safe nothrow {
8146 		version(Windows)
8147 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8148 		else version(X11)
8149 			return 4 * width;
8150 		else version(OSXCocoa)
8151 			return 4 * width;
8152 		else static assert(0);
8153 	}
8154 
8155 	/// for use with getDataPointer
8156 	final int bytesPerPixel() const pure @safe nothrow {
8157 		version(Windows)
8158 			return enableAlpha ? 4 : 3;
8159 		else version(X11)
8160 			return 4;
8161 		else version(OSXCocoa)
8162 			return 4;
8163 		else static assert(0);
8164 	}
8165 
8166 	///
8167 	immutable int width;
8168 
8169 	///
8170 	immutable int height;
8171 
8172 	///
8173 	immutable bool enableAlpha;
8174     //private:
8175 	mixin NativeImageImplementation!() impl;
8176 }
8177 
8178 /++
8179 	A convenience function to pop up a window displaying the image.
8180 	If you pass a win, it will draw the image in it. Otherwise, it will
8181 	create a window with the size of the image and run its event loop, closing
8182 	when a key is pressed.
8183 
8184 	History:
8185 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8186 		always block until the application quit which could cause bizarre behavior
8187 		inside a more complex application. Now, the default is to block until
8188 		this window closes if it is the only event loop running, and otherwise,
8189 		not to block at all and just pop up the display window asynchronously.
8190 +/
8191 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8192 	if(win is null) {
8193 		win = new SimpleWindow(image);
8194 		{
8195 			auto p = win.draw;
8196 			p.drawImage(Point(0, 0), image);
8197 		}
8198 		win.eventLoopWithBlockingMode(
8199 			bm, 0,
8200 			(KeyEvent ev) {
8201 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8202 			} );
8203 	} else {
8204 		win.image = image;
8205 	}
8206 }
8207 
8208 enum FontWeight : int {
8209 	dontcare = 0,
8210 	thin = 100,
8211 	extralight = 200,
8212 	light = 300,
8213 	regular = 400,
8214 	medium = 500,
8215 	semibold = 600,
8216 	bold = 700,
8217 	extrabold = 800,
8218 	heavy = 900
8219 }
8220 
8221 /++
8222 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8223 
8224 	History:
8225 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8226 +/
8227 interface MeasurableFont {
8228 	/++
8229 		Returns true if it is a monospace font, meaning each of the
8230 		glyphs (at least the ascii characters) have matching width
8231 		and no kerning, so you can determine the display width of some
8232 		strings by simply multiplying the string width by [averageWidth].
8233 
8234 		(Please note that multiply doesn't $(I actually) work in general,
8235 		consider characters like tab and newline, but it does sometimes.)
8236 	+/
8237 	bool isMonospace();
8238 
8239 	/++
8240 		The average width of glyphs in the font, traditionally equal to the
8241 		width of the lowercase x. Can be used to estimate bounding boxes,
8242 		especially if the font [isMonospace].
8243 
8244 		Given in pixels.
8245 	+/
8246 	int averageWidth();
8247 	/++
8248 		The height of the bounding box of a line.
8249 	+/
8250 	int height();
8251 	/++
8252 		The maximum ascent of a glyph above the baseline.
8253 
8254 		Given in pixels.
8255 	+/
8256 	int ascent();
8257 	/++
8258 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8259 
8260 		Given in pixels.
8261 	+/
8262 	int descent();
8263 	/++
8264 		The display width of the given string, and if you provide a window, it will use it to
8265 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8266 
8267 		Given in pixels.
8268 	+/
8269 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8270 
8271 }
8272 
8273 // FIXME: i need a font cache and it needs to handle disconnects.
8274 
8275 /++
8276 	Represents a font loaded off the operating system or the X server.
8277 
8278 
8279 	While the api here is unified cross platform, the fonts are not necessarily
8280 	available, even across machines of the same platform, so be sure to always check
8281 	for null (using [isNull]) and have a fallback plan.
8282 
8283 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8284 
8285 	Worst case, a null font will automatically fall back to the default font loaded
8286 	for your system.
8287 +/
8288 class OperatingSystemFont : MeasurableFont {
8289 	// FIXME: when the X Connection is lost, these need to be invalidated!
8290 	// that means I need to store the original stuff again to reconstruct it too.
8291 
8292 	version(X11) {
8293 		XFontStruct* font;
8294 		XFontSet fontset;
8295 
8296 		version(with_xft) {
8297 			XftFont* xftFont;
8298 			bool isXft;
8299 		}
8300 	} else version(Windows) {
8301 		HFONT font;
8302 		int width_;
8303 		int height_;
8304 	} else version(OSXCocoa) {
8305 		// FIXME
8306 	} else static assert(0);
8307 
8308 	/++
8309 		Constructs the class and immediately calls [load].
8310 	+/
8311 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8312 		load(name, size, weight, italic);
8313 	}
8314 
8315 	/++
8316 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8317 
8318 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8319 
8320 		History:
8321 			Added January 24, 2021.
8322 	+/
8323 	this() {
8324 		// this space intentionally left blank
8325 	}
8326 
8327 	/++
8328 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8329 
8330 		History:
8331 			Added November 13, 2020.
8332 	+/
8333 	version(with_xft)
8334 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8335 		unload();
8336 
8337 		if(!XftLibrary.attempted) {
8338 			XftLibrary.loadDynamicLibrary();
8339 		}
8340 
8341 		if(!XftLibrary.loadSuccessful)
8342 			return false;
8343 
8344 		auto display = XDisplayConnection.get;
8345 
8346 		char[256] nameBuffer = void;
8347 		int nbp = 0;
8348 
8349 		void add(in char[] a) {
8350 			nameBuffer[nbp .. nbp + a.length] = a[];
8351 			nbp += a.length;
8352 		}
8353 		add(name);
8354 
8355 		if(size) {
8356 			add(":size=");
8357 			add(toInternal!string(size));
8358 		}
8359 		if(weight != FontWeight.dontcare) {
8360 			add(":weight=");
8361 			add(weightToString(weight));
8362 		}
8363 		if(italic)
8364 			add(":slant=100");
8365 
8366 		nameBuffer[nbp] = 0;
8367 
8368 		this.xftFont = XftFontOpenName(
8369 			display,
8370 			DefaultScreen(display),
8371 			nameBuffer.ptr
8372 		);
8373 
8374 		this.isXft = true;
8375 
8376 		if(xftFont !is null) {
8377 			isMonospace_ = stringWidth("x") == stringWidth("M");
8378 			ascent_ = xftFont.ascent;
8379 			descent_ = xftFont.descent;
8380 		}
8381 
8382 		return !isNull();
8383 	}
8384 
8385 	/++
8386 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8387 
8388 
8389 		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.
8390 
8391 		If `pattern` is null, it returns all available font families.
8392 
8393 		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.
8394 
8395 		The format of the pattern is platform-specific.
8396 
8397 		History:
8398 			Added May 1, 2021 (dub v9.5)
8399 	+/
8400 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8401 		version(Windows) {
8402 			auto hdc = GetDC(null);
8403 			scope(exit) ReleaseDC(null, hdc);
8404 			LOGFONT logfont;
8405 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8406 				auto localHandler = *(cast(typeof(handler)*) p);
8407 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8408 			}
8409 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8410 		} else version(X11) {
8411 			//import core.stdc.stdio;
8412 			bool done = false;
8413 			version(with_xft) {
8414 				if(!XftLibrary.attempted) {
8415 					XftLibrary.loadDynamicLibrary();
8416 				}
8417 
8418 				if(!XftLibrary.loadSuccessful)
8419 					goto skipXft;
8420 
8421 				if(!FontConfigLibrary.attempted)
8422 					FontConfigLibrary.loadDynamicLibrary();
8423 				if(!FontConfigLibrary.loadSuccessful)
8424 					goto skipXft;
8425 
8426 				{
8427 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8428 					if(got is null)
8429 						goto skipXft;
8430 					scope(exit) FcFontSetDestroy(got);
8431 
8432 					auto fontPatterns = got.fonts[0 .. got.nfont];
8433 					foreach(candidate; fontPatterns) {
8434 						char* where, whereStyle;
8435 
8436 						char* pmg = FcNameUnparse(candidate);
8437 
8438 						//FcPatternGetString(candidate, "family", 0, &where);
8439 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8440 						//if(where && whereStyle) {
8441 						if(pmg) {
8442 							if(!handler(pmg.sliceCString))
8443 								return;
8444 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8445 						}
8446 					}
8447 				}
8448 			}
8449 
8450 			skipXft:
8451 
8452 			if(pattern is null)
8453 				pattern = "*";
8454 
8455 			int count;
8456 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8457 			scope(exit) XFreeFontNames(coreFontsRaw);
8458 
8459 			auto coreFonts = coreFontsRaw[0 .. count];
8460 
8461 			foreach(font; coreFonts) {
8462 				char[128] tmp;
8463 				tmp[0 ..5] = "core:";
8464 				auto cf = font.sliceCString;
8465 				if(5 + cf.length > tmp.length)
8466 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8467 				tmp[5 .. 5 + cf.length] = cf;
8468 				if(!handler(tmp[0 .. 5 + cf.length]))
8469 					return;
8470 			}
8471 		}
8472 	}
8473 
8474 	/++
8475 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8476 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8477 
8478 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8479 		underlying system doesn't support returning the raw bytes.
8480 
8481 		History:
8482 			Added September 10, 2021 (dub v10.3)
8483 	+/
8484 	ubyte[] getTtfBytes() {
8485 		if(isNull)
8486 			return null;
8487 
8488 		version(Windows) {
8489 			auto dc = GetDC(null);
8490 			auto orig = SelectObject(dc, font);
8491 
8492 			scope(exit) {
8493 				SelectObject(dc, orig);
8494 				ReleaseDC(null, dc);
8495 			}
8496 
8497 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8498 			if(res == GDI_ERROR)
8499 				return null;
8500 
8501 			ubyte[] buffer = new ubyte[](res);
8502 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8503 			if(res == GDI_ERROR)
8504 				return null; // wtf really tbh
8505 
8506 			return buffer;
8507 		} else version(with_xft) {
8508 			if(isXft && xftFont) {
8509 				if(!FontConfigLibrary.attempted)
8510 					FontConfigLibrary.loadDynamicLibrary();
8511 				if(!FontConfigLibrary.loadSuccessful)
8512 					return null;
8513 
8514 				char* file;
8515 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8516 					if (file !is null && file[0]) {
8517 						import core.stdc.stdio;
8518 						auto fp = fopen(file, "rb");
8519 						if(fp is null)
8520 							return null;
8521 						scope(exit)
8522 							fclose(fp);
8523 						fseek(fp, 0, SEEK_END);
8524 						ubyte[] buffer = new ubyte[](ftell(fp));
8525 						fseek(fp, 0, SEEK_SET);
8526 
8527 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8528 						if(got != buffer.length)
8529 							return null;
8530 
8531 						return buffer;
8532 					}
8533 				}
8534 			}
8535 			return null;
8536 		}
8537 	}
8538 
8539 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8540 
8541 	private string weightToString(FontWeight weight) {
8542 		with(FontWeight)
8543 		final switch(weight) {
8544 			case dontcare: return "*";
8545 			case thin: return "extralight";
8546 			case extralight: return "extralight";
8547 			case light: return "light";
8548 			case regular: return "regular";
8549 			case medium: return "medium";
8550 			case semibold: return "demibold";
8551 			case bold: return "bold";
8552 			case extrabold: return "demibold";
8553 			case heavy: return "black";
8554 		}
8555 	}
8556 
8557 	/++
8558 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8559 
8560 		History:
8561 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8562 	+/
8563 	version(X11)
8564 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8565 		unload();
8566 
8567 		string xfontstr;
8568 
8569 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8570 			// this is kinda a disgusting hack but if the user sends an exact
8571 			// string I'd like to honor it...
8572 			xfontstr = name;
8573 		} else {
8574 			string weightstr = weightToString(weight);
8575 			string sizestr;
8576 			if(size == 0)
8577 				sizestr = "*";
8578 			else
8579 				sizestr = toInternal!string(size);
8580 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8581 		}
8582 
8583 		//import std.stdio; writeln(xfontstr);
8584 
8585 		auto display = XDisplayConnection.get;
8586 
8587 		font = XLoadQueryFont(display, xfontstr.ptr);
8588 		if(font is null)
8589 			return false;
8590 
8591 		char** lol;
8592 		int lol2;
8593 		char* lol3;
8594 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8595 
8596 		prepareFontInfo();
8597 
8598 		return !isNull();
8599 	}
8600 
8601 	version(X11)
8602 	private void prepareFontInfo() {
8603 		if(font !is null) {
8604 			isMonospace_ = stringWidth("l") == stringWidth("M");
8605 			ascent_ = font.max_bounds.ascent;
8606 			descent_ = font.max_bounds.descent;
8607 		}
8608 	}
8609 
8610 	/++
8611 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8612 
8613 		History:
8614 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8615 	+/
8616 	version(Windows)
8617 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8618 		unload();
8619 
8620 		WCharzBuffer buffer = WCharzBuffer(name);
8621 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8622 
8623 		prepareFontInfo(hdc);
8624 
8625 		return !isNull();
8626 	}
8627 
8628 	version(Windows)
8629 	void prepareFontInfo(HDC hdc = null) {
8630 		if(font is null)
8631 			return;
8632 
8633 		TEXTMETRIC tm;
8634 		auto dc = hdc ? hdc : GetDC(null);
8635 		auto orig = SelectObject(dc, font);
8636 		GetTextMetrics(dc, &tm);
8637 		SelectObject(dc, orig);
8638 		if(hdc is null)
8639 			ReleaseDC(null, dc);
8640 
8641 		width_ = tm.tmAveCharWidth;
8642 		height_ = tm.tmHeight;
8643 		ascent_ = tm.tmAscent;
8644 		descent_ = tm.tmDescent;
8645 		// 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.
8646 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8647 	}
8648 
8649 
8650 	/++
8651 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8652 
8653 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8654 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8655 
8656 		On Windows, it forwards directly to [loadWin32].
8657 
8658 		Params:
8659 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8660 			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.
8661 			weight = approximate boldness, results may vary.
8662 			italic = try to get a slanted version of the given font.
8663 
8664 		History:
8665 			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.
8666 	+/
8667 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8668 		version(X11) {
8669 			version(with_xft) {
8670 				if(name.length > 5 && name[0 .. 5] == "core:") {
8671 					goto core;
8672 				}
8673 
8674 				if(loadXft(name, size, weight, italic))
8675 					return true;
8676 				// if xft fails, fallback to core to avoid breaking
8677 				// code that already depended on this.
8678 			}
8679 
8680 			core:
8681 
8682 			if(name.length > 5 && name[0 .. 5] == "core:") {
8683 				name = name[5 .. $];
8684 			}
8685 
8686 			return loadCoreX(name, size, weight, italic);
8687 		} else version(Windows) {
8688 			return loadWin32(name, size, weight, italic);
8689 		} else version(OSXCocoa) {
8690 			// FIXME
8691 			return false;
8692 		} else static assert(0);
8693 	}
8694 
8695 	///
8696 	void unload() {
8697 		if(isNull())
8698 			return;
8699 
8700 		version(X11) {
8701 			auto display = XDisplayConnection.display;
8702 
8703 			if(display is null)
8704 				return;
8705 
8706 			version(with_xft) {
8707 				if(isXft) {
8708 					if(xftFont)
8709 						XftFontClose(display, xftFont);
8710 					isXft = false;
8711 					xftFont = null;
8712 					return;
8713 				}
8714 			}
8715 
8716 			if(font && font !is ScreenPainterImplementation.defaultfont)
8717 				XFreeFont(display, font);
8718 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8719 				XFreeFontSet(display, fontset);
8720 
8721 			font = null;
8722 			fontset = null;
8723 		} else version(Windows) {
8724 			DeleteObject(font);
8725 			font = null;
8726 		} else version(OSXCocoa) {
8727 			// FIXME
8728 		} else static assert(0);
8729 	}
8730 
8731 	private bool isMonospace_;
8732 
8733 	/++
8734 		History:
8735 			Added January 16, 2021
8736 	+/
8737 	bool isMonospace() {
8738 		return isMonospace_;
8739 	}
8740 
8741 	/++
8742 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8743 
8744 		History:
8745 			Added March 26, 2020
8746 			Documented January 16, 2021
8747 	+/
8748 	int averageWidth() {
8749 		version(X11) {
8750 			return stringWidth("x");
8751 		} else version(Windows)
8752 			return width_;
8753 		else assert(0);
8754 	}
8755 
8756 	/++
8757 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8758 
8759 		History:
8760 			Added January 16, 2021
8761 	+/
8762 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8763 	// FIXME: what about tab?
8764 		if(isNull)
8765 			return 0;
8766 
8767 		version(X11) {
8768 			version(with_xft)
8769 				if(isXft && xftFont !is null) {
8770 					//return xftFont.max_advance_width;
8771 					XGlyphInfo extents;
8772 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8773 					//import std.stdio; writeln(extents);
8774 					return extents.xOff;
8775 				}
8776 			if(font is null)
8777 				return 0;
8778 			else if(fontset) {
8779 				XRectangle rect;
8780 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8781 
8782 				return rect.width;
8783 			} else {
8784 				return XTextWidth(font, s.ptr, cast(int) s.length);
8785 			}
8786 		} else version(Windows) {
8787 			WCharzBuffer buffer = WCharzBuffer(s);
8788 
8789 			return stringWidth(buffer.slice, window);
8790 		}
8791 		else assert(0);
8792 	}
8793 
8794 	version(Windows)
8795 	/// ditto
8796 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8797 		if(isNull)
8798 			return 0;
8799 		version(Windows) {
8800 			SIZE size;
8801 
8802 			prepareContext(window);
8803 			scope(exit) releaseContext();
8804 
8805 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8806 
8807 			return size.cx;
8808 		} else {
8809 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8810 			static assert(0, "not implemented yet");
8811 			//return stringWidth(s, window);
8812 		}
8813 	}
8814 
8815 	private {
8816 		int prepRefcount;
8817 
8818 		version(Windows) {
8819 			HDC dc;
8820 			HANDLE orig;
8821 			HWND hwnd;
8822 		}
8823 	}
8824 	/++
8825 		[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.
8826 
8827 		History:
8828 			Added January 23, 2021
8829 	+/
8830 	void prepareContext(SimpleWindow window = null) {
8831 		prepRefcount++;
8832 		if(prepRefcount == 1) {
8833 			version(Windows) {
8834 				hwnd = window is null ? null : window.impl.hwnd;
8835 				dc = GetDC(hwnd);
8836 				orig = SelectObject(dc, font);
8837 			}
8838 		}
8839 	}
8840 	/// ditto
8841 	void releaseContext() {
8842 		prepRefcount--;
8843 		if(prepRefcount == 0) {
8844 			version(Windows) {
8845 				SelectObject(dc, orig);
8846 				ReleaseDC(hwnd, dc);
8847 				hwnd = null;
8848 				dc = null;
8849 				orig = null;
8850 			}
8851 		}
8852 	}
8853 
8854 	/+
8855 		FIXME: I think I need advance and kerning pair
8856 
8857 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8858 	+/
8859 
8860 	/++
8861 		Returns the height of the font.
8862 
8863 		History:
8864 			Added March 26, 2020
8865 			Documented January 16, 2021
8866 	+/
8867 	int height() {
8868 		version(X11) {
8869 			version(with_xft)
8870 				if(isXft && xftFont !is null) {
8871 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8872 				}
8873 			if(font is null)
8874 				return 0;
8875 			return font.max_bounds.ascent + font.max_bounds.descent;
8876 		} else version(Windows)
8877 			return height_;
8878 		else assert(0);
8879 	}
8880 
8881 	private int ascent_;
8882 	private int descent_;
8883 
8884 	/++
8885 		Max ascent above the baseline.
8886 
8887 		History:
8888 			Added January 22, 2021
8889 	+/
8890 	int ascent() {
8891 		return ascent_;
8892 	}
8893 
8894 	/++
8895 		Max descent below the baseline.
8896 
8897 		History:
8898 			Added January 22, 2021
8899 	+/
8900 	int descent() {
8901 		return descent_;
8902 	}
8903 
8904 	/++
8905 		Loads the default font used by [ScreenPainter] if none others are loaded.
8906 
8907 		Returns:
8908 			This method mutates the `this` object, but then returns `this` for
8909 			easy chaining like:
8910 
8911 			---
8912 			auto font = foo.isNull ? foo : foo.loadDefault
8913 			---
8914 
8915 		History:
8916 			Added previously, but left unimplemented until January 24, 2021.
8917 	+/
8918 	OperatingSystemFont loadDefault() {
8919 		unload();
8920 
8921 		version(X11) {
8922 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8923 			// but meh since sdpy does its own thing, this should be ok too
8924 
8925 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8926 			this.font = ScreenPainterImplementation.defaultfont;
8927 			this.fontset = ScreenPainterImplementation.defaultfontset;
8928 
8929 			prepareFontInfo();
8930 		} else version(Windows) {
8931 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8932 			this.font = ScreenPainterImplementation.defaultGuiFont;
8933 
8934 			prepareFontInfo();
8935 		} else throw new NotYetImplementedException();
8936 
8937 		return this;
8938 	}
8939 
8940 	///
8941 	bool isNull() {
8942 		version(OSXCocoa) throw new NotYetImplementedException(); else {
8943 			version(with_xft)
8944 				if(isXft)
8945 					return xftFont is null;
8946 			return font is null;
8947 		}
8948 	}
8949 
8950 	/* Metrics */
8951 	/+
8952 		GetABCWidth
8953 		GetKerningPairs
8954 
8955 		if I do it right, I can size it all here, and match
8956 		what happens when I draw the full string with the OS functions.
8957 
8958 		subclasses might do the same thing while getting the glyphs on images
8959 	struct GlyphInfo {
8960 		int glyph;
8961 
8962 		size_t stringIdxStart;
8963 		size_t stringIdxEnd;
8964 
8965 		Rectangle boundingBox;
8966 	}
8967 	GlyphInfo[] getCharBoxes() {
8968 		// XftTextExtentsUtf8
8969 		return null;
8970 
8971 	}
8972 	+/
8973 
8974 	~this() {
8975 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
8976 		unload();
8977 	}
8978 }
8979 
8980 version(Windows)
8981 private string sliceCString(const(wchar)[] w) {
8982 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
8983 }
8984 
8985 private inout(char)[] sliceCString(inout(char)* s) {
8986 	import core.stdc.string;
8987 	auto len = strlen(s);
8988 	return s[0 .. len];
8989 }
8990 
8991 /**
8992 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
8993 	than constructing it directly. Then, it is reference counted so you can pass it
8994 	at around and when the last ref goes out of scope, the buffered drawing activities
8995 	are all carried out.
8996 
8997 
8998 	Most functions use the outlineColor instead of taking a color themselves.
8999 	ScreenPainter is reference counted and draws its buffer to the screen when its
9000 	final reference goes out of scope.
9001 */
9002 struct ScreenPainter {
9003 	CapableOfBeingDrawnUpon window;
9004 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) {
9005 		this.window = window;
9006 		if(window.closed)
9007 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9008 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9009 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9010 		if(window.activeScreenPainter !is null) {
9011 			impl = window.activeScreenPainter;
9012 			if(impl.referenceCount == 0) {
9013 				impl.window = window;
9014 				impl.create(handle);
9015 			}
9016 			impl.manualInvalidations = manualInvalidations;
9017 			impl.referenceCount++;
9018 		//	writeln("refcount ++ ", impl.referenceCount);
9019 		} else {
9020 			impl = new ScreenPainterImplementation;
9021 			impl.window = window;
9022 			impl.create(handle);
9023 			impl.referenceCount = 1;
9024 			impl.manualInvalidations = manualInvalidations;
9025 			window.activeScreenPainter = impl;
9026 			//import std.stdio; writeln("constructed");
9027 		}
9028 
9029 		copyActiveOriginals();
9030 	}
9031 
9032 	/++
9033 		EXPERIMENTAL. subject to change.
9034 
9035 		When you draw a cursor, you can draw this to notify your window of where it is,
9036 		for IME systems to use.
9037 	+/
9038 	void notifyCursorPosition(int x, int y, int width, int height) {
9039 		if(auto w = cast(SimpleWindow) window) {
9040 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9041 		}
9042 	}
9043 
9044 	/++
9045 		If you are using manual invalidations, this informs the
9046 		window system that a section needs to be redrawn.
9047 
9048 		If you didn't opt into manual invalidation, you don't
9049 		have to call this.
9050 
9051 		History:
9052 			Added December 30, 2021 (dub v10.5)
9053 	+/
9054 	void invalidateRect(Rectangle rect) {
9055 		if(impl is null) return;
9056 
9057 		// transform(rect)
9058 		rect.left += _originX;
9059 		rect.right += _originX;
9060 		rect.top += _originY;
9061 		rect.bottom += _originY;
9062 
9063 		impl.invalidateRect(rect);
9064 	}
9065 
9066 	private Pen originalPen;
9067 	private Color originalFillColor;
9068 	private arsd.color.Rectangle originalClipRectangle;
9069 	void copyActiveOriginals() {
9070 		if(impl is null) return;
9071 		originalPen = impl._activePen;
9072 		originalFillColor = impl._fillColor;
9073 		originalClipRectangle = impl._clipRectangle;
9074 	}
9075 
9076 	~this() {
9077 		if(impl is null) return;
9078 		impl.referenceCount--;
9079 		//writeln("refcount -- ", impl.referenceCount);
9080 		if(impl.referenceCount == 0) {
9081 			//import std.stdio; writeln("destructed");
9082 			impl.dispose();
9083 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9084 			//import std.stdio; writeln("paint finished");
9085 		} else {
9086 			// there is still an active reference, reset stuff so the
9087 			// next user doesn't get weirdness via the reference
9088 			this.rasterOp = RasterOp.normal;
9089 			pen = originalPen;
9090 			fillColor = originalFillColor;
9091 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9092 		}
9093 	}
9094 
9095 	this(this) {
9096 		if(impl is null) return;
9097 		impl.referenceCount++;
9098 		//writeln("refcount ++ ", impl.referenceCount);
9099 
9100 		copyActiveOriginals();
9101 	}
9102 
9103 	private int _originX;
9104 	private int _originY;
9105 	@property int originX() { return _originX; }
9106 	@property int originY() { return _originY; }
9107 	@property int originX(int a) {
9108 		_originX = a;
9109 		return _originX;
9110 	}
9111 	@property int originY(int a) {
9112 		_originY = a;
9113 		return _originY;
9114 	}
9115 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9116 	private void transform(ref Point p) {
9117 		if(impl is null) return;
9118 		p.x += _originX;
9119 		p.y += _originY;
9120 	}
9121 
9122 	// this needs to be checked BEFORE the originX/Y transformation
9123 	private bool isClipped(Point p) {
9124 		return !currentClipRectangle.contains(p);
9125 	}
9126 	private bool isClipped(Point p, int width, int height) {
9127 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9128 	}
9129 	private bool isClipped(Point p, Size s) {
9130 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9131 	}
9132 	private bool isClipped(Point p, Point p2) {
9133 		// need to ensure the end points are actually included inside, so the +1 does that
9134 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9135 	}
9136 
9137 
9138 	/++
9139 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9140 
9141 		Returns:
9142 			The old clip rectangle.
9143 
9144 		History:
9145 			Return value was `void` prior to May 10, 2021.
9146 
9147 	+/
9148 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9149 		if(impl is null) return currentClipRectangle;
9150 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9151 			return currentClipRectangle; // no need to do anything
9152 		auto old = currentClipRectangle;
9153 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9154 		transform(pt);
9155 
9156 		impl.setClipRectangle(pt.x, pt.y, width, height);
9157 
9158 		return old;
9159 	}
9160 
9161 	/// ditto
9162 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9163 		if(impl is null) return currentClipRectangle;
9164 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9165 	}
9166 
9167 	///
9168 	void setFont(OperatingSystemFont font) {
9169 		if(impl is null) return;
9170 		impl.setFont(font);
9171 	}
9172 
9173 	///
9174 	int fontHeight() {
9175 		if(impl is null) return 0;
9176 		return impl.fontHeight();
9177 	}
9178 
9179 	private Pen activePen;
9180 
9181 	///
9182 	@property void pen(Pen p) {
9183 		if(impl is null) return;
9184 		activePen = p;
9185 		impl.pen(p);
9186 	}
9187 
9188 	///
9189 	@scriptable
9190 	@property void outlineColor(Color c) {
9191 		if(impl is null) return;
9192 		if(activePen.color == c)
9193 			return;
9194 		activePen.color = c;
9195 		impl.pen(activePen);
9196 	}
9197 
9198 	///
9199 	@scriptable
9200 	@property void fillColor(Color c) {
9201 		if(impl is null) return;
9202 		impl.fillColor(c);
9203 	}
9204 
9205 	///
9206 	@property void rasterOp(RasterOp op) {
9207 		if(impl is null) return;
9208 		impl.rasterOp(op);
9209 	}
9210 
9211 
9212 	void updateDisplay() {
9213 		// FIXME this should do what the dtor does
9214 	}
9215 
9216 	/// 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)
9217 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9218 		if(impl is null) return;
9219 		if(isClipped(upperLeft, width, height)) return;
9220 		transform(upperLeft);
9221 		version(Windows) {
9222 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9223 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9224 			RECT clip = scroll;
9225 			RECT uncovered;
9226 			HRGN hrgn;
9227 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9228 				throw new Exception("ScrollDC");
9229 
9230 		} else version(X11) {
9231 			// FIXME: clip stuff outside this rectangle
9232 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9233 		} else version(OSXCocoa) {
9234 			throw new NotYetImplementedException();
9235 		} else static assert(0);
9236 	}
9237 
9238 	///
9239 	void clear(Color color = Color.white()) {
9240 		if(impl is null) return;
9241 		fillColor = color;
9242 		outlineColor = color;
9243 		drawRectangle(Point(0, 0), window.width, window.height);
9244 	}
9245 
9246 	/++
9247 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9248 
9249 		Params:
9250 			upperLeft = point on the window where the upper left corner of the image will be drawn
9251 			imageUpperLeft = point on the image to start the slice to draw
9252 			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.
9253 		History:
9254 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9255 	+/
9256 	version(OSXCocoa) {} else // NotYetImplementedException
9257 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9258 		if(impl is null) return;
9259 		if(isClipped(upperLeft, s.width, s.height)) return;
9260 		transform(upperLeft);
9261 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9262 	}
9263 
9264 	///
9265 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9266 		if(impl is null) return;
9267 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9268 		transform(upperLeft);
9269 		if(w == 0 || w > i.width)
9270 			w = i.width;
9271 		if(h == 0 || h > i.height)
9272 			h = i.height;
9273 		if(upperLeftOfImage.x < 0)
9274 			upperLeftOfImage.x = 0;
9275 		if(upperLeftOfImage.y < 0)
9276 			upperLeftOfImage.y = 0;
9277 
9278 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9279 	}
9280 
9281 	///
9282 	Size textSize(in char[] text) {
9283 		if(impl is null) return Size(0, 0);
9284 		return impl.textSize(text);
9285 	}
9286 
9287 	/++
9288 		Draws a string in the window with the set font (see [setFont] to change it).
9289 
9290 		Params:
9291 			upperLeft = the upper left point of the bounding box of the text
9292 			text = the string to draw
9293 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9294 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9295 	+/
9296 	@scriptable
9297 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9298 		if(impl is null) return;
9299 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9300 			if(isClipped(upperLeft, lowerRight)) return;
9301 			transform(lowerRight);
9302 		} else {
9303 			if(isClipped(upperLeft, textSize(text))) return;
9304 		}
9305 		transform(upperLeft);
9306 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9307 	}
9308 
9309 	/++
9310 		Draws text using a custom font.
9311 
9312 		This is still MAJOR work in progress.
9313 
9314 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9315 	+/
9316 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9317 		if(impl is null) return;
9318 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9319 		transform(upperLeft);
9320 		font.drawString(this, upperLeft, text);
9321 	}
9322 
9323 	version(Windows)
9324 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9325 		if(impl is null) return;
9326 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9327 		transform(upperLeft);
9328 
9329 		if(text.length && text[$-1] == '\n')
9330 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9331 
9332 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9333 	}
9334 
9335 	static struct TextDrawingContext {
9336 		Point boundingBoxUpperLeft;
9337 		Point boundingBoxLowerRight;
9338 
9339 		Point currentLocation;
9340 
9341 		Point lastDrewUpperLeft;
9342 		Point lastDrewLowerRight;
9343 
9344 		// how do i do right aligned rich text?
9345 		// i kinda want to do a pre-made drawing then right align
9346 		// draw the whole block.
9347 		//
9348 		// That's exactly the diff: inline vs block stuff.
9349 
9350 		// I need to get coordinates of an inline section out too,
9351 		// not just a bounding box, but a series of bounding boxes
9352 		// should be ok. Consider what's needed to detect a click
9353 		// on a link in the middle of a paragraph breaking a line.
9354 		//
9355 		// Generally, we should be able to get the rectangles of
9356 		// any portion we draw.
9357 		//
9358 		// It also needs to tell what text is left if it overflows
9359 		// out of the box, so we can do stuff like float images around
9360 		// it. It should not attempt to draw a letter that would be
9361 		// clipped.
9362 		//
9363 		// I might also turn off word wrap stuff.
9364 	}
9365 
9366 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9367 		if(impl is null) return;
9368 		// FIXME
9369 	}
9370 
9371 	/// Drawing an individual pixel is slow. Avoid it if possible.
9372 	void drawPixel(Point where) {
9373 		if(impl is null) return;
9374 		if(isClipped(where)) return;
9375 		transform(where);
9376 		impl.drawPixel(where.x, where.y);
9377 	}
9378 
9379 
9380 	/// Draws a pen using the current pen / outlineColor
9381 	@scriptable
9382 	void drawLine(Point starting, Point ending) {
9383 		if(impl is null) return;
9384 		if(isClipped(starting, ending)) return;
9385 		transform(starting);
9386 		transform(ending);
9387 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9388 	}
9389 
9390 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9391 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9392 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9393 	@scriptable
9394 	void drawRectangle(Point upperLeft, int width, int height) {
9395 		if(impl is null) return;
9396 		if(isClipped(upperLeft, width, height)) return;
9397 		transform(upperLeft);
9398 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9399 	}
9400 
9401 	/// ditto
9402 	void drawRectangle(Point upperLeft, Size size) {
9403 		if(impl is null) return;
9404 		if(isClipped(upperLeft, size.width, size.height)) return;
9405 		transform(upperLeft);
9406 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9407 	}
9408 
9409 	/// ditto
9410 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9411 		if(impl is null) return;
9412 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9413 		transform(upperLeft);
9414 		transform(lowerRightInclusive);
9415 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9416 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9417 	}
9418 
9419 	// overload added on May 12, 2021
9420 	/// ditto
9421 	void drawRectangle(Rectangle rect) {
9422 		drawRectangle(rect.upperLeft, rect.size);
9423 	}
9424 
9425 	/// Arguments are the points of the bounding rectangle
9426 	void drawEllipse(Point upperLeft, Point lowerRight) {
9427 		if(impl is null) return;
9428 		if(isClipped(upperLeft, lowerRight)) return;
9429 		transform(upperLeft);
9430 		transform(lowerRight);
9431 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9432 	}
9433 
9434 	/++
9435 		start and finish are units of degrees * 64
9436 
9437 		History:
9438 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9439 
9440 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9441 	+/
9442 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9443 		if(impl is null) return;
9444 		// FIXME: not actually implemented
9445 		if(isClipped(upperLeft, width, height)) return;
9446 		transform(upperLeft);
9447 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9448 	}
9449 
9450 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9451 	void drawCircle(Point upperLeft, int diameter) {
9452 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9453 	}
9454 
9455 	/// .
9456 	void drawPolygon(Point[] vertexes) {
9457 		if(impl is null) return;
9458 		assert(vertexes.length);
9459 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9460 		foreach(ref vertex; vertexes) {
9461 			if(vertex.x < minX)
9462 				minX = vertex.x;
9463 			if(vertex.y < minY)
9464 				minY = vertex.y;
9465 			if(vertex.x > maxX)
9466 				maxX = vertex.x;
9467 			if(vertex.y > maxY)
9468 				maxY = vertex.y;
9469 			transform(vertex);
9470 		}
9471 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9472 		impl.drawPolygon(vertexes);
9473 	}
9474 
9475 	/// ditto
9476 	void drawPolygon(Point[] vertexes...) {
9477 		if(impl is null) return;
9478 		drawPolygon(vertexes);
9479 	}
9480 
9481 
9482 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9483 
9484 	//mixin NativeScreenPainterImplementation!() impl;
9485 
9486 
9487 	// HACK: if I mixin the impl directly, it won't let me override the copy
9488 	// constructor! The linker complains about there being multiple definitions.
9489 	// I'll make the best of it and reference count it though.
9490 	ScreenPainterImplementation* impl;
9491 }
9492 
9493 	// HACK: I need a pointer to the implementation so it's separate
9494 	struct ScreenPainterImplementation {
9495 		CapableOfBeingDrawnUpon window;
9496 		int referenceCount;
9497 		mixin NativeScreenPainterImplementation!();
9498 	}
9499 
9500 // FIXME: i haven't actually tested the sprite class on MS Windows
9501 
9502 /**
9503 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9504 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9505 
9506 
9507 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9508 	though I'm not sure that's ideal and the implementation might change.
9509 
9510 	You create one by giving a window and an image. It optimizes for that window,
9511 	and copies the image into it to use as the initial picture. Creating a sprite
9512 	can be quite slow (especially over a network connection) so you should do it
9513 	as little as possible and just hold on to your sprite handles after making them.
9514 	simpledisplay does try to do its best though, using the XSHM extension if available,
9515 	but you should still write your code as if it will always be slow.
9516 
9517 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9518 	a fast operation - much faster than drawing the Image itself every time.
9519 
9520 	`Sprite` represents a scarce resource which should be freed when you
9521 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9522 	after it has been disposed. If you are unsure about this, don't take chances,
9523 	just let the garbage collector do it for you. But ideally, you can manage its
9524 	lifetime more efficiently.
9525 
9526 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9527 	support alpha blending in its drawing at this time. That might change in the
9528 	future, but if you need alpha blending right now, use OpenGL instead. See
9529 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9530 
9531 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9532 	in by setting the enableAlpha = true in the constructor.
9533 */
9534 version(OSXCocoa) {} else // NotYetImplementedException
9535 class Sprite : CapableOfBeingDrawnUpon {
9536 
9537 	///
9538 	ScreenPainter draw() {
9539 		return ScreenPainter(this, handle, false);
9540 	}
9541 
9542 	/++
9543 		Copies the sprite's current state into a [TrueColorImage].
9544 
9545 		Be warned: this can be a very slow operation
9546 
9547 		History:
9548 			Actually implemented on March 14, 2021
9549 	+/
9550 	TrueColorImage takeScreenshot() {
9551 		return trueColorImageFromNativeHandle(handle, width, height);
9552 	}
9553 
9554 	void delegate() paintingFinishedDg() { return null; }
9555 	bool closed() { return false; }
9556 	ScreenPainterImplementation* activeScreenPainter_;
9557 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9558 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9559 
9560 	version(Windows)
9561 		private ubyte* rawData;
9562 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9563 	// ditto on the XPicture stuff
9564 
9565 	version(X11) {
9566 		private static XRenderPictFormat* RGB24;
9567 		private static XRenderPictFormat* ARGB32;
9568 
9569 		private Picture xrenderPicture;
9570 	}
9571 
9572 	version(X11)
9573 	private static void requireXRender() {
9574 		if(!XRenderLibrary.loadAttempted) {
9575 			XRenderLibrary.loadDynamicLibrary();
9576 		}
9577 
9578 		if(!XRenderLibrary.loadSuccessful)
9579 			throw new Exception("XRender library load failure");
9580 
9581 		auto display = XDisplayConnection.get;
9582 
9583 		// FIXME: if we migrate X displays, these need to be changed
9584 		if(RGB24 is null)
9585 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9586 		if(ARGB32 is null)
9587 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9588 	}
9589 
9590 	protected this() {}
9591 
9592 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9593 		this._width = width;
9594 		this._height = height;
9595 		this.enableAlpha = enableAlpha;
9596 
9597 		version(X11) {
9598 			auto display = XDisplayConnection.get();
9599 
9600 			if(enableAlpha) {
9601 				requireXRender();
9602 			}
9603 
9604 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9605 
9606 			if(enableAlpha) {
9607 				XRenderPictureAttributes attrs;
9608 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9609 			}
9610 		} else version(Windows) {
9611 			version(CRuntime_DigitalMars) {
9612 				//if(enableAlpha)
9613 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9614 			}
9615 
9616 			BITMAPINFO infoheader;
9617 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9618 			infoheader.bmiHeader.biWidth = width;
9619 			infoheader.bmiHeader.biHeight = height;
9620 			infoheader.bmiHeader.biPlanes = 1;
9621 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9622 			infoheader.bmiHeader.biCompression = BI_RGB;
9623 
9624 			// FIXME: this should prolly be a device dependent bitmap...
9625 			handle = CreateDIBSection(
9626 				null,
9627 				&infoheader,
9628 				DIB_RGB_COLORS,
9629 				cast(void**) &rawData,
9630 				null,
9631 				0);
9632 
9633 			if(handle is null)
9634 				throw new Exception("couldn't create pixmap");
9635 		}
9636 	}
9637 
9638 	/// Makes a sprite based on the image with the initial contents from the Image
9639 	this(SimpleWindow win, Image i) {
9640 		this(win, i.width, i.height, i.enableAlpha);
9641 
9642 		version(X11) {
9643 			auto display = XDisplayConnection.get();
9644 			auto gc = XCreateGC(display, this.handle, 0, null);
9645 			scope(exit) XFreeGC(display, gc);
9646 			if(i.usingXshm)
9647 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9648 			else
9649 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9650 		} else version(Windows) {
9651 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9652 			auto arrLength = itemsPerLine * height;
9653 			rawData[0..arrLength] = i.rawData[0..arrLength];
9654 		} else version(OSXCocoa) {
9655 			// FIXME: I have no idea if this is even any good
9656 			ubyte* rawData;
9657 
9658 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9659 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
9660 				colorSpace,
9661 				kCGImageAlphaPremultipliedLast
9662 				|kCGBitmapByteOrder32Big);
9663 			CGColorSpaceRelease(colorSpace);
9664 			rawData = CGBitmapContextGetData(context);
9665 
9666 			auto rdl = (width * height * 4);
9667 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9668 		} else static assert(0);
9669 	}
9670 
9671 	/++
9672 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9673 
9674 		Params:
9675 			where = point on the window where the upper left corner of the image will be drawn
9676 			imageUpperLeft = point on the image to start the slice to draw
9677 			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.
9678 		History:
9679 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9680 	+/
9681 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9682 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9683 	}
9684 
9685 	/// Call this when you're ready to get rid of it
9686 	void dispose() {
9687 		version(X11) {
9688 			staticDispose(xrenderPicture, handle);
9689 			xrenderPicture = None;
9690 			handle = None;
9691 		} else version(Windows) {
9692 			staticDispose(handle);
9693 			handle = null;
9694 		} else version(OSXCocoa) {
9695 			staticDispose(context);
9696 			context = null;
9697 		} else static assert(0);
9698 
9699 	}
9700 
9701 	version(X11)
9702 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9703 		if(xrenderPicture)
9704 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9705 		if(handle)
9706 			XFreePixmap(XDisplayConnection.get(), handle);
9707 	}
9708 	else version(Windows)
9709 	static void staticDispose(HBITMAP handle) {
9710 		if(handle)
9711 			DeleteObject(handle);
9712 	}
9713 	else version(OSXCocoa)
9714 	static void staticDispose(CGContextRef context) {
9715 		if(context)
9716 			CGContextRelease(context);
9717 	}
9718 
9719 	~this() {
9720 		version(X11) { if(xrenderPicture || handle)
9721 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9722 		} else version(Windows) { if(handle)
9723 			cleanupQueue.queue!staticDispose(handle);
9724 		} else version(OSXCocoa) { if(context)
9725 			cleanupQueue.queue!staticDispose(context);
9726 		} else static assert(0);
9727 	}
9728 
9729 	///
9730 	final @property int width() { return _width; }
9731 
9732 	///
9733 	final @property int height() { return _height; }
9734 
9735 	///
9736 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9737 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9738 	}
9739 
9740 	auto nativeHandle() {
9741 		return handle;
9742 	}
9743 
9744 	private:
9745 
9746 	int _width;
9747 	int _height;
9748 	bool enableAlpha;
9749 	version(X11)
9750 		Pixmap handle;
9751 	else version(Windows)
9752 		HBITMAP handle;
9753 	else version(OSXCocoa)
9754 		CGContextRef context;
9755 	else static assert(0);
9756 }
9757 
9758 /++
9759 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9760 
9761 	History:
9762 		Added November 20, 2021 (dub v10.4)
9763 +/
9764 abstract class Gradient : Sprite {
9765 	protected this(int w, int h) {
9766 		version(X11) {
9767 			Sprite.requireXRender();
9768 
9769 			super();
9770 			enableAlpha = true;
9771 			_width = w;
9772 			_height = h;
9773 		} else version(Windows) {
9774 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9775 		}
9776 	}
9777 
9778 	version(Windows)
9779 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9780 		auto ptr = rawData;
9781 		foreach(j; 0 .. _height)
9782 		foreach(i; 0 .. _width) {
9783 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9784 			*rawData = (color.a * color.b) / 255; rawData++;
9785 			*rawData = (color.a * color.g) / 255; rawData++;
9786 			*rawData = (color.a * color.r) / 255; rawData++;
9787 			*rawData = color.a; rawData++;
9788 		}
9789 	}
9790 
9791 	version(X11)
9792 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9793 		assert(stops.length > 0);
9794 		assert(stops.length <= 16, "I got lazy with buffers");
9795 
9796 		XFixed[16] stopsPositions = void;
9797 		XRenderColor[16] colors = void;
9798 
9799 		foreach(idx, stop; stops) {
9800 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9801 			auto c = stop.c;
9802 			colors[idx] = XRenderColor(
9803 				cast(ushort)(c.r * ushort.max / 255),
9804 				cast(ushort)(c.g * ushort.max / 255),
9805 				cast(ushort)(c.b * ushort.max / 255),
9806 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9807 			);
9808 		}
9809 
9810 		xrenderPicture = dg(stopsPositions, colors);
9811 	}
9812 
9813 	///
9814 	static struct Stop {
9815 		float percentage; /// between 0 and 1.0
9816 		Color c;
9817 	}
9818 }
9819 
9820 /++
9821 	Creates a linear gradient between p1 and p2.
9822 
9823 	X ONLY RIGHT NOW
9824 
9825 	History:
9826 		Added November 20, 2021 (dub v10.4)
9827 
9828 	Bugs:
9829 		Not yet implemented on Windows.
9830 +/
9831 class LinearGradient : Gradient {
9832 	/++
9833 
9834 	+/
9835 	this(Point p1, Point p2, Stop[] stops...) {
9836 		super(p2.x, p2.y);
9837 
9838 		version(X11) {
9839 			XLinearGradient gradient;
9840 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9841 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9842 
9843 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9844 				return XRenderCreateLinearGradient(
9845 					XDisplayConnection.get,
9846 					&gradient,
9847 					stopsPositions.ptr,
9848 					colors.ptr,
9849 					cast(int) stops.length);
9850 			});
9851 		} else version(Windows) {
9852 			// FIXME
9853 			forEachPixel((int x, int y) {
9854 				import core.stdc.math;
9855 
9856 				//sqrtf(
9857 
9858 				return Color.transparent;
9859 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9860 			});
9861 		}
9862 	}
9863 }
9864 
9865 /++
9866 	A conical gradient goes from color to color around a circumference from a center point.
9867 
9868 	X ONLY RIGHT NOW
9869 
9870 	History:
9871 		Added November 20, 2021 (dub v10.4)
9872 
9873 	Bugs:
9874 		Not yet implemented on Windows.
9875 +/
9876 class ConicalGradient : Gradient {
9877 	/++
9878 
9879 	+/
9880 	this(Point center, float angleInDegrees, Stop[] stops...) {
9881 		super(center.x * 2, center.y * 2);
9882 
9883 		version(X11) {
9884 			XConicalGradient gradient;
9885 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9886 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9887 
9888 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9889 				return XRenderCreateConicalGradient(
9890 					XDisplayConnection.get,
9891 					&gradient,
9892 					stopsPositions.ptr,
9893 					colors.ptr,
9894 					cast(int) stops.length);
9895 			});
9896 		} else version(Windows) {
9897 			// FIXME
9898 			forEachPixel((int x, int y) {
9899 				import core.stdc.math;
9900 
9901 				//sqrtf(
9902 
9903 				return Color.transparent;
9904 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9905 			});
9906 
9907 		}
9908 	}
9909 }
9910 
9911 /++
9912 	A radial gradient goes from color to color based on distance from the center.
9913 	It is like rings of color.
9914 
9915 	X ONLY RIGHT NOW
9916 
9917 
9918 	More specifically, you create two circles: an inner circle and an outer circle.
9919 	The gradient is only drawn in the area outside the inner circle but inside the outer
9920 	circle. The closest line between those two circles forms the line for the gradient
9921 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9922 
9923 	History:
9924 		Added November 20, 2021 (dub v10.4)
9925 
9926 	Bugs:
9927 		Not yet implemented on Windows.
9928 +/
9929 class RadialGradient : Gradient {
9930 	/++
9931 
9932 	+/
9933 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
9934 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
9935 
9936 		version(X11) {
9937 			XRadialGradient gradient;
9938 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
9939 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
9940 
9941 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9942 				return XRenderCreateRadialGradient(
9943 					XDisplayConnection.get,
9944 					&gradient,
9945 					stopsPositions.ptr,
9946 					colors.ptr,
9947 					cast(int) stops.length);
9948 			});
9949 		} else version(Windows) {
9950 			// FIXME
9951 			forEachPixel((int x, int y) {
9952 				import core.stdc.math;
9953 
9954 				//sqrtf(
9955 
9956 				return Color.transparent;
9957 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9958 			});
9959 		}
9960 	}
9961 }
9962 
9963 
9964 
9965 /+
9966 	NOT IMPLEMENTED
9967 
9968 	A display-stored image optimized for relatively quick drawing, like
9969 	[Sprite], but this one supports alpha channel blending and does NOT
9970 	support direct drawing upon it with a [ScreenPainter].
9971 
9972 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
9973 	plain [ScreenPainter]... sort of.
9974 
9975 	On X11, it requires the Xrender extension and library. This is available
9976 	almost everywhere though.
9977 
9978 	History:
9979 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
9980 +/
9981 version(none)
9982 class AlphaSprite {
9983 	/++
9984 		Copies the given image into it.
9985 	+/
9986 	this(MemoryImage img) {
9987 
9988 		if(!XRenderLibrary.loadAttempted) {
9989 			XRenderLibrary.loadDynamicLibrary();
9990 
9991 			// FIXME: this needs to be reconstructed when the X server changes
9992 			repopulateX();
9993 		}
9994 		if(!XRenderLibrary.loadSuccessful)
9995 			throw new Exception("XRender library load failure");
9996 
9997 		// I probably need to put the alpha mask in a separate Picture
9998 		// ugh
9999 		// maybe the Sprite itself can have an alpha bitmask anyway
10000 
10001 
10002 		auto display = XDisplayConnection.get();
10003 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10004 
10005 
10006 		XRenderPictureAttributes attrs;
10007 
10008 		handle = XRenderCreatePicture(
10009 			XDisplayConnection.get,
10010 			pixmap,
10011 			RGBA,
10012 			0,
10013 			&attrs
10014 		);
10015 
10016 	}
10017 
10018 	// maybe i'll use the create gradient functions too with static factories..
10019 
10020 	void drawAt(ScreenPainter painter, Point where) {
10021 		//painter.drawPixmap(this, where);
10022 
10023 		XRenderPictureAttributes attrs;
10024 
10025 		auto pic = XRenderCreatePicture(
10026 			XDisplayConnection.get,
10027 			painter.impl.d,
10028 			RGB,
10029 			0,
10030 			&attrs
10031 		);
10032 
10033 		XRenderComposite(
10034 			XDisplayConnection.get,
10035 			3, // PictOpOver
10036 			handle,
10037 			None,
10038 			pic,
10039 			0, // src
10040 			0,
10041 			0, // mask
10042 			0,
10043 			10, // dest
10044 			10,
10045 			100, // width
10046 			100
10047 		);
10048 
10049 		/+
10050 		XRenderFreePicture(
10051 			XDisplayConnection.get,
10052 			pic
10053 		);
10054 
10055 		XRenderFreePicture(
10056 			XDisplayConnection.get,
10057 			fill
10058 		);
10059 		+/
10060 		// on Windows you can stretch but Xrender still can't :(
10061 	}
10062 
10063 	static XRenderPictFormat* RGB;
10064 	static XRenderPictFormat* RGBA;
10065 	static void repopulateX() {
10066 		auto display = XDisplayConnection.get;
10067 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10068 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10069 	}
10070 
10071 	XPixmap pixmap;
10072 	Picture handle;
10073 }
10074 
10075 ///
10076 interface CapableOfBeingDrawnUpon {
10077 	///
10078 	ScreenPainter draw();
10079 	///
10080 	int width();
10081 	///
10082 	int height();
10083 	protected ScreenPainterImplementation* activeScreenPainter();
10084 	protected void activeScreenPainter(ScreenPainterImplementation*);
10085 	bool closed();
10086 
10087 	void delegate() paintingFinishedDg();
10088 
10089 	/// Be warned: this can be a very slow operation
10090 	TrueColorImage takeScreenshot();
10091 }
10092 
10093 /// 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].
10094 void flushGui() {
10095 	version(X11) {
10096 		auto dpy = XDisplayConnection.get();
10097 		XLockDisplay(dpy);
10098 		scope(exit) XUnlockDisplay(dpy);
10099 		XFlush(dpy);
10100 	}
10101 }
10102 
10103 /++
10104 	Runs the given code in the GUI thread when its event loop
10105 	is available, blocking until it completes. This allows you
10106 	to create and manipulate windows from another thread without
10107 	invoking undefined behavior.
10108 
10109 	If this is the gui thread, it runs the code immediately.
10110 
10111 	If no gui thread exists yet, the current thread is assumed
10112 	to be it. Attempting to create windows or run the event loop
10113 	in any other thread will cause an assertion failure.
10114 
10115 
10116 	$(TIP
10117 		Did you know you can use UFCS on delegate literals?
10118 
10119 		() {
10120 			// code here
10121 		}.runInGuiThread;
10122 	)
10123 
10124 	Returns:
10125 		`true` if the function was called, `false` if it was not.
10126 		The function may not be called because the gui thread had
10127 		already terminated by the time you called this.
10128 
10129 	History:
10130 		Added April 10, 2020 (v7.2.0)
10131 
10132 		Return value added and implementation tweaked to avoid locking
10133 		at program termination on February 24, 2021 (v9.2.1).
10134 +/
10135 bool runInGuiThread(scope void delegate() dg) @trusted {
10136 	claimGuiThread();
10137 
10138 	if(thisIsGuiThread) {
10139 		dg();
10140 		return true;
10141 	}
10142 
10143 	if(guiThreadTerminating)
10144 		return false;
10145 
10146 	import core.sync.semaphore;
10147 	static Semaphore sc;
10148 	if(sc is null)
10149 		sc = new Semaphore();
10150 
10151 	static RunQueueMember* rqm;
10152 	if(rqm is null)
10153 		rqm = new RunQueueMember;
10154 	rqm.dg = cast(typeof(rqm.dg)) dg;
10155 	rqm.signal = sc;
10156 	rqm.thrown = null;
10157 
10158 	synchronized(runInGuiThreadLock) {
10159 		runInGuiThreadQueue ~= rqm;
10160 	}
10161 
10162 	if(!SimpleWindow.eventWakeUp())
10163 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10164 
10165 	rqm.signal.wait();
10166 	auto t = rqm.thrown;
10167 
10168 	if(t)
10169 		throw t;
10170 
10171 	return true;
10172 }
10173 
10174 // note it runs sync if this is the gui thread....
10175 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10176 	claimGuiThread();
10177 
10178 	try {
10179 
10180 		if(thisIsGuiThread) {
10181 			dg();
10182 			return;
10183 		}
10184 
10185 		if(guiThreadTerminating)
10186 			return;
10187 
10188 		RunQueueMember* rqm = new RunQueueMember;
10189 		rqm.dg = cast(typeof(rqm.dg)) dg;
10190 		rqm.signal = null;
10191 		rqm.thrown = null;
10192 
10193 		synchronized(runInGuiThreadLock) {
10194 			runInGuiThreadQueue ~= rqm;
10195 		}
10196 
10197 		if(!SimpleWindow.eventWakeUp())
10198 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10199 	} catch(Exception e) {
10200 		if(handleError)
10201 			handleError(e);
10202 	}
10203 }
10204 
10205 private void runPendingRunInGuiThreadDelegates() {
10206 	more:
10207 	RunQueueMember* next;
10208 	synchronized(runInGuiThreadLock) {
10209 		if(runInGuiThreadQueue.length) {
10210 			next = runInGuiThreadQueue[0];
10211 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10212 		} else {
10213 			next = null;
10214 		}
10215 	}
10216 
10217 	if(next) {
10218 		try {
10219 			next.dg();
10220 			next.thrown = null;
10221 		} catch(Throwable t) {
10222 			next.thrown = t;
10223 		}
10224 
10225 		if(next.signal)
10226 			next.signal.notify();
10227 
10228 		goto more;
10229 	}
10230 }
10231 
10232 private void claimGuiThread() nothrow {
10233 	import core.atomic;
10234 	if(cas(&guiThreadExists_, false, true))
10235 		thisIsGuiThread = true;
10236 }
10237 
10238 private struct RunQueueMember {
10239 	void delegate() dg;
10240 	import core.sync.semaphore;
10241 	Semaphore signal;
10242 	Throwable thrown;
10243 }
10244 
10245 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10246 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10247 private bool thisIsGuiThread = false;
10248 private shared bool guiThreadExists_ = false;
10249 private shared bool guiThreadTerminating = false;
10250 
10251 /++
10252 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10253 	event loop. All windows must be exclusively created and managed by a single thread.
10254 
10255 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10256 	when you call one of its constructors.
10257 
10258 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10259 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10260 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10261 
10262 	The reason this function is available is in case you want to message pass between a gui
10263 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10264 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10265 
10266 	History:
10267 		Added December 3, 2021 (dub v10.5)
10268 +/
10269 public bool guiThreadExists() {
10270 	return guiThreadExists_;
10271 }
10272 
10273 /++
10274 	Returns `true` if this thread is either running or set to be running the
10275 	simpledisplay.d gui core event loop because it owns windows.
10276 
10277 	It is important to keep gui-related functionality in the right thread, so you will
10278 	want to `runInGuiThread` when you call them (with some specific exceptions called
10279 	out in those specific functions' documentation). Notably, all windows must be
10280 	created and managed only from the gui thread.
10281 
10282 	Will return false if simpledisplay's other functions haven't been called
10283 	yet; check [guiThreadExists] in addition to this.
10284 
10285 	History:
10286 		Added December 3, 2021 (dub v10.5)
10287 +/
10288 public bool thisThreadRunningGui() {
10289 	return thisIsGuiThread;
10290 }
10291 
10292 /++
10293 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10294 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10295 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10296 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10297 	file instead if you are in one of those situations).
10298 
10299 	It does not support outputting very many types; just strings and ints are likely to actually work.
10300 
10301 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10302 	is unspecified meaning I can change it at any time. The only point of this function is to help
10303 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10304 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10305 	in those contexts.
10306 
10307 	$(WARNING
10308 		I reserve the right to change this function at any time. You can use it if it helps you
10309 		but do not rely on it for anything permanent.
10310 	)
10311 
10312 	History:
10313 		Added December 3, 2021. Not formally supported under any stable tag.
10314 +/
10315 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10316 	try {
10317 		version(Windows) {
10318 			import core.sys.windows.wincon;
10319 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10320 				AllocConsole();
10321 			const(char)* fn = "CONOUT$";
10322 		} else version(Posix) {
10323 			const(char)* fn = "/dev/tty";
10324 		} else static assert(0, "Function not implemented for your system");
10325 
10326 		if(fileOverride.length)
10327 			fn = fileOverride.ptr;
10328 
10329 		import core.stdc.stdio;
10330 		auto fp = fopen(fn, "wt");
10331 		if(fp is null) return;
10332 		scope(exit) fclose(fp);
10333 
10334 		string str;
10335 		foreach(item; t) {
10336 			static if(is(typeof(item) : const(char)[]))
10337 				str ~= item;
10338 			else
10339 				str ~= toInternal!string(item);
10340 			str ~= " ";
10341 		}
10342 		str ~= "\n";
10343 
10344 		fwrite(str.ptr, 1, str.length, fp);
10345 		fflush(fp);
10346 	} catch(Exception e) {
10347 		// sorry no hope
10348 	}
10349 }
10350 
10351 private void guiThreadFinalize() {
10352 	assert(thisIsGuiThread);
10353 
10354 	guiThreadTerminating = true; // don't add any more from this point on
10355 	runPendingRunInGuiThreadDelegates();
10356 }
10357 
10358 /+
10359 interface IPromise {
10360 	void reportProgress(int current, int max, string message);
10361 
10362 	/+ // not formally in cuz of templates but still
10363 	IPromise Then();
10364 	IPromise Catch();
10365 	IPromise Finally();
10366 	+/
10367 }
10368 
10369 /+
10370 	auto promise = async({ ... });
10371 	promise.Then(whatever).
10372 		Then(whateverelse).
10373 		Catch((exception) { });
10374 
10375 
10376 	A promise is run inside a fiber and it looks something like:
10377 
10378 	try {
10379 		auto res = whatever();
10380 		auto res2 = whateverelse(res);
10381 	} catch(Exception e) {
10382 		{ }(e);
10383 	}
10384 
10385 	When a thing succeeds, it is passed as an arg to the next
10386 +/
10387 class Promise(T) : IPromise {
10388 	auto Then() { return null; }
10389 	auto Catch() { return null; }
10390 	auto Finally() { return null; }
10391 
10392 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10393 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10394 	T await();
10395 }
10396 
10397 interface Task {
10398 }
10399 
10400 interface Resolvable(T) : Task {
10401 	void run();
10402 
10403 	void resolve(T);
10404 
10405 	Resolvable!T then(void delegate(T)); // returns a new promise
10406 	Resolvable!T error(Throwable); // js catch
10407 	Resolvable!T completed(); // js finally
10408 
10409 }
10410 
10411 /++
10412 	Runs `work` in a helper thread and sends its return value back to the main gui
10413 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10414 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10415 	kill the program.
10416 
10417 	You can call reportProgress(position, max, message) to update your parent window
10418 	on your progress.
10419 
10420 	I should also use `shared` methods. FIXME
10421 
10422 	History:
10423 		Added March 6, 2021 (dub version 9.3).
10424 +/
10425 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10426 	uponCompletion(work(null));
10427 }
10428 
10429 +/
10430 
10431 /// Used internal to dispatch events to various classes.
10432 interface CapableOfHandlingNativeEvent {
10433 	NativeEventHandler getNativeEventHandler();
10434 
10435 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10436 
10437 	version(X11) {
10438 		// if this is impossible, you are allowed to just throw from it
10439 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10440 		void recreateAfterDisconnect();
10441 		// discard any *connection specific* state, but keep enough that you
10442 		// can be recreated if possible. discardConnectionState() is always called immediately
10443 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10444 		// you need initialization order
10445 		void discardConnectionState();
10446 	}
10447 }
10448 
10449 version(X11)
10450 /++
10451 	State of keys on mouse events, especially motion.
10452 
10453 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10454 +/
10455 enum ModifierState : uint {
10456 	shift = 1, ///
10457 	capsLock = 2, ///
10458 	ctrl = 4, ///
10459 	alt = 8, /// Not always available on Windows
10460 	windows = 64, /// ditto
10461 	numLock = 16, ///
10462 
10463 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10464 	middleButtonDown = 512, /// ditto
10465 	rightButtonDown = 1024, /// ditto
10466 }
10467 else version(Windows)
10468 /// ditto
10469 enum ModifierState : uint {
10470 	shift = 4, ///
10471 	ctrl = 8, ///
10472 
10473 	// i'm not sure if the next two are available
10474 	alt = 256, /// not always available on Windows
10475 	windows = 512, /// ditto
10476 
10477 	capsLock = 1024, ///
10478 	numLock = 2048, ///
10479 
10480 	leftButtonDown = 1, /// not available on key events
10481 	middleButtonDown = 16, /// ditto
10482 	rightButtonDown = 2, /// ditto
10483 
10484 	backButtonDown = 0x20, /// not available on X
10485 	forwardButtonDown = 0x40, /// ditto
10486 }
10487 else version(OSXCocoa)
10488 // FIXME FIXME NotYetImplementedException
10489 enum ModifierState : uint {
10490 	shift = 1, ///
10491 	capsLock = 2, ///
10492 	ctrl = 4, ///
10493 	alt = 8, /// Not always available on Windows
10494 	windows = 64, /// ditto
10495 	numLock = 16, ///
10496 
10497 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10498 	middleButtonDown = 512, /// ditto
10499 	rightButtonDown = 1024, /// ditto
10500 }
10501 
10502 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10503 enum MouseButton : int {
10504 	none = 0,
10505 	left = 1, ///
10506 	right = 2, ///
10507 	middle = 4, ///
10508 	wheelUp = 8, ///
10509 	wheelDown = 16, ///
10510 	backButton = 32, /// often found on the thumb and used for back in browsers
10511 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10512 }
10513 
10514 version(X11) {
10515 	// FIXME: match ASCII whenever we can. Most of it is already there,
10516 	// but there's a few exceptions and mismatches with Windows
10517 
10518 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10519 	enum Key {
10520 		Escape = 0xff1b, ///
10521 		F1 = 0xffbe, ///
10522 		F2 = 0xffbf, ///
10523 		F3 = 0xffc0, ///
10524 		F4 = 0xffc1, ///
10525 		F5 = 0xffc2, ///
10526 		F6 = 0xffc3, ///
10527 		F7 = 0xffc4, ///
10528 		F8 = 0xffc5, ///
10529 		F9 = 0xffc6, ///
10530 		F10 = 0xffc7, ///
10531 		F11 = 0xffc8, ///
10532 		F12 = 0xffc9, ///
10533 		PrintScreen = 0xff61, ///
10534 		ScrollLock = 0xff14, ///
10535 		Pause = 0xff13, ///
10536 		Grave = 0x60, /// The $(BACKTICK) ~ key
10537 		// number keys across the top of the keyboard
10538 		N1 = 0x31, /// Number key atop the keyboard
10539 		N2 = 0x32, ///
10540 		N3 = 0x33, ///
10541 		N4 = 0x34, ///
10542 		N5 = 0x35, ///
10543 		N6 = 0x36, ///
10544 		N7 = 0x37, ///
10545 		N8 = 0x38, ///
10546 		N9 = 0x39, ///
10547 		N0 = 0x30, ///
10548 		Dash = 0x2d, ///
10549 		Equals = 0x3d, ///
10550 		Backslash = 0x5c, /// The \ | key
10551 		Backspace = 0xff08, ///
10552 		Insert = 0xff63, ///
10553 		Home = 0xff50, ///
10554 		PageUp = 0xff55, ///
10555 		Delete = 0xffff, ///
10556 		End = 0xff57, ///
10557 		PageDown = 0xff56, ///
10558 		Up = 0xff52, ///
10559 		Down = 0xff54, ///
10560 		Left = 0xff51, ///
10561 		Right = 0xff53, ///
10562 
10563 		Tab = 0xff09, ///
10564 		Q = 0x71, ///
10565 		W = 0x77, ///
10566 		E = 0x65, ///
10567 		R = 0x72, ///
10568 		T = 0x74, ///
10569 		Y = 0x79, ///
10570 		U = 0x75, ///
10571 		I = 0x69, ///
10572 		O = 0x6f, ///
10573 		P = 0x70, ///
10574 		LeftBracket = 0x5b, /// the [ { key
10575 		RightBracket = 0x5d, /// the ] } key
10576 		CapsLock = 0xffe5, ///
10577 		A = 0x61, ///
10578 		S = 0x73, ///
10579 		D = 0x64, ///
10580 		F = 0x66, ///
10581 		G = 0x67, ///
10582 		H = 0x68, ///
10583 		J = 0x6a, ///
10584 		K = 0x6b, ///
10585 		L = 0x6c, ///
10586 		Semicolon = 0x3b, ///
10587 		Apostrophe = 0x27, ///
10588 		Enter = 0xff0d, ///
10589 		Shift = 0xffe1, ///
10590 		Z = 0x7a, ///
10591 		X = 0x78, ///
10592 		C = 0x63, ///
10593 		V = 0x76, ///
10594 		B = 0x62, ///
10595 		N = 0x6e, ///
10596 		M = 0x6d, ///
10597 		Comma = 0x2c, ///
10598 		Period = 0x2e, ///
10599 		Slash = 0x2f, /// the / ? key
10600 		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
10601 		Ctrl = 0xffe3, ///
10602 		Windows = 0xffeb, ///
10603 		Alt = 0xffe9, ///
10604 		Space = 0x20, ///
10605 		Alt_r = 0xffea, /// ditto of shift_r
10606 		Windows_r = 0xffec, ///
10607 		Menu = 0xff67, ///
10608 		Ctrl_r = 0xffe4, ///
10609 
10610 		NumLock = 0xff7f, ///
10611 		Divide = 0xffaf, /// The / key on the number pad
10612 		Multiply = 0xffaa, /// The * key on the number pad
10613 		Minus = 0xffad, /// The - key on the number pad
10614 		Plus = 0xffab, /// The + key on the number pad
10615 		PadEnter = 0xff8d, /// Numberpad enter key
10616 		Pad1 = 0xff9c, /// Numberpad keys
10617 		Pad2 = 0xff99, ///
10618 		Pad3 = 0xff9b, ///
10619 		Pad4 = 0xff96, ///
10620 		Pad5 = 0xff9d, ///
10621 		Pad6 = 0xff98, ///
10622 		Pad7 = 0xff95, ///
10623 		Pad8 = 0xff97, ///
10624 		Pad9 = 0xff9a, ///
10625 		Pad0 = 0xff9e, ///
10626 		PadDot = 0xff9f, ///
10627 	}
10628 } else version(Windows) {
10629 	// the character here is for en-us layouts and for illustration only
10630 	// if you actually want to get characters, wait for character events
10631 	// (the argument to your event handler is simply a dchar)
10632 	// those will be converted by the OS for the right locale.
10633 
10634 	enum Key {
10635 		Escape = 0x1b,
10636 		F1 = 0x70,
10637 		F2 = 0x71,
10638 		F3 = 0x72,
10639 		F4 = 0x73,
10640 		F5 = 0x74,
10641 		F6 = 0x75,
10642 		F7 = 0x76,
10643 		F8 = 0x77,
10644 		F9 = 0x78,
10645 		F10 = 0x79,
10646 		F11 = 0x7a,
10647 		F12 = 0x7b,
10648 		PrintScreen = 0x2c,
10649 		ScrollLock = 0x91,
10650 		Pause = 0x13,
10651 		Grave = 0xc0,
10652 		// number keys across the top of the keyboard
10653 		N1 = 0x31,
10654 		N2 = 0x32,
10655 		N3 = 0x33,
10656 		N4 = 0x34,
10657 		N5 = 0x35,
10658 		N6 = 0x36,
10659 		N7 = 0x37,
10660 		N8 = 0x38,
10661 		N9 = 0x39,
10662 		N0 = 0x30,
10663 		Dash = 0xbd,
10664 		Equals = 0xbb,
10665 		Backslash = 0xdc,
10666 		Backspace = 0x08,
10667 		Insert = 0x2d,
10668 		Home = 0x24,
10669 		PageUp = 0x21,
10670 		Delete = 0x2e,
10671 		End = 0x23,
10672 		PageDown = 0x22,
10673 		Up = 0x26,
10674 		Down = 0x28,
10675 		Left = 0x25,
10676 		Right = 0x27,
10677 
10678 		Tab = 0x09,
10679 		Q = 0x51,
10680 		W = 0x57,
10681 		E = 0x45,
10682 		R = 0x52,
10683 		T = 0x54,
10684 		Y = 0x59,
10685 		U = 0x55,
10686 		I = 0x49,
10687 		O = 0x4f,
10688 		P = 0x50,
10689 		LeftBracket = 0xdb,
10690 		RightBracket = 0xdd,
10691 		CapsLock = 0x14,
10692 		A = 0x41,
10693 		S = 0x53,
10694 		D = 0x44,
10695 		F = 0x46,
10696 		G = 0x47,
10697 		H = 0x48,
10698 		J = 0x4a,
10699 		K = 0x4b,
10700 		L = 0x4c,
10701 		Semicolon = 0xba,
10702 		Apostrophe = 0xde,
10703 		Enter = 0x0d,
10704 		Shift = 0x10,
10705 		Z = 0x5a,
10706 		X = 0x58,
10707 		C = 0x43,
10708 		V = 0x56,
10709 		B = 0x42,
10710 		N = 0x4e,
10711 		M = 0x4d,
10712 		Comma = 0xbc,
10713 		Period = 0xbe,
10714 		Slash = 0xbf,
10715 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10716 		Ctrl = 0x11,
10717 		Windows = 0x5b,
10718 		Alt = -5, // FIXME
10719 		Space = 0x20,
10720 		Alt_r = 0xffea, // ditto of shift_r
10721 		Windows_r = 0x5c, // ditto of shift_r
10722 		Menu = 0x5d,
10723 		Ctrl_r = 0xa3, // ditto of shift_r
10724 
10725 		NumLock = 0x90,
10726 		Divide = 0x6f,
10727 		Multiply = 0x6a,
10728 		Minus = 0x6d,
10729 		Plus = 0x6b,
10730 		PadEnter = -8, // FIXME
10731 		Pad1 = 0x61,
10732 		Pad2 = 0x62,
10733 		Pad3 = 0x63,
10734 		Pad4 = 0x64,
10735 		Pad5 = 0x65,
10736 		Pad6 = 0x66,
10737 		Pad7 = 0x67,
10738 		Pad8 = 0x68,
10739 		Pad9 = 0x69,
10740 		Pad0 = 0x60,
10741 		PadDot = 0x6e,
10742 	}
10743 
10744 	// I'm keeping this around for reference purposes
10745 	// ideally all these buttons will be listed for all platforms,
10746 	// but now now I'm just focusing on my US keyboard
10747 	version(none)
10748 	enum Key {
10749 		LBUTTON = 0x01,
10750 		RBUTTON = 0x02,
10751 		CANCEL = 0x03,
10752 		MBUTTON = 0x04,
10753 		//static if (_WIN32_WINNT > =  0x500) {
10754 		XBUTTON1 = 0x05,
10755 		XBUTTON2 = 0x06,
10756 		//}
10757 		BACK = 0x08,
10758 		TAB = 0x09,
10759 		CLEAR = 0x0C,
10760 		RETURN = 0x0D,
10761 		SHIFT = 0x10,
10762 		CONTROL = 0x11,
10763 		MENU = 0x12,
10764 		PAUSE = 0x13,
10765 		CAPITAL = 0x14,
10766 		KANA = 0x15,
10767 		HANGEUL = 0x15,
10768 		HANGUL = 0x15,
10769 		JUNJA = 0x17,
10770 		FINAL = 0x18,
10771 		HANJA = 0x19,
10772 		KANJI = 0x19,
10773 		ESCAPE = 0x1B,
10774 		CONVERT = 0x1C,
10775 		NONCONVERT = 0x1D,
10776 		ACCEPT = 0x1E,
10777 		MODECHANGE = 0x1F,
10778 		SPACE = 0x20,
10779 		PRIOR = 0x21,
10780 		NEXT = 0x22,
10781 		END = 0x23,
10782 		HOME = 0x24,
10783 		LEFT = 0x25,
10784 		UP = 0x26,
10785 		RIGHT = 0x27,
10786 		DOWN = 0x28,
10787 		SELECT = 0x29,
10788 		PRINT = 0x2A,
10789 		EXECUTE = 0x2B,
10790 		SNAPSHOT = 0x2C,
10791 		INSERT = 0x2D,
10792 		DELETE = 0x2E,
10793 		HELP = 0x2F,
10794 		LWIN = 0x5B,
10795 		RWIN = 0x5C,
10796 		APPS = 0x5D,
10797 		SLEEP = 0x5F,
10798 		NUMPAD0 = 0x60,
10799 		NUMPAD1 = 0x61,
10800 		NUMPAD2 = 0x62,
10801 		NUMPAD3 = 0x63,
10802 		NUMPAD4 = 0x64,
10803 		NUMPAD5 = 0x65,
10804 		NUMPAD6 = 0x66,
10805 		NUMPAD7 = 0x67,
10806 		NUMPAD8 = 0x68,
10807 		NUMPAD9 = 0x69,
10808 		MULTIPLY = 0x6A,
10809 		ADD = 0x6B,
10810 		SEPARATOR = 0x6C,
10811 		SUBTRACT = 0x6D,
10812 		DECIMAL = 0x6E,
10813 		DIVIDE = 0x6F,
10814 		F1 = 0x70,
10815 		F2 = 0x71,
10816 		F3 = 0x72,
10817 		F4 = 0x73,
10818 		F5 = 0x74,
10819 		F6 = 0x75,
10820 		F7 = 0x76,
10821 		F8 = 0x77,
10822 		F9 = 0x78,
10823 		F10 = 0x79,
10824 		F11 = 0x7A,
10825 		F12 = 0x7B,
10826 		F13 = 0x7C,
10827 		F14 = 0x7D,
10828 		F15 = 0x7E,
10829 		F16 = 0x7F,
10830 		F17 = 0x80,
10831 		F18 = 0x81,
10832 		F19 = 0x82,
10833 		F20 = 0x83,
10834 		F21 = 0x84,
10835 		F22 = 0x85,
10836 		F23 = 0x86,
10837 		F24 = 0x87,
10838 		NUMLOCK = 0x90,
10839 		SCROLL = 0x91,
10840 		LSHIFT = 0xA0,
10841 		RSHIFT = 0xA1,
10842 		LCONTROL = 0xA2,
10843 		RCONTROL = 0xA3,
10844 		LMENU = 0xA4,
10845 		RMENU = 0xA5,
10846 		//static if (_WIN32_WINNT > =  0x500) {
10847 		BROWSER_BACK = 0xA6,
10848 		BROWSER_FORWARD = 0xA7,
10849 		BROWSER_REFRESH = 0xA8,
10850 		BROWSER_STOP = 0xA9,
10851 		BROWSER_SEARCH = 0xAA,
10852 		BROWSER_FAVORITES = 0xAB,
10853 		BROWSER_HOME = 0xAC,
10854 		VOLUME_MUTE = 0xAD,
10855 		VOLUME_DOWN = 0xAE,
10856 		VOLUME_UP = 0xAF,
10857 		MEDIA_NEXT_TRACK = 0xB0,
10858 		MEDIA_PREV_TRACK = 0xB1,
10859 		MEDIA_STOP = 0xB2,
10860 		MEDIA_PLAY_PAUSE = 0xB3,
10861 		LAUNCH_MAIL = 0xB4,
10862 		LAUNCH_MEDIA_SELECT = 0xB5,
10863 		LAUNCH_APP1 = 0xB6,
10864 		LAUNCH_APP2 = 0xB7,
10865 		//}
10866 		OEM_1 = 0xBA,
10867 		//static if (_WIN32_WINNT > =  0x500) {
10868 		OEM_PLUS = 0xBB,
10869 		OEM_COMMA = 0xBC,
10870 		OEM_MINUS = 0xBD,
10871 		OEM_PERIOD = 0xBE,
10872 		//}
10873 		OEM_2 = 0xBF,
10874 		OEM_3 = 0xC0,
10875 		OEM_4 = 0xDB,
10876 		OEM_5 = 0xDC,
10877 		OEM_6 = 0xDD,
10878 		OEM_7 = 0xDE,
10879 		OEM_8 = 0xDF,
10880 		//static if (_WIN32_WINNT > =  0x500) {
10881 		OEM_102 = 0xE2,
10882 		//}
10883 		PROCESSKEY = 0xE5,
10884 		//static if (_WIN32_WINNT > =  0x500) {
10885 		PACKET = 0xE7,
10886 		//}
10887 		ATTN = 0xF6,
10888 		CRSEL = 0xF7,
10889 		EXSEL = 0xF8,
10890 		EREOF = 0xF9,
10891 		PLAY = 0xFA,
10892 		ZOOM = 0xFB,
10893 		NONAME = 0xFC,
10894 		PA1 = 0xFD,
10895 		OEM_CLEAR = 0xFE,
10896 	}
10897 
10898 } else version(OSXCocoa) {
10899 	// FIXME
10900 	enum Key {
10901 		Escape = 0x1b,
10902 		F1 = 0x70,
10903 		F2 = 0x71,
10904 		F3 = 0x72,
10905 		F4 = 0x73,
10906 		F5 = 0x74,
10907 		F6 = 0x75,
10908 		F7 = 0x76,
10909 		F8 = 0x77,
10910 		F9 = 0x78,
10911 		F10 = 0x79,
10912 		F11 = 0x7a,
10913 		F12 = 0x7b,
10914 		PrintScreen = 0x2c,
10915 		ScrollLock = -2, // FIXME
10916 		Pause = -3, // FIXME
10917 		Grave = 0xc0,
10918 		// number keys across the top of the keyboard
10919 		N1 = 0x31,
10920 		N2 = 0x32,
10921 		N3 = 0x33,
10922 		N4 = 0x34,
10923 		N5 = 0x35,
10924 		N6 = 0x36,
10925 		N7 = 0x37,
10926 		N8 = 0x38,
10927 		N9 = 0x39,
10928 		N0 = 0x30,
10929 		Dash = 0xbd,
10930 		Equals = 0xbb,
10931 		Backslash = 0xdc,
10932 		Backspace = 0x08,
10933 		Insert = 0x2d,
10934 		Home = 0x24,
10935 		PageUp = 0x21,
10936 		Delete = 0x2e,
10937 		End = 0x23,
10938 		PageDown = 0x22,
10939 		Up = 0x26,
10940 		Down = 0x28,
10941 		Left = 0x25,
10942 		Right = 0x27,
10943 
10944 		Tab = 0x09,
10945 		Q = 0x51,
10946 		W = 0x57,
10947 		E = 0x45,
10948 		R = 0x52,
10949 		T = 0x54,
10950 		Y = 0x59,
10951 		U = 0x55,
10952 		I = 0x49,
10953 		O = 0x4f,
10954 		P = 0x50,
10955 		LeftBracket = 0xdb,
10956 		RightBracket = 0xdd,
10957 		CapsLock = 0x14,
10958 		A = 0x41,
10959 		S = 0x53,
10960 		D = 0x44,
10961 		F = 0x46,
10962 		G = 0x47,
10963 		H = 0x48,
10964 		J = 0x4a,
10965 		K = 0x4b,
10966 		L = 0x4c,
10967 		Semicolon = 0xba,
10968 		Apostrophe = 0xde,
10969 		Enter = 0x0d,
10970 		Shift = 0x10,
10971 		Z = 0x5a,
10972 		X = 0x58,
10973 		C = 0x43,
10974 		V = 0x56,
10975 		B = 0x42,
10976 		N = 0x4e,
10977 		M = 0x4d,
10978 		Comma = 0xbc,
10979 		Period = 0xbe,
10980 		Slash = 0xbf,
10981 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10982 		Ctrl = 0x11,
10983 		Windows = 0x5b,
10984 		Alt = -5, // FIXME
10985 		Space = 0x20,
10986 		Alt_r = 0xffea, // ditto of shift_r
10987 		Windows_r = -6, // FIXME
10988 		Menu = 0x5d,
10989 		Ctrl_r = -7, // FIXME
10990 
10991 		NumLock = 0x90,
10992 		Divide = 0x6f,
10993 		Multiply = 0x6a,
10994 		Minus = 0x6d,
10995 		Plus = 0x6b,
10996 		PadEnter = -8, // FIXME
10997 		// FIXME for the rest of these:
10998 		Pad1 = 0xff9c,
10999 		Pad2 = 0xff99,
11000 		Pad3 = 0xff9b,
11001 		Pad4 = 0xff96,
11002 		Pad5 = 0xff9d,
11003 		Pad6 = 0xff98,
11004 		Pad7 = 0xff95,
11005 		Pad8 = 0xff97,
11006 		Pad9 = 0xff9a,
11007 		Pad0 = 0xff9e,
11008 		PadDot = 0xff9f,
11009 	}
11010 
11011 }
11012 
11013 /* Additional utilities */
11014 
11015 
11016 Color fromHsl(real h, real s, real l) {
11017 	return arsd.color.fromHsl([h,s,l]);
11018 }
11019 
11020 
11021 
11022 /* ********** What follows is the system-specific implementations *********/
11023 version(Windows) {
11024 
11025 
11026 	// helpers for making HICONs from MemoryImages
11027 	class WindowsIcon {
11028 		struct Win32Icon(int colorCount) {
11029 		align(1):
11030 			uint biSize;
11031 			int biWidth;
11032 			int biHeight;
11033 			ushort biPlanes;
11034 			ushort biBitCount;
11035 			uint biCompression;
11036 			uint biSizeImage;
11037 			int biXPelsPerMeter;
11038 			int biYPelsPerMeter;
11039 			uint biClrUsed;
11040 			uint biClrImportant;
11041 			RGBQUAD[colorCount] biColors;
11042 			/* Pixels:
11043 			Uint8 pixels[]
11044 			*/
11045 			/* Mask:
11046 			Uint8 mask[]
11047 			*/
11048 
11049 			ubyte[4096] data;
11050 
11051 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11052 				width = mi.width;
11053 				height = mi.height;
11054 
11055 				auto indexedImage = cast(IndexedImage) mi;
11056 				if(indexedImage is null)
11057 					indexedImage = quantize(mi.getAsTrueColorImage());
11058 
11059 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
11060 				assert(height %4 == 0);
11061 
11062 				int icon_plen = height*((width+3)&~3);
11063 				int icon_mlen = height*((((width+7)/8)+3)&~3);
11064 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11065 
11066 				biSize = 40;
11067 				biWidth = width;
11068 				biHeight = height*2;
11069 				biPlanes = 1;
11070 				biBitCount = 8;
11071 				biSizeImage = icon_plen+icon_mlen;
11072 
11073 				int offset = 0;
11074 				int andOff = icon_plen * 8; // the and offset is in bits
11075 				for(int y = height - 1; y >= 0; y--) {
11076 					int off2 = y * width;
11077 					foreach(x; 0 .. width) {
11078 						const b = indexedImage.data[off2 + x];
11079 						data[offset] = b;
11080 						offset++;
11081 
11082 						const andBit = andOff % 8;
11083 						const andIdx = andOff / 8;
11084 						assert(b < indexedImage.palette.length);
11085 						// this is anded to the destination, since and 0 means erase,
11086 						// we want that to  be opaque, and 1 for transparent
11087 						auto transparent = (indexedImage.palette[b].a <= 127);
11088 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
11089 
11090 						andOff++;
11091 					}
11092 
11093 					andOff += andOff % 32;
11094 				}
11095 
11096 				foreach(idx, entry; indexedImage.palette) {
11097 					if(entry.a > 127) {
11098 						biColors[idx].rgbBlue = entry.b;
11099 						biColors[idx].rgbGreen = entry.g;
11100 						biColors[idx].rgbRed = entry.r;
11101 					} else {
11102 						biColors[idx].rgbBlue = 255;
11103 						biColors[idx].rgbGreen = 255;
11104 						biColors[idx].rgbRed = 255;
11105 					}
11106 				}
11107 
11108 				/*
11109 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
11110 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
11111 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
11112 				auto pngMap = fetchPaletteWin32(png);
11113 				biColors[0..pngMap.length] = pngMap[];
11114 				*/
11115 			}
11116 		}
11117 
11118 
11119 		Win32Icon!(256) icon_win32;
11120 
11121 
11122 		this(MemoryImage mi) {
11123 			int icon_len, width, height;
11124 
11125 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
11126 
11127 			/*
11128 			PNG* png = readPnpngData);
11129 			PNGHeader pngh = getHeader(png);
11130 			void* icon_win32;
11131 			if(pngh.depth == 4) {
11132 				auto i = new Win32Icon!(16);
11133 				i.fromPNG(png, pngh, icon_len, width, height);
11134 				icon_win32 = i;
11135 			}
11136 			else if(pngh.depth == 8) {
11137 				auto i = new Win32Icon!(256);
11138 				i.fromPNG(png, pngh, icon_len, width, height);
11139 				icon_win32 = i;
11140 			} else assert(0);
11141 			*/
11142 
11143 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
11144 
11145 			if(hIcon is null) throw new Exception("CreateIconFromResourceEx");
11146 		}
11147 
11148 		~this() {
11149 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11150 			DestroyIcon(hIcon);
11151 		}
11152 
11153 		HICON hIcon;
11154 	}
11155 
11156 
11157 
11158 
11159 
11160 
11161 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11162 	alias HWND NativeWindowHandle;
11163 
11164 	extern(Windows)
11165 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11166 		try {
11167 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11168 				// it returns zero if the message is handled, so we won't do anything more there
11169 				// do I like that though?
11170 				int mustReturn;
11171 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11172 				if(mustReturn)
11173 					return ret;
11174 			}
11175 
11176 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11177 				if(window.getNativeEventHandler !is null) {
11178 					int mustReturn;
11179 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11180 					if(mustReturn)
11181 						return ret;
11182 				}
11183 				if(auto w = cast(SimpleWindow) (*window))
11184 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11185 				else
11186 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11187 			} else {
11188 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11189 			}
11190 		} catch (Exception e) {
11191 			try {
11192 				sdpy_abort(e);
11193 				return 0;
11194 			} catch(Exception e) { assert(0); }
11195 		}
11196 	}
11197 
11198 	void sdpy_abort(Throwable e) nothrow {
11199 		try
11200 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11201 		catch(Exception e)
11202 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11203 		ExitProcess(1);
11204 	}
11205 
11206 	mixin template NativeScreenPainterImplementation() {
11207 		HDC hdc;
11208 		HWND hwnd;
11209 		//HDC windowHdc;
11210 		HBITMAP oldBmp;
11211 
11212 		void create(NativeWindowHandle window) {
11213 			hwnd = window;
11214 
11215 			if(auto sw = cast(SimpleWindow) this.window) {
11216 				// drawing on a window, double buffer
11217 				auto windowHdc = GetDC(hwnd);
11218 
11219 				auto buffer = sw.impl.buffer;
11220 				if(buffer is null) {
11221 					hdc = windowHdc;
11222 					windowDc = true;
11223 				} else {
11224 					hdc = CreateCompatibleDC(windowHdc);
11225 
11226 					ReleaseDC(hwnd, windowHdc);
11227 
11228 					oldBmp = SelectObject(hdc, buffer);
11229 				}
11230 			} else {
11231 				// drawing on something else, draw directly
11232 				hdc = CreateCompatibleDC(null);
11233 				SelectObject(hdc, window);
11234 			}
11235 
11236 			// X doesn't draw a text background, so neither should we
11237 			SetBkMode(hdc, TRANSPARENT);
11238 
11239 			ensureDefaultFontLoaded();
11240 
11241 			if(defaultGuiFont) {
11242 				SelectObject(hdc, defaultGuiFont);
11243 				// DeleteObject(defaultGuiFont);
11244 			}
11245 		}
11246 
11247 		static HFONT defaultGuiFont;
11248 		static void ensureDefaultFontLoaded() {
11249 			static bool triedDefaultGuiFont = false;
11250 			if(!triedDefaultGuiFont) {
11251 				NONCLIENTMETRICS params;
11252 				params.cbSize = params.sizeof;
11253 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11254 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11255 				}
11256 				triedDefaultGuiFont = true;
11257 			}
11258 		}
11259 
11260 		void setFont(OperatingSystemFont font) {
11261 			if(font && font.font) {
11262 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11263 					// error... how to handle tho?
11264 				}
11265 			}
11266 			else if(defaultGuiFont)
11267 				SelectObject(hdc, defaultGuiFont);
11268 		}
11269 
11270 		arsd.color.Rectangle _clipRectangle;
11271 
11272 		void setClipRectangle(int x, int y, int width, int height) {
11273 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11274 
11275 			if(width == 0 || height == 0) {
11276 				SelectClipRgn(hdc, null);
11277 			} else {
11278 				auto region = CreateRectRgn(x, y, x + width, y + height);
11279 				SelectClipRgn(hdc, region);
11280 				DeleteObject(region);
11281 			}
11282 		}
11283 
11284 
11285 		// just because we can on Windows...
11286 		//void create(Image image);
11287 
11288 		void invalidateRect(Rectangle invalidRect) {
11289 			RECT rect;
11290 			rect.left = invalidRect.left;
11291 			rect.right = invalidRect.right;
11292 			rect.top = invalidRect.top;
11293 			rect.bottom = invalidRect.bottom;
11294 			InvalidateRect(hwnd, &rect, false);
11295 		}
11296 		bool manualInvalidations;
11297 
11298 		void dispose() {
11299 			// FIXME: this.window.width/height is probably wrong
11300 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11301 			// ReleaseDC(hwnd, windowHdc);
11302 
11303 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11304 			if(cast(SimpleWindow) this.window) {
11305 				if(!manualInvalidations)
11306 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11307 			}
11308 
11309 			if(originalPen !is null)
11310 				SelectObject(hdc, originalPen);
11311 			if(currentPen !is null)
11312 				DeleteObject(currentPen);
11313 			if(originalBrush !is null)
11314 				SelectObject(hdc, originalBrush);
11315 			if(currentBrush !is null)
11316 				DeleteObject(currentBrush);
11317 
11318 			SelectObject(hdc, oldBmp);
11319 
11320 			if(windowDc)
11321 				ReleaseDC(hwnd, hdc);
11322 			else
11323 				DeleteDC(hdc);
11324 
11325 			if(window.paintingFinishedDg !is null)
11326 				window.paintingFinishedDg()();
11327 		}
11328 
11329 		bool windowDc;
11330 		HPEN originalPen;
11331 		HPEN currentPen;
11332 
11333 		Pen _activePen;
11334 
11335 		Color _outlineColor;
11336 
11337 		@property void pen(Pen p) {
11338 			_activePen = p;
11339 			_outlineColor = p.color;
11340 
11341 			HPEN pen;
11342 			if(p.color.a == 0) {
11343 				pen = GetStockObject(NULL_PEN);
11344 			} else {
11345 				int style = PS_SOLID;
11346 				final switch(p.style) {
11347 					case Pen.Style.Solid:
11348 						style = PS_SOLID;
11349 					break;
11350 					case Pen.Style.Dashed:
11351 						style = PS_DASH;
11352 					break;
11353 					case Pen.Style.Dotted:
11354 						style = PS_DOT;
11355 					break;
11356 				}
11357 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11358 			}
11359 			auto orig = SelectObject(hdc, pen);
11360 			if(originalPen is null)
11361 				originalPen = orig;
11362 
11363 			if(currentPen !is null)
11364 				DeleteObject(currentPen);
11365 
11366 			currentPen = pen;
11367 
11368 			// the outline is like a foreground since it's done that way on X
11369 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11370 
11371 		}
11372 
11373 		@property void rasterOp(RasterOp op) {
11374 			int mode;
11375 			final switch(op) {
11376 				case RasterOp.normal:
11377 					mode = R2_COPYPEN;
11378 				break;
11379 				case RasterOp.xor:
11380 					mode = R2_XORPEN;
11381 				break;
11382 			}
11383 			SetROP2(hdc, mode);
11384 		}
11385 
11386 		HBRUSH originalBrush;
11387 		HBRUSH currentBrush;
11388 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11389 		@property void fillColor(Color c) {
11390 			if(c == _fillColor)
11391 				return;
11392 			_fillColor = c;
11393 			HBRUSH brush;
11394 			if(c.a == 0) {
11395 				brush = GetStockObject(HOLLOW_BRUSH);
11396 			} else {
11397 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11398 			}
11399 			auto orig = SelectObject(hdc, brush);
11400 			if(originalBrush is null)
11401 				originalBrush = orig;
11402 
11403 			if(currentBrush !is null)
11404 				DeleteObject(currentBrush);
11405 
11406 			currentBrush = brush;
11407 
11408 			// background color is NOT set because X doesn't draw text backgrounds
11409 			//   SetBkColor(hdc, RGB(255, 255, 255));
11410 		}
11411 
11412 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11413 			BITMAP bm;
11414 
11415 			HDC hdcMem = CreateCompatibleDC(hdc);
11416 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11417 
11418 			GetObject(i.handle, bm.sizeof, &bm);
11419 
11420 			// or should I AlphaBlend!??!?!
11421 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11422 
11423 			SelectObject(hdcMem, hbmOld);
11424 			DeleteDC(hdcMem);
11425 		}
11426 
11427 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11428 			BITMAP bm;
11429 
11430 			HDC hdcMem = CreateCompatibleDC(hdc);
11431 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11432 
11433 			GetObject(s.handle, bm.sizeof, &bm);
11434 
11435 			version(CRuntime_DigitalMars) goto noalpha;
11436 
11437 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11438 			if(s.enableAlpha) {
11439 				auto dw = w ? w : bm.bmWidth;
11440 				auto dh = h ? h : bm.bmHeight;
11441 				BLENDFUNCTION bf;
11442 				bf.BlendOp = AC_SRC_OVER;
11443 				bf.SourceConstantAlpha = 255;
11444 				bf.AlphaFormat = AC_SRC_ALPHA;
11445 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11446 			} else {
11447 				noalpha:
11448 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11449 			}
11450 
11451 			SelectObject(hdcMem, hbmOld);
11452 			DeleteDC(hdcMem);
11453 		}
11454 
11455 		Size textSize(scope const(char)[] text) {
11456 			bool dummyX;
11457 			if(text.length == 0) {
11458 				text = " ";
11459 				dummyX = true;
11460 			}
11461 			RECT rect;
11462 			WCharzBuffer buffer = WCharzBuffer(text);
11463 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11464 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11465 		}
11466 
11467 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11468 			if(text.length && text[$-1] == '\n')
11469 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11470 			if(text.length && text[$-1] == '\r')
11471 				text = text[0 .. $-1];
11472 
11473 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11474 			if(x2 == 0 && y2 == 0) {
11475 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11476 			} else {
11477 				RECT rect;
11478 				rect.left = x;
11479 				rect.top = y;
11480 				rect.right = x2;
11481 				rect.bottom = y2;
11482 
11483 				uint mode = DT_LEFT;
11484 				if(alignment & TextAlignment.Right)
11485 					mode = DT_RIGHT;
11486 				else if(alignment & TextAlignment.Center)
11487 					mode = DT_CENTER;
11488 
11489 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11490 				if(alignment & TextAlignment.VerticalCenter)
11491 					mode |= DT_VCENTER | DT_SINGLELINE;
11492 
11493 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11494 			}
11495 
11496 			/*
11497 			uint mode;
11498 
11499 			if(alignment & TextAlignment.Center)
11500 				mode = TA_CENTER;
11501 
11502 			SetTextAlign(hdc, mode);
11503 			*/
11504 		}
11505 
11506 		int fontHeight() {
11507 			TEXTMETRIC metric;
11508 			if(GetTextMetricsW(hdc, &metric)) {
11509 				return metric.tmHeight;
11510 			}
11511 
11512 			return 16; // idk just guessing here, maybe we should throw
11513 		}
11514 
11515 		void drawPixel(int x, int y) {
11516 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11517 		}
11518 
11519 		// The basic shapes, outlined
11520 
11521 		void drawLine(int x1, int y1, int x2, int y2) {
11522 			MoveToEx(hdc, x1, y1, null);
11523 			LineTo(hdc, x2, y2);
11524 		}
11525 
11526 		void drawRectangle(int x, int y, int width, int height) {
11527 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11528 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11529 		}
11530 
11531 		/// Arguments are the points of the bounding rectangle
11532 		void drawEllipse(int x1, int y1, int x2, int y2) {
11533 			Ellipse(hdc, x1, y1, x2, y2);
11534 		}
11535 
11536 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11537 			if((start % (360*64)) == (finish % (360*64)))
11538 				drawEllipse(x1, y1, x1 + width, y1 + height);
11539 			else {
11540 				import core.stdc.math;
11541 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11542 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11543 
11544 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11545 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11546 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11547 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11548 
11549 				if(_activePen.color.a)
11550 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11551 				if(_fillColor.a)
11552 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11553 			}
11554 		}
11555 
11556 		void drawPolygon(Point[] vertexes) {
11557 			POINT[] points;
11558 			points.length = vertexes.length;
11559 
11560 			foreach(i, p; vertexes) {
11561 				points[i].x = p.x;
11562 				points[i].y = p.y;
11563 			}
11564 
11565 			Polygon(hdc, points.ptr, cast(int) points.length);
11566 		}
11567 	}
11568 
11569 
11570 	// Mix this into the SimpleWindow class
11571 	mixin template NativeSimpleWindowImplementation() {
11572 		int curHidden = 0; // counter
11573 		__gshared static bool[string] knownWinClasses;
11574 		static bool altPressed = false;
11575 
11576 		HANDLE oldCursor;
11577 
11578 		void hideCursor () {
11579 			if(curHidden == 0)
11580 				oldCursor = SetCursor(null);
11581 			++curHidden;
11582 		}
11583 
11584 		void showCursor () {
11585 			--curHidden;
11586 			if(curHidden == 0) {
11587 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11588 			}
11589 		}
11590 
11591 
11592 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11593 
11594 		void setMinSize (int minwidth, int minheight) {
11595 			minWidth = minwidth;
11596 			minHeight = minheight;
11597 		}
11598 		void setMaxSize (int maxwidth, int maxheight) {
11599 			maxWidth = maxwidth;
11600 			maxHeight = maxheight;
11601 		}
11602 
11603 		// FIXME i'm not sure that Windows has this functionality
11604 		// though it is nonessential anyway.
11605 		void setResizeGranularity (int granx, int grany) {}
11606 
11607 		ScreenPainter getPainter(bool manualInvalidations) {
11608 			return ScreenPainter(this, hwnd, manualInvalidations);
11609 		}
11610 
11611 		HBITMAP buffer;
11612 
11613 		void setTitle(string title) {
11614 			WCharzBuffer bfr = WCharzBuffer(title);
11615 			SetWindowTextW(hwnd, bfr.ptr);
11616 		}
11617 
11618 		string getTitle() {
11619 			auto len = GetWindowTextLengthW(hwnd);
11620 			if (!len)
11621 				return null;
11622 			wchar[256] tmpBuffer;
11623 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11624 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11625 			auto str = buffer[0 .. len2];
11626 			return makeUtf8StringFromWindowsString(str);
11627 		}
11628 
11629 		void move(int x, int y) {
11630 			RECT rect;
11631 			GetWindowRect(hwnd, &rect);
11632 			// move it while maintaining the same size...
11633 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11634 		}
11635 
11636 		void resize(int w, int h) {
11637 			RECT rect;
11638 			GetWindowRect(hwnd, &rect);
11639 
11640 			RECT client;
11641 			GetClientRect(hwnd, &client);
11642 
11643 			rect.right = rect.right - client.right + w;
11644 			rect.bottom = rect.bottom - client.bottom + h;
11645 
11646 			// same position, new size for the client rectangle
11647 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11648 
11649 			updateOpenglViewportIfNeeded(w, h);
11650 		}
11651 
11652 		void moveResize (int x, int y, int w, int h) {
11653 			// what's given is the client rectangle, we need to adjust
11654 
11655 			RECT rect;
11656 			rect.left = x;
11657 			rect.top = y;
11658 			rect.right = w + x;
11659 			rect.bottom = h + y;
11660 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11661 				throw new Exception("AdjustWindowRect");
11662 
11663 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11664 			updateOpenglViewportIfNeeded(w, h);
11665 			if (windowResized !is null) windowResized(w, h);
11666 		}
11667 
11668 		version(without_opengl) {} else {
11669 			HGLRC ghRC;
11670 			HDC ghDC;
11671 		}
11672 
11673 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11674 			string cnamec;
11675 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11676 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11677 				cnamec = "DSimpleWindow";
11678 			} else {
11679 				cnamec = sdpyWindowClass;
11680 			}
11681 
11682 			WCharzBuffer cn = WCharzBuffer(cnamec);
11683 
11684 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11685 
11686 			if(cnamec !in knownWinClasses) {
11687 				WNDCLASSEX wc;
11688 
11689 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11690 				// to the object. Maybe.
11691 				wc.cbSize = wc.sizeof;
11692 				wc.cbClsExtra = 0;
11693 				wc.cbWndExtra = 0;
11694 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11695 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11696 				wc.hIcon = LoadIcon(hInstance, null);
11697 				wc.hInstance = hInstance;
11698 				wc.lpfnWndProc = &WndProc;
11699 				wc.lpszClassName = cn.ptr;
11700 				wc.hIconSm = null;
11701 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11702 				if(!RegisterClassExW(&wc))
11703 					throw new WindowsApiException("RegisterClassExW");
11704 				knownWinClasses[cnamec] = true;
11705 			}
11706 
11707 			int style;
11708 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11709 
11710 			// FIXME: windowType and customizationFlags
11711 			final switch(windowType) {
11712 				case WindowTypes.normal:
11713 					if(resizability == Resizability.fixedSize) {
11714 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11715 					} else {
11716 						style = WS_OVERLAPPEDWINDOW;
11717 					}
11718 				break;
11719 				case WindowTypes.undecorated:
11720 					style = WS_POPUP | WS_SYSMENU;
11721 				break;
11722 				case WindowTypes.eventOnly:
11723 					_hidden = true;
11724 				break;
11725 				case WindowTypes.dropdownMenu:
11726 				case WindowTypes.popupMenu:
11727 				case WindowTypes.notification:
11728 					style = WS_POPUP;
11729 					flags |= WS_EX_NOACTIVATE;
11730 				break;
11731 				case WindowTypes.nestedChild:
11732 					style = WS_CHILD;
11733 				break;
11734 				case WindowTypes.minimallyWrapped:
11735 					assert(0, "construct minimally wrapped through the other ctor overlad");
11736 			}
11737 
11738 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11739 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11740 
11741 			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
11742 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11743 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11744 
11745 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11746 				setOpacity(255);
11747 
11748 			SimpleWindow.nativeMapping[hwnd] = this;
11749 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11750 
11751 			if(windowType == WindowTypes.eventOnly)
11752 				return;
11753 
11754 			HDC hdc = GetDC(hwnd);
11755 
11756 
11757 			version(without_opengl) {}
11758 			else {
11759 				if(opengl == OpenGlOptions.yes) {
11760 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11761 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11762 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11763 					ghDC = hdc;
11764 					PIXELFORMATDESCRIPTOR pfd;
11765 
11766 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11767 					pfd.nVersion = 1;
11768 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11769 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11770 					pfd.iPixelType = PFD_TYPE_RGBA;
11771 					pfd.cColorBits = 24;
11772 					pfd.cDepthBits = 24;
11773 					pfd.cAccumBits = 0;
11774 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11775 
11776 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11777 
11778 					if (pixelformat == 0)
11779 						throw new WindowsApiException("ChoosePixelFormat");
11780 
11781 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11782 						throw new WindowsApiException("SetPixelFormat");
11783 
11784 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11785 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11786 						// so we will create fake context to get that stupid address
11787 						auto tmpcc = wglCreateContext(ghDC);
11788 						if (tmpcc !is null) {
11789 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11790 							wglMakeCurrent(ghDC, tmpcc);
11791 							wglInitOtherFunctions();
11792 						}
11793 					}
11794 
11795 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11796 						int[9] contextAttribs = [
11797 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11798 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11799 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11800 							// for modern context, set "forward compatibility" flag too
11801 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11802 							0/*None*/,
11803 						];
11804 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11805 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11806 							// activate fallback mode
11807 							// 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;
11808 							ghRC = wglCreateContext(ghDC);
11809 						}
11810 						if (ghRC is null)
11811 							throw new WindowsApiException("wglCreateContextAttribsARB");
11812 					} else {
11813 						// try to do at least something
11814 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11815 							sdpyOpenGLContextVersion = 0;
11816 							ghRC = wglCreateContext(ghDC);
11817 						}
11818 						if (ghRC is null)
11819 							throw new WindowsApiException("wglCreateContext");
11820 					}
11821 				}
11822 			}
11823 
11824 			if(opengl == OpenGlOptions.no) {
11825 				buffer = CreateCompatibleBitmap(hdc, width, height);
11826 
11827 				auto hdcBmp = CreateCompatibleDC(hdc);
11828 				// make sure it's filled with a blank slate
11829 				auto oldBmp = SelectObject(hdcBmp, buffer);
11830 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11831 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11832 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11833 				SelectObject(hdcBmp, oldBmp);
11834 				SelectObject(hdcBmp, oldBrush);
11835 				SelectObject(hdcBmp, oldPen);
11836 				DeleteDC(hdcBmp);
11837 
11838 				bmpWidth = width;
11839 				bmpHeight = height;
11840 
11841 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11842 			}
11843 
11844 			// We want the window's client area to match the image size
11845 			RECT rcClient, rcWindow;
11846 			POINT ptDiff;
11847 			GetClientRect(hwnd, &rcClient);
11848 			GetWindowRect(hwnd, &rcWindow);
11849 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11850 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11851 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11852 
11853 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11854 				ShowWindow(hwnd, SW_SHOWNORMAL);
11855 			} else {
11856 				_hidden = true;
11857 			}
11858 			this._visibleForTheFirstTimeCalled = false; // hack!
11859 		}
11860 
11861 
11862 		void dispose() {
11863 			if(buffer)
11864 				DeleteObject(buffer);
11865 		}
11866 
11867 		void closeWindow() {
11868 			DestroyWindow(hwnd);
11869 		}
11870 
11871 		bool setOpacity(ubyte alpha) {
11872 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11873 		}
11874 
11875 		HANDLE currentCursor;
11876 
11877 		// returns zero if it recognized the event
11878 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11879 			MouseEvent mouse;
11880 
11881 			void mouseEvent(bool isScreen, ulong mods) {
11882 				auto x = LOWORD(lParam);
11883 				auto y = HIWORD(lParam);
11884 				if(isScreen) {
11885 					POINT p;
11886 					p.x = x;
11887 					p.y = y;
11888 					ScreenToClient(hwnd, &p);
11889 					x = cast(ushort) p.x;
11890 					y = cast(ushort) p.y;
11891 				}
11892 
11893 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11894 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11895 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11896 				}
11897 
11898 				mouse.x = x + offsetX;
11899 				mouse.y = y + offsetY;
11900 
11901 				wind.mdx(mouse);
11902 				mouse.modifierState = cast(int) mods;
11903 				mouse.window = wind;
11904 
11905 				if(wind.handleMouseEvent)
11906 					wind.handleMouseEvent(mouse);
11907 			}
11908 
11909 			switch(msg) {
11910 				case WM_GETMINMAXINFO:
11911 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11912 
11913 					if(wind.minWidth > 0) {
11914 						RECT rect;
11915 						rect.left = 100;
11916 						rect.top = 100;
11917 						rect.right = wind.minWidth + 100;
11918 						rect.bottom = wind.minHeight + 100;
11919 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11920 							throw new WindowsApiException("AdjustWindowRect");
11921 
11922 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11923 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11924 					}
11925 
11926 					if(wind.maxWidth < int.max) {
11927 						RECT rect;
11928 						rect.left = 100;
11929 						rect.top = 100;
11930 						rect.right = wind.maxWidth + 100;
11931 						rect.bottom = wind.maxHeight + 100;
11932 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11933 							throw new WindowsApiException("AdjustWindowRect");
11934 
11935 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11936 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11937 					}
11938 				break;
11939 				case WM_CHAR:
11940 					wchar c = cast(wchar) wParam;
11941 					if(wind.handleCharEvent)
11942 						wind.handleCharEvent(cast(dchar) c);
11943 				break;
11944 				  case WM_SETFOCUS:
11945 				  case WM_KILLFOCUS:
11946 					wind._focused = (msg == WM_SETFOCUS);
11947 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
11948 					if(wind.onFocusChange)
11949 						wind.onFocusChange(msg == WM_SETFOCUS);
11950 				  break;
11951 
11952 				case WM_SYSKEYDOWN:
11953 					goto case;
11954 				case WM_SYSKEYUP:
11955 					if(lParam & (1 << 29)) {
11956 						goto case;
11957 					} else {
11958 						// no window has keyboard focus
11959 						goto default;
11960 					}
11961 				case WM_KEYDOWN:
11962 				case WM_KEYUP:
11963 					KeyEvent ev;
11964 					ev.key = cast(Key) wParam;
11965 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
11966 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
11967 
11968 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
11969 
11970 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
11971 						ev.modifierState |= ModifierState.shift;
11972 					//k8: this doesn't work; thanks for nothing, windows
11973 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
11974 						ev.modifierState |= ModifierState.alt;*/
11975 					// this never seems to actually be set
11976 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11977 
11978 					if (wParam == 0x12) {
11979 						altPressed = (msg == WM_SYSKEYDOWN);
11980 					}
11981 
11982 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
11983 						altPressed = false;
11984 					}
11985 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
11986 
11987 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11988 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
11989 						ev.modifierState |= ModifierState.ctrl;
11990 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
11991 						ev.modifierState |= ModifierState.windows;
11992 					if(GetKeyState(Key.NumLock))
11993 						ev.modifierState |= ModifierState.numLock;
11994 					if(GetKeyState(Key.CapsLock))
11995 						ev.modifierState |= ModifierState.capsLock;
11996 
11997 					/+
11998 					// we always want to send the character too, so let's convert it
11999 					ubyte[256] state;
12000 					wchar[16] buffer;
12001 					GetKeyboardState(state.ptr);
12002 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12003 
12004 					foreach(dchar d; buffer) {
12005 						ev.character = d;
12006 						break;
12007 					}
12008 					+/
12009 
12010 					ev.window = wind;
12011 					if(wind.handleKeyEvent)
12012 						wind.handleKeyEvent(ev);
12013 				break;
12014 				case 0x020a /*WM_MOUSEWHEEL*/:
12015 					// send click
12016 					mouse.type = cast(MouseEventType) 1;
12017 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12018 					mouseEvent(true, LOWORD(wParam));
12019 
12020 					// also send release
12021 					mouse.type = cast(MouseEventType) 2;
12022 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12023 					mouseEvent(true, LOWORD(wParam));
12024 				break;
12025 				case WM_MOUSEMOVE:
12026 					mouse.type = cast(MouseEventType) 0;
12027 					mouseEvent(false, wParam);
12028 				break;
12029 				case WM_LBUTTONDOWN:
12030 				case WM_LBUTTONDBLCLK:
12031 					mouse.type = cast(MouseEventType) 1;
12032 					mouse.button = MouseButton.left;
12033 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12034 					mouseEvent(false, wParam);
12035 				break;
12036 				case WM_LBUTTONUP:
12037 					mouse.type = cast(MouseEventType) 2;
12038 					mouse.button = MouseButton.left;
12039 					mouseEvent(false, wParam);
12040 				break;
12041 				case WM_RBUTTONDOWN:
12042 				case WM_RBUTTONDBLCLK:
12043 					mouse.type = cast(MouseEventType) 1;
12044 					mouse.button = MouseButton.right;
12045 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12046 					mouseEvent(false, wParam);
12047 				break;
12048 				case WM_RBUTTONUP:
12049 					mouse.type = cast(MouseEventType) 2;
12050 					mouse.button = MouseButton.right;
12051 					mouseEvent(false, wParam);
12052 				break;
12053 				case WM_MBUTTONDOWN:
12054 				case WM_MBUTTONDBLCLK:
12055 					mouse.type = cast(MouseEventType) 1;
12056 					mouse.button = MouseButton.middle;
12057 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12058 					mouseEvent(false, wParam);
12059 				break;
12060 				case WM_MBUTTONUP:
12061 					mouse.type = cast(MouseEventType) 2;
12062 					mouse.button = MouseButton.middle;
12063 					mouseEvent(false, wParam);
12064 				break;
12065 				case WM_XBUTTONDOWN:
12066 				case WM_XBUTTONDBLCLK:
12067 					mouse.type = cast(MouseEventType) 1;
12068 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12069 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12070 					mouseEvent(false, wParam);
12071 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12072 				case WM_XBUTTONUP:
12073 					mouse.type = cast(MouseEventType) 2;
12074 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12075 					mouseEvent(false, wParam);
12076 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12077 
12078 				default: return 1;
12079 			}
12080 			return 0;
12081 		}
12082 
12083 		HWND hwnd;
12084 		private int oldWidth;
12085 		private int oldHeight;
12086 		private bool inSizeMove;
12087 
12088 		/++
12089 			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.
12090 
12091 			History:
12092 				Added November 23, 2021
12093 
12094 				Not fully stable, may be moved out of the impl struct.
12095 
12096 				Default value changed to `true` on February 15, 2021
12097 		+/
12098 		bool doLiveResizing = true;
12099 
12100 		package int bmpWidth;
12101 		package int bmpHeight;
12102 
12103 		// the extern(Windows) wndproc should just forward to this
12104 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12105 		try {
12106 			assert(hwnd is this.hwnd);
12107 
12108 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12109 			switch(msg) {
12110 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12111 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12112 					// The main things we can do are select, execute, close, or ignore
12113 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12114 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12115 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12116 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12117 
12118 					// returns the value in the *high order word* of the return value
12119 					// hence the << 16
12120 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12121 				case WM_SETCURSOR:
12122 					if(cast(HWND) wParam !is hwnd)
12123 						return 0; // further processing elsewhere
12124 
12125 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12126 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12127 						return 1;
12128 					} else {
12129 						return DefWindowProc(hwnd, msg, wParam, lParam);
12130 					}
12131 				//break;
12132 
12133 				case WM_CLOSE:
12134 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12135 				break;
12136 				case WM_DESTROY:
12137 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12138 					SimpleWindow.nativeMapping.remove(hwnd);
12139 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12140 
12141 					bool anyImportant = false;
12142 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12143 						if(w.beingOpenKeepsAppOpen) {
12144 							anyImportant = true;
12145 							break;
12146 						}
12147 					if(!anyImportant) {
12148 						PostQuitMessage(0);
12149 					}
12150 				break;
12151 				case 0x02E0 /*WM_DPICHANGED*/:
12152 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12153 
12154 					RECT* prcNewWindow = cast(RECT*)lParam;
12155 					// docs say this is the recommended position and we should honor it
12156 					SetWindowPos(hwnd,
12157 							null,
12158 							prcNewWindow.left,
12159 							prcNewWindow.top,
12160 							prcNewWindow.right - prcNewWindow.left,
12161 							prcNewWindow.bottom - prcNewWindow.top,
12162 							SWP_NOZORDER | SWP_NOACTIVATE);
12163 
12164 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12165 					// im not sure it is completely correct
12166 					// but without it the tabs and such do look weird as things change.
12167 					if(SystemParametersInfoForDpi) {
12168 						LOGFONT lfText;
12169 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12170 						HFONT hFontNew = CreateFontIndirect(&lfText);
12171 						if (hFontNew)
12172 						{
12173 							//DeleteObject(hFontOld);
12174 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12175 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12176 								return TRUE;
12177 							}
12178 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12179 						}
12180 					}
12181 
12182 					if(this.onDpiChanged)
12183 						this.onDpiChanged();
12184 				break;
12185 				case WM_ENTERIDLE:
12186 					// when a menu is up, it stops normal event processing (modal message loop)
12187 					// but this at least gives us a chance to SOMETIMES catch up
12188 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12189 					SimpleWindow.processAllCustomEvents;
12190 					SimpleWindow.processAllCustomEvents;
12191 					SleepEx(0, true);
12192 					break;
12193 				case WM_SIZE:
12194 					if(wParam == 1 /* SIZE_MINIMIZED */)
12195 						break;
12196 					_width = LOWORD(lParam);
12197 					_height = HIWORD(lParam);
12198 
12199 					// I want to avoid tearing in the windows (my code is inefficient
12200 					// so this is a hack around that) so while sizing, we don't trigger,
12201 					// but we do want to trigger on events like mazimize.
12202 					if(!inSizeMove || doLiveResizing)
12203 						goto size_changed;
12204 				break;
12205 				/+
12206 				case WM_SIZING:
12207 					import std.stdio; writeln("size");
12208 				break;
12209 				+/
12210 				// I don't like the tearing I get when redrawing on WM_SIZE
12211 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12212 				// so instead it is going to redraw only at the end of a size.
12213 				case 0x0231: /* WM_ENTERSIZEMOVE */
12214 					inSizeMove = true;
12215 				break;
12216 				case 0x0232: /* WM_EXITSIZEMOVE */
12217 					inSizeMove = false;
12218 
12219 					size_changed:
12220 
12221 					// nothing relevant changed, don't bother redrawing
12222 					if(oldWidth == _width && oldHeight == _height) {
12223 						break;
12224 					}
12225 
12226 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12227 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12228 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12229 						// gotta get the double buffer bmp to match the window
12230 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12231 
12232 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12233 						if(resizability != Resizability.automaticallyScaleIfPossible)
12234 						if(_width > bmpWidth || _height > bmpHeight) {
12235 							auto hdc = GetDC(hwnd);
12236 							auto oldBuffer = buffer;
12237 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12238 
12239 							auto hdcBmp = CreateCompatibleDC(hdc);
12240 							auto oldBmp = SelectObject(hdcBmp, buffer);
12241 
12242 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12243 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12244 
12245 							/+
12246 							RECT r;
12247 							r.left = 0;
12248 							r.top = 0;
12249 							r.right = width;
12250 							r.bottom = height;
12251 							auto c = Color.green;
12252 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12253 							FillRect(hdcBmp, &r, brush);
12254 							DeleteObject(brush);
12255 							+/
12256 
12257 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12258 
12259 							bmpWidth = _width;
12260 							bmpHeight = _height;
12261 
12262 							SelectObject(hdcOldBmp, oldOldBmp);
12263 							DeleteDC(hdcOldBmp);
12264 
12265 							SelectObject(hdcBmp, oldBmp);
12266 							DeleteDC(hdcBmp);
12267 
12268 							ReleaseDC(hwnd, hdc);
12269 
12270 							DeleteObject(oldBuffer);
12271 						}
12272 					}
12273 
12274 					updateOpenglViewportIfNeeded(width, height);
12275 
12276 					if(resizability != Resizability.automaticallyScaleIfPossible)
12277 					if(windowResized !is null)
12278 						windowResized(_width, _height);
12279 
12280 					if(inSizeMove) {
12281 						SimpleWindow.processAllCustomEvents();
12282 						SimpleWindow.processAllCustomEvents();
12283 					} else {
12284 						// when it is all done, make sure everything is freshly drawn or there might be
12285 						// weird bugs left.
12286 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12287 					}
12288 
12289 					oldWidth = this._width;
12290 					oldHeight = this._height;
12291 				break;
12292 				case WM_ERASEBKGND:
12293 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12294 					if (!this._visibleForTheFirstTimeCalled) {
12295 						this._visibleForTheFirstTimeCalled = true;
12296 						if (this.visibleForTheFirstTime !is null) {
12297 							this.visibleForTheFirstTime();
12298 						}
12299 					}
12300 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12301 					version(without_opengl) {} else {
12302 						if (openglMode == OpenGlOptions.yes) return 1;
12303 					}
12304 					// call windows default handler, so it can paint standard controls
12305 					goto default;
12306 				case WM_CTLCOLORBTN:
12307 				case WM_CTLCOLORSTATIC:
12308 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12309 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12310 					GetSysColorBrush(COLOR_3DFACE);
12311 				//break;
12312 				case WM_SHOWWINDOW:
12313 					this._visible = (wParam != 0);
12314 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12315 						this._visibleForTheFirstTimeCalled = true;
12316 						if (this.visibleForTheFirstTime !is null) {
12317 							this.visibleForTheFirstTime();
12318 						}
12319 					}
12320 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12321 					break;
12322 				case WM_PAINT: {
12323 					if (!this._visibleForTheFirstTimeCalled) {
12324 						this._visibleForTheFirstTimeCalled = true;
12325 						if (this.visibleForTheFirstTime !is null) {
12326 							this.visibleForTheFirstTime();
12327 						}
12328 					}
12329 
12330 					BITMAP bm;
12331 					PAINTSTRUCT ps;
12332 
12333 					HDC hdc = BeginPaint(hwnd, &ps);
12334 
12335 					if(openglMode == OpenGlOptions.no) {
12336 
12337 						HDC hdcMem = CreateCompatibleDC(hdc);
12338 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12339 
12340 						GetObject(buffer, bm.sizeof, &bm);
12341 
12342 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12343 						if(resizability == Resizability.automaticallyScaleIfPossible)
12344 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12345 						else
12346 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12347 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12348 
12349 						SelectObject(hdcMem, hbmOld);
12350 						DeleteDC(hdcMem);
12351 						EndPaint(hwnd, &ps);
12352 					} else {
12353 						EndPaint(hwnd, &ps);
12354 						version(without_opengl) {} else
12355 							redrawOpenGlSceneSoon();
12356 					}
12357 				} break;
12358 				  default:
12359 					return DefWindowProc(hwnd, msg, wParam, lParam);
12360 			}
12361 			 return 0;
12362 
12363 		}
12364 		catch(Throwable t) {
12365 			sdpyPrintDebugString(t.toString);
12366 			return 0;
12367 		}
12368 		}
12369 	}
12370 
12371 	mixin template NativeImageImplementation() {
12372 		HBITMAP handle;
12373 		ubyte* rawData;
12374 
12375 	final:
12376 
12377 		Color getPixel(int x, int y) {
12378 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12379 			// remember, bmps are upside down
12380 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12381 
12382 			Color c;
12383 			if(enableAlpha)
12384 				c.a = rawData[offset + 3];
12385 			else
12386 				c.a = 255;
12387 			c.b = rawData[offset + 0];
12388 			c.g = rawData[offset + 1];
12389 			c.r = rawData[offset + 2];
12390 			c.unPremultiply();
12391 			return c;
12392 		}
12393 
12394 		void setPixel(int x, int y, Color c) {
12395 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12396 			// remember, bmps are upside down
12397 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12398 
12399 			if(enableAlpha)
12400 				c.premultiply();
12401 
12402 			rawData[offset + 0] = c.b;
12403 			rawData[offset + 1] = c.g;
12404 			rawData[offset + 2] = c.r;
12405 			if(enableAlpha)
12406 				rawData[offset + 3] = c.a;
12407 		}
12408 
12409 		void convertToRgbaBytes(ubyte[] where) {
12410 			assert(where.length == this.width * this.height * 4);
12411 
12412 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12413 			int idx = 0;
12414 			int offset = itemsPerLine * (height - 1);
12415 			// remember, bmps are upside down
12416 			for(int y = height - 1; y >= 0; y--) {
12417 				auto offsetStart = offset;
12418 				for(int x = 0; x < width; x++) {
12419 					where[idx + 0] = rawData[offset + 2]; // r
12420 					where[idx + 1] = rawData[offset + 1]; // g
12421 					where[idx + 2] = rawData[offset + 0]; // b
12422 					if(enableAlpha) {
12423 						where[idx + 3] = rawData[offset + 3]; // a
12424 						unPremultiplyRgba(where[idx .. idx + 4]);
12425 						offset++;
12426 					} else
12427 						where[idx + 3] = 255; // a
12428 					idx += 4;
12429 					offset += 3;
12430 				}
12431 
12432 				offset = offsetStart - itemsPerLine;
12433 			}
12434 		}
12435 
12436 		void setFromRgbaBytes(in ubyte[] what) {
12437 			assert(what.length == this.width * this.height * 4);
12438 
12439 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12440 			int idx = 0;
12441 			int offset = itemsPerLine * (height - 1);
12442 			// remember, bmps are upside down
12443 			for(int y = height - 1; y >= 0; y--) {
12444 				auto offsetStart = offset;
12445 				for(int x = 0; x < width; x++) {
12446 					if(enableAlpha) {
12447 						auto a = what[idx + 3];
12448 
12449 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12450 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12451 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12452 						rawData[offset + 3] = a; // a
12453 						//premultiplyBgra(rawData[offset .. offset + 4]);
12454 						offset++;
12455 					} else {
12456 						rawData[offset + 2] = what[idx + 0]; // r
12457 						rawData[offset + 1] = what[idx + 1]; // g
12458 						rawData[offset + 0] = what[idx + 2]; // b
12459 					}
12460 					idx += 4;
12461 					offset += 3;
12462 				}
12463 
12464 				offset = offsetStart - itemsPerLine;
12465 			}
12466 		}
12467 
12468 
12469 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12470 			BITMAPINFO infoheader;
12471 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12472 			infoheader.bmiHeader.biWidth = width;
12473 			infoheader.bmiHeader.biHeight = height;
12474 			infoheader.bmiHeader.biPlanes = 1;
12475 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12476 			infoheader.bmiHeader.biCompression = BI_RGB;
12477 
12478 			handle = CreateDIBSection(
12479 				null,
12480 				&infoheader,
12481 				DIB_RGB_COLORS,
12482 				cast(void**) &rawData,
12483 				null,
12484 				0);
12485 			if(handle is null)
12486 				throw new WindowsApiException("create image failed");
12487 
12488 		}
12489 
12490 		void dispose() {
12491 			DeleteObject(handle);
12492 		}
12493 	}
12494 
12495 	enum KEY_ESCAPE = 27;
12496 }
12497 version(X11) {
12498 	/// This is the default font used. You might change this before doing anything else with
12499 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12500 	/// for cross-platform compatibility.
12501 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12502 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12503 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12504 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12505 
12506 	alias int delegate(XEvent) NativeEventHandler;
12507 	alias Window NativeWindowHandle;
12508 
12509 	enum KEY_ESCAPE = 9;
12510 
12511 	mixin template NativeScreenPainterImplementation() {
12512 		Display* display;
12513 		Drawable d;
12514 		Drawable destiny;
12515 
12516 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12517 		GC gc;
12518 
12519 		__gshared bool fontAttempted;
12520 
12521 		__gshared XFontStruct* defaultfont;
12522 		__gshared XFontSet defaultfontset;
12523 
12524 		XFontStruct* font;
12525 		XFontSet fontset;
12526 
12527 		void create(NativeWindowHandle window) {
12528 			this.display = XDisplayConnection.get();
12529 
12530 			Drawable buffer = None;
12531 			if(auto sw = cast(SimpleWindow) this.window) {
12532 				buffer = sw.impl.buffer;
12533 				this.destiny = cast(Drawable) window;
12534 			} else {
12535 				buffer = cast(Drawable) window;
12536 				this.destiny = None;
12537 			}
12538 
12539 			this.d = cast(Drawable) buffer;
12540 
12541 			auto dgc = DefaultGC(display, DefaultScreen(display));
12542 
12543 			this.gc = XCreateGC(display, d, 0, null);
12544 
12545 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12546 
12547 			ensureDefaultFontLoaded();
12548 
12549 			font = defaultfont;
12550 			fontset = defaultfontset;
12551 
12552 			if(font) {
12553 				XSetFont(display, gc, font.fid);
12554 			}
12555 		}
12556 
12557 		static void ensureDefaultFontLoaded() {
12558 			if(!fontAttempted) {
12559 				auto display = XDisplayConnection.get;
12560 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12561 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12562 				if(font is null) {
12563 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12564 					font = XLoadQueryFont(display, xfontstr.ptr);
12565 				}
12566 
12567 				char** lol;
12568 				int lol2;
12569 				char* lol3;
12570 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12571 
12572 				fontAttempted = true;
12573 
12574 				defaultfont = font;
12575 				defaultfontset = fontset;
12576 			}
12577 		}
12578 
12579 		arsd.color.Rectangle _clipRectangle;
12580 		void setClipRectangle(int x, int y, int width, int height) {
12581 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12582 			if(width == 0 || height == 0) {
12583 				XSetClipMask(display, gc, None);
12584 
12585 				if(xrenderPicturePainter) {
12586 
12587 					XRectangle[1] rects;
12588 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12589 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12590 				}
12591 
12592 				version(with_xft) {
12593 					if(xftFont is null || xftDraw is null)
12594 						return;
12595 					XftDrawSetClip(xftDraw, null);
12596 				}
12597 			} else {
12598 				XRectangle[1] rects;
12599 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12600 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12601 
12602 				if(xrenderPicturePainter)
12603 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12604 
12605 				version(with_xft) {
12606 					if(xftFont is null || xftDraw is null)
12607 						return;
12608 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12609 				}
12610 			}
12611 		}
12612 
12613 		version(with_xft) {
12614 			XftFont* xftFont;
12615 			XftDraw* xftDraw;
12616 
12617 			XftColor xftColor;
12618 
12619 			void updateXftColor() {
12620 				if(xftFont is null)
12621 					return;
12622 
12623 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12624 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12625 
12626 				XftColorAllocValue(
12627 					display,
12628 					DefaultVisual(display, DefaultScreen(display)),
12629 					DefaultColormap(display, 0),
12630 					&colorIn,
12631 					&xftColor
12632 				);
12633 			}
12634 		}
12635 
12636 		void setFont(OperatingSystemFont font) {
12637 			version(with_xft) {
12638 				if(font && font.isXft && font.xftFont)
12639 					this.xftFont = font.xftFont;
12640 				else
12641 					this.xftFont = null;
12642 
12643 				if(this.xftFont) {
12644 					if(xftDraw is null) {
12645 						xftDraw = XftDrawCreate(
12646 							display,
12647 							d,
12648 							DefaultVisual(display, DefaultScreen(display)),
12649 							DefaultColormap(display, 0)
12650 						);
12651 
12652 						updateXftColor();
12653 					}
12654 
12655 					return;
12656 				}
12657 			}
12658 
12659 			if(font && font.font) {
12660 				this.font = font.font;
12661 				this.fontset = font.fontset;
12662 				XSetFont(display, gc, font.font.fid);
12663 			} else {
12664 				this.font = defaultfont;
12665 				this.fontset = defaultfontset;
12666 			}
12667 
12668 		}
12669 
12670 		private Picture xrenderPicturePainter;
12671 
12672 		bool manualInvalidations;
12673 		void invalidateRect(Rectangle invalidRect) {
12674 			// FIXME if manualInvalidations
12675 		}
12676 
12677 		void dispose() {
12678 			this.rasterOp = RasterOp.normal;
12679 
12680 			if(xrenderPicturePainter) {
12681 				XRenderFreePicture(display, xrenderPicturePainter);
12682 				xrenderPicturePainter = None;
12683 			}
12684 
12685 			// FIXME: this.window.width/height is probably wrong
12686 
12687 			// src x,y     then dest x, y
12688 			if(destiny != None) {
12689 				// FIXME: if manual invalidations we can actually only copy some of the area.
12690 				// if(manualInvalidations)
12691 				XSetClipMask(display, gc, None);
12692 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12693 			}
12694 
12695 			XFreeGC(display, gc);
12696 
12697 			version(with_xft)
12698 			if(xftDraw) {
12699 				XftDrawDestroy(xftDraw);
12700 				xftDraw = null;
12701 			}
12702 
12703 			/+
12704 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12705 			if(font && font !is defaultfont) {
12706 				XFreeFont(display, font);
12707 				font = null;
12708 			}
12709 			if(fontset && fontset !is defaultfontset) {
12710 				XFreeFontSet(display, fontset);
12711 				fontset = null;
12712 			}
12713 			+/
12714 			XFlush(display);
12715 
12716 			if(window.paintingFinishedDg !is null)
12717 				window.paintingFinishedDg()();
12718 		}
12719 
12720 		bool backgroundIsNotTransparent = true;
12721 		bool foregroundIsNotTransparent = true;
12722 
12723 		bool _penInitialized = false;
12724 		Pen _activePen;
12725 
12726 		Color _outlineColor;
12727 		Color _fillColor;
12728 
12729 		@property void pen(Pen p) {
12730 			if(_penInitialized && p == _activePen) {
12731 				return;
12732 			}
12733 			_penInitialized = true;
12734 			_activePen = p;
12735 			_outlineColor = p.color;
12736 
12737 			int style;
12738 
12739 			byte dashLength;
12740 
12741 			final switch(p.style) {
12742 				case Pen.Style.Solid:
12743 					style = 0 /*LineSolid*/;
12744 				break;
12745 				case Pen.Style.Dashed:
12746 					style = 1 /*LineOnOffDash*/;
12747 					dashLength = 4;
12748 				break;
12749 				case Pen.Style.Dotted:
12750 					style = 1 /*LineOnOffDash*/;
12751 					dashLength = 1;
12752 				break;
12753 			}
12754 
12755 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
12756 			if(dashLength)
12757 				XSetDashes(display, gc, 0, &dashLength, 1);
12758 
12759 			if(p.color.a == 0) {
12760 				foregroundIsNotTransparent = false;
12761 				return;
12762 			}
12763 
12764 			foregroundIsNotTransparent = true;
12765 
12766 			XSetForeground(display, gc, colorToX(p.color, display));
12767 
12768 			version(with_xft)
12769 				updateXftColor();
12770 		}
12771 
12772 		RasterOp _currentRasterOp;
12773 		bool _currentRasterOpInitialized = false;
12774 		@property void rasterOp(RasterOp op) {
12775 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12776 				return;
12777 			_currentRasterOp = op;
12778 			_currentRasterOpInitialized = true;
12779 			int mode;
12780 			final switch(op) {
12781 				case RasterOp.normal:
12782 					mode = GXcopy;
12783 				break;
12784 				case RasterOp.xor:
12785 					mode = GXxor;
12786 				break;
12787 			}
12788 			XSetFunction(display, gc, mode);
12789 		}
12790 
12791 
12792 		bool _fillColorInitialized = false;
12793 
12794 		@property void fillColor(Color c) {
12795 			if(_fillColorInitialized && _fillColor == c)
12796 				return; // already good, no need to waste time calling it
12797 			_fillColor = c;
12798 			_fillColorInitialized = true;
12799 			if(c.a == 0) {
12800 				backgroundIsNotTransparent = false;
12801 				return;
12802 			}
12803 
12804 			backgroundIsNotTransparent = true;
12805 
12806 			XSetBackground(display, gc, colorToX(c, display));
12807 
12808 		}
12809 
12810 		void swapColors() {
12811 			auto tmp = _fillColor;
12812 			fillColor = _outlineColor;
12813 			auto newPen = _activePen;
12814 			newPen.color = tmp;
12815 			pen(newPen);
12816 		}
12817 
12818 		uint colorToX(Color c, Display* display) {
12819 			auto visual = DefaultVisual(display, DefaultScreen(display));
12820 			import core.bitop;
12821 			uint color = 0;
12822 			{
12823 			auto startBit = bsf(visual.red_mask);
12824 			auto lastBit = bsr(visual.red_mask);
12825 			auto r = cast(uint) c.r;
12826 			r >>= 7 - (lastBit - startBit);
12827 			r <<= startBit;
12828 			color |= r;
12829 			}
12830 			{
12831 			auto startBit = bsf(visual.green_mask);
12832 			auto lastBit = bsr(visual.green_mask);
12833 			auto g = cast(uint) c.g;
12834 			g >>= 7 - (lastBit - startBit);
12835 			g <<= startBit;
12836 			color |= g;
12837 			}
12838 			{
12839 			auto startBit = bsf(visual.blue_mask);
12840 			auto lastBit = bsr(visual.blue_mask);
12841 			auto b = cast(uint) c.b;
12842 			b >>= 7 - (lastBit - startBit);
12843 			b <<= startBit;
12844 			color |= b;
12845 			}
12846 
12847 
12848 
12849 			return color;
12850 		}
12851 
12852 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12853 			// source x, source y
12854 			if(ix >= i.width) return;
12855 			if(iy >= i.height) return;
12856 			if(ix + w > i.width) w = i.width - ix;
12857 			if(iy + h > i.height) h = i.height - iy;
12858 			if(i.usingXshm)
12859 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12860 			else
12861 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12862 		}
12863 
12864 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12865 			if(s.enableAlpha) {
12866 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12867 				if(this.xrenderPicturePainter == None) {
12868 					XRenderPictureAttributes attrs;
12869 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12870 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12871 
12872 					// need to initialize the clip
12873 					XRectangle[1] rects;
12874 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12875 
12876 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12877 				}
12878 
12879 				XRenderComposite(
12880 					display,
12881 					3, // PicOpOver
12882 					s.xrenderPicture,
12883 					None,
12884 					this.xrenderPicturePainter,
12885 					ix,
12886 					iy,
12887 					0,
12888 					0,
12889 					x,
12890 					y,
12891 					w ? w : s.width,
12892 					h ? h : s.height
12893 				);
12894 			} else {
12895 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12896 			}
12897 		}
12898 
12899 		int fontHeight() {
12900 			version(with_xft)
12901 				if(xftFont !is null)
12902 					return xftFont.height;
12903 			if(font)
12904 				return font.max_bounds.ascent + font.max_bounds.descent;
12905 			return 12; // pretty common default...
12906 		}
12907 
12908 		int textWidth(in char[] line) {
12909 			version(with_xft)
12910 			if(xftFont) {
12911 				if(line.length == 0)
12912 					return 0;
12913 				XGlyphInfo extents;
12914 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12915 				return extents.width;
12916 			}
12917 
12918 			if(fontset) {
12919 				if(line.length == 0)
12920 					return 0;
12921 				XRectangle rect;
12922 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12923 
12924 				return rect.width;
12925 			}
12926 
12927 			if(font)
12928 				// FIXME: unicode
12929 				return XTextWidth( font, line.ptr, cast(int) line.length);
12930 			else
12931 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
12932 		}
12933 
12934 		Size textSize(in char[] text) {
12935 			auto maxWidth = 0;
12936 			auto lineHeight = fontHeight;
12937 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
12938 			foreach(line; text.split('\n')) {
12939 				int textWidth = this.textWidth(line);
12940 				if(textWidth > maxWidth)
12941 					maxWidth = textWidth;
12942 				h += lineHeight + 4;
12943 			}
12944 			return Size(maxWidth, h);
12945 		}
12946 
12947 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
12948 			const(char)[] text;
12949 			version(with_xft)
12950 			if(xftFont) {
12951 				text = originalText;
12952 				goto loaded;
12953 			}
12954 
12955 			if(fontset)
12956 				text = originalText;
12957 			else {
12958 				text.reserve(originalText.length);
12959 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
12960 				// then strip the rest so there isn't garbage
12961 				foreach(dchar ch; originalText)
12962 					if(ch < 256)
12963 						text ~= cast(ubyte) ch;
12964 					else
12965 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
12966 			}
12967 			loaded:
12968 			if(text.length == 0)
12969 				return;
12970 
12971 			// FIXME: should we clip it to the bounding box?
12972 			int textHeight = fontHeight;
12973 
12974 			auto lines = text.split('\n');
12975 
12976 			const lineHeight = textHeight;
12977 			textHeight *= lines.length;
12978 
12979 			int cy = y;
12980 
12981 			if(alignment & TextAlignment.VerticalBottom) {
12982 				if(y2 <= 0)
12983 					return;
12984 				auto h = y2 - y;
12985 				if(h > textHeight) {
12986 					cy += h - textHeight;
12987 					cy -= lineHeight / 2;
12988 				}
12989 			} else if(alignment & TextAlignment.VerticalCenter) {
12990 				if(y2 <= 0)
12991 					return;
12992 				auto h = y2 - y;
12993 				if(textHeight < h) {
12994 					cy += (h - textHeight) / 2;
12995 					//cy -= lineHeight / 4;
12996 				}
12997 			}
12998 
12999 			foreach(line; text.split('\n')) {
13000 				int textWidth = this.textWidth(line);
13001 
13002 				int px = x, py = cy;
13003 
13004 				if(alignment & TextAlignment.Center) {
13005 					if(x2 <= 0)
13006 						return;
13007 					auto w = x2 - x;
13008 					if(w > textWidth)
13009 						px += (w - textWidth) / 2;
13010 				} else if(alignment & TextAlignment.Right) {
13011 					if(x2 <= 0)
13012 						return;
13013 					auto pos = x2 - textWidth;
13014 					if(pos > x)
13015 						px = pos;
13016 				}
13017 
13018 				version(with_xft)
13019 				if(xftFont) {
13020 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13021 
13022 					goto carry_on;
13023 				}
13024 
13025 				if(fontset)
13026 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13027 				else
13028 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13029 				carry_on:
13030 				cy += lineHeight + 4;
13031 			}
13032 		}
13033 
13034 		void drawPixel(int x, int y) {
13035 			XDrawPoint(display, d, gc, x, y);
13036 		}
13037 
13038 		// The basic shapes, outlined
13039 
13040 		void drawLine(int x1, int y1, int x2, int y2) {
13041 			if(foregroundIsNotTransparent)
13042 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13043 		}
13044 
13045 		void drawRectangle(int x, int y, int width, int height) {
13046 			if(backgroundIsNotTransparent) {
13047 				swapColors();
13048 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13049 				swapColors();
13050 			}
13051 			// 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
13052 			if(foregroundIsNotTransparent)
13053 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13054 		}
13055 
13056 		/// Arguments are the points of the bounding rectangle
13057 		void drawEllipse(int x1, int y1, int x2, int y2) {
13058 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13059 		}
13060 
13061 		// NOTE: start and finish are in units of degrees * 64
13062 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
13063 			if(backgroundIsNotTransparent) {
13064 				swapColors();
13065 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
13066 				swapColors();
13067 			}
13068 			if(foregroundIsNotTransparent) {
13069 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
13070 				// Windows draws the straight lines on the edges too so FIXME sort of
13071 			}
13072 		}
13073 
13074 		void drawPolygon(Point[] vertexes) {
13075 			XPoint[16] pointsBuffer;
13076 			XPoint[] points;
13077 			if(vertexes.length <= pointsBuffer.length)
13078 				points = pointsBuffer[0 .. vertexes.length];
13079 			else
13080 				points.length = vertexes.length;
13081 
13082 			foreach(i, p; vertexes) {
13083 				points[i].x = cast(short) p.x;
13084 				points[i].y = cast(short) p.y;
13085 			}
13086 
13087 			if(backgroundIsNotTransparent) {
13088 				swapColors();
13089 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13090 				swapColors();
13091 			}
13092 			if(foregroundIsNotTransparent) {
13093 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13094 			}
13095 		}
13096 	}
13097 
13098 	/* XRender { */
13099 
13100 	struct XRenderColor {
13101 		ushort red;
13102 		ushort green;
13103 		ushort blue;
13104 		ushort alpha;
13105 	}
13106 
13107 	alias Picture = XID;
13108 	alias PictFormat = XID;
13109 
13110 	struct XGlyphInfo {
13111 		ushort width;
13112 		ushort height;
13113 		short x;
13114 		short y;
13115 		short xOff;
13116 		short yOff;
13117 	}
13118 
13119 struct XRenderDirectFormat {
13120     short   red;
13121     short   redMask;
13122     short   green;
13123     short   greenMask;
13124     short   blue;
13125     short   blueMask;
13126     short   alpha;
13127     short   alphaMask;
13128 }
13129 
13130 struct XRenderPictFormat {
13131     PictFormat		id;
13132     int			type;
13133     int			depth;
13134     XRenderDirectFormat	direct;
13135     Colormap		colormap;
13136 }
13137 
13138 enum PictFormatID	=   (1 << 0);
13139 enum PictFormatType	=   (1 << 1);
13140 enum PictFormatDepth	=   (1 << 2);
13141 enum PictFormatRed	=   (1 << 3);
13142 enum PictFormatRedMask  =(1 << 4);
13143 enum PictFormatGreen	=   (1 << 5);
13144 enum PictFormatGreenMask=(1 << 6);
13145 enum PictFormatBlue	=   (1 << 7);
13146 enum PictFormatBlueMask =(1 << 8);
13147 enum PictFormatAlpha	=   (1 << 9);
13148 enum PictFormatAlphaMask=(1 << 10);
13149 enum PictFormatColormap =(1 << 11);
13150 
13151 struct XRenderPictureAttributes {
13152 	int 		repeat;
13153 	Picture		alpha_map;
13154 	int			alpha_x_origin;
13155 	int			alpha_y_origin;
13156 	int			clip_x_origin;
13157 	int			clip_y_origin;
13158 	Pixmap		clip_mask;
13159 	Bool		graphics_exposures;
13160 	int			subwindow_mode;
13161 	int			poly_edge;
13162 	int			poly_mode;
13163 	Atom		dither;
13164 	Bool		component_alpha;
13165 }
13166 
13167 alias int XFixed;
13168 
13169 struct XPointFixed {
13170     XFixed  x, y;
13171 }
13172 
13173 struct XCircle {
13174     XFixed x;
13175     XFixed y;
13176     XFixed radius;
13177 }
13178 
13179 struct XTransform {
13180     XFixed[3][3]  matrix;
13181 }
13182 
13183 struct XFilters {
13184     int	    nfilter;
13185     char    **filter;
13186     int	    nalias;
13187     short   *alias_;
13188 }
13189 
13190 struct XIndexValue {
13191     c_ulong    pixel;
13192     ushort   red, green, blue, alpha;
13193 }
13194 
13195 struct XAnimCursor {
13196     Cursor	    cursor;
13197     c_ulong   delay;
13198 }
13199 
13200 struct XLinearGradient {
13201     XPointFixed p1;
13202     XPointFixed p2;
13203 }
13204 
13205 struct XRadialGradient {
13206     XCircle inner;
13207     XCircle outer;
13208 }
13209 
13210 struct XConicalGradient {
13211     XPointFixed center;
13212     XFixed angle; /* in degrees */
13213 }
13214 
13215 enum PictStandardARGB32  = 0;
13216 enum PictStandardRGB24   = 1;
13217 enum PictStandardA8	 =  2;
13218 enum PictStandardA4	 =  3;
13219 enum PictStandardA1	 =  4;
13220 enum PictStandardNUM	 =  5;
13221 
13222 interface XRender {
13223 extern(C) @nogc:
13224 
13225 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13226 
13227 	Status XRenderQueryVersion (Display *dpy,
13228 			int     *major_versionp,
13229 			int     *minor_versionp);
13230 
13231 	Status XRenderQueryFormats (Display *dpy);
13232 
13233 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13234 
13235 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13236 
13237 	XRenderPictFormat *
13238 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13239 
13240 	XRenderPictFormat *
13241 		XRenderFindFormat (Display			*dpy,
13242 				c_ulong		mask,
13243 				const XRenderPictFormat	*templ,
13244 				int				count);
13245 	XRenderPictFormat *
13246 		XRenderFindStandardFormat (Display		*dpy,
13247 				int			format);
13248 
13249 	XIndexValue *
13250 		XRenderQueryPictIndexValues(Display			*dpy,
13251 				const XRenderPictFormat	*format,
13252 				int				*num);
13253 
13254 	Picture XRenderCreatePicture(
13255 		Display *dpy,
13256 		Drawable drawable,
13257 		const XRenderPictFormat *format,
13258 		c_ulong valuemask,
13259 		const XRenderPictureAttributes *attributes);
13260 
13261 	void XRenderChangePicture (Display				*dpy,
13262 				Picture				picture,
13263 				c_ulong			valuemask,
13264 				const XRenderPictureAttributes  *attributes);
13265 
13266 	void
13267 		XRenderSetPictureClipRectangles (Display	    *dpy,
13268 				Picture	    picture,
13269 				int		    xOrigin,
13270 				int		    yOrigin,
13271 				const XRectangle *rects,
13272 				int		    n);
13273 
13274 	void
13275 		XRenderSetPictureClipRegion (Display	    *dpy,
13276 				Picture	    picture,
13277 				Region	    r);
13278 
13279 	void
13280 		XRenderSetPictureTransform (Display	    *dpy,
13281 				Picture	    picture,
13282 				XTransform	    *transform);
13283 
13284 	void
13285 		XRenderFreePicture (Display                   *dpy,
13286 				Picture                   picture);
13287 
13288 	void
13289 		XRenderComposite (Display   *dpy,
13290 				int	    op,
13291 				Picture   src,
13292 				Picture   mask,
13293 				Picture   dst,
13294 				int	    src_x,
13295 				int	    src_y,
13296 				int	    mask_x,
13297 				int	    mask_y,
13298 				int	    dst_x,
13299 				int	    dst_y,
13300 				uint	width,
13301 				uint	height);
13302 
13303 
13304 	Picture XRenderCreateSolidFill (Display *dpy,
13305 			const XRenderColor *color);
13306 
13307 	Picture XRenderCreateLinearGradient (Display *dpy,
13308 			const XLinearGradient *gradient,
13309 			const XFixed *stops,
13310 			const XRenderColor *colors,
13311 			int nstops);
13312 
13313 	Picture XRenderCreateRadialGradient (Display *dpy,
13314 			const XRadialGradient *gradient,
13315 			const XFixed *stops,
13316 			const XRenderColor *colors,
13317 			int nstops);
13318 
13319 	Picture XRenderCreateConicalGradient (Display *dpy,
13320 			const XConicalGradient *gradient,
13321 			const XFixed *stops,
13322 			const XRenderColor *colors,
13323 			int nstops);
13324 
13325 
13326 
13327 	Cursor
13328 		XRenderCreateCursor (Display	    *dpy,
13329 				Picture	    source,
13330 				uint   x,
13331 				uint   y);
13332 
13333 	XFilters *
13334 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13335 
13336 	void
13337 		XRenderSetPictureFilter (Display    *dpy,
13338 				Picture    picture,
13339 				const char *filter,
13340 				XFixed	    *params,
13341 				int	    nparams);
13342 
13343 	Cursor
13344 		XRenderCreateAnimCursor (Display	*dpy,
13345 				int		ncursor,
13346 				XAnimCursor	*cursors);
13347 }
13348 
13349 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13350 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13351 
13352 	/* XRender } */
13353 
13354 	/* Xrandr { */
13355 
13356 struct XRRMonitorInfo {
13357     Atom name;
13358     Bool primary;
13359     Bool automatic;
13360     int noutput;
13361     int x;
13362     int y;
13363     int width;
13364     int height;
13365     int mwidth;
13366     int mheight;
13367     /*RROutput*/ void *outputs;
13368 }
13369 
13370 struct XRRScreenChangeNotifyEvent {
13371     int type;                   /* event base */
13372     c_ulong serial;       /* # of last request processed by server */
13373     Bool send_event;            /* true if this came from a SendEvent request */
13374     Display *display;           /* Display the event was read from */
13375     Window window;              /* window which selected for this event */
13376     Window root;                /* Root window for changed screen */
13377     Time timestamp;             /* when the screen change occurred */
13378     Time config_timestamp;      /* when the last configuration change */
13379     ushort/*SizeID*/ size_index;
13380     ushort/*SubpixelOrder*/ subpixel_order;
13381     ushort/*Rotation*/ rotation;
13382     int width;
13383     int height;
13384     int mwidth;
13385     int mheight;
13386 }
13387 
13388 enum RRScreenChangeNotify = 0;
13389 
13390 enum RRScreenChangeNotifyMask = 1;
13391 
13392 __gshared int xrrEventBase = -1;
13393 
13394 
13395 interface XRandr {
13396 extern(C) @nogc:
13397 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13398 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13399 
13400 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13401 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13402 
13403 	void XRRSelectInput(Display *dpy, Window window, int mask);
13404 }
13405 
13406 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13407 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13408 	/* Xrandr } */
13409 
13410 	/* Xft { */
13411 
13412 	// actually freetype
13413 	alias void FT_Face;
13414 
13415 	// actually fontconfig
13416 	private alias FcBool = int;
13417 	alias void FcCharSet;
13418 	alias void FcPattern;
13419 	alias void FcResult;
13420 	enum FcEndian { FcEndianBig, FcEndianLittle }
13421 	struct FcFontSet {
13422 		int nfont;
13423 		int sfont;
13424 		FcPattern** fonts;
13425 	}
13426 
13427 	// actually XRegion
13428 	struct BOX {
13429 		short x1, x2, y1, y2;
13430 	}
13431 	struct _XRegion {
13432 		c_long size;
13433 		c_long numRects;
13434 		BOX* rects;
13435 		BOX extents;
13436 	}
13437 
13438 	alias Region = _XRegion*;
13439 
13440 	// ok actually Xft
13441 
13442 	struct XftFontInfo;
13443 
13444 	struct XftFont {
13445 		int         ascent;
13446 		int         descent;
13447 		int         height;
13448 		int         max_advance_width;
13449 		FcCharSet*  charset;
13450 		FcPattern*  pattern;
13451 	}
13452 
13453 	struct XftDraw;
13454 
13455 	struct XftColor {
13456 		c_ulong pixel;
13457 		XRenderColor color;
13458 	}
13459 
13460 	struct XftCharSpec {
13461 		dchar           ucs4;
13462 		short           x;
13463 		short           y;
13464 	}
13465 
13466 	struct XftCharFontSpec {
13467 		XftFont         *font;
13468 		dchar           ucs4;
13469 		short           x;
13470 		short           y;
13471 	}
13472 
13473 	struct XftGlyphSpec {
13474 		uint            glyph;
13475 		short           x;
13476 		short           y;
13477 	}
13478 
13479 	struct XftGlyphFontSpec {
13480 		XftFont         *font;
13481 		uint            glyph;
13482 		short           x;
13483 		short           y;
13484 	}
13485 
13486 	interface Xft {
13487 	extern(C) @nogc pure:
13488 
13489 	Bool XftColorAllocName (Display  *dpy,
13490 				const Visual   *visual,
13491 				Colormap cmap,
13492 				const char     *name,
13493 				XftColor *result);
13494 
13495 	Bool XftColorAllocValue (Display         *dpy,
13496 				Visual          *visual,
13497 				Colormap        cmap,
13498 				const XRenderColor    *color,
13499 				XftColor        *result);
13500 
13501 	void XftColorFree (Display   *dpy,
13502 				Visual    *visual,
13503 				Colormap  cmap,
13504 				XftColor  *color);
13505 
13506 	Bool XftDefaultHasRender (Display *dpy);
13507 
13508 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13509 
13510 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13511 
13512 	XftDraw * XftDrawCreate (Display   *dpy,
13513 		       Drawable  drawable,
13514 		       Visual    *visual,
13515 		       Colormap  colormap);
13516 
13517 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13518 			     Pixmap   bitmap);
13519 
13520 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13521 			    Pixmap  pixmap,
13522 			    int     depth);
13523 
13524 	void XftDrawChange (XftDraw  *draw,
13525 		       Drawable drawable);
13526 
13527 	Display * XftDrawDisplay (XftDraw *draw);
13528 
13529 	Drawable XftDrawDrawable (XftDraw *draw);
13530 
13531 	Colormap XftDrawColormap (XftDraw *draw);
13532 
13533 	Visual * XftDrawVisual (XftDraw *draw);
13534 
13535 	void XftDrawDestroy (XftDraw *draw);
13536 
13537 	Picture XftDrawPicture (XftDraw *draw);
13538 
13539 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13540 
13541 	void XftDrawGlyphs (XftDraw          *draw,
13542 				const XftColor *color,
13543 				XftFont          *pub,
13544 				int              x,
13545 				int              y,
13546 				const uint  *glyphs,
13547 				int              nglyphs);
13548 
13549 	void XftDrawString8 (XftDraw             *draw,
13550 				const XftColor    *color,
13551 				XftFont             *pub,
13552 				int                 x,
13553 				int                 y,
13554 				const char     *string,
13555 				int                 len);
13556 
13557 	void XftDrawString16 (XftDraw            *draw,
13558 				const XftColor   *color,
13559 				XftFont            *pub,
13560 				int                x,
13561 				int                y,
13562 				const wchar   *string,
13563 				int                len);
13564 
13565 	void XftDrawString32 (XftDraw            *draw,
13566 				const XftColor   *color,
13567 				XftFont            *pub,
13568 				int                x,
13569 				int                y,
13570 				const dchar   *string,
13571 				int                len);
13572 
13573 	void XftDrawStringUtf8 (XftDraw          *draw,
13574 				const XftColor *color,
13575 				XftFont          *pub,
13576 				int              x,
13577 				int              y,
13578 				const char  *string,
13579 				int              len);
13580 	void XftDrawStringUtf16 (XftDraw             *draw,
13581 				const XftColor    *color,
13582 				XftFont             *pub,
13583 				int                 x,
13584 				int                 y,
13585 				const char     *string,
13586 				FcEndian            endian,
13587 				int                 len);
13588 
13589 	void XftDrawCharSpec (XftDraw                *draw,
13590 				const XftColor       *color,
13591 				XftFont                *pub,
13592 				const XftCharSpec    *chars,
13593 				int                    len);
13594 
13595 	void XftDrawCharFontSpec (XftDraw                    *draw,
13596 				const XftColor           *color,
13597 				const XftCharFontSpec    *chars,
13598 				int                        len);
13599 
13600 	void XftDrawGlyphSpec (XftDraw               *draw,
13601 				const XftColor      *color,
13602 				XftFont               *pub,
13603 				const XftGlyphSpec  *glyphs,
13604 				int                   len);
13605 
13606 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13607 				const XftColor          *color,
13608 				const XftGlyphFontSpec  *glyphs,
13609 				int                       len);
13610 
13611 	void XftDrawRect (XftDraw            *draw,
13612 				const XftColor   *color,
13613 				int                x,
13614 				int                y,
13615 				uint       width,
13616 				uint       height);
13617 
13618 	Bool XftDrawSetClip (XftDraw     *draw,
13619 				Region      r);
13620 
13621 
13622 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13623 				int                   xOrigin,
13624 				int                   yOrigin,
13625 				const XRectangle    *rects,
13626 				int                   n);
13627 
13628 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13629 				int        mode);
13630 
13631 	void XftGlyphExtents (Display            *dpy,
13632 				XftFont            *pub,
13633 				const uint    *glyphs,
13634 				int                nglyphs,
13635 				XGlyphInfo         *extents);
13636 
13637 	void XftTextExtents8 (Display            *dpy,
13638 				XftFont            *pub,
13639 				const char    *string,
13640 				int                len,
13641 				XGlyphInfo         *extents);
13642 
13643 	void XftTextExtents16 (Display           *dpy,
13644 				XftFont           *pub,
13645 				const wchar  *string,
13646 				int               len,
13647 				XGlyphInfo        *extents);
13648 
13649 	void XftTextExtents32 (Display           *dpy,
13650 				XftFont           *pub,
13651 				const dchar  *string,
13652 				int               len,
13653 				XGlyphInfo        *extents);
13654 
13655 	void XftTextExtentsUtf8 (Display         *dpy,
13656 				XftFont         *pub,
13657 				const char *string,
13658 				int             len,
13659 				XGlyphInfo      *extents);
13660 
13661 	void XftTextExtentsUtf16 (Display            *dpy,
13662 				XftFont            *pub,
13663 				const char    *string,
13664 				FcEndian           endian,
13665 				int                len,
13666 				XGlyphInfo         *extents);
13667 
13668 	FcPattern * XftFontMatch (Display           *dpy,
13669 				int               screen,
13670 				const FcPattern *pattern,
13671 				FcResult          *result);
13672 
13673 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13674 
13675 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13676 
13677 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13678 
13679 	FT_Face XftLockFace (XftFont *pub);
13680 
13681 	void XftUnlockFace (XftFont *pub);
13682 
13683 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13684 
13685 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13686 
13687 	dchar XftFontInfoHash (const XftFontInfo *fi);
13688 
13689 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13690 
13691 	XftFont * XftFontOpenInfo (Display        *dpy,
13692 				FcPattern      *pattern,
13693 				XftFontInfo    *fi);
13694 
13695 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13696 
13697 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13698 
13699 	void XftFontClose (Display *dpy, XftFont *pub);
13700 
13701 	FcBool XftInitFtLibrary();
13702 	void XftFontLoadGlyphs (Display          *dpy,
13703 				XftFont          *pub,
13704 				FcBool           need_bitmaps,
13705 				const uint  *glyphs,
13706 				int              nglyph);
13707 
13708 	void XftFontUnloadGlyphs (Display            *dpy,
13709 				XftFont            *pub,
13710 				const uint    *glyphs,
13711 				int                nglyph);
13712 
13713 	FcBool XftFontCheckGlyph (Display  *dpy,
13714 				XftFont  *pub,
13715 				FcBool   need_bitmaps,
13716 				uint  glyph,
13717 				uint  *missing,
13718 				int      *nmissing);
13719 
13720 	FcBool XftCharExists (Display      *dpy,
13721 				XftFont      *pub,
13722 				dchar    ucs4);
13723 
13724 	uint XftCharIndex (Display       *dpy,
13725 				XftFont       *pub,
13726 				dchar      ucs4);
13727 	FcBool XftInit (const char *config);
13728 
13729 	int XftGetVersion ();
13730 
13731 	FcFontSet * XftListFonts (Display   *dpy,
13732 				int       screen,
13733 				...);
13734 
13735 	FcPattern *XftNameParse (const char *name);
13736 
13737 	void XftGlyphRender (Display         *dpy,
13738 				int             op,
13739 				Picture         src,
13740 				XftFont         *pub,
13741 				Picture         dst,
13742 				int             srcx,
13743 				int             srcy,
13744 				int             x,
13745 				int             y,
13746 				const uint *glyphs,
13747 				int             nglyphs);
13748 
13749 	void XftGlyphSpecRender (Display                 *dpy,
13750 				int                     op,
13751 				Picture                 src,
13752 				XftFont                 *pub,
13753 				Picture                 dst,
13754 				int                     srcx,
13755 				int                     srcy,
13756 				const XftGlyphSpec    *glyphs,
13757 				int                     nglyphs);
13758 
13759 	void XftCharSpecRender (Display              *dpy,
13760 				int                  op,
13761 				Picture              src,
13762 				XftFont              *pub,
13763 				Picture              dst,
13764 				int                  srcx,
13765 				int                  srcy,
13766 				const XftCharSpec  *chars,
13767 				int                  len);
13768 	void XftGlyphFontSpecRender (Display                     *dpy,
13769 				int                         op,
13770 				Picture                     src,
13771 				Picture                     dst,
13772 				int                         srcx,
13773 				int                         srcy,
13774 				const XftGlyphFontSpec    *glyphs,
13775 				int                         nglyphs);
13776 
13777 	void XftCharFontSpecRender (Display                  *dpy,
13778 				int                      op,
13779 				Picture                  src,
13780 				Picture                  dst,
13781 				int                      srcx,
13782 				int                      srcy,
13783 				const XftCharFontSpec  *chars,
13784 				int                      len);
13785 
13786 	void XftTextRender8 (Display         *dpy,
13787 				int             op,
13788 				Picture         src,
13789 				XftFont         *pub,
13790 				Picture         dst,
13791 				int             srcx,
13792 				int             srcy,
13793 				int             x,
13794 				int             y,
13795 				const char *string,
13796 				int             len);
13797 	void XftTextRender16 (Display            *dpy,
13798 				int                op,
13799 				Picture            src,
13800 				XftFont            *pub,
13801 				Picture            dst,
13802 				int                srcx,
13803 				int                srcy,
13804 				int                x,
13805 				int                y,
13806 				const wchar   *string,
13807 				int                len);
13808 
13809 	void XftTextRender16BE (Display          *dpy,
13810 				int              op,
13811 				Picture          src,
13812 				XftFont          *pub,
13813 				Picture          dst,
13814 				int              srcx,
13815 				int              srcy,
13816 				int              x,
13817 				int              y,
13818 				const char  *string,
13819 				int              len);
13820 
13821 	void XftTextRender16LE (Display          *dpy,
13822 				int              op,
13823 				Picture          src,
13824 				XftFont          *pub,
13825 				Picture          dst,
13826 				int              srcx,
13827 				int              srcy,
13828 				int              x,
13829 				int              y,
13830 				const char  *string,
13831 				int              len);
13832 
13833 	void XftTextRender32 (Display            *dpy,
13834 				int                op,
13835 				Picture            src,
13836 				XftFont            *pub,
13837 				Picture            dst,
13838 				int                srcx,
13839 				int                srcy,
13840 				int                x,
13841 				int                y,
13842 				const dchar   *string,
13843 				int                len);
13844 
13845 	void XftTextRender32BE (Display          *dpy,
13846 				int              op,
13847 				Picture          src,
13848 				XftFont          *pub,
13849 				Picture          dst,
13850 				int              srcx,
13851 				int              srcy,
13852 				int              x,
13853 				int              y,
13854 				const char  *string,
13855 				int              len);
13856 
13857 	void XftTextRender32LE (Display          *dpy,
13858 				int              op,
13859 				Picture          src,
13860 				XftFont          *pub,
13861 				Picture          dst,
13862 				int              srcx,
13863 				int              srcy,
13864 				int              x,
13865 				int              y,
13866 				const char  *string,
13867 				int              len);
13868 
13869 	void XftTextRenderUtf8 (Display          *dpy,
13870 				int              op,
13871 				Picture          src,
13872 				XftFont          *pub,
13873 				Picture          dst,
13874 				int              srcx,
13875 				int              srcy,
13876 				int              x,
13877 				int              y,
13878 				const char  *string,
13879 				int              len);
13880 
13881 	void XftTextRenderUtf16 (Display         *dpy,
13882 				int             op,
13883 				Picture         src,
13884 				XftFont         *pub,
13885 				Picture         dst,
13886 				int             srcx,
13887 				int             srcy,
13888 				int             x,
13889 				int             y,
13890 				const char *string,
13891 				FcEndian        endian,
13892 				int             len);
13893 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13894 
13895 	}
13896 
13897 	interface FontConfig {
13898 	extern(C) @nogc pure:
13899 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13900 		void FcFontSetDestroy(FcFontSet*);
13901 		char* FcNameUnparse(const FcPattern *);
13902 	}
13903 
13904 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13905 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13906 
13907 
13908 	/* Xft } */
13909 
13910 	class XDisconnectException : Exception {
13911 		bool userRequested;
13912 		this(bool userRequested = true) {
13913 			this.userRequested = userRequested;
13914 			super("X disconnected");
13915 		}
13916 	}
13917 
13918 	/++
13919 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
13920 
13921 		Please note that it returns
13922 	+/
13923 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
13924 
13925 		static XErrorEvent[] errorBuffer;
13926 
13927 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
13928 			errorBuffer ~= *evt;
13929 			return 0;
13930 		}
13931 
13932 		auto savedErrorHandler = XSetErrorHandler(&handler);
13933 
13934 		try {
13935 			dg();
13936 		} finally {
13937 			XSync(XDisplayConnection.get, 0/*False*/);
13938 			XSetErrorHandler(savedErrorHandler);
13939 		}
13940 
13941 		auto bfr = errorBuffer;
13942 		errorBuffer = null;
13943 
13944 		return bfr;
13945 	}
13946 
13947 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
13948 	class XDisplayConnection {
13949 		private __gshared Display* display;
13950 		private __gshared XIM xim;
13951 		private __gshared char* displayName;
13952 
13953 		private __gshared int connectionSequence_;
13954 		private __gshared bool isLocal_;
13955 
13956 		/// use this for lazy caching when reconnection
13957 		static int connectionSequenceNumber() { return connectionSequence_; }
13958 
13959 		/++
13960 			Guesses if the connection appears to be local.
13961 
13962 			History:
13963 				Added June 3, 2021
13964 		+/
13965 		static @property bool isLocal() nothrow @trusted @nogc {
13966 			return isLocal_;
13967 		}
13968 
13969 		/// Attempts recreation of state, may require application assistance
13970 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
13971 		/// then call this, and if successful, reenter the loop.
13972 		static void discardAndRecreate(string newDisplayString = null) {
13973 			if(insideXEventLoop)
13974 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
13975 
13976 			// 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
13977 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
13978 
13979 			foreach(handle; chnenhm) {
13980 				handle.discardConnectionState();
13981 			}
13982 
13983 			discardState();
13984 
13985 			if(newDisplayString !is null)
13986 				setDisplayName(newDisplayString);
13987 
13988 			auto display = get();
13989 
13990 			foreach(handle; chnenhm) {
13991 				handle.recreateAfterDisconnect();
13992 			}
13993 		}
13994 
13995 		private __gshared EventMask rootEventMask;
13996 
13997 		/++
13998 			Requests the specified input from the root window on the connection, in addition to any other request.
13999 
14000 
14001 			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.
14002 
14003 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14004 		+/
14005 		static void addRootInput(EventMask mask) {
14006 			auto old = rootEventMask;
14007 			rootEventMask |= mask;
14008 			get(); // to ensure display connected
14009 			if(display !is null && rootEventMask != old)
14010 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14011 		}
14012 
14013 		static void discardState() {
14014 			freeImages();
14015 
14016 			foreach(atomPtr; interredAtoms)
14017 				*atomPtr = 0;
14018 			interredAtoms = null;
14019 			interredAtoms.assumeSafeAppend();
14020 
14021 			ScreenPainterImplementation.fontAttempted = false;
14022 			ScreenPainterImplementation.defaultfont = null;
14023 			ScreenPainterImplementation.defaultfontset = null;
14024 
14025 			Image.impl.xshmQueryCompleted = false;
14026 			Image.impl._xshmAvailable = false;
14027 
14028 			SimpleWindow.nativeMapping = null;
14029 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14030 			// GlobalHotkeyManager
14031 
14032 			display = null;
14033 			xim = null;
14034 		}
14035 
14036 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14037 		private static void createXIM () {
14038 			import core.stdc.locale : setlocale, LC_ALL;
14039 			import core.stdc.stdio : stderr, fprintf;
14040 			import core.stdc.stdlib : free;
14041 			import core.stdc.string : strdup;
14042 
14043 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14044 
14045 			auto olocale = strdup(setlocale(LC_ALL, null));
14046 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14047 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14048 
14049 			//fprintf(stderr, "opening IM...\n");
14050 			foreach (string s; mtry) {
14051 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14052 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14053 			}
14054 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14055 		}
14056 
14057 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14058 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14059 		static struct ImgList {
14060 			size_t img; // class; hide it from GC
14061 			ImgList* next;
14062 		}
14063 
14064 		static __gshared ImgList* imglist = null;
14065 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14066 
14067 		static void registerImage (Image img) {
14068 			if (!imglistLocked && img !is null) {
14069 				import core.stdc.stdlib : malloc;
14070 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14071 				assert(it !is null); // do proper checks
14072 				it.img = cast(size_t)cast(void*)img;
14073 				it.next = imglist;
14074 				imglist = it;
14075 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14076 			}
14077 		}
14078 
14079 		static void unregisterImage (Image img) {
14080 			if (!imglistLocked && img !is null) {
14081 				import core.stdc.stdlib : free;
14082 				ImgList* prev = null;
14083 				ImgList* cur = imglist;
14084 				while (cur !is null) {
14085 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14086 					prev = cur;
14087 					cur = cur.next;
14088 				}
14089 				if (cur !is null) {
14090 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14091 					free(cur);
14092 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14093 				} else {
14094 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14095 				}
14096 			}
14097 		}
14098 
14099 		static void freeImages () { // needed for discardAndRecreate
14100 			imglistLocked = true;
14101 			scope(exit) imglistLocked = false;
14102 			ImgList* cur = imglist;
14103 			ImgList* next = null;
14104 			while (cur !is null) {
14105 				import core.stdc.stdlib : free;
14106 				next = cur.next;
14107 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14108 				(cast(Image)cast(void*)cur.img).dispose();
14109 				free(cur);
14110 				cur = next;
14111 			}
14112 			imglist = null;
14113 		}
14114 
14115 		/// can be used to override normal handling of display name
14116 		/// from environment and/or command line
14117 		static setDisplayName(string newDisplayName) {
14118 			displayName = cast(char*) (newDisplayName ~ '\0');
14119 		}
14120 
14121 		/// resets to the default display string
14122 		static resetDisplayName() {
14123 			displayName = null;
14124 		}
14125 
14126 		///
14127 		static Display* get() {
14128 			if(display is null) {
14129 				if(!librariesSuccessfullyLoaded)
14130 					throw new Exception("Unable to load X11 client libraries");
14131 				display = XOpenDisplay(displayName);
14132 
14133 				isLocal_ = false;
14134 
14135 				connectionSequence_++;
14136 				if(display is null)
14137 					throw new Exception("Unable to open X display");
14138 
14139 				auto str = display.display_name;
14140 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14141 				// and otherwise it probably isn't
14142 				if(str is null || (str[0] != ':' && str[0] != '/'))
14143 					isLocal_ = false;
14144 				else
14145 					isLocal_ = true;
14146 
14147 				debug(sdpy_x_errors) {
14148 					XSetErrorHandler(&adrlogger);
14149 					XSynchronize(display, true);
14150 
14151 					extern(C) int wtf() {
14152 						if(errorHappened) {
14153 							asm { int 3; }
14154 							errorHappened = false;
14155 						}
14156 						return 0;
14157 					}
14158 					XSetAfterFunction(display, &wtf);
14159 				}
14160 
14161 
14162 				XSetIOErrorHandler(&x11ioerrCB);
14163 				Bool sup;
14164 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14165 				createXIM();
14166 				version(with_eventloop) {
14167 					import arsd.eventloop;
14168 					addFileEventListeners(display.fd, &eventListener, null, null);
14169 				}
14170 			}
14171 
14172 			return display;
14173 		}
14174 
14175 		extern(C)
14176 		static int x11ioerrCB(Display* dpy) {
14177 			throw new XDisconnectException(false);
14178 		}
14179 
14180 		version(with_eventloop) {
14181 			import arsd.eventloop;
14182 			static void eventListener(OsFileHandle fd) {
14183 				//this.mtLock();
14184 				//scope(exit) this.mtUnlock();
14185 				while(XPending(display))
14186 					doXNextEvent(display);
14187 			}
14188 		}
14189 
14190 		// close connection on program exit -- we need this to properly free all images
14191 		static ~this () {
14192 			// the gui thread must clean up after itself or else Xlib might deadlock
14193 			// using this flag on any thread destruction is the easiest way i know of
14194 			// (shared static this is run by the LAST thread to exit, which may not be
14195 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14196 			if(thisIsGuiThread)
14197 				close();
14198 		}
14199 
14200 		///
14201 		static void close() {
14202 			if(display is null)
14203 				return;
14204 
14205 			version(with_eventloop) {
14206 				import arsd.eventloop;
14207 				removeFileEventListeners(display.fd);
14208 			}
14209 
14210 			// now remove all registered images to prevent shared memory leaks
14211 			freeImages();
14212 
14213 			// tbh I don't know why it is doing this but like if this happens to run
14214 			// from the other thread there's frequent hanging inside here.
14215 			if(thisIsGuiThread)
14216 				XCloseDisplay(display);
14217 			display = null;
14218 		}
14219 	}
14220 
14221 	mixin template NativeImageImplementation() {
14222 		XImage* handle;
14223 		ubyte* rawData;
14224 
14225 		XShmSegmentInfo shminfo;
14226 
14227 		__gshared bool xshmQueryCompleted;
14228 		__gshared bool _xshmAvailable;
14229 		public static @property bool xshmAvailable() {
14230 			if(!xshmQueryCompleted) {
14231 				int i1, i2, i3;
14232 				xshmQueryCompleted = true;
14233 
14234 				if(!XDisplayConnection.isLocal)
14235 					_xshmAvailable = false;
14236 				else
14237 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14238 			}
14239 			return _xshmAvailable;
14240 		}
14241 
14242 		bool usingXshm;
14243 	final:
14244 
14245 		private __gshared bool xshmfailed;
14246 
14247 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14248 			auto display = XDisplayConnection.get();
14249 			assert(display !is null);
14250 			auto screen = DefaultScreen(display);
14251 
14252 			// it will only use shared memory for somewhat largish images,
14253 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14254 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14255 
14256 
14257 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14258 				// the actual use still fails. For example, if the program is in a container and permission denied
14259 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14260 				//
14261 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14262 
14263 
14264 				// synchronize so preexisting buffers are clear
14265 				XSync(display, false);
14266 				xshmfailed = false;
14267 
14268 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14269 
14270 
14271 				usingXshm = true;
14272 				handle = XShmCreateImage(
14273 					display,
14274 					DefaultVisual(display, screen),
14275 					enableAlpha ? 32: 24,
14276 					ImageFormat.ZPixmap,
14277 					null,
14278 					&shminfo,
14279 					width, height);
14280 				if(handle is null)
14281 					goto abortXshm1;
14282 
14283 				if(handle.bytes_per_line != 4 * width)
14284 					goto abortXshm2;
14285 
14286 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14287 				if(shminfo.shmid < 0)
14288 					goto abortXshm3;
14289 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14290 				if(rawData == cast(ubyte*) -1)
14291 					goto abortXshm4;
14292 				shminfo.readOnly = 0;
14293 				XShmAttach(display, &shminfo);
14294 
14295 				// and now to the final error check to ensure it actually worked.
14296 				XSync(display, false);
14297 				if(xshmfailed)
14298 					goto abortXshm5;
14299 
14300 				XSetErrorHandler(oldErrorHandler);
14301 
14302 				XDisplayConnection.registerImage(this);
14303 				// if I don't flush here there's a chance the dtor will run before the
14304 				// ctor and lead to a bad value X error. While this hurts the efficiency
14305 				// it is local anyway so prolly better to keep it simple
14306 				XFlush(display);
14307 
14308 				return;
14309 
14310 				abortXshm5:
14311 					shmdt(shminfo.shmaddr);
14312 					rawData = null;
14313 
14314 				abortXshm4:
14315 					shmctl(shminfo.shmid, IPC_RMID, null);
14316 
14317 				abortXshm3:
14318 					// nothing needed, the shmget failed so there's nothing to free
14319 
14320 				abortXshm2:
14321 					XDestroyImage(handle);
14322 					handle = null;
14323 
14324 				abortXshm1:
14325 					XSetErrorHandler(oldErrorHandler);
14326 					usingXshm = false;
14327 					handle = null;
14328 
14329 					shminfo = typeof(shminfo).init;
14330 
14331 					_xshmAvailable = false; // don't try again in the future
14332 
14333 					//import std.stdio; writeln("fallingback");
14334 
14335 					goto fallback;
14336 
14337 			} else {
14338 				fallback:
14339 
14340 				if (forcexshm) throw new Exception("can't create XShm Image");
14341 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14342 				import core.stdc.stdlib : malloc;
14343 				rawData = cast(ubyte*) malloc(width * height * 4);
14344 
14345 				handle = XCreateImage(
14346 					display,
14347 					DefaultVisual(display, screen),
14348 					enableAlpha ? 32 : 24, // bpp
14349 					ImageFormat.ZPixmap,
14350 					0, // offset
14351 					rawData,
14352 					width, height,
14353 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14354 			}
14355 		}
14356 
14357 		void dispose() {
14358 			// note: this calls free(rawData) for us
14359 			if(handle) {
14360 				if (usingXshm) {
14361 					XDisplayConnection.unregisterImage(this);
14362 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14363 				}
14364 				XDestroyImage(handle);
14365 				if(usingXshm) {
14366 					shmdt(shminfo.shmaddr);
14367 					shmctl(shminfo.shmid, IPC_RMID, null);
14368 				}
14369 				handle = null;
14370 			}
14371 		}
14372 
14373 		Color getPixel(int x, int y) {
14374 			auto offset = (y * width + x) * 4;
14375 			Color c;
14376 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14377 			c.b = rawData[offset + 0];
14378 			c.g = rawData[offset + 1];
14379 			c.r = rawData[offset + 2];
14380 			if(enableAlpha)
14381 				c.unPremultiply;
14382 			return c;
14383 		}
14384 
14385 		void setPixel(int x, int y, Color c) {
14386 			if(enableAlpha)
14387 				c.premultiply();
14388 			auto offset = (y * width + x) * 4;
14389 			rawData[offset + 0] = c.b;
14390 			rawData[offset + 1] = c.g;
14391 			rawData[offset + 2] = c.r;
14392 			if(enableAlpha)
14393 				rawData[offset + 3] = c.a;
14394 		}
14395 
14396 		void convertToRgbaBytes(ubyte[] where) {
14397 			assert(where.length == this.width * this.height * 4);
14398 
14399 			// if rawData had a length....
14400 			//assert(rawData.length == where.length);
14401 			for(int idx = 0; idx < where.length; idx += 4) {
14402 				where[idx + 0] = rawData[idx + 2]; // r
14403 				where[idx + 1] = rawData[idx + 1]; // g
14404 				where[idx + 2] = rawData[idx + 0]; // b
14405 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14406 
14407 				if(enableAlpha)
14408 					unPremultiplyRgba(where[idx .. idx + 4]);
14409 			}
14410 		}
14411 
14412 		void setFromRgbaBytes(in ubyte[] where) {
14413 			assert(where.length == this.width * this.height * 4);
14414 
14415 			// if rawData had a length....
14416 			//assert(rawData.length == where.length);
14417 			for(int idx = 0; idx < where.length; idx += 4) {
14418 				rawData[idx + 2] = where[idx + 0]; // r
14419 				rawData[idx + 1] = where[idx + 1]; // g
14420 				rawData[idx + 0] = where[idx + 2]; // b
14421 				if(enableAlpha) {
14422 					rawData[idx + 3] = where[idx + 3]; // a
14423 					premultiplyBgra(rawData[idx .. idx + 4]);
14424 				}
14425 			}
14426 		}
14427 
14428 	}
14429 
14430 	mixin template NativeSimpleWindowImplementation() {
14431 		GC gc;
14432 		Window window;
14433 		Display* display;
14434 
14435 		Pixmap buffer;
14436 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14437 		XIC xic; // input context
14438 		int curHidden = 0; // counter
14439 		Cursor blankCurPtr = 0;
14440 		int cursorSequenceNumber = 0;
14441 		int warpEventCount = 0; // number of mouse movement events to eat
14442 
14443 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14444 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14445 
14446 		version(without_opengl) {} else
14447 		GLXContext glc;
14448 
14449 		private void fixFixedSize(bool forced=false) (int width, int height) {
14450 			if (forced || this.resizability == Resizability.fixedSize) {
14451 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14452 				XSizeHints sh;
14453 				static if (!forced) {
14454 					c_long spr;
14455 					XGetWMNormalHints(display, window, &sh, &spr);
14456 					sh.flags |= PMaxSize | PMinSize;
14457 				} else {
14458 					sh.flags = PMaxSize | PMinSize;
14459 				}
14460 				sh.min_width = width;
14461 				sh.min_height = height;
14462 				sh.max_width = width;
14463 				sh.max_height = height;
14464 				XSetWMNormalHints(display, window, &sh);
14465 				//XFlush(display);
14466 			}
14467 		}
14468 
14469 		ScreenPainter getPainter(bool manualInvalidations) {
14470 			return ScreenPainter(this, window, manualInvalidations);
14471 		}
14472 
14473 		void move(int x, int y) {
14474 			XMoveWindow(display, window, x, y);
14475 		}
14476 
14477 		void resize(int w, int h) {
14478 			if (w < 1) w = 1;
14479 			if (h < 1) h = 1;
14480 			XResizeWindow(display, window, w, h);
14481 
14482 			// calling this now to avoid waiting for the server to
14483 			// acknowledge the resize; draws without returning to the
14484 			// event loop will thus actually work. the server's event
14485 			// btw might overrule this and resize it again
14486 			recordX11Resize(display, this, w, h);
14487 
14488 			updateOpenglViewportIfNeeded(w, h);
14489 		}
14490 
14491 		void moveResize (int x, int y, int w, int h) {
14492 			if (w < 1) w = 1;
14493 			if (h < 1) h = 1;
14494 			XMoveResizeWindow(display, window, x, y, w, h);
14495 			updateOpenglViewportIfNeeded(w, h);
14496 		}
14497 
14498 		void hideCursor () {
14499 			if (curHidden++ == 0) {
14500 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14501 					static const(char)[1] cmbmp = 0;
14502 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14503 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14504 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14505 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14506 					XFreePixmap(display, pm);
14507 				}
14508 				XDefineCursor(display, window, blankCurPtr);
14509 			}
14510 		}
14511 
14512 		void showCursor () {
14513 			if (--curHidden == 0) XUndefineCursor(display, window);
14514 		}
14515 
14516 		void warpMouse (int x, int y) {
14517 			// here i will send dummy "ignore next mouse motion" event,
14518 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14519 			// and we don't need to report it to the user (as warping is
14520 			// used when the user needs movement deltas).
14521 			//XClientMessageEvent xclient;
14522 			XEvent e;
14523 			e.xclient.type = EventType.ClientMessage;
14524 			e.xclient.window = window;
14525 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14526 			e.xclient.format = 32;
14527 			e.xclient.data.l[0] = 0;
14528 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14529 			//{ 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]); }
14530 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14531 			// now warp pointer...
14532 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14533 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14534 			// ...and flush
14535 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14536 			XFlush(display);
14537 		}
14538 
14539 		void sendDummyEvent () {
14540 			// here i will send dummy event to ping event queue
14541 			XEvent e;
14542 			e.xclient.type = EventType.ClientMessage;
14543 			e.xclient.window = window;
14544 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14545 			e.xclient.format = 32;
14546 			e.xclient.data.l[0] = 0;
14547 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14548 			XFlush(display);
14549 		}
14550 
14551 		void setTitle(string title) {
14552 			if (title.ptr is null) title = "";
14553 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14554 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14555 			XTextProperty windowName;
14556 			windowName.value = title.ptr;
14557 			windowName.encoding = XA_UTF8; //XA_STRING;
14558 			windowName.format = 8;
14559 			windowName.nitems = cast(uint)title.length;
14560 			XSetWMName(display, window, &windowName);
14561 			char[1024] namebuf = 0;
14562 			auto maxlen = namebuf.length-1;
14563 			if (maxlen > title.length) maxlen = title.length;
14564 			namebuf[0..maxlen] = title[0..maxlen];
14565 			XStoreName(display, window, namebuf.ptr);
14566 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14567 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14568 		}
14569 
14570 		string[] getTitles() {
14571 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14572 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14573 			XTextProperty textProp;
14574 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14575 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14576 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14577 				} else
14578 					return [];
14579 			} else
14580 				return null;
14581 		}
14582 
14583 		string getTitle() {
14584 			auto titles = getTitles();
14585 			return titles.length ? titles[0] : null;
14586 		}
14587 
14588 		void setMinSize (int minwidth, int minheight) {
14589 			import core.stdc.config : c_long;
14590 			if (minwidth < 1) minwidth = 1;
14591 			if (minheight < 1) minheight = 1;
14592 			XSizeHints sh;
14593 			c_long spr;
14594 			XGetWMNormalHints(display, window, &sh, &spr);
14595 			sh.min_width = minwidth;
14596 			sh.min_height = minheight;
14597 			sh.flags |= PMinSize;
14598 			XSetWMNormalHints(display, window, &sh);
14599 			flushGui();
14600 		}
14601 
14602 		void setMaxSize (int maxwidth, int maxheight) {
14603 			import core.stdc.config : c_long;
14604 			if (maxwidth < 1) maxwidth = 1;
14605 			if (maxheight < 1) maxheight = 1;
14606 			XSizeHints sh;
14607 			c_long spr;
14608 			XGetWMNormalHints(display, window, &sh, &spr);
14609 			sh.max_width = maxwidth;
14610 			sh.max_height = maxheight;
14611 			sh.flags |= PMaxSize;
14612 			XSetWMNormalHints(display, window, &sh);
14613 			flushGui();
14614 		}
14615 
14616 		void setResizeGranularity (int granx, int grany) {
14617 			import core.stdc.config : c_long;
14618 			if (granx < 1) granx = 1;
14619 			if (grany < 1) grany = 1;
14620 			XSizeHints sh;
14621 			c_long spr;
14622 			XGetWMNormalHints(display, window, &sh, &spr);
14623 			sh.width_inc = granx;
14624 			sh.height_inc = grany;
14625 			sh.flags |= PResizeInc;
14626 			XSetWMNormalHints(display, window, &sh);
14627 			flushGui();
14628 		}
14629 
14630 		void setOpacity (uint opacity) {
14631 			arch_ulong o = opacity;
14632 			if (opacity == uint.max)
14633 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14634 			else
14635 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14636 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14637 		}
14638 
14639 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14640 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14641 			display = XDisplayConnection.get();
14642 			auto screen = DefaultScreen(display);
14643 
14644 			bool overrideRedirect = false;
14645 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14646 				overrideRedirect = true;
14647 
14648 			version(without_opengl) {}
14649 			else {
14650 				if(opengl == OpenGlOptions.yes) {
14651 					GLXFBConfig fbconf = null;
14652 					XVisualInfo* vi = null;
14653 					bool useLegacy = false;
14654 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14655 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14656 						int[23] visualAttribs = [
14657 							GLX_X_RENDERABLE , 1/*True*/,
14658 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14659 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14660 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14661 							GLX_RED_SIZE     , 8,
14662 							GLX_GREEN_SIZE   , 8,
14663 							GLX_BLUE_SIZE    , 8,
14664 							GLX_ALPHA_SIZE   , 8,
14665 							GLX_DEPTH_SIZE   , 24,
14666 							GLX_STENCIL_SIZE , 8,
14667 							GLX_DOUBLEBUFFER , 1/*True*/,
14668 							0/*None*/,
14669 						];
14670 						int fbcount;
14671 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14672 						if (fbcount == 0) {
14673 							useLegacy = true; // try to do at least something
14674 						} else {
14675 							// pick the FB config/visual with the most samples per pixel
14676 							int bestidx = -1, bestns = -1;
14677 							foreach (int fbi; 0..fbcount) {
14678 								int sb, samples;
14679 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14680 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14681 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14682 							}
14683 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14684 							fbconf = fbc[bestidx];
14685 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14686 							XFree(fbc);
14687 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14688 						}
14689 					}
14690 					if (vi is null || useLegacy) {
14691 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14692 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14693 						useLegacy = true;
14694 					}
14695 					if (vi is null) throw new Exception("no open gl visual found");
14696 
14697 					XSetWindowAttributes swa;
14698 					auto root = RootWindow(display, screen);
14699 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14700 
14701 					swa.override_redirect = overrideRedirect;
14702 
14703 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14704 						0, 0, width, height,
14705 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14706 
14707 					// now try to use `glXCreateContextAttribsARB()` if it's here
14708 					if (!useLegacy) {
14709 						// request fairly advanced context, even with stencil buffer!
14710 						int[9] contextAttribs = [
14711 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14712 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14713 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14714 							// for modern context, set "forward compatibility" flag too
14715 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14716 							0/*None*/,
14717 						];
14718 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14719 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14720 							sdpyOpenGLContextVersion = 0;
14721 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14722 						}
14723 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14724 					} else {
14725 						// fallback to old GLX call
14726 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14727 							sdpyOpenGLContextVersion = 0;
14728 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14729 						}
14730 					}
14731 					// sync to ensure any errors generated are processed
14732 					XSync(display, 0/*False*/);
14733 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14734 					if(glc is null)
14735 						throw new Exception("glc");
14736 				}
14737 			}
14738 
14739 			if(opengl == OpenGlOptions.no) {
14740 
14741 				XSetWindowAttributes swa;
14742 				swa.background_pixel = WhitePixel(display, screen);
14743 				swa.border_pixel = BlackPixel(display, screen);
14744 				swa.override_redirect = overrideRedirect;
14745 				auto root = RootWindow(display, screen);
14746 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14747 
14748 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14749 					0, 0, width, height,
14750 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14751 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14752 
14753 
14754 
14755 				/*
14756 				window = XCreateSimpleWindow(
14757 					display,
14758 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14759 					0, 0, // x, y
14760 					width, height,
14761 					1, // border width
14762 					BlackPixel(display, screen), // border
14763 					WhitePixel(display, screen)); // background
14764 				*/
14765 
14766 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14767 				bufferw = width;
14768 				bufferh = height;
14769 
14770 				gc = DefaultGC(display, screen);
14771 
14772 				// clear out the buffer to get us started...
14773 				XSetForeground(display, gc, WhitePixel(display, screen));
14774 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14775 				XSetForeground(display, gc, BlackPixel(display, screen));
14776 			}
14777 
14778 			// input context
14779 			//TODO: create this only for top-level windows, and reuse that?
14780 			populateXic();
14781 
14782 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14783 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14784 			// window class
14785 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14786 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14787 				XClassHint klass;
14788 				XWMHints wh;
14789 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14790 					wh.input = true;
14791 					wh.flags |= InputHint;
14792 				}
14793 				XSizeHints size;
14794 				klass.res_name = sdpyWindowClassStr;
14795 				klass.res_class = sdpyWindowClassStr;
14796 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14797 			}
14798 
14799 			setTitle(title);
14800 			SimpleWindow.nativeMapping[window] = this;
14801 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14802 
14803 			// This gives our window a close button
14804 			if (windowType != WindowTypes.eventOnly) {
14805 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14806 				int useAtoms;
14807 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14808 					useAtoms = 2;
14809 				} else {
14810 					useAtoms = 1;
14811 				}
14812 				assert(useAtoms <= atoms.length);
14813 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14814 			}
14815 
14816 			// FIXME: windowType and customizationFlags
14817 			Atom[8] wsatoms; // here, due to goto
14818 			int wmsacount = 0; // here, due to goto
14819 
14820 			try
14821 			final switch(windowType) {
14822 				case WindowTypes.normal:
14823 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14824 				break;
14825 				case WindowTypes.undecorated:
14826 					motifHideDecorations();
14827 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14828 				break;
14829 				case WindowTypes.eventOnly:
14830 					_hidden = true;
14831 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14832 					goto hiddenWindow;
14833 				//break;
14834 				case WindowTypes.nestedChild:
14835 					// handled in XCreateWindow calls
14836 				break;
14837 
14838 				case WindowTypes.dropdownMenu:
14839 					motifHideDecorations();
14840 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14841 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14842 				break;
14843 				case WindowTypes.popupMenu:
14844 					motifHideDecorations();
14845 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14846 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14847 				break;
14848 				case WindowTypes.notification:
14849 					motifHideDecorations();
14850 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14851 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14852 				break;
14853 				case WindowTypes.minimallyWrapped:
14854 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14855 				/+
14856 				case WindowTypes.menu:
14857 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14858 					motifHideDecorations();
14859 				break;
14860 				case WindowTypes.desktop:
14861 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14862 				break;
14863 				case WindowTypes.dock:
14864 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14865 				break;
14866 				case WindowTypes.toolbar:
14867 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14868 				break;
14869 				case WindowTypes.menu:
14870 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14871 				break;
14872 				case WindowTypes.utility:
14873 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14874 				break;
14875 				case WindowTypes.splash:
14876 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14877 				break;
14878 				case WindowTypes.dialog:
14879 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14880 				break;
14881 				case WindowTypes.tooltip:
14882 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14883 				break;
14884 				case WindowTypes.notification:
14885 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14886 				break;
14887 				case WindowTypes.combo:
14888 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14889 				break;
14890 				case WindowTypes.dnd:
14891 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14892 				break;
14893 				+/
14894 			}
14895 			catch(Exception e) {
14896 				// XInternAtom failed, prolly a WM
14897 				// that doesn't support these things
14898 			}
14899 
14900 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14901 			// the two following flags may be ignored by WM
14902 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14903 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14904 
14905 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14906 
14907 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14908 
14909 			// What would be ideal here is if they only were
14910 			// selected if there was actually an event handler
14911 			// for them...
14912 
14913 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14914 
14915 			hiddenWindow:
14916 
14917 			// set the pid property for lookup later by window managers
14918 			// a standard convenience
14919 			import core.sys.posix.unistd;
14920 			arch_ulong pid = getpid();
14921 
14922 			XChangeProperty(
14923 				display,
14924 				impl.window,
14925 				GetAtom!("_NET_WM_PID", true)(display),
14926 				XA_CARDINAL,
14927 				32 /* bits */,
14928 				0 /*PropModeReplace*/,
14929 				&pid,
14930 				1);
14931 
14932 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
14933 				if(parent is null) assert(0);
14934 				XChangeProperty(
14935 					display,
14936 					impl.window,
14937 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
14938 					XA_WINDOW,
14939 					32 /* bits */,
14940 					0 /*PropModeReplace*/,
14941 					&parent.impl.window,
14942 					1);
14943 
14944 			}
14945 
14946 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
14947 				XMapWindow(display, window);
14948 			} else {
14949 				_hidden = true;
14950 			}
14951 		}
14952 
14953 		void populateXic() {
14954 			if (XDisplayConnection.xim !is null) {
14955 				xic = XCreateIC(XDisplayConnection.xim,
14956 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
14957 						/*XNClientWindow*/"clientWindow".ptr, window,
14958 						/*XNFocusWindow*/"focusWindow".ptr, window,
14959 						null);
14960 				if (xic is null) {
14961 					import core.stdc.stdio : stderr, fprintf;
14962 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
14963 				}
14964 			}
14965 		}
14966 
14967 		void selectDefaultInput(bool forceIncludeMouseMotion) {
14968 			auto mask = EventMask.ExposureMask |
14969 				EventMask.KeyPressMask |
14970 				EventMask.KeyReleaseMask |
14971 				EventMask.PropertyChangeMask |
14972 				EventMask.FocusChangeMask |
14973 				EventMask.StructureNotifyMask |
14974 				EventMask.SubstructureNotifyMask |
14975 				EventMask.VisibilityChangeMask
14976 				| EventMask.ButtonPressMask
14977 				| EventMask.ButtonReleaseMask
14978 			;
14979 
14980 			// xshm is our shortcut for local connections
14981 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
14982 				mask |= EventMask.PointerMotionMask;
14983 			else
14984 				mask |= EventMask.ButtonMotionMask;
14985 
14986 			XSelectInput(display, window, mask);
14987 		}
14988 
14989 
14990 		void setNetWMWindowType(Atom type) {
14991 			Atom[2] atoms;
14992 
14993 			atoms[0] = type;
14994 			// generic fallback
14995 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
14996 
14997 			XChangeProperty(
14998 				display,
14999 				impl.window,
15000 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15001 				XA_ATOM,
15002 				32 /* bits */,
15003 				0 /*PropModeReplace*/,
15004 				atoms.ptr,
15005 				cast(int) atoms.length);
15006 		}
15007 
15008 		void motifHideDecorations(bool hide = true) {
15009 			MwmHints hints;
15010 			hints.flags = MWM_HINTS_DECORATIONS;
15011 			hints.decorations = hide ? 0 : 1;
15012 
15013 			XChangeProperty(
15014 				display,
15015 				impl.window,
15016 				GetAtom!"_MOTIF_WM_HINTS"(display),
15017 				GetAtom!"_MOTIF_WM_HINTS"(display),
15018 				32 /* bits */,
15019 				0 /*PropModeReplace*/,
15020 				&hints,
15021 				hints.sizeof / 4);
15022 		}
15023 
15024 		/*k8: unused
15025 		void createOpenGlContext() {
15026 
15027 		}
15028 		*/
15029 
15030 		void closeWindow() {
15031 			// I can't close this or a child window closing will
15032 			// break events for everyone. So I'm just leaking it right
15033 			// now and that is probably perfectly fine...
15034 			version(none)
15035 			if (customEventFDRead != -1) {
15036 				import core.sys.posix.unistd : close;
15037 				auto same = customEventFDRead == customEventFDWrite;
15038 
15039 				close(customEventFDRead);
15040 				if(!same)
15041 					close(customEventFDWrite);
15042 				customEventFDRead = -1;
15043 				customEventFDWrite = -1;
15044 			}
15045 			if(buffer)
15046 				XFreePixmap(display, buffer);
15047 			bufferw = bufferh = 0;
15048 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15049 			XDestroyWindow(display, window);
15050 			XFlush(display);
15051 		}
15052 
15053 		void dispose() {
15054 		}
15055 
15056 		bool destroyed = false;
15057 	}
15058 
15059 	bool insideXEventLoop;
15060 }
15061 
15062 version(X11) {
15063 
15064 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15065 
15066 	private class ResizeEvent {
15067 		int width, height;
15068 	}
15069 
15070 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15071 		if(win.windowType == WindowTypes.minimallyWrapped)
15072 			return;
15073 
15074 		if(win.pendingResizeEvent is null) {
15075 			win.pendingResizeEvent = new ResizeEvent();
15076 			win.addEventListener((ResizeEvent re) {
15077 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15078 			});
15079 		}
15080 		win.pendingResizeEvent.width = width;
15081 		win.pendingResizeEvent.height = height;
15082 		if(!win.eventQueued!ResizeEvent) {
15083 			win.postEvent(win.pendingResizeEvent);
15084 		}
15085 	}
15086 
15087 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15088 		if(win.windowType == WindowTypes.minimallyWrapped)
15089 			return;
15090 		if(win.closed)
15091 			return;
15092 
15093 		if(width != win.width || height != win.height) {
15094 
15095 		// import std.stdio; writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15096 			win._width = width;
15097 			win._height = height;
15098 
15099 			if(win.openglMode == OpenGlOptions.no) {
15100 				// FIXME: could this be more efficient?
15101 
15102 				if (win.bufferw < width || win.bufferh < height) {
15103 					//{ 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); }
15104 					// grow the internal buffer to match the window...
15105 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15106 					{
15107 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15108 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15109 						scope(exit) XFreeGC(win.display, xgc);
15110 						XSetClipMask(win.display, xgc, None);
15111 						XSetForeground(win.display, xgc, 0);
15112 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15113 					}
15114 					XCopyArea(display,
15115 						cast(Drawable) win.buffer,
15116 						cast(Drawable) newPixmap,
15117 						win.gc, 0, 0,
15118 						win.bufferw < width ? win.bufferw : win.width,
15119 						win.bufferh < height ? win.bufferh : win.height,
15120 						0, 0);
15121 
15122 					XFreePixmap(display, win.buffer);
15123 					win.buffer = newPixmap;
15124 					win.bufferw = width;
15125 					win.bufferh = height;
15126 				}
15127 
15128 				// clear unused parts of the buffer
15129 				if (win.bufferw > width || win.bufferh > height) {
15130 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15131 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15132 					scope(exit) XFreeGC(win.display, xgc);
15133 					XSetClipMask(win.display, xgc, None);
15134 					XSetForeground(win.display, xgc, 0);
15135 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15136 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15137 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15138 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15139 				}
15140 
15141 			}
15142 
15143 			win.updateOpenglViewportIfNeeded(width, height);
15144 
15145 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15146 
15147 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15148 			if(win.windowResized !is null) {
15149 				XUnlockDisplay(display);
15150 				scope(exit) XLockDisplay(display);
15151 				win.windowResized(width, height);
15152 			}
15153 		}
15154 	}
15155 
15156 
15157 	/// Platform-specific, you might use it when doing a custom event loop.
15158 	bool doXNextEvent(Display* display) {
15159 		bool done;
15160 		XEvent e;
15161 		XNextEvent(display, &e);
15162 		version(sddddd) {
15163 			import std.stdio, std.conv : to;
15164 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15165 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15166 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15167 			}
15168 		}
15169 
15170 		// filter out compose events
15171 		if (XFilterEvent(&e, None)) {
15172 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15173 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15174 			return false;
15175 		}
15176 		// process keyboard mapping changes
15177 		if (e.type == EventType.KeymapNotify) {
15178 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15179 			XRefreshKeyboardMapping(&e.xmapping);
15180 			return false;
15181 		}
15182 
15183 		version(with_eventloop)
15184 			import arsd.eventloop;
15185 
15186 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15187 			// see windows impl's comments
15188 			XUnlockDisplay(display);
15189 			scope(exit) XLockDisplay(display);
15190 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15191 			if(ret == 0)
15192 				return done;
15193 		}
15194 
15195 
15196 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15197 			if(win.getNativeEventHandler !is null) {
15198 				XUnlockDisplay(display);
15199 				scope(exit) XLockDisplay(display);
15200 				auto ret = win.getNativeEventHandler()(e);
15201 				if(ret == 0)
15202 					return done;
15203 			}
15204 		}
15205 
15206 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15207 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15208 				// we get this because of the RRScreenChangeNotifyMask
15209 
15210 				// this isn't actually an ideal way to do it since it wastes time
15211 				// but meh it is simple and it works.
15212 				win.actualDpiLoadAttempted = false;
15213 				SimpleWindow.xRandrInfoLoadAttemped = false;
15214 				win.updateActualDpi(); // trigger a reload
15215 			}
15216 		}
15217 
15218 		switch(e.type) {
15219 		  case EventType.SelectionClear:
15220 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15221 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15222 				//import std.stdio; writeln("SelectionClear");
15223 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15224 			}
15225 		  break;
15226 		  case EventType.SelectionRequest:
15227 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15228 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15229 				// import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15230 				XUnlockDisplay(display);
15231 				scope(exit) XLockDisplay(display);
15232 				(*ssh).handleRequest(e);
15233 			}
15234 		  break;
15235 		  case EventType.PropertyNotify:
15236 			// import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15237 
15238 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15239 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15240 					ssh.sendMoreIncr(&e.xproperty);
15241 			}
15242 
15243 
15244 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15245 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15246 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15247 					Atom target;
15248 					int format;
15249 					arch_ulong bytesafter, length;
15250 					void* value;
15251 
15252 					ubyte[] s;
15253 					Atom targetToKeep;
15254 
15255 					XGetWindowProperty(
15256 						e.xproperty.display,
15257 						e.xproperty.window,
15258 						e.xproperty.atom,
15259 						0,
15260 						100000 /* length */,
15261 						true, /* erase it to signal we got it and want more */
15262 						0 /*AnyPropertyType*/,
15263 						&target, &format, &length, &bytesafter, &value);
15264 
15265 					if(!targetToKeep)
15266 						targetToKeep = target;
15267 
15268 					auto id = (cast(ubyte*) value)[0 .. length];
15269 
15270 					handler.handleIncrData(targetToKeep, id);
15271 
15272 					XFree(value);
15273 				}
15274 			}
15275 		  break;
15276 		  case EventType.SelectionNotify:
15277 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15278 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15279 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15280 					XUnlockDisplay(display);
15281 					scope(exit) XLockDisplay(display);
15282 					handler.handleData(None, null);
15283 				} else {
15284 					Atom target;
15285 					int format;
15286 					arch_ulong bytesafter, length;
15287 					void* value;
15288 					XGetWindowProperty(
15289 						e.xselection.display,
15290 						e.xselection.requestor,
15291 						e.xselection.property,
15292 						0,
15293 						100000 /* length */,
15294 						//false, /* don't erase it */
15295 						true, /* do erase it lol */
15296 						0 /*AnyPropertyType*/,
15297 						&target, &format, &length, &bytesafter, &value);
15298 
15299 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15300 
15301 					{
15302 						XUnlockDisplay(display);
15303 						scope(exit) XLockDisplay(display);
15304 
15305 						if(target == XA_ATOM) {
15306 							// initial request, see what they are able to work with and request the best one
15307 							// we can handle, if available
15308 
15309 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15310 							Atom best = handler.findBestFormat(answer);
15311 
15312 							/+
15313 							writeln("got ", answer);
15314 							foreach(a; answer)
15315 								printf("%s\n", XGetAtomName(display, a));
15316 							writeln("best ", best);
15317 							+/
15318 
15319 							if(best != None) {
15320 								// actually request the best format
15321 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15322 							}
15323 						} else if(target == GetAtom!"INCR"(display)) {
15324 							// incremental
15325 
15326 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15327 
15328 							// signal the sending program that we see
15329 							// the incr and are ready to receive more.
15330 							XDeleteProperty(
15331 								e.xselection.display,
15332 								e.xselection.requestor,
15333 								e.xselection.property);
15334 						} else {
15335 							// unsupported type... maybe, forward
15336 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15337 						}
15338 					}
15339 					XFree(value);
15340 					/*
15341 					XDeleteProperty(
15342 						e.xselection.display,
15343 						e.xselection.requestor,
15344 						e.xselection.property);
15345 					*/
15346 				}
15347 			}
15348 		  break;
15349 		  case EventType.ConfigureNotify:
15350 			auto event = e.xconfigure;
15351 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15352 				if(win.windowType == WindowTypes.minimallyWrapped)
15353 					break;
15354 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
15355 
15356 				/+
15357 					The ICCCM says window managers must send a synthetic event when the window
15358 					is moved but NOT when it is resized. In the resize case, an event is sent
15359 					with position (0, 0) which can be wrong and break the dpi calculations.
15360 
15361 					So we only consider the synthetic events from the WM and otherwise
15362 					need to wait for some other event to get the position which... sucks.
15363 
15364 					I'd rather not have windows changing their layout on mouse motion after
15365 					switching monitors... might be forced to but for now just ignoring it.
15366 
15367 					Easiest way to switch monitors without sending a size position is by
15368 					maximize or fullscreen in a setup like mine, but on most setups those
15369 					work on the monitor it is already living on, so it should be ok most the
15370 					time.
15371 				+/
15372 				if(event.send_event) {
15373 					win.screenPositionKnown = true;
15374 					win.screenPositionX = event.x;
15375 					win.screenPositionY = event.y;
15376 					win.updateActualDpi();
15377 				}
15378 
15379 				win.updateIMEPopupLocation();
15380 				recordX11ResizeAsync(display, *win, event.width, event.height);
15381 			}
15382 		  break;
15383 		  case EventType.Expose:
15384 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15385 				if(win.windowType == WindowTypes.minimallyWrapped)
15386 					break;
15387 				// if it is closing from a popup menu, it can get
15388 				// an Expose event right by the end and trigger a
15389 				// BadDrawable error ... we'll just check
15390 				// closed to handle that.
15391 				if((*win).closed) break;
15392 				if((*win).openglMode == OpenGlOptions.no) {
15393 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15394 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15395 					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);
15396 				} else {
15397 					// need to redraw the scene somehow
15398 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15399 						XUnlockDisplay(display);
15400 						scope(exit) XLockDisplay(display);
15401 						version(without_opengl) {} else
15402 						win.redrawOpenGlSceneSoon();
15403 					}
15404 				}
15405 			}
15406 		  break;
15407 		  case EventType.FocusIn:
15408 		  case EventType.FocusOut:
15409 
15410 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15411 				/+
15412 
15413 				void info(string detail) {
15414 					string s;
15415 					import std.conv;
15416 					import std.datetime;
15417 					s ~= to!string(Clock.currTime);
15418 					s ~= " ";
15419 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15420 					s ~= " ";
15421 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15422 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15423 					s ~= detail;
15424 					s ~= " ";
15425 
15426 					sdpyPrintDebugString(s);
15427 
15428 				}
15429 
15430 				switch(e.xfocus.detail) {
15431 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15432 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15433 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15434 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15435 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15436 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15437 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15438 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15439 					default:
15440 
15441 				}
15442 				+/
15443 
15444 
15445 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15446 					break; // just ignore these they seem irrelevant
15447 
15448 				auto old = win._focused;
15449 				win._focused = e.type == EventType.FocusIn;
15450 
15451 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15452 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15453 					win._focused = true;
15454 
15455 				if(win.demandingAttention)
15456 					demandAttention(*win, false);
15457 
15458 				win.updateIMEFocused();
15459 
15460 				if(old != win._focused && win.onFocusChange) {
15461 					XUnlockDisplay(display);
15462 					scope(exit) XLockDisplay(display);
15463 					win.onFocusChange(win._focused);
15464 				}
15465 			}
15466 		  break;
15467 		  case EventType.VisibilityNotify:
15468 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15469 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15470 						if (win.visibilityChanged !is null) {
15471 								XUnlockDisplay(display);
15472 								scope(exit) XLockDisplay(display);
15473 								win.visibilityChanged(false);
15474 							}
15475 					} else {
15476 						if (win.visibilityChanged !is null) {
15477 							XUnlockDisplay(display);
15478 							scope(exit) XLockDisplay(display);
15479 							win.visibilityChanged(true);
15480 						}
15481 					}
15482 				}
15483 				break;
15484 		  case EventType.ClientMessage:
15485 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15486 					// "ignore next mouse motion" event, increment ignore counter for teh window
15487 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15488 						++(*win).warpEventCount;
15489 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15490 					} else {
15491 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15492 					}
15493 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15494 					// user clicked the close button on the window manager
15495 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15496 						XUnlockDisplay(display);
15497 						scope(exit) XLockDisplay(display);
15498 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15499 					}
15500 
15501 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15502 					//import std.stdio; writeln("HAPPENED");
15503 					// user clicked the close button on the window manager
15504 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15505 						XUnlockDisplay(display);
15506 						scope(exit) XLockDisplay(display);
15507 
15508 						auto setTo = *win;
15509 
15510 						if(win.setRequestedInputFocus !is null) {
15511 							auto s = win.setRequestedInputFocus();
15512 							if(s !is null) {
15513 								setTo = s;
15514 							}
15515 						}
15516 
15517 						assert(setTo !is null);
15518 
15519 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15520 
15521 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15522 					}
15523 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15524 					foreach(nai; NotificationAreaIcon.activeIcons)
15525 						nai.newManager();
15526 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15527 
15528 					bool xDragWindow = true;
15529 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15530 						//XDefineCursor(display, xDragWindow.impl.window,
15531 							//import std.stdio; writeln("XdndStatus ", e.xclient.data.l);
15532 					}
15533 					if(auto dh = win.dropHandler) {
15534 
15535 						static Atom[3] xFormatsBuffer;
15536 						static Atom[] xFormats;
15537 
15538 						void resetXFormats() {
15539 							xFormatsBuffer[] = 0;
15540 							xFormats = xFormatsBuffer[];
15541 						}
15542 
15543 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15544 							// on Windows it is supposed to return the effect you actually do FIXME
15545 
15546 							auto sourceWindow =  e.xclient.data.l[0];
15547 
15548 							xFormatsBuffer[0] = e.xclient.data.l[2];
15549 							xFormatsBuffer[1] = e.xclient.data.l[3];
15550 							xFormatsBuffer[2] = e.xclient.data.l[4];
15551 
15552 							if(e.xclient.data.l[1] & 1) {
15553 								// can just grab it all but like we don't necessarily need them...
15554 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15555 							} else {
15556 								int len;
15557 								foreach(fmt; xFormatsBuffer)
15558 									if(fmt) len++;
15559 								xFormats = xFormatsBuffer[0 .. len];
15560 							}
15561 
15562 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15563 
15564 							dh.dragEnter(&pkg);
15565 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15566 
15567 							auto pack = e.xclient.data.l[2];
15568 
15569 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15570 
15571 
15572 							XClientMessageEvent xclient;
15573 
15574 							xclient.type = EventType.ClientMessage;
15575 							xclient.window = e.xclient.data.l[0];
15576 							xclient.message_type = GetAtom!"XdndStatus"(display);
15577 							xclient.format = 32;
15578 							xclient.data.l[0] = win.impl.window;
15579 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15580 							auto r = result.consistentWithin;
15581 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15582 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15583 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15584 
15585 							XSendEvent(
15586 								display,
15587 								e.xclient.data.l[0],
15588 								false,
15589 								EventMask.NoEventMask,
15590 								cast(XEvent*) &xclient
15591 							);
15592 
15593 
15594 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15595 							//import std.stdio; writeln("XdndLeave");
15596 							// drop cancelled.
15597 							// data.l[0] is the source window
15598 							dh.dragLeave();
15599 
15600 							resetXFormats();
15601 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15602 							// drop happening, should fetch data, then send finished
15603 							//import std.stdio; writeln("XdndDrop");
15604 
15605 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15606 
15607 							dh.drop(&pkg);
15608 
15609 							resetXFormats();
15610 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15611 							// import std.stdio; writeln("XdndFinished");
15612 
15613 							dh.finish();
15614 						}
15615 
15616 					}
15617 				}
15618 		  break;
15619 		  case EventType.MapNotify:
15620 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15621 					(*win)._visible = true;
15622 					if (!(*win)._visibleForTheFirstTimeCalled) {
15623 						(*win)._visibleForTheFirstTimeCalled = true;
15624 						if ((*win).visibleForTheFirstTime !is null) {
15625 							XUnlockDisplay(display);
15626 							scope(exit) XLockDisplay(display);
15627 							(*win).visibleForTheFirstTime();
15628 						}
15629 					}
15630 					if ((*win).visibilityChanged !is null) {
15631 						XUnlockDisplay(display);
15632 						scope(exit) XLockDisplay(display);
15633 						(*win).visibilityChanged(true);
15634 					}
15635 				}
15636 		  break;
15637 		  case EventType.UnmapNotify:
15638 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15639 					win._visible = false;
15640 					if (win.visibilityChanged !is null) {
15641 						XUnlockDisplay(display);
15642 						scope(exit) XLockDisplay(display);
15643 						win.visibilityChanged(false);
15644 					}
15645 			}
15646 		  break;
15647 		  case EventType.DestroyNotify:
15648 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15649 				if(win.destroyed)
15650 					break; // might get a notification both for itself and from its parent
15651 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15652 				win._closed = true; // just in case
15653 				win.destroyed = true;
15654 				if (win.xic !is null) {
15655 					XDestroyIC(win.xic);
15656 					win.xic = null; // just in case
15657 				}
15658 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15659 				bool anyImportant = false;
15660 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15661 					if(w.beingOpenKeepsAppOpen) {
15662 						anyImportant = true;
15663 						break;
15664 					}
15665 				if(!anyImportant) {
15666 					EventLoop.quitApplication();
15667 					done = true;
15668 				}
15669 			}
15670 			auto window = e.xdestroywindow.window;
15671 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15672 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15673 
15674 			version(with_eventloop) {
15675 				if(done) exit();
15676 			}
15677 		  break;
15678 
15679 		  case EventType.MotionNotify:
15680 			MouseEvent mouse;
15681 			auto event = e.xmotion;
15682 
15683 			mouse.type = MouseEventType.motion;
15684 			mouse.x = event.x;
15685 			mouse.y = event.y;
15686 			mouse.modifierState = event.state;
15687 
15688 			mouse.timestamp = event.time;
15689 
15690 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15691 				mouse.window = *win;
15692 				if (win.warpEventCount > 0) {
15693 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15694 					--(*win).warpEventCount;
15695 					(*win).mdx(mouse); // so deltas will be correctly updated
15696 				} else {
15697 					win.warpEventCount = 0; // just in case
15698 					(*win).mdx(mouse);
15699 					if((*win).handleMouseEvent) {
15700 						XUnlockDisplay(display);
15701 						scope(exit) XLockDisplay(display);
15702 						(*win).handleMouseEvent(mouse);
15703 					}
15704 				}
15705 			}
15706 
15707 		  	version(with_eventloop)
15708 				send(mouse);
15709 		  break;
15710 		  case EventType.ButtonPress:
15711 		  case EventType.ButtonRelease:
15712 			MouseEvent mouse;
15713 			auto event = e.xbutton;
15714 
15715 			mouse.timestamp = event.time;
15716 
15717 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15718 			mouse.x = event.x;
15719 			mouse.y = event.y;
15720 
15721 			static Time lastMouseDownTime = 0;
15722 
15723 			mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15724 			if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time;
15725 
15726 			switch(event.button) {
15727 				case 1: mouse.button = MouseButton.left; break; // left
15728 				case 2: mouse.button = MouseButton.middle; break; // middle
15729 				case 3: mouse.button = MouseButton.right; break; // right
15730 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15731 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15732 				case 6: break; // idk
15733 				case 7: break; // idk
15734 				case 8: mouse.button = MouseButton.backButton; break;
15735 				case 9: mouse.button = MouseButton.forwardButton; break;
15736 				default:
15737 			}
15738 
15739 			// FIXME: double check this
15740 			mouse.modifierState = event.state;
15741 
15742 			//mouse.modifierState = event.detail;
15743 
15744 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15745 				mouse.window = *win;
15746 				(*win).mdx(mouse);
15747 				if((*win).handleMouseEvent) {
15748 					XUnlockDisplay(display);
15749 					scope(exit) XLockDisplay(display);
15750 					(*win).handleMouseEvent(mouse);
15751 				}
15752 			}
15753 			version(with_eventloop)
15754 				send(mouse);
15755 		  break;
15756 
15757 		  case EventType.KeyPress:
15758 		  case EventType.KeyRelease:
15759 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15760 			KeyEvent ke;
15761 			ke.pressed = e.type == EventType.KeyPress;
15762 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15763 
15764 			auto sym = XKeycodeToKeysym(
15765 				XDisplayConnection.get(),
15766 				e.xkey.keycode,
15767 				0);
15768 
15769 			ke.key = cast(Key) sym;//e.xkey.keycode;
15770 
15771 			ke.modifierState = e.xkey.state;
15772 
15773 			// import std.stdio; writefln("%x", sym);
15774 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15775 			int charbuflen = 0; // return value of XwcLookupString
15776 			if (ke.pressed) {
15777 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15778 				if (win !is null && win.xic !is null) {
15779 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15780 					Status status;
15781 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15782 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15783 				} else {
15784 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15785 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15786 					char[16] buffer;
15787 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15788 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15789 				}
15790 			}
15791 
15792 			// if there's no char, subst one
15793 			if (charbuflen == 0) {
15794 				switch (sym) {
15795 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15796 					case 0xff8d: // keypad enter
15797 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15798 					default : // ignore
15799 				}
15800 			}
15801 
15802 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15803 				ke.window = *win;
15804 
15805 
15806 				if(win.inputProxy)
15807 					win = &win.inputProxy;
15808 
15809 				// char events are separate since they are on Windows too
15810 				// also, xcompose can generate long char sequences
15811 				// don't send char events if Meta and/or Hyper is pressed
15812 				// TODO: ctrl+char should only send control chars; not yet
15813 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15814 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15815 				}
15816 
15817 				dchar[32] charsComingBuffer;
15818 				int charsComingPosition;
15819 				dchar[] charsComing = charsComingBuffer[];
15820 
15821 				if (ke.pressed && charbuflen > 0) {
15822 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15823 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15824 						if(charsComingPosition >= charsComing.length)
15825 							charsComing.length = charsComingPosition + 8;
15826 
15827 						charsComing[charsComingPosition++] = ch;
15828 					}
15829 
15830 					charsComing = charsComing[0 .. charsComingPosition];
15831 				} else {
15832 					charsComing = null;
15833 				}
15834 
15835 				ke.charsPossible = charsComing;
15836 
15837 				if (win.handleKeyEvent) {
15838 					XUnlockDisplay(display);
15839 					scope(exit) XLockDisplay(display);
15840 					win.handleKeyEvent(ke);
15841 				}
15842 
15843 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15844 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15845 					XUnlockDisplay(display);
15846 					scope(exit) XLockDisplay(display);
15847 					foreach(ch; charsComing)
15848 						win.handleCharEvent(ch);
15849 				}
15850 			}
15851 
15852 			version(with_eventloop)
15853 				send(ke);
15854 		  break;
15855 		  default:
15856 		}
15857 
15858 		return done;
15859 	}
15860 }
15861 
15862 /* *************************************** */
15863 /*      Done with simpledisplay stuff      */
15864 /* *************************************** */
15865 
15866 // Necessary C library bindings follow
15867 version(Windows) {} else
15868 version(X11) {
15869 
15870 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15871 
15872 // X11 bindings needed here
15873 /*
15874 	A little of this is from the bindings project on
15875 	D Source and some of it is copy/paste from the C
15876 	header.
15877 
15878 	The DSource listing consistently used D's long
15879 	where C used long. That's wrong - C long is 32 bit, so
15880 	it should be int in D. I changed that here.
15881 
15882 	Note:
15883 	This isn't complete, just took what I needed for myself.
15884 */
15885 
15886 import core.stdc.stddef : wchar_t;
15887 
15888 interface XLib {
15889 extern(C) nothrow @nogc {
15890 	char* XResourceManagerString(Display*);
15891 	void XrmInitialize();
15892 	XrmDatabase XrmGetStringDatabase(char* data);
15893 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15894 
15895 	Cursor XCreateFontCursor(Display*, uint shape);
15896 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15897 	int XUndefineCursor(Display* display, Window w);
15898 
15899 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15900 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15901 	int XFreeCursor(Display* display, Cursor cursor);
15902 
15903 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
15904 
15905 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
15906 
15907 	XVaNestedList XVaCreateNestedList(int unused, ...);
15908 
15909 	char *XKeysymToString(KeySym keysym);
15910 	KeySym XKeycodeToKeysym(
15911 		Display*		/* display */,
15912 		KeyCode		/* keycode */,
15913 		int			/* index */
15914 	);
15915 
15916 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
15917 
15918 	int XFree(void*);
15919 	int XDeleteProperty(Display *display, Window w, Atom property);
15920 
15921 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
15922 
15923 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
15924 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
15925 		*actual_type_return, int *actual_format_return, arch_ulong
15926 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
15927 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
15928 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
15929 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
15930 
15931 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
15932 
15933 	Window XGetSelectionOwner(Display *display, Atom selection);
15934 
15935 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
15936 
15937 	char** XListFonts(Display*, const char*, int, int*);
15938 	void XFreeFontNames(char**);
15939 
15940 	Display* XOpenDisplay(const char*);
15941 	int XCloseDisplay(Display*);
15942 
15943 	int function() XSynchronize(Display*, bool);
15944 	int function() XSetAfterFunction(Display*, int function() proc);
15945 
15946 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
15947 
15948 	Bool XSupportsLocale();
15949 	char* XSetLocaleModifiers(const(char)* modifier_list);
15950 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15951 	Status XCloseOM(XOM om);
15952 
15953 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15954 	Status XCloseIM(XIM im);
15955 
15956 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15957 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15958 	Display* XDisplayOfIM(XIM im);
15959 	char* XLocaleOfIM(XIM im);
15960 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
15961 	void XDestroyIC(XIC ic);
15962 	void XSetICFocus(XIC ic);
15963 	void XUnsetICFocus(XIC ic);
15964 	//wchar_t* XwcResetIC(XIC ic);
15965 	char* XmbResetIC(XIC ic);
15966 	char* Xutf8ResetIC(XIC ic);
15967 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15968 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15969 	XIM XIMOfIC(XIC ic);
15970 
15971 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
15972 
15973 
15974 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
15975 	int XFreeFont(Display *display, XFontStruct *font_struct);
15976 	int XSetFont(Display* display, GC gc, Font font);
15977 	int XTextWidth(XFontStruct*, in char*, int);
15978 
15979 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
15980 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
15981 
15982 	Window XCreateSimpleWindow(
15983 		Display*	/* display */,
15984 		Window		/* parent */,
15985 		int			/* x */,
15986 		int			/* y */,
15987 		uint		/* width */,
15988 		uint		/* height */,
15989 		uint		/* border_width */,
15990 		uint		/* border */,
15991 		uint		/* background */
15992 	);
15993 	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);
15994 
15995 	int XReparentWindow(Display*, Window, Window, int, int);
15996 	int XClearWindow(Display*, Window);
15997 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
15998 	int XMoveWindow(Display*, Window, int, int);
15999 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16000 
16001 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16002 
16003 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16004 
16005 	XImage *XCreateImage(
16006 		Display*		/* display */,
16007 		Visual*		/* visual */,
16008 		uint	/* depth */,
16009 		int			/* format */,
16010 		int			/* offset */,
16011 		ubyte*		/* data */,
16012 		uint	/* width */,
16013 		uint	/* height */,
16014 		int			/* bitmap_pad */,
16015 		int			/* bytes_per_line */
16016 	);
16017 
16018 	Status XInitImage (XImage* image);
16019 
16020 	Atom XInternAtom(
16021 		Display*		/* display */,
16022 		const char*	/* atom_name */,
16023 		Bool		/* only_if_exists */
16024 	);
16025 
16026 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16027 	char* XGetAtomName(Display*, Atom);
16028 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16029 
16030 	int XPutImage(
16031 		Display*	/* display */,
16032 		Drawable	/* d */,
16033 		GC			/* gc */,
16034 		XImage*	/* image */,
16035 		int			/* src_x */,
16036 		int			/* src_y */,
16037 		int			/* dest_x */,
16038 		int			/* dest_y */,
16039 		uint		/* width */,
16040 		uint		/* height */
16041 	);
16042 
16043 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16044 
16045 
16046 	int XDestroyWindow(
16047 		Display*	/* display */,
16048 		Window		/* w */
16049 	);
16050 
16051 	int XDestroyImage(XImage*);
16052 
16053 	int XSelectInput(
16054 		Display*	/* display */,
16055 		Window		/* w */,
16056 		EventMask	/* event_mask */
16057 	);
16058 
16059 	int XMapWindow(
16060 		Display*	/* display */,
16061 		Window		/* w */
16062 	);
16063 
16064 	Status XIconifyWindow(Display*, Window, int);
16065 	int XMapRaised(Display*, Window);
16066 	int XMapSubwindows(Display*, Window);
16067 
16068 	int XNextEvent(
16069 		Display*	/* display */,
16070 		XEvent*		/* event_return */
16071 	);
16072 
16073 	int XMaskEvent(Display*, arch_long, XEvent*);
16074 
16075 	Bool XFilterEvent(XEvent *event, Window window);
16076 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16077 
16078 	Status XSetWMProtocols(
16079 		Display*	/* display */,
16080 		Window		/* w */,
16081 		Atom*		/* protocols */,
16082 		int			/* count */
16083 	);
16084 
16085 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16086 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16087 
16088 
16089 	Status XInitThreads();
16090 	void XLockDisplay (Display* display);
16091 	void XUnlockDisplay (Display* display);
16092 
16093 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16094 
16095 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16096 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16097 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16098 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16099 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16100 
16101 
16102 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16103 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
16104 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16105 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16106 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16107 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16108 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16109 	int XDrawPoint(Display*, Drawable, GC, int, int);
16110 	int XSetForeground(Display*, GC, uint);
16111 	int XSetBackground(Display*, GC, uint);
16112 
16113 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16114 	void XFreeFontSet(Display*, XFontSet);
16115 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
16116 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16117 
16118 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16119 
16120 
16121 //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);
16122 
16123 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16124 	int XSetFunction(Display*, GC, int);
16125 
16126 	GC XCreateGC(Display*, Drawable, uint, void*);
16127 	int XCopyGC(Display*, GC, uint, GC);
16128 	int XFreeGC(Display*, GC);
16129 
16130 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16131 	bool XCheckMaskEvent(Display*, int, XEvent*);
16132 
16133 	int XPending(Display*);
16134 	int XEventsQueued(Display* display, int mode);
16135 
16136 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16137 	int XFreePixmap(Display*, Pixmap);
16138 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16139 	int XFlush(Display*);
16140 	int XBell(Display*, int);
16141 	int XSync(Display*, bool);
16142 
16143 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16144 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16145 
16146 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16147 	int XUngrabKeyboard(Display*, Time);
16148 
16149 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16150 
16151 	KeySym XStringToKeysym(const char *string);
16152 
16153 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16154 
16155 	Window XDefaultRootWindow(Display*);
16156 
16157 	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);
16158 
16159 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16160 
16161 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16162 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16163 
16164 	Status XAllocColor(Display*, Colormap, XColor*);
16165 
16166 	int XWithdrawWindow(Display*, Window, int);
16167 	int XUnmapWindow(Display*, Window);
16168 	int XLowerWindow(Display*, Window);
16169 	int XRaiseWindow(Display*, Window);
16170 
16171 	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);
16172 	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);
16173 
16174 	int XGetInputFocus(Display*, Window*, int*);
16175 	int XSetInputFocus(Display*, Window, int, Time);
16176 
16177 	XErrorHandler XSetErrorHandler(XErrorHandler);
16178 
16179 	int XGetErrorText(Display*, int, char*, int);
16180 
16181 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16182 
16183 
16184 	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);
16185 	int XUngrabPointer(Display *display, Time time);
16186 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16187 
16188 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16189 
16190 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16191 	int XSetClipMask(Display*, GC, Pixmap);
16192 	int XSetClipOrigin(Display*, GC, int, int);
16193 
16194 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16195 
16196 	void XSetWMName(Display*, Window, XTextProperty*);
16197 	Status XGetWMName(Display*, Window, XTextProperty*);
16198 	int XStoreName(Display* display, Window w, const(char)* window_name);
16199 
16200 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16201 
16202 }
16203 }
16204 
16205 interface Xext {
16206 extern(C) nothrow @nogc {
16207 	Status XShmAttach(Display*, XShmSegmentInfo*);
16208 	Status XShmDetach(Display*, XShmSegmentInfo*);
16209 	Status XShmPutImage(
16210 		Display*            /* dpy */,
16211 		Drawable            /* d */,
16212 		GC                  /* gc */,
16213 		XImage*             /* image */,
16214 		int                 /* src_x */,
16215 		int                 /* src_y */,
16216 		int                 /* dst_x */,
16217 		int                 /* dst_y */,
16218 		uint        /* src_width */,
16219 		uint        /* src_height */,
16220 		Bool                /* send_event */
16221 	);
16222 
16223 	Status XShmQueryExtension(Display*);
16224 
16225 	XImage *XShmCreateImage(
16226 		Display*            /* dpy */,
16227 		Visual*             /* visual */,
16228 		uint        /* depth */,
16229 		int                 /* format */,
16230 		char*               /* data */,
16231 		XShmSegmentInfo*    /* shminfo */,
16232 		uint        /* width */,
16233 		uint        /* height */
16234 	);
16235 
16236 	Pixmap XShmCreatePixmap(
16237 		Display*            /* dpy */,
16238 		Drawable            /* d */,
16239 		char*               /* data */,
16240 		XShmSegmentInfo*    /* shminfo */,
16241 		uint        /* width */,
16242 		uint        /* height */,
16243 		uint        /* depth */
16244 	);
16245 
16246 }
16247 }
16248 
16249 	// this requires -lXpm
16250 	//int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16251 
16252 
16253 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16254 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16255 shared static this() {
16256 	xlib.loadDynamicLibrary();
16257 	xext.loadDynamicLibrary();
16258 }
16259 
16260 
16261 extern(C) nothrow @nogc {
16262 
16263 alias XrmDatabase = void*;
16264 struct XrmValue {
16265 	uint size;
16266 	void* addr;
16267 }
16268 
16269 struct XVisualInfo {
16270 	Visual* visual;
16271 	VisualID visualid;
16272 	int screen;
16273 	uint depth;
16274 	int c_class;
16275 	c_ulong red_mask;
16276 	c_ulong green_mask;
16277 	c_ulong blue_mask;
16278 	int colormap_size;
16279 	int bits_per_rgb;
16280 }
16281 
16282 enum VisualNoMask=	0x0;
16283 enum VisualIDMask=	0x1;
16284 enum VisualScreenMask=0x2;
16285 enum VisualDepthMask=	0x4;
16286 enum VisualClassMask=	0x8;
16287 enum VisualRedMaskMask=0x10;
16288 enum VisualGreenMaskMask=0x20;
16289 enum VisualBlueMaskMask=0x40;
16290 enum VisualColormapSizeMask=0x80;
16291 enum VisualBitsPerRGBMask=0x100;
16292 enum VisualAllMask=	0x1FF;
16293 
16294 enum AnyKey = 0;
16295 enum AnyModifier = 1 << 15;
16296 
16297 // XIM and other crap
16298 struct _XOM {}
16299 struct _XIM {}
16300 struct _XIC {}
16301 alias XOM = _XOM*;
16302 alias XIM = _XIM*;
16303 alias XIC = _XIC*;
16304 
16305 alias XVaNestedList = void*;
16306 
16307 alias XIMStyle = arch_ulong;
16308 enum : arch_ulong {
16309 	XIMPreeditArea      = 0x0001,
16310 	XIMPreeditCallbacks = 0x0002,
16311 	XIMPreeditPosition  = 0x0004,
16312 	XIMPreeditNothing   = 0x0008,
16313 	XIMPreeditNone      = 0x0010,
16314 	XIMStatusArea       = 0x0100,
16315 	XIMStatusCallbacks  = 0x0200,
16316 	XIMStatusNothing    = 0x0400,
16317 	XIMStatusNone       = 0x0800,
16318 }
16319 
16320 
16321 /* X Shared Memory Extension functions */
16322 	//pragma(lib, "Xshm");
16323 	alias arch_ulong ShmSeg;
16324 	struct XShmSegmentInfo {
16325 		ShmSeg shmseg;
16326 		int shmid;
16327 		ubyte* shmaddr;
16328 		Bool readOnly;
16329 	}
16330 
16331 	// and the necessary OS functions
16332 	int shmget(int, size_t, int);
16333 	void* shmat(int, in void*, int);
16334 	int shmdt(in void*);
16335 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16336 
16337 	enum IPC_PRIVATE = 0;
16338 	enum IPC_CREAT = 512;
16339 	enum IPC_RMID = 0;
16340 
16341 /* MIT-SHM end */
16342 
16343 
16344 enum MappingType:int {
16345 	MappingModifier		=0,
16346 	MappingKeyboard		=1,
16347 	MappingPointer		=2
16348 }
16349 
16350 /* ImageFormat -- PutImage, GetImage */
16351 enum ImageFormat:int {
16352 	XYBitmap	=0,	/* depth 1, XYFormat */
16353 	XYPixmap	=1,	/* depth == drawable depth */
16354 	ZPixmap	=2	/* depth == drawable depth */
16355 }
16356 
16357 enum ModifierName:int {
16358 	ShiftMapIndex	=0,
16359 	LockMapIndex	=1,
16360 	ControlMapIndex	=2,
16361 	Mod1MapIndex	=3,
16362 	Mod2MapIndex	=4,
16363 	Mod3MapIndex	=5,
16364 	Mod4MapIndex	=6,
16365 	Mod5MapIndex	=7
16366 }
16367 
16368 enum ButtonMask:int {
16369 	Button1Mask	=1<<8,
16370 	Button2Mask	=1<<9,
16371 	Button3Mask	=1<<10,
16372 	Button4Mask	=1<<11,
16373 	Button5Mask	=1<<12,
16374 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16375 }
16376 
16377 enum KeyOrButtonMask:uint {
16378 	ShiftMask	=1<<0,
16379 	LockMask	=1<<1,
16380 	ControlMask	=1<<2,
16381 	Mod1Mask	=1<<3,
16382 	Mod2Mask	=1<<4,
16383 	Mod3Mask	=1<<5,
16384 	Mod4Mask	=1<<6,
16385 	Mod5Mask	=1<<7,
16386 	Button1Mask	=1<<8,
16387 	Button2Mask	=1<<9,
16388 	Button3Mask	=1<<10,
16389 	Button4Mask	=1<<11,
16390 	Button5Mask	=1<<12,
16391 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16392 }
16393 
16394 enum ButtonName:int {
16395 	Button1	=1,
16396 	Button2	=2,
16397 	Button3	=3,
16398 	Button4	=4,
16399 	Button5	=5
16400 }
16401 
16402 /* Notify modes */
16403 enum NotifyModes:int
16404 {
16405 	NotifyNormal		=0,
16406 	NotifyGrab			=1,
16407 	NotifyUngrab		=2,
16408 	NotifyWhileGrabbed	=3
16409 }
16410 enum NotifyHint = 1;	/* for MotionNotify events */
16411 
16412 /* Notify detail */
16413 enum NotifyDetail:int
16414 {
16415 	NotifyAncestor			=0,
16416 	NotifyVirtual			=1,
16417 	NotifyInferior			=2,
16418 	NotifyNonlinear			=3,
16419 	NotifyNonlinearVirtual	=4,
16420 	NotifyPointer			=5,
16421 	NotifyPointerRoot		=6,
16422 	NotifyDetailNone		=7
16423 }
16424 
16425 /* Visibility notify */
16426 
16427 enum VisibilityNotify:int
16428 {
16429 VisibilityUnobscured		=0,
16430 VisibilityPartiallyObscured	=1,
16431 VisibilityFullyObscured		=2
16432 }
16433 
16434 
16435 enum WindowStackingMethod:int
16436 {
16437 	Above		=0,
16438 	Below		=1,
16439 	TopIf		=2,
16440 	BottomIf	=3,
16441 	Opposite	=4
16442 }
16443 
16444 /* Circulation request */
16445 enum CirculationRequest:int
16446 {
16447 	PlaceOnTop		=0,
16448 	PlaceOnBottom	=1
16449 }
16450 
16451 enum PropertyNotification:int
16452 {
16453 	PropertyNewValue	=0,
16454 	PropertyDelete		=1
16455 }
16456 
16457 enum ColorMapNotification:int
16458 {
16459 	ColormapUninstalled	=0,
16460 	ColormapInstalled		=1
16461 }
16462 
16463 
16464 	struct _XPrivate {}
16465 	struct _XrmHashBucketRec {}
16466 
16467 	alias void* XPointer;
16468 	alias void* XExtData;
16469 
16470 	version( X86_64 ) {
16471 		alias ulong XID;
16472 		alias ulong arch_ulong;
16473 		alias long arch_long;
16474 	} else version (AArch64) {
16475 		alias ulong XID;
16476 		alias ulong arch_ulong;
16477 		alias long arch_long;
16478 	} else {
16479 		alias uint XID;
16480 		alias uint arch_ulong;
16481 		alias int arch_long;
16482 	}
16483 
16484 	alias XID Window;
16485 	alias XID Drawable;
16486 	alias XID Pixmap;
16487 
16488 	alias arch_ulong Atom;
16489 	alias int Bool;
16490 	alias Display XDisplay;
16491 
16492 	alias int ByteOrder;
16493 	alias arch_ulong Time;
16494 	alias void ScreenFormat;
16495 
16496 	struct XImage {
16497 		int width, height;			/* size of image */
16498 		int xoffset;				/* number of pixels offset in X direction */
16499 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16500 		void *data;					/* pointer to image data */
16501 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16502 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16503 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16504 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16505 		int depth;					/* depth of image */
16506 		int bytes_per_line;			/* accelarator to next line */
16507 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16508 		arch_ulong red_mask;	/* bits in z arrangment */
16509 		arch_ulong green_mask;
16510 		arch_ulong blue_mask;
16511 		XPointer obdata;			/* hook for the object routines to hang on */
16512 		static struct F {				/* image manipulation routines */
16513 			XImage* function(
16514 				XDisplay* 			/* display */,
16515 				Visual*				/* visual */,
16516 				uint				/* depth */,
16517 				int					/* format */,
16518 				int					/* offset */,
16519 				ubyte*				/* data */,
16520 				uint				/* width */,
16521 				uint				/* height */,
16522 				int					/* bitmap_pad */,
16523 				int					/* bytes_per_line */) create_image;
16524 			int function(XImage *) destroy_image;
16525 			arch_ulong function(XImage *, int, int) get_pixel;
16526 			int function(XImage *, int, int, arch_ulong) put_pixel;
16527 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16528 			int function(XImage *, arch_long) add_pixel;
16529 		}
16530 		F f;
16531 	}
16532 	version(X86_64) static assert(XImage.sizeof == 136);
16533 	else version(X86) static assert(XImage.sizeof == 88);
16534 
16535 struct XCharStruct {
16536 	short       lbearing;       /* origin to left edge of raster */
16537 	short       rbearing;       /* origin to right edge of raster */
16538 	short       width;          /* advance to next char's origin */
16539 	short       ascent;         /* baseline to top edge of raster */
16540 	short       descent;        /* baseline to bottom edge of raster */
16541 	ushort attributes;  /* per char flags (not predefined) */
16542 }
16543 
16544 /*
16545  * To allow arbitrary information with fonts, there are additional properties
16546  * returned.
16547  */
16548 struct XFontProp {
16549 	Atom name;
16550 	arch_ulong card32;
16551 }
16552 
16553 alias Atom Font;
16554 
16555 struct XFontStruct {
16556 	XExtData *ext_data;           /* Hook for extension to hang data */
16557 	Font fid;                     /* Font ID for this font */
16558 	uint direction;           /* Direction the font is painted */
16559 	uint min_char_or_byte2;   /* First character */
16560 	uint max_char_or_byte2;   /* Last character */
16561 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16562 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16563 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16564 	uint default_char;        /* Char to print for undefined character */
16565 	int n_properties;             /* How many properties there are */
16566 	XFontProp *properties;        /* Pointer to array of additional properties*/
16567 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16568 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16569 	XCharStruct *per_char;        /* first_char to last_char information */
16570 	int ascent;                   /* Max extent above baseline for spacing */
16571 	int descent;                  /* Max descent below baseline for spacing */
16572 }
16573 
16574 
16575 /*
16576  * Definitions of specific events.
16577  */
16578 struct XKeyEvent
16579 {
16580 	int type;			/* of event */
16581 	arch_ulong serial;		/* # of last request processed by server */
16582 	Bool send_event;	/* true if this came from a SendEvent request */
16583 	Display *display;	/* Display the event was read from */
16584 	Window window;	        /* "event" window it is reported relative to */
16585 	Window root;	        /* root window that the event occurred on */
16586 	Window subwindow;	/* child window */
16587 	Time time;		/* milliseconds */
16588 	int x, y;		/* pointer x, y coordinates in event window */
16589 	int x_root, y_root;	/* coordinates relative to root */
16590 	KeyOrButtonMask state;	/* key or button mask */
16591 	uint keycode;	/* detail */
16592 	Bool same_screen;	/* same screen flag */
16593 }
16594 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16595 alias XKeyEvent XKeyPressedEvent;
16596 alias XKeyEvent XKeyReleasedEvent;
16597 
16598 struct XButtonEvent
16599 {
16600 	int type;		/* of event */
16601 	arch_ulong serial;	/* # of last request processed by server */
16602 	Bool send_event;	/* true if this came from a SendEvent request */
16603 	Display *display;	/* Display the event was read from */
16604 	Window window;	        /* "event" window it is reported relative to */
16605 	Window root;	        /* root window that the event occurred on */
16606 	Window subwindow;	/* child window */
16607 	Time time;		/* milliseconds */
16608 	int x, y;		/* pointer x, y coordinates in event window */
16609 	int x_root, y_root;	/* coordinates relative to root */
16610 	KeyOrButtonMask state;	/* key or button mask */
16611 	uint button;	/* detail */
16612 	Bool same_screen;	/* same screen flag */
16613 }
16614 alias XButtonEvent XButtonPressedEvent;
16615 alias XButtonEvent XButtonReleasedEvent;
16616 
16617 struct XMotionEvent{
16618 	int type;		/* of event */
16619 	arch_ulong serial;	/* # of last request processed by server */
16620 	Bool send_event;	/* true if this came from a SendEvent request */
16621 	Display *display;	/* Display the event was read from */
16622 	Window window;	        /* "event" window reported relative to */
16623 	Window root;	        /* root window that the event occurred on */
16624 	Window subwindow;	/* child window */
16625 	Time time;		/* milliseconds */
16626 	int x, y;		/* pointer x, y coordinates in event window */
16627 	int x_root, y_root;	/* coordinates relative to root */
16628 	KeyOrButtonMask state;	/* key or button mask */
16629 	byte is_hint;		/* detail */
16630 	Bool same_screen;	/* same screen flag */
16631 }
16632 alias XMotionEvent XPointerMovedEvent;
16633 
16634 struct XCrossingEvent{
16635 	int type;		/* of event */
16636 	arch_ulong serial;	/* # of last request processed by server */
16637 	Bool send_event;	/* true if this came from a SendEvent request */
16638 	Display *display;	/* Display the event was read from */
16639 	Window window;	        /* "event" window reported relative to */
16640 	Window root;	        /* root window that the event occurred on */
16641 	Window subwindow;	/* child window */
16642 	Time time;		/* milliseconds */
16643 	int x, y;		/* pointer x, y coordinates in event window */
16644 	int x_root, y_root;	/* coordinates relative to root */
16645 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16646 	NotifyDetail detail;
16647 	/*
16648 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16649 	 * NotifyNonlinear,NotifyNonlinearVirtual
16650 	 */
16651 	Bool same_screen;	/* same screen flag */
16652 	Bool focus;		/* Boolean focus */
16653 	KeyOrButtonMask state;	/* key or button mask */
16654 }
16655 alias XCrossingEvent XEnterWindowEvent;
16656 alias XCrossingEvent XLeaveWindowEvent;
16657 
16658 struct XFocusChangeEvent{
16659 	int type;		/* FocusIn or FocusOut */
16660 	arch_ulong serial;	/* # of last request processed by server */
16661 	Bool send_event;	/* true if this came from a SendEvent request */
16662 	Display *display;	/* Display the event was read from */
16663 	Window window;		/* window of event */
16664 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16665 				   NotifyGrab, NotifyUngrab */
16666 	NotifyDetail detail;
16667 	/*
16668 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16669 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16670 	 * NotifyPointerRoot, NotifyDetailNone
16671 	 */
16672 }
16673 alias XFocusChangeEvent XFocusInEvent;
16674 alias XFocusChangeEvent XFocusOutEvent;
16675 
16676 enum CWBackPixmap              = (1L<<0);
16677 enum CWBackPixel               = (1L<<1);
16678 enum CWBorderPixmap            = (1L<<2);
16679 enum CWBorderPixel             = (1L<<3);
16680 enum CWBitGravity              = (1L<<4);
16681 enum CWWinGravity              = (1L<<5);
16682 enum CWBackingStore            = (1L<<6);
16683 enum CWBackingPlanes           = (1L<<7);
16684 enum CWBackingPixel            = (1L<<8);
16685 enum CWOverrideRedirect        = (1L<<9);
16686 enum CWSaveUnder               = (1L<<10);
16687 enum CWEventMask               = (1L<<11);
16688 enum CWDontPropagate           = (1L<<12);
16689 enum CWColormap                = (1L<<13);
16690 enum CWCursor                  = (1L<<14);
16691 
16692 struct XWindowAttributes {
16693 	int x, y;			/* location of window */
16694 	int width, height;		/* width and height of window */
16695 	int border_width;		/* border width of window */
16696 	int depth;			/* depth of window */
16697 	Visual *visual;			/* the associated visual structure */
16698 	Window root;			/* root of screen containing window */
16699 	int class_;			/* InputOutput, InputOnly*/
16700 	int bit_gravity;		/* one of the bit gravity values */
16701 	int win_gravity;		/* one of the window gravity values */
16702 	int backing_store;		/* NotUseful, WhenMapped, Always */
16703 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16704 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16705 	Bool save_under;		/* boolean, should bits under be saved? */
16706 	Colormap colormap;		/* color map to be associated with window */
16707 	Bool map_installed;		/* boolean, is color map currently installed*/
16708 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16709 	arch_long all_event_masks;		/* set of events all people have interest in*/
16710 	arch_long your_event_mask;		/* my event mask */
16711 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16712 	Bool override_redirect;		/* boolean value for override-redirect */
16713 	Screen *screen;			/* back pointer to correct screen */
16714 }
16715 
16716 enum IsUnmapped = 0;
16717 enum IsUnviewable = 1;
16718 enum IsViewable = 2;
16719 
16720 struct XSetWindowAttributes {
16721 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16722 	arch_ulong background_pixel;/* background pixel */
16723 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16724 	arch_ulong border_pixel;/* border pixel value */
16725 	int bit_gravity;         /* one of bit gravity values */
16726 	int win_gravity;         /* one of the window gravity values */
16727 	int backing_store;       /* NotUseful, WhenMapped, Always */
16728 	arch_ulong backing_planes;/* planes to be preserved if possible */
16729 	arch_ulong backing_pixel;/* value to use in restoring planes */
16730 	Bool save_under;         /* should bits under be saved? (popups) */
16731 	arch_long event_mask;         /* set of events that should be saved */
16732 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16733 	Bool override_redirect;  /* boolean value for override_redirect */
16734 	Colormap colormap;       /* color map to be associated with window */
16735 	Cursor cursor;           /* cursor to be displayed (or None) */
16736 }
16737 
16738 
16739 alias int Status;
16740 
16741 
16742 enum EventMask:int
16743 {
16744 	NoEventMask				=0,
16745 	KeyPressMask			=1<<0,
16746 	KeyReleaseMask			=1<<1,
16747 	ButtonPressMask			=1<<2,
16748 	ButtonReleaseMask		=1<<3,
16749 	EnterWindowMask			=1<<4,
16750 	LeaveWindowMask			=1<<5,
16751 	PointerMotionMask		=1<<6,
16752 	PointerMotionHintMask	=1<<7,
16753 	Button1MotionMask		=1<<8,
16754 	Button2MotionMask		=1<<9,
16755 	Button3MotionMask		=1<<10,
16756 	Button4MotionMask		=1<<11,
16757 	Button5MotionMask		=1<<12,
16758 	ButtonMotionMask		=1<<13,
16759 	KeymapStateMask		=1<<14,
16760 	ExposureMask			=1<<15,
16761 	VisibilityChangeMask	=1<<16,
16762 	StructureNotifyMask		=1<<17,
16763 	ResizeRedirectMask		=1<<18,
16764 	SubstructureNotifyMask	=1<<19,
16765 	SubstructureRedirectMask=1<<20,
16766 	FocusChangeMask			=1<<21,
16767 	PropertyChangeMask		=1<<22,
16768 	ColormapChangeMask		=1<<23,
16769 	OwnerGrabButtonMask		=1<<24
16770 }
16771 
16772 struct MwmHints {
16773 	c_ulong flags;
16774 	c_ulong functions;
16775 	c_ulong decorations;
16776 	c_long input_mode;
16777 	c_ulong status;
16778 }
16779 
16780 enum {
16781 	MWM_HINTS_FUNCTIONS = (1L << 0),
16782 	MWM_HINTS_DECORATIONS =  (1L << 1),
16783 
16784 	MWM_FUNC_ALL = (1L << 0),
16785 	MWM_FUNC_RESIZE = (1L << 1),
16786 	MWM_FUNC_MOVE = (1L << 2),
16787 	MWM_FUNC_MINIMIZE = (1L << 3),
16788 	MWM_FUNC_MAXIMIZE = (1L << 4),
16789 	MWM_FUNC_CLOSE = (1L << 5),
16790 
16791 	MWM_DECOR_ALL = (1L << 0),
16792 	MWM_DECOR_BORDER = (1L << 1),
16793 	MWM_DECOR_RESIZEH = (1L << 2),
16794 	MWM_DECOR_TITLE = (1L << 3),
16795 	MWM_DECOR_MENU = (1L << 4),
16796 	MWM_DECOR_MINIMIZE = (1L << 5),
16797 	MWM_DECOR_MAXIMIZE = (1L << 6),
16798 }
16799 
16800 import core.stdc.config : c_long, c_ulong;
16801 
16802 	/* Size hints mask bits */
16803 
16804 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16805 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16806 	enum   PPosition   = (1L << 2)          /* program specified position */;
16807 	enum   PSize       = (1L << 3)          /* program specified size */;
16808 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16809 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16810 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16811 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16812 	enum   PBaseSize   = (1L << 8);
16813 	enum   PWinGravity = (1L << 9);
16814 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16815 	struct XSizeHints {
16816 		arch_long flags;         /* marks which fields in this structure are defined */
16817 		int x, y;           /* Obsolete */
16818 		int width, height;  /* Obsolete */
16819 		int min_width, min_height;
16820 		int max_width, max_height;
16821 		int width_inc, height_inc;
16822 		struct Aspect {
16823 			int x;       /* numerator */
16824 			int y;       /* denominator */
16825 		}
16826 
16827 		Aspect min_aspect;
16828 		Aspect max_aspect;
16829 		int base_width, base_height;
16830 		int win_gravity;
16831 		/* this structure may be extended in the future */
16832 	}
16833 
16834 
16835 
16836 enum EventType:int
16837 {
16838 	KeyPress			=2,
16839 	KeyRelease			=3,
16840 	ButtonPress			=4,
16841 	ButtonRelease		=5,
16842 	MotionNotify		=6,
16843 	EnterNotify			=7,
16844 	LeaveNotify			=8,
16845 	FocusIn				=9,
16846 	FocusOut			=10,
16847 	KeymapNotify		=11,
16848 	Expose				=12,
16849 	GraphicsExpose		=13,
16850 	NoExpose			=14,
16851 	VisibilityNotify	=15,
16852 	CreateNotify		=16,
16853 	DestroyNotify		=17,
16854 	UnmapNotify		=18,
16855 	MapNotify			=19,
16856 	MapRequest			=20,
16857 	ReparentNotify		=21,
16858 	ConfigureNotify		=22,
16859 	ConfigureRequest	=23,
16860 	GravityNotify		=24,
16861 	ResizeRequest		=25,
16862 	CirculateNotify		=26,
16863 	CirculateRequest	=27,
16864 	PropertyNotify		=28,
16865 	SelectionClear		=29,
16866 	SelectionRequest	=30,
16867 	SelectionNotify		=31,
16868 	ColormapNotify		=32,
16869 	ClientMessage		=33,
16870 	MappingNotify		=34,
16871 	LASTEvent			=35	/* must be bigger than any event # */
16872 }
16873 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16874 struct XKeymapEvent
16875 {
16876 	int type;
16877 	arch_ulong serial;	/* # of last request processed by server */
16878 	Bool send_event;	/* true if this came from a SendEvent request */
16879 	Display *display;	/* Display the event was read from */
16880 	Window window;
16881 	byte[32] key_vector;
16882 }
16883 
16884 struct XExposeEvent
16885 {
16886 	int type;
16887 	arch_ulong serial;	/* # of last request processed by server */
16888 	Bool send_event;	/* true if this came from a SendEvent request */
16889 	Display *display;	/* Display the event was read from */
16890 	Window window;
16891 	int x, y;
16892 	int width, height;
16893 	int count;		/* if non-zero, at least this many more */
16894 }
16895 
16896 struct XGraphicsExposeEvent{
16897 	int type;
16898 	arch_ulong serial;	/* # of last request processed by server */
16899 	Bool send_event;	/* true if this came from a SendEvent request */
16900 	Display *display;	/* Display the event was read from */
16901 	Drawable drawable;
16902 	int x, y;
16903 	int width, height;
16904 	int count;		/* if non-zero, at least this many more */
16905 	int major_code;		/* core is CopyArea or CopyPlane */
16906 	int minor_code;		/* not defined in the core */
16907 }
16908 
16909 struct XNoExposeEvent{
16910 	int type;
16911 	arch_ulong serial;	/* # of last request processed by server */
16912 	Bool send_event;	/* true if this came from a SendEvent request */
16913 	Display *display;	/* Display the event was read from */
16914 	Drawable drawable;
16915 	int major_code;		/* core is CopyArea or CopyPlane */
16916 	int minor_code;		/* not defined in the core */
16917 }
16918 
16919 struct XVisibilityEvent{
16920 	int type;
16921 	arch_ulong serial;	/* # of last request processed by server */
16922 	Bool send_event;	/* true if this came from a SendEvent request */
16923 	Display *display;	/* Display the event was read from */
16924 	Window window;
16925 	VisibilityNotify state;		/* Visibility state */
16926 }
16927 
16928 struct XCreateWindowEvent{
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 parent;		/* parent of the window */
16934 	Window window;		/* window id of window created */
16935 	int x, y;		/* window location */
16936 	int width, height;	/* size of window */
16937 	int border_width;	/* border width */
16938 	Bool override_redirect;	/* creation should be overridden */
16939 }
16940 
16941 struct XDestroyWindowEvent
16942 {
16943 	int type;
16944 	arch_ulong serial;		/* # of last request processed by server */
16945 	Bool send_event;	/* true if this came from a SendEvent request */
16946 	Display *display;	/* Display the event was read from */
16947 	Window event;
16948 	Window window;
16949 }
16950 
16951 struct XUnmapEvent
16952 {
16953 	int type;
16954 	arch_ulong serial;		/* # of last request processed by server */
16955 	Bool send_event;	/* true if this came from a SendEvent request */
16956 	Display *display;	/* Display the event was read from */
16957 	Window event;
16958 	Window window;
16959 	Bool from_configure;
16960 }
16961 
16962 struct XMapEvent
16963 {
16964 	int type;
16965 	arch_ulong serial;		/* # of last request processed by server */
16966 	Bool send_event;	/* true if this came from a SendEvent request */
16967 	Display *display;	/* Display the event was read from */
16968 	Window event;
16969 	Window window;
16970 	Bool override_redirect;	/* Boolean, is override set... */
16971 }
16972 
16973 struct XMapRequestEvent
16974 {
16975 	int type;
16976 	arch_ulong serial;	/* # of last request processed by server */
16977 	Bool send_event;	/* true if this came from a SendEvent request */
16978 	Display *display;	/* Display the event was read from */
16979 	Window parent;
16980 	Window window;
16981 }
16982 
16983 struct XReparentEvent
16984 {
16985 	int type;
16986 	arch_ulong serial;	/* # of last request processed by server */
16987 	Bool send_event;	/* true if this came from a SendEvent request */
16988 	Display *display;	/* Display the event was read from */
16989 	Window event;
16990 	Window window;
16991 	Window parent;
16992 	int x, y;
16993 	Bool override_redirect;
16994 }
16995 
16996 struct XConfigureEvent
16997 {
16998 	int type;
16999 	arch_ulong serial;	/* # of last request processed by server */
17000 	Bool send_event;	/* true if this came from a SendEvent request */
17001 	Display *display;	/* Display the event was read from */
17002 	Window event;
17003 	Window window;
17004 	int x, y;
17005 	int width, height;
17006 	int border_width;
17007 	Window above;
17008 	Bool override_redirect;
17009 }
17010 
17011 struct XGravityEvent
17012 {
17013 	int type;
17014 	arch_ulong serial;	/* # of last request processed by server */
17015 	Bool send_event;	/* true if this came from a SendEvent request */
17016 	Display *display;	/* Display the event was read from */
17017 	Window event;
17018 	Window window;
17019 	int x, y;
17020 }
17021 
17022 struct XResizeRequestEvent
17023 {
17024 	int type;
17025 	arch_ulong serial;	/* # of last request processed by server */
17026 	Bool send_event;	/* true if this came from a SendEvent request */
17027 	Display *display;	/* Display the event was read from */
17028 	Window window;
17029 	int width, height;
17030 }
17031 
17032 struct  XConfigureRequestEvent
17033 {
17034 	int type;
17035 	arch_ulong serial;	/* # of last request processed by server */
17036 	Bool send_event;	/* true if this came from a SendEvent request */
17037 	Display *display;	/* Display the event was read from */
17038 	Window parent;
17039 	Window window;
17040 	int x, y;
17041 	int width, height;
17042 	int border_width;
17043 	Window above;
17044 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17045 	arch_ulong value_mask;
17046 }
17047 
17048 struct XCirculateEvent
17049 {
17050 	int type;
17051 	arch_ulong serial;	/* # of last request processed by server */
17052 	Bool send_event;	/* true if this came from a SendEvent request */
17053 	Display *display;	/* Display the event was read from */
17054 	Window event;
17055 	Window window;
17056 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17057 }
17058 
17059 struct XCirculateRequestEvent
17060 {
17061 	int type;
17062 	arch_ulong serial;	/* # of last request processed by server */
17063 	Bool send_event;	/* true if this came from a SendEvent request */
17064 	Display *display;	/* Display the event was read from */
17065 	Window parent;
17066 	Window window;
17067 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17068 }
17069 
17070 struct XPropertyEvent
17071 {
17072 	int type;
17073 	arch_ulong serial;	/* # of last request processed by server */
17074 	Bool send_event;	/* true if this came from a SendEvent request */
17075 	Display *display;	/* Display the event was read from */
17076 	Window window;
17077 	Atom atom;
17078 	Time time;
17079 	PropertyNotification state;		/* NewValue, Deleted */
17080 }
17081 
17082 struct XSelectionClearEvent
17083 {
17084 	int type;
17085 	arch_ulong serial;	/* # of last request processed by server */
17086 	Bool send_event;	/* true if this came from a SendEvent request */
17087 	Display *display;	/* Display the event was read from */
17088 	Window window;
17089 	Atom selection;
17090 	Time time;
17091 }
17092 
17093 struct XSelectionRequestEvent
17094 {
17095 	int type;
17096 	arch_ulong serial;	/* # of last request processed by server */
17097 	Bool send_event;	/* true if this came from a SendEvent request */
17098 	Display *display;	/* Display the event was read from */
17099 	Window owner;
17100 	Window requestor;
17101 	Atom selection;
17102 	Atom target;
17103 	Atom property;
17104 	Time time;
17105 }
17106 
17107 struct XSelectionEvent
17108 {
17109 	int type;
17110 	arch_ulong serial;	/* # of last request processed by server */
17111 	Bool send_event;	/* true if this came from a SendEvent request */
17112 	Display *display;	/* Display the event was read from */
17113 	Window requestor;
17114 	Atom selection;
17115 	Atom target;
17116 	Atom property;		/* ATOM or None */
17117 	Time time;
17118 }
17119 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17120 
17121 struct XColormapEvent
17122 {
17123 	int type;
17124 	arch_ulong serial;	/* # of last request processed by server */
17125 	Bool send_event;	/* true if this came from a SendEvent request */
17126 	Display *display;	/* Display the event was read from */
17127 	Window window;
17128 	Colormap colormap;	/* COLORMAP or None */
17129 	Bool new_;		/* C++ */
17130 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17131 }
17132 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17133 
17134 struct XClientMessageEvent
17135 {
17136 	int type;
17137 	arch_ulong serial;	/* # of last request processed by server */
17138 	Bool send_event;	/* true if this came from a SendEvent request */
17139 	Display *display;	/* Display the event was read from */
17140 	Window window;
17141 	Atom message_type;
17142 	int format;
17143 	union Data{
17144 		byte[20] b;
17145 		short[10] s;
17146 		arch_ulong[5] l;
17147 	}
17148 	Data data;
17149 
17150 }
17151 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17152 
17153 struct XMappingEvent
17154 {
17155 	int type;
17156 	arch_ulong serial;	/* # of last request processed by server */
17157 	Bool send_event;	/* true if this came from a SendEvent request */
17158 	Display *display;	/* Display the event was read from */
17159 	Window window;		/* unused */
17160 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17161 				   MappingPointer */
17162 	int first_keycode;	/* first keycode */
17163 	int count;		/* defines range of change w. first_keycode*/
17164 }
17165 
17166 struct XErrorEvent
17167 {
17168 	int type;
17169 	Display *display;	/* Display the event was read from */
17170 	XID resourceid;		/* resource id */
17171 	arch_ulong serial;	/* serial number of failed request */
17172 	ubyte error_code;	/* error code of failed request */
17173 	ubyte request_code;	/* Major op-code of failed request */
17174 	ubyte minor_code;	/* Minor op-code of failed request */
17175 }
17176 
17177 struct XAnyEvent
17178 {
17179 	int type;
17180 	arch_ulong serial;	/* # of last request processed by server */
17181 	Bool send_event;	/* true if this came from a SendEvent request */
17182 	Display *display;/* Display the event was read from */
17183 	Window window;	/* window on which event was requested in event mask */
17184 }
17185 
17186 union XEvent{
17187 	int type;		/* must not be changed; first element */
17188 	XAnyEvent xany;
17189 	XKeyEvent xkey;
17190 	XButtonEvent xbutton;
17191 	XMotionEvent xmotion;
17192 	XCrossingEvent xcrossing;
17193 	XFocusChangeEvent xfocus;
17194 	XExposeEvent xexpose;
17195 	XGraphicsExposeEvent xgraphicsexpose;
17196 	XNoExposeEvent xnoexpose;
17197 	XVisibilityEvent xvisibility;
17198 	XCreateWindowEvent xcreatewindow;
17199 	XDestroyWindowEvent xdestroywindow;
17200 	XUnmapEvent xunmap;
17201 	XMapEvent xmap;
17202 	XMapRequestEvent xmaprequest;
17203 	XReparentEvent xreparent;
17204 	XConfigureEvent xconfigure;
17205 	XGravityEvent xgravity;
17206 	XResizeRequestEvent xresizerequest;
17207 	XConfigureRequestEvent xconfigurerequest;
17208 	XCirculateEvent xcirculate;
17209 	XCirculateRequestEvent xcirculaterequest;
17210 	XPropertyEvent xproperty;
17211 	XSelectionClearEvent xselectionclear;
17212 	XSelectionRequestEvent xselectionrequest;
17213 	XSelectionEvent xselection;
17214 	XColormapEvent xcolormap;
17215 	XClientMessageEvent xclient;
17216 	XMappingEvent xmapping;
17217 	XErrorEvent xerror;
17218 	XKeymapEvent xkeymap;
17219 	arch_ulong[24] pad;
17220 }
17221 
17222 
17223 	struct Display {
17224 		XExtData *ext_data;	/* hook for extension to hang data */
17225 		_XPrivate *private1;
17226 		int fd;			/* Network socket. */
17227 		int private2;
17228 		int proto_major_version;/* major version of server's X protocol */
17229 		int proto_minor_version;/* minor version of servers X protocol */
17230 		char *vendor;		/* vendor of the server hardware */
17231 	    	XID private3;
17232 		XID private4;
17233 		XID private5;
17234 		int private6;
17235 		XID function(Display*)resource_alloc;/* allocator function */
17236 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17237 		int bitmap_unit;	/* padding and data requirements */
17238 		int bitmap_pad;		/* padding requirements on bitmaps */
17239 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17240 		int nformats;		/* number of pixmap formats in list */
17241 		ScreenFormat *pixmap_format;	/* pixmap format list */
17242 		int private8;
17243 		int release;		/* release of the server */
17244 		_XPrivate *private9;
17245 		_XPrivate *private10;
17246 		int qlen;		/* Length of input event queue */
17247 		arch_ulong last_request_read; /* seq number of last event read */
17248 		arch_ulong request;	/* sequence number of last request. */
17249 		XPointer private11;
17250 		XPointer private12;
17251 		XPointer private13;
17252 		XPointer private14;
17253 		uint max_request_size; /* maximum number 32 bit words in request*/
17254 		_XrmHashBucketRec *db;
17255 		int function  (Display*)private15;
17256 		char *display_name;	/* "host:display" string used on this connect*/
17257 		int default_screen;	/* default screen for operations */
17258 		int nscreens;		/* number of screens on this server*/
17259 		Screen *screens;	/* pointer to list of screens */
17260 		arch_ulong motion_buffer;	/* size of motion buffer */
17261 		arch_ulong private16;
17262 		int min_keycode;	/* minimum defined keycode */
17263 		int max_keycode;	/* maximum defined keycode */
17264 		XPointer private17;
17265 		XPointer private18;
17266 		int private19;
17267 		byte *xdefaults;	/* contents of defaults from server */
17268 		/* there is more to this structure, but it is private to Xlib */
17269 	}
17270 
17271 	// I got these numbers from a C program as a sanity test
17272 	version(X86_64) {
17273 		static assert(Display.sizeof == 296);
17274 		static assert(XPointer.sizeof == 8);
17275 		static assert(XErrorEvent.sizeof == 40);
17276 		static assert(XAnyEvent.sizeof == 40);
17277 		static assert(XMappingEvent.sizeof == 56);
17278 		static assert(XEvent.sizeof == 192);
17279     	} else version (AArch64) {
17280         	// omit check for aarch64
17281 	} else {
17282 		static assert(Display.sizeof == 176);
17283 		static assert(XPointer.sizeof == 4);
17284 		static assert(XEvent.sizeof == 96);
17285 	}
17286 
17287 struct Depth
17288 {
17289 	int depth;		/* this depth (Z) of the depth */
17290 	int nvisuals;		/* number of Visual types at this depth */
17291 	Visual *visuals;	/* list of visuals possible at this depth */
17292 }
17293 
17294 alias void* GC;
17295 alias c_ulong VisualID;
17296 alias XID Colormap;
17297 alias XID Cursor;
17298 alias XID KeySym;
17299 alias uint KeyCode;
17300 enum None = 0;
17301 }
17302 
17303 version(without_opengl) {}
17304 else {
17305 extern(C) nothrow @nogc {
17306 
17307 
17308 static if(!SdpyIsUsingIVGLBinds) {
17309 enum GLX_USE_GL=            1;       /* support GLX rendering */
17310 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17311 enum GLX_LEVEL=             3;       /* level in plane stacking */
17312 enum GLX_RGBA=              4;       /* true if RGBA mode */
17313 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17314 enum GLX_STEREO=            6;       /* stereo buffering supported */
17315 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17316 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17317 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17318 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17319 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17320 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17321 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17322 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17323 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17324 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17325 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17326 
17327 
17328 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17329 
17330 
17331 
17332 enum GL_TRUE = 1;
17333 enum GL_FALSE = 0;
17334 alias int GLint;
17335 }
17336 
17337 alias XID GLXContextID;
17338 alias XID GLXPixmap;
17339 alias XID GLXDrawable;
17340 alias XID GLXPbuffer;
17341 alias XID GLXWindow;
17342 alias XID GLXFBConfigID;
17343 alias void* GLXContext;
17344 
17345 }
17346 }
17347 
17348 enum AllocNone = 0;
17349 
17350 extern(C) {
17351 	/* WARNING, this type not in Xlib spec */
17352 	extern(C) alias XIOErrorHandler = int function (Display* display);
17353 }
17354 
17355 extern(C) nothrow
17356 alias XErrorHandler = int function(Display*, XErrorEvent*);
17357 
17358 extern(C) nothrow @nogc {
17359 struct Screen{
17360 	XExtData *ext_data;		/* hook for extension to hang data */
17361 	Display *display;		/* back pointer to display structure */
17362 	Window root;			/* Root window id. */
17363 	int width, height;		/* width and height of screen */
17364 	int mwidth, mheight;	/* width and height of  in millimeters */
17365 	int ndepths;			/* number of depths possible */
17366 	Depth *depths;			/* list of allowable depths on the screen */
17367 	int root_depth;			/* bits per pixel */
17368 	Visual *root_visual;	/* root visual */
17369 	GC default_gc;			/* GC for the root root visual */
17370 	Colormap cmap;			/* default color map */
17371 	uint white_pixel;
17372 	uint black_pixel;		/* White and Black pixel values */
17373 	int max_maps, min_maps;	/* max and min color maps */
17374 	int backing_store;		/* Never, WhenMapped, Always */
17375 	bool save_unders;
17376 	int root_input_mask;	/* initial root input mask */
17377 }
17378 
17379 struct Visual
17380 {
17381 	XExtData *ext_data;	/* hook for extension to hang data */
17382 	VisualID visualid;	/* visual id of this visual */
17383 	int class_;			/* class of screen (monochrome, etc.) */
17384 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17385 	int bits_per_rgb;	/* log base 2 of distinct color values */
17386 	int map_entries;	/* color map entries */
17387 }
17388 
17389 	alias Display* _XPrivDisplay;
17390 
17391 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17392 		assert(dpy !is null);
17393 		return &dpy.screens[scr];
17394 	}
17395 
17396 	extern(D) Window RootWindow(Display *dpy,int scr) {
17397 		return ScreenOfDisplay(dpy,scr).root;
17398 	}
17399 
17400 	struct XWMHints {
17401 		arch_long flags;
17402 		Bool input;
17403 		int initial_state;
17404 		Pixmap icon_pixmap;
17405 		Window icon_window;
17406 		int icon_x, icon_y;
17407 		Pixmap icon_mask;
17408 		XID window_group;
17409 	}
17410 
17411 	struct XClassHint {
17412 		char* res_name;
17413 		char* res_class;
17414 	}
17415 
17416 	extern(D) int DefaultScreen(Display *dpy) {
17417 		return dpy.default_screen;
17418 	}
17419 
17420 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17421 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17422 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17423 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17424 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17425 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17426 
17427 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17428 
17429 	enum int AnyPropertyType = 0;
17430 	enum int Success = 0;
17431 
17432 	enum int RevertToNone = None;
17433 	enum int PointerRoot = 1;
17434 	enum Time CurrentTime = 0;
17435 	enum int RevertToPointerRoot = PointerRoot;
17436 	enum int RevertToParent = 2;
17437 
17438 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17439 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17440 	}
17441 
17442 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17443 		return ScreenOfDisplay(dpy,scr).root_visual;
17444 	}
17445 
17446 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17447 		return ScreenOfDisplay(dpy,scr).default_gc;
17448 	}
17449 
17450 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17451 		return ScreenOfDisplay(dpy,scr).black_pixel;
17452 	}
17453 
17454 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17455 		return ScreenOfDisplay(dpy,scr).white_pixel;
17456 	}
17457 
17458 	alias void* XFontSet; // i think
17459 	struct XmbTextItem {
17460 		char* chars;
17461 		int nchars;
17462 		int delta;
17463 		XFontSet font_set;
17464 	}
17465 
17466 	struct XTextItem {
17467 		char* chars;
17468 		int nchars;
17469 		int delta;
17470 		Font font;
17471 	}
17472 
17473 	enum {
17474 		GXclear        = 0x0, /* 0 */
17475 		GXand          = 0x1, /* src AND dst */
17476 		GXandReverse   = 0x2, /* src AND NOT dst */
17477 		GXcopy         = 0x3, /* src */
17478 		GXandInverted  = 0x4, /* NOT src AND dst */
17479 		GXnoop         = 0x5, /* dst */
17480 		GXxor          = 0x6, /* src XOR dst */
17481 		GXor           = 0x7, /* src OR dst */
17482 		GXnor          = 0x8, /* NOT src AND NOT dst */
17483 		GXequiv        = 0x9, /* NOT src XOR dst */
17484 		GXinvert       = 0xa, /* NOT dst */
17485 		GXorReverse    = 0xb, /* src OR NOT dst */
17486 		GXcopyInverted = 0xc, /* NOT src */
17487 		GXorInverted   = 0xd, /* NOT src OR dst */
17488 		GXnand         = 0xe, /* NOT src OR NOT dst */
17489 		GXset          = 0xf, /* 1 */
17490 	}
17491 	enum QueueMode : int {
17492 		QueuedAlready,
17493 		QueuedAfterReading,
17494 		QueuedAfterFlush
17495 	}
17496 
17497 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17498 
17499 	struct XPoint {
17500 		short x;
17501 		short y;
17502 	}
17503 
17504 	enum CoordMode:int {
17505 		CoordModeOrigin = 0,
17506 		CoordModePrevious = 1
17507 	}
17508 
17509 	enum PolygonShape:int {
17510 		Complex = 0,
17511 		Nonconvex = 1,
17512 		Convex = 2
17513 	}
17514 
17515 	struct XTextProperty {
17516 		const(char)* value;		/* same as Property routines */
17517 		Atom encoding;			/* prop type */
17518 		int format;				/* prop data format: 8, 16, or 32 */
17519 		arch_ulong nitems;		/* number of data items in value */
17520 	}
17521 
17522 	version( X86_64 ) {
17523 		static assert(XTextProperty.sizeof == 32);
17524 	}
17525 
17526 
17527 	struct XGCValues {
17528 		int function_;           /* logical operation */
17529 		arch_ulong plane_mask;/* plane mask */
17530 		arch_ulong foreground;/* foreground pixel */
17531 		arch_ulong background;/* background pixel */
17532 		int line_width;         /* line width */
17533 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17534 		int cap_style;          /* CapNotLast, CapButt,
17535 					   CapRound, CapProjecting */
17536 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17537 		int fill_style;         /* FillSolid, FillTiled,
17538 					   FillStippled, FillOpaeueStippled */
17539 		int fill_rule;          /* EvenOddRule, WindingRule */
17540 		int arc_mode;           /* ArcChord, ArcPieSlice */
17541 		Pixmap tile;            /* tile pixmap for tiling operations */
17542 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17543 		int ts_x_origin;        /* offset for tile or stipple operations */
17544 		int ts_y_origin;
17545 		Font font;              /* default text font for text operations */
17546 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17547 		Bool graphics_exposures;/* boolean, should exposures be generated */
17548 		int clip_x_origin;      /* origin for clipping */
17549 		int clip_y_origin;
17550 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17551 		int dash_offset;        /* patterned/dashed line information */
17552 		char dashes;
17553 	}
17554 
17555 	struct XColor {
17556 		arch_ulong pixel;
17557 		ushort red, green, blue;
17558 		byte flags;
17559 		byte pad;
17560 	}
17561 
17562 	struct XRectangle {
17563 		short x;
17564 		short y;
17565 		ushort width;
17566 		ushort height;
17567 	}
17568 
17569 	enum ClipByChildren = 0;
17570 	enum IncludeInferiors = 1;
17571 
17572 	enum Atom XA_PRIMARY = 1;
17573 	enum Atom XA_SECONDARY = 2;
17574 	enum Atom XA_STRING = 31;
17575 	enum Atom XA_CARDINAL = 6;
17576 	enum Atom XA_WM_NAME = 39;
17577 	enum Atom XA_ATOM = 4;
17578 	enum Atom XA_WINDOW = 33;
17579 	enum Atom XA_WM_HINTS = 35;
17580 	enum int PropModeAppend = 2;
17581 	enum int PropModeReplace = 0;
17582 	enum int PropModePrepend = 1;
17583 
17584 	enum int CopyFromParent = 0;
17585 	enum int InputOutput = 1;
17586 
17587 	// XWMHints
17588 	enum InputHint = 1 << 0;
17589 	enum StateHint = 1 << 1;
17590 	enum IconPixmapHint = (1L << 2);
17591 	enum IconWindowHint = (1L << 3);
17592 	enum IconPositionHint = (1L << 4);
17593 	enum IconMaskHint = (1L << 5);
17594 	enum WindowGroupHint = (1L << 6);
17595 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17596 	enum XUrgencyHint = (1L << 8);
17597 
17598 	// GC Components
17599 	enum GCFunction           =   (1L<<0);
17600 	enum GCPlaneMask         =    (1L<<1);
17601 	enum GCForeground       =     (1L<<2);
17602 	enum GCBackground      =      (1L<<3);
17603 	enum GCLineWidth      =       (1L<<4);
17604 	enum GCLineStyle     =        (1L<<5);
17605 	enum GCCapStyle     =         (1L<<6);
17606 	enum GCJoinStyle   =          (1L<<7);
17607 	enum GCFillStyle  =           (1L<<8);
17608 	enum GCFillRule  =            (1L<<9);
17609 	enum GCTile     =             (1L<<10);
17610 	enum GCStipple           =    (1L<<11);
17611 	enum GCTileStipXOrigin  =     (1L<<12);
17612 	enum GCTileStipYOrigin =      (1L<<13);
17613 	enum GCFont               =   (1L<<14);
17614 	enum GCSubwindowMode     =    (1L<<15);
17615 	enum GCGraphicsExposures=     (1L<<16);
17616 	enum GCClipXOrigin     =      (1L<<17);
17617 	enum GCClipYOrigin    =       (1L<<18);
17618 	enum GCClipMask      =        (1L<<19);
17619 	enum GCDashOffset   =         (1L<<20);
17620 	enum GCDashList    =          (1L<<21);
17621 	enum GCArcMode    =           (1L<<22);
17622 	enum GCLastBit   =            22;
17623 
17624 
17625 	enum int WithdrawnState = 0;
17626 	enum int NormalState = 1;
17627 	enum int IconicState = 3;
17628 
17629 }
17630 } else version (OSXCocoa) {
17631 private:
17632 	alias void* id;
17633 	alias void* Class;
17634 	alias void* SEL;
17635 	alias void* IMP;
17636 	alias void* Ivar;
17637 	alias byte BOOL;
17638 	alias const(void)* CFStringRef;
17639 	alias const(void)* CFAllocatorRef;
17640 	alias const(void)* CFTypeRef;
17641 	alias const(void)* CGContextRef;
17642 	alias const(void)* CGColorSpaceRef;
17643 	alias const(void)* CGImageRef;
17644 	alias ulong CGBitmapInfo;
17645 
17646 	struct objc_super {
17647 		id self;
17648 		Class superclass;
17649 	}
17650 
17651 	struct CFRange {
17652 		long location, length;
17653 	}
17654 
17655 	struct NSPoint {
17656 		double x, y;
17657 
17658 		static fromTuple(T)(T tupl) {
17659 			return NSPoint(tupl.tupleof);
17660 		}
17661 	}
17662 	struct NSSize {
17663 		double width, height;
17664 	}
17665 	struct NSRect {
17666 		NSPoint origin;
17667 		NSSize size;
17668 	}
17669 	alias NSPoint CGPoint;
17670 	alias NSSize CGSize;
17671 	alias NSRect CGRect;
17672 
17673 	struct CGAffineTransform {
17674 		double a, b, c, d, tx, ty;
17675 	}
17676 
17677 	enum NSApplicationActivationPolicyRegular = 0;
17678 	enum NSBackingStoreBuffered = 2;
17679 	enum kCFStringEncodingUTF8 = 0x08000100;
17680 
17681 	enum : size_t {
17682 		NSBorderlessWindowMask = 0,
17683 		NSTitledWindowMask = 1 << 0,
17684 		NSClosableWindowMask = 1 << 1,
17685 		NSMiniaturizableWindowMask = 1 << 2,
17686 		NSResizableWindowMask = 1 << 3,
17687 		NSTexturedBackgroundWindowMask = 1 << 8
17688 	}
17689 
17690 	enum : ulong {
17691 		kCGImageAlphaNone,
17692 		kCGImageAlphaPremultipliedLast,
17693 		kCGImageAlphaPremultipliedFirst,
17694 		kCGImageAlphaLast,
17695 		kCGImageAlphaFirst,
17696 		kCGImageAlphaNoneSkipLast,
17697 		kCGImageAlphaNoneSkipFirst
17698 	}
17699 	enum : ulong {
17700 		kCGBitmapAlphaInfoMask = 0x1F,
17701 		kCGBitmapFloatComponents = (1 << 8),
17702 		kCGBitmapByteOrderMask = 0x7000,
17703 		kCGBitmapByteOrderDefault = (0 << 12),
17704 		kCGBitmapByteOrder16Little = (1 << 12),
17705 		kCGBitmapByteOrder32Little = (2 << 12),
17706 		kCGBitmapByteOrder16Big = (3 << 12),
17707 		kCGBitmapByteOrder32Big = (4 << 12)
17708 	}
17709 	enum CGPathDrawingMode {
17710 		kCGPathFill,
17711 		kCGPathEOFill,
17712 		kCGPathStroke,
17713 		kCGPathFillStroke,
17714 		kCGPathEOFillStroke
17715 	}
17716 	enum objc_AssociationPolicy : size_t {
17717 		OBJC_ASSOCIATION_ASSIGN = 0,
17718 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
17719 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
17720 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
17721 		OBJC_ASSOCIATION_COPY = 0x303 //01403
17722 	}
17723 
17724 	extern(C) {
17725 		id objc_msgSend(id receiver, SEL selector, ...);
17726 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
17727 		id objc_getClass(const(char)* name);
17728 		SEL sel_registerName(const(char)* str);
17729 		Class objc_allocateClassPair(Class superclass, const(char)* name,
17730 									 size_t extra_bytes);
17731 		void objc_registerClassPair(Class cls);
17732 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
17733 		id objc_getAssociatedObject(id object, void* key);
17734 		void objc_setAssociatedObject(id object, void* key, id value,
17735 									  objc_AssociationPolicy policy);
17736 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
17737 		id object_getIvar(id object, Ivar ivar);
17738 		void object_setIvar(id object, Ivar ivar, id value);
17739 		BOOL class_addIvar(Class cls, const(char)* name,
17740 						   size_t size, ubyte alignment, const(char)* types);
17741 
17742 		extern __gshared id NSApp;
17743 
17744 		void CFRelease(CFTypeRef obj);
17745 
17746 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
17747 											const(char)* bytes, long numBytes,
17748 											long encoding,
17749 											BOOL isExternalRepresentation);
17750 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
17751 							 char lossByte, bool isExternalRepresentation,
17752 							 char* buffer, long maxBufLen, long* usedBufLen);
17753 		long CFStringGetLength(CFStringRef theString);
17754 
17755 		CGContextRef CGBitmapContextCreate(void* data,
17756 										   size_t width, size_t height,
17757 										   size_t bitsPerComponent,
17758 										   size_t bytesPerRow,
17759 										   CGColorSpaceRef colorspace,
17760 										   CGBitmapInfo bitmapInfo);
17761 		void CGContextRelease(CGContextRef c);
17762 		ubyte* CGBitmapContextGetData(CGContextRef c);
17763 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
17764 		size_t CGBitmapContextGetWidth(CGContextRef c);
17765 		size_t CGBitmapContextGetHeight(CGContextRef c);
17766 
17767 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
17768 		void CGColorSpaceRelease(CGColorSpaceRef cs);
17769 
17770 		void CGContextSetRGBStrokeColor(CGContextRef c,
17771 										double red, double green, double blue,
17772 										double alpha);
17773 		void CGContextSetRGBFillColor(CGContextRef c,
17774 									  double red, double green, double blue,
17775 									  double alpha);
17776 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
17777 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
17778 									  const(char)* str, size_t length);
17779 		void CGContextStrokeLineSegments(CGContextRef c,
17780 										 const(CGPoint)* points, size_t count);
17781 
17782 		void CGContextBeginPath(CGContextRef c);
17783 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
17784 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
17785 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
17786 							 double startAngle, double endAngle, long clockwise);
17787 		void CGContextAddRect(CGContextRef c, CGRect rect);
17788 		void CGContextAddLines(CGContextRef c,
17789 							   const(CGPoint)* points, size_t count);
17790 		void CGContextSaveGState(CGContextRef c);
17791 		void CGContextRestoreGState(CGContextRef c);
17792 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
17793 								 ulong textEncoding);
17794 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
17795 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
17796 
17797 		void CGImageRelease(CGImageRef image);
17798 	}
17799 
17800 private:
17801     // A convenient method to create a CFString (=NSString) from a D string.
17802     CFStringRef createCFString(string str) {
17803         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
17804                                              kCFStringEncodingUTF8, false);
17805     }
17806 
17807     // Objective-C calls.
17808     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
17809         auto _cmd = sel_registerName(selector.ptr);
17810         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17811         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
17812     }
17813     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
17814         auto _cmd = sel_registerName(selector.ptr);
17815         auto cls = objc_getClass(className);
17816         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17817         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
17818     }
17819     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
17820         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
17821     }
17822 
17823     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
17824     alias objc_msgSend_classMethod!("alloc", id) alloc;
17825     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
17826                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
17827     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
17828     alias objc_msgSend_specialized!("center", void) center;
17829     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
17830     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
17831     alias objc_msgSend_specialized!("release", void) release;
17832     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
17833     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
17834     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
17835     alias objc_msgSend_specialized!("invalidate", void) invalidate;
17836     alias objc_msgSend_specialized!("close", void) close;
17837     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
17838                                     id, double, id, SEL, id, BOOL) scheduledTimer;
17839     alias objc_msgSend_specialized!("run", void) run;
17840     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
17841                                     id) currentNSGraphicsContext;
17842     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
17843     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
17844     alias objc_msgSend_specialized!("superclass", Class) superclass;
17845     alias objc_msgSend_specialized!("init", id) init;
17846     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
17847     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
17848     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
17849                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
17850     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
17851     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
17852     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
17853                                     void, BOOL) activateIgnoringOtherApps;
17854     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
17855                                     id) sharedNSApplication;
17856     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
17857 } else static assert(0, "Unsupported operating system");
17858 
17859 
17860 version(OSXCocoa) {
17861 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
17862 	//
17863 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
17864 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
17865 	//
17866 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
17867 	// Probably won't even fully compile right now
17868 
17869     import std.math : PI;
17870     import std.algorithm : map;
17871     import std.array : array;
17872 
17873     alias SimpleWindow NativeWindowHandle;
17874     alias void delegate(id) NativeEventHandler;
17875 
17876     __gshared Ivar simpleWindowIvar;
17877 
17878     enum KEY_ESCAPE = 27;
17879 
17880     mixin template NativeImageImplementation() {
17881         CGContextRef context;
17882         ubyte* rawData;
17883     final:
17884 
17885 	void convertToRgbaBytes(ubyte[] where) {
17886 		assert(where.length == this.width * this.height * 4);
17887 
17888 		// if rawData had a length....
17889 		//assert(rawData.length == where.length);
17890 		for(long idx = 0; idx < where.length; idx += 4) {
17891 			auto alpha = rawData[idx + 3];
17892 			if(alpha == 255) {
17893 				where[idx + 0] = rawData[idx + 0]; // r
17894 				where[idx + 1] = rawData[idx + 1]; // g
17895 				where[idx + 2] = rawData[idx + 2]; // b
17896 				where[idx + 3] = rawData[idx + 3]; // a
17897 			} else {
17898 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
17899 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
17900 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
17901 				where[idx + 3] = rawData[idx + 3]; // a
17902 
17903 			}
17904 		}
17905 	}
17906 
17907 	void setFromRgbaBytes(in ubyte[] where) {
17908 		// FIXME: this is probably wrong
17909 		assert(where.length == this.width * this.height * 4);
17910 
17911 		// if rawData had a length....
17912 		//assert(rawData.length == where.length);
17913 		for(long idx = 0; idx < where.length; idx += 4) {
17914 			auto alpha = rawData[idx + 3];
17915 			if(alpha == 255) {
17916 				rawData[idx + 0] = where[idx + 0]; // r
17917 				rawData[idx + 1] = where[idx + 1]; // g
17918 				rawData[idx + 2] = where[idx + 2]; // b
17919 				rawData[idx + 3] = where[idx + 3]; // a
17920 			} else {
17921 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
17922 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
17923 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
17924 				rawData[idx + 3] = where[idx + 3]; // a
17925 
17926 			}
17927 		}
17928 	}
17929 
17930 
17931         void createImage(int width, int height, bool forcexshm=false) {
17932             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17933             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
17934                                             colorSpace,
17935                                             kCGImageAlphaPremultipliedLast
17936                                                    |kCGBitmapByteOrder32Big);
17937             CGColorSpaceRelease(colorSpace);
17938             rawData = CGBitmapContextGetData(context);
17939         }
17940         void dispose() {
17941             CGContextRelease(context);
17942         }
17943 
17944         void setPixel(int x, int y, Color c) {
17945             auto offset = (y * width + x) * 4;
17946             if (c.a == 255) {
17947                 rawData[offset + 0] = c.r;
17948                 rawData[offset + 1] = c.g;
17949                 rawData[offset + 2] = c.b;
17950                 rawData[offset + 3] = c.a;
17951             } else {
17952                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
17953                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
17954                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
17955                 rawData[offset + 3] = c.a;
17956             }
17957         }
17958     }
17959 
17960     mixin template NativeScreenPainterImplementation() {
17961         CGContextRef context;
17962         ubyte[4] _outlineComponents;
17963 	id view;
17964 
17965         void create(NativeWindowHandle window) {
17966             context = window.drawingContext;
17967 	    view = window.view;
17968         }
17969 
17970         void dispose() {
17971             	setNeedsDisplay(view, true);
17972         }
17973 
17974 	bool manualInvalidations;
17975 	void invalidateRect(Rectangle invalidRect) { }
17976 
17977 	// NotYetImplementedException
17978 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
17979 	void rasterOp(RasterOp op) {}
17980 	Pen _activePen;
17981 	Color _fillColor;
17982 	Rectangle _clipRectangle;
17983 	void setClipRectangle(int, int, int, int) {}
17984 	void setFont(OperatingSystemFont) {}
17985 	int fontHeight() { return 14; }
17986 
17987 	// end
17988 
17989         void pen(Pen pen) {
17990 	    _activePen = pen;
17991 	    auto color = pen.color; // FIXME
17992             double alphaComponent = color.a/255.0f;
17993             CGContextSetRGBStrokeColor(context,
17994                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
17995 
17996             if (color.a != 255) {
17997                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
17998                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
17999                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18000                 _outlineComponents[3] = color.a;
18001             } else {
18002                 _outlineComponents[0] = color.r;
18003                 _outlineComponents[1] = color.g;
18004                 _outlineComponents[2] = color.b;
18005                 _outlineComponents[3] = color.a;
18006             }
18007         }
18008 
18009         @property void fillColor(Color color) {
18010             CGContextSetRGBFillColor(context,
18011                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18012         }
18013 
18014         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18015 		// NotYetImplementedException for upper left/width/height
18016             auto cgImage = CGBitmapContextCreateImage(image.context);
18017             auto size = CGSize(CGBitmapContextGetWidth(image.context),
18018                                CGBitmapContextGetHeight(image.context));
18019             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18020             CGImageRelease(cgImage);
18021         }
18022 
18023 	version(OSXCocoa) {} else // NotYetImplementedException
18024         void drawPixmap(Sprite image, int x, int y) {
18025 		// FIXME: is this efficient?
18026             auto cgImage = CGBitmapContextCreateImage(image.context);
18027             auto size = CGSize(CGBitmapContextGetWidth(image.context),
18028                                CGBitmapContextGetHeight(image.context));
18029             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18030             CGImageRelease(cgImage);
18031         }
18032 
18033 
18034         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18035 		// FIXME: alignment
18036             if (_outlineComponents[3] != 0) {
18037                 CGContextSaveGState(context);
18038                 auto invAlpha = 1.0f/_outlineComponents[3];
18039                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18040                                                   _outlineComponents[1]*invAlpha,
18041                                                   _outlineComponents[2]*invAlpha,
18042                                                   _outlineComponents[3]/255.0f);
18043                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
18044 // auto cfstr = cast(id)createCFString(text);
18045 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18046 // NSPoint(x, y), null);
18047 // CFRelease(cfstr);
18048                 CGContextRestoreGState(context);
18049             }
18050         }
18051 
18052         void drawPixel(int x, int y) {
18053             auto rawData = CGBitmapContextGetData(context);
18054             auto width = CGBitmapContextGetWidth(context);
18055             auto height = CGBitmapContextGetHeight(context);
18056             auto offset = ((height - y - 1) * width + x) * 4;
18057             rawData[offset .. offset+4] = _outlineComponents;
18058         }
18059 
18060         void drawLine(int x1, int y1, int x2, int y2) {
18061             CGPoint[2] linePoints;
18062             linePoints[0] = CGPoint(x1, y1);
18063             linePoints[1] = CGPoint(x2, y2);
18064             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18065         }
18066 
18067         void drawRectangle(int x, int y, int width, int height) {
18068             CGContextBeginPath(context);
18069             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18070             CGContextAddRect(context, rect);
18071             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18072         }
18073 
18074         void drawEllipse(int x1, int y1, int x2, int y2) {
18075             CGContextBeginPath(context);
18076             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18077             CGContextAddEllipseInRect(context, rect);
18078             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18079         }
18080 
18081         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18082             // @@@BUG@@@ Does not support elliptic arc (width != height).
18083             CGContextBeginPath(context);
18084             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18085                             start*PI/(180*64), finish*PI/(180*64), 0);
18086             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18087         }
18088 
18089         void drawPolygon(Point[] intPoints) {
18090             CGContextBeginPath(context);
18091             auto points = array(map!(CGPoint.fromTuple)(intPoints));
18092             CGContextAddLines(context, points.ptr, points.length);
18093             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18094         }
18095     }
18096 
18097     mixin template NativeSimpleWindowImplementation() {
18098         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18099             synchronized {
18100                 if (NSApp == null) initializeApp();
18101             }
18102 
18103             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18104 
18105             // create the window.
18106             window = initWithContentRect(alloc("NSWindow"),
18107                                          contentRect,
18108                                          NSTitledWindowMask
18109                                             |NSClosableWindowMask
18110                                             |NSMiniaturizableWindowMask
18111                                             |NSResizableWindowMask,
18112                                          NSBackingStoreBuffered,
18113                                          true);
18114 
18115             // set the title & move the window to center.
18116             auto windowTitle = createCFString(title);
18117             setTitle(window, windowTitle);
18118             CFRelease(windowTitle);
18119             center(window);
18120 
18121             // create area to draw on.
18122             auto colorSpace = CGColorSpaceCreateDeviceRGB();
18123             drawingContext = CGBitmapContextCreate(null, width, height,
18124                                                    8, 4*width, colorSpace,
18125                                                    kCGImageAlphaPremultipliedLast
18126                                                       |kCGBitmapByteOrder32Big);
18127             CGColorSpaceRelease(colorSpace);
18128             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18129             auto matrix = CGContextGetTextMatrix(drawingContext);
18130             matrix.c = -matrix.c;
18131             matrix.d = -matrix.d;
18132             CGContextSetTextMatrix(drawingContext, matrix);
18133 
18134             // create the subview that things will be drawn on.
18135             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
18136             setContentView(window, view);
18137             object_setIvar(view, simpleWindowIvar, cast(id)this);
18138             release(view);
18139 
18140             setBackgroundColor(window, whiteNSColor);
18141             makeKeyAndOrderFront(window, null);
18142         }
18143         void dispose() {
18144             closeWindow();
18145             release(window);
18146         }
18147         void closeWindow() {
18148             invalidate(timer);
18149             .close(window);
18150         }
18151 
18152         ScreenPainter getPainter(bool manualInvalidations) {
18153 		return ScreenPainter(this, this, manualInvalidations);
18154 	}
18155 
18156         id window;
18157         id timer;
18158         id view;
18159         CGContextRef drawingContext;
18160     }
18161 
18162     extern(C) {
18163     private:
18164         BOOL returnTrue3(id self, SEL _cmd, id app) {
18165             return true;
18166         }
18167         BOOL returnTrue2(id self, SEL _cmd) {
18168             return true;
18169         }
18170 
18171         void pulse(id self, SEL _cmd) {
18172             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18173             simpleWindow.handlePulse();
18174             setNeedsDisplay(self, true);
18175         }
18176         void drawRect(id self, SEL _cmd, NSRect rect) {
18177             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18178             auto curCtx = graphicsPort(currentNSGraphicsContext);
18179             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18180             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
18181                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
18182             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18183             CGImageRelease(cgImage);
18184         }
18185         void keyDown(id self, SEL _cmd, id event) {
18186             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18187 
18188             // the event may have multiple characters, and we send them all at
18189             // once.
18190             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
18191                 auto chars = characters(event);
18192                 auto range = CFRange(0, CFStringGetLength(chars));
18193                 auto buffer = new char[range.length*3];
18194                 long actualLength;
18195                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
18196                                  buffer.ptr, cast(int) buffer.length, &actualLength);
18197                 foreach (dchar dc; buffer[0..actualLength]) {
18198                     if (simpleWindow.handleCharEvent)
18199                         simpleWindow.handleCharEvent(dc);
18200 		    // NotYetImplementedException
18201                     //if (simpleWindow.handleKeyEvent)
18202                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
18203                 }
18204             }
18205 
18206             // the event's 'keyCode' is hardware-dependent. I don't think people
18207             // will like it. Let's leave it to the native handler.
18208 
18209             // perform the default action.
18210 
18211 	    // so the default action is to make a bomp sound and i dont want that
18212 	    // sooooooooo yeah not gonna do that.
18213 
18214             //auto superData = objc_super(self, superclass(self));
18215             //alias extern(C) void function(objc_super*, SEL, id) T;
18216             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
18217         }
18218     }
18219 
18220     // initialize the app so that it can be interacted with the user.
18221     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
18222     private void initializeApp() {
18223         // push an autorelease pool to avoid leaking.
18224         init(alloc("NSAutoreleasePool"));
18225 
18226         // create a new NSApp instance
18227         sharedNSApplication;
18228         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
18229 
18230         // create the "Quit" menu.
18231         auto menuBar = init(alloc("NSMenu"));
18232         auto appMenuItem = init(alloc("NSMenuItem"));
18233         addItem(menuBar, appMenuItem);
18234         setMainMenu(NSApp, menuBar);
18235         release(appMenuItem);
18236         release(menuBar);
18237 
18238         auto appMenu = init(alloc("NSMenu"));
18239         auto quitTitle = createCFString("Quit");
18240         auto q = createCFString("q");
18241         auto quitItem = initWithTitle(alloc("NSMenuItem"),
18242                                       quitTitle, sel_registerName("terminate:"), q);
18243         addItem(appMenu, quitItem);
18244         setSubmenu(appMenuItem, appMenu);
18245         release(quitItem);
18246         release(appMenu);
18247         CFRelease(q);
18248         CFRelease(quitTitle);
18249 
18250         // assign a delegate for the application, allow it to quit when the last
18251         // window is closed.
18252         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
18253                                                     "SDWindowCloseDelegate", 0);
18254         class_addMethod(delegateClass,
18255                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
18256                         &returnTrue3, "c@:@");
18257         objc_registerClassPair(delegateClass);
18258 
18259         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
18260         setDelegate(NSApp, appDelegate);
18261         activateIgnoringOtherApps(NSApp, true);
18262 
18263         // create a new view that draws the graphics and respond to keyDown
18264         // events.
18265         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
18266                                                 "SDGraphicsView", (void*).sizeof);
18267         class_addIvar(viewClass, "simpledisplay_simpleWindow",
18268                       (void*).sizeof, (void*).alignof, "^v");
18269         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
18270                         &pulse, "v@:");
18271         class_addMethod(viewClass, sel_registerName("drawRect:"),
18272                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
18273         class_addMethod(viewClass, sel_registerName("isFlipped"),
18274                         &returnTrue2, "c@:");
18275         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
18276                         &returnTrue2, "c@:");
18277         class_addMethod(viewClass, sel_registerName("keyDown:"),
18278                         &keyDown, "v@:@");
18279         objc_registerClassPair(viewClass);
18280         simpleWindowIvar = class_getInstanceVariable(viewClass,
18281                                                      "simpledisplay_simpleWindow");
18282     }
18283 }
18284 
18285 version(without_opengl) {} else
18286 extern(System) nothrow @nogc {
18287 	//enum uint GL_VERSION = 0x1F02;
18288 	//const(char)* glGetString (/*GLenum*/uint);
18289 	version(X11) {
18290 	static if (!SdpyIsUsingIVGLBinds) {
18291 
18292 		enum GLX_X_RENDERABLE = 0x8012;
18293 		enum GLX_DRAWABLE_TYPE = 0x8010;
18294 		enum GLX_RENDER_TYPE = 0x8011;
18295 		enum GLX_X_VISUAL_TYPE = 0x22;
18296 		enum GLX_TRUE_COLOR = 0x8002;
18297 		enum GLX_WINDOW_BIT = 0x00000001;
18298 		enum GLX_RGBA_BIT = 0x00000001;
18299 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18300 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18301 		enum GLX_SAMPLES = 0x186a1;
18302 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18303 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18304 	}
18305 
18306 		// GLX_EXT_swap_control
18307 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18308 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18309 
18310 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18311 		extern(System) {
18312 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18313 		}
18314 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18315 
18316 		// this made public so we don't have to get it again and again
18317 		public bool glXCreateContextAttribsARB_present () {
18318 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18319 				// get it
18320 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18321 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18322 			}
18323 			return (glXCreateContextAttribsARBFn !is null);
18324 		}
18325 
18326 		// this made public so we don't have to get it again and again
18327 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18328 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18329 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18330 		}
18331 
18332 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18333 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18334 
18335 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18336 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18337 			if (_glx_swapInterval_fn is null) {
18338 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18339 				if (_glx_swapInterval_fn is null) {
18340 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18341 					return;
18342 				}
18343 				version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); }
18344 			}
18345 
18346 			if(glXSwapIntervalMESA is null) {
18347 				// it seems to require both to actually take effect on many computers
18348 				// idk why
18349 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18350 				if(glXSwapIntervalMESA is null)
18351 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18352 			}
18353 
18354 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18355 				glXSwapIntervalMESA(wait ? 1 : 0);
18356 
18357 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18358 		}
18359 	} else version(Windows) {
18360 	static if (!SdpyIsUsingIVGLBinds) {
18361 	enum GL_TRUE = 1;
18362 	enum GL_FALSE = 0;
18363 	alias int GLint;
18364 
18365 	public void* glbindGetProcAddress (const(char)* name) {
18366 		void* res = wglGetProcAddress(name);
18367 		if (res is null) {
18368 			/+
18369 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18370 			import core.sys.windows.windef, core.sys.windows.winbase;
18371 			__gshared HINSTANCE dll = null;
18372 			if (dll is null) {
18373 				dll = LoadLibraryA("opengl32.dll");
18374 				if (dll is null) return null; // <32, but idc
18375 			}
18376 			res = GetProcAddress(dll, name);
18377 			+/
18378 			res = GetProcAddress(gl.libHandle, name);
18379 		}
18380 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18381 		return res;
18382 	}
18383 	}
18384 
18385 
18386  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18387         void wglSetVSync(bool wait) {
18388 		if(wglSwapIntervalEXT is null) {
18389 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18390 			if(wglSwapIntervalEXT is null)
18391 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18392 		}
18393 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18394 			return;
18395 
18396 		wglSwapIntervalEXT(wait ? 1 : 0);
18397 	}
18398 
18399 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18400 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18401 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18402 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18403 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18404 
18405 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18406 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18407 
18408 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18409 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18410 
18411 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18412 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18413 
18414 		void wglInitOtherFunctions () {
18415 			if (wglCreateContextAttribsARB is null) {
18416 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18417 			}
18418 		}
18419 	}
18420 
18421 	static if (!SdpyIsUsingIVGLBinds) {
18422 
18423 	interface GL {
18424 		extern(System) @nogc nothrow:
18425 
18426 		void glGetIntegerv(int, void*);
18427 		void glMatrixMode(int);
18428 		void glPushMatrix();
18429 		void glLoadIdentity();
18430 		void glOrtho(double, double, double, double, double, double);
18431 		void glFrustum(double, double, double, double, double, double);
18432 
18433 		void glPopMatrix();
18434 		void glEnable(int);
18435 		void glDisable(int);
18436 		void glClear(int);
18437 		void glBegin(int);
18438 		void glVertex2f(float, float);
18439 		void glVertex3f(float, float, float);
18440 		void glEnd();
18441 		void glColor3b(byte, byte, byte);
18442 		void glColor3ub(ubyte, ubyte, ubyte);
18443 		void glColor4b(byte, byte, byte, byte);
18444 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18445 		void glColor3i(int, int, int);
18446 		void glColor3ui(uint, uint, uint);
18447 		void glColor4i(int, int, int, int);
18448 		void glColor4ui(uint, uint, uint, uint);
18449 		void glColor3f(float, float, float);
18450 		void glColor4f(float, float, float, float);
18451 		void glTranslatef(float, float, float);
18452 		void glScalef(float, float, float);
18453 		version(X11) {
18454 			void glSecondaryColor3b(byte, byte, byte);
18455 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18456 			void glSecondaryColor3i(int, int, int);
18457 			void glSecondaryColor3ui(uint, uint, uint);
18458 			void glSecondaryColor3f(float, float, float);
18459 		}
18460 
18461 		void glDrawElements(int, int, int, void*);
18462 
18463 		void glRotatef(float, float, float, float);
18464 
18465 		uint glGetError();
18466 
18467 		void glDeleteTextures(int, uint*);
18468 
18469 
18470 		void glRasterPos2i(int, int);
18471 		void glDrawPixels(int, int, uint, uint, void*);
18472 		void glClearColor(float, float, float, float);
18473 
18474 
18475 		void glPixelStorei(uint, int);
18476 
18477 		void glGenTextures(uint, uint*);
18478 		void glBindTexture(int, int);
18479 		void glTexParameteri(uint, uint, int);
18480 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18481 		void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
18482 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18483 			/*GLsizei*/int width, /*GLsizei*/int height,
18484 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
18485 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18486 
18487 		void glLineWidth(int);
18488 
18489 
18490 		void glTexCoord2f(float, float);
18491 		void glVertex2i(int, int);
18492 		void glBlendFunc (int, int);
18493 		void glDepthFunc (int);
18494 		void glViewport(int, int, int, int);
18495 
18496 		void glClearDepth(double);
18497 
18498 		void glReadBuffer(uint);
18499 		void glReadPixels(int, int, int, int, int, int, void*);
18500 
18501 		void glFlush();
18502 		void glFinish();
18503 
18504 		version(Windows) {
18505 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18506 			HGLRC wglCreateContext(HDC);
18507 			HGLRC wglCreateLayerContext(HDC, int);
18508 			BOOL wglDeleteContext(HGLRC);
18509 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18510 			HGLRC wglGetCurrentContext();
18511 			HDC wglGetCurrentDC();
18512 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18513 			PROC wglGetProcAddress(LPCSTR);
18514 			BOOL wglMakeCurrent(HDC, HGLRC);
18515 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18516 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18517 			BOOL wglShareLists(HGLRC, HGLRC);
18518 			BOOL wglSwapLayerBuffers(HDC, UINT);
18519 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18520 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18521 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18522 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18523 		}
18524 
18525 	}
18526 
18527 	interface GL3 {
18528 		extern(System) @nogc nothrow:
18529 
18530 		void glGenVertexArrays(GLsizei, GLuint*);
18531 		void glBindVertexArray(GLuint);
18532 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18533 		void glGenerateMipmap(GLenum);
18534 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18535 		void glStencilMask(GLuint);
18536 		void glStencilFunc(GLenum, GLint, GLuint);
18537 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18538 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18539 		GLuint glCreateProgram();
18540 		GLuint glCreateShader(GLenum);
18541 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18542 		void glCompileShader(GLuint);
18543 		void glGetShaderiv(GLuint, GLenum, GLint*);
18544 		void glAttachShader(GLuint, GLuint);
18545 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18546 		void glLinkProgram(GLuint);
18547 		void glGetProgramiv(GLuint, GLenum, GLint*);
18548 		void glDeleteProgram(GLuint);
18549 		void glDeleteShader(GLuint);
18550 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18551 		void glGenBuffers(GLsizei, GLuint*);
18552 
18553 		void glUniform1f(GLint location, GLfloat v0);
18554 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18555 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18556 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18557 		void glUniform1i(GLint location, GLint v0);
18558 		void glUniform2i(GLint location, GLint v0, GLint v1);
18559 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18560 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18561 		void glUniform1ui(GLint location, GLuint v0);
18562 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18563 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18564 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18565 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18566 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18567 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18568 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18569 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18570 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18571 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18572 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18573 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18574 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18575 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18576 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18577 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18578 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18579 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18580 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18581 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18582 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18583 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18584 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18585 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18586 
18587 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18588 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18589 		void glDrawArrays(GLenum, GLint, GLsizei);
18590 		void glStencilOp(GLenum, GLenum, GLenum);
18591 		void glUseProgram(GLuint);
18592 		void glCullFace(GLenum);
18593 		void glFrontFace(GLenum);
18594 		void glActiveTexture(GLenum);
18595 		void glBindBuffer(GLenum, GLuint);
18596 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18597 		void glEnableVertexAttribArray(GLuint);
18598 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18599 		void glUniform1i(GLint, GLint);
18600 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18601 		void glDisableVertexAttribArray(GLuint);
18602 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18603 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18604 		void glLogicOp (GLenum opcode);
18605 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18606 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18607 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18608 		GLenum glCheckFramebufferStatus (GLenum target);
18609 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18610 	}
18611 
18612 	interface GL4 {
18613 		extern(System) @nogc nothrow:
18614 
18615 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18616 			/*GLsizei*/int width, /*GLsizei*/int height,
18617 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
18618 	}
18619 
18620 	interface GLU {
18621 		extern(System) @nogc nothrow:
18622 
18623 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18624 		void gluPerspective(double, double, double, double);
18625 
18626 		char* gluErrorString(uint);
18627 	}
18628 
18629 
18630 	enum GL_RED = 0x1903;
18631 	enum GL_ALPHA = 0x1906;
18632 
18633 	enum uint GL_FRONT = 0x0404;
18634 
18635 	enum uint GL_BLEND = 0x0be2;
18636 	enum uint GL_LEQUAL = 0x0203;
18637 
18638 
18639 	enum uint GL_RGB = 0x1907;
18640 	enum uint GL_BGRA = 0x80e1;
18641 	enum uint GL_RGBA = 0x1908;
18642 	enum uint GL_TEXTURE_2D =   0x0DE1;
18643 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18644 	enum uint GL_NEAREST = 0x2600;
18645 	enum uint GL_LINEAR = 0x2601;
18646 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18647 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18648 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18649 	enum uint GL_REPEAT = 0x2901;
18650 	enum uint GL_CLAMP = 0x2900;
18651 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18652 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18653 	enum uint GL_DECAL = 0x2101;
18654 	enum uint GL_MODULATE = 0x2100;
18655 	enum uint GL_TEXTURE_ENV = 0x2300;
18656 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18657 	enum uint GL_REPLACE = 0x1E01;
18658 	enum uint GL_LIGHTING = 0x0B50;
18659 	enum uint GL_DITHER = 0x0BD0;
18660 
18661 	enum uint GL_NO_ERROR = 0;
18662 
18663 
18664 
18665 	enum int GL_VIEWPORT = 0x0BA2;
18666 	enum int GL_MODELVIEW = 0x1700;
18667 	enum int GL_TEXTURE = 0x1702;
18668 	enum int GL_PROJECTION = 0x1701;
18669 	enum int GL_DEPTH_TEST = 0x0B71;
18670 
18671 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18672 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18673 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18674 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18675 
18676 	enum int GL_POINTS = 0x0000;
18677 	enum int GL_LINES =  0x0001;
18678 	enum int GL_LINE_LOOP = 0x0002;
18679 	enum int GL_LINE_STRIP = 0x0003;
18680 	enum int GL_TRIANGLES = 0x0004;
18681 	enum int GL_TRIANGLE_STRIP = 5;
18682 	enum int GL_TRIANGLE_FAN = 6;
18683 	enum int GL_QUADS = 7;
18684 	enum int GL_QUAD_STRIP = 8;
18685 	enum int GL_POLYGON = 9;
18686 
18687 	alias GLvoid = void;
18688 	alias GLboolean = ubyte;
18689 	alias GLuint = uint;
18690 	alias GLenum = uint;
18691 	alias GLchar = char;
18692 	alias GLsizei = int;
18693 	alias GLfloat = float;
18694 	alias GLintptr = size_t;
18695 	alias GLsizeiptr = ptrdiff_t;
18696 
18697 
18698 	enum uint GL_INVALID_ENUM = 0x0500;
18699 
18700 	enum uint GL_ZERO = 0;
18701 	enum uint GL_ONE = 1;
18702 
18703 	enum uint GL_BYTE = 0x1400;
18704 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18705 	enum uint GL_SHORT = 0x1402;
18706 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18707 	enum uint GL_INT = 0x1404;
18708 	enum uint GL_UNSIGNED_INT = 0x1405;
18709 	enum uint GL_FLOAT = 0x1406;
18710 	enum uint GL_2_BYTES = 0x1407;
18711 	enum uint GL_3_BYTES = 0x1408;
18712 	enum uint GL_4_BYTES = 0x1409;
18713 	enum uint GL_DOUBLE = 0x140A;
18714 
18715 	enum uint GL_STREAM_DRAW = 0x88E0;
18716 
18717 	enum uint GL_CCW = 0x0901;
18718 
18719 	enum uint GL_STENCIL_TEST = 0x0B90;
18720 	enum uint GL_SCISSOR_TEST = 0x0C11;
18721 
18722 	enum uint GL_EQUAL = 0x0202;
18723 	enum uint GL_NOTEQUAL = 0x0205;
18724 
18725 	enum uint GL_ALWAYS = 0x0207;
18726 	enum uint GL_KEEP = 0x1E00;
18727 
18728 	enum uint GL_INCR = 0x1E02;
18729 
18730 	enum uint GL_INCR_WRAP = 0x8507;
18731 	enum uint GL_DECR_WRAP = 0x8508;
18732 
18733 	enum uint GL_CULL_FACE = 0x0B44;
18734 	enum uint GL_BACK = 0x0405;
18735 
18736 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18737 	enum uint GL_VERTEX_SHADER = 0x8B31;
18738 
18739 	enum uint GL_COMPILE_STATUS = 0x8B81;
18740 	enum uint GL_LINK_STATUS = 0x8B82;
18741 
18742 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18743 
18744 	enum uint GL_STATIC_DRAW = 0x88E4;
18745 
18746 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18747 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18748 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18749 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18750 
18751 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18752 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18753 
18754 	enum uint GL_TEXTURE0 = 0x84C0U;
18755 	enum uint GL_TEXTURE1 = 0x84C1U;
18756 
18757 	enum uint GL_ARRAY_BUFFER = 0x8892;
18758 
18759 	enum uint GL_SRC_COLOR = 0x0300;
18760 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18761 	enum uint GL_SRC_ALPHA = 0x0302;
18762 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18763 	enum uint GL_DST_ALPHA = 0x0304;
18764 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18765 	enum uint GL_DST_COLOR = 0x0306;
18766 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18767 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18768 
18769 	enum uint GL_INVERT = 0x150AU;
18770 
18771 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18772 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18773 
18774 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18775 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18776 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18777 
18778 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18779 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18780 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18781 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18782 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18783 
18784 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18785 	enum uint GL_CLEAR = 0x1500U;
18786 	enum uint GL_COPY = 0x1503U;
18787 	enum uint GL_XOR = 0x1506U;
18788 
18789 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18790 
18791 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18792 
18793 	}
18794 }
18795 
18796 /++
18797 	History:
18798 		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.
18799 +/
18800 __gshared bool gluSuccessfullyLoaded = true;
18801 
18802 version(without_opengl) {} else {
18803 static if(!SdpyIsUsingIVGLBinds) {
18804 	version(Windows) {
18805 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18806 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18807 	} else {
18808 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18809 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18810 	}
18811 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18812 
18813 
18814 	shared static this() {
18815 		gl.loadDynamicLibrary();
18816 
18817 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18818 		// unless those functions are actually used
18819 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18820 		glu.loadDynamicLibrary();
18821 	}
18822 }
18823 }
18824 
18825 /++
18826 	Convenience method for converting D arrays to opengl buffer data
18827 
18828 	I would LOVE to overload it with the original glBufferData, but D won't
18829 	let me since glBufferData is a function pointer :(
18830 
18831 	Added: August 25, 2020 (version 8.5)
18832 +/
18833 version(without_opengl) {} else
18834 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18835 	glBufferData(target, data.length, data.ptr, usage);
18836 }
18837 
18838 /+
18839 /++
18840 	A matrix for simple uses that easily integrates with [OpenGlShader].
18841 
18842 	Might not be useful to you since it only as some simple functions and
18843 	probably isn't that fast.
18844 
18845 	Note it uses an inline static array for its storage, so copying it
18846 	may be expensive.
18847 +/
18848 struct BasicMatrix(int columns, int rows, T = float) {
18849 	import core.stdc.math;
18850 
18851 	T[columns * rows] data = 0.0;
18852 
18853 	/++
18854 		Basic operations that operate *in place*.
18855 	+/
18856 	void translate() {
18857 
18858 	}
18859 
18860 	/// ditto
18861 	void scale() {
18862 
18863 	}
18864 
18865 	/// ditto
18866 	void rotate() {
18867 
18868 	}
18869 
18870 	/++
18871 
18872 	+/
18873 	static if(columns == rows)
18874 	static BasicMatrix identity() {
18875 		BasicMatrix m;
18876 		foreach(i; 0 .. columns)
18877 			data[0 + i + i * columns] = 1.0;
18878 		return m;
18879 	}
18880 
18881 	static BasicMatrix ortho() {
18882 		return BasicMatrix.init;
18883 	}
18884 }
18885 +/
18886 
18887 /++
18888 	Convenience class for using opengl shaders.
18889 
18890 	Ensure that you've loaded opengl 3+ and set your active
18891 	context before trying to use this.
18892 
18893 	Added: August 25, 2020 (version 8.5)
18894 +/
18895 version(without_opengl) {} else
18896 final class OpenGlShader {
18897 	private int shaderProgram_;
18898 	private @property void shaderProgram(int a) {
18899 		shaderProgram_ = a;
18900 	}
18901 	/// Get the program ID for use in OpenGL functions.
18902 	public @property int shaderProgram() {
18903 		return shaderProgram_;
18904 	}
18905 
18906 	/++
18907 
18908 	+/
18909 	static struct Source {
18910 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
18911 		string code; ///
18912 	}
18913 
18914 	/++
18915 		Helper method to just compile some shader code and check for errors
18916 		while you do glCreateShader, etc. on the outside yourself.
18917 
18918 		This just does `glShaderSource` and `glCompileShader` for the given code.
18919 
18920 		If you the OpenGlShader class constructor, you never need to call this yourself.
18921 	+/
18922 	static void compile(int sid, Source code) {
18923 		const(char)*[1] buffer;
18924 		int[1] lengthBuffer;
18925 
18926 		buffer[0] = code.code.ptr;
18927 		lengthBuffer[0] = cast(int) code.code.length;
18928 
18929 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
18930 		glCompileShader(sid);
18931 
18932 		int success;
18933 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
18934 		if(!success) {
18935 			char[512] info;
18936 			int len;
18937 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
18938 
18939 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
18940 		}
18941 	}
18942 
18943 	/++
18944 		Calls `glLinkProgram` and throws if error a occurs.
18945 
18946 		If you the OpenGlShader class constructor, you never need to call this yourself.
18947 	+/
18948 	static void link(int shaderProgram) {
18949 		glLinkProgram(shaderProgram);
18950 		int success;
18951 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
18952 		if(!success) {
18953 			char[512] info;
18954 			int len;
18955 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
18956 
18957 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
18958 		}
18959 	}
18960 
18961 	/++
18962 		Constructs the shader object by calling `glCreateProgram`, then
18963 		compiling each given [Source], and finally, linking them together.
18964 
18965 		Throws: on compile or link failure.
18966 	+/
18967 	this(Source[] codes...) {
18968 		shaderProgram = glCreateProgram();
18969 
18970 		int[16] shadersBufferStack;
18971 
18972 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
18973 			shadersBufferStack[0 .. codes.length] :
18974 			new int[](codes.length);
18975 
18976 		foreach(idx, code; codes) {
18977 			shadersBuffer[idx] = glCreateShader(code.type);
18978 
18979 			compile(shadersBuffer[idx], code);
18980 
18981 			glAttachShader(shaderProgram, shadersBuffer[idx]);
18982 		}
18983 
18984 		link(shaderProgram);
18985 
18986 		foreach(s; shadersBuffer)
18987 			glDeleteShader(s);
18988 	}
18989 
18990 	/// Calls `glUseProgram(this.shaderProgram)`
18991 	void use() {
18992 		glUseProgram(this.shaderProgram);
18993 	}
18994 
18995 	/// Deletes the program.
18996 	void delete_() {
18997 		glDeleteProgram(shaderProgram);
18998 		shaderProgram = 0;
18999 	}
19000 
19001 	/++
19002 		[OpenGlShader.uniforms].name gives you one of these.
19003 
19004 		You can get the id out of it or just assign
19005 	+/
19006 	static struct Uniform {
19007 		/// the id passed to glUniform*
19008 		int id;
19009 
19010 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19011 		void opAssign(float x, float y, float z, float w) {
19012 			if(id != -1)
19013 			glUniform4f(id, x, y, z, w);
19014 		}
19015 
19016 		void opAssign(float x) {
19017 			if(id != -1)
19018 			glUniform1f(id, x);
19019 		}
19020 
19021 		void opAssign(float x, float y) {
19022 			if(id != -1)
19023 			glUniform2f(id, x, y);
19024 		}
19025 
19026 		void opAssign(T)(T t) {
19027 			t.glUniform(id);
19028 		}
19029 	}
19030 
19031 	static struct UniformsHelper {
19032 		OpenGlShader _shader;
19033 
19034 		@property Uniform opDispatch(string name)() {
19035 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19036 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19037 			//if(i == -1)
19038 				//throw new Exception("Could not find uniform " ~ name);
19039 			return Uniform(i);
19040 		}
19041 
19042 		@property void opDispatch(string name, T)(T t) {
19043 			Uniform f = this.opDispatch!name;
19044 			t.glUniform(f);
19045 		}
19046 	}
19047 
19048 	/++
19049 		Gives access to the uniforms through dot access.
19050 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19051 	+/
19052 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19053 }
19054 
19055 version(without_opengl) {} else {
19056 /++
19057 	A static container of experimental types and value constructors for opengl 3+ shaders.
19058 
19059 
19060 	You can declare variables like:
19061 
19062 	```
19063 	OGL.vec3f something;
19064 	```
19065 
19066 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19067 
19068 	```
19069 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19070 	```
19071 
19072 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19073 
19074 
19075 	History:
19076 		Added December 7, 2021. Not yet stable.
19077 +/
19078 final class OGL {
19079 	static:
19080 
19081 	private template typeFromSpecifier(string specifier) {
19082 		static if(specifier == "f")
19083 			alias typeFromSpecifier = GLfloat;
19084 		else static if(specifier == "i")
19085 			alias typeFromSpecifier = GLint;
19086 		else static if(specifier == "ui")
19087 			alias typeFromSpecifier = GLuint;
19088 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19089 	}
19090 
19091 	private template CommonType(T...) {
19092 		static if(T.length == 1)
19093 			alias CommonType = T[0];
19094 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19095 			alias CommonType = CommonType!(C, T[2 .. $]);
19096 	}
19097 
19098 	private template typesToSpecifier(T...) {
19099 		static if(is(CommonType!T == float))
19100 			enum typesToSpecifier = "f";
19101 		else static if(is(CommonType!T == int))
19102 			enum typesToSpecifier = "i";
19103 		else static if(is(CommonType!T == uint))
19104 			enum typesToSpecifier = "ui";
19105 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19106 	}
19107 
19108 	private template genNames(size_t dim, size_t dim2 = 0) {
19109 		string helper() {
19110 			string s;
19111 			if(dim2) {
19112 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19113 			} else {
19114 				if(dim > 0) s ~= "type x = 0;";
19115 				if(dim > 1) s ~= "type y = 0;";
19116 				if(dim > 2) s ~= "type z = 0;";
19117 				if(dim > 3) s ~= "type w = 0;";
19118 			}
19119 			return s;
19120 		}
19121 
19122 		enum genNames = helper();
19123 	}
19124 
19125 	// there's vec, arrays of vec, mat, and arrays of mat
19126 	template opDispatch(string name)
19127 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19128 	{
19129 		static if(name[4] == 'x') {
19130 			enum dimX = cast(int) (name[3] - '0');
19131 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19132 
19133 			enum dimY = cast(int) (name[5] - '0');
19134 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19135 
19136 			enum isArray = name[$ - 1] == 'v';
19137 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19138 			alias type = typeFromSpecifier!typeSpecifier;
19139 		} else {
19140 			enum dim = cast(int) (name[3] - '0');
19141 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19142 			enum isArray = name[$ - 1] == 'v';
19143 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19144 			alias type = typeFromSpecifier!typeSpecifier;
19145 		}
19146 
19147 		align(1)
19148 		struct opDispatch {
19149 			align(1):
19150 			static if(name[4] == 'x')
19151 				mixin(genNames!(dimX, dimY));
19152 			else
19153 				mixin(genNames!dim);
19154 
19155 			private void glUniform(OpenGlShader.Uniform assignTo) {
19156 				glUniform(assignTo.id);
19157 			}
19158 			private void glUniform(int assignTo) {
19159 				static if(name[4] == 'x') {
19160 					// FIXME
19161 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19162 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19163 				} else
19164 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19165 			}
19166 		}
19167 	}
19168 
19169 	auto vec(T...)(T members) {
19170 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19171 	}
19172 }
19173 }
19174 
19175 version(linux) {
19176 	version(with_eventloop) {} else {
19177 		private int epollFd = -1;
19178 		void prepareEventLoop() {
19179 			if(epollFd != -1)
19180 				return; // already initialized, no need to do it again
19181 			import ep = core.sys.linux.epoll;
19182 
19183 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19184 			if(epollFd == -1)
19185 				throw new Exception("epoll create failure");
19186 		}
19187 	}
19188 } else version(Posix) {
19189 	void prepareEventLoop() {}
19190 }
19191 
19192 version(X11) {
19193 	import core.stdc.locale : LC_ALL; // rdmd fix
19194 	__gshared bool sdx_isUTF8Locale;
19195 
19196 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19197 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19198 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19199 	// anal magic is here. I (Ketmar) hope you like it.
19200 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19201 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19202 	// later.
19203 
19204 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19205 	shared static this () {
19206 		if(!librariesSuccessfullyLoaded)
19207 			return;
19208 
19209 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19210 
19211 		// this doesn't hurt; it may add some locking, but the speed is still
19212 		// allows doing 60 FPS videogames; also, ignore the result, as most
19213 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19214 		// never seen this failing).
19215 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19216 
19217 		setlocale(LC_ALL, "");
19218 		// check if out locale is UTF-8
19219 		auto lct = setlocale(LC_CTYPE, null);
19220 		if (lct is null) {
19221 			sdx_isUTF8Locale = false;
19222 		} else {
19223 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19224 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19225 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19226 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19227 				{
19228 					sdx_isUTF8Locale = true;
19229 					break;
19230 				}
19231 			}
19232 		}
19233 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19234 	}
19235 }
19236 
19237 class ExperimentalTextComponent2 {
19238 	/+
19239 		Stage 1: get it working monospace
19240 		Stage 2: use proportional font
19241 		Stage 3: allow changes in inline style
19242 		Stage 4: allow new fonts and sizes in the middle
19243 		Stage 5: optimize gap buffer
19244 		Stage 6: optimize layout
19245 		Stage 7: word wrap
19246 		Stage 8: justification
19247 		Stage 9: editing, selection, etc.
19248 
19249 			Operations:
19250 				insert text
19251 				overstrike text
19252 				select
19253 				cut
19254 				modify
19255 	+/
19256 
19257 	/++
19258 		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.
19259 	+/
19260 	this(SimpleWindow window) {
19261 		this.window = window;
19262 	}
19263 
19264 	private SimpleWindow window;
19265 
19266 
19267 	/++
19268 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19269 		representing the internal parts. The first pass is focused on the x parameter, then the
19270 		renderer is responsible for going back to the parts in the current line and calling
19271 		adjustDownForAscent to change the y params.
19272 	+/
19273 	static interface ComponentRenderHelper {
19274 
19275 		/+
19276 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19277 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19278 			to move (adjust y to make room for new line) until you get back to the same position,
19279 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19280 
19281 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19282 			once you reach something that is unchanged, you can stop.
19283 		+/
19284 
19285 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19286 
19287 		int ascent() const;
19288 		int descent() const;
19289 
19290 		int advance() const;
19291 
19292 		bool endsWithExplititLineBreak() const;
19293 	}
19294 
19295 	static interface RenderResult {
19296 		/++
19297 			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.
19298 		+/
19299 		void popFront();
19300 		@property bool empty() const;
19301 		@property ComponentRenderHelper front() const;
19302 
19303 		void repositionForNextLine(Point baseline, int availableWidth);
19304 	}
19305 
19306 	static interface ComponentInFlow {
19307 		void draw(ScreenPainter painter);
19308 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19309 
19310 		bool startsWithExplicitLineBreak() const;
19311 	}
19312 
19313 	static class TextFlowComponent : ComponentInFlow {
19314 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19315 
19316 		Color foreground;
19317 		Color background;
19318 
19319 		OperatingSystemFont font; // should NEVER be null
19320 
19321 		ubyte attributes; // underline, strike through, display on new block
19322 
19323 		version(Windows)
19324 			const(wchar)[] content;
19325 		else
19326 			const(char)[] content; // this should NEVER have a newline, except at the end
19327 
19328 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19329 
19330 		// could prolly put some spacing around it too like margin / padding
19331 
19332 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19333 			in { assert(font !is null);
19334 			     assert(!font.isNull); }
19335 			do
19336 		{
19337 			this.foreground = f;
19338 			this.background = b;
19339 			this.font = font;
19340 
19341 			this.attributes = attr;
19342 			version(Windows) {
19343 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19344 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19345 				auto buffer = new wchar[](sz);
19346 				this.content = makeWindowsString(c, buffer, conversionFlags);
19347 			} else {
19348 				this.content = c.dup;
19349 			}
19350 		}
19351 
19352 		void draw(ScreenPainter painter) {
19353 			painter.setFont(this.font);
19354 			painter.outlineColor = this.foreground;
19355 			painter.fillColor = Color.transparent;
19356 			foreach(rendered; this.rendered) {
19357 				// the component works in term of baseline,
19358 				// but the painter works in term of upper left bounding box
19359 				// so need to translate that
19360 
19361 				if(this.background.a) {
19362 					painter.fillColor = this.background;
19363 					painter.outlineColor = this.background;
19364 
19365 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19366 
19367 					painter.outlineColor = this.foreground;
19368 					painter.fillColor = Color.transparent;
19369 				}
19370 
19371 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19372 
19373 				// FIXME: strike through, underline, highlight selection, etc.
19374 			}
19375 		}
19376 	}
19377 
19378 	// I could split the parts into words on render
19379 	// for easier word-wrap, each one being an unbreakable "inline-block"
19380 	private TextFlowComponent[] parts;
19381 	private int needsRerenderFrom;
19382 
19383 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19384 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19385 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19386 	}
19387 
19388 	static struct RenderedComponent {
19389 		int startX;
19390 		int startY;
19391 		short width;
19392 		// 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!
19393 		// for individual chars in here you've gotta process on demand
19394 		version(Windows)
19395 			const(wchar)[] slice;
19396 		else
19397 			const(char)[] slice;
19398 	}
19399 
19400 
19401 	void rerender(Rectangle boundingBox) {
19402 		Point baseline = boundingBox.upperLeft;
19403 
19404 		this.boundingBox.left = boundingBox.left;
19405 		this.boundingBox.top = boundingBox.top;
19406 
19407 		auto remainingParts = parts;
19408 
19409 		int largestX;
19410 
19411 
19412 		foreach(part; parts)
19413 			part.font.prepareContext(window);
19414 		scope(exit)
19415 		foreach(part; parts)
19416 			part.font.releaseContext();
19417 
19418 		calculateNextLine:
19419 
19420 		int nextLineHeight = 0;
19421 		int nextBiggestDescent = 0;
19422 
19423 		foreach(part; remainingParts) {
19424 			auto height = part.font.ascent;
19425 			if(height > nextLineHeight)
19426 				nextLineHeight = height;
19427 			if(part.font.descent > nextBiggestDescent)
19428 				nextBiggestDescent = part.font.descent;
19429 			if(part.content.length && part.content[$-1] == '\n')
19430 				break;
19431 		}
19432 
19433 		baseline.y += nextLineHeight;
19434 		auto lineStart = baseline;
19435 
19436 		while(remainingParts.length) {
19437 			remainingParts[0].rendered = null;
19438 
19439 			bool eol;
19440 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19441 				eol = true;
19442 
19443 			// FIXME: word wrap
19444 			auto font = remainingParts[0].font;
19445 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19446 			auto width = font.stringWidth(slice, window);
19447 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19448 
19449 			remainingParts = remainingParts[1 .. $];
19450 			baseline.x += width;
19451 
19452 			if(eol) {
19453 				baseline.y += nextBiggestDescent;
19454 				if(baseline.x > largestX)
19455 					largestX = baseline.x;
19456 				baseline.x = lineStart.x;
19457 				goto calculateNextLine;
19458 			}
19459 		}
19460 
19461 		if(baseline.x > largestX)
19462 			largestX = baseline.x;
19463 
19464 		this.boundingBox.right = largestX;
19465 		this.boundingBox.bottom = baseline.y;
19466 	}
19467 
19468 	// you must call rerender first!
19469 	void draw(ScreenPainter painter) {
19470 		foreach(part; parts) {
19471 			part.draw(painter);
19472 		}
19473 	}
19474 
19475 	struct IdentifyResult {
19476 		TextFlowComponent part;
19477 		int charIndexInPart;
19478 		int totalCharIndex = -1; // if this is -1, it just means the end
19479 
19480 		Rectangle boundingBox;
19481 	}
19482 
19483 	IdentifyResult identify(Point pt, bool exact = false) {
19484 		if(parts.length == 0)
19485 			return IdentifyResult(null, 0);
19486 
19487 		if(pt.y < boundingBox.top) {
19488 			if(exact)
19489 				return IdentifyResult(null, 1);
19490 			return IdentifyResult(parts[0], 0);
19491 		}
19492 		if(pt.y > boundingBox.bottom) {
19493 			if(exact)
19494 				return IdentifyResult(null, 2);
19495 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19496 		}
19497 
19498 		int tci = 0;
19499 
19500 		// I should probably like binary search this or something...
19501 		foreach(ref part; parts) {
19502 			foreach(rendered; part.rendered) {
19503 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19504 				if(rect.contains(pt)) {
19505 					auto x = pt.x - rendered.startX;
19506 					auto estimatedIdx = x / part.font.averageWidth;
19507 
19508 					if(estimatedIdx < 0)
19509 						estimatedIdx = 0;
19510 
19511 					if(estimatedIdx > rendered.slice.length)
19512 						estimatedIdx = cast(int) rendered.slice.length;
19513 
19514 					int idx;
19515 					int x1, x2;
19516 					if(part.font.isMonospace) {
19517 						auto w = part.font.averageWidth;
19518 						if(!exact && x > (estimatedIdx + 1) * w)
19519 							return IdentifyResult(null, 4);
19520 						idx = estimatedIdx;
19521 						x1 = idx * w;
19522 						x2 = (idx + 1) * w;
19523 					} else {
19524 						idx = estimatedIdx;
19525 
19526 						part.font.prepareContext(window);
19527 						scope(exit) part.font.releaseContext();
19528 
19529 						// int iterations;
19530 
19531 						while(true) {
19532 							// iterations++;
19533 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19534 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19535 
19536 							x1 += rendered.startX;
19537 							x2 += rendered.startX;
19538 
19539 							if(pt.x < x1) {
19540 								if(idx == 0) {
19541 									if(exact)
19542 										return IdentifyResult(null, 6);
19543 									else
19544 										break;
19545 								}
19546 								idx--;
19547 							} else if(pt.x > x2) {
19548 								idx++;
19549 								if(idx > rendered.slice.length) {
19550 									if(exact)
19551 										return IdentifyResult(null, 5);
19552 									else
19553 										break;
19554 								}
19555 							} else if(pt.x >= x1 && pt.x <= x2) {
19556 								if(idx)
19557 									idx--; // point it at the original index
19558 								break; // we fit
19559 							}
19560 						}
19561 
19562 						// import std.stdio; writeln(iterations)
19563 					}
19564 
19565 
19566 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19567 				}
19568 			}
19569 			tci += cast(int) part.content.length; // FIXME: utf-8?
19570 		}
19571 		return IdentifyResult(null, 3);
19572 	}
19573 
19574 	Rectangle boundingBox; // only set after [rerender]
19575 
19576 	// text will be positioned around the exclusion zone
19577 	static struct ExclusionZone {
19578 
19579 	}
19580 
19581 	ExclusionZone[] exclusionZones;
19582 }
19583 
19584 
19585 // Don't use this yet. When I'm happy with it, I will move it to the
19586 // regular module namespace.
19587 mixin template ExperimentalTextComponent() {
19588 
19589 static:
19590 
19591 	alias Rectangle = arsd.color.Rectangle;
19592 
19593 	struct ForegroundColor {
19594 		Color color;
19595 		alias color this;
19596 
19597 		this(Color c) {
19598 			color = c;
19599 		}
19600 
19601 		this(int r, int g, int b, int a = 255) {
19602 			color = Color(r, g, b, a);
19603 		}
19604 
19605 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19606 			return ForegroundColor(mixin("Color." ~ s));
19607 		}
19608 	}
19609 
19610 	struct BackgroundColor {
19611 		Color color;
19612 		alias color this;
19613 
19614 		this(Color c) {
19615 			color = c;
19616 		}
19617 
19618 		this(int r, int g, int b, int a = 255) {
19619 			color = Color(r, g, b, a);
19620 		}
19621 
19622 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19623 			return BackgroundColor(mixin("Color." ~ s));
19624 		}
19625 	}
19626 
19627 	static class InlineElement {
19628 		string text;
19629 
19630 		BlockElement containingBlock;
19631 
19632 		Color color = Color.black;
19633 		Color backgroundColor = Color.transparent;
19634 		ushort styles;
19635 
19636 		string font;
19637 		int fontSize;
19638 
19639 		int lineHeight;
19640 
19641 		void* identifier;
19642 
19643 		Rectangle boundingBox;
19644 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19645 
19646 		bool isMergeCompatible(InlineElement other) {
19647 			return
19648 				containingBlock is other.containingBlock &&
19649 				color == other.color &&
19650 				backgroundColor == other.backgroundColor &&
19651 				styles == other.styles &&
19652 				font == other.font &&
19653 				fontSize == other.fontSize &&
19654 				lineHeight == other.lineHeight &&
19655 				true;
19656 		}
19657 
19658 		int xOfIndex(size_t index) {
19659 			if(index < letterXs.length)
19660 				return letterXs[index];
19661 			else
19662 				return boundingBox.right;
19663 		}
19664 
19665 		InlineElement clone() {
19666 			auto ie = new InlineElement();
19667 			ie.tupleof = this.tupleof;
19668 			return ie;
19669 		}
19670 
19671 		InlineElement getPreviousInlineElement() {
19672 			InlineElement prev = null;
19673 			foreach(ie; this.containingBlock.parts) {
19674 				if(ie is this)
19675 					break;
19676 				prev = ie;
19677 			}
19678 			if(prev is null) {
19679 				BlockElement pb;
19680 				BlockElement cb = this.containingBlock;
19681 				moar:
19682 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19683 					if(ie is cb)
19684 						break;
19685 					pb = ie;
19686 				}
19687 				if(pb is null)
19688 					return null;
19689 				if(pb.parts.length == 0) {
19690 					cb = pb;
19691 					goto moar;
19692 				}
19693 
19694 				prev = pb.parts[$-1];
19695 
19696 			}
19697 			return prev;
19698 		}
19699 
19700 		InlineElement getNextInlineElement() {
19701 			InlineElement next = null;
19702 			foreach(idx, ie; this.containingBlock.parts) {
19703 				if(ie is this) {
19704 					if(idx + 1 < this.containingBlock.parts.length)
19705 						next = this.containingBlock.parts[idx + 1];
19706 					break;
19707 				}
19708 			}
19709 			if(next is null) {
19710 				BlockElement n;
19711 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19712 					if(ie is this.containingBlock) {
19713 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19714 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19715 						break;
19716 					}
19717 				}
19718 				if(n is null)
19719 					return null;
19720 
19721 				if(n.parts.length)
19722 					next = n.parts[0];
19723 				else {} // FIXME
19724 
19725 			}
19726 			return next;
19727 		}
19728 
19729 	}
19730 
19731 	// Block elements are used entirely for positioning inline elements,
19732 	// which are the things that are actually drawn.
19733 	class BlockElement {
19734 		InlineElement[] parts;
19735 		uint alignment;
19736 
19737 		int whiteSpace; // pre, pre-wrap, wrap
19738 
19739 		TextLayout containingLayout;
19740 
19741 		// inputs
19742 		Point where;
19743 		Size minimumSize;
19744 		Size maximumSize;
19745 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19746 		void* identifier;
19747 
19748 		Rectangle margin;
19749 		Rectangle padding;
19750 
19751 		// outputs
19752 		Rectangle[] boundingBoxes;
19753 	}
19754 
19755 	struct TextIdentifyResult {
19756 		InlineElement element;
19757 		int offset;
19758 
19759 		private TextIdentifyResult fixupNewline() {
19760 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19761 				offset--;
19762 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19763 				offset--;
19764 			}
19765 			return this;
19766 		}
19767 	}
19768 
19769 	class TextLayout {
19770 		BlockElement[] blocks;
19771 		Rectangle boundingBox_;
19772 		Rectangle boundingBox() { return boundingBox_; }
19773 		void boundingBox(Rectangle r) {
19774 			if(r != boundingBox_) {
19775 				boundingBox_ = r;
19776 				layoutInvalidated = true;
19777 			}
19778 		}
19779 
19780 		Rectangle contentBoundingBox() {
19781 			Rectangle r;
19782 			foreach(block; blocks)
19783 			foreach(ie; block.parts) {
19784 				if(ie.boundingBox.right > r.right)
19785 					r.right = ie.boundingBox.right;
19786 				if(ie.boundingBox.bottom > r.bottom)
19787 					r.bottom = ie.boundingBox.bottom;
19788 			}
19789 			return r;
19790 		}
19791 
19792 		BlockElement[] getBlocks() {
19793 			return blocks;
19794 		}
19795 
19796 		InlineElement[] getTexts() {
19797 			InlineElement[] elements;
19798 			foreach(block; blocks)
19799 				elements ~= block.parts;
19800 			return elements;
19801 		}
19802 
19803 		string getPlainText() {
19804 			string text;
19805 			foreach(block; blocks)
19806 				foreach(part; block.parts)
19807 					text ~= part.text;
19808 			return text;
19809 		}
19810 
19811 		string getHtml() {
19812 			return null; // FIXME
19813 		}
19814 
19815 		this(Rectangle boundingBox) {
19816 			this.boundingBox = boundingBox;
19817 		}
19818 
19819 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19820 			auto be = new BlockElement();
19821 			be.containingLayout = this;
19822 			if(after is null)
19823 				blocks ~= be;
19824 			else {
19825 				foreach(idx, b; blocks) {
19826 					if(b is after.containingBlock) {
19827 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19828 						break;
19829 					}
19830 				}
19831 			}
19832 			return be;
19833 		}
19834 
19835 		void clear() {
19836 			blocks = null;
19837 			selectionStart = selectionEnd = caret = Caret.init;
19838 		}
19839 
19840 		void addText(Args...)(Args args) {
19841 			if(blocks.length == 0)
19842 				addBlock();
19843 
19844 			InlineElement ie = new InlineElement();
19845 			foreach(idx, arg; args) {
19846 				static if(is(typeof(arg) == ForegroundColor))
19847 					ie.color = arg;
19848 				else static if(is(typeof(arg) == TextFormat)) {
19849 					if(arg & 0x8000) // ~TextFormat.something turns it off
19850 						ie.styles &= arg;
19851 					else
19852 						ie.styles |= arg;
19853 				} else static if(is(typeof(arg) == string)) {
19854 					static if(idx == 0 && args.length > 1)
19855 						static assert(0, "Put styles before the string.");
19856 					size_t lastLineIndex;
19857 					foreach(cidx, char a; arg) {
19858 						if(a == '\n') {
19859 							ie.text = arg[lastLineIndex .. cidx + 1];
19860 							lastLineIndex = cidx + 1;
19861 							ie.containingBlock = blocks[$-1];
19862 							blocks[$-1].parts ~= ie.clone;
19863 							ie.text = null;
19864 						} else {
19865 
19866 						}
19867 					}
19868 
19869 					ie.text = arg[lastLineIndex .. $];
19870 					ie.containingBlock = blocks[$-1];
19871 					blocks[$-1].parts ~= ie.clone;
19872 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
19873 				}
19874 			}
19875 
19876 			invalidateLayout();
19877 		}
19878 
19879 		void tryMerge(InlineElement into, InlineElement what) {
19880 			if(!into.isMergeCompatible(what)) {
19881 				return; // cannot merge, different configs
19882 			}
19883 
19884 			// cool, can merge, bring text together...
19885 			into.text ~= what.text;
19886 
19887 			// and remove what
19888 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
19889 				if(what.containingBlock.parts[a] is what) {
19890 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
19891 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
19892 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
19893 
19894 				}
19895 			}
19896 
19897 			// FIXME: ensure no other carets have a reference to it
19898 		}
19899 
19900 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
19901 		TextIdentifyResult identify(int x, int y, bool exact = false) {
19902 			TextIdentifyResult inexactMatch;
19903 			foreach(block; blocks) {
19904 				foreach(part; block.parts) {
19905 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
19906 
19907 						// FIXME binary search
19908 						int tidx;
19909 						int lastX;
19910 						foreach_reverse(idxo, lx; part.letterXs) {
19911 							int idx = cast(int) idxo;
19912 							if(lx <= x) {
19913 								if(lastX && lastX - x < x - lx)
19914 									tidx = idx + 1;
19915 								else
19916 									tidx = idx;
19917 								break;
19918 							}
19919 							lastX = lx;
19920 						}
19921 
19922 						return TextIdentifyResult(part, tidx).fixupNewline;
19923 					} else if(!exact) {
19924 						// we're not in the box, but are we on the same line?
19925 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
19926 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
19927 					}
19928 				}
19929 			}
19930 
19931 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
19932 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
19933 
19934 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
19935 		}
19936 
19937 		void moveCaretToPixelCoordinates(int x, int y) {
19938 			auto result = identify(x, y);
19939 			caret.inlineElement = result.element;
19940 			caret.offset = result.offset;
19941 		}
19942 
19943 		void selectToPixelCoordinates(int x, int y) {
19944 			auto result = identify(x, y);
19945 
19946 			if(y < caretLastDrawnY1) {
19947 				// on a previous line, carat is selectionEnd
19948 				selectionEnd = caret;
19949 
19950 				selectionStart = Caret(this, result.element, result.offset);
19951 			} else if(y > caretLastDrawnY2) {
19952 				// on a later line
19953 				selectionStart = caret;
19954 
19955 				selectionEnd = Caret(this, result.element, result.offset);
19956 			} else {
19957 				// on the same line...
19958 				if(x <= caretLastDrawnX) {
19959 					selectionEnd = caret;
19960 					selectionStart = Caret(this, result.element, result.offset);
19961 				} else {
19962 					selectionStart = caret;
19963 					selectionEnd = Caret(this, result.element, result.offset);
19964 				}
19965 
19966 			}
19967 		}
19968 
19969 
19970 		/// Call this if the inputs change. It will reflow everything
19971 		void redoLayout(ScreenPainter painter) {
19972 			//painter.setClipRectangle(boundingBox);
19973 			auto pos = Point(boundingBox.left, boundingBox.top);
19974 
19975 			int lastHeight;
19976 			void nl() {
19977 				pos.x = boundingBox.left;
19978 				pos.y += lastHeight;
19979 			}
19980 			foreach(block; blocks) {
19981 				nl();
19982 				foreach(part; block.parts) {
19983 					part.letterXs = null;
19984 
19985 					auto size = painter.textSize(part.text);
19986 					version(Windows)
19987 						if(part.text.length && part.text[$-1] == '\n')
19988 							size.height /= 2; // windows counts the new line at the end, but we don't want that
19989 
19990 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
19991 
19992 					foreach(idx, char c; part.text) {
19993 							// FIXME: unicode
19994 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
19995 					}
19996 
19997 					pos.x += size.width;
19998 					if(pos.x >= boundingBox.right) {
19999 						pos.y += size.height;
20000 						pos.x = boundingBox.left;
20001 						lastHeight = 0;
20002 					} else {
20003 						lastHeight = size.height;
20004 					}
20005 
20006 					if(part.text.length && part.text[$-1] == '\n')
20007 						nl();
20008 				}
20009 			}
20010 
20011 			layoutInvalidated = false;
20012 		}
20013 
20014 		bool layoutInvalidated = true;
20015 		void invalidateLayout() {
20016 			layoutInvalidated = true;
20017 		}
20018 
20019 // FIXME: caret can remain sometimes when inserting
20020 // FIXME: inserting at the beginning once you already have something can eff it up.
20021 		void drawInto(ScreenPainter painter, bool focused = false) {
20022 			if(layoutInvalidated)
20023 				redoLayout(painter);
20024 			foreach(block; blocks) {
20025 				foreach(part; block.parts) {
20026 					painter.outlineColor = part.color;
20027 					painter.fillColor = part.backgroundColor;
20028 
20029 					auto pos = part.boundingBox.upperLeft;
20030 					auto size = part.boundingBox.size;
20031 
20032 					painter.drawText(pos, part.text);
20033 					if(part.styles & TextFormat.underline)
20034 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
20035 					if(part.styles & TextFormat.strikethrough)
20036 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
20037 				}
20038 			}
20039 
20040 			// on every redraw, I will force the caret to be
20041 			// redrawn too, in order to eliminate perceived lag
20042 			// when moving around with the mouse.
20043 			eraseCaret(painter);
20044 
20045 			if(focused) {
20046 				highlightSelection(painter);
20047 				drawCaret(painter);
20048 			}
20049 		}
20050 
20051 		Color selectionXorColor = Color(255, 255, 127);
20052 
20053 		void highlightSelection(ScreenPainter painter) {
20054 			if(selectionStart is selectionEnd)
20055 				return; // no selection
20056 
20057 			if(selectionStart.inlineElement is null) return;
20058 			if(selectionEnd.inlineElement is null) return;
20059 
20060 			assert(selectionStart.inlineElement !is null);
20061 			assert(selectionEnd.inlineElement !is null);
20062 
20063 			painter.rasterOp = RasterOp.xor;
20064 			painter.outlineColor = Color.transparent;
20065 			painter.fillColor = selectionXorColor;
20066 
20067 			auto at = selectionStart.inlineElement;
20068 			auto atOffset = selectionStart.offset;
20069 			bool done;
20070 			while(at) {
20071 				auto box = at.boundingBox;
20072 				if(atOffset < at.letterXs.length)
20073 					box.left = at.letterXs[atOffset];
20074 
20075 				if(at is selectionEnd.inlineElement) {
20076 					if(selectionEnd.offset < at.letterXs.length)
20077 						box.right = at.letterXs[selectionEnd.offset];
20078 					done = true;
20079 				}
20080 
20081 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20082 
20083 				if(done)
20084 					break;
20085 
20086 				at = at.getNextInlineElement();
20087 				atOffset = 0;
20088 			}
20089 		}
20090 
20091 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20092 		bool caretShowingOnScreen = false;
20093 		void drawCaret(ScreenPainter painter) {
20094 			//painter.setClipRectangle(boundingBox);
20095 			int x, y1, y2;
20096 			if(caret.inlineElement is null) {
20097 				x = boundingBox.left;
20098 				y1 = boundingBox.top + 2;
20099 				y2 = boundingBox.top + painter.fontHeight;
20100 			} else {
20101 				x = caret.inlineElement.xOfIndex(caret.offset);
20102 				y1 = caret.inlineElement.boundingBox.top + 2;
20103 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20104 			}
20105 
20106 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20107 				eraseCaret(painter);
20108 
20109 			painter.pen = Pen(Color.white, 1);
20110 			painter.rasterOp = RasterOp.xor;
20111 			painter.drawLine(
20112 				Point(x, y1),
20113 				Point(x, y2)
20114 			);
20115 			painter.rasterOp = RasterOp.normal;
20116 			caretShowingOnScreen = !caretShowingOnScreen;
20117 
20118 			if(caretShowingOnScreen) {
20119 				caretLastDrawnX = x;
20120 				caretLastDrawnY1 = y1;
20121 				caretLastDrawnY2 = y2;
20122 			}
20123 		}
20124 
20125 		Rectangle caretBoundingBox() {
20126 			int x, y1, y2;
20127 			if(caret.inlineElement is null) {
20128 				x = boundingBox.left;
20129 				y1 = boundingBox.top + 2;
20130 				y2 = boundingBox.top + 16;
20131 			} else {
20132 				x = caret.inlineElement.xOfIndex(caret.offset);
20133 				y1 = caret.inlineElement.boundingBox.top + 2;
20134 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20135 			}
20136 
20137 			return Rectangle(x, y1, x + 1, y2);
20138 		}
20139 
20140 		void eraseCaret(ScreenPainter painter) {
20141 			//painter.setClipRectangle(boundingBox);
20142 			if(!caretShowingOnScreen) return;
20143 			painter.pen = Pen(Color.white, 1);
20144 			painter.rasterOp = RasterOp.xor;
20145 			painter.drawLine(
20146 				Point(caretLastDrawnX, caretLastDrawnY1),
20147 				Point(caretLastDrawnX, caretLastDrawnY2)
20148 			);
20149 
20150 			caretShowingOnScreen = false;
20151 			painter.rasterOp = RasterOp.normal;
20152 		}
20153 
20154 		/// Caret movement api
20155 		/// These should give the user a logical result based on what they see on screen...
20156 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20157 		void moveUp() {
20158 			if(caret.inlineElement is null) return;
20159 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20160 			auto y = caret.inlineElement.boundingBox.top + 2;
20161 
20162 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20163 			if(y < 0)
20164 				return;
20165 
20166 			auto i = identify(x, y);
20167 
20168 			if(i.element) {
20169 				caret.inlineElement = i.element;
20170 				caret.offset = i.offset;
20171 			}
20172 		}
20173 		void moveDown() {
20174 			if(caret.inlineElement is null) return;
20175 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20176 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20177 
20178 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20179 
20180 			auto i = identify(x, y);
20181 			if(i.element) {
20182 				caret.inlineElement = i.element;
20183 				caret.offset = i.offset;
20184 			}
20185 		}
20186 		void moveLeft() {
20187 			if(caret.inlineElement is null) return;
20188 			if(caret.offset)
20189 				caret.offset--;
20190 			else {
20191 				auto p = caret.inlineElement.getPreviousInlineElement();
20192 				if(p) {
20193 					caret.inlineElement = p;
20194 					if(p.text.length && p.text[$-1] == '\n')
20195 						caret.offset = cast(int) p.text.length - 1;
20196 					else
20197 						caret.offset = cast(int) p.text.length;
20198 				}
20199 			}
20200 		}
20201 		void moveRight() {
20202 			if(caret.inlineElement is null) return;
20203 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20204 				caret.offset++;
20205 			} else {
20206 				auto p = caret.inlineElement.getNextInlineElement();
20207 				if(p) {
20208 					caret.inlineElement = p;
20209 					caret.offset = 0;
20210 				}
20211 			}
20212 		}
20213 		void moveHome() {
20214 			if(caret.inlineElement is null) return;
20215 			auto x = 0;
20216 			auto y = caret.inlineElement.boundingBox.top + 2;
20217 
20218 			auto i = identify(x, y);
20219 
20220 			if(i.element) {
20221 				caret.inlineElement = i.element;
20222 				caret.offset = i.offset;
20223 			}
20224 		}
20225 		void moveEnd() {
20226 			if(caret.inlineElement is null) return;
20227 			auto x = int.max;
20228 			auto y = caret.inlineElement.boundingBox.top + 2;
20229 
20230 			auto i = identify(x, y);
20231 
20232 			if(i.element) {
20233 				caret.inlineElement = i.element;
20234 				caret.offset = i.offset;
20235 			}
20236 
20237 		}
20238 		void movePageUp(ref Caret caret) {}
20239 		void movePageDown(ref Caret caret) {}
20240 
20241 		void moveDocumentStart(ref Caret caret) {
20242 			if(blocks.length && blocks[0].parts.length)
20243 				caret = Caret(this, blocks[0].parts[0], 0);
20244 			else
20245 				caret = Caret.init;
20246 		}
20247 
20248 		void moveDocumentEnd(ref Caret caret) {
20249 			if(blocks.length) {
20250 				auto parts = blocks[$-1].parts;
20251 				if(parts.length) {
20252 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20253 				} else {
20254 					caret = Caret.init;
20255 				}
20256 			} else
20257 				caret = Caret.init;
20258 		}
20259 
20260 		void deleteSelection() {
20261 			if(selectionStart is selectionEnd)
20262 				return;
20263 
20264 			if(selectionStart.inlineElement is null) return;
20265 			if(selectionEnd.inlineElement is null) return;
20266 
20267 			assert(selectionStart.inlineElement !is null);
20268 			assert(selectionEnd.inlineElement !is null);
20269 
20270 			auto at = selectionStart.inlineElement;
20271 
20272 			if(selectionEnd.inlineElement is at) {
20273 				// same element, need to chop out
20274 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20275 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20276 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20277 			} else {
20278 				// different elements, we can do it with slicing
20279 				at.text = at.text[0 .. selectionStart.offset];
20280 				if(selectionStart.offset < at.letterXs.length)
20281 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20282 
20283 				at = at.getNextInlineElement();
20284 
20285 				while(at) {
20286 					if(at is selectionEnd.inlineElement) {
20287 						at.text = at.text[selectionEnd.offset .. $];
20288 						if(selectionEnd.offset < at.letterXs.length)
20289 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20290 						selectionEnd.offset = 0;
20291 						break;
20292 					} else {
20293 						auto cfd = at;
20294 						cfd.text = null; // delete the whole thing
20295 
20296 						at = at.getNextInlineElement();
20297 
20298 						if(cfd.text.length == 0) {
20299 							// and remove cfd
20300 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20301 								if(cfd.containingBlock.parts[a] is cfd) {
20302 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20303 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20304 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20305 
20306 								}
20307 							}
20308 						}
20309 					}
20310 				}
20311 			}
20312 
20313 			caret = selectionEnd;
20314 			selectNone();
20315 
20316 			invalidateLayout();
20317 
20318 		}
20319 
20320 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20321 		void insert(in char[] text) {
20322 			foreach(dchar ch; text)
20323 				insert(ch);
20324 		}
20325 		/// ditto
20326 		void insert(dchar ch) {
20327 
20328 			bool selectionDeleted = false;
20329 			if(selectionStart !is selectionEnd) {
20330 				deleteSelection();
20331 				selectionDeleted = true;
20332 			}
20333 
20334 			if(ch == 127) {
20335 				delete_();
20336 				return;
20337 			}
20338 			if(ch == 8) {
20339 				if(!selectionDeleted)
20340 					backspace();
20341 				return;
20342 			}
20343 
20344 			invalidateLayout();
20345 
20346 			if(ch == 13) ch = 10;
20347 			auto e = caret.inlineElement;
20348 			if(e is null) {
20349 				addText("" ~ cast(char) ch) ; // FIXME
20350 				return;
20351 			}
20352 
20353 			if(caret.offset == e.text.length) {
20354 				e.text ~= cast(char) ch; // FIXME
20355 				caret.offset++;
20356 				if(ch == 10) {
20357 					auto c = caret.inlineElement.clone;
20358 					c.text = null;
20359 					c.letterXs = null;
20360 					insertPartAfter(c,e);
20361 					caret = Caret(this, c, 0);
20362 				}
20363 			} else {
20364 				// FIXME cast char sucks
20365 				if(ch == 10) {
20366 					auto c = caret.inlineElement.clone;
20367 					c.text = e.text[caret.offset .. $];
20368 					if(caret.offset < c.letterXs.length)
20369 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20370 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20371 					if(caret.offset <= e.letterXs.length) {
20372 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20373 					}
20374 					insertPartAfter(c,e);
20375 					caret = Caret(this, c, 0);
20376 				} else {
20377 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20378 					caret.offset++;
20379 				}
20380 			}
20381 		}
20382 
20383 		void insertPartAfter(InlineElement what, InlineElement where) {
20384 			foreach(idx, p; where.containingBlock.parts) {
20385 				if(p is where) {
20386 					if(idx + 1 == where.containingBlock.parts.length)
20387 						where.containingBlock.parts ~= what;
20388 					else
20389 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20390 					return;
20391 				}
20392 			}
20393 		}
20394 
20395 		void cleanupStructures() {
20396 			for(size_t i = 0; i < blocks.length; i++) {
20397 				auto block = blocks[i];
20398 				for(size_t a = 0; a < block.parts.length; a++) {
20399 					auto part = block.parts[a];
20400 					if(part.text.length == 0) {
20401 						for(size_t b = a; b < block.parts.length - 1; b++)
20402 							block.parts[b] = block.parts[b+1];
20403 						block.parts = block.parts[0 .. $-1];
20404 					}
20405 				}
20406 				if(block.parts.length == 0) {
20407 					for(size_t a = i; a < blocks.length - 1; a++)
20408 						blocks[a] = blocks[a+1];
20409 					blocks = blocks[0 .. $-1];
20410 				}
20411 			}
20412 		}
20413 
20414 		void backspace() {
20415 			try_again:
20416 			auto e = caret.inlineElement;
20417 			if(e is null)
20418 				return;
20419 			if(caret.offset == 0) {
20420 				auto prev = e.getPreviousInlineElement();
20421 				if(prev is null)
20422 					return;
20423 				auto newOffset = cast(int) prev.text.length;
20424 				tryMerge(prev, e);
20425 				caret.inlineElement = prev;
20426 				caret.offset = prev is null ? 0 : newOffset;
20427 
20428 				goto try_again;
20429 			} else if(caret.offset == e.text.length) {
20430 				e.text = e.text[0 .. $-1];
20431 				caret.offset--;
20432 			} else {
20433 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20434 				caret.offset--;
20435 			}
20436 			//cleanupStructures();
20437 
20438 			invalidateLayout();
20439 		}
20440 		void delete_() {
20441 			if(selectionStart !is selectionEnd)
20442 				deleteSelection();
20443 			else {
20444 				auto before = caret;
20445 				moveRight();
20446 				if(caret != before) {
20447 					backspace();
20448 				}
20449 			}
20450 
20451 			invalidateLayout();
20452 		}
20453 		void overstrike() {}
20454 
20455 		/// Selection API. See also: caret movement.
20456 		void selectAll() {
20457 			moveDocumentStart(selectionStart);
20458 			moveDocumentEnd(selectionEnd);
20459 		}
20460 		bool selectNone() {
20461 			if(selectionStart != selectionEnd) {
20462 				selectionStart = selectionEnd = Caret.init;
20463 				return true;
20464 			}
20465 			return false;
20466 		}
20467 
20468 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20469 		/// They will modify the current selection if there is one and will splice one in if needed.
20470 		void changeAttributes() {}
20471 
20472 
20473 		/// Text search api. They manipulate the selection and/or caret.
20474 		void findText(string text) {}
20475 		void findIndex(size_t textIndex) {}
20476 
20477 		// sample event handlers
20478 
20479 		void handleEvent(KeyEvent event) {
20480 			//if(event.type == KeyEvent.Type.KeyPressed) {
20481 
20482 			//}
20483 		}
20484 
20485 		void handleEvent(dchar ch) {
20486 
20487 		}
20488 
20489 		void handleEvent(MouseEvent event) {
20490 
20491 		}
20492 
20493 		bool contentEditable; // can it be edited?
20494 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20495 		bool contentSelectable; // selectable?
20496 
20497 		Caret caret;
20498 		Caret selectionStart;
20499 		Caret selectionEnd;
20500 
20501 		bool insertMode;
20502 	}
20503 
20504 	struct Caret {
20505 		TextLayout layout;
20506 		InlineElement inlineElement;
20507 		int offset;
20508 	}
20509 
20510 	enum TextFormat : ushort {
20511 		// decorations
20512 		underline = 1,
20513 		strikethrough = 2,
20514 
20515 		// font selectors
20516 
20517 		bold = 0x4000 | 1, // weight 700
20518 		light = 0x4000 | 2, // weight 300
20519 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20520 		// bold | light is really invalid but should give weight 500
20521 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20522 
20523 		italic = 0x4000 | 8,
20524 		smallcaps = 0x4000 | 16,
20525 	}
20526 
20527 	void* findFont(string family, int weight, TextFormat formats) {
20528 		return null;
20529 	}
20530 
20531 }
20532 
20533 /++
20534 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20535 
20536 	History:
20537 		Added February 19, 2021
20538 +/
20539 /// Group: drag_and_drop
20540 interface DropHandler {
20541 	/++
20542 		Called when the drag enters the handler's area.
20543 	+/
20544 	DragAndDropAction dragEnter(DropPackage*);
20545 	/++
20546 		Called when the drag leaves the handler's area or is
20547 		cancelled. You should free your resources when this is called.
20548 	+/
20549 	void dragLeave();
20550 	/++
20551 		Called continually as the drag moves over the handler's area.
20552 
20553 		Returns: feedback to the dragger
20554 	+/
20555 	DropParameters dragOver(Point pt);
20556 	/++
20557 		The user dropped the data and you should process it now. You can
20558 		access the data through the given [DropPackage].
20559 	+/
20560 	void drop(scope DropPackage*);
20561 	/++
20562 		Called when the drop is complete. You should free whatever temporary
20563 		resources you were using. It is often reasonable to simply forward
20564 		this call to [dragLeave].
20565 	+/
20566 	void finish();
20567 
20568 	/++
20569 		Parameters returned by [DropHandler.drop].
20570 	+/
20571 	static struct DropParameters {
20572 		/++
20573 			Acceptable action over this area.
20574 		+/
20575 		DragAndDropAction action;
20576 		/++
20577 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20578 
20579 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20580 		+/
20581 		Rectangle consistentWithin;
20582 	}
20583 }
20584 
20585 /++
20586 	History:
20587 		Added February 19, 2021
20588 +/
20589 /// Group: drag_and_drop
20590 enum DragAndDropAction {
20591 	none = 0,
20592 	copy,
20593 	move,
20594 	link,
20595 	ask,
20596 	custom
20597 }
20598 
20599 /++
20600 	An opaque structure representing dropped data. It contains
20601 	private, platform-specific data that your `drop` function
20602 	should simply forward.
20603 
20604 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20605 
20606 	History:
20607 		Added February 19, 2021
20608 +/
20609 /// Group: drag_and_drop
20610 struct DropPackage {
20611 	/++
20612 		Lists the available formats as magic numbers. You should compare these
20613 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20614 		understand the passed data.
20615 	+/
20616 	DraggableData.FormatId[] availableFormats() {
20617 		version(X11) {
20618 			return xFormats;
20619 		} else version(Windows) {
20620 			if(pDataObj is null)
20621 				return null;
20622 
20623 			typeof(return) ret;
20624 
20625 			IEnumFORMATETC ef;
20626 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20627 				FORMATETC fmt;
20628 				ULONG fetched;
20629 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20630 					if(fetched == 0)
20631 						break;
20632 
20633 					if(fmt.lindex != -1)
20634 						continue;
20635 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20636 						continue;
20637 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20638 						continue;
20639 
20640 					ret ~= fmt.cfFormat;
20641 				}
20642 			}
20643 
20644 			return ret;
20645 		}
20646 	}
20647 
20648 	/++
20649 		Gets data from the drop and optionally accepts it.
20650 
20651 		Returns:
20652 			void because the data is fed asynchronously through the `dg` parameter.
20653 
20654 		Params:
20655 			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.
20656 
20657 			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.
20658 
20659 			Calling `getData` again after accepting a drop is not permitted.
20660 
20661 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20662 
20663 			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.
20664 
20665 		Throws:
20666 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20667 
20668 		History:
20669 			Included in first release of [DropPackage].
20670 	+/
20671 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20672 		version(X11) {
20673 
20674 			auto display = XDisplayConnection.get();
20675 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20676 			auto best = format;
20677 
20678 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20679 
20680 				XDisplay* display;
20681 				Atom selectionAtom;
20682 				DraggableData.FormatId best;
20683 				DraggableData.FormatId format;
20684 				void delegate(scope ubyte[] data) dg;
20685 				DragAndDropAction acceptedAction;
20686 				Window sourceWindow;
20687 				SimpleWindow win;
20688 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20689 					this.display = display;
20690 					this.win = win;
20691 					this.sourceWindow = sourceWindow;
20692 					this.format = format;
20693 					this.selectionAtom = selectionAtom;
20694 					this.best = best;
20695 					this.dg = dg;
20696 					this.acceptedAction = acceptedAction;
20697 				}
20698 
20699 
20700 				mixin X11GetSelectionHandler_Basics;
20701 
20702 				void handleData(Atom target, in ubyte[] data) {
20703 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20704 
20705 					dg(cast(ubyte[]) data);
20706 
20707 					if(acceptedAction != DragAndDropAction.none) {
20708 						auto display = XDisplayConnection.get;
20709 
20710 						XClientMessageEvent xclient;
20711 
20712 						xclient.type = EventType.ClientMessage;
20713 						xclient.window = sourceWindow;
20714 						xclient.message_type = GetAtom!"XdndFinished"(display);
20715 						xclient.format = 32;
20716 						xclient.data.l[0] = win.impl.window;
20717 						xclient.data.l[1] = 1; // drop successful
20718 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20719 
20720 						XSendEvent(
20721 							display,
20722 							sourceWindow,
20723 							false,
20724 							EventMask.NoEventMask,
20725 							cast(XEvent*) &xclient
20726 						);
20727 
20728 						XFlush(display);
20729 					}
20730 				}
20731 
20732 				Atom findBestFormat(Atom[] answer) {
20733 					Atom best = None;
20734 					foreach(option; answer) {
20735 						if(option == format) {
20736 							best = option;
20737 							break;
20738 						}
20739 						/*
20740 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20741 							best = option;
20742 							break;
20743 						} else if(option == XA_STRING) {
20744 							best = option;
20745 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20746 							best = option;
20747 						}
20748 						*/
20749 					}
20750 					return best;
20751 				}
20752 			}
20753 
20754 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20755 
20756 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20757 
20758 		} else version(Windows) {
20759 
20760 			// clean up like DragLeave
20761 			// pass effect back up
20762 
20763 			FORMATETC t;
20764 			assert(format >= 0 && format <= ushort.max);
20765 			t.cfFormat = cast(ushort) format;
20766 			t.lindex = -1;
20767 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20768 			t.tymed = TYMED.TYMED_HGLOBAL;
20769 
20770 			STGMEDIUM m;
20771 
20772 			if(pDataObj.GetData(&t, &m) != S_OK) {
20773 				// fail
20774 			} else {
20775 				// succeed, take the data and clean up
20776 
20777 				// FIXME: ensure it is legit HGLOBAL
20778 				auto handle = m.hGlobal;
20779 
20780 				if(handle) {
20781 					auto sz = GlobalSize(handle);
20782 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20783 						scope(exit) GlobalUnlock(handle);
20784 						scope(exit) GlobalFree(handle);
20785 
20786 						auto data = ptr[0 .. sz];
20787 
20788 						dg(data);
20789 					}
20790 				}
20791 			}
20792 		}
20793 	}
20794 
20795 	private:
20796 
20797 	version(X11) {
20798 		SimpleWindow win;
20799 		Window sourceWindow;
20800 		Time dataTimestamp;
20801 
20802 		Atom[] xFormats;
20803 	}
20804 	version(Windows) {
20805 		IDataObject pDataObj;
20806 	}
20807 }
20808 
20809 /++
20810 	A generic helper base class for making a drop handler with a preference list of custom types.
20811 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20812 	droppers too.
20813 
20814 	It assumes the whole window it used, but you can subclass to change that.
20815 
20816 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20817 
20818 	History:
20819 		Added February 19, 2021
20820 +/
20821 /// Group: drag_and_drop
20822 class GenericDropHandlerBase : DropHandler {
20823 	// no fancy state here so no need to do anything here
20824 	void finish() { }
20825 	void dragLeave() { }
20826 
20827 	private DragAndDropAction acceptedAction;
20828 	private DraggableData.FormatId acceptedFormat;
20829 	private void delegate(scope ubyte[]) acceptedHandler;
20830 
20831 	struct FormatHandler {
20832 		DraggableData.FormatId format;
20833 		void delegate(scope ubyte[]) handler;
20834 	}
20835 
20836 	protected abstract FormatHandler[] formatHandlers();
20837 
20838 	DragAndDropAction dragEnter(DropPackage* pkg) {
20839 		debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20840 		foreach(fmt; formatHandlers())
20841 		foreach(f; pkg.availableFormats())
20842 			if(f == fmt.format) {
20843 				acceptedFormat = f;
20844 				acceptedHandler = fmt.handler;
20845 				return acceptedAction = DragAndDropAction.copy;
20846 			}
20847 		return acceptedAction = DragAndDropAction.none;
20848 	}
20849 	DropParameters dragOver(Point pt) {
20850 		return DropParameters(acceptedAction);
20851 	}
20852 
20853 	void drop(scope DropPackage* dropPackage) {
20854 		if(!acceptedFormat || acceptedHandler is null) {
20855 			debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20856 			return; // prolly shouldn't happen anyway...
20857 		}
20858 
20859 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20860 	}
20861 }
20862 
20863 /++
20864 	A simple handler for making your window accept drops of plain text.
20865 
20866 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20867 
20868 	History:
20869 		Added February 22, 2021
20870 +/
20871 /// Group: drag_and_drop
20872 class TextDropHandler : GenericDropHandlerBase {
20873 	private void delegate(in char[] text) dg;
20874 
20875 	/++
20876 
20877 	+/
20878 	this(void delegate(in char[] text) dg) {
20879 		this.dg = dg;
20880 	}
20881 
20882 	protected override FormatHandler[] formatHandlers() {
20883 		version(X11)
20884 			return [
20885 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
20886 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
20887 			];
20888 		else version(Windows)
20889 			return [
20890 				FormatHandler(CF_UNICODETEXT, &translator),
20891 			];
20892 	}
20893 
20894 	private void translator(scope ubyte[] data) {
20895 		version(X11)
20896 			dg(cast(char[]) data);
20897 		else version(Windows)
20898 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
20899 	}
20900 }
20901 
20902 /++
20903 	A simple handler for making your window accept drops of files, issued to you as file names.
20904 
20905 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20906 
20907 	History:
20908 		Added February 22, 2021
20909 +/
20910 /// Group: drag_and_drop
20911 
20912 class FilesDropHandler : GenericDropHandlerBase {
20913 	private void delegate(in char[][]) dg;
20914 
20915 	/++
20916 
20917 	+/
20918 	this(void delegate(in char[][] fileNames) dg) {
20919 		this.dg = dg;
20920 	}
20921 
20922 	protected override FormatHandler[] formatHandlers() {
20923 		version(X11)
20924 			return [
20925 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
20926 			];
20927 		else version(Windows)
20928 			return [
20929 				FormatHandler(CF_HDROP, &translator),
20930 			];
20931 	}
20932 
20933 	private void translator(scope ubyte[] data) {
20934 		version(X11) {
20935 			char[] listString = cast(char[]) data;
20936 			char[][16] buffer;
20937 			int count;
20938 			char[][] result = buffer[];
20939 
20940 			void commit(char[] s) {
20941 				if(count == result.length)
20942 					result.length += 16;
20943 				if(s.length > 7 && s[0 ..7] == "file://")
20944 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
20945 				result[count++] = s;
20946 			}
20947 
20948 			size_t last;
20949 			foreach(idx, char c; listString) {
20950 				if(c == '\n') {
20951 					commit(listString[last .. idx - 1]); // a \r
20952 					last = idx + 1; // a \n
20953 				}
20954 			}
20955 
20956 			if(last < listString.length) {
20957 				commit(listString[last .. $]);
20958 			}
20959 
20960 			// FIXME: they are uris now, should I translate it to local file names?
20961 			// of course the host name is supposed to be there cuz of X rokking...
20962 
20963 			dg(result[0 .. count]);
20964 		} else version(Windows) {
20965 
20966 			static struct DROPFILES {
20967 				DWORD pFiles;
20968 				POINT pt;
20969 				BOOL  fNC;
20970 				BOOL  fWide;
20971 			}
20972 
20973 
20974 			const(char)[][16] buffer;
20975 			int count;
20976 			const(char)[][] result = buffer[];
20977 			size_t last;
20978 
20979 			void commitA(in char[] stuff) {
20980 				if(count == result.length)
20981 					result.length += 16;
20982 				result[count++] = stuff;
20983 			}
20984 
20985 			void commitW(in wchar[] stuff) {
20986 				commitA(makeUtf8StringFromWindowsString(stuff));
20987 			}
20988 
20989 			void magic(T)(T chars) {
20990 				size_t idx;
20991 				while(chars[idx]) {
20992 					last = idx;
20993 					while(chars[idx]) {
20994 						idx++;
20995 					}
20996 					static if(is(T == char*))
20997 						commitA(chars[last .. idx]);
20998 					else
20999 						commitW(chars[last .. idx]);
21000 					idx++;
21001 				}
21002 			}
21003 
21004 			auto df = cast(DROPFILES*) data.ptr;
21005 			if(df.fWide) {
21006 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21007 				magic(chars);
21008 			} else {
21009 				char* chars = cast(char*) (data.ptr + df.pFiles);
21010 				magic(chars);
21011 			}
21012 			dg(result[0 .. count]);
21013 		}
21014 	}
21015 }
21016 
21017 /++
21018 	Interface to describe data being dragged. See also [draggable] helper function.
21019 
21020 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21021 
21022 	History:
21023 		Added February 19, 2021
21024 +/
21025 interface DraggableData {
21026 	version(X11)
21027 		alias FormatId = Atom;
21028 	else
21029 		alias FormatId = uint;
21030 	/++
21031 		Gets the platform-specific FormatId associated with the given named format.
21032 
21033 		This may be a MIME type, but may also be other various strings defined by the
21034 		programs you want to interoperate with.
21035 
21036 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
21037 		and convert it to some particular type for you.
21038 	+/
21039 	static FormatId getFormatId(string name)() {
21040 		version(X11)
21041 			return GetAtom!name(XDisplayConnection.get);
21042 		else version(Windows) {
21043 			static UINT cache;
21044 			if(!cache)
21045 				cache = RegisterClipboardFormatA(name);
21046 			return cache;
21047 		} else
21048 			throw new NotYetImplementedException();
21049 	}
21050 
21051 	/++
21052 		Looks up a string to represent the name for the given format, if there is one.
21053 
21054 		You should avoid using this function because it is slow. It is provided more for
21055 		debugging than for primary use.
21056 	+/
21057 	static string getFormatName(FormatId format) {
21058 		version(X11) {
21059 			if(format == 0)
21060 				return "None";
21061 			else
21062 				return getAtomName(format, XDisplayConnection.get);
21063 		} else version(Windows) {
21064 			switch(format) {
21065 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
21066 				case CF_DIBV5: return "CF_DIBV5";
21067 				case CF_RIFF: return "CF_RIFF";
21068 				case CF_WAVE: return "CF_WAVE";
21069 				case CF_HDROP: return "CF_HDROP";
21070 				default:
21071 					char[1024] name;
21072 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21073 					return name[0 .. count].idup;
21074 			}
21075 		}
21076 	}
21077 
21078 	FormatId[] availableFormats();
21079 	// Return the slice of data you filled, empty slice if done.
21080 	// this is to support the incremental thing
21081 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21082 
21083 	size_t dataLength(FormatId format);
21084 }
21085 
21086 /++
21087 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21088 
21089 	History:
21090 		Added February 19, 2021
21091 +/
21092 DraggableData draggable(string s) {
21093 	version(X11)
21094 	return new class X11SetSelectionHandler_Text, DraggableData {
21095 		this() {
21096 			super(s);
21097 		}
21098 
21099 		override FormatId[] availableFormats() {
21100 			return X11SetSelectionHandler_Text.availableFormats();
21101 		}
21102 
21103 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21104 			return X11SetSelectionHandler_Text.getData(format, data);
21105 		}
21106 
21107 		size_t dataLength(FormatId format) {
21108 			return s.length;
21109 		}
21110 	};
21111 	version(Windows)
21112 	return new class DraggableData {
21113 		FormatId[] availableFormats() {
21114 			return [CF_UNICODETEXT];
21115 		}
21116 
21117 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21118 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21119 		}
21120 
21121 		size_t dataLength(FormatId format) {
21122 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21123 		}
21124 	};
21125 }
21126 
21127 /++
21128 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21129 
21130 	History:
21131 		Added February 19, 2021
21132 +/
21133 /// Group: drag_and_drop
21134 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21135 in {
21136 	assert(window !is null);
21137 	assert(handler !is null);
21138 }
21139 do
21140 {
21141 	version(X11) {
21142 		auto sh = cast(X11SetSelectionHandler) handler;
21143 		if(sh is null) {
21144 			// gotta make my own adapter.
21145 			sh = new class X11SetSelectionHandler {
21146 				mixin X11SetSelectionHandler_Basics;
21147 
21148 				Atom[] availableFormats() { return handler.availableFormats(); }
21149 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21150 					return handler.getData(format, data);
21151 				}
21152 
21153 				// since the drop selection is only ever used once it isn't important
21154 				// to reset it.
21155 				void done() {}
21156 			};
21157 		}
21158 		return doDragDropX11(window, sh, action);
21159 	} else version(Windows) {
21160 		return doDragDropWindows(window, handler, action);
21161 	} else throw new NotYetImplementedException();
21162 }
21163 
21164 version(Windows)
21165 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21166 	IDataObject obj = new class IDataObject {
21167 		ULONG refCount;
21168 		ULONG AddRef() {
21169 			return ++refCount;
21170 		}
21171 		ULONG Release() {
21172 			return --refCount;
21173 		}
21174 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21175 			if (IID_IUnknown == *riid) {
21176 				*ppv = cast(void*) cast(IUnknown) this;
21177 			}
21178 			else if (IID_IDataObject == *riid) {
21179 				*ppv = cast(void*) cast(IDataObject) this;
21180 			}
21181 			else {
21182 				*ppv = null;
21183 				return E_NOINTERFACE;
21184 			}
21185 
21186 			AddRef();
21187 			return NOERROR;
21188 		}
21189 
21190 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21191 			// import std.stdio; writeln("Advise");
21192 			return E_NOTIMPL;
21193 		}
21194 		HRESULT DUnadvise(DWORD dwConnection) {
21195 			return E_NOTIMPL;
21196 		}
21197 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21198 			// import std.stdio; writeln("EnumDAdvise");
21199 			return OLE_E_ADVISENOTSUPPORTED;
21200 		}
21201 		// tell what formats it supports
21202 
21203 		FORMATETC[] types;
21204 		this() {
21205 			FORMATETC t;
21206 			foreach(ty; handler.availableFormats()) {
21207 				assert(ty <= ushort.max && ty >= 0);
21208 				t.cfFormat = cast(ushort) ty;
21209 				t.lindex = -1;
21210 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21211 				t.tymed = TYMED.TYMED_HGLOBAL;
21212 			}
21213 			types ~= t;
21214 		}
21215 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21216 			if(dwDirection == DATADIR.DATADIR_GET) {
21217 				*ppenumFormatEtc = new class IEnumFORMATETC {
21218 					ULONG refCount;
21219 					ULONG AddRef() {
21220 						return ++refCount;
21221 					}
21222 					ULONG Release() {
21223 						return --refCount;
21224 					}
21225 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21226 						if (IID_IUnknown == *riid) {
21227 							*ppv = cast(void*) cast(IUnknown) this;
21228 						}
21229 						else if (IID_IEnumFORMATETC == *riid) {
21230 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21231 						}
21232 						else {
21233 							*ppv = null;
21234 							return E_NOINTERFACE;
21235 						}
21236 
21237 						AddRef();
21238 						return NOERROR;
21239 					}
21240 
21241 
21242 					int pos;
21243 					this() {
21244 						pos = 0;
21245 					}
21246 
21247 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21248 						// import std.stdio; writeln("clone");
21249 						return E_NOTIMPL; // FIXME
21250 					}
21251 
21252 					// Caller is responsible for freeing memory
21253 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21254 						// fetched may be null if celt is one
21255 						if(celt != 1)
21256 							return E_NOTIMPL; // FIXME
21257 
21258 						if(celt + pos > types.length)
21259 							return S_FALSE;
21260 
21261 						*rgelt = types[pos++];
21262 
21263 						if(pceltFetched !is null)
21264 							*pceltFetched = 1;
21265 
21266 						// import std.stdio; writeln("ok celt ", celt);
21267 						return S_OK;
21268 					}
21269 
21270 					HRESULT Reset() {
21271 						pos = 0;
21272 						return S_OK;
21273 					}
21274 
21275 					HRESULT Skip(ULONG celt) {
21276 						if(celt + pos <= types.length) {
21277 							pos += celt;
21278 							return S_OK;
21279 						}
21280 						return S_FALSE;
21281 					}
21282 				};
21283 
21284 				return S_OK;
21285 			} else
21286 				return E_NOTIMPL;
21287 		}
21288 		// given a format, return the format you'd prefer to use cuz it is identical
21289 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21290 			// FIXME: prolly could be better but meh
21291 			// import std.stdio; writeln("gcf: ", *pformatectIn);
21292 			*pformatetcOut = *pformatectIn;
21293 			return S_OK;
21294 		}
21295 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21296 			foreach(ty; types) {
21297 				if(ty == *pformatetcIn) {
21298 					auto format = ty.cfFormat;
21299 					// import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty);
21300 					STGMEDIUM medium;
21301 					medium.tymed = TYMED.TYMED_HGLOBAL;
21302 
21303 					auto sz = handler.dataLength(format);
21304 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21305 					if(handle is null) throw new Exception("GlobalAlloc");
21306 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21307 						auto slice = data[0 .. sz];
21308 						scope(exit)
21309 							GlobalUnlock(handle);
21310 
21311 						handler.getData(format, cast(ubyte[]) slice[]);
21312 					}
21313 
21314 
21315 					medium.hGlobal = handle; // FIXME
21316 					*pmedium = medium;
21317 					return S_OK;
21318 				}
21319 			}
21320 			return DV_E_FORMATETC;
21321 		}
21322 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21323 			// import std.stdio; writeln("GDH: ", *pformatetcIn);
21324 			return E_NOTIMPL; // FIXME
21325 		}
21326 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21327 			auto search = *pformatetc;
21328 			search.tymed &= TYMED.TYMED_HGLOBAL;
21329 			foreach(ty; types)
21330 				if(ty == search) {
21331 					// import std.stdio; writeln("QueryGetData ", search, " ", types[0]);
21332 					return S_OK;
21333 				}
21334 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21335 				//import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]);
21336 			}
21337 			return S_FALSE;
21338 		}
21339 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21340 			// import std.stdio; writeln("SetData: ");
21341 			return E_NOTIMPL;
21342 		}
21343 	};
21344 
21345 
21346 	IDropSource src = new class IDropSource {
21347 		ULONG refCount;
21348 		ULONG AddRef() {
21349 			return ++refCount;
21350 		}
21351 		ULONG Release() {
21352 			return --refCount;
21353 		}
21354 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21355 			if (IID_IUnknown == *riid) {
21356 				*ppv = cast(void*) cast(IUnknown) this;
21357 			}
21358 			else if (IID_IDropSource == *riid) {
21359 				*ppv = cast(void*) cast(IDropSource) this;
21360 			}
21361 			else {
21362 				*ppv = null;
21363 				return E_NOINTERFACE;
21364 			}
21365 
21366 			AddRef();
21367 			return NOERROR;
21368 		}
21369 
21370 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21371 			if(fEscapePressed)
21372 				return DRAGDROP_S_CANCEL;
21373 			if(!(grfKeyState & MK_LBUTTON))
21374 				return DRAGDROP_S_DROP;
21375 			return S_OK;
21376 		}
21377 
21378 		int GiveFeedback(uint dwEffect) {
21379 			return DRAGDROP_S_USEDEFAULTCURSORS;
21380 		}
21381 	};
21382 
21383 	DWORD effect;
21384 
21385 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21386 
21387 	DROPEFFECT de = win32DragAndDropAction(action);
21388 
21389 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21390 	// but still prolly a FIXME
21391 
21392 	auto ret = DoDragDrop(obj, src, de, &effect);
21393 	/+
21394 	import std.stdio;
21395 	if(ret == DRAGDROP_S_DROP)
21396 		writeln("drop ", effect);
21397 	else if(ret == DRAGDROP_S_CANCEL)
21398 		writeln("cancel");
21399 	else if(ret == S_OK)
21400 		writeln("ok");
21401 	else writeln(ret);
21402 	+/
21403 
21404 	return ret;
21405 }
21406 
21407 version(Windows)
21408 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21409 	DROPEFFECT de;
21410 
21411 	with(DragAndDropAction)
21412 	with(DROPEFFECT)
21413 	final switch(action) {
21414 		case none: de = DROPEFFECT_NONE; break;
21415 		case copy: de = DROPEFFECT_COPY; break;
21416 		case move: de = DROPEFFECT_MOVE; break;
21417 		case link: de = DROPEFFECT_LINK; break;
21418 		case ask: throw new Exception("ask not implemented yet");
21419 		case custom: throw new Exception("custom not implemented yet");
21420 	}
21421 
21422 	return de;
21423 }
21424 
21425 
21426 /++
21427 	History:
21428 		Added February 19, 2021
21429 +/
21430 /// Group: drag_and_drop
21431 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21432 	version(X11) {
21433 		auto display = XDisplayConnection.get;
21434 
21435 		Atom atom = 5; // right???
21436 
21437 		XChangeProperty(
21438 			display,
21439 			window.impl.window,
21440 			GetAtom!"XdndAware"(display),
21441 			XA_ATOM,
21442 			32 /* bits */,
21443 			PropModeReplace,
21444 			&atom,
21445 			1);
21446 
21447 		window.dropHandler = handler;
21448 	} else version(Windows) {
21449 
21450 		initDnd();
21451 
21452 		auto dropTarget = new class (handler) IDropTarget {
21453 			DropHandler handler;
21454 			this(DropHandler handler) {
21455 				this.handler = handler;
21456 			}
21457 			ULONG refCount;
21458 			ULONG AddRef() {
21459 				return ++refCount;
21460 			}
21461 			ULONG Release() {
21462 				return --refCount;
21463 			}
21464 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21465 				if (IID_IUnknown == *riid) {
21466 					*ppv = cast(void*) cast(IUnknown) this;
21467 				}
21468 				else if (IID_IDropTarget == *riid) {
21469 					*ppv = cast(void*) cast(IDropTarget) this;
21470 				}
21471 				else {
21472 					*ppv = null;
21473 					return E_NOINTERFACE;
21474 				}
21475 
21476 				AddRef();
21477 				return NOERROR;
21478 			}
21479 
21480 
21481 			// ///////////////////
21482 
21483 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21484 				DropPackage dropPackage = DropPackage(pDataObj);
21485 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21486 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21487 			}
21488 
21489 			HRESULT DragLeave() {
21490 				handler.dragLeave();
21491 				// release the IDataObject if needed
21492 				return S_OK;
21493 			}
21494 
21495 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21496 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21497 
21498 				*pdwEffect = win32DragAndDropAction(res.action);
21499 				// same as DragEnter basically
21500 				return S_OK;
21501 			}
21502 
21503 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21504 				DropPackage pkg = DropPackage(pDataObj);
21505 				handler.drop(&pkg);
21506 
21507 				return S_OK;
21508 			}
21509 		};
21510 		// Windows can hold on to the handler and try to call it
21511 		// during which time the GC can't see it. so important to
21512 		// manually manage this. At some point i'll FIXME and make
21513 		// all my com instances manually managed since they supposed
21514 		// to respect the refcount.
21515 		import core.memory;
21516 		GC.addRoot(cast(void*) dropTarget);
21517 
21518 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21519 			throw new Exception("register");
21520 
21521 		window.dropHandler = handler;
21522 	} else throw new NotYetImplementedException();
21523 }
21524 
21525 
21526 
21527 static if(UsingSimpledisplayX11) {
21528 
21529 enum _NET_WM_STATE_ADD = 1;
21530 enum _NET_WM_STATE_REMOVE = 0;
21531 enum _NET_WM_STATE_TOGGLE = 2;
21532 
21533 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21534 void demandAttention(SimpleWindow window, bool needs = true) {
21535 	demandAttention(window.impl.window, needs);
21536 }
21537 
21538 /// ditto
21539 void demandAttention(Window window, bool needs = true) {
21540 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21541 }
21542 
21543 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21544 	auto display = XDisplayConnection.get();
21545 	if(atom == None)
21546 		return; // non-failure error
21547 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21548 
21549 	XClientMessageEvent xclient;
21550 
21551 	xclient.type = EventType.ClientMessage;
21552 	xclient.window = window;
21553 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21554 	xclient.format = 32;
21555 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21556 	xclient.data.l[1] = atom;
21557 	xclient.data.l[2] = atom2;
21558 	xclient.data.l[3] = 1;
21559 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21560 
21561 	XSendEvent(
21562 		display,
21563 		RootWindow(display, DefaultScreen(display)),
21564 		false,
21565 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21566 		cast(XEvent*) &xclient
21567 	);
21568 
21569 	/+
21570 	XChangeProperty(
21571 		display,
21572 		window.impl.window,
21573 		GetAtom!"_NET_WM_STATE"(display),
21574 		XA_ATOM,
21575 		32 /* bits */,
21576 		PropModeAppend,
21577 		&atom,
21578 		1);
21579 	+/
21580 }
21581 
21582 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21583 	Atom actionAtom;
21584 	with(DragAndDropAction)
21585 	final switch(action) {
21586 		case none: actionAtom = None; break;
21587 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21588 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21589 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21590 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21591 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21592 	}
21593 
21594 	return actionAtom;
21595 }
21596 
21597 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21598 	// FIXME: I need to show user feedback somehow.
21599 	auto display = XDisplayConnection.get;
21600 
21601 	auto actionAtom = dndActionAtom(display, action);
21602 	assert(actionAtom, "Don't use action none to accept a drop");
21603 
21604 	setX11Selection!"XdndSelection"(window, handler, null);
21605 
21606 	auto oldKeyHandler = window.handleKeyEvent;
21607 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21608 
21609 	auto oldCharHandler = window.handleCharEvent;
21610 	scope(exit) window.handleCharEvent = oldCharHandler;
21611 
21612 	auto oldMouseHandler = window.handleMouseEvent;
21613 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21614 
21615 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21616 
21617 	import core.sys.posix.sys.time;
21618 	timeval tv;
21619 	gettimeofday(&tv, null);
21620 
21621 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21622 
21623 	Time lastMouseTimestamp;
21624 
21625 	bool dnding = true;
21626 	Window lastIn = None;
21627 
21628 	void leave() {
21629 		if(lastIn == None)
21630 			return;
21631 
21632 		XEvent ev;
21633 		ev.xclient.type = EventType.ClientMessage;
21634 		ev.xclient.window = lastIn;
21635 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21636 		ev.xclient.format = 32;
21637 		ev.xclient.data.l[0] = window.impl.window;
21638 
21639 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21640 		XFlush(display);
21641 
21642 		lastIn = None;
21643 	}
21644 
21645 	void enter(Window w) {
21646 		assert(lastIn == None);
21647 
21648 		lastIn = w;
21649 
21650 		XEvent ev;
21651 		ev.xclient.type = EventType.ClientMessage;
21652 		ev.xclient.window = lastIn;
21653 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21654 		ev.xclient.format = 32;
21655 		ev.xclient.data.l[0] = window.impl.window;
21656 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21657 
21658 		auto types = handler.availableFormats();
21659 		assert(types.length > 0);
21660 
21661 		ev.xclient.data.l[2] = types[0];
21662 		if(types.length > 1)
21663 			ev.xclient.data.l[3] = types[1];
21664 		if(types.length > 2)
21665 			ev.xclient.data.l[4] = types[2];
21666 
21667 		// FIXME: other types?!?!? and make sure we skip TARGETS
21668 
21669 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21670 		XFlush(display);
21671 	}
21672 
21673 	void position(int rootX, int rootY) {
21674 		assert(lastIn != None);
21675 
21676 		XEvent ev;
21677 		ev.xclient.type = EventType.ClientMessage;
21678 		ev.xclient.window = lastIn;
21679 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21680 		ev.xclient.format = 32;
21681 		ev.xclient.data.l[0] = window.impl.window;
21682 		ev.xclient.data.l[1] = 0; // reserved
21683 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21684 		ev.xclient.data.l[3] = dataTimestamp;
21685 		ev.xclient.data.l[4] = actionAtom;
21686 
21687 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21688 		XFlush(display);
21689 
21690 	}
21691 
21692 	void drop() {
21693 		XEvent ev;
21694 		ev.xclient.type = EventType.ClientMessage;
21695 		ev.xclient.window = lastIn;
21696 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21697 		ev.xclient.format = 32;
21698 		ev.xclient.data.l[0] = window.impl.window;
21699 		ev.xclient.data.l[1] = 0; // reserved
21700 		ev.xclient.data.l[2] = dataTimestamp;
21701 
21702 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21703 		XFlush(display);
21704 
21705 		lastIn = None;
21706 		dnding = false;
21707 	}
21708 
21709 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21710 	// but idk if i should...
21711 
21712 	window.setEventHandlers(
21713 		delegate(KeyEvent ev) {
21714 			if(ev.pressed == true && ev.key == Key.Escape) {
21715 				// cancel
21716 				dnding = false;
21717 			}
21718 		},
21719 		delegate(MouseEvent ev) {
21720 			if(ev.timestamp < lastMouseTimestamp)
21721 				return;
21722 
21723 			lastMouseTimestamp = ev.timestamp;
21724 
21725 			if(ev.type == MouseEventType.motion) {
21726 				auto display = XDisplayConnection.get;
21727 				auto root = RootWindow(display, DefaultScreen(display));
21728 
21729 				Window topWindow;
21730 				int rootX, rootY;
21731 
21732 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21733 
21734 				if(topWindow == None)
21735 					return;
21736 
21737 				top:
21738 				if(auto result = topWindow in eligibility) {
21739 					auto dropWindow = *result;
21740 					if(dropWindow == None) {
21741 						leave();
21742 						return;
21743 					}
21744 
21745 					if(dropWindow != lastIn) {
21746 						leave();
21747 						enter(dropWindow);
21748 						position(rootX, rootY);
21749 					} else {
21750 						position(rootX, rootY);
21751 					}
21752 				} else {
21753 					// determine eligibility
21754 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21755 					if(data.length == 1) {
21756 						// in case there is no WM or it isn't reparenting
21757 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21758 					} else {
21759 
21760 						Window tryScanChildren(Window search, int maxRecurse) {
21761 							// could be reparenting window manager, so gotta check the next few children too
21762 							Window child;
21763 							int x;
21764 							int y;
21765 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21766 
21767 							if(child == None)
21768 								return None;
21769 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21770 							if(data.length == 1) {
21771 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21772 							} else {
21773 								if(maxRecurse)
21774 									return tryScanChildren(child, maxRecurse - 1);
21775 								else
21776 									return None;
21777 							}
21778 
21779 						}
21780 
21781 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21782 						auto topResult = tryScanChildren(topWindow, 3);
21783 						// it is easy to have a false negative due to the mouse going over a WM
21784 						// child window like the close button if separate from the frame... so I
21785 						// can't really cache negatives, :(
21786 						if(topResult != None) {
21787 							eligibility[topWindow] = topResult;
21788 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21789 						}
21790 					}
21791 
21792 				}
21793 
21794 			} else if(ev.type == MouseEventType.buttonReleased) {
21795 				drop();
21796 				dnding = false;
21797 			}
21798 		}
21799 	);
21800 
21801 	window.grabInput();
21802 	scope(exit)
21803 		window.releaseInputGrab();
21804 
21805 
21806 	EventLoop.get.run(() => dnding);
21807 
21808 	return 0;
21809 }
21810 
21811 /// X-specific
21812 TrueColorImage getWindowNetWmIcon(Window window) {
21813 	try {
21814 		auto display = XDisplayConnection.get;
21815 
21816 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21817 
21818 		if (data.length > arch_ulong.sizeof * 2) {
21819 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21820 			// these are an array of rgba images that we have to convert into pixmaps ourself
21821 
21822 			int width = cast(int) meta[0];
21823 			int height = cast(int) meta[1];
21824 
21825 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21826 
21827 			static if(arch_ulong.sizeof == 4) {
21828 				bytes = bytes[0 .. width * height * 4];
21829 				alias imageData = bytes;
21830 			} else static if(arch_ulong.sizeof == 8) {
21831 				bytes = bytes[0 .. width * height * 8];
21832 				auto imageData = new ubyte[](4 * width * height);
21833 			} else static assert(0);
21834 
21835 
21836 
21837 			// this returns ARGB. Remember it is little-endian so
21838 			//                                         we have BGRA
21839 			// our thing uses RGBA, which in little endian, is ABGR
21840 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21841 				auto r = bytes[idx + 2];
21842 				auto g = bytes[idx + 1];
21843 				auto b = bytes[idx + 0];
21844 				auto a = bytes[idx + 3];
21845 
21846 				imageData[idx2 + 0] = r;
21847 				imageData[idx2 + 1] = g;
21848 				imageData[idx2 + 2] = b;
21849 				imageData[idx2 + 3] = a;
21850 			}
21851 
21852 			return new TrueColorImage(width, height, imageData);
21853 		}
21854 
21855 		return null;
21856 	} catch(Exception e) {
21857 		return null;
21858 	}
21859 }
21860 
21861 } /* UsingSimpledisplayX11 */
21862 
21863 
21864 void loadBinNameToWindowClassName () {
21865 	import core.stdc.stdlib : realloc;
21866 	version(linux) {
21867 		// args[0] MAY be empty, so we'll just use this
21868 		import core.sys.posix.unistd : readlink;
21869 		char[1024] ebuf = void; // 1KB should be enough for everyone!
21870 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
21871 		if (len < 1) return;
21872 	} else /*version(Windows)*/ {
21873 		import core.runtime : Runtime;
21874 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
21875 		auto ebuf = Runtime.args[0];
21876 		auto len = ebuf.length;
21877 	}
21878 	auto pos = len;
21879 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
21880 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
21881 	if (sdpyWindowClassStr is null) return; // oops
21882 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
21883 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
21884 }
21885 
21886 /++
21887 	An interface representing a font that is drawn with custom facilities.
21888 
21889 	You might want [OperatingSystemFont] instead, which represents
21890 	a font loaded and drawn by functions native to the operating system.
21891 
21892 	WARNING: I might still change this.
21893 +/
21894 interface DrawableFont : MeasurableFont {
21895 	/++
21896 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
21897 
21898 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
21899 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
21900 		fill color, but that's up to the implementation.
21901 	+/
21902 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
21903 
21904 	/++
21905 		Requests that the given string is added to the image cache. You should only do this rarely, but
21906 		if you have a string that you know will be used over and over again, adding it to a cache can
21907 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
21908 		to implement this as a do-nothing method).
21909 	+/
21910 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
21911 }
21912 
21913 /++
21914 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
21915 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
21916 
21917 	You should also consider [OperatingSystemFont], which loads and draws a font with
21918 	facilities native to the user's operating system. You might also consider
21919 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
21920 	of game, as they have their own ways to draw text too.
21921 
21922 	Be warned: this can be slow, especially on remote connections to the X server, since
21923 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
21924 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
21925 	experiment in your specific case.
21926 
21927 	Please note that the return type of [DrawableFont] also includes an implementation of
21928 	[MeasurableFont].
21929 +/
21930 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
21931 	import arsd.ttf;
21932 	static class ArsdTtfFont : DrawableFont {
21933 		TtfFont font;
21934 		int size;
21935 		this(in ubyte[] data, int size) {
21936 			font = TtfFont(data);
21937 			this.size = size;
21938 
21939 
21940 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
21941 			int ascent_, descent_, line_gap;
21942 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
21943 
21944 			int advance, lsb;
21945 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
21946 			xWidth = cast(int) (advance * scale);
21947 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
21948 			MWidth = cast(int) (advance * scale);
21949 		}
21950 
21951 		private int ascent_;
21952 		private int descent_;
21953 		private int xWidth;
21954 		private int MWidth;
21955 
21956 		bool isMonospace() {
21957 			return xWidth == MWidth;
21958 		}
21959 		int averageWidth() {
21960 			return xWidth;
21961 		}
21962 		int height() {
21963 			return size;
21964 		}
21965 		int ascent() {
21966 			return ascent_;
21967 		}
21968 		int descent() {
21969 			return descent_;
21970 		}
21971 
21972 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
21973 			int width, height;
21974 			font.getStringSize(s, size, width, height);
21975 			return width;
21976 		}
21977 
21978 
21979 
21980 		Sprite[string] cache;
21981 
21982 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
21983 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
21984 			cache[text] = sprite;
21985 		}
21986 
21987 		Image stringToImage(Color fg, Color bg, in char[] text) {
21988 			int width, height;
21989 			auto data = font.renderString(text, size, width, height);
21990 			auto image = new TrueColorImage(width, height);
21991 			int pos = 0;
21992 			foreach(y; 0 .. height)
21993 			foreach(x; 0 .. width) {
21994 				fg.a = data[0];
21995 				bg.a = 255;
21996 				auto color = alphaBlend(fg, bg);
21997 				image.imageData.bytes[pos++] = color.r;
21998 				image.imageData.bytes[pos++] = color.g;
21999 				image.imageData.bytes[pos++] = color.b;
22000 				image.imageData.bytes[pos++] = data[0];
22001 				data = data[1 .. $];
22002 			}
22003 			assert(data.length == 0);
22004 
22005 			return Image.fromMemoryImage(image);
22006 		}
22007 
22008 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22009 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22010 
22011 			auto fg = painter.impl._outlineColor;
22012 			auto bg = painter.impl._fillColor;
22013 
22014 			if(sprite !is null) {
22015 				auto w = cast(SimpleWindow) painter.window;
22016 				assert(w !is null);
22017 
22018 				sprite.drawAt(painter, upperLeft);
22019 			} else {
22020 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
22021 			}
22022 		}
22023 	}
22024 
22025 	return new ArsdTtfFont(data, size);
22026 }
22027 
22028 class NotYetImplementedException : Exception {
22029 	this(string file = __FILE__, size_t line = __LINE__) {
22030 		super("Not yet implemented", file, line);
22031 	}
22032 }
22033 
22034 ///
22035 __gshared bool librariesSuccessfullyLoaded = true;
22036 ///
22037 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
22038 
22039 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
22040 	mixin(staticForeachReplacement!Iface);
22041 
22042 	void loadDynamicLibrary() @nogc {
22043 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22044 	}
22045 
22046         void loadDynamicLibraryForReal() {
22047                 foreach(name; __traits(derivedMembers, Iface)) {
22048                         mixin("alias tmp = " ~ name ~ ";");
22049                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
22050                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
22051                 }
22052         }
22053 }
22054 
22055 private const(char)[] staticForeachReplacement(Iface)() pure {
22056 /*
22057 	// just this for gdc 9....
22058 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
22059 
22060         static foreach(name; __traits(derivedMembers, Iface))
22061                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22062 */
22063 
22064 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
22065 	size_t pos;
22066 
22067 	void append(in char[] what) {
22068 		if(pos + what.length > code.length)
22069 			code.length = (code.length * 3) / 2;
22070 		code[pos .. pos + what.length] = what[];
22071 		pos += what.length;
22072 	}
22073 
22074         foreach(name; __traits(derivedMembers, Iface)) {
22075                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
22076 		append(name);
22077 		append(`")) `);
22078 		append(name);
22079 		append(";");
22080 	}
22081 
22082 	return code[0 .. pos];
22083 }
22084 
22085 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22086 	mixin(staticForeachReplacement!Iface);
22087 
22088 	private __gshared void* libHandle;
22089 	private __gshared bool attempted;
22090 
22091         void loadDynamicLibrary() @nogc {
22092 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22093 	}
22094 
22095 	bool loadAttempted() {
22096 		return attempted;
22097 	}
22098 	bool loadSuccessful() {
22099 		return libHandle !is null;
22100 	}
22101 
22102         void loadDynamicLibraryForReal() {
22103 		attempted = true;
22104                 version(Posix) {
22105                         import core.sys.posix.dlfcn;
22106 			version(OSX) {
22107 				version(X11)
22108                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22109 				else
22110                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22111 			} else {
22112                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22113 				if(libHandle is null)
22114                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22115 			}
22116 
22117 			static void* loadsym(void* l, const char* name) {
22118 				import core.stdc.stdlib;
22119 				if(l is null)
22120 					return &abort;
22121 				return dlsym(l, name);
22122 			}
22123                 } else version(Windows) {
22124                         import core.sys.windows.winbase;
22125                         libHandle = LoadLibrary(library ~ ".dll");
22126 			static void* loadsym(void* l, const char* name) {
22127 				import core.stdc.stdlib;
22128 				if(l is null)
22129 					return &abort;
22130 				return GetProcAddress(l, name);
22131 			}
22132                 }
22133                 if(libHandle is null) {
22134 			success = false;
22135                         //throw new Exception("load failure of library " ~ library);
22136 		}
22137                 foreach(name; __traits(derivedMembers, Iface)) {
22138                         mixin("alias tmp = " ~ name ~ ";");
22139                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22140                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22141                 }
22142         }
22143 
22144         void unloadDynamicLibrary() {
22145                 version(Posix) {
22146                         import core.sys.posix.dlfcn;
22147                         dlclose(libHandle);
22148                 } else version(Windows) {
22149                         import core.sys.windows.winbase;
22150                         FreeLibrary(libHandle);
22151                 }
22152                 foreach(name; __traits(derivedMembers, Iface))
22153                         mixin(name ~ " = null;");
22154         }
22155 }
22156 
22157 /+
22158 	The GC can be called from any thread, and a lot of cleanup must be done
22159 	on the gui thread. Since the GC can interrupt any locks - including being
22160 	triggered inside a critical section - it is vital to avoid deadlocks to get
22161 	these functions called from the right place.
22162 
22163 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22164 	right now.
22165 
22166 	The cleanup function is run when the event loop gets around to it, which is just
22167 	whenever there's something there after it has been woken up for other work. It does
22168 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22169 	(Well actually it might be ok but i don't wanna mess with it right now.)
22170 +/
22171 private struct CleanupQueue {
22172 	import core.stdc.stdlib;
22173 
22174 	void queue(alias func, T...)(T args) {
22175 		static struct Args {
22176 			T args;
22177 		}
22178 		static struct RealJob {
22179 			Job j;
22180 			Args a;
22181 		}
22182 		static void call(Job* data) {
22183 			auto rj = cast(RealJob*) data;
22184 			func(rj.a.args);
22185 		}
22186 
22187 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22188 		thing.j.call = &call;
22189 		thing.a.args = args;
22190 
22191 		buffer[tail++] = cast(Job*) thing;
22192 
22193 		// FIXME: set overflowed
22194 	}
22195 
22196 	void process() {
22197 		const tail = this.tail;
22198 
22199 		while(tail != head) {
22200 			Job* job = cast(Job*) buffer[head++];
22201 			job.call(job);
22202 			free(job);
22203 		}
22204 
22205 		if(overflowed)
22206 			throw new Exception("cleanup overflowed");
22207 	}
22208 
22209 	private:
22210 
22211 	ubyte tail; // must ONLY be written by queue
22212 	ubyte head; // must ONLY be written by process
22213 	bool overflowed;
22214 
22215 	static struct Job {
22216 		void function(Job*) call;
22217 	}
22218 
22219 	void*[256] buffer;
22220 }
22221 private __gshared CleanupQueue cleanupQueue;
22222 
22223 version(X11)
22224 /++
22225 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22226 
22227 	$(WARNING
22228 		This function is exempted from stability guarantees.
22229 	)
22230 +/
22231 float customScalingFactorForMonitor(int monitorNumber) {
22232 	import core.stdc.stdlib;
22233 	auto val = getenv("ARSD_SCALING_FACTOR");
22234 
22235 	if(val is null)
22236 		return 1.0;
22237 
22238 	char[16] buffer = 0;
22239 	int pos;
22240 
22241 	const(char)* at = val;
22242 
22243 	foreach(item; 0 .. monitorNumber + 1) {
22244 		if(*at == 0)
22245 			break; // reuse the last number when we at the end of the string
22246 		pos = 0;
22247 		while(pos + 1 < buffer.length && *at && *at != ';') {
22248 			buffer[pos++] = *at;
22249 			at++;
22250 		}
22251 		if(*at)
22252 			at++; // skip the semicolon
22253 		buffer[pos] = 0;
22254 	}
22255 
22256 	//sdpyPrintDebugString(buffer[0 .. pos]);
22257 
22258 	import core.stdc.math;
22259 	auto f = atof(buffer.ptr);
22260 
22261 	if(f <= 0.0 || isnan(f) || isinf(f))
22262 		return 1.0;
22263 
22264 	return f;
22265 }
22266 
22267 void guiAbortProcess(string msg) {
22268 	import core.stdc.stdlib;
22269 	version(Windows) {
22270 		WCharzBuffer t = WCharzBuffer(msg);
22271 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22272 	} else {
22273 		import core.stdc.stdio;
22274 		fwrite(msg.ptr, 1, msg.length, stderr);
22275 		msg = "\n";
22276 		fwrite(msg.ptr, 1, msg.length, stderr);
22277 		fflush(stderr);
22278 	}
22279 
22280 	abort();
22281 }
22282 
22283 private int minInternal(int a, int b) {
22284 	return (a < b) ? a : b;
22285 }
22286 
22287 private alias scriptable = arsd_jsvar_compatible;