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 	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.
156 
157 	On Ubuntu, you might need to install X11 development libraries to
158 	successfully link.
159 
160 	$(CONSOLE
161 		$ sudo apt-get install libglc-dev
162 		$ sudo apt-get install libx11-dev
163 	)
164 
165 
166 	Jump_list:
167 
168 	Don't worry, you don't have to read this whole documentation file!
169 
170 	Check out the [#event-example] and [#Pong-example] to get started quickly.
171 
172 	The main classes you may want to create are [SimpleWindow], [Timer],
173 	[Image], and [Sprite].
174 
175 	The main functions you'll want are [setClipboardText] and [getClipboardText].
176 
177 	There are also platform-specific functions available such as [XDisplayConnection]
178 	and [GetAtom] for X11, among others.
179 
180 	See the examples and topics list below to learn more.
181 
182 	$(WARNING
183 		There should only be one GUI thread per application,
184 		and all windows should be created in it and your
185 		event loop should run there.
186 
187 		To do otherwise is undefined behavior and has no
188 		cross platform guarantees.
189 	)
190 
191 	$(H2 About this documentation)
192 
193 	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.
194 
195 	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!
196 
197 	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.
198 
199 	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.
200 
201 	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.
202 
203 	At points, I will talk about implementation details in the documentation. These are sometimes
204 	subject to change, but nevertheless useful to understand what is really going on. You can learn
205 	more about some of the referenced things by searching the web for info about using them from C.
206 	You can always look at the source of simpledisplay.d too for the most authoritative source on
207 	its specific implementation. If you disagree with how I did something, please contact me so we
208 	can discuss it!
209 
210 	$(H2 Using with fibers)
211 
212 	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).
213 
214 	$(H2 Topics)
215 
216 	$(H3 $(ID topic-windows) Windows)
217 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
218 		window on the user's screen.
219 
220 		You may create multiple windows, if the underlying platform supports it. You may check
221 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
222 		SimpleWindow's constructor at runtime to handle those cases.
223 
224 		A single running event loop will handle as many windows as needed.
225 
226 	$(H3 $(ID topic-event-loops) Event loops)
227 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
228 
229 		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:
230 
231 		---
232 		// dmd example.d simpledisplay.d color.d
233 		import arsd.simpledisplay;
234 		void main() {
235 			auto window = new SimpleWindow(200, 200);
236 			window.eventLoop(0,
237 			  delegate (dchar) { /* got a character key press */ }
238 			);
239 		}
240 		---
241 
242 		$(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.)
243 
244 		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.
245 
246 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
247 
248 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
249 
250 		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.
251 
252 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
253 		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.
254 
255 		See the [NotificationAreaIcon] class.
256 
257 	$(H3 $(ID topic-input-handling) Input handling)
258 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
259 
260 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
261 
262 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
263 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
264 
265 		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:
266 
267 		---
268 		// dmd example.d simpledisplay.d color.d
269 		import arsd.simpledisplay;
270 		void main() {
271 			auto window = new SimpleWindow(200, 200);
272 			{ // introduce sub-scope
273 				auto painter = window.draw(); // begin drawing
274 				/* draw here */
275 				painter.outlineColor = Color.red;
276 				painter.fillColor = Color.black;
277 				painter.drawRectangle(Point(0, 0), 200, 200);
278 			} // end scope, calling `painter`'s destructor, drawing to the screen.
279 			window.eventLoop(0); // handle events
280 		}
281 		---
282 
283 		Painting is done based on two color properties, a pen and a brush.
284 
285 		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.
286 
287 		FIXME Add example of 2d opengl drawing here.
288 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
289 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
290 
291 		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.
292 
293 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
294 
295 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
296 
297 		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].
298 
299 		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.
300 
301 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
302 
303 		---
304 		import arsd.simpledisplay;
305 
306 		void main() {
307 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
308 
309 			float otherColor = 0.0;
310 			float colorDelta = 0.05;
311 
312 			window.redrawOpenGlScene = delegate() {
313 				glLoadIdentity();
314 				glBegin(GL_QUADS);
315 
316 				glColor3f(1.0, otherColor, 0);
317 				glVertex3f(-0.8, -0.8, 0);
318 
319 				glColor3f(1.0, otherColor, 1.0);
320 				glVertex3f(0.8, -0.8, 0);
321 
322 				glColor3f(0, 1.0, otherColor);
323 				glVertex3f(0.8, 0.8, 0);
324 
325 				glColor3f(otherColor, 0, 1.0);
326 				glVertex3f(-0.8, 0.8, 0);
327 
328 				glEnd();
329 			};
330 
331 			window.eventLoop(50, () {
332 				otherColor += colorDelta;
333 				if(otherColor > 1.0) {
334 					otherColor = 1.0;
335 					colorDelta = -0.05;
336 				}
337 				if(otherColor < 0) {
338 					otherColor = 0;
339 					colorDelta = 0.05;
340 				}
341 				// at the end of the timer, we have to request a redraw
342 				// or we won't see the changes.
343 				window.redrawOpenGlSceneSoon();
344 			});
345 		}
346 		---
347 
348 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
349 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
350 		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.
351 
352 		This example program shows how you can set up a shader to draw a rectangle:
353 
354 		---
355 		module opengl3test;
356 		import arsd.simpledisplay;
357 
358 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
359 
360 		void main() {
361 			// First thing we do, before creating the window, is declare what version we want.
362 			setOpenGLContextVersion(3, 3);
363 			// turning off legacy compat is required to use version 3.3 and newer
364 			openGLContextCompatible = false;
365 
366 			uint VAO;
367 			OpenGlShader shader;
368 
369 			// then we can create the window.
370 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
371 
372 			// additional setup needs to be done when it is visible, simpledisplay offers a property
373 			// for exactly that:
374 			window.visibleForTheFirstTime = delegate() {
375 				// now with the window loaded, we can start loading the modern opengl functions.
376 
377 				// you MUST set the context first.
378 				window.setAsCurrentOpenGlContext;
379 				// then load the remainder of the library
380 				gl3.loadDynamicLibrary();
381 
382 				// now you can create the shaders, etc.
383 				shader = new OpenGlShader(
384 					OpenGlShader.Source(GL_VERTEX_SHADER, `
385 						#version 330 core
386 						layout (location = 0) in vec3 aPos;
387 						void main() {
388 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
389 						}
390 					`),
391 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
392 						#version 330 core
393 						out vec4 FragColor;
394 						uniform vec4 mycolor;
395 						void main() {
396 							FragColor = mycolor;
397 						}
398 					`),
399 				);
400 
401 				// and do whatever other setup you want.
402 
403 				float[] vertices = [
404 					0.5f,  0.5f, 0.0f,  // top right
405 					0.5f, -0.5f, 0.0f,  // bottom right
406 					-0.5f, -0.5f, 0.0f,  // bottom left
407 					-0.5f,  0.5f, 0.0f   // top left 
408 				];
409 				uint[] indices = [  // note that we start from 0!
410 					0, 1, 3,  // first Triangle
411 					1, 2, 3   // second Triangle
412 				];
413 				uint VBO, EBO;
414 				glGenVertexArrays(1, &VAO);
415 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
416 				glBindVertexArray(VAO);
417 
418 				glGenBuffers(1, &VBO);
419 				glGenBuffers(1, &EBO);
420 
421 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
422 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
423 
424 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
425 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
426 
427 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
428 				glEnableVertexAttribArray(0);
429 
430 				// the library will set the initial viewport and trigger our first draw,
431 				// so these next two lines are NOT needed. they are just here as comments
432 				// to show what would happen next.
433 
434 				// glViewport(0, 0, window.width, window.height);
435 				// window.redrawOpenGlSceneNow();
436 			};
437 
438 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
439 			// it is our render method.
440 			window.redrawOpenGlScene = delegate() {
441 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
442 				glClear(GL_COLOR_BUFFER_BIT);
443 
444 				glUseProgram(shader.shaderProgram);
445 
446 				// the shader helper class has methods to set uniforms too
447 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
448 
449 				glBindVertexArray(VAO);
450 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
451 			};
452 
453 			window.eventLoop(0);
454 		}
455 		---
456 
457 	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.
458 
459 
460 	$(H3 $(ID topic-images) Displaying images)
461 		You can also load PNG images using [arsd.png].
462 
463 		---
464 		// dmd example.d simpledisplay.d color.d png.d
465 		import arsd.simpledisplay;
466 		import arsd.png;
467 
468 		void main() {
469 			auto image = Image.fromMemoryImage(readPng("image.png"));
470 			displayImage(image);
471 		}
472 		---
473 
474 		Compile with `dmd example.d simpledisplay.d png.d`.
475 
476 		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.
477 
478 	$(H3 $(ID topic-sprites) Sprites)
479 		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.
480 
481 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
482 
483 	$(H3 $(ID topic-clipboard) Clipboard)
484 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
485 
486 		It also has helpers for handling X-specific events.
487 
488 	$(H3 $(ID topic-dnd) Drag and Drop)
489 		See [enableDragAndDrop] and [draggable].
490 
491 	$(H3 $(ID topic-timers) Timers)
492 		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].
493 
494 		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.
495 
496 		---
497 			import arsd.simpledisplay;
498 
499 			void main() {
500 				auto window = new SimpleWindow(400, 400);
501 				// every 100 ms, it will draw a random line
502 				// on the window.
503 				window.eventLoop(100, {
504 					auto painter = window.draw();
505 
506 					import std.random;
507 					// random color
508 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
509 					// random line
510 					painter.drawLine(
511 						Point(uniform(0, window.width), uniform(0, window.height)),
512 						Point(uniform(0, window.width), uniform(0, window.height)));
513 
514 				});
515 			}
516 		---
517 
518 		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.
519 
520 		The pulse timer and instances of the [Timer] class may be combined at will.
521 
522 		---
523 			import arsd.simpledisplay;
524 
525 			void main() {
526 				auto window = new SimpleWindow(400, 400);
527 				auto timer = new Timer(1000, delegate {
528 					auto painter = window.draw();
529 					painter.clear();
530 				});
531 
532 				window.eventLoop(0);
533 			}
534 		---
535 
536 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
537 
538 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
539 		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.
540 
541 		See also: `xwindows.d` from my github.
542 
543 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
544 		`handleNativeEvent` and `handleNativeGlobalEvent`.
545 
546 	$(H3 $(ID topic-integration) Integration with other libraries)
547 		Integration with a third-party event loop is possible.
548 
549 		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.
550 
551 	$(H3 $(ID topic-guis) GUI widgets)
552 		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!
553 
554 		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.
555 
556 		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.)
557 
558 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
559 
560 	$(H2 Platform-specific tips and tricks)
561 
562 	X_tips:
563 
564 	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.
565 
566 	Windows_tips:
567 
568 	You can add icons or manifest files to your exe using a resource file.
569 
570 	To create a Windows .ico file, use the gimp or something. I'll write a helper
571 	program later.
572 
573 	Create `yourapp.rc`:
574 
575 	```rc
576 		1 ICON filename.ico
577 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
578 	```
579 
580 	And `yourapp.exe.manifest`:
581 
582 	```xml
583 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
584 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
585 		<assemblyIdentity
586 		    version="1.0.0.0"
587 		    processorArchitecture="*"
588 		    name="CompanyName.ProductName.YourApplication"
589 		    type="win32"
590 		/>
591 		<description>Your application description here.</description>
592 		<dependency>
593 		    <dependentAssembly>
594 			<assemblyIdentity
595 			    type="win32"
596 			    name="Microsoft.Windows.Common-Controls"
597 			    version="6.0.0.0"
598 			    processorArchitecture="*"
599 			    publicKeyToken="6595b64144ccf1df"
600 			    language="*"
601 			/>
602 		    </dependentAssembly>
603 		</dependency>
604 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
605 			<windowsSettings>
606 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
607 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
608 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
609 				<!-- to render crisply in DPI-unaware contexts --> 
610 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
611 			</windowsSettings>
612 		</application>
613 		</assembly>
614 	```
615 
616 	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`.
617 
618 	Doing this lets you opt into various new things since Windows XP.
619 
620 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
621 
622 	$(H2 Tips)
623 
624 	$(H3 Name conflicts)
625 
626 	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:
627 
628 	---
629 	static import sdpy = arsd.simpledisplay;
630 	import arsd.simpledisplay : SimpleWindow;
631 
632 	void main() {
633 		auto window = new SimpleWindow();
634 		sdpy.EventLoop.get.run();
635 	}
636 	---
637 
638 	$(H2 $(ID developer-notes) Developer notes)
639 
640 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
641 	implementation though.
642 
643 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
644 	suck. If I was rewriting it, I wouldn't do it that way again.
645 
646 	This file must not have any more required dependencies. If you need bindings, add
647 	them right to this file. Once it gets into druntime and is there for a while, remove
648 	bindings from here to avoid conflicts (or put them in an appropriate version block
649 	so it continues to just work on old dmd), but wait a couple releases before making the
650 	transition so this module remains usable with older versions of dmd.
651 
652 	You may have optional dependencies if needed by putting them in version blocks or
653 	template functions. You may also extend the module with other modules with UFCS without
654 	actually editing this - that is nice to do if you can.
655 
656 	Try to make functions work the same way across operating systems. I typically make
657 	it thinly wrap Windows, then emulate that on Linux.
658 
659 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
660 	Phobos! So try to avoid it.
661 
662 	See more comments throughout the source.
663 
664 	I realize this file is fairly large, but over half that is just bindings at the bottom
665 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
666 	to understand. I suggest you jump around the source by looking for a particular
667 	declaration you're interested in, like `class SimpleWindow` using your editor's search
668 	function, then look at one piece at a time.
669 
670 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
671 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
672 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
673 
674 	I live in the eastern United States, so I will most likely not be around at night in
675 	that US east timezone.
676 
677 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
678 
679 	Building documentation: use my adrdox generator, `dub run adrdox`.
680 
681 	Examples:
682 
683 	$(DIV $(ID Event-example))
684 	$(H3 $(ID event-example) Event example)
685 	This program creates a window and draws events inside them as they
686 	happen, scrolling the text in the window as needed. Run this program
687 	and experiment to get a feel for where basic input events take place
688 	in the library.
689 
690 	---
691 	// dmd example.d simpledisplay.d color.d
692 	import arsd.simpledisplay;
693 	import std.conv;
694 
695 	void main() {
696 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
697 
698 		int y = 0;
699 
700 		void addLine(string text) {
701 			auto painter = window.draw();
702 
703 			if(y + painter.fontHeight >= window.height) {
704 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
705 				y -= painter.fontHeight;
706 			}
707 
708 			painter.outlineColor = Color.red;
709 			painter.fillColor = Color.black;
710 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
711 
712 			painter.outlineColor = Color.white;
713 
714 			painter.drawText(Point(10, y), text);
715 
716 			y += painter.fontHeight;
717 		}
718 
719 		window.eventLoop(1000,
720 		  () {
721 			addLine("Timer went off!");
722 		  },
723 		  (KeyEvent event) {
724 			addLine(to!string(event));
725 		  },
726 		  (MouseEvent event) {
727 			addLine(to!string(event));
728 		  },
729 		  (dchar ch) {
730 			addLine(to!string(ch));
731 		  }
732 		);
733 	}
734 	---
735 
736 	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.
737 
738 	$(COMMENT
739 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
740 
741 	---
742 
743 	---
744 	)
745 
746 
747 +/
748 module arsd.simpledisplay;
749 
750 // FIXME: tetris demo
751 // FIXME: space invaders demo
752 // FIXME: asteroids demo
753 
754 /++ $(ID Pong-example)
755 	$(H3 Pong)
756 
757 	This program creates a little Pong-like game. Player one is controlled
758 	with the keyboard.  Player two is controlled with the mouse. It demos
759 	the pulse timer, event handling, and some basic drawing.
760 +/
761 version(demos)
762 unittest {
763 	// dmd example.d simpledisplay.d color.d
764 	import arsd.simpledisplay;
765 
766 	enum paddleMovementSpeed = 8;
767 	enum paddleHeight = 48;
768 
769 	void main() {
770 		auto window = new SimpleWindow(600, 400, "Pong game!");
771 
772 		int playerOnePosition, playerTwoPosition;
773 		int playerOneMovement, playerTwoMovement;
774 		int playerOneScore, playerTwoScore;
775 
776 		int ballX, ballY;
777 		int ballDx, ballDy;
778 
779 		void serve() {
780 			import std.random;
781 
782 			ballX = window.width / 2;
783 			ballY = window.height / 2;
784 			ballDx = uniform(-4, 4) * 3;
785 			ballDy = uniform(-4, 4) * 3;
786 			if(ballDx == 0)
787 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
788 		}
789 
790 		serve();
791 
792 		window.eventLoop(50, // set a 50 ms timer pulls
793 			// This runs once per timer pulse
794 			delegate () {
795 				auto painter = window.draw();
796 
797 				painter.clear();
798 
799 				// Update everyone's motion
800 				playerOnePosition += playerOneMovement;
801 				playerTwoPosition += playerTwoMovement;
802 
803 				ballX += ballDx;
804 				ballY += ballDy;
805 
806 				// Bounce off the top and bottom edges of the window
807 				if(ballY + 7 >= window.height)
808 					ballDy = -ballDy;
809 				if(ballY - 8 <= 0)
810 					ballDy = -ballDy;
811 
812 				// Bounce off the paddle, if it is in position
813 				if(ballX - 8 <= 16) {
814 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
815 						ballDx = -ballDx + 1; // add some speed to keep it interesting
816 						ballDy += playerOneMovement; // and y movement based on your controls too
817 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
818 					} else {
819 						// Missed it
820 						playerTwoScore ++;
821 						serve();
822 					}
823 				}
824 
825 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
826 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
827 						ballDx = -ballDx - 1;
828 						ballDy += playerTwoMovement;
829 						ballX = window.width - 24;
830 					} else {
831 						// Missed it
832 						playerOneScore ++;
833 						serve();
834 					}
835 				}
836 
837 				// Draw the paddles
838 				painter.outlineColor = Color.black;
839 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
840 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
841 
842 				// Draw the ball
843 				painter.fillColor = Color.red;
844 				painter.outlineColor = Color.yellow;
845 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
846 
847 				// Draw the score
848 				painter.outlineColor = Color.blue;
849 				import std.conv;
850 				painter.drawText(Point(64, 4), to!string(playerOneScore));
851 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
852 
853 			},
854 			delegate (KeyEvent event) {
855 				// Player 1's controls are the arrow keys on the keyboard
856 				if(event.key == Key.Down)
857 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
858 				if(event.key == Key.Up)
859 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
860 
861 			},
862 			delegate (MouseEvent event) {
863 				// Player 2's controls are mouse movement while the left button is held down
864 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
865 					if(event.dy > 0)
866 						playerTwoMovement = paddleMovementSpeed;
867 					else if(event.dy < 0)
868 						playerTwoMovement = -paddleMovementSpeed;
869 				} else {
870 					playerTwoMovement = 0;
871 				}
872 			}
873 		);
874 	}
875 }
876 
877 /++ $(H3 $(ID example-minesweeper) Minesweeper)
878 
879 	This minesweeper demo shows how we can implement another classic
880 	game with simpledisplay and shows some mouse input and basic output
881 	code.
882 +/
883 version(demos)
884 unittest {
885 	import arsd.simpledisplay;
886 
887 	enum GameSquare {
888 		mine = 0,
889 		clear,
890 		m1, m2, m3, m4, m5, m6, m7, m8
891 	}
892 
893 	enum UserSquare {
894 		unknown,
895 		revealed,
896 		flagged,
897 		questioned
898 	}
899 
900 	enum GameState {
901 		inProgress,
902 		lose,
903 		win
904 	}
905 
906 	GameSquare[] board;
907 	UserSquare[] userState;
908 	GameState gameState;
909 	int boardWidth;
910 	int boardHeight;
911 
912 	bool isMine(int x, int y) {
913 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
914 			return false;
915 		return board[y * boardWidth + x] == GameSquare.mine;
916 	}
917 
918 	GameState reveal(int x, int y) {
919 		if(board[y * boardWidth + x] == GameSquare.clear) {
920 			floodFill(userState, boardWidth, boardHeight,
921 				UserSquare.unknown, UserSquare.revealed,
922 				x, y,
923 				(x, y) {
924 					if(board[y * boardWidth + x] == GameSquare.clear)
925 						return true;
926 					else {
927 						userState[y * boardWidth + x] = UserSquare.revealed;
928 						return false;
929 					}
930 				});
931 		} else {
932 			userState[y * boardWidth + x] = UserSquare.revealed;
933 			if(isMine(x, y))
934 				return GameState.lose;
935 		}
936 
937 		foreach(state; userState) {
938 			if(state == UserSquare.unknown || state == UserSquare.questioned)
939 				return GameState.inProgress;
940 		}
941 
942 		return GameState.win;
943 	}
944 
945 	void initializeBoard(int width, int height, int numberOfMines) {
946 		boardWidth = width;
947 		boardHeight = height;
948 		board.length = width * height;
949 
950 		userState.length = width * height;
951 		userState[] = UserSquare.unknown; 
952 
953 		import std.algorithm, std.random, std.range;
954 
955 		board[] = GameSquare.clear;
956 
957 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
958 			board[minePosition] = GameSquare.mine;
959 
960 		int x;
961 		int y;
962 		foreach(idx, ref square; board) {
963 			if(square == GameSquare.clear) {
964 				int danger = 0;
965 				danger += isMine(x-1, y-1)?1:0;
966 				danger += isMine(x-1, y)?1:0;
967 				danger += isMine(x-1, y+1)?1:0;
968 				danger += isMine(x, y-1)?1:0;
969 				danger += isMine(x, y+1)?1:0;
970 				danger += isMine(x+1, y-1)?1:0;
971 				danger += isMine(x+1, y)?1:0;
972 				danger += isMine(x+1, y+1)?1:0;
973 
974 				square = cast(GameSquare) (danger + 1);
975 			}
976 
977 			x++;
978 			if(x == width) {
979 				x = 0;
980 				y++;
981 			}
982 		}
983 	}
984 
985 	void redraw(SimpleWindow window) {
986 		import std.conv;
987 
988 		auto painter = window.draw();
989 
990 		painter.clear();
991 
992 		final switch(gameState) with(GameState) {
993 			case inProgress:
994 				break;
995 			case win:
996 				painter.fillColor = Color.green;
997 				painter.drawRectangle(Point(0, 0), window.width, window.height);
998 				return;
999 			case lose:
1000 				painter.fillColor = Color.red;
1001 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1002 				return;
1003 		}
1004 
1005 		int x = 0;
1006 		int y = 0;
1007 
1008 		foreach(idx, square; board) {
1009 			auto state = userState[idx];
1010 
1011 			final switch(state) with(UserSquare) {
1012 				case unknown:
1013 					painter.outlineColor = Color.black;
1014 					painter.fillColor = Color(128,128,128);
1015 
1016 					painter.drawRectangle(
1017 						Point(x * 20, y * 20),
1018 						20, 20
1019 					);
1020 				break;
1021 				case revealed:
1022 					if(square == GameSquare.clear) {
1023 						painter.outlineColor = Color.white;
1024 						painter.fillColor = Color.white;
1025 
1026 						painter.drawRectangle(
1027 							Point(x * 20, y * 20),
1028 							20, 20
1029 						);
1030 					} else {
1031 						painter.outlineColor = Color.black;
1032 						painter.fillColor = Color.white;
1033 
1034 						painter.drawText(
1035 							Point(x * 20, y * 20),
1036 							to!string(square)[1..2],
1037 							Point(x * 20 + 20, y * 20 + 20),
1038 							TextAlignment.Center | TextAlignment.VerticalCenter);
1039 					}
1040 				break;
1041 				case flagged:
1042 					painter.outlineColor = Color.black;
1043 					painter.fillColor = Color.red;
1044 					painter.drawRectangle(
1045 						Point(x * 20, y * 20),
1046 						20, 20
1047 					);
1048 				break;
1049 				case questioned:
1050 					painter.outlineColor = Color.black;
1051 					painter.fillColor = Color.yellow;
1052 					painter.drawRectangle(
1053 						Point(x * 20, y * 20),
1054 						20, 20
1055 					);
1056 				break;
1057 			}
1058 
1059 			x++;
1060 			if(x == boardWidth) {
1061 				x = 0;
1062 				y++;
1063 			}
1064 		}
1065 
1066 	}
1067 
1068 	void main() {
1069 		auto window = new SimpleWindow(200, 200);
1070 
1071 		initializeBoard(10, 10, 10);
1072 
1073 		redraw(window);
1074 		window.eventLoop(0,
1075 			delegate (MouseEvent me) {
1076 				if(me.type != MouseEventType.buttonPressed)
1077 					return;
1078 				auto x = me.x / 20;
1079 				auto y = me.y / 20;
1080 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1081 					if(me.button == MouseButton.left) {
1082 						gameState = reveal(x, y);
1083 					} else {
1084 						userState[y*boardWidth+x] = UserSquare.flagged;
1085 					}
1086 					redraw(window);
1087 				}
1088 			}
1089 		);
1090 	}
1091 }
1092 
1093 /*
1094 version(OSX) {
1095 	version=without_opengl;
1096 	version=allow_unimplemented_features;
1097 	version=OSXCocoa;
1098 	pragma(linkerDirective, "-framework Cocoa");
1099 }
1100 */
1101 
1102 version(without_opengl) {
1103 	enum SdpyIsUsingIVGLBinds = false;
1104 } else /*version(Posix)*/ {
1105 	static if (__traits(compiles, (){import iv.glbinds;})) {
1106 		enum SdpyIsUsingIVGLBinds = true;
1107 		public import iv.glbinds;
1108 		//pragma(msg, "SDPY: using iv.glbinds");
1109 	} else {
1110 		enum SdpyIsUsingIVGLBinds = false;
1111 	}
1112 //} else {
1113 //	enum SdpyIsUsingIVGLBinds = false;
1114 }
1115 
1116 
1117 version(Windows) {
1118 	//import core.sys.windows.windows;
1119 	import core.sys.windows.winnls;
1120 	import core.sys.windows.windef;
1121 	import core.sys.windows.basetyps;
1122 	import core.sys.windows.winbase;
1123 	import core.sys.windows.winuser;
1124 	import core.sys.windows.shellapi;
1125 	import core.sys.windows.wingdi;
1126 	static import gdi = core.sys.windows.wingdi; // so i
1127 
1128 	pragma(lib, "gdi32");
1129 	pragma(lib, "user32");
1130 
1131 	// for AlphaBlend... a breaking change....
1132 	version(CRuntime_DigitalMars) { } else
1133 		pragma(lib, "msimg32");
1134 } else version (linux) {
1135 	//k8: this is hack for rdmd. sorry.
1136 	static import core.sys.linux.epoll;
1137 	static import core.sys.linux.timerfd;
1138 }
1139 
1140 
1141 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1142 
1143 // http://wiki.dlang.org/Simpledisplay.d
1144 
1145 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1146 
1147 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1148 // but can i control the scroll lock led
1149 
1150 
1151 // Note: if you are using Image on X, you might want to do:
1152 /*
1153 	static if(UsingSimpledisplayX11) {
1154 		if(!Image.impl.xshmAvailable) {
1155 			// the images will use the slower XPutImage, you might
1156 			// want to consider an alternative method to get better speed
1157 		}
1158 	}
1159 
1160 	If the shared memory extension is available though, simpledisplay uses it
1161 	for a significant speed boost whenever you draw large Images.
1162 */
1163 
1164 // 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.
1165 
1166 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1167 
1168 /*
1169 	Biggest FIXME:
1170 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1171 
1172 		clean up opengl contexts when their windows close
1173 
1174 		fix resizing the bitmaps/pixmaps
1175 */
1176 
1177 // BTW on Windows:
1178 // -L/SUBSYSTEM:WINDOWS:5.0
1179 // to dmd will make a nice windows binary w/o a console if you want that.
1180 
1181 /*
1182 	Stuff to add:
1183 
1184 	use multibyte functions everywhere we can
1185 
1186 	OpenGL windows
1187 	more event stuff
1188 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1189 
1190 
1191 	resizeEvent
1192 		and make the windows non-resizable by default,
1193 		or perhaps stretched (if I can find something in X like StretchBlt)
1194 
1195 	take a screenshot function!
1196 
1197 	Pens and brushes?
1198 	Maybe a global event loop?
1199 
1200 	Mouse deltas
1201 	Key items
1202 */
1203 
1204 /*
1205 From MSDN:
1206 
1207 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1208 
1209 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.
1210 
1211 */
1212 
1213 version(linux) {
1214 	version = X11;
1215 	version(without_libnotify) {
1216 		// we cool
1217 	}
1218 	else
1219 		version = libnotify;
1220 }
1221 
1222 version(libnotify) {
1223 	pragma(lib, "dl");
1224 	import core.sys.posix.dlfcn;
1225 
1226 	void delegate()[int] libnotify_action_delegates;
1227 	int libnotify_action_delegates_count;
1228 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1229 		auto idx = cast(int) user_data;
1230 		if(auto dgptr = idx in libnotify_action_delegates) {
1231 			(*dgptr)();
1232 			libnotify_action_delegates.remove(idx);
1233 		}
1234 	}
1235 
1236 	struct C_DynamicLibrary {
1237 		void* handle;
1238 		this(string name) {
1239 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1240 			if(handle is null)
1241 				throw new Exception("dlopen");
1242 		}
1243 
1244 		void close() {
1245 			dlclose(handle);
1246 		}
1247 
1248 		~this() {
1249 			// close
1250 		}
1251 
1252 		// FIXME: this looks up by name every time.... 
1253 		template call(string func, Ret, Args...) {
1254 			extern(C) Ret function(Args) fptr;
1255 			typeof(fptr) call() {
1256 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1257 				return fptr;
1258 			}
1259 		}
1260 	}
1261 
1262 	C_DynamicLibrary* libnotify;
1263 }
1264 
1265 version(OSX) {
1266 	version(OSXCocoa) {}
1267 	else { version = X11; }
1268 }
1269 	//version = OSXCocoa; // this was written by KennyTM
1270 version(FreeBSD)
1271 	version = X11;
1272 version(Solaris)
1273 	version = X11;
1274 
1275 version(X11) {
1276 	version(without_xft) {}
1277 	else version=with_xft;
1278 }
1279 
1280 void featureNotImplemented()() {
1281 	version(allow_unimplemented_features)
1282 		throw new NotYetImplementedException();
1283 	else
1284 		static assert(0);
1285 }
1286 
1287 // these are so the static asserts don't trigger unless you want to
1288 // add support to it for an OS
1289 version(Windows)
1290 	version = with_timer;
1291 version(linux)
1292 	version = with_timer;
1293 
1294 version(with_timer)
1295 	enum bool SimpledisplayTimerAvailable = true;
1296 else
1297 	enum bool SimpledisplayTimerAvailable = false;
1298 
1299 /// 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.
1300 version(Windows)
1301 	enum bool UsingSimpledisplayWindows = true;
1302 else
1303 	enum bool UsingSimpledisplayWindows = false;
1304 
1305 /// 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.
1306 version(X11)
1307 	enum bool UsingSimpledisplayX11 = true;
1308 else
1309 	enum bool UsingSimpledisplayX11 = false;
1310 
1311 /// 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.
1312 version(OSXCocoa)
1313 	enum bool UsingSimpledisplayCocoa = true;
1314 else
1315 	enum bool UsingSimpledisplayCocoa = false;
1316 
1317 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1318 version(Windows)
1319 	enum multipleWindowsSupported = true;
1320 else version(X11)
1321 	enum multipleWindowsSupported = true;
1322 else version(OSXCocoa)
1323 	enum multipleWindowsSupported = true;
1324 else
1325 	static assert(0);
1326 
1327 version(without_opengl)
1328 	enum bool OpenGlEnabled = false;
1329 else
1330 	enum bool OpenGlEnabled = true;
1331 
1332 /++
1333 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1334 	If you mix this in above your `main` function, you no longer need to use the linker
1335 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1336 
1337 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1338 
1339 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1340 	stderr writeln. It will fail and throw an exception.
1341 
1342 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1343 
1344 	History:
1345 		Added November 24, 2021 (dub v10.4)
1346 +/
1347 mixin template EnableWindowsSubsystem() {
1348 	version(Windows)
1349 	version(CRuntime_Microsoft) {
1350 		pragma(linkerDirective, "/subsystem:windows");
1351 		version(LDC)
1352 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1353 		else
1354 			pragma(linkerDirective, "/entry:mainCRTStartup");
1355 	}
1356 }
1357 
1358 
1359 /++
1360 	After selecting a type from [WindowTypes], you may further customize
1361 	its behavior by setting one or more of these flags.
1362 
1363 
1364 	The different window types have different meanings of `normal`. If the
1365 	window type already is a good match for what you want to do, you should
1366 	just use [WindowFlags.normal], the default, which will do the right thing
1367 	for your users.
1368 
1369 	The window flags will not always be honored by the operating system
1370 	and window managers; they are hints, not commands.
1371 +/
1372 enum WindowFlags : int {
1373 	normal = 0, ///
1374 	skipTaskbar = 1, ///
1375 	alwaysOnTop = 2, ///
1376 	alwaysOnBottom = 4, ///
1377 	cannotBeActivated = 8, ///
1378 	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.
1379 	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.
1380 	/++
1381 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1382 		it is still a top-level window. This should NOT be set separately for most window types.
1383 
1384 		A transient window will not keep the application open if its main window closes.
1385 
1386 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1387 
1388 
1389 		From the ICCM:
1390 
1391 		$(BLOCKQUOTE
1392 			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. 
1393 
1394 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1395 		)
1396 
1397 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1398 
1399 		History:
1400 			Added February 23, 2021 but not yet stabilized.
1401 	+/
1402 	transient = 64,
1403 	/++
1404 		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.
1405 
1406 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1407 
1408 		History:
1409 			Added April 1, 2022
1410 	+/
1411 	managesChildWindowFocus = 128,
1412 
1413 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1414 }
1415 
1416 /++
1417 	When creating a window, you can pass a type to SimpleWindow's constructor,
1418 	then further customize the window by changing `WindowFlags`.
1419 
1420 
1421 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1422 	use. The others are there to build a foundation for a higher level GUI toolkit,
1423 	but are themselves not as high level as you might think from their names.
1424 
1425 	This list is based on the EMWH spec for X11.
1426 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1427 +/
1428 enum WindowTypes : int {
1429 	/// An ordinary application window.
1430 	normal,
1431 	/// 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.
1432 	undecorated,
1433 	/// 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.
1434 	eventOnly,
1435 	/// A drop down menu, such as from a menu bar
1436 	dropdownMenu,
1437 	/// A popup menu, such as from a right click
1438 	popupMenu,
1439 	/// A popup bubble notification
1440 	notification,
1441 	/*
1442 	menu, /// a tearable menu bar
1443 	splashScreen, /// a loading splash screen for your application
1444 	tooltip, /// A tiny window showing temporary help text or something.
1445 	comboBoxDropdown,
1446 	dialog,
1447 	toolbar
1448 	*/
1449 	/// a child nested inside the parent. You must pass a parent window to the ctor
1450 	nestedChild,
1451 }
1452 
1453 
1454 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1455 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1456 private __gshared char* sdpyWindowClassStr = null;
1457 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1458 
1459 /**
1460 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1461 	You may want to change context version if you want to use advanced shaders or
1462 	other modern OpenGL techinques. This setting doesn't affect already created
1463 	windows. You may use version 2.1 as your default, which should be supported
1464 	by any box since 2006, so seems to be a reasonable choice.
1465 
1466 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1467 	old context creation code without any version specified. This is the safest
1468 	way to init OpenGL, but it may not give you access to advanced features.
1469 
1470 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1471 */
1472 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1473 
1474 /**
1475 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1476 	pipeline functions, and without "compatible" mode you won't be able to use
1477 	your old non-shader-based code with such contexts. By default SimpleDisplay
1478 	creates compatible context, so you can gradually upgrade your OpenGL code if
1479 	you want to (or leave it as is, as it should "just work").
1480 */
1481 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1482 
1483 /**
1484 	Set to `true` to allow creating OpenGL context with lower version than requested
1485 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1486 	`openGLContextFallbackActivated()` will return `true`.
1487 	*/
1488 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1489 
1490 /**
1491 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1492 	*/
1493 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1494 
1495 
1496 /**
1497 	Set window class name for all following `new SimpleWindow()` calls.
1498 
1499 	WARNING! For Windows, you should set your class name before creating any
1500 	window, and NEVER change it after that!
1501 */
1502 void sdpyWindowClass (const(char)[] v) {
1503 	import core.stdc.stdlib : realloc;
1504 	if (v.length == 0) v = "SimpleDisplayWindow";
1505 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1506 	if (sdpyWindowClassStr is null) return; // oops
1507 	sdpyWindowClassStr[0..v.length+1] = 0;
1508 	sdpyWindowClassStr[0..v.length] = v[];
1509 }
1510 
1511 /**
1512 	Get current window class name.
1513 */
1514 string sdpyWindowClass () {
1515 	if (sdpyWindowClassStr is null) return null;
1516 	foreach (immutable idx; 0..size_t.max-1) {
1517 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1518 	}
1519 	return null;
1520 }
1521 
1522 /++
1523 	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.
1524 
1525 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1526 +/
1527 float[2] getDpi() {
1528 	float[2] dpi;
1529 	version(Windows) {
1530 		HDC screen = GetDC(null);
1531 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1532 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1533 	} else version(X11) {
1534 		auto display = XDisplayConnection.get;
1535 		auto screen = DefaultScreen(display);
1536 
1537 		void fallback() {
1538 			/+
1539 			// 25.4 millimeters in an inch...
1540 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1541 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1542 			+/
1543 
1544 			// the physical size isn't actually as important as the logical size since this is
1545 			// all about scaling really
1546 			dpi[0] = 96;
1547 			dpi[1] = 96;
1548 		}
1549 
1550 		auto xft = getXftDpi();
1551 		if(xft is float.init)
1552 			fallback();
1553 		else {
1554 			dpi[0] = xft;
1555 			dpi[1] = xft;
1556 		}
1557 	}
1558 
1559 	return dpi;
1560 }
1561 
1562 version(X11)
1563 float getXftDpi() {
1564 	auto display = XDisplayConnection.get;
1565 
1566 	char* resourceString = XResourceManagerString(display);
1567 	XrmInitialize();
1568 
1569 	if (resourceString) {
1570 		auto db = XrmGetStringDatabase(resourceString);
1571 		XrmValue value;
1572 		char* type;
1573 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1574 			if (value.addr) {
1575 				import core.stdc.stdlib;
1576 				return atof(cast(char*) value.addr);
1577 			}
1578 		}
1579 	}
1580 
1581 	return float.init;
1582 }
1583 
1584 /++
1585 	Implementation used by [SimpleWindow.takeScreenshot].
1586 
1587 	Params:
1588 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1589 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1590 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1591 		x = the x-offset of the image to capture, from the left.
1592 		y = the y-offset of the image to capture, from the top.
1593 
1594 	History:
1595 		Added on March 14, 2021
1596 
1597 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1598 		
1599 +/
1600 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1601 	TrueColorImage got;
1602 	version(X11) {
1603 		auto display = XDisplayConnection.get;
1604 		if(handle == 0)
1605 			handle = RootWindow(display, DefaultScreen(display));
1606 
1607 		if(width == 0 || height == 0) {
1608 			Window root;
1609 			int xpos, ypos;
1610 			uint widthret, heightret, borderret, depthret;
1611 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1612 
1613 			if(width == 0)
1614 				width = widthret;
1615 			if(height == 0)
1616 				height = heightret;
1617 		}
1618 
1619 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1620 
1621 		// https://github.com/adamdruppe/arsd/issues/98
1622 
1623 		auto i = new Image(image);
1624 		got = i.toTrueColorImage();
1625 
1626 		XDestroyImage(image);
1627 	} else version(Windows) {
1628 		auto hdc = GetDC(handle);
1629 		scope(exit) ReleaseDC(handle, hdc);
1630 
1631 		if(width == 0 || height == 0) {
1632 			BITMAP bmHeader;
1633 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1634 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1635 			if(width == 0)
1636 				width = bmHeader.bmWidth;
1637 			if(height == 0)
1638 				height = bmHeader.bmHeight;
1639 		}
1640 
1641 		auto i = new Image(width, height);
1642 		HDC hdcMem = CreateCompatibleDC(hdc);
1643 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1644 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1645 		SelectObject(hdcMem, hbmOld);
1646 		DeleteDC(hdcMem);
1647 
1648 		got = i.toTrueColorImage();
1649 	} else featureNotImplemented();
1650 
1651 	return got;
1652 }
1653 
1654 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1655 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1656 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1657 
1658 version(Windows)
1659 shared static this() {
1660 	auto lib = LoadLibrary("User32.dll");
1661 	if(lib is null)
1662 		return;
1663 	//scope(exit)
1664 		//FreeLibrary(lib);
1665 
1666 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1667 
1668 	if(SetProcessDpiAwarenessContext is null)
1669 		return;
1670 
1671 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1672 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1673 		//writeln(GetLastError());
1674 	}
1675 
1676 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1677 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1678 }
1679 
1680 /++
1681 	Blocking mode for event loop calls associated with a window instance.
1682 
1683 	History:
1684 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1685 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1686 		is, all would block until the application quit.
1687 
1688 		That behavior can still be achieved here with `untilApplicationQuits`,
1689 		or explicitly calling the top-level `EventLoop.get.run` function.
1690 +/
1691 enum BlockingMode {
1692 	/++
1693 		The event loop call will block until the whole application is ready
1694 		to quit if it is the only one running, but if it is nested inside
1695 		another one, it will only block until the window you're calling it on
1696 		closes.
1697 	+/
1698 	automatic             = 0x00,
1699 	/++
1700 		The event loop call will only return when the whole application
1701 		is ready to quit. This usually means all windows have been closed.
1702 
1703 		This is appropriate for your main application event loop.
1704 	+/
1705 	untilApplicationQuits = 0x01,
1706 	/++
1707 		The event loop will return when the window you're calling it on
1708 		closes. If there are other windows still open, they may be destroyed
1709 		unless you have another event loop running later.
1710 
1711 		This might be appropriate for a modal dialog box loop. Remember that
1712 		other windows are still processing input though, so you can end up
1713 		with a lengthy call stack if this happens in a loop, similar to a
1714 		recursive function (well, it literally is a recursive function, just
1715 		not an obvious looking one).
1716 	+/
1717 	untilWindowCloses     = 0x02,
1718 	/++
1719 		If an event loop is already running, this call will immediately
1720 		return, allowing the existing loop to handle it. If not, this call
1721 		will block until the condition you bitwise-or into the flag.
1722 
1723 		The default is to block until the application quits, same as with
1724 		the `automatic` setting (since if it were nested, which triggers until
1725 		window closes in automatic, this flag would instead not block at all),
1726 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1727 		it will only nest until the window closes. You might want that if you are
1728 		going to open two windows simultaneously and want closing just one of them
1729 		to trigger the event loop return.
1730 	+/
1731 	onlyIfNotNested       = 0x10,
1732 }
1733 
1734 /++
1735 	The flagship window class.
1736 
1737 
1738 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1739 	out of more advanced or complex features of the underlying windowing system.
1740 
1741 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1742 	and get a suitable window to work with.
1743 
1744 	From there, you can opt into additional features, like custom resizability and OpenGL support
1745 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1746 	and customization flags with the final two constructor arguments.
1747 
1748 	If none of that works for you, you can also create a window using native function calls, then
1749 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1750 	though, if you do this, managing the window is still your own responsibility! Notably, you
1751 	will need to destroy it yourself.
1752 +/
1753 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1754 
1755 	/++
1756 		Copies the window's current state into a [TrueColorImage].
1757 
1758 		Be warned: this can be a very slow operation
1759 
1760 		History:
1761 			Actually implemented on March 14, 2021
1762 	+/
1763 	TrueColorImage takeScreenshot() {
1764 		version(Windows)
1765 			return trueColorImageFromNativeHandle(impl.hwnd, width, height);
1766 		else version(OSXCocoa)
1767 			throw new NotYetImplementedException();
1768 		else
1769 			return trueColorImageFromNativeHandle(impl.window, width, height);
1770 	}
1771 
1772 	/++
1773 		Returns the actual logical DPI for the window on its current display monitor. If the window
1774 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1775 
1776 		Please note this function may return zero if it doesn't know the answer!
1777 
1778 
1779 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1780 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1781 
1782 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1783 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1784 		window primarily resides on by checking the center point of the window against the monitor map.
1785 
1786 		Returns:
1787 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1788 			assumes the X and Y dpi are the same.
1789 
1790 		History:
1791 			Added November 26, 2021 (dub v10.4)
1792 
1793 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1794 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1795 			that.
1796 
1797 		Bugs:
1798 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1799 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1800 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1801 			and 1.5 on the secondary monitor.
1802 
1803 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1804 			is a historical misnomer - the real thing of interest is the scale factor and due to
1805 			compatibility concerns the scale would modify dpi values to trick applications. But since
1806 			that's the terminology common out there, I used it too.
1807 
1808 		See_Also:
1809 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1810 			as this since the window many be on a different monitor, but it is a reasonable fallback
1811 			to use if `actualDpi` returns 0.
1812 
1813 			[onDpiChanged] is changed when `actualDpi` has changed.
1814 	+/
1815 	int actualDpi() {
1816 		if(!actualDpiLoadAttempted) {
1817 			// FIXME: do the actual monitor we are on
1818 			// and on X this is a good chance to load the monitor map.
1819 			version(Windows) {
1820 				if(GetDpiForWindow)
1821 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1822 			} else version(X11) {
1823 				if(!xRandrInfoLoadAttemped) {
1824 					xRandrInfoLoadAttemped = true;
1825 					if(!XRandrLibrary.attempted) {
1826 						XRandrLibrary.loadDynamicLibrary();
1827 					}
1828 
1829 					if(XRandrLibrary.loadSuccessful) {
1830 						auto display = XDisplayConnection.get;
1831 						int scratch;
1832 						int major, minor;
1833 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1834 							goto fallback;
1835 
1836 						XRRQueryVersion(display, &major, &minor);
1837 						if(major <= 1 && minor < 5)
1838 							goto fallback;
1839 
1840 						int count;
1841 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1842 						if(monitors is null)
1843 							goto fallback;
1844 						scope(exit) XRRFreeMonitors(monitors);
1845 
1846 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1847 						MonitorInfo.info.assumeSafeAppend();
1848 						foreach(idx, monitor; monitors[0 .. count]) {
1849 							MonitorInfo.info ~= MonitorInfo(
1850 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1851 								Size(monitor.mwidth, monitor.mheight),
1852 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1853 							);
1854 
1855 							/+
1856 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1857 							// unknown physical size, just guess 96 to avoid divide by zero
1858 							MonitorInfo.info ~= MonitorInfo(
1859 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1860 								Size(monitor.mwidth, monitor.mheight),
1861 								96
1862 							);
1863 							else
1864 							// and actual thing
1865 							MonitorInfo.info ~= MonitorInfo(
1866 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1867 								Size(monitor.mwidth, monitor.mheight),
1868 								minInternal(
1869 									// millimeter to int then rounding up.
1870 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1871 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1872 								)
1873 							);
1874 							+/
1875 						}
1876 					// import std.stdio; writeln("Here", MonitorInfo.info);
1877 					}
1878 				}
1879 
1880 				if(XRandrLibrary.loadSuccessful) {
1881 					updateActualDpi(true);
1882 					//import std.stdio; writeln("updated");
1883 
1884 					if(!requestedInput) {
1885 						// this is what requests live updates should the configuration change
1886 						// each time you select input, it sends an initial event, so very important
1887 						// to not get into a loop of selecting input, getting event, updating data,
1888 						// and reselecting input...
1889 						requestedInput = true;
1890 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1891 						//import std.stdio; writeln("requested input");
1892 					}
1893 				} else {
1894 					fallback:
1895 					// make sure we disable events that aren't coming
1896 					xrrEventBase = -1;
1897 					// best guess... respect the custom scaling user command to some extent at least though
1898 					actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1899 				}
1900 			}
1901 			actualDpiLoadAttempted = true;
1902 		}
1903 		return actualDpi_;
1904 	}
1905 
1906 	private int actualDpi_;
1907 	private bool actualDpiLoadAttempted;
1908 
1909 	version(X11) private {
1910 		bool requestedInput;
1911 		static bool xRandrInfoLoadAttemped;
1912 		struct MonitorInfo {
1913 			Rectangle position;
1914 			Size size;
1915 			int dpi;
1916 
1917 			static MonitorInfo[] info;
1918 		}
1919 		bool screenPositionKnown;
1920 		int screenPositionX;
1921 		int screenPositionY;
1922 		void updateActualDpi(bool loadingNow = false) {
1923 			if(!loadingNow && !actualDpiLoadAttempted)
1924 				actualDpi(); // just to make it do the load 
1925 			foreach(idx, m; MonitorInfo.info) {
1926 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1927 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1928 					actualDpi_ = m.dpi;
1929 					//import std.stdio; writeln("monitor ", idx);
1930 					if(changed && onDpiChanged)
1931 						onDpiChanged();
1932 					break;
1933 				}
1934 			}
1935 		}
1936 	}
1937 
1938 	/++
1939 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
1940 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
1941 
1942 		History:
1943 			Added November 26, 2021 (dub v10.4)
1944 
1945 		See_Also:
1946 			[actualDpi]
1947 	+/
1948 	void delegate() onDpiChanged;
1949 
1950 	version(X11) {
1951 		void recreateAfterDisconnect() {
1952 			if(!stateDiscarded) return;
1953 
1954 			if(_parent !is null && _parent.stateDiscarded)
1955 				_parent.recreateAfterDisconnect();
1956 
1957 			bool wasHidden = hidden;
1958 
1959 			activeScreenPainter = null; // should already be done but just to confirm
1960 
1961 			actualDpi_ = 0;
1962 			actualDpiLoadAttempted = false;
1963 			xRandrInfoLoadAttemped = false;
1964 
1965 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1966 
1967 			if(auto dh = dropHandler) {
1968 				dropHandler = null;
1969 				enableDragAndDrop(this, dh);
1970 			}
1971 
1972 			if(recreateAdditionalConnectionState)
1973 				recreateAdditionalConnectionState();
1974 
1975 			hidden = wasHidden;
1976 			stateDiscarded = false;
1977 		}
1978 
1979 		bool stateDiscarded;
1980 		void discardConnectionState() {
1981 			if(XDisplayConnection.display)
1982 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
1983 			if(discardAdditionalConnectionState)
1984 				discardAdditionalConnectionState();
1985 			stateDiscarded = true;
1986 		}
1987 
1988 		void delegate() discardAdditionalConnectionState;
1989 		void delegate() recreateAdditionalConnectionState;
1990 
1991 	}
1992 
1993 	private DropHandler dropHandler;
1994 
1995 	SimpleWindow _parent;
1996 	bool beingOpenKeepsAppOpen = true;
1997 	/++
1998 		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.
1999 
2000 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2001 
2002 		Params:
2003 
2004 		width = the width of the window's client area, in pixels
2005 		height = the height of the window's client area, in pixels
2006 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2007 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2008 		resizable = [Resizability] has three options:
2009 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2010 			$(P `fixedSize` will not allow the user to resize the window.)
2011 			$(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.)
2012 		windowType = The type of window you want to make.
2013 		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.
2014 		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".
2015 	+/
2016 	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) {
2017 		claimGuiThread();
2018 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2019 		this._width = width;
2020 		this._height = height;
2021 		this.openglMode = opengl;
2022 		this.resizability = resizable;
2023 		this.windowType = windowType;
2024 		this.customizationFlags = customizationFlags;
2025 		this._title = (title is null ? "D Application" : title);
2026 		this._parent = parent;
2027 		impl.createWindow(width, height, this._title, opengl, parent);
2028 
2029 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2030 			beingOpenKeepsAppOpen = false;
2031 	}
2032 
2033 	/// ditto
2034 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2035 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2036 	}
2037 
2038 	/// Same as above, except using the `Size` struct instead of separate width and height.
2039 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2040 		this(size.width, size.height, title, opengl, resizable);
2041 	}
2042 
2043 	/// ditto
2044 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2045 		this(size, title, opengl, resizable);
2046 	}
2047 
2048 
2049 	/++
2050 		Creates a window based on the given [Image]. It's client area
2051 		width and height is equal to the image. (A window's client area
2052 		is the drawable space inside; it excludes the title bar, etc.)
2053 
2054 		Windows based on images will not be resizable and do not use OpenGL.
2055 
2056 		It will draw the image in upon creation, but this will be overwritten
2057 		upon any draws, including the initial window visible event.
2058 
2059 		You probably do not want to use this and it may be removed from
2060 		the library eventually, or I might change it to be a "permanent"
2061 		background image; one that is automatically drawn on it before any
2062 		other drawing event. idk.
2063 	+/
2064 	this(Image image, string title = null) {
2065 		this(image.width, image.height, title);
2066 		this.image = image;
2067 	}
2068 
2069 	/++
2070 		Wraps a native window handle with very little additional processing - notably no destruction
2071 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2072 		windows created through the low level API (so you can use platform-specific options and
2073 		other details SimpleWindow does not expose) available to the event loop wrappers.
2074 	+/
2075 	this(NativeWindowHandle nativeWindow) {
2076 		version(Windows)
2077 			impl.hwnd = nativeWindow;
2078 		else version(X11) {
2079 			impl.window = nativeWindow;
2080 			if(nativeWindow)
2081 				display = XDisplayConnection.get(); // get initial display to not segfault
2082 		} else version(OSXCocoa)
2083 			throw new NotYetImplementedException();
2084 		else featureNotImplemented();
2085 		// FIXME: set the size correctly
2086 		_width = 1;
2087 		_height = 1;
2088 		if(nativeWindow)
2089 			nativeMapping[nativeWindow] = this;
2090 
2091 		beingOpenKeepsAppOpen = false;
2092 
2093 		if(nativeWindow)
2094 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2095 		_suppressDestruction = true; // so it doesn't try to close
2096 	}
2097 
2098 	/++
2099 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2100 		The delegate will be called when the window manager asks you to take focus.
2101 
2102 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2103 
2104 		History:
2105 			Added April 1, 2022 (dub v10.8)
2106 	+/
2107 	SimpleWindow delegate() setRequestedInputFocus;
2108 
2109 	/// Experimental, do not use yet
2110 	/++
2111 		Grabs exclusive input from the user until you release it with
2112 		[releaseInputGrab].
2113 
2114 
2115 		Note: it is extremely rude to do this without good reason.
2116 		Reasons may include doing some kind of mouse drag operation
2117 		or popping up a temporary menu that should get events and will
2118 		be dismissed at ease by the user clicking away.
2119 
2120 		Params:
2121 			keyboard = do you want to grab keyboard input?
2122 			mouse = grab mouse input?
2123 			confine = confine the mouse cursor to inside this window?
2124 
2125 		History:
2126 			Prior to March 11, 2021, grabbing the keyboard would always also
2127 			set the X input focus. Now, it only focuses if it is a non-transient
2128 			window and otherwise manages the input direction internally.
2129 
2130 			This means spurious focus/blur events will no longer be sent and the
2131 			application will not steal focus from other applications (which the
2132 			window manager may have rejected anyway).
2133 	+/
2134 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2135 		static if(UsingSimpledisplayX11) {
2136 			XSync(XDisplayConnection.get, 0);
2137 			if(keyboard) {
2138 				if(isTransient && _parent) {
2139 					/*
2140 					FIXME:
2141 						setting the keyboard focus is not actually that helpful, what I more likely want
2142 						is the events from the parent window to be sent over here if we're transient.
2143 					*/
2144 
2145 					_parent.inputProxy = this;
2146 				} else {
2147 					XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
2148 				}
2149 			}
2150 			if(mouse) {
2151 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 
2152 				EventMask.PointerMotionMask // FIXME: not efficient
2153 				| EventMask.ButtonPressMask
2154 				| EventMask.ButtonReleaseMask
2155 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2156 				)
2157 			{
2158 				XSync(XDisplayConnection.get, 0);
2159 				import core.stdc.stdio;
2160 				printf("Grab input failed %d\n", res);
2161 				//throw new Exception("Grab input failed");
2162 			} else {
2163 				// cool
2164 			}
2165 			}
2166 
2167 		} else version(Windows) {
2168 			// FIXME: keyboard?
2169 			SetCapture(impl.hwnd);
2170 			if(confine) {
2171 				RECT rcClip;
2172 				//RECT rcOldClip;
2173 				//GetClipCursor(&rcOldClip); 
2174 				GetWindowRect(hwnd, &rcClip); 
2175 				ClipCursor(&rcClip); 
2176 			}
2177 		} else version(OSXCocoa) {
2178 			throw new NotYetImplementedException();
2179 		} else static assert(0);
2180 	}
2181 
2182 	/++
2183 		Returns the native window.
2184 
2185 		History:
2186 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2187 			to access it through the `impl` member (which is semi-supported
2188 			but platform specific and here it is simple enough to offer an accessor).
2189 
2190 		Bugs:
2191 			Not implemented outside Windows or X11.
2192 	+/
2193 	NativeWindowHandle nativeWindowHandle() {
2194 		version(X11)
2195 			return impl.window;
2196 		else version(Windows)
2197 			return impl.hwnd;
2198 		else
2199 			throw new NotYetImplementedException();
2200 	}
2201 
2202 	private bool isTransient() {
2203 		with(WindowTypes)
2204 		final switch(windowType) {
2205 			case normal, undecorated, eventOnly:
2206 			case nestedChild:
2207 				return (customizationFlags & WindowFlags.transient) ? true : false;
2208 			case dropdownMenu, popupMenu, notification:
2209 				return true;
2210 		}
2211 	}
2212 
2213 	private SimpleWindow inputProxy;
2214 
2215 	/++
2216 		Releases the grab acquired by [grabInput].
2217 	+/
2218 	void releaseInputGrab() {
2219 		static if(UsingSimpledisplayX11) {
2220 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2221 			if(_parent)
2222 				_parent.inputProxy = null;
2223 		} else version(Windows) {
2224 			ReleaseCapture();
2225 			ClipCursor(null); 
2226 		} else version(OSXCocoa) {
2227 			throw new NotYetImplementedException();
2228 		} else static assert(0);
2229 	}
2230 
2231 	/++
2232 		Sets the input focus to this window.
2233 
2234 		You shouldn't call this very often - please let the user control the input focus.
2235 	+/
2236 	void focus() {
2237 		static if(UsingSimpledisplayX11) {
2238 			XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
2239 		} else version(Windows) {
2240 			SetFocus(this.impl.hwnd);
2241 		} else version(OSXCocoa) {
2242 			throw new NotYetImplementedException();
2243 		} else static assert(0);
2244 	}
2245 
2246 	/++
2247 		Requests attention from the user for this window.
2248 
2249 
2250 		The typical result of this function is to change the color
2251 		of the taskbar icon, though it may be tweaked on specific
2252 		platforms.
2253 
2254 		It is meant to unobtrusively tell the user that something
2255 		relevant to them happened in the background and they should
2256 		check the window when they get a chance. Upon receiving the
2257 		keyboard focus, the window will automatically return to its
2258 		natural state.
2259 
2260 		If the window already has the keyboard focus, this function
2261 		may do nothing, because the user is presumed to already be
2262 		giving the window attention.
2263 
2264 		Implementation_note:
2265 
2266 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2267 		atom on X11 and the FlashWindow function on Windows.
2268 	+/
2269 	void requestAttention() {
2270 		if(_focused)
2271 			return;
2272 
2273 		version(Windows) {
2274 			FLASHWINFO info;
2275 			info.cbSize = info.sizeof;
2276 			info.hwnd = impl.hwnd;
2277 			info.dwFlags = FLASHW_TRAY;
2278 			info.uCount = 1;
2279 
2280 			FlashWindowEx(&info);
2281 
2282 		} else version(X11) {
2283 			demandingAttention = true;
2284 			demandAttention(this, true);
2285 		} else version(OSXCocoa) {
2286 			throw new NotYetImplementedException();
2287 		} else static assert(0);
2288 	}
2289 
2290 	private bool _focused;
2291 
2292 	version(X11) private bool demandingAttention;
2293 
2294 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2295 	/// You'll have to call `close()` manually if you set this delegate.
2296 	void delegate () closeQuery;
2297 
2298 	/// This will be called when window visibility was changed.
2299 	void delegate (bool becomesVisible) visibilityChanged;
2300 
2301 	/// This will be called when window becomes visible for the first time.
2302 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2303 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2304 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2305 	private bool _visibleForTheFirstTimeCalled;
2306 	void delegate () visibleForTheFirstTime;
2307 
2308 	/// Returns true if the window has been closed.
2309 	final @property bool closed() { return _closed; }
2310 
2311 	private final @property bool notClosed() { return !_closed; }
2312 
2313 	/// Returns true if the window is focused.
2314 	final @property bool focused() { return _focused; }
2315 
2316 	private bool _visible;
2317 	/// Returns true if the window is visible (mapped).
2318 	final @property bool visible() { return _visible; }
2319 
2320 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2321 	void close() {
2322 		if (!_closed) {
2323 			runInGuiThread( {
2324 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2325 				if (onClosing !is null) onClosing();
2326 				impl.closeWindow();
2327 				_closed = true;
2328 			} );
2329 		}
2330 	}
2331 
2332 	/++
2333 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2334 
2335 		History:
2336 			Overload added on March 7, 2021.
2337 	+/
2338 	void close() shared {
2339 		(cast() this).close();
2340 	}
2341 
2342 	/++
2343 
2344 	+/
2345 	void maximize() {
2346 		version(Windows)
2347 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2348 		else version(X11) {
2349 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2350 
2351 			// also note _NET_WM_STATE_FULLSCREEN
2352 		}
2353 
2354 	}
2355 
2356 	private bool _fullscreen;
2357 	version(Windows)
2358 	private WINDOWPLACEMENT g_wpPrev;
2359 
2360 	/// not fully implemented but planned for a future release
2361 	void fullscreen(bool yes) {
2362 		version(Windows) {
2363 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2364 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2365 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2366 				MONITORINFO mi;
2367 				mi.cbSize = MONITORINFO.sizeof;
2368 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2369 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2370 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2371 					SetWindowLong(hwnd, GWL_STYLE,
2372 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2373 					SetWindowPos(hwnd, HWND_TOP,
2374 						     mi.rcMonitor.left, mi.rcMonitor.top,
2375 						     mi.rcMonitor.right - mi.rcMonitor.left,
2376 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2377 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2378 				}
2379 			} else {
2380 				SetWindowLong(hwnd, GWL_STYLE,
2381 					      dwStyle | WS_OVERLAPPEDWINDOW);
2382 				SetWindowPlacement(hwnd, &g_wpPrev);
2383 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2384 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2385 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2386 			}
2387 
2388 		} else version(X11) {
2389 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2390 		}
2391 
2392 		_fullscreen = yes;
2393 
2394 	}
2395 
2396 	bool fullscreen() {
2397 		return _fullscreen;
2398 	}
2399 
2400 	/++
2401 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2402 
2403 	+/
2404 	void minimize() {
2405 		version(Windows)
2406 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2407 		//else version(X11)
2408 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2409 	}
2410 
2411 	/// Alias for `hidden = false`
2412 	void show() {
2413 		hidden = false;
2414 	}
2415 
2416 	/// Alias for `hidden = true`
2417 	void hide() {
2418 		hidden = true;
2419 	}
2420 
2421 	/// Hide cursor when it enters the window.
2422 	void hideCursor() {
2423 		version(OSXCocoa) throw new NotYetImplementedException(); else
2424 		if (!_closed) impl.hideCursor();
2425 	}
2426 
2427 	/// Don't hide cursor when it enters the window.
2428 	void showCursor() {
2429 		version(OSXCocoa) throw new NotYetImplementedException(); else
2430 		if (!_closed) impl.showCursor();
2431 	}
2432 
2433 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2434 	 *
2435 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2436 	 * control. Try to think for other approaches before using this function.
2437 	 *
2438 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2439 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2440 	 *       receive "mouse moved here" event.
2441 	 */
2442 	bool warpMouse (int x, int y) {
2443 		version(X11) {
2444 			if (!_closed) { impl.warpMouse(x, y); return true; }
2445 		} else version(Windows) {
2446 			if (!_closed) {
2447 				POINT point;
2448 				point.x = x;
2449 				point.y = y;
2450 				if(ClientToScreen(impl.hwnd, &point)) {
2451 					SetCursorPos(point.x, point.y);
2452 					return true;
2453 				}
2454 			}
2455 		}
2456 		return false;
2457 	}
2458 
2459 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2460 	void sendDummyEvent () {
2461 		version(X11) {
2462 			if (!_closed) { impl.sendDummyEvent(); }
2463 		}
2464 	}
2465 
2466 	/// Set window minimal size.
2467 	void setMinSize (int minwidth, int minheight) {
2468 		version(OSXCocoa) throw new NotYetImplementedException(); else
2469 		if (!_closed) impl.setMinSize(minwidth, minheight);
2470 	}
2471 
2472 	/// Set window maximal size.
2473 	void setMaxSize (int maxwidth, int maxheight) {
2474 		version(OSXCocoa) throw new NotYetImplementedException(); else
2475 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2476 	}
2477 
2478 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2479 	/// Currently only supported on X11.
2480 	void setResizeGranularity (int granx, int grany) {
2481 		version(OSXCocoa) throw new NotYetImplementedException(); else
2482 		if (!_closed) impl.setResizeGranularity(granx, grany);
2483 	}
2484 
2485 	/// Move window.
2486 	void move(int x, int y) {
2487 		version(OSXCocoa) throw new NotYetImplementedException(); else
2488 		if (!_closed) impl.move(x, y);
2489 	}
2490 
2491 	/// ditto
2492 	void move(Point p) {
2493 		version(OSXCocoa) throw new NotYetImplementedException(); else
2494 		if (!_closed) impl.move(p.x, p.y);
2495 	}
2496 
2497 	/++
2498 		Resize window.
2499 
2500 		Note that the width and height of the window are NOT instantly
2501 		updated - it waits for the window manager to approve the resize
2502 		request, which means you must return to the event loop before the
2503 		width and height are actually changed.
2504 	+/
2505 	void resize(int w, int h) {
2506 		if(!_closed && _fullscreen) fullscreen = false;
2507 		version(OSXCocoa) throw new NotYetImplementedException(); else
2508 		if (!_closed) impl.resize(w, h);
2509 	}
2510 
2511 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2512 	void moveResize (int x, int y, int w, int h) {
2513 		if(!_closed && _fullscreen) fullscreen = false;
2514 		version(OSXCocoa) throw new NotYetImplementedException(); else
2515 		if (!_closed) impl.moveResize(x, y, w, h);
2516 	}
2517 
2518 	private bool _hidden;
2519 
2520 	/// Returns true if the window is hidden.
2521 	final @property bool hidden() {
2522 		return _hidden;
2523 	}
2524 
2525 	/// Shows or hides the window based on the bool argument.
2526 	final @property void hidden(bool b) {
2527 		_hidden = b;
2528 		version(Windows) {
2529 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2530 		} else version(X11) {
2531 			if(b)
2532 				//XUnmapWindow(impl.display, impl.window);
2533 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2534 			else
2535 				XMapWindow(impl.display, impl.window);
2536 		} else version(OSXCocoa) {
2537 			throw new NotYetImplementedException();
2538 		} else static assert(0);
2539 	}
2540 
2541 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2542 	void opacity(double opacity) @property
2543 	in {
2544 		assert(opacity >= 0 && opacity <= 1);
2545 	} do {
2546 		version (Windows) {
2547 			impl.setOpacity(cast(ubyte)(255 * opacity));
2548 		} else version (X11) {
2549 			impl.setOpacity(cast(uint)(uint.max * opacity));
2550 		} else throw new NotYetImplementedException();
2551 	}
2552 
2553 	/++
2554 		Sets your event handlers, without entering the event loop. Useful if you
2555 		have multiple windows - set the handlers on each window, then only do
2556 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2557 
2558 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2559 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2560 		delegate signatures.
2561 	+/
2562 	void setEventHandlers(T...)(T eventHandlers) {
2563 		// FIXME: add more events
2564 		foreach(handler; eventHandlers) {
2565 			static if(__traits(compiles, handleKeyEvent = handler)) {
2566 				handleKeyEvent = handler;
2567 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2568 				handleCharEvent = handler;
2569 			} else static if(__traits(compiles, handlePulse = handler)) {
2570 				handlePulse = handler;
2571 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2572 				handleMouseEvent = handler;
2573 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2574 		}
2575 	}
2576 
2577 	/++
2578 		The event loop automatically returns when the window is closed
2579 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2580 		pulse timer is created. The event loop will block until an event
2581 		arrives or the pulse timer goes off.
2582 
2583 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2584 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2585 		[handleMouseEvent], based on the signature of delegates you provide.
2586 
2587 		Give one with no parameters to set a timer pulse handler. Give one that
2588 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2589 		and one that takes `dchar` for a char event handler. You can use as many
2590 		or as few handlers as you need for your application.
2591 
2592 		History:
2593 			The overload without `pulseTimeout` was added on December 8, 2021.
2594 
2595 			On December 9, 2021, the default blocking mode (which is now configurable
2596 			because [eventLoopWithBlockingMode] was added) switched from
2597 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2598 			should almost never be noticeable to you since the typical simpledisplay
2599 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2600 
2601 		See_Also:
2602 			[eventLoopWithBlockingMode]
2603 	+/
2604 	final int eventLoop(T...)(
2605 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2606 		T eventHandlers) /// delegate list like std.concurrency.receive
2607 	{
2608 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2609 	}
2610 
2611 	/// ditto
2612 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2613 	{
2614 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2615 	}
2616 
2617 	/++
2618 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2619 
2620 		History:
2621 			Added December 8, 2021 (dub v10.5)
2622 
2623 			Previously, this implementation was right inside [eventLoop], but when I wanted
2624 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2625 			just renamed it instead of adding as an overload. Besides, the new name makes it
2626 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2627 
2628 		See_Also:
2629 			[SimpleWindow.eventLoop], [EventLoop]
2630 
2631 		Bugs:
2632 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2633 	+/
2634 	final int eventLoopWithBlockingMode(T...)(
2635 		BlockingMode blockingMode, /// when you want this function to block until
2636 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2637 		T eventHandlers) /// delegate list like std.concurrency.receive
2638 	{
2639 		setEventHandlers(eventHandlers);
2640 
2641 		version(with_eventloop) {
2642 			// delegates event loop to my other module
2643 			version(X11)
2644 				XFlush(display);
2645 
2646 			import arsd.eventloop;
2647 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2648 			scope(exit) clearInterval(handle);
2649 
2650 			loop();
2651 			return 0;
2652 		} else version(OSXCocoa) {
2653 			// FIXME
2654 			if (handlePulse !is null && pulseTimeout != 0) {
2655 				timer = scheduledTimer(pulseTimeout*1e-3,
2656 					view, sel_registerName("simpledisplay_pulse"),
2657 					null, true);
2658 			}
2659 
2660             		setNeedsDisplay(view, true);
2661             		run(NSApp);
2662             		return 0;
2663         	} else {
2664 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2665 
2666 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2667 				return 0;
2668 
2669 			return el.run(
2670 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2671 					null :
2672 					&this.notClosed
2673 			);
2674 		}
2675 	}
2676 
2677 	/++
2678 		This lets you draw on the window (or its backing buffer) using basic
2679 		2D primitives.
2680 
2681 		Be sure to call this in a limited scope because your changes will not
2682 		actually appear on the window until ScreenPainter's destructor runs.
2683 
2684 		Returns: an instance of [ScreenPainter], which has the drawing methods
2685 		on it to draw on this window.
2686 
2687 		Params:
2688 			manualInvalidations = if you set this to true, you will need to
2689 			set the invalid rectangle on the painter yourself. If false, it
2690 			assumes the whole window has been redrawn each time you draw.
2691 
2692 			Only invalidated rectangles are blitted back to the window when
2693 			the destructor runs. Doing this yourself can reduce flickering
2694 			of child windows.
2695 
2696 		History:
2697 			The `manualInvalidations` parameter overload was added on
2698 			December 30, 2021 (dub v10.5)
2699 	+/
2700 	ScreenPainter draw() {
2701 		return draw(false);
2702 	}
2703 	/// ditto
2704 	ScreenPainter draw(bool manualInvalidations) {
2705 		return impl.getPainter(manualInvalidations);
2706 	}
2707 
2708 	// This is here to implement the interface we use for various native handlers.
2709 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2710 
2711 	// maps native window handles to SimpleWindow instances, if there are any
2712 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2713 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2714 
2715 	/// Width of the window's drawable client area, in pixels.
2716 	@scriptable
2717 	final @property int width() const pure nothrow @safe @nogc { return _width; }
2718 
2719 	/// Height of the window's drawable client area, in pixels.
2720 	@scriptable
2721 	final @property int height() const pure nothrow @safe @nogc { return _height; }
2722 
2723 	private int _width;
2724 	private int _height;
2725 
2726 	// HACK: making the best of some copy constructor woes with refcounting
2727 	private ScreenPainterImplementation* activeScreenPainter_;
2728 
2729 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2730 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2731 
2732 	private OpenGlOptions openglMode;
2733 	private Resizability resizability;
2734 	private WindowTypes windowType;
2735 	private int customizationFlags;
2736 
2737 	/// `true` if OpenGL was initialized for this window.
2738 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2739 		version(without_opengl)
2740 			return false;
2741 		else
2742 			return (openglMode == OpenGlOptions.yes);
2743 	}
2744 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2745 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2746 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2747 
2748 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2749 	/// to call this, as it's not recommended to share window between threads.
2750 	void mtLock () {
2751 		version(X11) {
2752 			XLockDisplay(this.display);
2753 		}
2754 	}
2755 
2756 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2757 	/// to call this, as it's not recommended to share window between threads.
2758 	void mtUnlock () {
2759 		version(X11) {
2760 			XUnlockDisplay(this.display);
2761 		}
2762 	}
2763 
2764 	/// Emit a beep to get user's attention.
2765 	void beep () {
2766 		version(X11) {
2767 			XBell(this.display, 100);
2768 		} else version(Windows) {
2769 			MessageBeep(0xFFFFFFFF);
2770 		}
2771 	}
2772 
2773 
2774 
2775 	version(without_opengl) {} else {
2776 
2777 		/// 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`.
2778 		void delegate() redrawOpenGlScene;
2779 
2780 		/// This will allow you to change OpenGL vsync state.
2781 		final @property void vsync (bool wait) {
2782 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2783 		  version(X11) {
2784 		    setAsCurrentOpenGlContext();
2785 		    glxSetVSync(display, impl.window, wait);
2786 		  } else version(Windows) {
2787 		    setAsCurrentOpenGlContext();
2788                     wglSetVSync(wait);
2789 		  }
2790 		}
2791 
2792 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
2793 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
2794 		/// enough without waiting 'em to finish their frame bussiness.
2795 		bool useGLFinish = true;
2796 
2797 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
2798 		/// 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.
2799 		void redrawOpenGlSceneNow() {
2800 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
2801 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2802 			if(redrawOpenGlScene is null)
2803 				return;
2804 
2805 			this.mtLock();
2806 			scope(exit) this.mtUnlock();
2807 
2808 			this.setAsCurrentOpenGlContext();
2809 
2810 			redrawOpenGlScene();
2811 
2812 			this.swapOpenGlBuffers();
2813 			// 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.
2814 			if (useGLFinish) glFinish();
2815 		}
2816 
2817 		private bool redrawOpenGlSceneSoonSet = false;
2818 		private static class RedrawOpenGlSceneEvent {
2819 			SimpleWindow w;
2820 			this(SimpleWindow w) { this.w = w; }
2821 		}
2822 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
2823 		/++
2824 			Queues an opengl redraw as soon as the other pending events are cleared.
2825 		+/
2826 		void redrawOpenGlSceneSoon() {
2827 			if(!redrawOpenGlSceneSoonSet) {
2828 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
2829 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
2830 				redrawOpenGlSceneSoonSet = true;
2831 			}
2832 			this.postEvent(redrawOpenGlSceneEvent, true);
2833 		}
2834 
2835 
2836 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2837 		void setAsCurrentOpenGlContext() {
2838 			assert(openglMode == OpenGlOptions.yes);
2839 			version(X11) {
2840 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
2841 					throw new Exception("glXMakeCurrent");
2842 			} else version(Windows) {
2843 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2844 				if (!wglMakeCurrent(ghDC, ghRC))
2845 					throw new Exception("wglMakeCurrent"); // let windows users suffer too
2846 			}
2847 		}
2848 
2849 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2850 		/// This doesn't throw, returning success flag instead.
2851 		bool setAsCurrentOpenGlContextNT() nothrow {
2852 			assert(openglMode == OpenGlOptions.yes);
2853 			version(X11) {
2854 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
2855 			} else version(Windows) {
2856 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2857 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
2858 			}
2859 		}
2860 
2861 		/// 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.
2862 		/// This doesn't throw, returning success flag instead.
2863 		bool releaseCurrentOpenGlContext() nothrow {
2864 			assert(openglMode == OpenGlOptions.yes);
2865 			version(X11) {
2866 				return (glXMakeCurrent(display, 0, null) != 0);
2867 			} else version(Windows) {
2868 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2869 				return wglMakeCurrent(ghDC, null) ? true : false;
2870 			}
2871 		}
2872 
2873 		/++
2874 			simpledisplay always uses double buffering, usually automatically. This
2875 			manually swaps the OpenGL buffers.
2876 
2877 
2878 			You should not need to call this yourself because simpledisplay will do it
2879 			for you after calling your `redrawOpenGlScene`.
2880 
2881 			Remember that this may throw an exception, which you can catch in a multithreaded
2882 			application to keep your thread from dying from an unhandled exception.
2883 		+/
2884 		void swapOpenGlBuffers() {
2885 			assert(openglMode == OpenGlOptions.yes);
2886 			version(X11) {
2887 				if (!this._visible) return; // no need to do this if window is invisible
2888 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2889 				glXSwapBuffers(display, impl.window);
2890 			} else version(Windows) {
2891 				SwapBuffers(ghDC);
2892 			}
2893 		}
2894 	}
2895 
2896 	/++
2897 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
2898 
2899 
2900 		---
2901 			auto window = new SimpleWindow(100, 100, "First title");
2902 			window.title = "A new title";
2903 		---
2904 
2905 		You may call this function at any time.
2906 	+/
2907 	@property void title(string title) {
2908 		_title = title;
2909 		version(OSXCocoa) throw new NotYetImplementedException(); else
2910 		impl.setTitle(title);
2911 	}
2912 
2913 	private string _title;
2914 
2915 	/// Gets the title
2916 	@property string title() {
2917 		if(_title is null)
2918 			_title = getRealTitle();
2919 		return _title;
2920 	}
2921 
2922 	/++
2923 		Get the title as set by the window manager.
2924 		May not match what you attempted to set.
2925 	+/
2926 	string getRealTitle() {
2927 		static if(is(typeof(impl.getTitle())))
2928 			return impl.getTitle();
2929 		else
2930 			return null;
2931 	}
2932 
2933 	// don't use this generally it is not yet really released
2934 	version(X11)
2935 	@property Image secret_icon() {
2936 		return secret_icon_inner;
2937 	}
2938 	private Image secret_icon_inner;
2939 
2940 
2941 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
2942 	@property void icon(MemoryImage icon) {
2943 		if(icon is null)
2944 			return;
2945 		auto tci = icon.getAsTrueColorImage();
2946 		version(Windows) {
2947 			winIcon = new WindowsIcon(icon);
2948 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
2949 		} else version(X11) {
2950 			secret_icon_inner = Image.fromMemoryImage(icon);
2951 			// FIXME: ensure this is correct
2952 			auto display = XDisplayConnection.get;
2953 			arch_ulong[] buffer;
2954 			buffer ~= icon.width;
2955 			buffer ~= icon.height;
2956 			foreach(c; tci.imageData.colors) {
2957 				arch_ulong b;
2958 				b |= c.a << 24;
2959 				b |= c.r << 16;
2960 				b |= c.g << 8;
2961 				b |= c.b;
2962 				buffer ~= b;
2963 			}
2964 
2965 			XChangeProperty(
2966 				display,
2967 				impl.window,
2968 				GetAtom!("_NET_WM_ICON", true)(display),
2969 				GetAtom!"CARDINAL"(display),
2970 				32 /* bits */,
2971 				0 /*PropModeReplace*/,
2972 				buffer.ptr,
2973 				cast(int) buffer.length);
2974 		} else version(OSXCocoa) {
2975 			throw new NotYetImplementedException();
2976 		} else static assert(0);
2977 	}
2978 
2979 	version(Windows)
2980 		private WindowsIcon winIcon;
2981 
2982 	bool _suppressDestruction;
2983 
2984 	~this() {
2985 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
2986 		if(_suppressDestruction)
2987 			return;
2988 		impl.dispose();
2989 	}
2990 
2991 	private bool _closed;
2992 
2993 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
2994 	/*
2995 	ScreenPainter drawTransiently() {
2996 		return impl.getPainter();
2997 	}
2998 	*/
2999 
3000 	/// Draws an image on the window. This is meant to provide quick look
3001 	/// of a static image generated elsewhere.
3002 	@property void image(Image i) {
3003 	/+
3004 		version(Windows) {
3005 			BITMAP bm;
3006 			HDC hdc = GetDC(hwnd);
3007 			HDC hdcMem = CreateCompatibleDC(hdc);
3008 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3009 
3010 			GetObject(i.handle, bm.sizeof, &bm);
3011 
3012 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3013 
3014 			SelectObject(hdcMem, hbmOld);
3015 			DeleteDC(hdcMem);
3016 			ReleaseDC(hwnd, hdc);
3017 
3018 			/*
3019 			RECT r;
3020 			r.right = i.width;
3021 			r.bottom = i.height;
3022 			InvalidateRect(hwnd, &r, false);
3023 			*/
3024 		} else
3025 		version(X11) {
3026 			if(!destroyed) {
3027 				if(i.usingXshm)
3028 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3029 				else
3030 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3031 			}
3032 		} else
3033 		version(OSXCocoa) {
3034 			draw().drawImage(Point(0, 0), i);
3035 			setNeedsDisplay(view, true);
3036 		} else static assert(0);
3037 	+/
3038 		auto painter = this.draw;
3039 		painter.drawImage(Point(0, 0), i);
3040 	}
3041 
3042 	/++
3043 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3044 
3045 		---
3046 		window.cursor = GenericCursor.Help;
3047 		// now the window mouse cursor is set to a generic help
3048 		---
3049 
3050 	+/
3051 	@property void cursor(MouseCursor cursor) {
3052 		version(OSXCocoa)
3053 			featureNotImplemented();
3054 		else
3055 		if(this.impl.curHidden <= 0) {
3056 			static if(UsingSimpledisplayX11) {
3057 				auto ch = cursor.cursorHandle;
3058 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3059 			} else version(Windows) {
3060 				auto ch = cursor.cursorHandle;
3061 				impl.currentCursor = ch;
3062 				SetCursor(ch); // redraw without waiting for mouse movement to update
3063 			} else featureNotImplemented();
3064 		}
3065 
3066 	}
3067 
3068 	/// What follows are the event handlers. These are set automatically
3069 	/// by the eventLoop function, but are still public so you can change
3070 	/// them later. wasPressed == true means key down. false == key up.
3071 
3072 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3073 	void delegate(KeyEvent ke) handleKeyEvent;
3074 
3075 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3076 	void delegate(dchar c) handleCharEvent;
3077 
3078 	/// Handles a timer pulse. Settable through setEventHandlers.
3079 	void delegate() handlePulse;
3080 
3081 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3082 	void delegate(bool) onFocusChange;
3083 
3084 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3085 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3086 	void delegate() onClosing;
3087 
3088 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3089 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3090 	 * last minute cleanup. */
3091 	void delegate() onDestroyed;
3092 
3093 	static if (UsingSimpledisplayX11)
3094 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3095 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3096 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3097 	 *
3098 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3099 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3100 
3101 	//version(Windows)
3102 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3103 
3104 	private {
3105 		int lastMouseX = int.min;
3106 		int lastMouseY = int.min;
3107 		void mdx(ref MouseEvent ev) {
3108 			if(lastMouseX == int.min || lastMouseY == int.min) {
3109 				ev.dx = 0;
3110 				ev.dy = 0;
3111 			} else {
3112 				ev.dx = ev.x - lastMouseX;
3113 				ev.dy = ev.y - lastMouseY;
3114 			}
3115 
3116 			lastMouseX = ev.x;
3117 			lastMouseY = ev.y;
3118 		}
3119 	}
3120 
3121 	/// Mouse event handler. Settable through setEventHandlers.
3122 	void delegate(MouseEvent) handleMouseEvent;
3123 
3124 	/// use to redraw child widgets if you use system apis to add stuff
3125 	void delegate() paintingFinished;
3126 
3127 	void delegate() paintingFinishedDg() {
3128 		return paintingFinished;
3129 	}
3130 
3131 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3132 	/// for this to ever happen.
3133 	void delegate(int width, int height) windowResized;
3134 
3135 	/++
3136 		Platform specific - handle any native message this window gets.
3137 
3138 		Note: this is called *in addition to* other event handlers, unless you either:
3139 
3140 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3141 
3142 		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.
3143 
3144 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3145 
3146 		On X, it takes the form of `int delegate(XEvent)`.
3147 
3148 		History:
3149 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3150 
3151 			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.
3152 	+/
3153 	NativeEventHandler handleNativeEvent_;
3154 
3155 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3156 		return handleNativeEvent_;
3157 	}
3158 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3159 		handleNativeEvent_ = neh;
3160 	}
3161 
3162 	version(Windows)
3163 	// compatibility shim with the old deprecated way
3164 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3165 	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) {
3166 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3167 			auto ret = dg(h, m, w, l);
3168 			if(ret == 0)
3169 				r = 1;
3170 			return ret;
3171 		};
3172 	}
3173 
3174 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3175 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3176 	/// this instead and it will work the same way.
3177 	__gshared NativeEventHandler handleNativeGlobalEvent;
3178 
3179 //  private:
3180 	/// The native implementation is available, but you shouldn't use it unless you are
3181 	/// familiar with the underlying operating system, don't mind depending on it, and
3182 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3183 	/// do what you need to do with handleNativeEvent instead.
3184 	///
3185 	/// This is likely to eventually change to be just a struct holding platform-specific
3186 	/// handles instead of a template mixin at some point because I'm not happy with the
3187 	/// code duplication here (ironically).
3188 	mixin NativeSimpleWindowImplementation!() impl;
3189 
3190 	/**
3191 		This is in-process one-way (from anything to window) event sending mechanics.
3192 		It is thread-safe, so it can be used in multi-threaded applications to send,
3193 		for example, "wake up and repaint" events when thread completed some operation.
3194 		This will allow to avoid using timer pulse to check events with synchronization,
3195 		'cause event handler will be called in UI thread. You can stop guessing which
3196 		pulse frequency will be enough for your app.
3197 		Note that events handlers may be called in arbitrary order, i.e. last registered
3198 		handler can be called first, and vice versa.
3199 	*/
3200 public:
3201 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3202 	 * "spamming" window with events it can't cope with.
3203 	 * It is safe to call this from non-UI threads.
3204 	 */
3205 	@property bool eventQueueEmpty() () {
3206 		synchronized(this) {
3207 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3208 		}
3209 		return true;
3210 	}
3211 
3212 	/** Does our custom event queue contains at least one with the given type?
3213 	 * Can be used in simple cases to prevent "spamming" window with events
3214 	 * it can't cope with.
3215 	 * It is safe to call this from non-UI threads.
3216 	 */
3217 	@property bool eventQueued(ET:Object) () {
3218 		synchronized(this) {
3219 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3220 				if (!o.doProcess) {
3221 					if (cast(ET)(o.evt)) return true;
3222 				}
3223 			}
3224 		}
3225 		return false;
3226 	}
3227 
3228 	/++
3229 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3230 
3231 		History:
3232 			Added May 12, 2021
3233 	+/
3234 	void delegate(Exception e) nothrow eventUncaughtException;
3235 
3236 	/** Add listener for custom event. Can be used like this:
3237 	 *
3238 	 * ---------------------
3239 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3240 	 *   ...
3241 	 *   win.removeEventListener(eid);
3242 	 * ---------------------
3243 	 *
3244 	 * Returns: 0 on failure (should never happen, so ignore it)
3245 	 *
3246 	 * $(WARNING Don't use this method in object destructors!)
3247 	 *
3248 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3249 	 *           'cause if event handler id counter will overflow, you won't be able
3250 	 *           to register any more events.)
3251 	 */
3252 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3253 		if (dg is null) return 0; // ignore empty handlers
3254 		synchronized(this) {
3255 			//FIXME: abort on overflow?
3256 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3257 			EventHandlerEntry e;
3258 			e.dg = delegate (Object o) {
3259 				if (auto co = cast(ET)o) {
3260 					try {
3261 						dg(co);
3262 					} catch (Exception e) {
3263 						// sorry!
3264 						if(eventUncaughtException)
3265 							eventUncaughtException(e);
3266 					}
3267 					return true;
3268 				}
3269 				return false;
3270 			};
3271 			e.id = lastUsedHandlerId;
3272 			auto optr = eventHandlers.ptr;
3273 			eventHandlers ~= e;
3274 			if (eventHandlers.ptr !is optr) {
3275 				import core.memory : GC;
3276 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3277 			}
3278 			return lastUsedHandlerId;
3279 		}
3280 	}
3281 
3282 	/// Remove event listener. It is safe to pass invalid event id here.
3283 	/// $(WARNING Don't use this method in object destructors!)
3284 	void removeEventListener() (uint id) {
3285 		if (id == 0 || id > lastUsedHandlerId) return;
3286 		synchronized(this) {
3287 			foreach (immutable idx; 0..eventHandlers.length) {
3288 				if (eventHandlers[idx].id == id) {
3289 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3290 					eventHandlers[$-1].dg = null;
3291 					eventHandlers.length -= 1;
3292 					eventHandlers.assumeSafeAppend;
3293 					return;
3294 				}
3295 			}
3296 		}
3297 	}
3298 
3299 	/// Post event to queue. It is safe to call this from non-UI threads.
3300 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3301 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3302 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3303 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3304 		if (this.closed) return false; // closed windows can't handle events
3305 
3306 		// remove all events of type `ET`
3307 		void removeAllET () {
3308 			uint eidx = 0, ec = eventQueueUsed;
3309 			auto eptr = eventQueue.ptr;
3310 			while (eidx < ec) {
3311 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3312 				if (cast(ET)eptr.evt !is null) {
3313 					// i found her!
3314 					if (inCustomEventProcessor) {
3315 						// if we're in custom event processing loop, processor will clear it for us
3316 						eptr.evt = null;
3317 						++eidx;
3318 						++eptr;
3319 					} else {
3320 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3321 						ec = --eventQueueUsed;
3322 						// clear last event (it is already copied)
3323 						eventQueue.ptr[ec].evt = null;
3324 					}
3325 				} else {
3326 					++eidx;
3327 					++eptr;
3328 				}
3329 			}
3330 		}
3331 
3332 		if (evt is null) {
3333 			if (replace) { synchronized(this) removeAllET(); }
3334 			// ignore empty events, they can't be handled anyway
3335 			return false;
3336 		}
3337 
3338 		// add events even if no event FD/event object created yet
3339 		synchronized(this) {
3340 			if (replace) removeAllET();
3341 			if (eventQueueUsed == uint.max) return false; // just in case
3342 			if (eventQueueUsed < eventQueue.length) {
3343 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3344 			} else {
3345 				if (eventQueue.capacity == eventQueue.length) {
3346 					// need to reallocate; do a trick to ensure that old array is cleared
3347 					auto oarr = eventQueue;
3348 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3349 					// just in case, do yet another check
3350 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3351 					import core.memory : GC;
3352 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3353 				} else {
3354 					auto optr = eventQueue.ptr;
3355 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3356 					assert(eventQueue.ptr is optr);
3357 				}
3358 				++eventQueueUsed;
3359 				assert(eventQueueUsed == eventQueue.length);
3360 			}
3361 			if (!eventWakeUp()) {
3362 				// can't wake up event processor, so there is no reason to keep the event
3363 				assert(eventQueueUsed > 0);
3364 				eventQueue[--eventQueueUsed].evt = null;
3365 				return false;
3366 			}
3367 			return true;
3368 		}
3369 	}
3370 
3371 	/// Post event to queue. It is safe to call this from non-UI threads.
3372 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3373 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3374 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3375 		return postTimeout!ET(evt, 0, replace);
3376 	}
3377 
3378 private:
3379 	private import core.time : MonoTime;
3380 
3381 	version(Posix) {
3382 		__gshared int customEventFDRead = -1;
3383 		__gshared int customEventFDWrite = -1;
3384 		__gshared int customSignalFD = -1;
3385 	} else version(Windows) {
3386 		__gshared HANDLE customEventH = null;
3387 	}
3388 
3389 	// wake up event processor
3390 	static bool eventWakeUp () {
3391 		version(X11) {
3392 			import core.sys.posix.unistd : write;
3393 			ulong n = 1;
3394 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3395 			return true;
3396 		} else version(Windows) {
3397 			if (customEventH !is null) SetEvent(customEventH);
3398 			return true;
3399 		} else {
3400 			// not implemented for other OSes
3401 			return false;
3402 		}
3403 	}
3404 
3405 	static struct QueuedEvent {
3406 		Object evt;
3407 		bool timed = false;
3408 		MonoTime hittime = MonoTime.zero;
3409 		bool doProcess = false; // process event at the current iteration (internal flag)
3410 
3411 		this (Object aevt, uint toutmsecs) {
3412 			evt = aevt;
3413 			if (toutmsecs > 0) {
3414 				import core.time : msecs;
3415 				timed = true;
3416 				hittime = MonoTime.currTime+toutmsecs.msecs;
3417 			}
3418 		}
3419 	}
3420 
3421 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3422 	static struct EventHandlerEntry {
3423 		CustomEventHandler dg;
3424 		uint id;
3425 	}
3426 
3427 	uint lastUsedHandlerId;
3428 	EventHandlerEntry[] eventHandlers;
3429 	QueuedEvent[] eventQueue = null;
3430 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3431 	bool inCustomEventProcessor = false; // required to properly remove events
3432 
3433 	// process queued events and call custom event handlers
3434 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3435 	void processCustomEvents () {
3436 		bool hasSomethingToDo = false;
3437 		uint ecount;
3438 		bool ocep;
3439 		synchronized(this) {
3440 			ocep = inCustomEventProcessor;
3441 			inCustomEventProcessor = true;
3442 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3443 			auto ctt = MonoTime.currTime;
3444 			bool hasEmpty = false;
3445 			// mark events to process (this is required for `eventQueued()`)
3446 			foreach (ref qe; eventQueue[0..ecount]) {
3447 				if (qe.evt is null) { hasEmpty = true; continue; }
3448 				if (qe.timed) {
3449 					qe.doProcess = (qe.hittime <= ctt);
3450 				} else {
3451 					qe.doProcess = true;
3452 				}
3453 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3454 			}
3455 			if (!hasSomethingToDo) {
3456 				// remove empty events
3457 				if (hasEmpty) {
3458 					uint eidx = 0, ec = eventQueueUsed;
3459 					auto eptr = eventQueue.ptr;
3460 					while (eidx < ec) {
3461 						if (eptr.evt is null) {
3462 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3463 							ec = --eventQueueUsed;
3464 							eventQueue.ptr[ec].evt = null; // make GC life easier
3465 						} else {
3466 							++eidx;
3467 							++eptr;
3468 						}
3469 					}
3470 				}
3471 				inCustomEventProcessor = ocep;
3472 				return;
3473 			}
3474 		}
3475 		// process marked events
3476 		uint efree = 0; // non-processed events will be put at this index
3477 		EventHandlerEntry[] eh;
3478 		Object evt;
3479 		foreach (immutable eidx; 0..ecount) {
3480 			synchronized(this) {
3481 				if (!eventQueue[eidx].doProcess) {
3482 					// skip this event
3483 					assert(efree <= eidx);
3484 					if (efree != eidx) {
3485 						// copy this event to queue start
3486 						eventQueue[efree] = eventQueue[eidx];
3487 						eventQueue[eidx].evt = null; // just in case
3488 					}
3489 					++efree;
3490 					continue;
3491 				}
3492 				evt = eventQueue[eidx].evt;
3493 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3494 				if (evt is null) continue; // just in case
3495 				// try all handlers; this can be slow, but meh...
3496 				eh = eventHandlers;
3497 			}
3498 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3499 			evt = null;
3500 			eh = null;
3501 		}
3502 		synchronized(this) {
3503 			// move all unprocessed events to queue top; efree holds first "free index"
3504 			foreach (immutable eidx; ecount..eventQueueUsed) {
3505 				assert(efree <= eidx);
3506 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3507 				++efree;
3508 			}
3509 			eventQueueUsed = efree;
3510 			// wake up event processor on next event loop iteration if we have more queued events
3511 			// also, remove empty events
3512 			bool awaken = false;
3513 			uint eidx = 0, ec = eventQueueUsed;
3514 			auto eptr = eventQueue.ptr;
3515 			while (eidx < ec) {
3516 				if (eptr.evt is null) {
3517 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3518 					ec = --eventQueueUsed;
3519 					eventQueue.ptr[ec].evt = null; // make GC life easier
3520 				} else {
3521 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3522 					++eidx;
3523 					++eptr;
3524 				}
3525 			}
3526 			inCustomEventProcessor = ocep;
3527 		}
3528 	}
3529 
3530 	// for all windows in nativeMapping
3531 	package static void processAllCustomEvents () {
3532 
3533 		cleanupQueue.process();
3534 
3535 		justCommunication.processCustomEvents();
3536 
3537 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3538 			if (sw is null || sw.closed) continue;
3539 			sw.processCustomEvents();
3540 		}
3541 
3542 		runPendingRunInGuiThreadDelegates();
3543 	}
3544 
3545 	// 0: infinite (i.e. no scheduled events in queue)
3546 	uint eventQueueTimeoutMSecs () {
3547 		synchronized(this) {
3548 			if (eventQueueUsed == 0) return 0;
3549 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3550 			uint res = int.max;
3551 			auto ctt = MonoTime.currTime;
3552 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3553 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3554 				if (qe.doProcess) continue; // just in case
3555 				if (!qe.timed) return 1; // minimal
3556 				if (qe.hittime <= ctt) return 1; // minimal
3557 				auto tms = (qe.hittime-ctt).total!"msecs";
3558 				if (tms < 1) tms = 1; // safety net
3559 				if (tms >= int.max) tms = int.max-1; // and another safety net
3560 				if (res > tms) res = cast(uint)tms;
3561 			}
3562 			return (res >= int.max ? 0 : res);
3563 		}
3564 	}
3565 
3566 	// for all windows in nativeMapping
3567 	static uint eventAllQueueTimeoutMSecs () {
3568 		uint res = uint.max;
3569 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3570 			if (sw is null || sw.closed) continue;
3571 			uint to = sw.eventQueueTimeoutMSecs();
3572 			if (to && to < res) {
3573 				res = to;
3574 				if (to == 1) break; // can't have less than this
3575 			}
3576 		}
3577 		return (res >= int.max ? 0 : res);
3578 	}
3579 
3580 	version(X11) {
3581 		ResizeEvent pendingResizeEvent;
3582 	}
3583 }
3584 
3585 /++
3586 	Magic pseudo-window for just posting events to a global queue.
3587 
3588 	Not entirely supported, I might delete it at any time.
3589 
3590 	Added Nov 5, 2021.
3591 +/
3592 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init);
3593 
3594 /* Drag and drop support { */
3595 version(X11) {
3596 
3597 } else version(Windows) {
3598 	import core.sys.windows.uuid;
3599 	import core.sys.windows.ole2;
3600 	import core.sys.windows.oleidl;
3601 	import core.sys.windows.objidl;
3602 	import core.sys.windows.wtypes;
3603 
3604 	pragma(lib, "ole32");
3605 	void initDnd() {
3606 		auto err = OleInitialize(null);
3607 		if(err != S_OK && err != S_FALSE)
3608 			throw new Exception("init");//err);
3609 	}
3610 }
3611 /* } End drag and drop support */
3612 
3613 
3614 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3615 /// See [GenericCursor].
3616 class MouseCursor {
3617 	int osId;
3618 	bool isStockCursor;
3619 	private this(int osId) {
3620 		this.osId = osId;
3621 		this.isStockCursor = true;
3622 	}
3623 
3624 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3625 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3626 
3627 	version(Windows) {
3628 		HCURSOR cursor_;
3629 		HCURSOR cursorHandle() {
3630 			if(cursor_ is null)
3631 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3632 			return cursor_;
3633 		}
3634 
3635 	} else static if(UsingSimpledisplayX11) {
3636 		Cursor cursor_ = None;
3637 		int xDisplaySequence;
3638 
3639 		Cursor cursorHandle() {
3640 			if(this.osId == None)
3641 				return None;
3642 
3643 			// we need to reload if we on a new X connection
3644 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3645 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3646 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3647 			}
3648 			return cursor_;
3649 		}
3650 	}
3651 }
3652 
3653 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3654 // https://tronche.com/gui/x/xlib/appendix/b/
3655 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3656 /// 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.
3657 enum GenericCursorType {
3658 	Default, /// The default arrow pointer.
3659 	Wait, /// A cursor indicating something is loading and the user must wait.
3660 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3661 	Help, /// A cursor indicating the user can get help about the pointer location.
3662 	Cross, /// A crosshair.
3663 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3664 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3665 	UpArrow, /// An arrow pointing straight up.
3666 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3667 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3668 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3669 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3670 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3671 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3672 
3673 }
3674 
3675 /*
3676 	X_plus == css cell == Windows ?
3677 */
3678 
3679 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3680 static struct GenericCursor {
3681 	static:
3682 	///
3683 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3684 		static MouseCursor mc;
3685 
3686 		auto type = __traits(getMember, GenericCursorType, str);
3687 
3688 		if(mc is null) {
3689 
3690 			version(Windows) {
3691 				int osId;
3692 				final switch(type) {
3693 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3694 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3695 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3696 					case GenericCursorType.Help: osId = IDC_HELP; break;
3697 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3698 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3699 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3700 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3701 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3702 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3703 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3704 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3705 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3706 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3707 				}
3708 			} else static if(UsingSimpledisplayX11) {
3709 				int osId;
3710 				final switch(type) {
3711 					case GenericCursorType.Default: osId = None; break;
3712 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3713 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3714 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3715 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3716 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3717 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3718 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3719 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3720 
3721 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3722 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3723 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3724 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3725 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3726 				}
3727 
3728 			} else featureNotImplemented();
3729 
3730 			mc = new MouseCursor(osId);
3731 		}
3732 		return mc;
3733 	}
3734 }
3735 
3736 
3737 /++
3738 	If you want to get more control over the event loop, you can use this.
3739 
3740 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
3741 	to `EventLoop.get.run`.
3742 +/
3743 struct EventLoop {
3744 	@disable this();
3745 
3746 	/// Gets a reference to an existing event loop
3747 	static EventLoop get() {
3748 		return EventLoop(0, null);
3749 	}
3750 
3751 	static void quitApplication() {
3752 		EventLoop.get().exit();
3753 	}
3754 
3755 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3756 
3757 	/// Construct an application-global event loop for yourself
3758 	/// See_Also: [SimpleWindow.setEventHandlers]
3759 	this(long pulseTimeout, void delegate() handlePulse) {
3760 		synchronized(monitor) {
3761 			if(impl is null) {
3762 				claimGuiThread();
3763 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3764 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
3765 			} else {
3766 				if(pulseTimeout) {
3767 					impl.pulseTimeout = pulseTimeout;
3768 					impl.handlePulse = handlePulse;
3769 				}
3770 			}
3771 			impl.refcount++;
3772 		}
3773 	}
3774 
3775 	~this() {
3776 		if(impl is null)
3777 			return;
3778 		impl.refcount--;
3779 		if(impl.refcount == 0) {
3780 			impl.dispose();
3781 			if(thisIsGuiThread)
3782 				guiThreadFinalize();
3783 		}
3784 
3785 	}
3786 
3787 	this(this) {
3788 		if(impl is null)
3789 			return;
3790 		impl.refcount++;
3791 	}
3792 
3793 	/// Runs the event loop until the whileCondition, if present, returns false
3794 	int run(bool delegate() whileCondition = null) {
3795 		assert(impl !is null);
3796 		impl.notExited = true;
3797 		return impl.run(whileCondition);
3798 	}
3799 
3800 	/// Exits the event loop
3801 	void exit() {
3802 		assert(impl !is null);
3803 		impl.notExited = false;
3804 	}
3805 
3806 	version(linux)
3807 	ref void delegate(int) signalHandler() {
3808 		assert(impl !is null);
3809 		return impl.signalHandler;
3810 	}
3811 
3812 	__gshared static EventLoopImpl* impl;
3813 }
3814 
3815 version(linux)
3816 	void delegate(int, int) globalHupHandler;
3817 
3818 version(Posix)
3819 	void makeNonBlocking(int fd) {
3820 		import fcntl = core.sys.posix.fcntl;
3821 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
3822 		if(flags == -1)
3823 			throw new Exception("fcntl get");
3824 		flags |= fcntl.O_NONBLOCK;
3825 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
3826 		if(s == -1)
3827 			throw new Exception("fcntl set");
3828 	}
3829 
3830 struct EventLoopImpl {
3831 	int refcount;
3832 
3833 	bool notExited = true;
3834 
3835 	version(linux) {
3836 		static import ep = core.sys.linux.epoll;
3837 		static import unix = core.sys.posix.unistd;
3838 		static import err = core.stdc.errno;
3839 		import core.sys.linux.timerfd;
3840 
3841 		void delegate(int) signalHandler;
3842 	}
3843 
3844 	version(X11) {
3845 		int pulseFd = -1;
3846 		version(linux) ep.epoll_event[16] events = void;
3847 	} else version(Windows) {
3848 		Timer pulser;
3849 		HANDLE[] handles;
3850 	}
3851 
3852 
3853 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3854 	/// to call this, as it's not recommended to share window between threads.
3855 	void mtLock () {
3856 		version(X11) {
3857 			XLockDisplay(this.display);
3858 		}
3859 	}
3860 
3861 	version(X11)
3862 	auto display() { return XDisplayConnection.get; }
3863 
3864 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3865 	/// to call this, as it's not recommended to share window between threads.
3866 	void mtUnlock () {
3867 		version(X11) {
3868 			XUnlockDisplay(this.display);
3869 		}
3870 	}
3871 
3872 	version(with_eventloop)
3873 	void initialize(long pulseTimeout) {}
3874 	else
3875 	void initialize(long pulseTimeout) {
3876 		version(Windows) {
3877 			if(pulseTimeout && handlePulse !is null)
3878 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
3879 
3880 			if (customEventH is null) {
3881 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
3882 				if (customEventH !is null) {
3883 					handles ~= customEventH;
3884 				} else {
3885 					// this is something that should not be; better be safe than sorry
3886 					throw new Exception("can't create eventfd for custom event processing");
3887 				}
3888 			}
3889 
3890 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
3891 		}
3892 
3893 		version(linux) {
3894 			prepareEventLoop();
3895 			{
3896 				auto display = XDisplayConnection.get;
3897 				// adding Xlib file
3898 				ep.epoll_event ev = void;
3899 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3900 				ev.events = ep.EPOLLIN;
3901 				ev.data.fd = display.fd;
3902 				//import std.conv;
3903 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
3904 					throw new Exception("add x fd");// ~ to!string(epollFd));
3905 				displayFd = display.fd;
3906 			}
3907 
3908 			if(pulseTimeout && handlePulse !is null) {
3909 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
3910 				if(pulseFd == -1)
3911 					throw new Exception("pulse timer create failed");
3912 
3913 				itimerspec value;
3914 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
3915 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3916 
3917 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
3918 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3919 
3920 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
3921 					throw new Exception("couldn't make pulse timer");
3922 
3923 				ep.epoll_event ev = void;
3924 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3925 				ev.events = ep.EPOLLIN;
3926 				ev.data.fd = pulseFd;
3927 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
3928 			}
3929 
3930 			// eventfd for custom events
3931 			if (customEventFDWrite == -1) {
3932 				customEventFDWrite = eventfd(0, 0);
3933 				customEventFDRead = customEventFDWrite;
3934 				if (customEventFDRead >= 0) {
3935 					ep.epoll_event ev = void;
3936 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3937 					ev.events = ep.EPOLLIN;
3938 					ev.data.fd = customEventFDRead;
3939 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
3940 				} else {
3941 					// this is something that should not be; better be safe than sorry
3942 					throw new Exception("can't create eventfd for custom event processing");
3943 				}
3944 			}
3945 
3946 			if (customSignalFD == -1) {
3947 				import core.sys.linux.sys.signalfd;
3948 
3949 				sigset_t sigset;
3950 				auto err = sigemptyset(&sigset);
3951 				assert(!err);
3952 				err = sigaddset(&sigset, SIGINT);
3953 				assert(!err);
3954 				err = sigaddset(&sigset, SIGHUP);
3955 				assert(!err);
3956 				err = sigprocmask(SIG_BLOCK, &sigset, null);
3957 				assert(!err);
3958 
3959 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
3960 				assert(customSignalFD != -1);
3961 
3962 				ep.epoll_event ev = void;
3963 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3964 				ev.events = ep.EPOLLIN;
3965 				ev.data.fd = customSignalFD;
3966 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
3967 			}
3968 		} else version(Posix) {
3969 			prepareEventLoop();
3970 			if (customEventFDRead == -1) {
3971 				int[2] bfr;
3972 				import core.sys.posix.unistd;
3973 				auto ret = pipe(bfr);
3974 				if(ret == -1) throw new Exception("pipe");
3975 				customEventFDRead = bfr[0];
3976 				customEventFDWrite = bfr[1];
3977 			}
3978 
3979 		}
3980 
3981 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
3982 
3983 		version(linux) {
3984 			this.mtLock();
3985 			scope(exit) this.mtUnlock();
3986 			XPending(display); // no, really
3987 		}
3988 
3989 		disposed = false;
3990 	}
3991 
3992 	bool disposed = true;
3993 	version(X11)
3994 		int displayFd = -1;
3995 
3996 	version(with_eventloop)
3997 	void dispose() {}
3998 	else
3999 	void dispose() {
4000 		disposed = true;
4001 		version(X11) {
4002 			if(pulseFd != -1) {
4003 				import unix = core.sys.posix.unistd;
4004 				unix.close(pulseFd);
4005 				pulseFd = -1;
4006 			}
4007 
4008 				version(linux)
4009 				if(displayFd != -1) {
4010 					// 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
4011 					ep.epoll_event ev = void;
4012 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4013 					ev.events = ep.EPOLLIN;
4014 					ev.data.fd = displayFd;
4015 					//import std.conv;
4016 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4017 					displayFd = -1;
4018 				}
4019 
4020 		} else version(Windows) {
4021 			if(pulser !is null) {
4022 				pulser.destroy();
4023 				pulser = null;
4024 			}
4025 			if (customEventH !is null) {
4026 				CloseHandle(customEventH);
4027 				customEventH = null;
4028 			}
4029 		}
4030 	}
4031 
4032 	this(long pulseTimeout, void delegate() handlePulse) {
4033 		this.pulseTimeout = pulseTimeout;
4034 		this.handlePulse = handlePulse;
4035 		initialize(pulseTimeout);
4036 	}
4037 
4038 	private long pulseTimeout;
4039 	void delegate() handlePulse;
4040 
4041 	~this() {
4042 		dispose();
4043 	}
4044 
4045 	version(Posix)
4046 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4047 	version(Posix)
4048 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4049 	version(linux)
4050 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4051 	version(Windows)
4052 	ref auto customEventH() { return SimpleWindow.customEventH; }
4053 
4054 	version(with_eventloop) {
4055 		int loopHelper(bool delegate() whileCondition) {
4056 			// FIXME: whileCondition
4057 			import arsd.eventloop;
4058 			loop();
4059 			return 0;
4060 		}
4061 	} else
4062 	int loopHelper(bool delegate() whileCondition) {
4063 		version(X11) {
4064 			bool done = false;
4065 
4066 			XFlush(display);
4067 			insideXEventLoop = true;
4068 			scope(exit) insideXEventLoop = false;
4069 
4070 			version(linux) {
4071 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4072 					bool forceXPending = false;
4073 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4074 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4075 					{
4076 						this.mtLock();
4077 						scope(exit) this.mtUnlock();
4078 						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
4079 					}
4080 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4081 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4082 					if(nfds == -1) {
4083 						if(err.errno == err.EINTR) {
4084 							//if(forceXPending) goto xpending;
4085 							continue; // interrupted by signal, just try again
4086 						}
4087 						throw new Exception("epoll wait failure");
4088 					}
4089 
4090 					SimpleWindow.processAllCustomEvents(); // anyway
4091 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4092 					foreach(idx; 0 .. nfds) {
4093 						if(done) break;
4094 						auto fd = events[idx].data.fd;
4095 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4096 						auto flags = events[idx].events;
4097 						if(flags & ep.EPOLLIN) {
4098 							if (fd == customSignalFD) {
4099 								version(linux) {
4100 									import core.sys.linux.sys.signalfd;
4101 									import core.sys.posix.unistd : read;
4102 									signalfd_siginfo info;
4103 									read(customSignalFD, &info, info.sizeof);
4104 
4105 									auto sig = info.ssi_signo;
4106 
4107 									if(EventLoop.get.signalHandler !is null) {
4108 										EventLoop.get.signalHandler()(sig);
4109 									} else {
4110 										EventLoop.get.exit();
4111 									}
4112 								}
4113 							} else if(fd == display.fd) {
4114 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
4115 								this.mtLock();
4116 								scope(exit) this.mtUnlock();
4117 								while(!done && XPending(display)) {
4118 									done = doXNextEvent(this.display);
4119 								}
4120 								forceXPending = false;
4121 							} else if(fd == pulseFd) {
4122 								long expirationCount;
4123 								// 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...
4124 
4125 								handlePulse();
4126 
4127 								// read just to clear the buffer so poll doesn't trigger again
4128 								// BTW I read AFTER the pulse because if the pulse handler takes
4129 								// a lot of time to execute, we don't want the app to get stuck
4130 								// in a loop of timer hits without a chance to do anything else
4131 								//
4132 								// IOW handlePulse happens at most once per pulse interval.
4133 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4134 								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
4135 							} else if (fd == customEventFDRead) {
4136 								// we have some custom events; process 'em
4137 								import core.sys.posix.unistd : read;
4138 								ulong n;
4139 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4140 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4141 								//SimpleWindow.processAllCustomEvents();
4142 							} else {
4143 								// some other timer
4144 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
4145 
4146 								if(Timer* t = fd in Timer.mapping)
4147 									(*t).trigger();
4148 
4149 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4150 									(*pfr).ready(flags);
4151 
4152 								// or i might add support for other FDs too
4153 								// but for now it is just timer
4154 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4155 							}
4156 						}
4157 						if(flags & ep.EPOLLHUP) {
4158 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4159 								(*pfr).hup(flags);
4160 							if(globalHupHandler)
4161 								globalHupHandler(fd, flags);
4162 						}
4163 						/+
4164 						} else {
4165 							// not interested in OUT, we are just reading here.
4166 							//
4167 							// error or hup might also be reported
4168 							// but it shouldn't here since we are only
4169 							// using a few types of FD and Xlib will report
4170 							// if it dies.
4171 							// so instead of thoughtfully handling it, I'll
4172 							// just throw. for now at least
4173 
4174 							throw new Exception("epoll did something else");
4175 						}
4176 						+/
4177 					}
4178 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4179 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4180 					xpending:
4181 					if (!done && forceXPending) {
4182 						this.mtLock();
4183 						scope(exit) this.mtUnlock();
4184 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4185 						while(!done && XPending(display)) {
4186 							done = doXNextEvent(this.display);
4187 						}
4188 					}
4189 				}
4190 			} else {
4191 				// Generic fallback: yes to simple pulse support,
4192 				// but NO timer support!
4193 
4194 				// FIXME: we could probably support the POSIX timer_create
4195 				// signal-based option, but I'm in no rush to write it since
4196 				// I prefer the fd-based functions.
4197 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4198 
4199 					import core.sys.posix.poll;
4200 
4201 					pollfd[] pfds;
4202 					pollfd[32] pfdsBuffer;
4203 					auto len = PosixFdReader.mapping.length + 2;
4204 					// FIXME: i should just reuse the buffer
4205 					if(len < pfdsBuffer.length)
4206 						pfds = pfdsBuffer[0 .. len];
4207 					else
4208 						pfds = new pollfd[](len);
4209 
4210 					pfds[0].fd = display.fd;
4211 					pfds[0].events = POLLIN;
4212 					pfds[0].revents = 0;
4213 
4214 					int slot = 1;
4215 
4216 					if(customEventFDRead != -1) {
4217 						pfds[slot].fd = customEventFDRead;
4218 						pfds[slot].events = POLLIN;
4219 						pfds[slot].revents = 0;
4220 
4221 						slot++;
4222 					}
4223 
4224 					foreach(fd, obj; PosixFdReader.mapping) {
4225 						if(!obj.enabled) continue;
4226 						pfds[slot].fd = fd;
4227 						pfds[slot].events = POLLIN;
4228 						pfds[slot].revents = 0;
4229 
4230 						slot++;
4231 					}
4232 
4233 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4234 					if(ret == -1) throw new Exception("poll");
4235 
4236 					if(ret == 0) {
4237 						// FIXME it may not necessarily time out if events keep coming
4238 						if(handlePulse !is null)
4239 							handlePulse();
4240 					} else {
4241 						foreach(s; 0 .. slot) {
4242 							if(pfds[s].revents == 0) continue;
4243 
4244 							if(pfds[s].fd == display.fd) {
4245 								while(!done && XPending(display)) {
4246 									this.mtLock();
4247 									scope(exit) this.mtUnlock();
4248 									done = doXNextEvent(this.display);
4249 								}
4250 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4251 
4252 								import core.sys.posix.unistd : read;
4253 								ulong n;
4254 								read(customEventFDRead, &n, n.sizeof);
4255 								SimpleWindow.processAllCustomEvents();
4256 							} else {
4257 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4258 								if(pfds[s].revents & POLLNVAL) {
4259 									obj.dispose();
4260 								} else {
4261 									obj.ready(pfds[s].revents);
4262 								}
4263 							}
4264 
4265 							ret--;
4266 							if(ret == 0) break;
4267 						}
4268 					}
4269 				}
4270 			}
4271 		}
4272 		
4273 		version(Windows) {
4274 			int ret = -1;
4275 			MSG message;
4276 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4277 				eventLoopRound++;
4278 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4279 				auto waitResult = MsgWaitForMultipleObjectsEx(
4280 					cast(int) handles.length, handles.ptr,
4281 					(wto == 0 ? INFINITE : wto), /* timeout */
4282 					0x04FF, /* QS_ALLINPUT */
4283 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4284 
4285 				SimpleWindow.processAllCustomEvents(); // anyway
4286 				enum WAIT_OBJECT_0 = 0;
4287 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4288 					auto h = handles[waitResult - WAIT_OBJECT_0];
4289 					if(auto e = h in WindowsHandleReader.mapping) {
4290 						(*e).ready();
4291 					}
4292 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4293 					// message ready
4294 					int count;
4295 					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
4296 						ret = GetMessage(&message, null, 0, 0);
4297 						if(ret == -1)
4298 							throw new Exception("GetMessage failed");
4299 						TranslateMessage(&message);
4300 						DispatchMessage(&message);
4301 
4302 						count++;
4303 						if(count > 10)
4304 							break; // take the opportunity to catch up on other events
4305 
4306 						if(ret == 0) { // WM_QUIT
4307 							EventLoop.quitApplication();
4308 							break;
4309 						}
4310 					}
4311 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4312 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4313 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4314 					// timeout, should never happen since we aren't using it
4315 				} else if(waitResult == 0xFFFFFFFF) {
4316 						// failed
4317 						throw new Exception("MsgWaitForMultipleObjectsEx failed");
4318 				} else {
4319 					// idk....
4320 				}
4321 			}
4322 
4323 			// return message.wParam;
4324 			return 0;
4325 		} else {
4326 			return 0;
4327 		}
4328 	}
4329 
4330 	int run(bool delegate() whileCondition = null) {
4331 		if(disposed)
4332 			initialize(this.pulseTimeout);
4333 
4334 		version(X11) {
4335 			try {
4336 				return loopHelper(whileCondition);
4337 			} catch(XDisconnectException e) {
4338 				if(e.userRequested) {
4339 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4340 						item.discardConnectionState();
4341 					XCloseDisplay(XDisplayConnection.display);
4342 				}
4343 
4344 				XDisplayConnection.display = null;
4345 
4346 				this.dispose();
4347 
4348 				throw e;
4349 			}
4350 		} else {
4351 			return loopHelper(whileCondition);
4352 		}
4353 	}
4354 }
4355 
4356 
4357 /++
4358 	Provides an icon on the system notification area (also known as the system tray).
4359 
4360 
4361 	If a notification area is not available with the NotificationIcon object is created,
4362 	it will silently succeed and simply attempt to create one when an area becomes available.
4363 
4364 
4365 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
4366 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
4367 	use the older version.
4368 +/
4369 version(OSXCocoa) {} else // NotYetImplementedException
4370 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4371 
4372 	version(X11) {
4373 		void recreateAfterDisconnect() {
4374 			stateDiscarded = false;
4375 			clippixmap = None;
4376 			throw new Exception("NOT IMPLEMENTED");
4377 		}
4378 
4379 		bool stateDiscarded;
4380 		void discardConnectionState() {
4381 			stateDiscarded = true;
4382 		}
4383 	}
4384 
4385 
4386 	version(X11) {
4387 		Image img;
4388 
4389 		NativeEventHandler getNativeEventHandler() {
4390 			return delegate int(XEvent e) {
4391 				switch(e.type) {
4392 					case EventType.Expose:
4393 					//case EventType.VisibilityNotify:
4394 						redraw();
4395 					break;
4396 					case EventType.ClientMessage:
4397 						version(sddddd) {
4398 						import std.stdio;
4399 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4400 						writeln("\t", e.xclient.format);
4401 						writeln("\t", e.xclient.data.l);
4402 						}
4403 					break;
4404 					case EventType.ButtonPress:
4405 						auto event = e.xbutton;
4406 						if (onClick !is null || onClickEx !is null) {
4407 							MouseButton mb = cast(MouseButton)0;
4408 							switch (event.button) {
4409 								case 1: mb = MouseButton.left; break; // left
4410 								case 2: mb = MouseButton.middle; break; // middle
4411 								case 3: mb = MouseButton.right; break; // right
4412 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4413 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4414 								case 6: break; // idk
4415 								case 7: break; // idk
4416 								case 8: mb = MouseButton.backButton; break;
4417 								case 9: mb = MouseButton.forwardButton; break;
4418 								default:
4419 							}
4420 							if (mb) {
4421 								try { onClick()(mb); } catch (Exception) {}
4422 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4423 							}
4424 						}
4425 					break;
4426 					case EventType.EnterNotify:
4427 						if (onEnter !is null) {
4428 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4429 						}
4430 						break;
4431 					case EventType.LeaveNotify:
4432 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4433 						break;
4434 					case EventType.DestroyNotify:
4435 						active = false;
4436 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4437 					break;
4438 					case EventType.ConfigureNotify:
4439 						auto event = e.xconfigure;
4440 						this.width = event.width;
4441 						this.height = event.height;
4442 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4443 						redraw();
4444 					break;
4445 					default: return 1;
4446 				}
4447 				return 1;
4448 			};
4449 		}
4450 
4451 		/* private */ void hideBalloon() {
4452 			balloon.close();
4453 			version(with_timer)
4454 				timer.destroy();
4455 			balloon = null;
4456 			version(with_timer)
4457 				timer = null;
4458 		}
4459 
4460 		void redraw() {
4461 			if (!active) return;
4462 
4463 			auto display = XDisplayConnection.get;
4464 			auto gc = DefaultGC(display, DefaultScreen(display));
4465 			XClearWindow(display, nativeHandle);
4466 
4467 			XSetClipMask(display, gc, clippixmap);
4468 
4469 			XSetForeground(display, gc,
4470 				cast(uint) 0 << 16 |
4471 				cast(uint) 0 << 8 |
4472 				cast(uint) 0);
4473 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4474 
4475 			if (img is null) {
4476 				XSetForeground(display, gc,
4477 					cast(uint) 0 << 16 |
4478 					cast(uint) 127 << 8 |
4479 					cast(uint) 0);
4480 				XFillArc(display, nativeHandle,
4481 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4482 			} else {
4483 				int dx = 0;
4484 				int dy = 0;
4485 				if(width > img.width)
4486 					dx = (width - img.width) / 2;
4487 				if(height > img.height)
4488 					dy = (height - img.height) / 2;
4489 				XSetClipOrigin(display, gc, dx, dy);
4490 
4491 				if (img.usingXshm)
4492 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
4493 				else
4494 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
4495 			}
4496 			XSetClipMask(display, gc, None);
4497 			flushGui();
4498 		}
4499 
4500 		static Window getTrayOwner() {
4501 			auto display = XDisplayConnection.get;
4502 			auto i = cast(int) DefaultScreen(display);
4503 			if(i < 10 && i >= 0) {
4504 				static Atom atom;
4505 				if(atom == None)
4506 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4507 				return XGetSelectionOwner(display, atom);
4508 			}
4509 			return None;
4510 		}
4511 
4512 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4513 			auto to = getTrayOwner();
4514 			auto display = XDisplayConnection.get;
4515 			XEvent ev;
4516 			ev.xclient.type = EventType.ClientMessage;
4517 			ev.xclient.window = to;
4518 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4519 			ev.xclient.format = 32;
4520 			ev.xclient.data.l[0] = CurrentTime;
4521 			ev.xclient.data.l[1] = message;
4522 			ev.xclient.data.l[2] = d1;
4523 			ev.xclient.data.l[3] = d2;
4524 			ev.xclient.data.l[4] = d3;
4525 
4526 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4527 		}
4528 
4529 		private static NotificationAreaIcon[] activeIcons;
4530 
4531 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4532 		private void newManager() {
4533 			close();
4534 			createXWin();
4535 
4536 			if(this.clippixmap)
4537 				XFreePixmap(XDisplayConnection.get, clippixmap);
4538 			if(this.originalMemoryImage)
4539 				this.icon = this.originalMemoryImage;
4540 			else if(this.img)
4541 				this.icon = this.img;
4542 		}
4543 
4544 		private void createXWin () {
4545 			// create window
4546 			auto display = XDisplayConnection.get;
4547 
4548 			// to check for MANAGER on root window to catch new/changed tray owners
4549 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4550 			// so if a thing does appear, we can handle it
4551 			foreach(ai; activeIcons)
4552 				if(ai is this)
4553 					goto alreadythere;
4554 			activeIcons ~= this;
4555 			alreadythere:
4556 
4557 			// and check for an existing tray
4558 			auto trayOwner = getTrayOwner();
4559 			if(trayOwner == None)
4560 				return;
4561 				//throw new Exception("No notification area found");
4562 
4563 			Visual* v = cast(Visual*) CopyFromParent;
4564 			/+
4565 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4566 			if(visualProp !is null) {
4567 				c_ulong[] info = cast(c_ulong[]) visualProp;
4568 				if(info.length == 1) {
4569 					auto vid = info[0];
4570 					int returned;
4571 					XVisualInfo t;
4572 					t.visualid = vid;
4573 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4574 					if(got !is null) {
4575 						if(returned == 1) {
4576 							v = got.visual;
4577 							import std.stdio;
4578 							writeln("using special visual ", *got);
4579 						}
4580 						XFree(got);
4581 					}
4582 				}
4583 			}
4584 			+/
4585 
4586 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
4587 			assert(nativeWindow);
4588 
4589 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4590 
4591 			nativeHandle = nativeWindow;
4592 
4593 			///+
4594 			arch_ulong[2] info;
4595 			info[0] = 0;
4596 			info[1] = 1;
4597 
4598 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4599 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4600 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4601 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4602 
4603 			XChangeProperty(
4604 				display,
4605 				nativeWindow,
4606 				GetAtom!("_XEMBED_INFO", true)(display),
4607 				GetAtom!("_XEMBED_INFO", true)(display),
4608 				32 /* bits */,
4609 				0 /*PropModeReplace*/,
4610 				info.ptr,
4611 				2);
4612 
4613 			import core.sys.posix.unistd;
4614 			arch_ulong pid = getpid();
4615 
4616 			XChangeProperty(
4617 				display,
4618 				nativeWindow,
4619 				GetAtom!("_NET_WM_PID", true)(display),
4620 				XA_CARDINAL,
4621 				32 /* bits */,
4622 				0 /*PropModeReplace*/,
4623 				&pid,
4624 				1);
4625 
4626 			updateNetWmIcon();
4627 
4628 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4629 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4630 				XClassHint klass;
4631 				XWMHints wh;
4632 				XSizeHints size;
4633 				klass.res_name = sdpyWindowClassStr;
4634 				klass.res_class = sdpyWindowClassStr;
4635 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4636 			}
4637 
4638 				// believe it or not, THIS is what xfce needed for the 9999 issue
4639 				XSizeHints sh;
4640 					c_long spr;
4641 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4642 					sh.flags |= PMaxSize | PMinSize;
4643 				// FIXME maybe nicer resizing
4644 				sh.min_width = 16;
4645 				sh.min_height = 16;
4646 				sh.max_width = 16;
4647 				sh.max_height = 16;
4648 				XSetWMNormalHints(display, nativeWindow, &sh);
4649 
4650 
4651 			//+/
4652 
4653 
4654 			XSelectInput(display, nativeWindow,
4655 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4656 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4657 
4658 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4659 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4660 			active = true;
4661 		}
4662 
4663 		void updateNetWmIcon() {
4664 			if(img is null) return;
4665 			auto display = XDisplayConnection.get;
4666 			// FIXME: ensure this is correct
4667 			arch_ulong[] buffer;
4668 			auto imgMi = img.toTrueColorImage;
4669 			buffer ~= imgMi.width;
4670 			buffer ~= imgMi.height;
4671 			foreach(c; imgMi.imageData.colors) {
4672 				arch_ulong b;
4673 				b |= c.a << 24;
4674 				b |= c.r << 16;
4675 				b |= c.g << 8;
4676 				b |= c.b;
4677 				buffer ~= b;
4678 			}
4679 
4680 			XChangeProperty(
4681 				display,
4682 				nativeHandle,
4683 				GetAtom!"_NET_WM_ICON"(display),
4684 				GetAtom!"CARDINAL"(display),
4685 				32 /* bits */,
4686 				0 /*PropModeReplace*/,
4687 				buffer.ptr,
4688 				cast(int) buffer.length);
4689 		}
4690 
4691 
4692 
4693 		private SimpleWindow balloon;
4694 		version(with_timer)
4695 		private Timer timer;
4696 
4697 		private Window nativeHandle;
4698 		private Pixmap clippixmap = None;
4699 		private int width = 16;
4700 		private int height = 16;
4701 		private bool active = false;
4702 
4703 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4704 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4705 		void delegate () onLeave; /// X11 only.
4706 
4707 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4708 
4709 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4710 		void getWindowRect (out int x, out int y, out int width, out int height) {
4711 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4712 			Window dummyw;
4713 			auto dpy = XDisplayConnection.get;
4714 			//XWindowAttributes xwa;
4715 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4716 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4717 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4718 			width = this.width;
4719 			height = this.height;
4720 		}
4721 	}
4722 
4723 	/+
4724 		What I actually want from this:
4725 
4726 		* set / change: icon, tooltip
4727 		* handle: mouse click, right click
4728 		* show: notification bubble.
4729 	+/
4730 
4731 	version(Windows) {
4732 		WindowsIcon win32Icon;
4733 		HWND hwnd;
4734 
4735 		NOTIFYICONDATAW data;
4736 
4737 		NativeEventHandler getNativeEventHandler() {
4738 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
4739 				if(msg == WM_USER) {
4740 					auto event = LOWORD(lParam);
4741 					auto iconId = HIWORD(lParam);
4742 					//auto x = GET_X_LPARAM(wParam);
4743 					//auto y = GET_Y_LPARAM(wParam);
4744 					switch(event) {
4745 						case WM_LBUTTONDOWN:
4746 							onClick()(MouseButton.left);
4747 						break;
4748 						case WM_RBUTTONDOWN:
4749 							onClick()(MouseButton.right);
4750 						break;
4751 						case WM_MBUTTONDOWN:
4752 							onClick()(MouseButton.middle);
4753 						break;
4754 						case WM_MOUSEMOVE:
4755 							// sent, we could use it.
4756 						break;
4757 						case WM_MOUSEWHEEL:
4758 							// NOT SENT
4759 						break;
4760 						//case NIN_KEYSELECT:
4761 						//case NIN_SELECT:
4762 						//break;
4763 						default: {}
4764 					}
4765 				}
4766 				return 0;
4767 			};
4768 		}
4769 
4770 		enum NIF_SHOWTIP = 0x00000080;
4771 
4772 		private static struct NOTIFYICONDATAW {
4773 			DWORD cbSize;
4774 			HWND  hWnd;
4775 			UINT  uID;
4776 			UINT  uFlags;
4777 			UINT  uCallbackMessage;
4778 			HICON hIcon;
4779 			WCHAR[128] szTip;
4780 			DWORD dwState;
4781 			DWORD dwStateMask;
4782 			WCHAR[256] szInfo;
4783 			union {
4784 				UINT uTimeout;
4785 				UINT uVersion;
4786 			}
4787 			WCHAR[64] szInfoTitle;
4788 			DWORD dwInfoFlags;
4789 			GUID  guidItem;
4790 			HICON hBalloonIcon;
4791 		}
4792 
4793 	}
4794 
4795 	/++
4796 		Note that on Windows, only left, right, and middle buttons are sent.
4797 		Mouse wheel buttons are NOT set, so don't rely on those events if your
4798 		program is meant to be used on Windows too.
4799 	+/
4800 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
4801 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
4802 		// but on X, we need an Image, so its canonical ctor is there. They should
4803 		// forward to each other though.
4804 		version(X11) {
4805 			this.name = name;
4806 			this.onClick = onClick;
4807 			createXWin();
4808 			this.icon = icon;
4809 		} else version(Windows) {
4810 			this.onClick = onClick;
4811 			this.win32Icon = new WindowsIcon(icon);
4812 
4813 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
4814 
4815 			static bool registered = false;
4816 			if(!registered) {
4817 				WNDCLASSEX wc;
4818 				wc.cbSize = wc.sizeof;
4819 				wc.hInstance = hInstance;
4820 				wc.lpfnWndProc = &WndProc;
4821 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
4822 				if(!RegisterClassExW(&wc))
4823 					throw new WindowsApiException("RegisterClass");
4824 				registered = true;
4825 			}
4826 
4827 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
4828 			if(hwnd is null)
4829 				throw new Exception("CreateWindow");
4830 
4831 			data.cbSize = data.sizeof;
4832 			data.hWnd = hwnd;
4833 			data.uID = cast(uint) cast(void*) this;
4834 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
4835 				// NIF_INFO means show balloon
4836 			data.uCallbackMessage = WM_USER;
4837 			data.hIcon = this.win32Icon.hIcon;
4838 			data.szTip = ""; // FIXME
4839 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4840 			data.dwStateMask = NIS_HIDDEN; // windows vista
4841 
4842 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
4843 
4844 
4845 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
4846 
4847 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
4848 		} else version(OSXCocoa) {
4849 			throw new NotYetImplementedException();
4850 		} else static assert(0);
4851 	}
4852 
4853 	/// ditto
4854 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
4855 		version(X11) {
4856 			this.onClick = onClick;
4857 			this.name = name;
4858 			createXWin();
4859 			this.icon = icon;
4860 		} else version(Windows) {
4861 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
4862 		} else version(OSXCocoa) {
4863 			throw new NotYetImplementedException();
4864 		} else static assert(0);
4865 	}
4866 
4867 	version(X11) {
4868 		/++
4869 			X-specific extension (for now at least)
4870 		+/
4871 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4872 			this.onClickEx = onClickEx;
4873 			createXWin();
4874 			if (icon !is null) this.icon = icon;
4875 		}
4876 
4877 		/// ditto
4878 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4879 			this.onClickEx = onClickEx;
4880 			createXWin();
4881 			this.icon = icon;
4882 		}
4883 	}
4884 
4885 	private void delegate (MouseButton button) onClick_;
4886 
4887 	///
4888 	@property final void delegate(MouseButton) onClick() {
4889 		if(onClick_ is null)
4890 			onClick_ = delegate void(MouseButton) {};
4891 		return onClick_;
4892 	}
4893 
4894 	/// ditto
4895 	@property final void onClick(void delegate(MouseButton) handler) {
4896 		// I made this a property setter so we can wrap smaller arg
4897 		// delegates and just forward all to onClickEx or something.
4898 		onClick_ = handler;
4899 	}
4900 
4901 
4902 	string name_;
4903 	@property void name(string n) {
4904 		name_ = n;
4905 	}
4906 
4907 	@property string name() {
4908 		return name_;
4909 	}
4910 
4911 	private MemoryImage originalMemoryImage;
4912 
4913 	///
4914 	@property void icon(MemoryImage i) {
4915 		version(X11) {
4916 			this.originalMemoryImage = i;
4917 			if (!active) return;
4918 			if (i !is null) {
4919 				this.img = Image.fromMemoryImage(i);
4920 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
4921 				//import std.stdio; writeln("using pixmap ", clippixmap);
4922 				updateNetWmIcon();
4923 				redraw();
4924 			} else {
4925 				if (this.img !is null) {
4926 					this.img = null;
4927 					redraw();
4928 				}
4929 			}
4930 		} else version(Windows) {
4931 			this.win32Icon = new WindowsIcon(i);
4932 
4933 			data.uFlags = NIF_ICON;
4934 			data.hIcon = this.win32Icon.hIcon;
4935 
4936 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4937 		} else version(OSXCocoa) {
4938 			throw new NotYetImplementedException();
4939 		} else static assert(0);
4940 	}
4941 
4942 	/// ditto
4943 	@property void icon (Image i) {
4944 		version(X11) {
4945 			if (!active) return;
4946 			if (i !is img) {
4947 				originalMemoryImage = null;
4948 				img = i;
4949 				redraw();
4950 			}
4951 		} else version(Windows) {
4952 			this.icon(i is null ? null : i.toTrueColorImage());
4953 		} else version(OSXCocoa) {
4954 			throw new NotYetImplementedException();
4955 		} else static assert(0);
4956 	}
4957 
4958 	/++
4959 		Shows a balloon notification. You can only show one balloon at a time, if you call
4960 		it twice while one is already up, the first balloon will be replaced.
4961 		
4962 		
4963 		The user is free to block notifications and they will automatically disappear after
4964 		a timeout period.
4965 
4966 		Params:
4967 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
4968 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
4969 			icon = the icon to display with the notification. If null, it uses your existing icon.
4970 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
4971 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
4972 	+/
4973 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
4974 		bool useCustom = true;
4975 		version(libnotify) {
4976 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
4977 			try {
4978 				if(!active) return;
4979 
4980 				if(libnotify is null) {
4981 					libnotify = new C_DynamicLibrary("libnotify.so");
4982 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
4983 				}
4984 
4985 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
4986 
4987 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
4988 
4989 				if(onclick) {
4990 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
4991 					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);
4992 					libnotify_action_delegates_count++;
4993 				}
4994 
4995 				// FIXME icon
4996 
4997 				// set hint image-data
4998 				// set default action for onclick
4999 
5000 				void* error;
5001 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5002 
5003 				useCustom = false;
5004 			} catch(Exception e) {
5005 
5006 			}
5007 		}
5008 		
5009 		version(X11) {
5010 		if(useCustom) {
5011 			if(!active) return;
5012 			if(balloon) {
5013 				hideBalloon();
5014 			}
5015 			// I know there are two specs for this, but one is never
5016 			// implemented by any window manager I have ever seen, and
5017 			// the other is a bloated mess and too complicated for simpledisplay...
5018 			// so doing my own little window instead.
5019 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5020 
5021 			int x, y, width, height;
5022 			getWindowRect(x, y, width, height);
5023 
5024 			int bx = x - balloon.width;
5025 			int by = y - balloon.height;
5026 			if(bx < 0)
5027 				bx = x + width + balloon.width;
5028 			if(by < 0)
5029 				by = y + height;
5030 
5031 			// just in case, make sure it is actually on scren
5032 			if(bx < 0)
5033 				bx = 0;
5034 			if(by < 0)
5035 				by = 0;
5036 
5037 			balloon.move(bx, by);
5038 			auto painter = balloon.draw();
5039 			painter.fillColor = Color(220, 220, 220);
5040 			painter.outlineColor = Color.black;
5041 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5042 			auto iconWidth = icon is null ? 0 : icon.width;
5043 			if(icon)
5044 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5045 			iconWidth += 6; // margin around the icon
5046 
5047 			// draw a close button
5048 			painter.outlineColor = Color(44, 44, 44);
5049 			painter.fillColor = Color(255, 255, 255);
5050 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5051 			painter.pen = Pen(Color.black, 3);
5052 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5053 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5054 			painter.pen = Pen(Color.black, 1);
5055 			painter.fillColor = Color(220, 220, 220);
5056 
5057 			// Draw the title and message
5058 			painter.drawText(Point(4 + iconWidth, 4), title);
5059 			painter.drawLine(
5060 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5061 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5062 			);
5063 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5064 
5065 			balloon.setEventHandlers(
5066 				(MouseEvent ev) {
5067 					if(ev.type == MouseEventType.buttonPressed) {
5068 						if(ev.x > balloon.width - 16 && ev.y < 16)
5069 							hideBalloon();
5070 						else if(onclick)
5071 							onclick();
5072 					}
5073 				}
5074 			);
5075 			balloon.show();
5076 
5077 			version(with_timer)
5078 			timer = new Timer(timeout, &hideBalloon);
5079 			else {} // FIXME
5080 		}
5081 		} else version(Windows) {
5082 			enum NIF_INFO = 0x00000010;
5083 
5084 			data.uFlags = NIF_INFO;
5085 
5086 			// FIXME: go back to the last valid unicode code point
5087 			if(title.length > 40)
5088 				title = title[0 .. 40];
5089 			if(message.length > 220)
5090 				message = message[0 .. 220];
5091 
5092 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5093 			enum NIIF_LARGE_ICON  = 0x00000020;
5094 			enum NIIF_NOSOUND = 0x00000010;
5095 			enum NIIF_USER = 0x00000004;
5096 			enum NIIF_ERROR = 0x00000003;
5097 			enum NIIF_WARNING = 0x00000002;
5098 			enum NIIF_INFO = 0x00000001;
5099 			enum NIIF_NONE = 0;
5100 
5101 			WCharzBuffer t = WCharzBuffer(title);
5102 			WCharzBuffer m = WCharzBuffer(message);
5103 
5104 			t.copyInto(data.szInfoTitle);
5105 			m.copyInto(data.szInfo);
5106 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5107 
5108 			if(icon !is null) {
5109 				auto i = new WindowsIcon(icon);
5110 				data.hBalloonIcon = i.hIcon;
5111 				data.dwInfoFlags |= NIIF_USER;
5112 			}
5113 
5114 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5115 		} else version(OSXCocoa) {
5116 			throw new NotYetImplementedException();
5117 		} else static assert(0);
5118 	}
5119 
5120 	///
5121 	//version(Windows)
5122 	void show() {
5123 		version(X11) {
5124 			if(!hidden)
5125 				return;
5126 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5127 			hidden = false;
5128 		} else version(Windows) {
5129 			data.uFlags = NIF_STATE;
5130 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5131 			data.dwStateMask = NIS_HIDDEN; // windows vista
5132 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5133 		} else version(OSXCocoa) {
5134 			throw new NotYetImplementedException();
5135 		} else static assert(0);
5136 	}
5137 
5138 	version(X11)
5139 		bool hidden = false;
5140 
5141 	///
5142 	//version(Windows)
5143 	void hide() {
5144 		version(X11) {
5145 			if(hidden)
5146 				return;
5147 			hidden = true;
5148 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5149 		} else version(Windows) {
5150 			data.uFlags = NIF_STATE;
5151 			data.dwState = NIS_HIDDEN; // windows vista
5152 			data.dwStateMask = NIS_HIDDEN; // windows vista
5153 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5154 		} else version(OSXCocoa) {
5155 			throw new NotYetImplementedException();
5156 		} else static assert(0);
5157 	}
5158 
5159 	///
5160 	void close () {
5161 		version(X11) {
5162 			if (active) {
5163 				active = false; // event handler will set this too, but meh
5164 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5165 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5166 				flushGui();
5167 			}
5168 		} else version(Windows) {
5169 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5170 		} else version(OSXCocoa) {
5171 			throw new NotYetImplementedException();
5172 		} else static assert(0);
5173 	}
5174 
5175 	~this() {
5176 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5177 		version(X11)
5178 			if(clippixmap != None)
5179 				XFreePixmap(XDisplayConnection.get, clippixmap);
5180 		close();
5181 	}
5182 }
5183 
5184 version(X11)
5185 /// Call `XFreePixmap` on the return value.
5186 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5187 	char[] data = new char[](i.width * i.height / 8 + 2);
5188 	data[] = 0;
5189 
5190 	int bitOffset = 0;
5191 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5192 		ubyte v = c.a > 128 ? 1 : 0;
5193 		data[bitOffset / 8] |= v << (bitOffset%8);
5194 		bitOffset++;
5195 	}
5196 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5197 	return handle;
5198 }
5199 
5200 
5201 // basic functions to make timers
5202 /**
5203 	A timer that will trigger your function on a given interval.
5204 
5205 
5206 	You create a timer with an interval and a callback. It will continue
5207 	to fire on the interval until it is destroyed.
5208 
5209 	There are currently no one-off timers (instead, just create one and
5210 	destroy it when it is triggered) nor are there pause/resume functions -
5211 	the timer must again be destroyed and recreated if you want to pause it.
5212 
5213 	auto timer = new Timer(50, { it happened!; });
5214 	timer.destroy();
5215 
5216 	Timers can only be expected to fire when the event loop is running and only
5217 	once per iteration through the event loop.
5218 
5219 	History:
5220 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5221 		slow could lock up the event loop. It now guarantees other things will
5222 		get a chance to run between timer calls, even if that means not keeping up
5223 		with the requested interval.
5224 */
5225 version(with_timer) {
5226 class Timer {
5227 // FIXME: needs pause and unpause
5228 	// FIXME: I might add overloads for ones that take a count of
5229 	// how many elapsed since last time (on Windows, it will divide
5230 	// the ticks thing given, on Linux it is just available) and
5231 	// maybe one that takes an instance of the Timer itself too
5232 	/// Create a timer with a callback when it triggers.
5233 	this(int intervalInMilliseconds, void delegate() onPulse) {
5234 		assert(onPulse !is null);
5235 
5236 		this.intervalInMilliseconds = intervalInMilliseconds;
5237 		this.onPulse = onPulse;
5238 
5239 		version(Windows) {
5240 			/*
5241 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5242 			if(handle == 0)
5243 				throw new Exception("SetTimer fail");
5244 			*/
5245 
5246 			// thanks to Archival 998 for the WaitableTimer blocks
5247 			handle = CreateWaitableTimer(null, false, null);
5248 			long initialTime = -intervalInMilliseconds;
5249 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5250 				throw new Exception("SetWaitableTimer Failed");
5251 
5252 			mapping[handle] = this;
5253 
5254 		} else version(linux) {
5255 			static import ep = core.sys.linux.epoll;
5256 
5257 			import core.sys.linux.timerfd;
5258 
5259 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5260 			if(fd == -1)
5261 				throw new Exception("timer create failed");
5262 
5263 			mapping[fd] = this;
5264 
5265 			itimerspec value;
5266 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5267 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5268 
5269 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5270 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5271 
5272 			if(timerfd_settime(fd, 0, &value, null) == -1)
5273 				throw new Exception("couldn't make pulse timer");
5274 
5275 			version(with_eventloop) {
5276 				import arsd.eventloop;
5277 				addFileEventListeners(fd, &trigger, null, null);
5278 			} else {
5279 				prepareEventLoop();
5280 
5281 				ep.epoll_event ev = void;
5282 				ev.events = ep.EPOLLIN;
5283 				ev.data.fd = fd;
5284 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5285 			}
5286 		} else featureNotImplemented();
5287 	}
5288 
5289 	private int intervalInMilliseconds;
5290 
5291 	// just cuz I sometimes call it this.
5292 	alias dispose = destroy;
5293 
5294 	/// Stop and destroy the timer object.
5295 	void destroy() {
5296 		version(Windows) {
5297 			staticDestroy(handle);
5298 			handle = null;
5299 		} else version(linux) {
5300 			staticDestroy(fd);
5301 			fd = -1;
5302 		} else featureNotImplemented();
5303 	}
5304 
5305 	version(Windows)
5306 	static void staticDestroy(HANDLE handle) {
5307 		if(handle) {
5308 			// KillTimer(null, handle);
5309 			CancelWaitableTimer(cast(void*)handle);
5310 			mapping.remove(handle);
5311 			CloseHandle(handle);
5312 		}
5313 	}
5314 	else version(linux)
5315 	static void staticDestroy(int fd) {
5316 		if(fd != -1) {
5317 			import unix = core.sys.posix.unistd;
5318 			static import ep = core.sys.linux.epoll;
5319 
5320 			version(with_eventloop) {
5321 				import arsd.eventloop;
5322 				removeFileEventListeners(fd);
5323 			} else {
5324 				ep.epoll_event ev = void;
5325 				ev.events = ep.EPOLLIN;
5326 				ev.data.fd = fd;
5327 
5328 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5329 			}
5330 			unix.close(fd);
5331 			mapping.remove(fd);
5332 		}
5333 	}
5334 
5335 	~this() {
5336 		version(Windows) { if(handle)
5337 			cleanupQueue.queue!staticDestroy(handle);
5338 		} else version(linux) { if(fd != -1)
5339 			cleanupQueue.queue!staticDestroy(fd);
5340 		}
5341 	}
5342 
5343 
5344 	void changeTime(int intervalInMilliseconds)
5345 	{
5346 		this.intervalInMilliseconds = intervalInMilliseconds;
5347 		version(Windows)
5348 		{
5349 			if(handle)
5350 			{
5351 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5352 				long initialTime = -intervalInMilliseconds;
5353 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5354 					throw new Exception("couldn't change pulse timer");
5355 			}
5356 		}
5357 	}
5358 
5359 
5360 	private:
5361 
5362 	void delegate() onPulse;
5363 
5364 	int lastEventLoopRoundTriggered;
5365 
5366 	void trigger() {
5367 		version(linux) {
5368 			import unix = core.sys.posix.unistd;
5369 			long val;
5370 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5371 		} else version(Windows) {
5372 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5373 				return; // never try to actually run faster than the event loop
5374 			lastEventLoopRoundTriggered = eventLoopRound;
5375 		} else featureNotImplemented();
5376 
5377 		onPulse();
5378 	}
5379 
5380 	version(Windows)
5381 	void rearm() {
5382 
5383 	}
5384 
5385 	version(Windows)
5386 		extern(Windows)
5387 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5388 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5389 			if(Timer* t = timer in mapping) {
5390 				try
5391 				(*t).trigger();
5392 				catch(Exception e) { sdpy_abort(e); assert(0); }
5393 			}
5394 		}
5395 
5396 	version(Windows) {
5397 		//UINT_PTR handle;
5398 		//static Timer[UINT_PTR] mapping;
5399 		HANDLE handle;
5400 		__gshared Timer[HANDLE] mapping;
5401 	} else version(linux) {
5402 		int fd = -1;
5403 		__gshared Timer[int] mapping;
5404 	} else static assert(0, "timer not supported");
5405 }
5406 }
5407 
5408 version(Windows)
5409 private int eventLoopRound;
5410 
5411 version(Windows)
5412 /// 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
5413 class WindowsHandleReader {
5414 	///
5415 	this(void delegate() onReady, HANDLE handle) {
5416 		this.onReady = onReady;
5417 		this.handle = handle;
5418 
5419 		mapping[handle] = this;
5420 
5421 		enable();
5422 	}
5423 
5424 	///
5425 	void enable() {
5426 		auto el = EventLoop.get().impl;
5427 		el.handles ~= handle;
5428 	}
5429 
5430 	///
5431 	void disable() {
5432 		auto el = EventLoop.get().impl;
5433 		for(int i = 0; i < el.handles.length; i++) {
5434 			if(el.handles[i] is handle) {
5435 				el.handles[i] = el.handles[$-1];
5436 				el.handles = el.handles[0 .. $-1];
5437 				return;
5438 			}
5439 		}
5440 	}
5441 
5442 	void dispose() {
5443 		disable();
5444 		if(handle)
5445 			mapping.remove(handle);
5446 		handle = null;
5447 	}
5448 
5449 	void ready() {
5450 		if(onReady)
5451 			onReady();
5452 	}
5453 
5454 	HANDLE handle;
5455 	void delegate() onReady;
5456 
5457 	__gshared WindowsHandleReader[HANDLE] mapping;
5458 }
5459 
5460 version(Posix)
5461 /// Lets you add files to the event loop for reading. Use at your own risk.
5462 class PosixFdReader {
5463 	///
5464 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5465 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5466 	}
5467 
5468 	///
5469 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5470 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5471 	}
5472 
5473 	///
5474 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5475 		this.onReady = onReady;
5476 		this.fd = fd;
5477 		this.captureWrites = captureWrites;
5478 		this.captureReads = captureReads;
5479 
5480 		mapping[fd] = this;
5481 
5482 		version(with_eventloop) {
5483 			import arsd.eventloop;
5484 			addFileEventListeners(fd, &readyel);
5485 		} else {
5486 			enable();
5487 		}
5488 	}
5489 
5490 	bool captureReads;
5491 	bool captureWrites;
5492 
5493 	version(with_eventloop) {} else
5494 	///
5495 	void enable() {
5496 		prepareEventLoop();
5497 
5498 		enabled = true;
5499 
5500 		version(linux) {
5501 			static import ep = core.sys.linux.epoll;
5502 			ep.epoll_event ev = void;
5503 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5504 			//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5505 			ev.data.fd = fd;
5506 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5507 		} else {
5508 
5509 		}
5510 	}
5511 
5512 	version(with_eventloop) {} else
5513 	///
5514 	void disable() {
5515 		prepareEventLoop();
5516 
5517 		enabled = false;
5518 
5519 		version(linux) {
5520 			static import ep = core.sys.linux.epoll;
5521 			ep.epoll_event ev = void;
5522 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5523 			//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5524 			ev.data.fd = fd;
5525 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5526 		}
5527 	}
5528 
5529 	version(with_eventloop) {} else
5530 	///
5531 	void dispose() {
5532 		if(enabled)
5533 			disable();
5534 		if(fd != -1)
5535 			mapping.remove(fd);
5536 		fd = -1;
5537 	}
5538 
5539 	void delegate(int, bool, bool) onReady;
5540 
5541 	version(with_eventloop)
5542 	void readyel() {
5543 		onReady(fd, true, true);
5544 	}
5545 
5546 	void ready(uint flags) {
5547 		version(linux) {
5548 			static import ep = core.sys.linux.epoll;
5549 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5550 		} else {
5551 			import core.sys.posix.poll;
5552 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5553 		}
5554 	}
5555 
5556 	void hup(uint flags) {
5557 		if(onHup)
5558 			onHup();
5559 	}
5560 
5561 	void delegate() onHup;
5562 
5563 	int fd = -1;
5564 	private bool enabled;
5565 	__gshared PosixFdReader[int] mapping;
5566 }
5567 
5568 // basic functions to access the clipboard
5569 /+
5570 
5571 
5572 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5573 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5574 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5575 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5576 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5577 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5578 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5579 
5580 +/
5581 
5582 /++
5583 	this does a delegate because it is actually an async call on X...
5584 	the receiver may never be called if the clipboard is empty or unavailable
5585 	gets plain text from the clipboard.
5586 +/
5587 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5588 	version(Windows) {
5589 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5590 		if(OpenClipboard(hwndOwner) == 0)
5591 			throw new Exception("OpenClipboard");
5592 		scope(exit)
5593 			CloseClipboard();
5594 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5595 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5596 
5597 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5598 				scope(exit)
5599 					GlobalUnlock(dataHandle);
5600 
5601 				// FIXME: CR/LF conversions
5602 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5603 				int len = 0;
5604 				auto d = data;
5605 				while(*d) {
5606 					d++;
5607 					len++;
5608 				}
5609 				string s;
5610 				s.reserve(len);
5611 				foreach(dchar ch; data[0 .. len]) {
5612 					s ~= ch;
5613 				}
5614 				receiver(s);
5615 			}
5616 		}
5617 	} else version(X11) {
5618 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5619 	} else version(OSXCocoa) {
5620 		throw new NotYetImplementedException();
5621 	} else static assert(0);
5622 }
5623 
5624 // FIXME: a clipboard listener might be cool btw
5625 
5626 /++
5627 	this does a delegate because it is actually an async call on X...
5628 	the receiver may never be called if the clipboard is empty or unavailable
5629 	gets image from the clipboard.
5630 
5631 	templated because it introduces an optional dependency on arsd.bmp
5632 +/
5633 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5634 	version(Windows) {
5635 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5636 		if(OpenClipboard(hwndOwner) == 0)
5637 			throw new Exception("OpenClipboard");
5638 		scope(exit)
5639 			CloseClipboard();
5640 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5641 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5642 				scope(exit)
5643 					GlobalUnlock(dataHandle);
5644 
5645 				auto len = GlobalSize(dataHandle);
5646 
5647 				import arsd.bmp;
5648 				auto img = readBmp(data[0 .. len], false);
5649 				receiver(img);
5650 			}
5651 		}
5652 	} else version(X11) {
5653 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5654 	} else version(OSXCocoa) {
5655 		throw new NotYetImplementedException();
5656 	} else static assert(0);
5657 }
5658 
5659 version(Windows)
5660 struct WCharzBuffer {
5661 	wchar[] buffer;
5662 	wchar[256] staticBuffer = void;
5663 
5664 	size_t length() {
5665 		return buffer.length;
5666 	}
5667 
5668 	wchar* ptr() {
5669 		return buffer.ptr;
5670 	}
5671 
5672 	wchar[] slice() {
5673 		return buffer;
5674 	}
5675 
5676 	void copyInto(R)(ref R r) {
5677 		static if(is(R == wchar[N], size_t N)) {
5678 			r[0 .. this.length] = slice[];
5679 			r[this.length] = 0;
5680 		} else static assert(0, "can only copy into wchar[n], not " ~ R.stringof);
5681 	}
5682 
5683 	/++
5684 		conversionFlags = [WindowsStringConversionFlags]
5685 	+/
5686 	this(in char[] data, int conversionFlags = 0) {
5687 		conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name
5688 		auto sz = sizeOfConvertedWstring(data, conversionFlags);
5689 		if(sz > staticBuffer.length)
5690 			buffer = new wchar[](sz);
5691 		else
5692 			buffer = staticBuffer[];
5693 
5694 		buffer = makeWindowsString(data, buffer, conversionFlags);
5695 	}
5696 }
5697 
5698 version(Windows)
5699 int sizeOfConvertedWstring(in char[] s, int conversionFlags) {
5700 	int size = 0;
5701 
5702 	if(conversionFlags & WindowsStringConversionFlags.convertNewLines) {
5703 		// need to convert line endings, which means the length will get bigger.
5704 
5705 		// BTW I betcha this could be faster with some simd stuff.
5706 		char last;
5707 		foreach(char ch; s) {
5708 			if(ch == 10 && last != 13)
5709 				size++; // will add a 13 before it...
5710 			size++;
5711 			last = ch;
5712 		}
5713 	} else {
5714 		// no conversion necessary, just estimate based on length
5715 		/*
5716 			I don't think there's any string with a longer length
5717 			in code units when encoded in UTF-16 than it has in UTF-8.
5718 			This will probably over allocate, but that's OK.
5719 		*/
5720 		size = cast(int) s.length;
5721 	}
5722 
5723 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate)
5724 		size++;
5725 
5726 	return size;
5727 }
5728 
5729 version(Windows)
5730 enum WindowsStringConversionFlags : int {
5731 	zeroTerminate = 1,
5732 	convertNewLines = 2,
5733 }
5734 
5735 version(Windows)
5736 class WindowsApiException : Exception {
5737 	char[256] buffer;
5738 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5739 		assert(msg.length < 100);
5740 
5741 		auto error = GetLastError();
5742 		buffer[0 .. msg.length] = msg;
5743 		buffer[msg.length] = ' ';
5744 
5745 		int pos = cast(int) msg.length + 1;
5746 
5747 		if(error == 0)
5748 			buffer[pos++] = '0';
5749 		else {
5750 
5751 			auto ec = error;
5752 			auto init = pos;
5753 			while(ec) {
5754 				buffer[pos++] = (ec % 10) + '0';
5755 				ec /= 10;
5756 			}
5757 
5758 			buffer[pos++] = ' ';
5759 
5760 			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);
5761 
5762 			pos += size;
5763 		}
5764 
5765 
5766 		super(cast(string) buffer[0 .. pos], file, line, next);
5767 	}
5768 }
5769 
5770 class ErrnoApiException : Exception {
5771 	char[256] buffer;
5772 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5773 		assert(msg.length < 100);
5774 
5775 		import core.stdc.errno;
5776 		auto error = errno;
5777 		buffer[0 .. msg.length] = msg;
5778 		buffer[msg.length] = ' ';
5779 
5780 		int pos = cast(int) msg.length + 1;
5781 
5782 		if(error == 0)
5783 			buffer[pos++] = '0';
5784 		else {
5785 			auto init = pos;
5786 			while(error) {
5787 				buffer[pos++] = (error % 10) + '0';
5788 				error /= 10;
5789 			}
5790 			for(int i = 0; i < (pos - init) / 2; i++) {
5791 				char c = buffer[i + init];
5792 				buffer[i + init] = buffer[pos - (i + init) - 1];
5793 				buffer[pos - (i + init) - 1] = c;
5794 			}
5795 		}
5796 
5797 
5798 		super(cast(string) buffer[0 .. pos], file, line, next);
5799 	}
5800 
5801 }
5802 
5803 version(Windows)
5804 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) {
5805 	if(str.length == 0)
5806 		return null;
5807 
5808 	int pos = 0;
5809 	dchar last;
5810 	foreach(dchar c; str) {
5811 		if(c <= 0xFFFF) {
5812 			if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13)
5813 				buffer[pos++] = 13;
5814 			buffer[pos++] = cast(wchar) c;
5815 		} else if(c <= 0x10FFFF) {
5816 			buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
5817 			buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
5818 		}
5819 
5820 		last = c;
5821 	}
5822 
5823 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) {
5824 		buffer[pos] = 0;
5825 	}
5826 
5827 	return buffer[0 .. pos];
5828 }
5829 
5830 version(Windows)
5831 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
5832 	if(str.length == 0)
5833 		return null;
5834 
5835 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
5836 	if(got == 0) {
5837 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5838 			throw new Exception("not enough buffer");
5839 		else
5840 			throw new Exception("conversion"); // FIXME: GetLastError
5841 	}
5842 	return buffer[0 .. got];
5843 }
5844 
5845 version(Windows)
5846 string makeUtf8StringFromWindowsString(in wchar[] str) {
5847 	char[] buffer;
5848 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
5849 	buffer.length = got;
5850 
5851 	// it is unique because we just allocated it above!
5852 	return cast(string) makeUtf8StringFromWindowsString(str, buffer);
5853 }
5854 
5855 version(Windows)
5856 string makeUtf8StringFromWindowsString(wchar* str) {
5857 	char[] buffer;
5858 	auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
5859 	buffer.length = got;
5860 
5861 	got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
5862 	if(got == 0) {
5863 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5864 			throw new Exception("not enough buffer");
5865 		else
5866 			throw new Exception("conversion"); // FIXME: GetLastError
5867 	}
5868 	return cast(string) buffer[0 .. got];
5869 }
5870 
5871 int findIndexOfZero(in wchar[] str) {
5872 	foreach(idx, wchar ch; str)
5873 		if(ch == 0)
5874 			return cast(int) idx;
5875 	return cast(int) str.length;
5876 }
5877 int findIndexOfZero(in char[] str) {
5878 	foreach(idx, char ch; str)
5879 		if(ch == 0)
5880 			return cast(int) idx;
5881 	return cast(int) str.length;
5882 }
5883 
5884 /// Copies some text to the clipboard.
5885 void setClipboardText(SimpleWindow clipboardOwner, string text) {
5886 	assert(clipboardOwner !is null);
5887 	version(Windows) {
5888 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5889 			throw new Exception("OpenClipboard");
5890 		scope(exit)
5891 			CloseClipboard();
5892 		EmptyClipboard();
5893 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5894 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
5895 		if(handle is null) throw new Exception("GlobalAlloc");
5896 		if(auto data = cast(wchar*) GlobalLock(handle)) {
5897 			auto slice = data[0 .. sz];
5898 			scope(failure)
5899 				GlobalUnlock(handle);
5900 
5901 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5902 
5903 			GlobalUnlock(handle);
5904 			SetClipboardData(CF_UNICODETEXT, handle);
5905 		}
5906 	} else version(X11) {
5907 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
5908 	} else version(OSXCocoa) {
5909 		throw new NotYetImplementedException();
5910 	} else static assert(0);
5911 }
5912 
5913 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
5914 	assert(clipboardOwner !is null);
5915 	version(Windows) {
5916 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5917 			throw new Exception("OpenClipboard");
5918 		scope(exit)
5919 			CloseClipboard();
5920 		EmptyClipboard();
5921 
5922 
5923 		import arsd.bmp;
5924 		ubyte[] mdata;
5925 		mdata.reserve(img.width * img.height);
5926 		void sink(ubyte b) {
5927 			mdata ~= b;
5928 		}
5929 		writeBmpIndirect(img, &sink, false);
5930 
5931 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
5932 		if(handle is null) throw new Exception("GlobalAlloc");
5933 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
5934 			auto slice = data[0 .. mdata.length];
5935 			scope(failure)
5936 				GlobalUnlock(handle);
5937 
5938 			slice[] = mdata[];
5939 
5940 			GlobalUnlock(handle);
5941 			SetClipboardData(CF_DIB, handle);
5942 		}
5943 	} else version(X11) {
5944 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
5945 			mixin X11SetSelectionHandler_Basics;
5946 			private const(ubyte)[] mdata;
5947 			private const(ubyte)[] mdata_original;
5948 			this(MemoryImage img) {
5949 				import arsd.bmp;
5950 
5951 				mdata.reserve(img.width * img.height);
5952 				void sink(ubyte b) {
5953 					mdata ~= b;
5954 				}
5955 				writeBmpIndirect(img, &sink, true);
5956 
5957 				mdata_original = mdata;
5958 			}
5959 
5960 			Atom[] availableFormats() {
5961 				auto display = XDisplayConnection.get;
5962 				return [
5963 					GetAtom!"image/bmp"(display),
5964 					GetAtom!"TARGETS"(display)
5965 				];
5966 			}
5967 
5968 			ubyte[] getData(Atom format, return scope ubyte[] data) {
5969 				if(mdata.length < data.length) {
5970 					data[0 .. mdata.length] = mdata[];
5971 					auto ret = data[0 .. mdata.length];
5972 					mdata = mdata[$..$];
5973 					return ret;
5974 				} else {
5975 					data[] = mdata[0 .. data.length];
5976 					mdata = mdata[data.length .. $];
5977 					return data[];
5978 				}
5979 			}
5980 
5981 			void done() {
5982 				mdata = mdata_original;
5983 			}
5984 		}
5985 
5986 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
5987 	} else version(OSXCocoa) {
5988 		throw new NotYetImplementedException();
5989 	} else static assert(0);
5990 }
5991 
5992 
5993 version(X11) {
5994 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
5995 
5996 	private Atom*[] interredAtoms; // for discardAndRecreate
5997 
5998 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
5999 	/// Platform-specific for X11.
6000 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6001 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6002 		static Atom a;
6003 		if(!a) {
6004 			a = XInternAtom(display, name, !create);
6005 			interredAtoms ~= &a;
6006 		}
6007 		if(a == None)
6008 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6009 		return a;
6010 	}
6011 
6012 	/// Platform-specific for X11 - gets atom names as a string.
6013 	string getAtomName(Atom atom, Display* display) {
6014 		auto got = XGetAtomName(display, atom);
6015 		scope(exit) XFree(got);
6016 		import core.stdc.string;
6017 		string s = got[0 .. strlen(got)].idup;
6018 		return s;
6019 	}
6020 
6021 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6022 	void setPrimarySelection(SimpleWindow window, string text) {
6023 		setX11Selection!"PRIMARY"(window, text);
6024 	}
6025 
6026 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6027 	void setSecondarySelection(SimpleWindow window, string text) {
6028 		setX11Selection!"SECONDARY"(window, text);
6029 	}
6030 
6031 	interface X11SetSelectionHandler {
6032 		// should include TARGETS right now
6033 		Atom[] availableFormats();
6034 		// Return the slice of data you filled, empty slice if done.
6035 		// this is to support the incremental thing
6036 		ubyte[] getData(Atom format, return scope ubyte[] data);
6037 
6038 		void done();
6039 
6040 		void handleRequest(XEvent);
6041 
6042 		bool matchesIncr(Window, Atom);
6043 		void sendMoreIncr(XPropertyEvent*);
6044 	}
6045 
6046 	mixin template X11SetSelectionHandler_Basics() {
6047 		Window incrWindow;
6048 		Atom incrAtom;
6049 		Atom selectionAtom;
6050 		Atom formatAtom;
6051 		ubyte[] toSend;
6052 		bool matchesIncr(Window w, Atom a) {
6053 			return incrAtom && incrAtom == a && w == incrWindow;
6054 		}
6055 		void sendMoreIncr(XPropertyEvent* event) {
6056 			auto display = XDisplayConnection.get;
6057 
6058 			XChangeProperty (display,
6059 				incrWindow,
6060 				incrAtom,
6061 				formatAtom,
6062 				8 /* bits */, PropModeReplace,
6063 				toSend.ptr, cast(int) toSend.length);
6064 
6065 			if(toSend.length != 0) {
6066 				toSend = this.getData(formatAtom, toSend[]);
6067 			} else {
6068 				this.done();
6069 				incrWindow = None;
6070 				incrAtom = None;
6071 				selectionAtom = None;
6072 				formatAtom = None;
6073 				toSend = null;
6074 			}
6075 		}
6076 		void handleRequest(XEvent ev) {
6077 
6078 			auto display = XDisplayConnection.get;
6079 
6080 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6081 			XSelectionEvent selectionEvent;
6082 			selectionEvent.type = EventType.SelectionNotify;
6083 			selectionEvent.display = event.display;
6084 			selectionEvent.requestor = event.requestor;
6085 			selectionEvent.selection = event.selection;
6086 			selectionEvent.time = event.time;
6087 			selectionEvent.target = event.target;
6088 
6089 			bool supportedType() {
6090 				foreach(t; this.availableFormats())
6091 					if(t == event.target)
6092 						return true;
6093 				return false;
6094 			}
6095 
6096 			if(event.property == None) {
6097 				selectionEvent.property = event.target;
6098 
6099 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6100 				XFlush(display);
6101 			} if(event.target == GetAtom!"TARGETS"(display)) {
6102 				/* respond with the supported types */
6103 				auto tlist = this.availableFormats();
6104 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6105 				selectionEvent.property = event.property;
6106 
6107 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6108 				XFlush(display);
6109 			} else if(supportedType()) {
6110 				auto buffer = new ubyte[](1024 * 64);
6111 				auto toSend = this.getData(event.target, buffer[]);
6112 
6113 				if(toSend.length < 32 * 1024) {
6114 					// small enough to send directly...
6115 					selectionEvent.property = event.property;
6116 					XChangeProperty (display,
6117 						selectionEvent.requestor,
6118 						selectionEvent.property,
6119 						event.target,
6120 						8 /* bits */, 0 /* PropModeReplace */,
6121 						toSend.ptr, cast(int) toSend.length);
6122 
6123 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6124 					XFlush(display);
6125 				} else {
6126 					// large, let's send incrementally
6127 					arch_ulong l = toSend.length;
6128 
6129 					// if I wanted other events from this window don't want to clear that out....
6130 					XWindowAttributes xwa;
6131 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6132 
6133 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6134 
6135 					incrWindow = event.requestor;
6136 					incrAtom = event.property;
6137 					formatAtom = event.target;
6138 					selectionAtom = event.selection;
6139 					this.toSend = toSend;
6140 
6141 					selectionEvent.property = event.property;
6142 					XChangeProperty (display,
6143 						selectionEvent.requestor,
6144 						selectionEvent.property,
6145 						GetAtom!"INCR"(display),
6146 						32 /* bits */, PropModeReplace,
6147 						&l, 1);
6148 
6149 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6150 					XFlush(display);
6151 				}
6152 				//if(after)
6153 					//after();
6154 			} else {
6155 				debug(sdpy_clip) {
6156 					import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display));
6157 				}
6158 				selectionEvent.property = None; // I don't know how to handle this type...
6159 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6160 				XFlush(display);
6161 			}
6162 		}
6163 	}
6164 
6165 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6166 		mixin X11SetSelectionHandler_Basics;
6167 		private const(ubyte)[] text;
6168 		private const(ubyte)[] text_original;
6169 		this(string text) {
6170 			this.text = cast(const ubyte[]) text;
6171 			this.text_original = this.text;
6172 		}
6173 		Atom[] availableFormats() {
6174 			auto display = XDisplayConnection.get;
6175 			return [
6176 				GetAtom!"UTF8_STRING"(display),
6177 				GetAtom!"text/plain"(display),
6178 				XA_STRING,
6179 				GetAtom!"TARGETS"(display)
6180 			];
6181 		}
6182 
6183 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6184 			if(text.length < data.length) {
6185 				data[0 .. text.length] = text[];
6186 				return data[0 .. text.length];
6187 			} else {
6188 				data[] = text[0 .. data.length];
6189 				text = text[data.length .. $];
6190 				return data[];
6191 			}
6192 		}
6193 
6194 		void done() {
6195 			text = text_original;
6196 		}
6197 	}
6198 
6199 	/// 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?!)
6200 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6201 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6202 	}
6203 
6204 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6205 		assert(window !is null);
6206 
6207 		auto display = XDisplayConnection.get();
6208 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6209 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6210 		else Atom a = GetAtom!atomName(display);
6211 
6212 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6213 
6214 		window.impl.setSelectionHandlers[a] = data;
6215 	}
6216 
6217 	///
6218 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6219 		getX11Selection!"PRIMARY"(window, handler);
6220 	}
6221 
6222 	// added July 28, 2020
6223 	// undocumented as experimental tho
6224 	interface X11GetSelectionHandler {
6225 		void handleData(Atom target, in ubyte[] data);
6226 		Atom findBestFormat(Atom[] answer);
6227 
6228 		void prepareIncremental(Window, Atom);
6229 		bool matchesIncr(Window, Atom);
6230 		void handleIncrData(Atom, in ubyte[] data);
6231 	}
6232 
6233 	mixin template X11GetSelectionHandler_Basics() {
6234 		Window incrWindow;
6235 		Atom incrAtom;
6236 
6237 		void prepareIncremental(Window w, Atom a) {
6238 			incrWindow = w;
6239 			incrAtom = a;
6240 		}
6241 		bool matchesIncr(Window w, Atom a) {
6242 			return incrWindow == w && incrAtom == a;
6243 		}
6244 
6245 		Atom incrFormatAtom;
6246 		ubyte[] incrData;
6247 		void handleIncrData(Atom format, in ubyte[] data) {
6248 			incrFormatAtom = format;
6249 
6250 			if(data.length)
6251 				incrData ~= data;
6252 			else
6253 				handleData(incrFormatAtom, incrData);
6254 
6255 		}
6256 	}
6257 
6258 	///
6259 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6260 		assert(window !is null);
6261 
6262 		auto display = XDisplayConnection.get();
6263 		auto atom = GetAtom!atomName(display);
6264 
6265 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6266 			this(void delegate(in char[]) handler) {
6267 				this.handler = handler;
6268 			}
6269 
6270 			mixin X11GetSelectionHandler_Basics;
6271 
6272 			void delegate(in char[]) handler;
6273 
6274 			void handleData(Atom target, in ubyte[] data) {
6275 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6276 					handler(cast(const char[]) data);
6277 			}
6278 
6279 			Atom findBestFormat(Atom[] answer) {
6280 				Atom best = None;
6281 				foreach(option; answer) {
6282 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6283 						best = option;
6284 						break;
6285 					} else if(option == XA_STRING) {
6286 						best = option;
6287 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6288 						best = option;
6289 					}
6290 				}
6291 				return best;
6292 			}
6293 		}
6294 
6295 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6296 
6297 		auto target = GetAtom!"TARGETS"(display);
6298 
6299 		// SDD_DATA is "simpledisplay.d data"
6300 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6301 	}
6302 
6303 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6304 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6305 		assert(window !is null);
6306 
6307 		auto display = XDisplayConnection.get();
6308 		auto atom = GetAtom!atomName(display);
6309 
6310 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6311 			this(void delegate(MemoryImage) handler) {
6312 				this.handler = handler;
6313 			}
6314 
6315 			mixin X11GetSelectionHandler_Basics;
6316 
6317 			void delegate(MemoryImage) handler;
6318 
6319 			void handleData(Atom target, in ubyte[] data) {
6320 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6321 					import arsd.bmp;
6322 					handler(readBmp(data));
6323 				}
6324 			}
6325 
6326 			Atom findBestFormat(Atom[] answer) {
6327 				Atom best = None;
6328 				foreach(option; answer) {
6329 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6330 						best = option;
6331 					}
6332 				}
6333 				return best;
6334 			}
6335 
6336 		}
6337 
6338 
6339 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6340 
6341 		auto target = GetAtom!"TARGETS"(display);
6342 
6343 		// SDD_DATA is "simpledisplay.d data"
6344 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6345 	}
6346 
6347 
6348 	///
6349 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6350 		Atom actualType;
6351 		int actualFormat;
6352 		arch_ulong actualItems;
6353 		arch_ulong bytesRemaining;
6354 		void* data;
6355 
6356 		auto display = XDisplayConnection.get();
6357 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6358 			if(actualFormat == 0)
6359 				return null;
6360 			else {
6361 				int byteLength;
6362 				if(actualFormat == 32) {
6363 					// 32 means it is a C long... which is variable length
6364 					actualFormat = cast(int) arch_long.sizeof * 8;
6365 				}
6366 
6367 				// then it is just a bit count
6368 				byteLength = cast(int) (actualItems * actualFormat / 8);
6369 
6370 				auto d = new ubyte[](byteLength);
6371 				d[] = cast(ubyte[]) data[0 .. byteLength];
6372 				XFree(data);
6373 				return d;
6374 			}
6375 		}
6376 		return null;
6377 	}
6378 
6379 	/* defined in the systray spec */
6380 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6381 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6382 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6383 
6384 
6385 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6386 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6387 	public class GlobalHotkey {
6388 		KeyEvent key;
6389 		void delegate () handler;
6390 
6391 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6392 
6393 		/// Create from initialzed KeyEvent object
6394 		this (KeyEvent akey, void delegate () ahandler=null) {
6395 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6396 			key = akey;
6397 			handler = ahandler;
6398 		}
6399 
6400 		/// Create from emacs-like key name ("C-M-Y", etc.)
6401 		this (const(char)[] akey, void delegate () ahandler=null) {
6402 			key = KeyEvent.parse(akey);
6403 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6404 			handler = ahandler;
6405 		}
6406 
6407 	}
6408 
6409 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6410 		//conwriteln("failed to grab key");
6411 		GlobalHotkeyManager.ghfailed = true;
6412 		return 0;
6413 	}
6414 
6415 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6416 		Image.impl.xshmfailed = true;
6417 		return 0;
6418 	}
6419 
6420 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6421 		import core.stdc.stdio;
6422 		char[265] buffer;
6423 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6424 		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);
6425 		return 0;
6426 	}
6427 
6428 	/++
6429 		Global hotkey manager. It contains static methods to manage global hotkeys.
6430 
6431 		---
6432 		 try {
6433 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6434 		} catch (Exception e) {
6435 			conwriteln("ERROR registering hotkey!");
6436 		}
6437 		EventLoop.get.run();
6438 		---
6439 
6440 		The key strings are based on Emacs. In practical terms,
6441 		`M` means `alt` and `H` means the Windows logo key. `C`
6442 		is `ctrl`.
6443 
6444 		$(WARNING
6445 			This is X-specific right now. If you are on
6446 			Windows, try [registerHotKey] instead.
6447 
6448 			We will probably merge these into a single
6449 			interface later.
6450 		)
6451 	+/
6452 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6453 		version(X11) {
6454 			void recreateAfterDisconnect() {
6455 				throw new Exception("NOT IMPLEMENTED");
6456 			}
6457 			void discardConnectionState() {
6458 				throw new Exception("NOT IMPLEMENTED");
6459 			}
6460 		}
6461 
6462 		private static immutable uint[8] masklist = [ 0,
6463 			KeyOrButtonMask.LockMask,
6464 			KeyOrButtonMask.Mod2Mask,
6465 			KeyOrButtonMask.Mod3Mask,
6466 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6467 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6468 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6469 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6470 		];
6471 		private __gshared GlobalHotkeyManager ghmanager;
6472 		private __gshared bool ghfailed = false;
6473 
6474 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6475 			if (modmask == 0) return false;
6476 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6477 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6478 			return true;
6479 		}
6480 
6481 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6482 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6483 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6484 			return modmask;
6485 		}
6486 
6487 		private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) {
6488 			uint keycode = cast(uint)ke.key;
6489 			auto dpy = XDisplayConnection.get;
6490 			return XKeysymToKeycode(dpy, keycode);
6491 		}
6492 
6493 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6494 
6495 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6496 
6497 		NativeEventHandler getNativeEventHandler () {
6498 			return delegate int (XEvent e) {
6499 				if (e.type != EventType.KeyPress) return 1;
6500 				auto kev = cast(const(XKeyEvent)*)&e;
6501 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6502 				if (auto ghkp = hash in globalHotkeyList) {
6503 					try {
6504 						ghkp.doHandle();
6505 					} catch (Exception e) {
6506 						import core.stdc.stdio : stderr, fprintf;
6507 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6508 					}
6509 				}
6510 				return 1;
6511 			};
6512 		}
6513 
6514 		private this () {
6515 			auto dpy = XDisplayConnection.get;
6516 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6517 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6518 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6519 		}
6520 
6521 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6522 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6523 		static void register (GlobalHotkey gh) {
6524 			if (gh is null) return;
6525 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6526 
6527 			auto dpy = XDisplayConnection.get;
6528 			immutable keycode = keyEvent2KeyCode(gh.key);
6529 
6530 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6531 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6532 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6533 			XSync(dpy, 0/*False*/);
6534 
6535 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6536 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6537 			ghfailed = false;
6538 			foreach (immutable uint ormask; masklist[]) {
6539 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6540 			}
6541 			XSync(dpy, 0/*False*/);
6542 			XSetErrorHandler(savedErrorHandler);
6543 
6544 			if (ghfailed) {
6545 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6546 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6547 				XSync(dpy, 0/*False*/);
6548 				XSetErrorHandler(savedErrorHandler);
6549 				throw new Exception("cannot register global hotkey");
6550 			}
6551 
6552 			globalHotkeyList[hash] = gh;
6553 		}
6554 
6555 		/// Ditto
6556 		static void register (const(char)[] akey, void delegate () ahandler) {
6557 			register(new GlobalHotkey(akey, ahandler));
6558 		}
6559 
6560 		private static void removeByHash (ulong hash) {
6561 			if (auto ghp = hash in globalHotkeyList) {
6562 				auto dpy = XDisplayConnection.get;
6563 				immutable keycode = keyEvent2KeyCode(ghp.key);
6564 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6565 				XSync(dpy, 0/*False*/);
6566 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6567 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6568 				XSync(dpy, 0/*False*/);
6569 				XSetErrorHandler(savedErrorHandler);
6570 				globalHotkeyList.remove(hash);
6571 			}
6572 		}
6573 
6574 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6575 		/// It is safe to unregister unknown or invalid hotkey.
6576 		static void unregister (GlobalHotkey gh) {
6577 			//TODO: add second AA for faster search? prolly doesn't worth it.
6578 			if (gh is null) return;
6579 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6580 				if (kv.value is gh) {
6581 					removeByHash(kv.key);
6582 					return;
6583 				}
6584 			}
6585 		}
6586 
6587 		/// Ditto.
6588 		static void unregister (const(char)[] key) {
6589 			auto kev = KeyEvent.parse(key);
6590 			immutable keycode = keyEvent2KeyCode(kev);
6591 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6592 		}
6593 	}
6594 }
6595 
6596 version(Windows) {
6597 	/++
6598 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6599 
6600 		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).
6601 	+/
6602 	void sendSyntheticInput(wstring s) {
6603 			INPUT[] inputs;
6604 			inputs.reserve(s.length * 2);
6605 
6606 			foreach(wchar c; s) {
6607 				INPUT input;
6608 				input.type = INPUT_KEYBOARD;
6609 				input.ki.wScan = c;
6610 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6611 				inputs ~= input;
6612 
6613 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6614 				inputs ~= input;
6615 			}
6616 
6617 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6618 				throw new Exception("SendInput failed");
6619 			}
6620 
6621 	}
6622 
6623 
6624 	// global hotkey helper function
6625 
6626 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6627 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6628 		__gshared int hotkeyId = 0;
6629 		int id = ++hotkeyId;
6630 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6631 			throw new Exception("RegisterHotKey failed");
6632 
6633 		__gshared void delegate()[WPARAM][HWND] handlers;
6634 
6635 		handlers[window.impl.hwnd][id] = handler;
6636 
6637 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6638 
6639 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6640 			switch(msg) {
6641 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6642 				case WM_HOTKEY:
6643 					if(auto list = hwnd in handlers) {
6644 						if(auto h = wParam in *list) {
6645 							(*h)();
6646 							return 0;
6647 						}
6648 					}
6649 				goto default;
6650 				default:
6651 			}
6652 			if(oldHandler)
6653 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6654 			return 1; // pass it on
6655 		};
6656 
6657 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6658 			oldHandler = window.handleNativeEvent;
6659 			window.handleNativeEvent = nativeEventHandler;
6660 		}
6661 
6662 		return id;
6663 	}
6664 
6665 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6666 	void unregisterHotKey(SimpleWindow window, int id) {
6667 		if(!UnregisterHotKey(window.impl.hwnd, id))
6668 			throw new Exception("UnregisterHotKey");
6669 	}
6670 }
6671 
6672 version (X11) {
6673 	pragma(lib, "dl");
6674 	import core.sys.posix.dlfcn;
6675 }
6676 
6677 /++
6678 	Allows for sending synthetic input to the X server via the Xtst
6679 	extension or on Windows using SendInput.
6680 
6681 	Please remember user input is meant to be user - don't use this
6682 	if you have some other alternative!
6683 
6684 	History:
6685 		Added May 17, 2020 with the X implementation.
6686 
6687 		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.)
6688 	Bugs:
6689 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6690 +/
6691 struct SyntheticInput {
6692 	@disable this();
6693 
6694 	private int* refcount;
6695 
6696 	version(X11) {
6697 		private void* lib;
6698 
6699 		private extern(C) {
6700 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6701 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6702 		}
6703 	}
6704 
6705 	/// The dummy param must be 0.
6706 	this(int dummy) {
6707 		version(X11) {
6708 			lib = dlopen("libXtst.so", RTLD_NOW);
6709 			if(lib is null)
6710 				throw new Exception("cannot load xtest lib extension");
6711 			scope(failure)
6712 				dlclose(lib);
6713 
6714 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6715 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6716 
6717 			if(XTestFakeKeyEvent is null)
6718 				throw new Exception("No XTestFakeKeyEvent");
6719 			if(XTestFakeButtonEvent is null)
6720 				throw new Exception("No XTestFakeButtonEvent");
6721 		}
6722 
6723 		refcount = new int;
6724 		*refcount = 1;
6725 	}
6726 
6727 	this(this) {
6728 		if(refcount)
6729 			*refcount += 1;
6730 	}
6731 
6732 	~this() {
6733 		if(refcount) {
6734 			*refcount -= 1;
6735 			if(*refcount == 0)
6736 				// I commented this because if I close the lib before
6737 				// XCloseDisplay, it is liable to segfault... so just
6738 				// gonna keep it loaded if it is loaded, no big deal
6739 				// anyway.
6740 				{} // dlclose(lib);
6741 		}
6742 	}
6743 
6744 	/++
6745 		Simulates typing a string into the keyboard.
6746 
6747 		Bugs:
6748 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6749 
6750 			Not implemented except on Windows and X11.
6751 	+/
6752 	void sendSyntheticInput(string s) {
6753 		version(Windows) {
6754 			INPUT[] inputs;
6755 			inputs.reserve(s.length * 2);
6756 
6757 			auto ei = GetMessageExtraInfo();
6758 
6759 			foreach(wchar c; s) {
6760 				INPUT input;
6761 				input.type = INPUT_KEYBOARD;
6762 				input.ki.wScan = c;
6763 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6764 				input.ki.dwExtraInfo = ei;
6765 				inputs ~= input;
6766 
6767 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6768 				inputs ~= input;
6769 			}
6770 
6771 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6772 				throw new Exception("SendInput failed");
6773 			}
6774 		} else version(X11) {
6775 			int delay = 0;
6776 			foreach(ch; s) {
6777 				pressKey(cast(Key) ch, true, delay);
6778 				pressKey(cast(Key) ch, false, delay);
6779 				delay += 5;
6780 			}
6781 		} else throw new NotYetImplementedException();
6782 	}
6783 
6784 	/++
6785 		Sends a fake press or release key event.
6786 
6787 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6788 
6789 		Bugs:
6790 			The `delay` parameter is not implemented yet on Windows.
6791 
6792 			Not implemented except on Windows and X11.
6793 	+/
6794 	void pressKey(Key key, bool pressed, int delay = 0) {
6795 		version(Windows) {
6796 			INPUT input;
6797 			input.type = INPUT_KEYBOARD;
6798 			input.ki.wVk = cast(ushort) key;
6799 
6800 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6801 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6802 
6803 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6804 				throw new Exception("SendInput failed");
6805 			}
6806 		} else version(X11) {
6807 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6808 		} else throw new NotYetImplementedException();
6809 	}
6810 
6811 	/++
6812 		Sends a fake mouse button press or release event.
6813 
6814 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6815 
6816 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6817 
6818 		Bugs:
6819 			The `delay` parameter is not implemented yet on Windows.
6820 
6821 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6822 
6823 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6824 	+/
6825 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6826 		version(Windows) {
6827 			INPUT input;
6828 			input.type = INPUT_MOUSE;
6829 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6830 
6831 			// input.mi.mouseData for a wheel event
6832 
6833 			switch(button) {
6834 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6835 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6836 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6837 				case MouseButton.wheelUp:
6838 				case MouseButton.wheelDown:
6839 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6840 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6841 				break;
6842 				case MouseButton.backButton: throw new NotYetImplementedException();
6843 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6844 				default:
6845 			}
6846 
6847 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6848 				throw new Exception("SendInput failed");
6849 			}
6850 		} else version(X11) {
6851 			int btn;
6852 
6853 			switch(button) {
6854 				case MouseButton.left: btn = 1; break;
6855 				case MouseButton.middle: btn = 2; break;
6856 				case MouseButton.right: btn = 3; break;
6857 				case MouseButton.wheelUp: btn = 4; break;
6858 				case MouseButton.wheelDown: btn = 5; break;
6859 				case MouseButton.backButton: btn = 8; break;
6860 				case MouseButton.forwardButton: btn = 9; break;
6861 				default:
6862 			}
6863 
6864 			assert(btn);
6865 
6866 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6867 		} else throw new NotYetImplementedException();
6868 	}
6869 
6870 	///
6871 	static void moveMouseArrowBy(int dx, int dy) {
6872 		version(Windows) {
6873 			INPUT input;
6874 			input.type = INPUT_MOUSE;
6875 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6876 			input.mi.dx = dx;
6877 			input.mi.dy = dy;
6878 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
6879 
6880 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6881 				throw new Exception("SendInput failed");
6882 			}
6883 		} else version(X11) {
6884 			auto disp = XDisplayConnection.get();
6885 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
6886 			XFlush(disp);
6887 		} else throw new NotYetImplementedException();
6888 	}
6889 
6890 	///
6891 	static void moveMouseArrowTo(int x, int y) {
6892 		version(Windows) {
6893 			INPUT input;
6894 			input.type = INPUT_MOUSE;
6895 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6896 			input.mi.dx = x;
6897 			input.mi.dy = y;
6898 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
6899 
6900 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6901 				throw new Exception("SendInput failed");
6902 			}
6903 		} else version(X11) {
6904 			auto disp = XDisplayConnection.get();
6905 			auto root = RootWindow(disp, DefaultScreen(disp));
6906 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
6907 			XFlush(disp);
6908 		} else throw new NotYetImplementedException();
6909 	}
6910 }
6911 
6912 
6913 
6914 /++
6915 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
6916 
6917 	See_Also:
6918 	$(LIST
6919 		*[ScreenPainter]
6920 		*[ScreenPainter.rasterOp]
6921 	)
6922 +/
6923 enum RasterOp {
6924 	normal, /// Replaces the pixel.
6925 	xor, /// Uses bitwise xor to draw.
6926 }
6927 
6928 // being phobos-free keeps the size WAY down
6929 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
6930 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
6931 package(arsd) const(wchar)* toWStringz(string s) {
6932 	wstring r;
6933 	foreach(dchar c; s)
6934 		r ~= c;
6935 	r ~= '\0';
6936 	return r.ptr;
6937 }
6938 private string[] split(in void[] a, char c) {
6939 		string[] ret;
6940 		size_t previous = 0;
6941 		foreach(i, char ch; cast(ubyte[]) a) {
6942 			if(ch == c) {
6943 				ret ~= cast(string) a[previous .. i];
6944 				previous = i + 1;
6945 			}
6946 		}
6947 		if(previous != a.length)
6948 			ret ~= cast(string) a[previous .. $];
6949 		return ret;
6950 	}
6951 
6952 version(without_opengl) {
6953 	enum OpenGlOptions {
6954 		no,
6955 	}
6956 } else {
6957 	/++
6958 		Determines if you want an OpenGL context created on the new window.
6959 
6960 
6961 		See more: [#topics-3d|in the 3d topic].
6962 
6963 		---
6964 		import arsd.simpledisplay;
6965 		void main() {
6966 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
6967 
6968 			// Set up the matrix
6969 			window.setAsCurrentOpenGlContext(); // make this window active
6970 
6971 			// This is called on each frame, we will draw our scene
6972 			window.redrawOpenGlScene = delegate() {
6973 
6974 			};
6975 
6976 			window.eventLoop(0);
6977 		}
6978 		---
6979 	+/
6980 	enum OpenGlOptions {
6981 		no, /// No OpenGL context is created
6982 		yes, /// Yes, create an OpenGL context
6983 	}
6984 
6985 	version(X11) {
6986 		static if (!SdpyIsUsingIVGLBinds) {
6987 
6988 
6989 			struct __GLXFBConfigRec {}
6990 			alias GLXFBConfig = __GLXFBConfigRec*;
6991 
6992 			//pragma(lib, "GL");
6993 			//pragma(lib, "GLU");
6994 			interface GLX {
6995 			extern(C) nothrow @nogc {
6996 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
6997 						const int *attrib_list);
6998 
6999 				 void glXCopyContext(Display *dpy, GLXContext src,
7000 						GLXContext dst, arch_ulong mask);
7001 
7002 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7003 						GLXContext share_list, Bool direct);
7004 
7005 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7006 						Pixmap pixmap);
7007 
7008 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7009 
7010 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7011 
7012 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7013 						int attrib, int *value);
7014 
7015 				 GLXContext glXGetCurrentContext();
7016 
7017 				 GLXDrawable glXGetCurrentDrawable();
7018 
7019 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7020 
7021 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7022 						GLXContext ctx);
7023 
7024 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7025 
7026 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7027 
7028 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7029 
7030 				 void glXUseXFont(Font font, int first, int count, int list_base);
7031 
7032 				 void glXWaitGL();
7033 
7034 				 void glXWaitX();
7035 
7036 
7037 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7038 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7039 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7040 
7041 				char* glXQueryExtensionsString (Display*, int);
7042 				void* glXGetProcAddress (const(char)*);
7043 
7044 			}
7045 			}
7046 
7047 			version(OSX)
7048 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7049 			else
7050 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7051 			shared static this() {
7052 				glx.loadDynamicLibrary();
7053 			}
7054 
7055 			alias glbindGetProcAddress = glXGetProcAddress;
7056 		}
7057 	} else version(Windows) {
7058 		/* it is done below by interface GL */
7059 	} else
7060 		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.");
7061 }
7062 
7063 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7064 alias Resizablity = Resizability;
7065 
7066 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7067 enum Resizability {
7068 	fixedSize, /// the window cannot be resized
7069 	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.
7070 	automaticallyScaleIfPossible, /// if possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size. If this is impossible, it will not allow the user to resize the window at all. Note: window.width and window.height WILL be adjusted, which might throw you off if you draw based on them, so keep track of your expected width and height separately. That way, when it is scaled, things won't be thrown off.
7071 
7072 	// FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events
7073 }
7074 
7075 
7076 /++
7077 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7078 +/
7079 enum TextAlignment : uint {
7080 	Left = 0, ///
7081 	Center = 1, ///
7082 	Right = 2, ///
7083 
7084 	VerticalTop = 0, ///
7085 	VerticalCenter = 4, ///
7086 	VerticalBottom = 8, ///
7087 }
7088 
7089 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7090 alias Rectangle = arsd.color.Rectangle;
7091 
7092 
7093 /++
7094 	Keyboard press and release events.
7095 +/
7096 struct KeyEvent {
7097 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7098 	Key key;
7099 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7100 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7101 
7102 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7103 
7104 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7105 
7106 	SimpleWindow window; /// associated Window
7107 
7108 	/++
7109 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7110 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7111 		to predict if char events are actually coming..
7112 
7113 		Only available on X systems since this information is not given ahead of time elsewhere.
7114 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7115 
7116 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7117 		and potential quirks I'd recommend avoiding it.
7118 
7119 		History:
7120 			Added April 26, 2021 (dub v9.5)
7121 	+/
7122 	version(X11)
7123 		dchar[] charsPossible;
7124 
7125 	// convert key event to simplified string representation a-la emacs
7126 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7127 		uint dpos = 0;
7128 		void put (const(char)[] s...) nothrow @trusted {
7129 			static if (growdest) {
7130 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7131 			} else {
7132 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7133 			}
7134 		}
7135 
7136 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7137 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7138 		}
7139 
7140 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7141 
7142 		// put modifiers
7143 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7144 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7145 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7146 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7147 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7148 
7149 		if (this.key) {
7150 			foreach (string kn; __traits(allMembers, Key)) {
7151 				if (this.key == __traits(getMember, Key, kn)) {
7152 					// HACK!
7153 					static if (kn == "N0") put("0");
7154 					else static if (kn == "N1") put("1");
7155 					else static if (kn == "N2") put("2");
7156 					else static if (kn == "N3") put("3");
7157 					else static if (kn == "N4") put("4");
7158 					else static if (kn == "N5") put("5");
7159 					else static if (kn == "N6") put("6");
7160 					else static if (kn == "N7") put("7");
7161 					else static if (kn == "N8") put("8");
7162 					else static if (kn == "N9") put("9");
7163 					else put(kn);
7164 					return dest[0..dpos];
7165 				}
7166 			}
7167 			put("Unknown");
7168 		} else {
7169 			if (dpos && dest[dpos-1] == '+') --dpos;
7170 		}
7171 		return dest[0..dpos];
7172 	}
7173 
7174 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7175 
7176 	/** Parse string into key name with modifiers. It accepts things like:
7177 	 *
7178 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7179 	 *
7180 	 * Ctrl+Win+1 -- windows style
7181 	 *
7182 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7183 	 *
7184 	 * Ctrl Win 1 -- and space
7185 	 *
7186 	 * and even "Win + 1 + Ctrl".
7187 	 */
7188 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7189 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7190 
7191 		// remove trailing spaces
7192 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7193 
7194 		// tokens delimited by blank, '+', or '-'
7195 		// null on eol
7196 		const(char)[] getToken () nothrow @trusted @nogc {
7197 			// remove leading spaces and delimiters
7198 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7199 			if (name.length == 0) return null; // oops, no more tokens
7200 			// get token
7201 			size_t epos = 0;
7202 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7203 			assert(epos > 0 && epos <= name.length);
7204 			auto res = name[0..epos];
7205 			name = name[epos..$];
7206 			return res;
7207 		}
7208 
7209 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7210 			if (s0.length != s1.length) return false;
7211 			foreach (immutable ci, char c0; s0) {
7212 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7213 				char c1 = s1[ci];
7214 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7215 				if (c0 != c1) return false;
7216 			}
7217 			return true;
7218 		}
7219 
7220 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7221 		if (updown !is null) *updown = -1;
7222 		KeyEvent res;
7223 		res.key = cast(Key)0; // just in case
7224 		const(char)[] tk, tkn; // last token
7225 		bool allowEmascStyle = true;
7226 		bool ignoreModifiers = false;
7227 		tokenloop: for (;;) {
7228 			tk = tkn;
7229 			tkn = getToken();
7230 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7231 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7232 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7233 			if (allowEmascStyle && tkn.length != 0) {
7234 				if (tk.length == 1) {
7235 					char mdc = tk[0];
7236 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7237 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7238 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7239 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7240 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7241 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7242 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7243 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7244 				}
7245 			}
7246 			allowEmascStyle = false;
7247 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7248 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7249 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7250 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7251 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7252 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7253 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7254 			if (tk.length == 0) continue;
7255 			// try key name
7256 			if (res.key == 0) {
7257 				// little hack
7258 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7259 					final switch (tk[0]) {
7260 						case '0': tk = "N0"; break;
7261 						case '1': tk = "N1"; break;
7262 						case '2': tk = "N2"; break;
7263 						case '3': tk = "N3"; break;
7264 						case '4': tk = "N4"; break;
7265 						case '5': tk = "N5"; break;
7266 						case '6': tk = "N6"; break;
7267 						case '7': tk = "N7"; break;
7268 						case '8': tk = "N8"; break;
7269 						case '9': tk = "N9"; break;
7270 					}
7271 				}
7272 				foreach (string kn; __traits(allMembers, Key)) {
7273 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7274 				}
7275 			}
7276 			// unknown or duplicate key name, get out of here
7277 			break;
7278 		}
7279 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7280 		return res; // something
7281 	}
7282 
7283 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7284 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7285 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7286 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7287 		}
7288 		bool ignoreMods;
7289 		int updown;
7290 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7291 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7292 		if (this.key != ke.key) {
7293 			// things like "ctrl+alt" are complicated
7294 			uint tkm = this.modifierState&modmask;
7295 			uint kkm = ke.modifierState&modmask;
7296 			Key tk = this.key;
7297 			// ke
7298 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7299 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7300 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7301 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7302 			// this
7303 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7304 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7305 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7306 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7307 			return (tk == ke.key && tkm == kkm);
7308 		}
7309 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7310 	}
7311 }
7312 
7313 /// Sets the application name.
7314 @property string ApplicationName(string name) {
7315 	return _applicationName = name;
7316 }
7317 
7318 string _applicationName;
7319 
7320 /// ditto
7321 @property string ApplicationName() {
7322 	if(_applicationName is null) {
7323 		import core.runtime;
7324 		return Runtime.args[0];
7325 	}
7326 	return _applicationName;
7327 }
7328 
7329 
7330 /// Type of a [MouseEvent].
7331 enum MouseEventType : int {
7332 	motion = 0, /// The mouse moved inside the window
7333 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7334 	buttonReleased = 2, /// A mouse button was released
7335 }
7336 
7337 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7338 /++
7339 	Listen for this on your event listeners if you are interested in mouse action.
7340 
7341 	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.
7342 
7343 	Examples:
7344 
7345 	This will draw boxes on the window with the mouse as you hold the left button.
7346 	---
7347 	import arsd.simpledisplay;
7348 
7349 	void main() {
7350 		auto window = new SimpleWindow();
7351 
7352 		window.eventLoop(0,
7353 			(MouseEvent ev) {
7354 				if(ev.modifierState & ModifierState.leftButtonDown) {
7355 					auto painter = window.draw();
7356 					painter.fillColor = Color.red;
7357 					painter.outlineColor = Color.black;
7358 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7359 				}
7360 			}
7361 		);
7362 	}
7363 	---
7364 +/
7365 struct MouseEvent {
7366 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7367 
7368 	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.
7369 	int y; /// Current Y position of the cursor when the event fired.
7370 
7371 	int dx; /// Change in X position since last report
7372 	int dy; /// Change in Y position since last report
7373 
7374 	MouseButton button; /// See [MouseButton]
7375 	int modifierState; /// See [ModifierState]
7376 
7377 	version(X11)
7378 		private Time timestamp;
7379 
7380 	/// Returns a linear representation of mouse button,
7381 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7382 	///
7383 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7384 	@property ubyte buttonLinear() const {
7385 		import core.bitop;
7386 		if(button == 0)
7387 			return 0;
7388 		return (bsf(button) + 1) & 0b1111;
7389 	}
7390 
7391 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7392 
7393 	SimpleWindow window; /// The window in which the event happened.
7394 
7395 	Point globalCoordinates() {
7396 		Point p;
7397 		if(window is null)
7398 			throw new Exception("wtf");
7399 		static if(UsingSimpledisplayX11) {
7400 			Window child;
7401 			XTranslateCoordinates(
7402 				XDisplayConnection.get,
7403 				window.impl.window,
7404 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7405 				x, y, &p.x, &p.y, &child);
7406 			return p;
7407 		} else version(Windows) {
7408 			POINT[1] points;
7409 			points[0].x = x;
7410 			points[0].y = y;
7411 			MapWindowPoints(
7412 				window.impl.hwnd,
7413 				null,
7414 				points.ptr,
7415 				points.length
7416 			);
7417 			p.x = points[0].x;
7418 			p.y = points[0].y;
7419 
7420 			return p;
7421 		} else version(OSXCocoa) {
7422 			throw new NotYetImplementedException();
7423 		} else static assert(0);
7424 	}
7425 
7426 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7427 
7428 	/**
7429 	can contain emacs-like modifier prefix
7430 	case-insensitive names:
7431 		lmbX/leftX
7432 		rmbX/rightX
7433 		mmbX/middleX
7434 		wheelX
7435 		motion (no prefix allowed)
7436 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7437 	*/
7438 	static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7439 		if (str.length == 0) return false; // just in case
7440 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7441 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7442 		auto anchor = str;
7443 		uint mods = 0; // uint.max == any
7444 		// interesting bits in kmod
7445 		uint kmodmask =
7446 			ModifierState.shift|
7447 			ModifierState.ctrl|
7448 			ModifierState.alt|
7449 			ModifierState.windows|
7450 			ModifierState.leftButtonDown|
7451 			ModifierState.middleButtonDown|
7452 			ModifierState.rightButtonDown|
7453 			0;
7454 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7455 		bool wasButtons = false;
7456 		while (str.length) {
7457 			if (str.ptr[0] <= ' ') {
7458 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7459 				continue;
7460 			}
7461 			// one-letter modifier?
7462 			if (str.length >= 2 && str.ptr[1] == '-') {
7463 				switch (str.ptr[0]) {
7464 					case '*': // "any" modifier (cannot be undone)
7465 						mods = mods.max;
7466 						break;
7467 					case 'C': case 'c': // emacs "ctrl"
7468 						if (mods != mods.max) mods |= ModifierState.ctrl;
7469 						break;
7470 					case 'M': case 'm': // emacs "meta"
7471 						if (mods != mods.max) mods |= ModifierState.alt;
7472 						break;
7473 					case 'S': case 's': // emacs "shift"
7474 						if (mods != mods.max) mods |= ModifierState.shift;
7475 						break;
7476 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7477 						if (mods != mods.max) mods |= ModifierState.windows;
7478 						break;
7479 					default:
7480 						return false; // unknown modifier
7481 				}
7482 				str = str[2..$];
7483 				continue;
7484 			}
7485 			// word
7486 			char[16] buf = void; // locased
7487 			auto wep = 0;
7488 			while (str.length) {
7489 				immutable char ch = str.ptr[0];
7490 				if (ch <= ' ' || ch == '-') break;
7491 				str = str[1..$];
7492 				if (wep > buf.length) return false; // too long
7493 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7494 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7495 				else return false; // invalid char
7496 			}
7497 			if (wep == 0) return false; // just in case
7498 			uint bnum;
7499 			enum UpDown { None = -1, Up, Down, Any }
7500 			auto updown = UpDown.None; // 0: up; 1: down
7501 			switch (buf[0..wep]) {
7502 				// left button
7503 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7504 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7505 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7506 				case "lmb": case "left": bnum = 0; break;
7507 				// middle button
7508 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7509 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7510 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7511 				case "mmb": case "middle": bnum = 1; break;
7512 				// right button
7513 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7514 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7515 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7516 				case "rmb": case "right": bnum = 2; break;
7517 				// wheel
7518 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7519 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7520 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7521 				case "wheel": bnum = 3; break;
7522 				// motion
7523 				case "motion": bnum = 7; break;
7524 				// unknown
7525 				default: return false;
7526 			}
7527 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7528 			// parse possible "-up" or "-down"
7529 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7530 				wep = 0;
7531 				foreach (immutable idx, immutable char ch; str[1..$]) {
7532 					if (ch <= ' ' || ch == '-') break;
7533 					assert(idx == wep); // for now; trick
7534 					if (wep > buf.length) { wep = 0; break; } // too long
7535 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7536 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7537 					else { wep = 0; break; } // invalid char
7538 				}
7539 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7540 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7541 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7542 				// remove parsed part
7543 				if (updown != UpDown.None) str = str[wep+1..$];
7544 			}
7545 			if (updown == UpDown.None) {
7546 				updown = UpDown.Down;
7547 			}
7548 			wasButtons = wasButtons || (bnum <= 2);
7549 			//assert(updown != UpDown.None);
7550 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7551 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7552 			if (lastButt != lastButt.max) {
7553 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7554 				if (mods != mods.max) {
7555 					uint butbit = 0;
7556 					final switch (lastButt&0x03) {
7557 						case 0: butbit = ModifierState.leftButtonDown; break;
7558 						case 1: butbit = ModifierState.middleButtonDown; break;
7559 						case 2: butbit = ModifierState.rightButtonDown; break;
7560 					}
7561 					     if (lastButt&Flag.Down) mods |= butbit;
7562 					else if (lastButt&Flag.Up) mods &= ~butbit;
7563 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7564 				}
7565 			}
7566 			// remember last button
7567 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7568 		}
7569 		// no button -- nothing to do
7570 		if (lastButt == lastButt.max) return false;
7571 		// done parsing, check if something's left
7572 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7573 		// remove action button from mask
7574 		if ((lastButt&0xff) < 3) {
7575 			final switch (lastButt&0x03) {
7576 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7577 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7578 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7579 			}
7580 		}
7581 		// special case: "Motion" means "ignore buttons"
7582 		if ((lastButt&0xff) == 7 && !wasButtons) {
7583 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7584 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7585 		}
7586 		uint kmod = event.modifierState&kmodmask;
7587 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7588 		// check modifier state
7589 		if (mods != mods.max) {
7590 			if (kmod != mods) return false;
7591 		}
7592 		// now check type
7593 		if ((lastButt&0xff) == 7) {
7594 			// motion
7595 			if (event.type != MouseEventType.motion) return false;
7596 		} else if ((lastButt&0xff) == 3) {
7597 			// wheel
7598 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7599 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7600 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7601 			return false;
7602 		} else {
7603 			// buttons
7604 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7605 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7606 			{
7607 				return false;
7608 			}
7609 			// button number
7610 			switch (lastButt&0x03) {
7611 				case 0: if (event.button != MouseButton.left) return false; break;
7612 				case 1: if (event.button != MouseButton.middle) return false; break;
7613 				case 2: if (event.button != MouseButton.right) return false; break;
7614 				default: return false;
7615 			}
7616 		}
7617 		return true;
7618 	}
7619 }
7620 
7621 version(arsd_mevent_strcmp_test) unittest {
7622 	MouseEvent event;
7623 	event.type = MouseEventType.buttonPressed;
7624 	event.button = MouseButton.left;
7625 	event.modifierState = ModifierState.ctrl;
7626 	assert(event == "C-LMB");
7627 	assert(event != "C-LMBUP");
7628 	assert(event != "C-LMB-UP");
7629 	assert(event != "C-S-LMB");
7630 	assert(event == "*-LMB");
7631 	assert(event != "*-LMB-UP");
7632 
7633 	event.type = MouseEventType.buttonReleased;
7634 	assert(event != "C-LMB");
7635 	assert(event == "C-LMBUP");
7636 	assert(event == "C-LMB-UP");
7637 	assert(event != "C-S-LMB");
7638 	assert(event != "*-LMB");
7639 	assert(event == "*-LMB-UP");
7640 
7641 	event.button = MouseButton.right;
7642 	event.modifierState |= ModifierState.shift;
7643 	event.type = MouseEventType.buttonPressed;
7644 	assert(event != "C-LMB");
7645 	assert(event != "C-LMBUP");
7646 	assert(event != "C-LMB-UP");
7647 	assert(event != "C-S-LMB");
7648 	assert(event != "*-LMB");
7649 	assert(event != "*-LMB-UP");
7650 
7651 	assert(event != "C-RMB");
7652 	assert(event != "C-RMBUP");
7653 	assert(event != "C-RMB-UP");
7654 	assert(event == "C-S-RMB");
7655 	assert(event == "*-RMB");
7656 	assert(event != "*-RMB-UP");
7657 }
7658 
7659 /// This gives a few more options to drawing lines and such
7660 struct Pen {
7661 	Color color; /// the foreground color
7662 	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.
7663 	Style style; /// See [Style]
7664 /+
7665 // From X.h
7666 
7667 #define LineSolid		0
7668 #define LineOnOffDash		1
7669 #define LineDoubleDash		2
7670        LineDou-        The full path of the line is drawn, but the
7671        bleDash         even dashes are filled differently from the
7672                        odd dashes (see fill-style) with CapButt
7673                        style used where even and odd dashes meet.
7674 
7675 
7676 
7677 /* capStyle */
7678 
7679 #define CapNotLast		0
7680 #define CapButt			1
7681 #define CapRound		2
7682 #define CapProjecting		3
7683 
7684 /* joinStyle */
7685 
7686 #define JoinMiter		0
7687 #define JoinRound		1
7688 #define JoinBevel		2
7689 
7690 /* fillStyle */
7691 
7692 #define FillSolid		0
7693 #define FillTiled		1
7694 #define FillStippled		2
7695 #define FillOpaqueStippled	3
7696 
7697 
7698 +/
7699 	/// Style of lines drawn
7700 	enum Style {
7701 		Solid, /// a solid line
7702 		Dashed, /// a dashed line
7703 		Dotted, /// a dotted line
7704 	}
7705 }
7706 
7707 
7708 /++
7709 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7710 
7711 
7712 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7713 
7714 	$(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.)
7715 
7716 	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.
7717 
7718 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7719 
7720 	$(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.
7721 
7722 	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!
7723 
7724 	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!)
7725 
7726 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7727 
7728 	---
7729 		auto image = new Image(256, 256);
7730 		scope(exit) destroy(image);
7731 	---
7732 
7733 	As long as you don't hold on to it outside the scope.
7734 
7735 	I might change it to be an owned pointer at some point in the future.
7736 
7737 	)
7738 
7739 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7740 	you can also often get a fair amount of speedup by getting the raw data format and
7741 	writing some custom code.
7742 
7743 	FIXME INSERT EXAMPLES HERE
7744 
7745 
7746 +/
7747 final class Image {
7748 	///
7749 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7750 		this.width = width;
7751 		this.height = height;
7752 		this.enableAlpha = enableAlpha;
7753 
7754 		impl.createImage(width, height, forcexshm, enableAlpha);
7755 	}
7756 
7757 	///
7758 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7759 		this(size.width, size.height, forcexshm, enableAlpha);
7760 	}
7761 
7762 	private bool suppressDestruction;
7763 
7764 	version(X11)
7765 	this(XImage* handle) {
7766 		this.handle = handle;
7767 		this.rawData = cast(ubyte*) handle.data;
7768 		this.width = handle.width;
7769 		this.height = handle.height;
7770 		this.enableAlpha = handle.depth == 32;
7771 		suppressDestruction = true;
7772 	}
7773 
7774 	~this() {
7775 		if(suppressDestruction) return;
7776 		impl.dispose();
7777 	}
7778 
7779 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7780 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7781 	pure const @system nothrow {
7782 		/*
7783 			To use these to draw a blue rectangle with size WxH at position X,Y...
7784 
7785 			// make certain that it will fit before we proceed
7786 			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!
7787 
7788 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7789 			// (though calculating them isn't really that expensive).
7790 			auto nextLineAdjustment = img.adjustmentForNextLine();
7791 			auto offR = img.redByteOffset();
7792 			auto offB = img.blueByteOffset();
7793 			auto offG = img.greenByteOffset();
7794 			auto bpp = img.bytesPerPixel();
7795 
7796 			auto data = img.getDataPointer();
7797 
7798 			// figure out the starting byte offset
7799 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7800 
7801 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7802 
7803 			// and now our drawing loop for the rectangle
7804 			foreach(y; 0 .. H) {
7805 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7806 				foreach(x; 0 .. W) {
7807 					// write our color
7808 					data[offR] = 0;
7809 					data[offG] = 0;
7810 					data[offB] = 255;
7811 
7812 					data += bpp; // moving to the next pixel is just an addition...
7813 				}
7814 				startOfLine += nextLineAdjustment;
7815 			}
7816 
7817 
7818 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7819 
7820 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7821 			can be made into a bitmask or something so we can write them as *uint...
7822 		*/
7823 
7824 		///
7825 		int offsetForTopLeftPixel() {
7826 			version(X11) {
7827 				return 0;
7828 			} else version(Windows) {
7829 				if(enableAlpha) {
7830 					return (width * 4) * (height - 1);
7831 				} else {
7832 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7833 				}
7834 			} else version(OSXCocoa) {
7835 				return 0 ; //throw new NotYetImplementedException();
7836 			} else static assert(0, "fill in this info for other OSes");
7837 		}
7838 
7839 		///
7840 		int offsetForPixel(int x, int y) {
7841 			version(X11) {
7842 				auto offset = (y * width + x) * 4;
7843 				return offset;
7844 			} else version(Windows) {
7845 				if(enableAlpha) {
7846 					auto itemsPerLine = width * 4;
7847 					// remember, bmps are upside down
7848 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7849 					return offset;
7850 				} else {
7851 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7852 					// remember, bmps are upside down
7853 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7854 					return offset;
7855 				}
7856 			} else version(OSXCocoa) {
7857 				return 0 ; //throw new NotYetImplementedException();
7858 			} else static assert(0, "fill in this info for other OSes");
7859 		}
7860 
7861 		///
7862 		int adjustmentForNextLine() {
7863 			version(X11) {
7864 				return width * 4;
7865 			} else version(Windows) {
7866 				// windows bmps are upside down, so the adjustment is actually negative
7867 				if(enableAlpha)
7868 					return - (cast(int) width * 4);
7869 				else
7870 					return -((cast(int) width * 3 + 3) / 4) * 4;
7871 			} else version(OSXCocoa) {
7872 				return 0 ; //throw new NotYetImplementedException();
7873 			} else static assert(0, "fill in this info for other OSes");
7874 		}
7875 
7876 		/// once you have the position of a pixel, use these to get to the proper color
7877 		int redByteOffset() {
7878 			version(X11) {
7879 				return 2;
7880 			} else version(Windows) {
7881 				return 2;
7882 			} else version(OSXCocoa) {
7883 				return 0 ; //throw new NotYetImplementedException();
7884 			} else static assert(0, "fill in this info for other OSes");
7885 		}
7886 
7887 		///
7888 		int greenByteOffset() {
7889 			version(X11) {
7890 				return 1;
7891 			} else version(Windows) {
7892 				return 1;
7893 			} else version(OSXCocoa) {
7894 				return 0 ; //throw new NotYetImplementedException();
7895 			} else static assert(0, "fill in this info for other OSes");
7896 		}
7897 
7898 		///
7899 		int blueByteOffset() {
7900 			version(X11) {
7901 				return 0;
7902 			} else version(Windows) {
7903 				return 0;
7904 			} else version(OSXCocoa) {
7905 				return 0 ; //throw new NotYetImplementedException();
7906 			} else static assert(0, "fill in this info for other OSes");
7907 		}
7908 
7909 		/// Only valid if [enableAlpha] is true
7910 		int alphaByteOffset() {
7911 			version(X11) {
7912 				return 3;
7913 			} else version(Windows) {
7914 				return 3;
7915 			} else version(OSXCocoa) {
7916 				return 3; //throw new NotYetImplementedException();
7917 			} else static assert(0, "fill in this info for other OSes");
7918 		}
7919 	}
7920 
7921 	///
7922 	final void putPixel(int x, int y, Color c) {
7923 		if(x < 0 || x >= width)
7924 			return;
7925 		if(y < 0 || y >= height)
7926 			return;
7927 
7928 		impl.setPixel(x, y, c);
7929 	}
7930 
7931 	///
7932 	final Color getPixel(int x, int y) {
7933 		if(x < 0 || x >= width)
7934 			return Color.transparent;
7935 		if(y < 0 || y >= height)
7936 			return Color.transparent;
7937 
7938 		version(OSXCocoa) throw new NotYetImplementedException(); else
7939 		return impl.getPixel(x, y);
7940 	}
7941 
7942 	///
7943 	final void opIndexAssign(Color c, int x, int y) {
7944 		putPixel(x, y, c);
7945 	}
7946 
7947 	///
7948 	TrueColorImage toTrueColorImage() {
7949 		auto tci = new TrueColorImage(width, height);
7950 		convertToRgbaBytes(tci.imageData.bytes);
7951 		return tci;
7952 	}
7953 
7954 	///
7955 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) {
7956 		auto tci = i.getAsTrueColorImage();
7957 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
7958 		img.setRgbaBytes(tci.imageData.bytes);
7959 		return img;
7960 	}
7961 
7962 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
7963 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
7964 	/// if you pass null, it will allocate a new one.
7965 	ubyte[] getRgbaBytes(ubyte[] where = null) {
7966 		if(where is null)
7967 			where = new ubyte[this.width*this.height*4];
7968 		convertToRgbaBytes(where);
7969 		return where;
7970 	}
7971 
7972 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
7973 	void setRgbaBytes(in ubyte[] from ) {
7974 		assert(from.length == this.width * this.height * 4);
7975 		setFromRgbaBytes(from);
7976 	}
7977 
7978 	// FIXME: make properly cross platform by getting rgba right
7979 
7980 	/// warning: this is not portable across platforms because the data format can change
7981 	ubyte* getDataPointer() {
7982 		return impl.rawData;
7983 	}
7984 
7985 	/// for use with getDataPointer
7986 	final int bytesPerLine() const pure @safe nothrow {
7987 		version(Windows)
7988 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
7989 		else version(X11)
7990 			return 4 * width;
7991 		else version(OSXCocoa)
7992 			return 4 * width;
7993 		else static assert(0);
7994 	}
7995 
7996 	/// for use with getDataPointer
7997 	final int bytesPerPixel() const pure @safe nothrow {
7998 		version(Windows)
7999 			return enableAlpha ? 4 : 3;
8000 		else version(X11)
8001 			return 4;
8002 		else version(OSXCocoa)
8003 			return 4;
8004 		else static assert(0);
8005 	}
8006 
8007 	///
8008 	immutable int width;
8009 
8010 	///
8011 	immutable int height;
8012 
8013 	///
8014 	immutable bool enableAlpha;
8015     //private:
8016 	mixin NativeImageImplementation!() impl;
8017 }
8018 
8019 /++
8020 	A convenience function to pop up a window displaying the image.
8021 	If you pass a win, it will draw the image in it. Otherwise, it will
8022 	create a window with the size of the image and run its event loop, closing
8023 	when a key is pressed.
8024 
8025 	History:
8026 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8027 		always block until the application quit which could cause bizarre behavior
8028 		inside a more complex application. Now, the default is to block until
8029 		this window closes if it is the only event loop running, and otherwise,
8030 		not to block at all and just pop up the display window asynchronously.
8031 +/
8032 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8033 	if(win is null) {
8034 		win = new SimpleWindow(image);
8035 		{
8036 			auto p = win.draw;
8037 			p.drawImage(Point(0, 0), image);
8038 		}
8039 		win.eventLoopWithBlockingMode(
8040 			bm, 0,
8041 			(KeyEvent ev) {
8042 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8043 			} );
8044 	} else {
8045 		win.image = image;
8046 	}
8047 }
8048 
8049 enum FontWeight : int {
8050 	dontcare = 0,
8051 	thin = 100,
8052 	extralight = 200,
8053 	light = 300,
8054 	regular = 400,
8055 	medium = 500,
8056 	semibold = 600,
8057 	bold = 700,
8058 	extrabold = 800,
8059 	heavy = 900
8060 }
8061 
8062 /++
8063 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8064 
8065 	History:
8066 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8067 +/
8068 interface MeasurableFont {
8069 	/++
8070 		Returns true if it is a monospace font, meaning each of the
8071 		glyphs (at least the ascii characters) have matching width
8072 		and no kerning, so you can determine the display width of some
8073 		strings by simply multiplying the string width by [averageWidth].
8074 
8075 		(Please note that multiply doesn't $(I actually) work in general,
8076 		consider characters like tab and newline, but it does sometimes.)
8077 	+/
8078 	bool isMonospace();
8079 
8080 	/++
8081 		The average width of glyphs in the font, traditionally equal to the
8082 		width of the lowercase x. Can be used to estimate bounding boxes,
8083 		especially if the font [isMonospace].
8084 
8085 		Given in pixels.
8086 	+/
8087 	int averageWidth();
8088 	/++
8089 		The height of the bounding box of a line.
8090 	+/
8091 	int height();
8092 	/++
8093 		The maximum ascent of a glyph above the baseline.
8094 
8095 		Given in pixels.
8096 	+/
8097 	int ascent();
8098 	/++
8099 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8100 
8101 		Given in pixels.
8102 	+/
8103 	int descent();
8104 	/++
8105 		The display width of the given string, and if you provide a window, it will use it to
8106 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8107 
8108 		Given in pixels.
8109 	+/
8110 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8111 
8112 }
8113 
8114 // FIXME: i need a font cache and it needs to handle disconnects.
8115 
8116 /++
8117 	Represents a font loaded off the operating system or the X server.
8118 
8119 
8120 	While the api here is unified cross platform, the fonts are not necessarily
8121 	available, even across machines of the same platform, so be sure to always check
8122 	for null (using [isNull]) and have a fallback plan.
8123 
8124 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8125 
8126 	Worst case, a null font will automatically fall back to the default font loaded
8127 	for your system.
8128 +/
8129 class OperatingSystemFont : MeasurableFont {
8130 	// FIXME: when the X Connection is lost, these need to be invalidated!
8131 	// that means I need to store the original stuff again to reconstruct it too.
8132 
8133 	version(X11) {
8134 		XFontStruct* font;
8135 		XFontSet fontset;
8136 
8137 		version(with_xft) {
8138 			XftFont* xftFont;
8139 			bool isXft;
8140 		}
8141 	} else version(Windows) {
8142 		HFONT font;
8143 		int width_;
8144 		int height_;
8145 	} else version(OSXCocoa) {
8146 		// FIXME
8147 	} else static assert(0);
8148 
8149 	/++
8150 		Constructs the class and immediately calls [load].
8151 	+/
8152 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8153 		load(name, size, weight, italic);
8154 	}
8155 
8156 	/++
8157 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8158 
8159 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8160 
8161 		History:
8162 			Added January 24, 2021.
8163 	+/
8164 	this() {
8165 		// this space intentionally left blank
8166 	}
8167 
8168 	/++
8169 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8170 
8171 		History:
8172 			Added November 13, 2020.
8173 	+/
8174 	version(with_xft)
8175 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8176 		unload();
8177 
8178 		if(!XftLibrary.attempted) {
8179 			XftLibrary.loadDynamicLibrary();
8180 		}
8181 
8182 		if(!XftLibrary.loadSuccessful)
8183 			return false;
8184 
8185 		auto display = XDisplayConnection.get;
8186 
8187 		char[256] nameBuffer = void;
8188 		int nbp = 0;
8189 
8190 		void add(in char[] a) {
8191 			nameBuffer[nbp .. nbp + a.length] = a[];
8192 			nbp += a.length;
8193 		}
8194 		add(name);
8195 
8196 		if(size) {
8197 			add(":size=");
8198 			add(toInternal!string(size));
8199 		}
8200 		if(weight != FontWeight.dontcare) {
8201 			add(":weight=");
8202 			add(weightToString(weight));
8203 		}
8204 		if(italic)
8205 			add(":slant=100");
8206 
8207 		nameBuffer[nbp] = 0;
8208 
8209 		this.xftFont = XftFontOpenName(
8210 			display,
8211 			DefaultScreen(display),
8212 			nameBuffer.ptr
8213 		);
8214 
8215 		this.isXft = true;
8216 
8217 		if(xftFont !is null) {
8218 			isMonospace_ = stringWidth("x") == stringWidth("M");
8219 			ascent_ = xftFont.ascent;
8220 			descent_ = xftFont.descent;
8221 		}
8222 
8223 		return !isNull();
8224 	}
8225 
8226 	/++
8227 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8228 
8229 
8230 		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.
8231 
8232 		If `pattern` is null, it returns all available font families.
8233 
8234 		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.
8235 
8236 		The format of the pattern is platform-specific.
8237 
8238 		History:
8239 			Added May 1, 2021 (dub v9.5)
8240 	+/
8241 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8242 		version(Windows) {
8243 			auto hdc = GetDC(null);
8244 			scope(exit) ReleaseDC(null, hdc);
8245 			LOGFONT logfont;
8246 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8247 				auto localHandler = *(cast(typeof(handler)*) p);
8248 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8249 			}
8250 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8251 		} else version(X11) {
8252 			//import core.stdc.stdio;
8253 			bool done = false;
8254 			version(with_xft) {
8255 				if(!XftLibrary.attempted) {
8256 					XftLibrary.loadDynamicLibrary();
8257 				}
8258 
8259 				if(!XftLibrary.loadSuccessful)
8260 					goto skipXft;
8261 
8262 				if(!FontConfigLibrary.attempted)
8263 					FontConfigLibrary.loadDynamicLibrary();
8264 				if(!FontConfigLibrary.loadSuccessful)
8265 					goto skipXft;
8266 
8267 				{
8268 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8269 					if(got is null)
8270 						goto skipXft;
8271 					scope(exit) FcFontSetDestroy(got);
8272 
8273 					auto fontPatterns = got.fonts[0 .. got.nfont];
8274 					foreach(candidate; fontPatterns) {
8275 						char* where, whereStyle;
8276 
8277 						char* pmg = FcNameUnparse(candidate);
8278 
8279 						//FcPatternGetString(candidate, "family", 0, &where);
8280 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8281 						//if(where && whereStyle) {
8282 						if(pmg) {
8283 							if(!handler(pmg.sliceCString))
8284 								return;
8285 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8286 						}
8287 					}
8288 				}
8289 			}
8290 
8291 			skipXft:
8292 
8293 			if(pattern is null)
8294 				pattern = "*";
8295 
8296 			int count;
8297 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8298 			scope(exit) XFreeFontNames(coreFontsRaw);
8299 
8300 			auto coreFonts = coreFontsRaw[0 .. count];
8301 
8302 			foreach(font; coreFonts) {
8303 				char[128] tmp;
8304 				tmp[0 ..5] = "core:";
8305 				auto cf = font.sliceCString;
8306 				if(5 + cf.length > tmp.length)
8307 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8308 				tmp[5 .. 5 + cf.length] = cf;
8309 				if(!handler(tmp[0 .. 5 + cf.length]))
8310 					return;
8311 			}
8312 		}
8313 	}
8314 
8315 	/++
8316 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8317 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8318 
8319 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8320 		underlying system doesn't support returning the raw bytes.
8321 
8322 		History:
8323 			Added September 10, 2021 (dub v10.3)
8324 	+/
8325 	ubyte[] getTtfBytes() {
8326 		if(isNull)
8327 			return null;
8328 
8329 		version(Windows) {
8330 			auto dc = GetDC(null);
8331 			auto orig = SelectObject(dc, font);
8332 
8333 			scope(exit) {
8334 				SelectObject(dc, orig);
8335 				ReleaseDC(null, dc);
8336 			}
8337 
8338 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8339 			if(res == GDI_ERROR)
8340 				return null;
8341 
8342 			ubyte[] buffer = new ubyte[](res);
8343 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8344 			if(res == GDI_ERROR)
8345 				return null; // wtf really tbh
8346 
8347 			return buffer;
8348 		} else version(with_xft) {
8349 			if(isXft && xftFont) {
8350 				if(!FontConfigLibrary.attempted)
8351 					FontConfigLibrary.loadDynamicLibrary();
8352 				if(!FontConfigLibrary.loadSuccessful)
8353 					return null;
8354 
8355 				char* file;
8356 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8357 					if (file !is null && file[0]) {
8358 						import core.stdc.stdio;
8359 						auto fp = fopen(file, "rb");
8360 						if(fp is null)
8361 							return null;
8362 						scope(exit)
8363 							fclose(fp);
8364 						fseek(fp, 0, SEEK_END);
8365 						ubyte[] buffer = new ubyte[](ftell(fp));
8366 						fseek(fp, 0, SEEK_SET);
8367 
8368 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8369 						if(got != buffer.length)
8370 							return null;
8371 
8372 						return buffer;
8373 					}
8374 				}
8375 			}
8376 			return null;
8377 		}
8378 	}
8379 
8380 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8381 
8382 	private string weightToString(FontWeight weight) {
8383 		with(FontWeight)
8384 		final switch(weight) {
8385 			case dontcare: return "*";
8386 			case thin: return "extralight";
8387 			case extralight: return "extralight";
8388 			case light: return "light";
8389 			case regular: return "regular";
8390 			case medium: return "medium";
8391 			case semibold: return "demibold";
8392 			case bold: return "bold";
8393 			case extrabold: return "demibold";
8394 			case heavy: return "black";
8395 		}
8396 	}
8397 
8398 	/++
8399 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8400 
8401 		History:
8402 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8403 	+/
8404 	version(X11)
8405 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8406 		unload();
8407 
8408 		string xfontstr;
8409 
8410 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8411 			// this is kinda a disgusting hack but if the user sends an exact
8412 			// string I'd like to honor it...
8413 			xfontstr = name;
8414 		} else {
8415 			string weightstr = weightToString(weight);
8416 			string sizestr;
8417 			if(size == 0)
8418 				sizestr = "*";
8419 			else
8420 				sizestr = toInternal!string(size);
8421 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8422 		}
8423 
8424 		//import std.stdio; writeln(xfontstr);
8425 
8426 		auto display = XDisplayConnection.get;
8427 
8428 		font = XLoadQueryFont(display, xfontstr.ptr);
8429 		if(font is null)
8430 			return false;
8431 
8432 		char** lol;
8433 		int lol2;
8434 		char* lol3;
8435 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8436 
8437 		prepareFontInfo();
8438 
8439 		return !isNull();
8440 	}
8441 
8442 	version(X11)
8443 	private void prepareFontInfo() {
8444 		if(font !is null) {
8445 			isMonospace_ = stringWidth("l") == stringWidth("M");
8446 			ascent_ = font.max_bounds.ascent;
8447 			descent_ = font.max_bounds.descent;
8448 		}
8449 	}
8450 
8451 	/++
8452 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8453 
8454 		History:
8455 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8456 	+/
8457 	version(Windows)
8458 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8459 		unload();
8460 
8461 		WCharzBuffer buffer = WCharzBuffer(name);
8462 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8463 
8464 		prepareFontInfo(hdc);
8465 
8466 		return !isNull();
8467 	}
8468 
8469 	version(Windows)
8470 	void prepareFontInfo(HDC hdc = null) {
8471 		if(font is null)
8472 			return;
8473 
8474 		TEXTMETRIC tm;
8475 		auto dc = hdc ? hdc : GetDC(null);
8476 		auto orig = SelectObject(dc, font);
8477 		GetTextMetrics(dc, &tm);
8478 		SelectObject(dc, orig);
8479 		if(hdc is null)
8480 			ReleaseDC(null, dc);
8481 
8482 		width_ = tm.tmAveCharWidth;
8483 		height_ = tm.tmHeight;
8484 		ascent_ = tm.tmAscent;
8485 		descent_ = tm.tmDescent;
8486 		// 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.
8487 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8488 	}
8489 
8490 
8491 	/++
8492 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8493 
8494 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8495 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8496 
8497 		On Windows, it forwards directly to [loadWin32].
8498 
8499 		Params:
8500 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8501 			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.
8502 			weight = approximate boldness, results may vary.
8503 			italic = try to get a slanted version of the given font.
8504 
8505 		History:
8506 			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.
8507 	+/
8508 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8509 		version(X11) {
8510 			version(with_xft) {
8511 				if(name.length > 5 && name[0 .. 5] == "core:") {
8512 					goto core;
8513 				}
8514 
8515 				if(loadXft(name, size, weight, italic))
8516 					return true;
8517 				// if xft fails, fallback to core to avoid breaking
8518 				// code that already depended on this.
8519 			}
8520 
8521 			core:
8522 
8523 			if(name.length > 5 && name[0 .. 5] == "core:") {
8524 				name = name[5 .. $];
8525 			}
8526 
8527 			return loadCoreX(name, size, weight, italic);
8528 		} else version(Windows) {
8529 			return loadWin32(name, size, weight, italic);
8530 		} else version(OSXCocoa) {
8531 			// FIXME
8532 			return false;
8533 		} else static assert(0);
8534 	}
8535 
8536 	///
8537 	void unload() {
8538 		if(isNull())
8539 			return;
8540 
8541 		version(X11) {
8542 			auto display = XDisplayConnection.display;
8543 
8544 			if(display is null)
8545 				return;
8546 
8547 			version(with_xft) {
8548 				if(isXft) {
8549 					if(xftFont)
8550 						XftFontClose(display, xftFont);
8551 					isXft = false;
8552 					xftFont = null;
8553 					return;
8554 				}
8555 			}
8556 
8557 			if(font && font !is ScreenPainterImplementation.defaultfont)
8558 				XFreeFont(display, font);
8559 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8560 				XFreeFontSet(display, fontset);
8561 
8562 			font = null;
8563 			fontset = null;
8564 		} else version(Windows) {
8565 			DeleteObject(font);
8566 			font = null;
8567 		} else version(OSXCocoa) {
8568 			// FIXME
8569 		} else static assert(0);
8570 	}
8571 
8572 	private bool isMonospace_;
8573 
8574 	/++
8575 		History:
8576 			Added January 16, 2021
8577 	+/
8578 	bool isMonospace() {
8579 		return isMonospace_;
8580 	}
8581 
8582 	/++
8583 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8584 
8585 		History:
8586 			Added March 26, 2020
8587 			Documented January 16, 2021
8588 	+/
8589 	int averageWidth() {
8590 		version(X11) {
8591 			return stringWidth("x");
8592 		} else version(Windows)
8593 			return width_;
8594 		else assert(0);
8595 	}
8596 
8597 	/++
8598 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8599 
8600 		History:
8601 			Added January 16, 2021
8602 	+/
8603 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8604 	// FIXME: what about tab?
8605 		if(isNull)
8606 			return 0;
8607 
8608 		version(X11) {
8609 			version(with_xft)
8610 				if(isXft && xftFont !is null) {
8611 					//return xftFont.max_advance_width;
8612 					XGlyphInfo extents;
8613 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8614 					//import std.stdio; writeln(extents);
8615 					return extents.xOff;
8616 				}
8617 			if(font is null)
8618 				return 0;
8619 			else if(fontset) {
8620 				XRectangle rect;
8621 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8622 
8623 				return rect.width;
8624 			} else {
8625 				return XTextWidth(font, s.ptr, cast(int) s.length);
8626 			}
8627 		} else version(Windows) {
8628 			WCharzBuffer buffer = WCharzBuffer(s);
8629 
8630 			return stringWidth(buffer.slice, window);
8631 		}
8632 		else assert(0);
8633 	}
8634 
8635 	version(Windows)
8636 	/// ditto
8637 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8638 		if(isNull)
8639 			return 0;
8640 		version(Windows) {
8641 			SIZE size;
8642 
8643 			prepareContext(window);
8644 			scope(exit) releaseContext();
8645 
8646 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8647 
8648 			return size.cx;
8649 		} else {
8650 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8651 			static assert(0, "not implemented yet");
8652 			//return stringWidth(s, window);
8653 		}
8654 	}
8655 
8656 	private {
8657 		int prepRefcount;
8658 
8659 		version(Windows) {
8660 			HDC dc;
8661 			HANDLE orig;
8662 			HWND hwnd;
8663 		}
8664 	}
8665 	/++
8666 		[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.
8667 
8668 		History:
8669 			Added January 23, 2021
8670 	+/
8671 	void prepareContext(SimpleWindow window = null) {
8672 		prepRefcount++;
8673 		if(prepRefcount == 1) {
8674 			version(Windows) {
8675 				hwnd = window is null ? null : window.impl.hwnd;
8676 				dc = GetDC(hwnd);
8677 				orig = SelectObject(dc, font);
8678 			}
8679 		}
8680 	}
8681 	/// ditto
8682 	void releaseContext() {
8683 		prepRefcount--;
8684 		if(prepRefcount == 0) {
8685 			version(Windows) {
8686 				SelectObject(dc, orig);
8687 				ReleaseDC(hwnd, dc);
8688 				hwnd = null;
8689 				dc = null;
8690 				orig = null;
8691 			}
8692 		}
8693 	}
8694 
8695 	/+
8696 		FIXME: I think I need advance and kerning pair
8697 
8698 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8699 	+/
8700 
8701 	/++
8702 		Returns the height of the font.
8703 
8704 		History:
8705 			Added March 26, 2020
8706 			Documented January 16, 2021
8707 	+/
8708 	int height() {
8709 		version(X11) {
8710 			version(with_xft)
8711 				if(isXft && xftFont !is null) {
8712 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8713 				}
8714 			if(font is null)
8715 				return 0;
8716 			return font.max_bounds.ascent + font.max_bounds.descent;
8717 		} else version(Windows)
8718 			return height_;
8719 		else assert(0);
8720 	}
8721 
8722 	private int ascent_;
8723 	private int descent_;
8724 
8725 	/++
8726 		Max ascent above the baseline.
8727 
8728 		History:
8729 			Added January 22, 2021
8730 	+/
8731 	int ascent() {
8732 		return ascent_;
8733 	}
8734 
8735 	/++
8736 		Max descent below the baseline.
8737 
8738 		History:
8739 			Added January 22, 2021
8740 	+/
8741 	int descent() {
8742 		return descent_;
8743 	}
8744 
8745 	/++
8746 		Loads the default font used by [ScreenPainter] if none others are loaded.
8747 
8748 		Returns:
8749 			This method mutates the `this` object, but then returns `this` for
8750 			easy chaining like:
8751 
8752 			---
8753 			auto font = foo.isNull ? foo : foo.loadDefault
8754 			---
8755 
8756 		History:
8757 			Added previously, but left unimplemented until January 24, 2021.
8758 	+/
8759 	OperatingSystemFont loadDefault() {
8760 		unload();
8761 
8762 		version(X11) {
8763 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8764 			// but meh since sdpy does its own thing, this should be ok too
8765 
8766 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8767 			this.font = ScreenPainterImplementation.defaultfont;
8768 			this.fontset = ScreenPainterImplementation.defaultfontset;
8769 
8770 			prepareFontInfo();
8771 		} else version(Windows) {
8772 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8773 			this.font = ScreenPainterImplementation.defaultGuiFont;
8774 
8775 			prepareFontInfo();
8776 		} else throw new NotYetImplementedException();
8777 
8778 		return this;
8779 	}
8780 
8781 	///
8782 	bool isNull() {
8783 		version(OSXCocoa) throw new NotYetImplementedException(); else {
8784 			version(with_xft)
8785 				if(isXft)
8786 					return xftFont is null;
8787 			return font is null;
8788 		}
8789 	}
8790 
8791 	/* Metrics */
8792 	/+
8793 		GetABCWidth
8794 		GetKerningPairs
8795 
8796 		if I do it right, I can size it all here, and match
8797 		what happens when I draw the full string with the OS functions.
8798 
8799 		subclasses might do the same thing while getting the glyphs on images
8800 	struct GlyphInfo {
8801 		int glyph;
8802 
8803 		size_t stringIdxStart;
8804 		size_t stringIdxEnd;
8805 
8806 		Rectangle boundingBox;
8807 	}
8808 	GlyphInfo[] getCharBoxes() {
8809 		// XftTextExtentsUtf8
8810 		return null;
8811 
8812 	}
8813 	+/
8814 
8815 	~this() {
8816 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
8817 		unload();
8818 	}
8819 }
8820 
8821 version(Windows)
8822 private string sliceCString(const(wchar)[] w) {
8823 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
8824 }
8825 
8826 private inout(char)[] sliceCString(inout(char)* s) {
8827 	import core.stdc.string;
8828 	auto len = strlen(s);
8829 	return s[0 .. len];
8830 }
8831 
8832 /**
8833 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
8834 	than constructing it directly. Then, it is reference counted so you can pass it
8835 	at around and when the last ref goes out of scope, the buffered drawing activities
8836 	are all carried out.
8837 
8838 
8839 	Most functions use the outlineColor instead of taking a color themselves.
8840 	ScreenPainter is reference counted and draws its buffer to the screen when its
8841 	final reference goes out of scope.
8842 */
8843 struct ScreenPainter {
8844 	CapableOfBeingDrawnUpon window;
8845 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) {
8846 		this.window = window;
8847 		if(window.closed)
8848 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
8849 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
8850 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
8851 		if(window.activeScreenPainter !is null) {
8852 			impl = window.activeScreenPainter;
8853 			if(impl.referenceCount == 0) {
8854 				impl.window = window;
8855 				impl.create(handle);
8856 			}
8857 			impl.manualInvalidations = manualInvalidations;
8858 			impl.referenceCount++;
8859 		//	writeln("refcount ++ ", impl.referenceCount);
8860 		} else {
8861 			impl = new ScreenPainterImplementation;
8862 			impl.window = window;
8863 			impl.create(handle);
8864 			impl.referenceCount = 1;
8865 			impl.manualInvalidations = manualInvalidations;
8866 			window.activeScreenPainter = impl;
8867 			//import std.stdio; writeln("constructed");
8868 		}
8869 
8870 		copyActiveOriginals();
8871 	}
8872 
8873 	/++
8874 		If you are using manual invalidations, this informs the
8875 		window system that a section needs to be redrawn.
8876 
8877 		If you didn't opt into manual invalidation, you don't
8878 		have to call this.
8879 
8880 		History:
8881 			Added December 30, 2021 (dub v10.5)
8882 	+/
8883 	void invalidateRect(Rectangle rect) {
8884 		if(impl is null) return;
8885 
8886 		// transform(rect)
8887 		rect.left += _originX;
8888 		rect.right += _originX;
8889 		rect.top += _originY;
8890 		rect.bottom += _originY;
8891 
8892 		impl.invalidateRect(rect);
8893 	}
8894 
8895 	private Pen originalPen;
8896 	private Color originalFillColor;
8897 	private arsd.color.Rectangle originalClipRectangle;
8898 	void copyActiveOriginals() {
8899 		if(impl is null) return;
8900 		originalPen = impl._activePen;
8901 		originalFillColor = impl._fillColor;
8902 		originalClipRectangle = impl._clipRectangle;
8903 	}
8904 
8905 	~this() {
8906 		if(impl is null) return;
8907 		impl.referenceCount--;
8908 		//writeln("refcount -- ", impl.referenceCount);
8909 		if(impl.referenceCount == 0) {
8910 			//import std.stdio; writeln("destructed");
8911 			impl.dispose();
8912 			*window.activeScreenPainter = ScreenPainterImplementation.init;
8913 			//import std.stdio; writeln("paint finished");
8914 		} else {
8915 			// there is still an active reference, reset stuff so the
8916 			// next user doesn't get weirdness via the reference
8917 			this.rasterOp = RasterOp.normal;
8918 			pen = originalPen;
8919 			fillColor = originalFillColor;
8920 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
8921 		}
8922 	}
8923 
8924 	this(this) {
8925 		if(impl is null) return;
8926 		impl.referenceCount++;
8927 		//writeln("refcount ++ ", impl.referenceCount);
8928 
8929 		copyActiveOriginals();
8930 	}
8931 
8932 	private int _originX;
8933 	private int _originY;
8934 	@property int originX() { return _originX; }
8935 	@property int originY() { return _originY; }
8936 	@property int originX(int a) {
8937 		_originX = a;
8938 		return _originX;
8939 	}
8940 	@property int originY(int a) {
8941 		_originY = a;
8942 		return _originY;
8943 	}
8944 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
8945 	private void transform(ref Point p) {
8946 		if(impl is null) return;
8947 		p.x += _originX;
8948 		p.y += _originY;
8949 	}
8950 
8951 	// this needs to be checked BEFORE the originX/Y transformation
8952 	private bool isClipped(Point p) {
8953 		return !currentClipRectangle.contains(p);
8954 	}
8955 	private bool isClipped(Point p, int width, int height) {
8956 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
8957 	}
8958 	private bool isClipped(Point p, Size s) {
8959 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
8960 	}
8961 	private bool isClipped(Point p, Point p2) {
8962 		// need to ensure the end points are actually included inside, so the +1 does that
8963 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
8964 	}
8965 
8966 
8967 	/++
8968 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
8969 
8970 		Returns:
8971 			The old clip rectangle.
8972 
8973 		History:
8974 			Return value was `void` prior to May 10, 2021.
8975 
8976 	+/
8977 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
8978 		if(impl is null) return currentClipRectangle;
8979 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
8980 			return currentClipRectangle; // no need to do anything
8981 		auto old = currentClipRectangle;
8982 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
8983 		transform(pt);
8984 
8985 		impl.setClipRectangle(pt.x, pt.y, width, height);
8986 
8987 		return old;
8988 	}
8989 
8990 	/// ditto
8991 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
8992 		if(impl is null) return currentClipRectangle;
8993 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
8994 	}
8995 
8996 	///
8997 	void setFont(OperatingSystemFont font) {
8998 		if(impl is null) return;
8999 		impl.setFont(font);
9000 	}
9001 
9002 	///
9003 	int fontHeight() {
9004 		if(impl is null) return 0;
9005 		return impl.fontHeight();
9006 	}
9007 
9008 	private Pen activePen;
9009 
9010 	///
9011 	@property void pen(Pen p) {
9012 		if(impl is null) return;
9013 		activePen = p;
9014 		impl.pen(p);
9015 	}
9016 
9017 	///
9018 	@scriptable
9019 	@property void outlineColor(Color c) {
9020 		if(impl is null) return;
9021 		if(activePen.color == c)
9022 			return;
9023 		activePen.color = c;
9024 		impl.pen(activePen);
9025 	}
9026 
9027 	///
9028 	@scriptable
9029 	@property void fillColor(Color c) {
9030 		if(impl is null) return;
9031 		impl.fillColor(c);
9032 	}
9033 
9034 	///
9035 	@property void rasterOp(RasterOp op) {
9036 		if(impl is null) return;
9037 		impl.rasterOp(op);
9038 	}
9039 
9040 
9041 	void updateDisplay() {
9042 		// FIXME this should do what the dtor does
9043 	}
9044 
9045 	/// 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)
9046 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9047 		if(impl is null) return;
9048 		if(isClipped(upperLeft, width, height)) return;
9049 		transform(upperLeft);
9050 		version(Windows) {
9051 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9052 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9053 			RECT clip = scroll;
9054 			RECT uncovered;
9055 			HRGN hrgn;
9056 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9057 				throw new Exception("ScrollDC");
9058 
9059 		} else version(X11) {
9060 			// FIXME: clip stuff outside this rectangle
9061 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9062 		} else version(OSXCocoa) {
9063 			throw new NotYetImplementedException();
9064 		} else static assert(0);
9065 	}
9066 
9067 	///
9068 	void clear(Color color = Color.white()) {
9069 		if(impl is null) return;
9070 		fillColor = color;
9071 		outlineColor = color;
9072 		drawRectangle(Point(0, 0), window.width, window.height);
9073 	}
9074 
9075 	/++
9076 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9077 
9078 		Params:
9079 			upperLeft = point on the window where the upper left corner of the image will be drawn
9080 			imageUpperLeft = point on the image to start the slice to draw
9081 			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.
9082 		History:
9083 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9084 	+/
9085 	version(OSXCocoa) {} else // NotYetImplementedException
9086 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9087 		if(impl is null) return;
9088 		if(isClipped(upperLeft, s.width, s.height)) return;
9089 		transform(upperLeft);
9090 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9091 	}
9092 
9093 	///
9094 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9095 		if(impl is null) return;
9096 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9097 		transform(upperLeft);
9098 		if(w == 0 || w > i.width)
9099 			w = i.width;
9100 		if(h == 0 || h > i.height)
9101 			h = i.height;
9102 		if(upperLeftOfImage.x < 0)
9103 			upperLeftOfImage.x = 0;
9104 		if(upperLeftOfImage.y < 0)
9105 			upperLeftOfImage.y = 0;
9106 
9107 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9108 	}
9109 
9110 	///
9111 	Size textSize(in char[] text) {
9112 		if(impl is null) return Size(0, 0);
9113 		return impl.textSize(text);
9114 	}
9115 
9116 	/++
9117 		Draws a string in the window with the set font (see [setFont] to change it).
9118 
9119 		Params:
9120 			upperLeft = the upper left point of the bounding box of the text
9121 			text = the string to draw
9122 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9123 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9124 	+/
9125 	@scriptable
9126 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9127 		if(impl is null) return;
9128 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9129 			if(isClipped(upperLeft, lowerRight)) return;
9130 			transform(lowerRight);
9131 		} else {
9132 			if(isClipped(upperLeft, textSize(text))) return;
9133 		}
9134 		transform(upperLeft);
9135 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9136 	}
9137 
9138 	/++
9139 		Draws text using a custom font.
9140 
9141 		This is still MAJOR work in progress.
9142 
9143 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9144 	+/
9145 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9146 		if(impl is null) return;
9147 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9148 		transform(upperLeft);
9149 		font.drawString(this, upperLeft, text);
9150 	}
9151 
9152 	version(Windows)
9153 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9154 		if(impl is null) return;
9155 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9156 		transform(upperLeft);
9157 
9158 		if(text.length && text[$-1] == '\n')
9159 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9160 
9161 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9162 	}
9163 
9164 	static struct TextDrawingContext {
9165 		Point boundingBoxUpperLeft;
9166 		Point boundingBoxLowerRight;
9167 
9168 		Point currentLocation;
9169 
9170 		Point lastDrewUpperLeft;
9171 		Point lastDrewLowerRight;
9172 
9173 		// how do i do right aligned rich text?
9174 		// i kinda want to do a pre-made drawing then right align
9175 		// draw the whole block.
9176 		//
9177 		// That's exactly the diff: inline vs block stuff.
9178 
9179 		// I need to get coordinates of an inline section out too,
9180 		// not just a bounding box, but a series of bounding boxes
9181 		// should be ok. Consider what's needed to detect a click
9182 		// on a link in the middle of a paragraph breaking a line.
9183 		//
9184 		// Generally, we should be able to get the rectangles of
9185 		// any portion we draw.
9186 		//
9187 		// It also needs to tell what text is left if it overflows
9188 		// out of the box, so we can do stuff like float images around
9189 		// it. It should not attempt to draw a letter that would be
9190 		// clipped.
9191 		//
9192 		// I might also turn off word wrap stuff.
9193 	}
9194 
9195 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9196 		if(impl is null) return;
9197 		// FIXME
9198 	}
9199 
9200 	/// Drawing an individual pixel is slow. Avoid it if possible.
9201 	void drawPixel(Point where) {
9202 		if(impl is null) return;
9203 		if(isClipped(where)) return;
9204 		transform(where);
9205 		impl.drawPixel(where.x, where.y);
9206 	}
9207 
9208 
9209 	/// Draws a pen using the current pen / outlineColor
9210 	@scriptable
9211 	void drawLine(Point starting, Point ending) {
9212 		if(impl is null) return;
9213 		if(isClipped(starting, ending)) return;
9214 		transform(starting);
9215 		transform(ending);
9216 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9217 	}
9218 
9219 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9220 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9221 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9222 	@scriptable
9223 	void drawRectangle(Point upperLeft, int width, int height) {
9224 		if(impl is null) return;
9225 		if(isClipped(upperLeft, width, height)) return;
9226 		transform(upperLeft);
9227 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9228 	}
9229 
9230 	/// ditto
9231 	void drawRectangle(Point upperLeft, Size size) {
9232 		if(impl is null) return;
9233 		if(isClipped(upperLeft, size.width, size.height)) return;
9234 		transform(upperLeft);
9235 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9236 	}
9237 
9238 	/// ditto
9239 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9240 		if(impl is null) return;
9241 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9242 		transform(upperLeft);
9243 		transform(lowerRightInclusive);
9244 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9245 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9246 	}
9247 
9248 	// overload added on May 12, 2021
9249 	/// ditto
9250 	void drawRectangle(Rectangle rect) {
9251 		drawRectangle(rect.upperLeft, rect.size);
9252 	}
9253 
9254 	/// Arguments are the points of the bounding rectangle
9255 	void drawEllipse(Point upperLeft, Point lowerRight) {
9256 		if(impl is null) return;
9257 		if(isClipped(upperLeft, lowerRight)) return;
9258 		transform(upperLeft);
9259 		transform(lowerRight);
9260 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9261 	}
9262 
9263 	/++
9264 		start and finish are units of degrees * 64
9265 
9266 		History:
9267 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9268 
9269 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9270 	+/
9271 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9272 		if(impl is null) return;
9273 		// FIXME: not actually implemented
9274 		if(isClipped(upperLeft, width, height)) return;
9275 		transform(upperLeft);
9276 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9277 	}
9278 
9279 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9280 	void drawCircle(Point upperLeft, int diameter) {
9281 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9282 	}
9283 
9284 	/// .
9285 	void drawPolygon(Point[] vertexes) {
9286 		if(impl is null) return;
9287 		assert(vertexes.length);
9288 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9289 		foreach(ref vertex; vertexes) {
9290 			if(vertex.x < minX)
9291 				minX = vertex.x;
9292 			if(vertex.y < minY)
9293 				minY = vertex.y;
9294 			if(vertex.x > maxX)
9295 				maxX = vertex.x;
9296 			if(vertex.y > maxY)
9297 				maxY = vertex.y;
9298 			transform(vertex);
9299 		}
9300 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9301 		impl.drawPolygon(vertexes);
9302 	}
9303 
9304 	/// ditto
9305 	void drawPolygon(Point[] vertexes...) {
9306 		if(impl is null) return;
9307 		drawPolygon(vertexes);
9308 	}
9309 
9310 
9311 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9312 
9313 	//mixin NativeScreenPainterImplementation!() impl;
9314 
9315 
9316 	// HACK: if I mixin the impl directly, it won't let me override the copy
9317 	// constructor! The linker complains about there being multiple definitions.
9318 	// I'll make the best of it and reference count it though.
9319 	ScreenPainterImplementation* impl;
9320 }
9321 
9322 	// HACK: I need a pointer to the implementation so it's separate
9323 	struct ScreenPainterImplementation {
9324 		CapableOfBeingDrawnUpon window;
9325 		int referenceCount;
9326 		mixin NativeScreenPainterImplementation!();
9327 	}
9328 
9329 // FIXME: i haven't actually tested the sprite class on MS Windows
9330 
9331 /**
9332 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9333 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9334 
9335 
9336 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9337 	though I'm not sure that's ideal and the implementation might change.
9338 
9339 	You create one by giving a window and an image. It optimizes for that window,
9340 	and copies the image into it to use as the initial picture. Creating a sprite
9341 	can be quite slow (especially over a network connection) so you should do it
9342 	as little as possible and just hold on to your sprite handles after making them.
9343 	simpledisplay does try to do its best though, using the XSHM extension if available,
9344 	but you should still write your code as if it will always be slow.
9345 
9346 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9347 	a fast operation - much faster than drawing the Image itself every time.
9348 
9349 	`Sprite` represents a scarce resource which should be freed when you
9350 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9351 	after it has been disposed. If you are unsure about this, don't take chances,
9352 	just let the garbage collector do it for you. But ideally, you can manage its
9353 	lifetime more efficiently.
9354 
9355 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9356 	support alpha blending in its drawing at this time. That might change in the
9357 	future, but if you need alpha blending right now, use OpenGL instead. See
9358 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9359 
9360 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9361 	in by setting the enableAlpha = true in the constructor.
9362 */
9363 version(OSXCocoa) {} else // NotYetImplementedException
9364 class Sprite : CapableOfBeingDrawnUpon {
9365 
9366 	///
9367 	ScreenPainter draw() {
9368 		return ScreenPainter(this, handle, false);
9369 	}
9370 
9371 	/++
9372 		Copies the sprite's current state into a [TrueColorImage].
9373 
9374 		Be warned: this can be a very slow operation
9375 
9376 		History:
9377 			Actually implemented on March 14, 2021
9378 	+/
9379 	TrueColorImage takeScreenshot() {
9380 		return trueColorImageFromNativeHandle(handle, width, height);
9381 	}
9382 
9383 	void delegate() paintingFinishedDg() { return null; }
9384 	bool closed() { return false; }
9385 	ScreenPainterImplementation* activeScreenPainter_;
9386 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9387 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9388 
9389 	version(Windows)
9390 		private ubyte* rawData;
9391 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9392 	// ditto on the XPicture stuff
9393 
9394 	version(X11) {
9395 		private static XRenderPictFormat* RGB24;
9396 		private static XRenderPictFormat* ARGB32;
9397 
9398 		private Picture xrenderPicture;
9399 	}
9400 
9401 	version(X11)
9402 	private static void requireXRender() {
9403 		if(!XRenderLibrary.loadAttempted) {
9404 			XRenderLibrary.loadDynamicLibrary();
9405 		}
9406 
9407 		if(!XRenderLibrary.loadSuccessful)
9408 			throw new Exception("XRender library load failure");
9409 
9410 		auto display = XDisplayConnection.get;
9411 
9412 		// FIXME: if we migrate X displays, these need to be changed
9413 		if(RGB24 is null)
9414 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9415 		if(ARGB32 is null)
9416 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9417 	}
9418 
9419 	protected this() {}
9420 
9421 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9422 		this._width = width;
9423 		this._height = height;
9424 		this.enableAlpha = enableAlpha;
9425 
9426 		version(X11) {
9427 			auto display = XDisplayConnection.get();
9428 
9429 			if(enableAlpha) {
9430 				requireXRender();
9431 			}
9432 
9433 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9434 
9435 			if(enableAlpha) {
9436 				XRenderPictureAttributes attrs;
9437 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9438 			}
9439 		} else version(Windows) {
9440 			version(CRuntime_DigitalMars) {
9441 				//if(enableAlpha)
9442 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9443 			}
9444 
9445 			BITMAPINFO infoheader;
9446 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9447 			infoheader.bmiHeader.biWidth = width;
9448 			infoheader.bmiHeader.biHeight = height;
9449 			infoheader.bmiHeader.biPlanes = 1;
9450 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9451 			infoheader.bmiHeader.biCompression = BI_RGB;
9452 
9453 			// FIXME: this should prolly be a device dependent bitmap...
9454 			handle = CreateDIBSection(
9455 				null,
9456 				&infoheader,
9457 				DIB_RGB_COLORS,
9458 				cast(void**) &rawData,
9459 				null,
9460 				0);
9461 
9462 			if(handle is null)
9463 				throw new Exception("couldn't create pixmap");
9464 		}
9465 	}
9466 
9467 	/// Makes a sprite based on the image with the initial contents from the Image
9468 	this(SimpleWindow win, Image i) {
9469 		this(win, i.width, i.height, i.enableAlpha);
9470 
9471 		version(X11) {
9472 			auto display = XDisplayConnection.get();
9473 			auto gc = XCreateGC(display, this.handle, 0, null);
9474 			scope(exit) XFreeGC(display, gc);
9475 			if(i.usingXshm)
9476 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9477 			else
9478 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9479 		} else version(Windows) {
9480 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9481 			auto arrLength = itemsPerLine * height;
9482 			rawData[0..arrLength] = i.rawData[0..arrLength];
9483 		} else version(OSXCocoa) {
9484 			// FIXME: I have no idea if this is even any good
9485 			ubyte* rawData;
9486 
9487 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9488 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
9489 				colorSpace,
9490 				kCGImageAlphaPremultipliedLast
9491 				|kCGBitmapByteOrder32Big);
9492 			CGColorSpaceRelease(colorSpace);
9493 			rawData = CGBitmapContextGetData(context);
9494 
9495 			auto rdl = (width * height * 4);
9496 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9497 		} else static assert(0);
9498 	}
9499 
9500 	/++
9501 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9502 
9503 		Params:
9504 			where = point on the window where the upper left corner of the image will be drawn
9505 			imageUpperLeft = point on the image to start the slice to draw
9506 			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.
9507 		History:
9508 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9509 	+/
9510 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9511 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9512 	}
9513 
9514 	/// Call this when you're ready to get rid of it
9515 	void dispose() {
9516 		version(X11) {
9517 			staticDispose(xrenderPicture, handle);
9518 			xrenderPicture = None;
9519 			handle = None;
9520 		} else version(Windows) {
9521 			staticDispose(handle);
9522 			handle = null;
9523 		} else version(OSXCocoa) {
9524 			staticDispose(context);
9525 			context = null;
9526 		} else static assert(0);
9527 
9528 	}
9529 
9530 	version(X11)
9531 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9532 		if(xrenderPicture)
9533 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9534 		if(handle)
9535 			XFreePixmap(XDisplayConnection.get(), handle);
9536 	}
9537 	else version(Windows)
9538 	static void staticDispose(HBITMAP handle) {
9539 		if(handle)
9540 			DeleteObject(handle);
9541 	}
9542 	else version(OSXCocoa)
9543 	static void staticDispose(CGContextRef context) {
9544 		if(context)
9545 			CGContextRelease(context);
9546 	}
9547 
9548 	~this() {
9549 		version(X11) { if(xrenderPicture || handle)
9550 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9551 		} else version(Windows) { if(handle)
9552 			cleanupQueue.queue!staticDispose(handle);
9553 		} else version(OSXCocoa) { if(context)
9554 			cleanupQueue.queue!staticDispose(context);
9555 		} else static assert(0);
9556 	}
9557 
9558 	///
9559 	final @property int width() { return _width; }
9560 
9561 	///
9562 	final @property int height() { return _height; }
9563 
9564 	///
9565 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9566 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9567 	}
9568 
9569 	auto nativeHandle() {
9570 		return handle;
9571 	}
9572 
9573 	private:
9574 
9575 	int _width;
9576 	int _height;
9577 	bool enableAlpha;
9578 	version(X11)
9579 		Pixmap handle;
9580 	else version(Windows)
9581 		HBITMAP handle;
9582 	else version(OSXCocoa)
9583 		CGContextRef context;
9584 	else static assert(0);
9585 }
9586 
9587 /++
9588 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9589 
9590 	History:
9591 		Added November 20, 2021 (dub v10.4)
9592 +/
9593 abstract class Gradient : Sprite {
9594 	protected this(int w, int h) {
9595 		version(X11) {
9596 			Sprite.requireXRender();
9597 
9598 			super();
9599 			enableAlpha = true;
9600 			_width = w;
9601 			_height = h;
9602 		} else version(Windows) {
9603 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9604 		}
9605 	}
9606 
9607 	version(Windows)
9608 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9609 		auto ptr = rawData;
9610 		foreach(j; 0 .. _height)
9611 		foreach(i; 0 .. _width) {
9612 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9613 			*rawData = (color.a * color.b) / 255; rawData++;
9614 			*rawData = (color.a * color.g) / 255; rawData++;
9615 			*rawData = (color.a * color.r) / 255; rawData++;
9616 			*rawData = color.a; rawData++;
9617 		}
9618 	}
9619 
9620 	version(X11)
9621 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9622 		assert(stops.length > 0);
9623 		assert(stops.length <= 16, "I got lazy with buffers");
9624 
9625 		XFixed[16] stopsPositions = void;
9626 		XRenderColor[16] colors = void;
9627 
9628 		foreach(idx, stop; stops) {
9629 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9630 			auto c = stop.c;
9631 			colors[idx] = XRenderColor(
9632 				cast(ushort)(c.r * ushort.max / 255),
9633 				cast(ushort)(c.g * ushort.max / 255),
9634 				cast(ushort)(c.b * ushort.max / 255),
9635 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9636 			);
9637 		}
9638 
9639 		xrenderPicture = dg(stopsPositions, colors);
9640 	}
9641 
9642 	///
9643 	static struct Stop {
9644 		float percentage; /// between 0 and 1.0
9645 		Color c;
9646 	}
9647 }
9648 
9649 /++
9650 	Creates a linear gradient between p1 and p2.
9651 
9652 	X ONLY RIGHT NOW
9653 
9654 	History:
9655 		Added November 20, 2021 (dub v10.4)
9656 
9657 	Bugs:
9658 		Not yet implemented on Windows.
9659 +/
9660 class LinearGradient : Gradient {
9661 	/++
9662 
9663 	+/
9664 	this(Point p1, Point p2, Stop[] stops...) {
9665 		super(p2.x, p2.y);
9666 
9667 		version(X11) {
9668 			XLinearGradient gradient;
9669 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9670 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9671 
9672 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9673 				return XRenderCreateLinearGradient(
9674 					XDisplayConnection.get,
9675 					&gradient,
9676 					stopsPositions.ptr,
9677 					colors.ptr,
9678 					cast(int) stops.length);
9679 			});
9680 		} else version(Windows) {
9681 			// FIXME
9682 			forEachPixel((int x, int y) {
9683 				import core.stdc.math;
9684 
9685 				//sqrtf(
9686 
9687 				return Color.transparent;
9688 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9689 			});
9690 		}
9691 	}
9692 }
9693 
9694 /++
9695 	A conical gradient goes from color to color around a circumference from a center point.
9696 
9697 	X ONLY RIGHT NOW
9698 
9699 	History:
9700 		Added November 20, 2021 (dub v10.4)
9701 
9702 	Bugs:
9703 		Not yet implemented on Windows.
9704 +/
9705 class ConicalGradient : Gradient {
9706 	/++
9707 
9708 	+/
9709 	this(Point center, float angleInDegrees, Stop[] stops...) {
9710 		super(center.x * 2, center.y * 2);
9711 
9712 		version(X11) {
9713 			XConicalGradient gradient;
9714 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9715 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9716 
9717 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9718 				return XRenderCreateConicalGradient(
9719 					XDisplayConnection.get,
9720 					&gradient,
9721 					stopsPositions.ptr,
9722 					colors.ptr,
9723 					cast(int) stops.length);
9724 			});
9725 		} else version(Windows) {
9726 			// FIXME
9727 			forEachPixel((int x, int y) {
9728 				import core.stdc.math;
9729 
9730 				//sqrtf(
9731 
9732 				return Color.transparent;
9733 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9734 			});
9735 
9736 		}
9737 	}
9738 }
9739 
9740 /++
9741 	A radial gradient goes from color to color based on distance from the center.
9742 	It is like rings of color.
9743 
9744 	X ONLY RIGHT NOW
9745 
9746 
9747 	More specifically, you create two circles: an inner circle and an outer circle.
9748 	The gradient is only drawn in the area outside the inner circle but inside the outer
9749 	circle. The closest line between those two circles forms the line for the gradient
9750 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9751 
9752 	History:
9753 		Added November 20, 2021 (dub v10.4)
9754 
9755 	Bugs:
9756 		Not yet implemented on Windows.
9757 +/
9758 class RadialGradient : Gradient {
9759 	/++
9760 
9761 	+/
9762 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
9763 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
9764 
9765 		version(X11) {
9766 			XRadialGradient gradient;
9767 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
9768 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
9769 
9770 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9771 				return XRenderCreateRadialGradient(
9772 					XDisplayConnection.get,
9773 					&gradient,
9774 					stopsPositions.ptr,
9775 					colors.ptr,
9776 					cast(int) stops.length);
9777 			});
9778 		} else version(Windows) {
9779 			// FIXME
9780 			forEachPixel((int x, int y) {
9781 				import core.stdc.math;
9782 
9783 				//sqrtf(
9784 
9785 				return Color.transparent;
9786 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9787 			});
9788 		}
9789 	}
9790 }
9791 
9792 
9793 
9794 /+
9795 	NOT IMPLEMENTED
9796 
9797 	A display-stored image optimized for relatively quick drawing, like
9798 	[Sprite], but this one supports alpha channel blending and does NOT
9799 	support direct drawing upon it with a [ScreenPainter].
9800 
9801 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
9802 	plain [ScreenPainter]... sort of.
9803 
9804 	On X11, it requires the Xrender extension and library. This is available
9805 	almost everywhere though.
9806 
9807 	History:
9808 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
9809 +/
9810 version(none)
9811 class AlphaSprite {
9812 	/++
9813 		Copies the given image into it.
9814 	+/
9815 	this(MemoryImage img) {
9816 
9817 		if(!XRenderLibrary.loadAttempted) {
9818 			XRenderLibrary.loadDynamicLibrary();
9819 
9820 			// FIXME: this needs to be reconstructed when the X server changes
9821 			repopulateX();
9822 		}
9823 		if(!XRenderLibrary.loadSuccessful)
9824 			throw new Exception("XRender library load failure");
9825 
9826 		// I probably need to put the alpha mask in a separate Picture
9827 		// ugh
9828 		// maybe the Sprite itself can have an alpha bitmask anyway
9829 
9830 
9831 		auto display = XDisplayConnection.get();
9832 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
9833 
9834 
9835 		XRenderPictureAttributes attrs;
9836 
9837 		handle = XRenderCreatePicture(
9838 			XDisplayConnection.get,
9839 			pixmap,
9840 			RGBA,
9841 			0,
9842 			&attrs
9843 		);
9844 
9845 	}
9846 
9847 	// maybe i'll use the create gradient functions too with static factories..
9848 
9849 	void drawAt(ScreenPainter painter, Point where) {
9850 		//painter.drawPixmap(this, where);
9851 
9852 		XRenderPictureAttributes attrs;
9853 
9854 		auto pic = XRenderCreatePicture(
9855 			XDisplayConnection.get,
9856 			painter.impl.d,
9857 			RGB,
9858 			0,
9859 			&attrs
9860 		);
9861 
9862 		XRenderComposite(
9863 			XDisplayConnection.get,
9864 			3, // PictOpOver
9865 			handle,
9866 			None,
9867 			pic,
9868 			0, // src
9869 			0,
9870 			0, // mask
9871 			0,
9872 			10, // dest
9873 			10,
9874 			100, // width
9875 			100
9876 		);
9877 
9878 		/+
9879 		XRenderFreePicture(
9880 			XDisplayConnection.get,
9881 			pic
9882 		);
9883 
9884 		XRenderFreePicture(
9885 			XDisplayConnection.get,
9886 			fill
9887 		);
9888 		+/
9889 		// on Windows you can stretch but Xrender still can't :(
9890 	}
9891 
9892 	static XRenderPictFormat* RGB;
9893 	static XRenderPictFormat* RGBA;
9894 	static void repopulateX() {
9895 		auto display = XDisplayConnection.get;
9896 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
9897 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
9898 	}
9899 
9900 	XPixmap pixmap;
9901 	Picture handle;
9902 }
9903 
9904 ///
9905 interface CapableOfBeingDrawnUpon {
9906 	///
9907 	ScreenPainter draw();
9908 	///
9909 	int width();
9910 	///
9911 	int height();
9912 	protected ScreenPainterImplementation* activeScreenPainter();
9913 	protected void activeScreenPainter(ScreenPainterImplementation*);
9914 	bool closed();
9915 
9916 	void delegate() paintingFinishedDg();
9917 
9918 	/// Be warned: this can be a very slow operation
9919 	TrueColorImage takeScreenshot();
9920 }
9921 
9922 /// 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].
9923 void flushGui() {
9924 	version(X11) {
9925 		auto dpy = XDisplayConnection.get();
9926 		XLockDisplay(dpy);
9927 		scope(exit) XUnlockDisplay(dpy);
9928 		XFlush(dpy);
9929 	}
9930 }
9931 
9932 /++
9933 	Runs the given code in the GUI thread when its event loop
9934 	is available, blocking until it completes. This allows you
9935 	to create and manipulate windows from another thread without
9936 	invoking undefined behavior.
9937 
9938 	If this is the gui thread, it runs the code immediately.
9939 
9940 	If no gui thread exists yet, the current thread is assumed
9941 	to be it. Attempting to create windows or run the event loop
9942 	in any other thread will cause an assertion failure.
9943 
9944 
9945 	$(TIP
9946 		Did you know you can use UFCS on delegate literals?
9947 
9948 		() {
9949 			// code here
9950 		}.runInGuiThread;
9951 	)
9952 
9953 	Returns:
9954 		`true` if the function was called, `false` if it was not.
9955 		The function may not be called because the gui thread had
9956 		already terminated by the time you called this.
9957 
9958 	History:
9959 		Added April 10, 2020 (v7.2.0)
9960 
9961 		Return value added and implementation tweaked to avoid locking
9962 		at program termination on February 24, 2021 (v9.2.1).
9963 +/
9964 bool runInGuiThread(scope void delegate() dg) @trusted {
9965 	claimGuiThread();
9966 
9967 	if(thisIsGuiThread) {
9968 		dg();
9969 		return true;
9970 	}
9971 
9972 	if(guiThreadTerminating)
9973 		return false;
9974 
9975 	import core.sync.semaphore;
9976 	static Semaphore sc;
9977 	if(sc is null)
9978 		sc = new Semaphore();
9979 
9980 	static RunQueueMember* rqm;
9981 	if(rqm is null)
9982 		rqm = new RunQueueMember;
9983 	rqm.dg = cast(typeof(rqm.dg)) dg;
9984 	rqm.signal = sc;
9985 	rqm.thrown = null;
9986 
9987 	synchronized(runInGuiThreadLock) {
9988 		runInGuiThreadQueue ~= rqm;
9989 	}
9990 
9991 	if(!SimpleWindow.eventWakeUp())
9992 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
9993 
9994 	rqm.signal.wait();
9995 	auto t = rqm.thrown;
9996 
9997 	if(t)
9998 		throw t;
9999 
10000 	return true;
10001 }
10002 
10003 // note it runs sync if this is the gui thread....
10004 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10005 	claimGuiThread();
10006 
10007 	try {
10008 
10009 		if(thisIsGuiThread) {
10010 			dg();
10011 			return;
10012 		}
10013 
10014 		if(guiThreadTerminating)
10015 			return;
10016 
10017 		RunQueueMember* rqm = new RunQueueMember;
10018 		rqm.dg = cast(typeof(rqm.dg)) dg;
10019 		rqm.signal = null;
10020 		rqm.thrown = null;
10021 
10022 		synchronized(runInGuiThreadLock) {
10023 			runInGuiThreadQueue ~= rqm;
10024 		}
10025 
10026 		if(!SimpleWindow.eventWakeUp())
10027 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10028 	} catch(Exception e) {
10029 		if(handleError)
10030 			handleError(e);
10031 	}
10032 }
10033 
10034 private void runPendingRunInGuiThreadDelegates() {
10035 	more:
10036 	RunQueueMember* next;
10037 	synchronized(runInGuiThreadLock) {
10038 		if(runInGuiThreadQueue.length) {
10039 			next = runInGuiThreadQueue[0];
10040 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10041 		} else {
10042 			next = null;
10043 		}
10044 	}
10045 
10046 	if(next) {
10047 		try {
10048 			next.dg();
10049 			next.thrown = null;
10050 		} catch(Throwable t) {
10051 			next.thrown = t;
10052 		}
10053 
10054 		if(next.signal)
10055 			next.signal.notify();
10056 
10057 		goto more;
10058 	}
10059 }
10060 
10061 private void claimGuiThread() nothrow {
10062 	import core.atomic;
10063 	if(cas(&guiThreadExists_, false, true))
10064 		thisIsGuiThread = true;
10065 }
10066 
10067 private struct RunQueueMember {
10068 	void delegate() dg;
10069 	import core.sync.semaphore;
10070 	Semaphore signal;
10071 	Throwable thrown;
10072 }
10073 
10074 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10075 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10076 private bool thisIsGuiThread = false;
10077 private shared bool guiThreadExists_ = false;
10078 private shared bool guiThreadTerminating = false;
10079 
10080 /++
10081 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10082 	event loop. All windows must be exclusively created and managed by a single thread.
10083 
10084 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10085 	when you call one of its constructors.
10086 
10087 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10088 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10089 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10090 
10091 	The reason this function is available is in case you want to message pass between a gui
10092 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10093 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10094 
10095 	History:
10096 		Added December 3, 2021 (dub v10.5)
10097 +/
10098 public bool guiThreadExists() {
10099 	return guiThreadExists_;
10100 }
10101 
10102 /++
10103 	Returns `true` if this thread is either running or set to be running the
10104 	simpledisplay.d gui core event loop because it owns windows.
10105 
10106 	It is important to keep gui-related functionality in the right thread, so you will
10107 	want to `runInGuiThread` when you call them (with some specific exceptions called
10108 	out in those specific functions' documentation). Notably, all windows must be
10109 	created and managed only from the gui thread.
10110 
10111 	Will return false if simpledisplay's other functions haven't been called
10112 	yet; check [guiThreadExists] in addition to this.
10113 
10114 	History:
10115 		Added December 3, 2021 (dub v10.5)
10116 +/
10117 public bool thisThreadRunningGui() {
10118 	return thisIsGuiThread;
10119 }
10120 
10121 /++
10122 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10123 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10124 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10125 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10126 	file instead if you are in one of those situations).
10127 
10128 	It does not support outputting very many types; just strings and ints are likely to actually work.
10129 
10130 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10131 	is unspecified meaning I can change it at any time. The only point of this function is to help
10132 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10133 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10134 	in those contexts.
10135 
10136 	$(WARNING
10137 		I reserve the right to change this function at any time. You can use it if it helps you
10138 		but do not rely on it for anything permanent.
10139 	)
10140 
10141 	History:
10142 		Added December 3, 2021. Not formally supported under any stable tag.
10143 +/
10144 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10145 	try {
10146 		version(Windows) {
10147 			import core.sys.windows.wincon;
10148 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10149 				AllocConsole();
10150 			const(char)* fn = "CONOUT$";
10151 		} else version(Posix) {
10152 			const(char)* fn = "/dev/tty";
10153 		} else static assert(0, "Function not implemented for your system");
10154 
10155 		if(fileOverride.length)
10156 			fn = fileOverride.ptr;
10157 
10158 		import core.stdc.stdio;
10159 		auto fp = fopen(fn, "wt");
10160 		if(fp is null) return;
10161 		scope(exit) fclose(fp);
10162 
10163 		string str;
10164 		foreach(item; t) {
10165 			static if(is(typeof(item) : const(char)[]))
10166 				str ~= item;
10167 			else
10168 				str ~= toInternal!string(item);
10169 			str ~= " ";
10170 		}
10171 		str ~= "\n";
10172 
10173 		fwrite(str.ptr, 1, str.length, fp);
10174 		fflush(fp);
10175 	} catch(Exception e) {
10176 		// sorry no hope
10177 	}
10178 }
10179 
10180 private void guiThreadFinalize() {
10181 	assert(thisIsGuiThread);
10182 
10183 	guiThreadTerminating = true; // don't add any more from this point on
10184 	runPendingRunInGuiThreadDelegates();
10185 }
10186 
10187 /+
10188 interface IPromise {
10189 	void reportProgress(int current, int max, string message);
10190 
10191 	/+ // not formally in cuz of templates but still
10192 	IPromise Then();
10193 	IPromise Catch();
10194 	IPromise Finally();
10195 	+/
10196 }
10197 
10198 /+
10199 	auto promise = async({ ... });
10200 	promise.Then(whatever).
10201 		Then(whateverelse).
10202 		Catch((exception) { });
10203 
10204 
10205 	A promise is run inside a fiber and it looks something like:
10206 
10207 	try {
10208 		auto res = whatever();
10209 		auto res2 = whateverelse(res);
10210 	} catch(Exception e) {
10211 		{ }(e);
10212 	}
10213 
10214 	When a thing succeeds, it is passed as an arg to the next
10215 +/
10216 class Promise(T) : IPromise {
10217 	auto Then() { return null; }
10218 	auto Catch() { return null; }
10219 	auto Finally() { return null; }
10220 
10221 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10222 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10223 	T await();
10224 }
10225 
10226 interface Task {
10227 }
10228 
10229 interface Resolvable(T) : Task {
10230 	void run();
10231 
10232 	void resolve(T);
10233 
10234 	Resolvable!T then(void delegate(T)); // returns a new promise
10235 	Resolvable!T error(Throwable); // js catch
10236 	Resolvable!T completed(); // js finally
10237 
10238 }
10239 
10240 /++
10241 	Runs `work` in a helper thread and sends its return value back to the main gui
10242 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10243 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10244 	kill the program.
10245 
10246 	You can call reportProgress(position, max, message) to update your parent window
10247 	on your progress.
10248 
10249 	I should also use `shared` methods. FIXME
10250 
10251 	History:
10252 		Added March 6, 2021 (dub version 9.3).
10253 +/
10254 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10255 	uponCompletion(work(null));
10256 }
10257 
10258 +/
10259 
10260 /// Used internal to dispatch events to various classes.
10261 interface CapableOfHandlingNativeEvent {
10262 	NativeEventHandler getNativeEventHandler();
10263 
10264 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10265 
10266 	version(X11) {
10267 		// if this is impossible, you are allowed to just throw from it
10268 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10269 		void recreateAfterDisconnect();
10270 		// discard any *connection specific* state, but keep enough that you
10271 		// can be recreated if possible. discardConnectionState() is always called immediately
10272 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10273 		// you need initialization order
10274 		void discardConnectionState();
10275 	}
10276 }
10277 
10278 version(X11)
10279 /++
10280 	State of keys on mouse events, especially motion.
10281 
10282 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10283 +/
10284 enum ModifierState : uint {
10285 	shift = 1, ///
10286 	capsLock = 2, ///
10287 	ctrl = 4, ///
10288 	alt = 8, /// Not always available on Windows
10289 	windows = 64, /// ditto
10290 	numLock = 16, ///
10291 
10292 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10293 	middleButtonDown = 512, /// ditto
10294 	rightButtonDown = 1024, /// ditto
10295 }
10296 else version(Windows)
10297 /// ditto
10298 enum ModifierState : uint {
10299 	shift = 4, ///
10300 	ctrl = 8, ///
10301 
10302 	// i'm not sure if the next two are available
10303 	alt = 256, /// not always available on Windows
10304 	windows = 512, /// ditto
10305 
10306 	capsLock = 1024, ///
10307 	numLock = 2048, ///
10308 
10309 	leftButtonDown = 1, /// not available on key events
10310 	middleButtonDown = 16, /// ditto
10311 	rightButtonDown = 2, /// ditto
10312 
10313 	backButtonDown = 0x20, /// not available on X
10314 	forwardButtonDown = 0x40, /// ditto
10315 }
10316 else version(OSXCocoa)
10317 // FIXME FIXME NotYetImplementedException
10318 enum ModifierState : uint {
10319 	shift = 1, ///
10320 	capsLock = 2, ///
10321 	ctrl = 4, ///
10322 	alt = 8, /// Not always available on Windows
10323 	windows = 64, /// ditto
10324 	numLock = 16, ///
10325 
10326 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10327 	middleButtonDown = 512, /// ditto
10328 	rightButtonDown = 1024, /// ditto
10329 }
10330 
10331 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10332 enum MouseButton : int {
10333 	none = 0,
10334 	left = 1, ///
10335 	right = 2, ///
10336 	middle = 4, ///
10337 	wheelUp = 8, ///
10338 	wheelDown = 16, ///
10339 	backButton = 32, /// often found on the thumb and used for back in browsers
10340 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10341 }
10342 
10343 version(X11) {
10344 	// FIXME: match ASCII whenever we can. Most of it is already there,
10345 	// but there's a few exceptions and mismatches with Windows
10346 
10347 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10348 	enum Key {
10349 		Escape = 0xff1b, ///
10350 		F1 = 0xffbe, ///
10351 		F2 = 0xffbf, ///
10352 		F3 = 0xffc0, ///
10353 		F4 = 0xffc1, ///
10354 		F5 = 0xffc2, ///
10355 		F6 = 0xffc3, ///
10356 		F7 = 0xffc4, ///
10357 		F8 = 0xffc5, ///
10358 		F9 = 0xffc6, ///
10359 		F10 = 0xffc7, ///
10360 		F11 = 0xffc8, ///
10361 		F12 = 0xffc9, ///
10362 		PrintScreen = 0xff61, ///
10363 		ScrollLock = 0xff14, ///
10364 		Pause = 0xff13, ///
10365 		Grave = 0x60, /// The $(BACKTICK) ~ key
10366 		// number keys across the top of the keyboard
10367 		N1 = 0x31, /// Number key atop the keyboard
10368 		N2 = 0x32, ///
10369 		N3 = 0x33, ///
10370 		N4 = 0x34, ///
10371 		N5 = 0x35, ///
10372 		N6 = 0x36, ///
10373 		N7 = 0x37, ///
10374 		N8 = 0x38, ///
10375 		N9 = 0x39, ///
10376 		N0 = 0x30, ///
10377 		Dash = 0x2d, ///
10378 		Equals = 0x3d, ///
10379 		Backslash = 0x5c, /// The \ | key
10380 		Backspace = 0xff08, ///
10381 		Insert = 0xff63, ///
10382 		Home = 0xff50, ///
10383 		PageUp = 0xff55, ///
10384 		Delete = 0xffff, ///
10385 		End = 0xff57, ///
10386 		PageDown = 0xff56, ///
10387 		Up = 0xff52, ///
10388 		Down = 0xff54, ///
10389 		Left = 0xff51, ///
10390 		Right = 0xff53, ///
10391 
10392 		Tab = 0xff09, ///
10393 		Q = 0x71, ///
10394 		W = 0x77, ///
10395 		E = 0x65, ///
10396 		R = 0x72, ///
10397 		T = 0x74, ///
10398 		Y = 0x79, ///
10399 		U = 0x75, ///
10400 		I = 0x69, ///
10401 		O = 0x6f, ///
10402 		P = 0x70, ///
10403 		LeftBracket = 0x5b, /// the [ { key
10404 		RightBracket = 0x5d, /// the ] } key
10405 		CapsLock = 0xffe5, ///
10406 		A = 0x61, ///
10407 		S = 0x73, ///
10408 		D = 0x64, ///
10409 		F = 0x66, ///
10410 		G = 0x67, ///
10411 		H = 0x68, ///
10412 		J = 0x6a, ///
10413 		K = 0x6b, ///
10414 		L = 0x6c, ///
10415 		Semicolon = 0x3b, ///
10416 		Apostrophe = 0x27, ///
10417 		Enter = 0xff0d, ///
10418 		Shift = 0xffe1, ///
10419 		Z = 0x7a, ///
10420 		X = 0x78, ///
10421 		C = 0x63, ///
10422 		V = 0x76, ///
10423 		B = 0x62, ///
10424 		N = 0x6e, ///
10425 		M = 0x6d, ///
10426 		Comma = 0x2c, ///
10427 		Period = 0x2e, ///
10428 		Slash = 0x2f, /// the / ? key
10429 		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
10430 		Ctrl = 0xffe3, ///
10431 		Windows = 0xffeb, ///
10432 		Alt = 0xffe9, ///
10433 		Space = 0x20, ///
10434 		Alt_r = 0xffea, /// ditto of shift_r
10435 		Windows_r = 0xffec, ///
10436 		Menu = 0xff67, ///
10437 		Ctrl_r = 0xffe4, ///
10438 
10439 		NumLock = 0xff7f, ///
10440 		Divide = 0xffaf, /// The / key on the number pad
10441 		Multiply = 0xffaa, /// The * key on the number pad
10442 		Minus = 0xffad, /// The - key on the number pad
10443 		Plus = 0xffab, /// The + key on the number pad
10444 		PadEnter = 0xff8d, /// Numberpad enter key
10445 		Pad1 = 0xff9c, /// Numberpad keys
10446 		Pad2 = 0xff99, ///
10447 		Pad3 = 0xff9b, ///
10448 		Pad4 = 0xff96, ///
10449 		Pad5 = 0xff9d, ///
10450 		Pad6 = 0xff98, ///
10451 		Pad7 = 0xff95, ///
10452 		Pad8 = 0xff97, ///
10453 		Pad9 = 0xff9a, ///
10454 		Pad0 = 0xff9e, ///
10455 		PadDot = 0xff9f, ///
10456 	}
10457 } else version(Windows) {
10458 	// the character here is for en-us layouts and for illustration only
10459 	// if you actually want to get characters, wait for character events
10460 	// (the argument to your event handler is simply a dchar)
10461 	// those will be converted by the OS for the right locale.
10462 
10463 	enum Key {
10464 		Escape = 0x1b,
10465 		F1 = 0x70,
10466 		F2 = 0x71,
10467 		F3 = 0x72,
10468 		F4 = 0x73,
10469 		F5 = 0x74,
10470 		F6 = 0x75,
10471 		F7 = 0x76,
10472 		F8 = 0x77,
10473 		F9 = 0x78,
10474 		F10 = 0x79,
10475 		F11 = 0x7a,
10476 		F12 = 0x7b,
10477 		PrintScreen = 0x2c,
10478 		ScrollLock = 0x91,
10479 		Pause = 0x13,
10480 		Grave = 0xc0,
10481 		// number keys across the top of the keyboard
10482 		N1 = 0x31,
10483 		N2 = 0x32,
10484 		N3 = 0x33,
10485 		N4 = 0x34,
10486 		N5 = 0x35,
10487 		N6 = 0x36,
10488 		N7 = 0x37,
10489 		N8 = 0x38,
10490 		N9 = 0x39,
10491 		N0 = 0x30,
10492 		Dash = 0xbd,
10493 		Equals = 0xbb,
10494 		Backslash = 0xdc,
10495 		Backspace = 0x08,
10496 		Insert = 0x2d,
10497 		Home = 0x24,
10498 		PageUp = 0x21,
10499 		Delete = 0x2e,
10500 		End = 0x23,
10501 		PageDown = 0x22,
10502 		Up = 0x26,
10503 		Down = 0x28,
10504 		Left = 0x25,
10505 		Right = 0x27,
10506 
10507 		Tab = 0x09,
10508 		Q = 0x51,
10509 		W = 0x57,
10510 		E = 0x45,
10511 		R = 0x52,
10512 		T = 0x54,
10513 		Y = 0x59,
10514 		U = 0x55,
10515 		I = 0x49,
10516 		O = 0x4f,
10517 		P = 0x50,
10518 		LeftBracket = 0xdb,
10519 		RightBracket = 0xdd,
10520 		CapsLock = 0x14,
10521 		A = 0x41,
10522 		S = 0x53,
10523 		D = 0x44,
10524 		F = 0x46,
10525 		G = 0x47,
10526 		H = 0x48,
10527 		J = 0x4a,
10528 		K = 0x4b,
10529 		L = 0x4c,
10530 		Semicolon = 0xba,
10531 		Apostrophe = 0xde,
10532 		Enter = 0x0d,
10533 		Shift = 0x10,
10534 		Z = 0x5a,
10535 		X = 0x58,
10536 		C = 0x43,
10537 		V = 0x56,
10538 		B = 0x42,
10539 		N = 0x4e,
10540 		M = 0x4d,
10541 		Comma = 0xbc,
10542 		Period = 0xbe,
10543 		Slash = 0xbf,
10544 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10545 		Ctrl = 0x11,
10546 		Windows = 0x5b,
10547 		Alt = -5, // FIXME
10548 		Space = 0x20,
10549 		Alt_r = 0xffea, // ditto of shift_r
10550 		Windows_r = 0x5c, // ditto of shift_r
10551 		Menu = 0x5d,
10552 		Ctrl_r = 0xa3, // ditto of shift_r
10553 
10554 		NumLock = 0x90,
10555 		Divide = 0x6f,
10556 		Multiply = 0x6a,
10557 		Minus = 0x6d,
10558 		Plus = 0x6b,
10559 		PadEnter = -8, // FIXME
10560 		Pad1 = 0x61,
10561 		Pad2 = 0x62,
10562 		Pad3 = 0x63,
10563 		Pad4 = 0x64,
10564 		Pad5 = 0x65,
10565 		Pad6 = 0x66,
10566 		Pad7 = 0x67,
10567 		Pad8 = 0x68,
10568 		Pad9 = 0x69,
10569 		Pad0 = 0x60,
10570 		PadDot = 0x6e,
10571 	}
10572 
10573 	// I'm keeping this around for reference purposes
10574 	// ideally all these buttons will be listed for all platforms,
10575 	// but now now I'm just focusing on my US keyboard
10576 	version(none)
10577 	enum Key {
10578 		LBUTTON = 0x01,
10579 		RBUTTON = 0x02,
10580 		CANCEL = 0x03,
10581 		MBUTTON = 0x04,
10582 		//static if (_WIN32_WINNT > =  0x500) {
10583 		XBUTTON1 = 0x05,
10584 		XBUTTON2 = 0x06,
10585 		//}
10586 		BACK = 0x08,
10587 		TAB = 0x09,
10588 		CLEAR = 0x0C,
10589 		RETURN = 0x0D,
10590 		SHIFT = 0x10,
10591 		CONTROL = 0x11,
10592 		MENU = 0x12,
10593 		PAUSE = 0x13,
10594 		CAPITAL = 0x14,
10595 		KANA = 0x15,
10596 		HANGEUL = 0x15,
10597 		HANGUL = 0x15,
10598 		JUNJA = 0x17,
10599 		FINAL = 0x18,
10600 		HANJA = 0x19,
10601 		KANJI = 0x19,
10602 		ESCAPE = 0x1B,
10603 		CONVERT = 0x1C,
10604 		NONCONVERT = 0x1D,
10605 		ACCEPT = 0x1E,
10606 		MODECHANGE = 0x1F,
10607 		SPACE = 0x20,
10608 		PRIOR = 0x21,
10609 		NEXT = 0x22,
10610 		END = 0x23,
10611 		HOME = 0x24,
10612 		LEFT = 0x25,
10613 		UP = 0x26,
10614 		RIGHT = 0x27,
10615 		DOWN = 0x28,
10616 		SELECT = 0x29,
10617 		PRINT = 0x2A,
10618 		EXECUTE = 0x2B,
10619 		SNAPSHOT = 0x2C,
10620 		INSERT = 0x2D,
10621 		DELETE = 0x2E,
10622 		HELP = 0x2F,
10623 		LWIN = 0x5B,
10624 		RWIN = 0x5C,
10625 		APPS = 0x5D,
10626 		SLEEP = 0x5F,
10627 		NUMPAD0 = 0x60,
10628 		NUMPAD1 = 0x61,
10629 		NUMPAD2 = 0x62,
10630 		NUMPAD3 = 0x63,
10631 		NUMPAD4 = 0x64,
10632 		NUMPAD5 = 0x65,
10633 		NUMPAD6 = 0x66,
10634 		NUMPAD7 = 0x67,
10635 		NUMPAD8 = 0x68,
10636 		NUMPAD9 = 0x69,
10637 		MULTIPLY = 0x6A,
10638 		ADD = 0x6B,
10639 		SEPARATOR = 0x6C,
10640 		SUBTRACT = 0x6D,
10641 		DECIMAL = 0x6E,
10642 		DIVIDE = 0x6F,
10643 		F1 = 0x70,
10644 		F2 = 0x71,
10645 		F3 = 0x72,
10646 		F4 = 0x73,
10647 		F5 = 0x74,
10648 		F6 = 0x75,
10649 		F7 = 0x76,
10650 		F8 = 0x77,
10651 		F9 = 0x78,
10652 		F10 = 0x79,
10653 		F11 = 0x7A,
10654 		F12 = 0x7B,
10655 		F13 = 0x7C,
10656 		F14 = 0x7D,
10657 		F15 = 0x7E,
10658 		F16 = 0x7F,
10659 		F17 = 0x80,
10660 		F18 = 0x81,
10661 		F19 = 0x82,
10662 		F20 = 0x83,
10663 		F21 = 0x84,
10664 		F22 = 0x85,
10665 		F23 = 0x86,
10666 		F24 = 0x87,
10667 		NUMLOCK = 0x90,
10668 		SCROLL = 0x91,
10669 		LSHIFT = 0xA0,
10670 		RSHIFT = 0xA1,
10671 		LCONTROL = 0xA2,
10672 		RCONTROL = 0xA3,
10673 		LMENU = 0xA4,
10674 		RMENU = 0xA5,
10675 		//static if (_WIN32_WINNT > =  0x500) {
10676 		BROWSER_BACK = 0xA6,
10677 		BROWSER_FORWARD = 0xA7,
10678 		BROWSER_REFRESH = 0xA8,
10679 		BROWSER_STOP = 0xA9,
10680 		BROWSER_SEARCH = 0xAA,
10681 		BROWSER_FAVORITES = 0xAB,
10682 		BROWSER_HOME = 0xAC,
10683 		VOLUME_MUTE = 0xAD,
10684 		VOLUME_DOWN = 0xAE,
10685 		VOLUME_UP = 0xAF,
10686 		MEDIA_NEXT_TRACK = 0xB0,
10687 		MEDIA_PREV_TRACK = 0xB1,
10688 		MEDIA_STOP = 0xB2,
10689 		MEDIA_PLAY_PAUSE = 0xB3,
10690 		LAUNCH_MAIL = 0xB4,
10691 		LAUNCH_MEDIA_SELECT = 0xB5,
10692 		LAUNCH_APP1 = 0xB6,
10693 		LAUNCH_APP2 = 0xB7,
10694 		//}
10695 		OEM_1 = 0xBA,
10696 		//static if (_WIN32_WINNT > =  0x500) {
10697 		OEM_PLUS = 0xBB,
10698 		OEM_COMMA = 0xBC,
10699 		OEM_MINUS = 0xBD,
10700 		OEM_PERIOD = 0xBE,
10701 		//}
10702 		OEM_2 = 0xBF,
10703 		OEM_3 = 0xC0,
10704 		OEM_4 = 0xDB,
10705 		OEM_5 = 0xDC,
10706 		OEM_6 = 0xDD,
10707 		OEM_7 = 0xDE,
10708 		OEM_8 = 0xDF,
10709 		//static if (_WIN32_WINNT > =  0x500) {
10710 		OEM_102 = 0xE2,
10711 		//}
10712 		PROCESSKEY = 0xE5,
10713 		//static if (_WIN32_WINNT > =  0x500) {
10714 		PACKET = 0xE7,
10715 		//}
10716 		ATTN = 0xF6,
10717 		CRSEL = 0xF7,
10718 		EXSEL = 0xF8,
10719 		EREOF = 0xF9,
10720 		PLAY = 0xFA,
10721 		ZOOM = 0xFB,
10722 		NONAME = 0xFC,
10723 		PA1 = 0xFD,
10724 		OEM_CLEAR = 0xFE,
10725 	}
10726 
10727 } else version(OSXCocoa) {
10728 	// FIXME
10729 	enum Key {
10730 		Escape = 0x1b,
10731 		F1 = 0x70,
10732 		F2 = 0x71,
10733 		F3 = 0x72,
10734 		F4 = 0x73,
10735 		F5 = 0x74,
10736 		F6 = 0x75,
10737 		F7 = 0x76,
10738 		F8 = 0x77,
10739 		F9 = 0x78,
10740 		F10 = 0x79,
10741 		F11 = 0x7a,
10742 		F12 = 0x7b,
10743 		PrintScreen = 0x2c,
10744 		ScrollLock = -2, // FIXME
10745 		Pause = -3, // FIXME
10746 		Grave = 0xc0,
10747 		// number keys across the top of the keyboard
10748 		N1 = 0x31,
10749 		N2 = 0x32,
10750 		N3 = 0x33,
10751 		N4 = 0x34,
10752 		N5 = 0x35,
10753 		N6 = 0x36,
10754 		N7 = 0x37,
10755 		N8 = 0x38,
10756 		N9 = 0x39,
10757 		N0 = 0x30,
10758 		Dash = 0xbd,
10759 		Equals = 0xbb,
10760 		Backslash = 0xdc,
10761 		Backspace = 0x08,
10762 		Insert = 0x2d,
10763 		Home = 0x24,
10764 		PageUp = 0x21,
10765 		Delete = 0x2e,
10766 		End = 0x23,
10767 		PageDown = 0x22,
10768 		Up = 0x26,
10769 		Down = 0x28,
10770 		Left = 0x25,
10771 		Right = 0x27,
10772 
10773 		Tab = 0x09,
10774 		Q = 0x51,
10775 		W = 0x57,
10776 		E = 0x45,
10777 		R = 0x52,
10778 		T = 0x54,
10779 		Y = 0x59,
10780 		U = 0x55,
10781 		I = 0x49,
10782 		O = 0x4f,
10783 		P = 0x50,
10784 		LeftBracket = 0xdb,
10785 		RightBracket = 0xdd,
10786 		CapsLock = 0x14,
10787 		A = 0x41,
10788 		S = 0x53,
10789 		D = 0x44,
10790 		F = 0x46,
10791 		G = 0x47,
10792 		H = 0x48,
10793 		J = 0x4a,
10794 		K = 0x4b,
10795 		L = 0x4c,
10796 		Semicolon = 0xba,
10797 		Apostrophe = 0xde,
10798 		Enter = 0x0d,
10799 		Shift = 0x10,
10800 		Z = 0x5a,
10801 		X = 0x58,
10802 		C = 0x43,
10803 		V = 0x56,
10804 		B = 0x42,
10805 		N = 0x4e,
10806 		M = 0x4d,
10807 		Comma = 0xbc,
10808 		Period = 0xbe,
10809 		Slash = 0xbf,
10810 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10811 		Ctrl = 0x11,
10812 		Windows = 0x5b,
10813 		Alt = -5, // FIXME
10814 		Space = 0x20,
10815 		Alt_r = 0xffea, // ditto of shift_r
10816 		Windows_r = -6, // FIXME
10817 		Menu = 0x5d,
10818 		Ctrl_r = -7, // FIXME
10819 
10820 		NumLock = 0x90,
10821 		Divide = 0x6f,
10822 		Multiply = 0x6a,
10823 		Minus = 0x6d,
10824 		Plus = 0x6b,
10825 		PadEnter = -8, // FIXME
10826 		// FIXME for the rest of these:
10827 		Pad1 = 0xff9c,
10828 		Pad2 = 0xff99,
10829 		Pad3 = 0xff9b,
10830 		Pad4 = 0xff96,
10831 		Pad5 = 0xff9d,
10832 		Pad6 = 0xff98,
10833 		Pad7 = 0xff95,
10834 		Pad8 = 0xff97,
10835 		Pad9 = 0xff9a,
10836 		Pad0 = 0xff9e,
10837 		PadDot = 0xff9f,
10838 	}
10839 
10840 }
10841 
10842 /* Additional utilities */
10843 
10844 
10845 Color fromHsl(real h, real s, real l) {
10846 	return arsd.color.fromHsl([h,s,l]);
10847 }
10848 
10849 
10850 
10851 /* ********** What follows is the system-specific implementations *********/
10852 version(Windows) {
10853 
10854 
10855 	// helpers for making HICONs from MemoryImages
10856 	class WindowsIcon {
10857 		struct Win32Icon(int colorCount) {
10858 		align(1):
10859 			uint biSize;
10860 			int biWidth;
10861 			int biHeight;
10862 			ushort biPlanes;
10863 			ushort biBitCount;
10864 			uint biCompression;
10865 			uint biSizeImage;
10866 			int biXPelsPerMeter;
10867 			int biYPelsPerMeter;
10868 			uint biClrUsed;
10869 			uint biClrImportant;
10870 			RGBQUAD[colorCount] biColors;
10871 			/* Pixels:
10872 			Uint8 pixels[]
10873 			*/
10874 			/* Mask:
10875 			Uint8 mask[]
10876 			*/
10877 
10878 			ubyte[4096] data;
10879 
10880 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
10881 				width = mi.width;
10882 				height = mi.height;
10883 
10884 				auto indexedImage = cast(IndexedImage) mi;
10885 				if(indexedImage is null)
10886 					indexedImage = quantize(mi.getAsTrueColorImage());
10887 
10888 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
10889 				assert(height %4 == 0);
10890 
10891 				int icon_plen = height*((width+3)&~3);
10892 				int icon_mlen = height*((((width+7)/8)+3)&~3);
10893 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
10894 
10895 				biSize = 40;
10896 				biWidth = width;
10897 				biHeight = height*2;
10898 				biPlanes = 1;
10899 				biBitCount = 8;
10900 				biSizeImage = icon_plen+icon_mlen;
10901 
10902 				int offset = 0;
10903 				int andOff = icon_plen * 8; // the and offset is in bits
10904 				for(int y = height - 1; y >= 0; y--) {
10905 					int off2 = y * width;
10906 					foreach(x; 0 .. width) {
10907 						const b = indexedImage.data[off2 + x];
10908 						data[offset] = b;
10909 						offset++;
10910 
10911 						const andBit = andOff % 8;
10912 						const andIdx = andOff / 8;
10913 						assert(b < indexedImage.palette.length);
10914 						// this is anded to the destination, since and 0 means erase,
10915 						// we want that to  be opaque, and 1 for transparent
10916 						auto transparent = (indexedImage.palette[b].a <= 127);
10917 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
10918 
10919 						andOff++;
10920 					}
10921 
10922 					andOff += andOff % 32;
10923 				}
10924 
10925 				foreach(idx, entry; indexedImage.palette) {
10926 					if(entry.a > 127) {
10927 						biColors[idx].rgbBlue = entry.b;
10928 						biColors[idx].rgbGreen = entry.g;
10929 						biColors[idx].rgbRed = entry.r;
10930 					} else {
10931 						biColors[idx].rgbBlue = 255;
10932 						biColors[idx].rgbGreen = 255;
10933 						biColors[idx].rgbRed = 255;
10934 					}
10935 				}
10936 
10937 				/*
10938 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
10939 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
10940 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
10941 				auto pngMap = fetchPaletteWin32(png);
10942 				biColors[0..pngMap.length] = pngMap[];
10943 				*/
10944 			}
10945 		}
10946 
10947 
10948 		Win32Icon!(256) icon_win32;
10949 
10950 
10951 		this(MemoryImage mi) {
10952 			int icon_len, width, height;
10953 
10954 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
10955 
10956 			/*
10957 			PNG* png = readPnpngData);
10958 			PNGHeader pngh = getHeader(png);
10959 			void* icon_win32;
10960 			if(pngh.depth == 4) {
10961 				auto i = new Win32Icon!(16);
10962 				i.fromPNG(png, pngh, icon_len, width, height);
10963 				icon_win32 = i;
10964 			}
10965 			else if(pngh.depth == 8) {
10966 				auto i = new Win32Icon!(256);
10967 				i.fromPNG(png, pngh, icon_len, width, height);
10968 				icon_win32 = i;
10969 			} else assert(0);
10970 			*/
10971 
10972 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
10973 
10974 			if(hIcon is null) throw new Exception("CreateIconFromResourceEx");
10975 		}
10976 
10977 		~this() {
10978 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
10979 			DestroyIcon(hIcon);
10980 		}
10981 
10982 		HICON hIcon;
10983 	}
10984 
10985 
10986 
10987 
10988 
10989 
10990 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
10991 	alias HWND NativeWindowHandle;
10992 
10993 	extern(Windows)
10994 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
10995 		try {
10996 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
10997 				// it returns zero if the message is handled, so we won't do anything more there
10998 				// do I like that though?
10999 				int mustReturn;
11000 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11001 				if(mustReturn)
11002 					return ret;
11003 			}
11004 
11005 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11006 				if(window.getNativeEventHandler !is null) {
11007 					int mustReturn;
11008 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11009 					if(mustReturn)
11010 						return ret;
11011 				}
11012 				if(auto w = cast(SimpleWindow) (*window))
11013 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11014 				else
11015 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11016 			} else {
11017 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11018 			}
11019 		} catch (Exception e) {
11020 			try {
11021 				sdpy_abort(e);
11022 				return 0;
11023 			} catch(Exception e) { assert(0); }
11024 		}
11025 	}
11026 
11027 	void sdpy_abort(Throwable e) nothrow {
11028 		try
11029 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11030 		catch(Exception e)
11031 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11032 		ExitProcess(1);
11033 	}
11034 
11035 	mixin template NativeScreenPainterImplementation() {
11036 		HDC hdc;
11037 		HWND hwnd;
11038 		//HDC windowHdc;
11039 		HBITMAP oldBmp;
11040 
11041 		void create(NativeWindowHandle window) {
11042 			hwnd = window;
11043 
11044 			if(auto sw = cast(SimpleWindow) this.window) {
11045 				// drawing on a window, double buffer
11046 				auto windowHdc = GetDC(hwnd);
11047 
11048 				auto buffer = sw.impl.buffer;
11049 				if(buffer is null) {
11050 					hdc = windowHdc;
11051 					windowDc = true;
11052 				} else {
11053 					hdc = CreateCompatibleDC(windowHdc);
11054 
11055 					ReleaseDC(hwnd, windowHdc);
11056 
11057 					oldBmp = SelectObject(hdc, buffer);
11058 				}
11059 			} else {
11060 				// drawing on something else, draw directly
11061 				hdc = CreateCompatibleDC(null);
11062 				SelectObject(hdc, window);
11063 			}
11064 
11065 			// X doesn't draw a text background, so neither should we
11066 			SetBkMode(hdc, TRANSPARENT);
11067 
11068 			ensureDefaultFontLoaded();
11069 
11070 			if(defaultGuiFont) {
11071 				SelectObject(hdc, defaultGuiFont);
11072 				// DeleteObject(defaultGuiFont);
11073 			}
11074 		}
11075 
11076 		static HFONT defaultGuiFont;
11077 		static void ensureDefaultFontLoaded() {
11078 			static bool triedDefaultGuiFont = false;
11079 			if(!triedDefaultGuiFont) {
11080 				NONCLIENTMETRICS params;
11081 				params.cbSize = params.sizeof;
11082 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11083 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11084 				}
11085 				triedDefaultGuiFont = true;
11086 			}
11087 		}
11088 
11089 		void setFont(OperatingSystemFont font) {
11090 			if(font && font.font) {
11091 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11092 					// error... how to handle tho?
11093 				}
11094 			}
11095 			else if(defaultGuiFont)
11096 				SelectObject(hdc, defaultGuiFont);
11097 		}
11098 
11099 		arsd.color.Rectangle _clipRectangle;
11100 
11101 		void setClipRectangle(int x, int y, int width, int height) {
11102 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11103 
11104 			if(width == 0 || height == 0) {
11105 				SelectClipRgn(hdc, null);
11106 			} else {
11107 				auto region = CreateRectRgn(x, y, x + width, y + height);
11108 				SelectClipRgn(hdc, region);
11109 				DeleteObject(region);
11110 			}
11111 		}
11112 
11113 
11114 		// just because we can on Windows...
11115 		//void create(Image image);
11116 
11117 		void invalidateRect(Rectangle invalidRect) {
11118 			RECT rect;
11119 			rect.left = invalidRect.left;
11120 			rect.right = invalidRect.right;
11121 			rect.top = invalidRect.top;
11122 			rect.bottom = invalidRect.bottom;
11123 			InvalidateRect(hwnd, &rect, false);
11124 		}
11125 		bool manualInvalidations;
11126 
11127 		void dispose() {
11128 			// FIXME: this.window.width/height is probably wrong
11129 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11130 			// ReleaseDC(hwnd, windowHdc);
11131 
11132 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11133 			if(cast(SimpleWindow) this.window) {
11134 				if(!manualInvalidations)
11135 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11136 			}
11137 
11138 			if(originalPen !is null)
11139 				SelectObject(hdc, originalPen);
11140 			if(currentPen !is null)
11141 				DeleteObject(currentPen);
11142 			if(originalBrush !is null)
11143 				SelectObject(hdc, originalBrush);
11144 			if(currentBrush !is null)
11145 				DeleteObject(currentBrush);
11146 
11147 			SelectObject(hdc, oldBmp);
11148 
11149 			if(windowDc)
11150 				ReleaseDC(hwnd, hdc);
11151 			else
11152 				DeleteDC(hdc);
11153 
11154 			if(window.paintingFinishedDg !is null)
11155 				window.paintingFinishedDg()();
11156 		}
11157 
11158 		bool windowDc;
11159 		HPEN originalPen;
11160 		HPEN currentPen;
11161 
11162 		Pen _activePen;
11163 
11164 		Color _outlineColor;
11165 
11166 		@property void pen(Pen p) {
11167 			_activePen = p;
11168 			_outlineColor = p.color;
11169 
11170 			HPEN pen;
11171 			if(p.color.a == 0) {
11172 				pen = GetStockObject(NULL_PEN);
11173 			} else {
11174 				int style = PS_SOLID;
11175 				final switch(p.style) {
11176 					case Pen.Style.Solid:
11177 						style = PS_SOLID;
11178 					break;
11179 					case Pen.Style.Dashed:
11180 						style = PS_DASH;
11181 					break;
11182 					case Pen.Style.Dotted:
11183 						style = PS_DOT;
11184 					break;
11185 				}
11186 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11187 			}
11188 			auto orig = SelectObject(hdc, pen);
11189 			if(originalPen is null)
11190 				originalPen = orig;
11191 
11192 			if(currentPen !is null)
11193 				DeleteObject(currentPen);
11194 
11195 			currentPen = pen;
11196 
11197 			// the outline is like a foreground since it's done that way on X
11198 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11199 
11200 		}
11201 
11202 		@property void rasterOp(RasterOp op) {
11203 			int mode;
11204 			final switch(op) {
11205 				case RasterOp.normal:
11206 					mode = R2_COPYPEN;
11207 				break;
11208 				case RasterOp.xor:
11209 					mode = R2_XORPEN;
11210 				break;
11211 			}
11212 			SetROP2(hdc, mode);
11213 		}
11214 
11215 		HBRUSH originalBrush;
11216 		HBRUSH currentBrush;
11217 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11218 		@property void fillColor(Color c) {
11219 			if(c == _fillColor)
11220 				return;
11221 			_fillColor = c;
11222 			HBRUSH brush;
11223 			if(c.a == 0) {
11224 				brush = GetStockObject(HOLLOW_BRUSH);
11225 			} else {
11226 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11227 			}
11228 			auto orig = SelectObject(hdc, brush);
11229 			if(originalBrush is null)
11230 				originalBrush = orig;
11231 
11232 			if(currentBrush !is null)
11233 				DeleteObject(currentBrush);
11234 
11235 			currentBrush = brush;
11236 
11237 			// background color is NOT set because X doesn't draw text backgrounds
11238 			//   SetBkColor(hdc, RGB(255, 255, 255));
11239 		}
11240 
11241 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11242 			BITMAP bm;
11243 
11244 			HDC hdcMem = CreateCompatibleDC(hdc);
11245 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11246 
11247 			GetObject(i.handle, bm.sizeof, &bm);
11248 
11249 			// or should I AlphaBlend!??!?!
11250 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11251 
11252 			SelectObject(hdcMem, hbmOld);
11253 			DeleteDC(hdcMem);
11254 		}
11255 
11256 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11257 			BITMAP bm;
11258 
11259 			HDC hdcMem = CreateCompatibleDC(hdc);
11260 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11261 
11262 			GetObject(s.handle, bm.sizeof, &bm);
11263 
11264 			version(CRuntime_DigitalMars) goto noalpha;
11265 
11266 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11267 			if(s.enableAlpha) {
11268 				auto dw = w ? w : bm.bmWidth;
11269 				auto dh = h ? h : bm.bmHeight;
11270 				BLENDFUNCTION bf;
11271 				bf.BlendOp = AC_SRC_OVER;
11272 				bf.SourceConstantAlpha = 255;
11273 				bf.AlphaFormat = AC_SRC_ALPHA;
11274 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11275 			} else {
11276 				noalpha:
11277 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11278 			}
11279 
11280 			SelectObject(hdcMem, hbmOld);
11281 			DeleteDC(hdcMem);
11282 		}
11283 
11284 		Size textSize(scope const(char)[] text) {
11285 			bool dummyX;
11286 			if(text.length == 0) {
11287 				text = " ";
11288 				dummyX = true;
11289 			}
11290 			RECT rect;
11291 			WCharzBuffer buffer = WCharzBuffer(text);
11292 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11293 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11294 		}
11295 
11296 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11297 			if(text.length && text[$-1] == '\n')
11298 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11299 			if(text.length && text[$-1] == '\r')
11300 				text = text[0 .. $-1];
11301 
11302 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11303 			if(x2 == 0 && y2 == 0) {
11304 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11305 			} else {
11306 				RECT rect;
11307 				rect.left = x;
11308 				rect.top = y;
11309 				rect.right = x2;
11310 				rect.bottom = y2;
11311 
11312 				uint mode = DT_LEFT;
11313 				if(alignment & TextAlignment.Right)
11314 					mode = DT_RIGHT;
11315 				else if(alignment & TextAlignment.Center)
11316 					mode = DT_CENTER;
11317 
11318 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11319 				if(alignment & TextAlignment.VerticalCenter)
11320 					mode |= DT_VCENTER | DT_SINGLELINE;
11321 
11322 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11323 			}
11324 
11325 			/*
11326 			uint mode;
11327 
11328 			if(alignment & TextAlignment.Center)
11329 				mode = TA_CENTER;
11330 
11331 			SetTextAlign(hdc, mode);
11332 			*/
11333 		}
11334 
11335 		int fontHeight() {
11336 			TEXTMETRIC metric;
11337 			if(GetTextMetricsW(hdc, &metric)) {
11338 				return metric.tmHeight;
11339 			}
11340 
11341 			return 16; // idk just guessing here, maybe we should throw
11342 		}
11343 
11344 		void drawPixel(int x, int y) {
11345 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11346 		}
11347 
11348 		// The basic shapes, outlined
11349 
11350 		void drawLine(int x1, int y1, int x2, int y2) {
11351 			MoveToEx(hdc, x1, y1, null);
11352 			LineTo(hdc, x2, y2);
11353 		}
11354 
11355 		void drawRectangle(int x, int y, int width, int height) {
11356 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11357 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11358 		}
11359 
11360 		/// Arguments are the points of the bounding rectangle
11361 		void drawEllipse(int x1, int y1, int x2, int y2) {
11362 			Ellipse(hdc, x1, y1, x2, y2);
11363 		}
11364 
11365 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11366 			if((start % (360*64)) == (finish % (360*64)))
11367 				drawEllipse(x1, y1, x1 + width, y1 + height);
11368 			else {
11369 				import core.stdc.math;
11370 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11371 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11372 
11373 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11374 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11375 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11376 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11377 
11378 				if(_activePen.color.a)
11379 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11380 				if(_fillColor.a)
11381 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11382 			}
11383 		}
11384 
11385 		void drawPolygon(Point[] vertexes) {
11386 			POINT[] points;
11387 			points.length = vertexes.length;
11388 
11389 			foreach(i, p; vertexes) {
11390 				points[i].x = p.x;
11391 				points[i].y = p.y;
11392 			}
11393 
11394 			Polygon(hdc, points.ptr, cast(int) points.length);
11395 		}
11396 	}
11397 
11398 
11399 	// Mix this into the SimpleWindow class
11400 	mixin template NativeSimpleWindowImplementation() {
11401 		int curHidden = 0; // counter
11402 		__gshared static bool[string] knownWinClasses;
11403 		static bool altPressed = false;
11404 
11405 		HANDLE oldCursor;
11406 
11407 		void hideCursor () {
11408 			if(curHidden == 0)
11409 				oldCursor = SetCursor(null);
11410 			++curHidden;
11411 		}
11412 
11413 		void showCursor () {
11414 			--curHidden;
11415 			if(curHidden == 0) {
11416 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11417 			}
11418 		}
11419 
11420 
11421 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11422 
11423 		void setMinSize (int minwidth, int minheight) {
11424 			minWidth = minwidth;
11425 			minHeight = minheight;
11426 		}
11427 		void setMaxSize (int maxwidth, int maxheight) {
11428 			maxWidth = maxwidth;
11429 			maxHeight = maxheight;
11430 		}
11431 
11432 		// FIXME i'm not sure that Windows has this functionality
11433 		// though it is nonessential anyway.
11434 		void setResizeGranularity (int granx, int grany) {}
11435 
11436 		ScreenPainter getPainter(bool manualInvalidations) {
11437 			return ScreenPainter(this, hwnd, manualInvalidations);
11438 		}
11439 
11440 		HBITMAP buffer;
11441 
11442 		void setTitle(string title) {
11443 			WCharzBuffer bfr = WCharzBuffer(title);
11444 			SetWindowTextW(hwnd, bfr.ptr);
11445 		}
11446 
11447 		string getTitle() {
11448 			auto len = GetWindowTextLengthW(hwnd);
11449 			if (!len)
11450 				return null;
11451 			wchar[256] tmpBuffer;
11452 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11453 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11454 			auto str = buffer[0 .. len2];
11455 			return makeUtf8StringFromWindowsString(str);
11456 		}
11457 
11458 		void move(int x, int y) {
11459 			RECT rect;
11460 			GetWindowRect(hwnd, &rect);
11461 			// move it while maintaining the same size...
11462 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11463 		}
11464 
11465 		void resize(int w, int h) {
11466 			RECT rect;
11467 			GetWindowRect(hwnd, &rect);
11468 
11469 			RECT client;
11470 			GetClientRect(hwnd, &client);
11471 
11472 			rect.right = rect.right - client.right + w;
11473 			rect.bottom = rect.bottom - client.bottom + h;
11474 
11475 			// same position, new size for the client rectangle
11476 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11477 
11478 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
11479 		}
11480 
11481 		void moveResize (int x, int y, int w, int h) {
11482 			// what's given is the client rectangle, we need to adjust
11483 
11484 			RECT rect;
11485 			rect.left = x;
11486 			rect.top = y;
11487 			rect.right = w + x;
11488 			rect.bottom = h + y;
11489 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11490 				throw new Exception("AdjustWindowRect");
11491 
11492 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11493 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
11494 			if (windowResized !is null) windowResized(w, h);
11495 		}
11496 
11497 		version(without_opengl) {} else {
11498 			HGLRC ghRC;
11499 			HDC ghDC;
11500 		}
11501 
11502 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11503 			string cnamec;
11504 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11505 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11506 				cnamec = "DSimpleWindow";
11507 			} else {
11508 				cnamec = sdpyWindowClass;
11509 			}
11510 
11511 			WCharzBuffer cn = WCharzBuffer(cnamec);
11512 
11513 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11514 
11515 			if(cnamec !in knownWinClasses) {
11516 				WNDCLASSEX wc;
11517 
11518 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11519 				// to the object. Maybe.
11520 				wc.cbSize = wc.sizeof;
11521 				wc.cbClsExtra = 0;
11522 				wc.cbWndExtra = 0;
11523 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11524 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11525 				wc.hIcon = LoadIcon(hInstance, null);
11526 				wc.hInstance = hInstance;
11527 				wc.lpfnWndProc = &WndProc;
11528 				wc.lpszClassName = cn.ptr;
11529 				wc.hIconSm = null;
11530 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11531 				if(!RegisterClassExW(&wc))
11532 					throw new WindowsApiException("RegisterClassExW");
11533 				knownWinClasses[cnamec] = true;
11534 			}
11535 
11536 			int style;
11537 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11538 
11539 			// FIXME: windowType and customizationFlags
11540 			final switch(windowType) {
11541 				case WindowTypes.normal:
11542 					style = WS_OVERLAPPEDWINDOW;
11543 				break;
11544 				case WindowTypes.undecorated:
11545 					style = WS_POPUP | WS_SYSMENU;
11546 				break;
11547 				case WindowTypes.eventOnly:
11548 					_hidden = true;
11549 				break;
11550 				case WindowTypes.dropdownMenu:
11551 				case WindowTypes.popupMenu:
11552 				case WindowTypes.notification:
11553 					style = WS_POPUP;
11554 					flags |= WS_EX_NOACTIVATE;
11555 				break;
11556 				case WindowTypes.nestedChild:
11557 					style = WS_CHILD;
11558 				break;
11559 			}
11560 
11561 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11562 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11563 
11564 			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
11565 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11566 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11567 
11568 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11569 				setOpacity(255);
11570 
11571 			SimpleWindow.nativeMapping[hwnd] = this;
11572 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11573 
11574 			if(windowType == WindowTypes.eventOnly)
11575 				return;
11576 
11577 			HDC hdc = GetDC(hwnd);
11578 
11579 
11580 			version(without_opengl) {}
11581 			else {
11582 				if(opengl == OpenGlOptions.yes) {
11583 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11584 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11585 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11586 					ghDC = hdc;
11587 					PIXELFORMATDESCRIPTOR pfd;
11588 
11589 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11590 					pfd.nVersion = 1;
11591 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11592 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11593 					pfd.iPixelType = PFD_TYPE_RGBA;
11594 					pfd.cColorBits = 24;
11595 					pfd.cDepthBits = 24;
11596 					pfd.cAccumBits = 0;
11597 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11598 
11599 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11600 
11601 					if (pixelformat == 0)
11602 						throw new WindowsApiException("ChoosePixelFormat");
11603 
11604 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11605 						throw new WindowsApiException("SetPixelFormat");
11606 
11607 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11608 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11609 						// so we will create fake context to get that stupid address
11610 						auto tmpcc = wglCreateContext(ghDC);
11611 						if (tmpcc !is null) {
11612 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11613 							wglMakeCurrent(ghDC, tmpcc);
11614 							wglInitOtherFunctions();
11615 						}
11616 					}
11617 
11618 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11619 						int[9] contextAttribs = [
11620 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11621 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11622 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11623 							// for modern context, set "forward compatibility" flag too
11624 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11625 							0/*None*/,
11626 						];
11627 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11628 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11629 							// activate fallback mode
11630 							// 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;
11631 							ghRC = wglCreateContext(ghDC);
11632 						}
11633 						if (ghRC is null)
11634 							throw new WindowsApiException("wglCreateContextAttribsARB");
11635 					} else {
11636 						// try to do at least something
11637 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11638 							sdpyOpenGLContextVersion = 0;
11639 							ghRC = wglCreateContext(ghDC);
11640 						}
11641 						if (ghRC is null)
11642 							throw new WindowsApiException("wglCreateContext");
11643 					}
11644 				}
11645 			}
11646 
11647 			if(opengl == OpenGlOptions.no) {
11648 				buffer = CreateCompatibleBitmap(hdc, width, height);
11649 
11650 				auto hdcBmp = CreateCompatibleDC(hdc);
11651 				// make sure it's filled with a blank slate
11652 				auto oldBmp = SelectObject(hdcBmp, buffer);
11653 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11654 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11655 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11656 				SelectObject(hdcBmp, oldBmp);
11657 				SelectObject(hdcBmp, oldBrush);
11658 				SelectObject(hdcBmp, oldPen);
11659 				DeleteDC(hdcBmp);
11660 
11661 				bmpWidth = width;
11662 				bmpHeight = height;
11663 
11664 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11665 			}
11666 
11667 			// We want the window's client area to match the image size
11668 			RECT rcClient, rcWindow;
11669 			POINT ptDiff;
11670 			GetClientRect(hwnd, &rcClient);
11671 			GetWindowRect(hwnd, &rcWindow);
11672 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11673 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11674 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11675 
11676 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11677 				ShowWindow(hwnd, SW_SHOWNORMAL);
11678 			} else {
11679 				_hidden = true;
11680 			}
11681 			this._visibleForTheFirstTimeCalled = false; // hack!
11682 		}
11683 
11684 
11685 		void dispose() {
11686 			if(buffer)
11687 				DeleteObject(buffer);
11688 		}
11689 
11690 		void closeWindow() {
11691 			DestroyWindow(hwnd);
11692 		}
11693 
11694 		bool setOpacity(ubyte alpha) {
11695 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11696 		}
11697 
11698 		HANDLE currentCursor;
11699 
11700 		// returns zero if it recognized the event
11701 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11702 			MouseEvent mouse;
11703 
11704 			void mouseEvent(bool isScreen, ulong mods) {
11705 				auto x = LOWORD(lParam);
11706 				auto y = HIWORD(lParam);
11707 				if(isScreen) {
11708 					POINT p;
11709 					p.x = x;
11710 					p.y = y;
11711 					ScreenToClient(hwnd, &p);
11712 					x = cast(ushort) p.x;
11713 					y = cast(ushort) p.y;
11714 				}
11715 				mouse.x = x + offsetX;
11716 				mouse.y = y + offsetY;
11717 
11718 				wind.mdx(mouse);
11719 				mouse.modifierState = cast(int) mods;
11720 				mouse.window = wind;
11721 
11722 				if(wind.handleMouseEvent)
11723 					wind.handleMouseEvent(mouse);
11724 			}
11725 
11726 			switch(msg) {
11727 				case WM_GETMINMAXINFO:
11728 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11729 
11730 					if(wind.minWidth > 0) {
11731 						RECT rect;
11732 						rect.left = 100;
11733 						rect.top = 100;
11734 						rect.right = wind.minWidth + 100;
11735 						rect.bottom = wind.minHeight + 100;
11736 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11737 							throw new WindowsApiException("AdjustWindowRect");
11738 
11739 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11740 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11741 					}
11742 
11743 					if(wind.maxWidth < int.max) {
11744 						RECT rect;
11745 						rect.left = 100;
11746 						rect.top = 100;
11747 						rect.right = wind.maxWidth + 100;
11748 						rect.bottom = wind.maxHeight + 100;
11749 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11750 							throw new WindowsApiException("AdjustWindowRect");
11751 
11752 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11753 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11754 					}
11755 				break;
11756 				case WM_CHAR:
11757 					wchar c = cast(wchar) wParam;
11758 					if(wind.handleCharEvent)
11759 						wind.handleCharEvent(cast(dchar) c);
11760 				break;
11761 				  case WM_SETFOCUS:
11762 				  case WM_KILLFOCUS:
11763 					wind._focused = (msg == WM_SETFOCUS);
11764 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
11765 					if(wind.onFocusChange)
11766 						wind.onFocusChange(msg == WM_SETFOCUS);
11767 				  break;
11768 
11769 				case WM_SYSKEYDOWN:
11770 					goto case;
11771 				case WM_SYSKEYUP:
11772 					if(lParam & (1 << 29)) {
11773 						goto case;
11774 					} else {
11775 						// no window has keyboard focus
11776 						goto default;
11777 					}
11778 				case WM_KEYDOWN:
11779 				case WM_KEYUP:
11780 					KeyEvent ev;
11781 					ev.key = cast(Key) wParam;
11782 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
11783 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
11784 
11785 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
11786 
11787 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
11788 						ev.modifierState |= ModifierState.shift;
11789 					//k8: this doesn't work; thanks for nothing, windows
11790 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
11791 						ev.modifierState |= ModifierState.alt;*/
11792 					// this never seems to actually be set
11793 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11794 
11795 					if (wParam == 0x12) {
11796 						altPressed = (msg == WM_SYSKEYDOWN);
11797 					}
11798 
11799 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
11800 						altPressed = false;
11801 					}
11802 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
11803 
11804 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11805 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
11806 						ev.modifierState |= ModifierState.ctrl;
11807 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
11808 						ev.modifierState |= ModifierState.windows;
11809 					if(GetKeyState(Key.NumLock))
11810 						ev.modifierState |= ModifierState.numLock;
11811 					if(GetKeyState(Key.CapsLock))
11812 						ev.modifierState |= ModifierState.capsLock;
11813 
11814 					/+
11815 					// we always want to send the character too, so let's convert it
11816 					ubyte[256] state;
11817 					wchar[16] buffer;
11818 					GetKeyboardState(state.ptr);
11819 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
11820 
11821 					foreach(dchar d; buffer) {
11822 						ev.character = d;
11823 						break;
11824 					}
11825 					+/
11826 
11827 					ev.window = wind;
11828 					if(wind.handleKeyEvent)
11829 						wind.handleKeyEvent(ev);
11830 				break;
11831 				case 0x020a /*WM_MOUSEWHEEL*/:
11832 					// send click
11833 					mouse.type = cast(MouseEventType) 1;
11834 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11835 					mouseEvent(true, LOWORD(wParam));
11836 
11837 					// also send release
11838 					mouse.type = cast(MouseEventType) 2;
11839 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11840 					mouseEvent(true, LOWORD(wParam));
11841 				break;
11842 				case WM_MOUSEMOVE:
11843 					mouse.type = cast(MouseEventType) 0;
11844 					mouseEvent(false, wParam);
11845 				break;
11846 				case WM_LBUTTONDOWN:
11847 				case WM_LBUTTONDBLCLK:
11848 					mouse.type = cast(MouseEventType) 1;
11849 					mouse.button = MouseButton.left;
11850 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
11851 					mouseEvent(false, wParam);
11852 				break;
11853 				case WM_LBUTTONUP:
11854 					mouse.type = cast(MouseEventType) 2;
11855 					mouse.button = MouseButton.left;
11856 					mouseEvent(false, wParam);
11857 				break;
11858 				case WM_RBUTTONDOWN:
11859 				case WM_RBUTTONDBLCLK:
11860 					mouse.type = cast(MouseEventType) 1;
11861 					mouse.button = MouseButton.right;
11862 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
11863 					mouseEvent(false, wParam);
11864 				break;
11865 				case WM_RBUTTONUP:
11866 					mouse.type = cast(MouseEventType) 2;
11867 					mouse.button = MouseButton.right;
11868 					mouseEvent(false, wParam);
11869 				break;
11870 				case WM_MBUTTONDOWN:
11871 				case WM_MBUTTONDBLCLK:
11872 					mouse.type = cast(MouseEventType) 1;
11873 					mouse.button = MouseButton.middle;
11874 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
11875 					mouseEvent(false, wParam);
11876 				break;
11877 				case WM_MBUTTONUP:
11878 					mouse.type = cast(MouseEventType) 2;
11879 					mouse.button = MouseButton.middle;
11880 					mouseEvent(false, wParam);
11881 				break;
11882 				case WM_XBUTTONDOWN:
11883 				case WM_XBUTTONDBLCLK:
11884 					mouse.type = cast(MouseEventType) 1;
11885 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11886 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
11887 					mouseEvent(false, wParam);
11888 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
11889 				case WM_XBUTTONUP:
11890 					mouse.type = cast(MouseEventType) 2;
11891 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11892 					mouseEvent(false, wParam);
11893 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
11894 
11895 				default: return 1;
11896 			}
11897 			return 0;
11898 		}
11899 
11900 		HWND hwnd;
11901 		private int oldWidth;
11902 		private int oldHeight;
11903 		private bool inSizeMove;
11904 
11905 		/++
11906 			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.
11907 
11908 			History:
11909 				Added November 23, 2021
11910 
11911 				Not fully stable, may be moved out of the impl struct.
11912 
11913 				Default value changed to `true` on February 15, 2021
11914 		+/
11915 		bool doLiveResizing = true;
11916 
11917 		package int bmpWidth;
11918 		package int bmpHeight;
11919 
11920 		// the extern(Windows) wndproc should just forward to this
11921 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
11922 		try {
11923 			assert(hwnd is this.hwnd);
11924 
11925 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
11926 			switch(msg) {
11927 				case WM_MENUCHAR: // menu active but key not associated with a thing.
11928 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
11929 					// The main things we can do are select, execute, close, or ignore
11930 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
11931 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
11932 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
11933 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
11934 
11935 					// returns the value in the *high order word* of the return value
11936 					// hence the << 16
11937 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
11938 				case WM_SETCURSOR:
11939 					if(cast(HWND) wParam !is hwnd)
11940 						return 0; // further processing elsewhere
11941 
11942 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
11943 						SetCursor(this.curHidden > 0 ? null : currentCursor);
11944 						return 1;
11945 					} else {
11946 						return DefWindowProc(hwnd, msg, wParam, lParam);
11947 					}
11948 				//break;
11949 
11950 				case WM_CLOSE:
11951 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
11952 				break;
11953 				case WM_DESTROY:
11954 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
11955 					SimpleWindow.nativeMapping.remove(hwnd);
11956 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
11957 
11958 					bool anyImportant = false;
11959 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
11960 						if(w.beingOpenKeepsAppOpen) {
11961 							anyImportant = true;
11962 							break;
11963 						}
11964 					if(!anyImportant) {
11965 						PostQuitMessage(0);
11966 					}
11967 				break;
11968 				case 0x02E0 /*WM_DPICHANGED*/:
11969 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
11970 
11971 					RECT* prcNewWindow = cast(RECT*)lParam;
11972 					// docs say this is the recommended position and we should honor it
11973 					SetWindowPos(hwnd,
11974 							null,
11975 							prcNewWindow.left,
11976 							prcNewWindow.top,
11977 							prcNewWindow.right - prcNewWindow.left,
11978 							prcNewWindow.bottom - prcNewWindow.top,
11979 							SWP_NOZORDER | SWP_NOACTIVATE);
11980 
11981 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
11982 					// im not sure it is completely correct
11983 					// but without it the tabs and such do look weird as things change.
11984 					if(SystemParametersInfoForDpi) {
11985 						LOGFONT lfText;
11986 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
11987 						HFONT hFontNew = CreateFontIndirect(&lfText);
11988 						if (hFontNew)
11989 						{
11990 							//DeleteObject(hFontOld);
11991 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
11992 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
11993 								return TRUE;
11994 							}
11995 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
11996 						}
11997 					}
11998 
11999 					if(this.onDpiChanged)
12000 						this.onDpiChanged();
12001 				break;
12002 				case WM_ENTERIDLE:
12003 					// when a menu is up, it stops normal event processing (modal message loop)
12004 					// but this at least gives us a chance to SOMETIMES catch up
12005 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12006 					SimpleWindow.processAllCustomEvents;
12007 					SimpleWindow.processAllCustomEvents;
12008 					SleepEx(0, true);
12009 					break;
12010 				case WM_SIZE:
12011 					if(wParam == 1 /* SIZE_MINIMIZED */)
12012 						break;
12013 					_width = LOWORD(lParam);
12014 					_height = HIWORD(lParam);
12015 
12016 					// I want to avoid tearing in the windows (my code is inefficient
12017 					// so this is a hack around that) so while sizing, we don't trigger,
12018 					// but we do want to trigger on events like mazimize.
12019 					if(!inSizeMove || doLiveResizing)
12020 						goto size_changed;
12021 				break;
12022 				/+
12023 				case WM_SIZING:
12024 					import std.stdio; writeln("size");
12025 				break;
12026 				+/
12027 				// I don't like the tearing I get when redrawing on WM_SIZE
12028 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12029 				// so instead it is going to redraw only at the end of a size.
12030 				case 0x0231: /* WM_ENTERSIZEMOVE */
12031 					inSizeMove = true;
12032 				break;
12033 				case 0x0232: /* WM_EXITSIZEMOVE */
12034 					inSizeMove = false;
12035 
12036 					size_changed:
12037 
12038 					// nothing relevant changed, don't bother redrawing
12039 					if(oldWidth == width && oldHeight == height) {
12040 						break;
12041 					}
12042 
12043 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12044 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12045 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12046 						// gotta get the double buffer bmp to match the window
12047 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12048 						if(width > bmpWidth || height > bmpHeight) {
12049 							auto hdc = GetDC(hwnd);
12050 							auto oldBuffer = buffer;
12051 							buffer = CreateCompatibleBitmap(hdc, width, height);
12052 
12053 							auto hdcBmp = CreateCompatibleDC(hdc);
12054 							auto oldBmp = SelectObject(hdcBmp, buffer);
12055 
12056 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12057 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12058 
12059 							/+
12060 							RECT r;
12061 							r.left = 0;
12062 							r.top = 0;
12063 							r.right = width;
12064 							r.bottom = height;
12065 							auto c = Color.green;
12066 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12067 							FillRect(hdcBmp, &r, brush);
12068 							DeleteObject(brush);
12069 							+/
12070 
12071 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12072 
12073 							bmpWidth = width;
12074 							bmpHeight = height;
12075 
12076 							SelectObject(hdcOldBmp, oldOldBmp);
12077 							DeleteDC(hdcOldBmp);
12078 
12079 							SelectObject(hdcBmp, oldBmp);
12080 							DeleteDC(hdcBmp);
12081 
12082 							ReleaseDC(hwnd, hdc);
12083 
12084 							DeleteObject(oldBuffer);
12085 						}
12086 					}
12087 
12088 					version(without_opengl) {} else
12089 					if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
12090 						glViewport(0, 0, width, height);
12091 					}
12092 
12093 					if(windowResized !is null)
12094 						windowResized(width, height);
12095 
12096 					if(inSizeMove) {
12097 						SimpleWindow.processAllCustomEvents();
12098 						SimpleWindow.processAllCustomEvents();
12099 					} else {
12100 						// when it is all done, make sure everything is freshly drawn or there might be
12101 						// weird bugs left.
12102 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12103 					}
12104 
12105 					oldWidth = this.width;
12106 					oldHeight = this.height;
12107 				break;
12108 				case WM_ERASEBKGND:
12109 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12110 					if (!this._visibleForTheFirstTimeCalled) {
12111 						this._visibleForTheFirstTimeCalled = true;
12112 						if (this.visibleForTheFirstTime !is null) {
12113 							version(without_opengl) {} else {
12114 								if(openglMode == OpenGlOptions.yes) {
12115 									this.setAsCurrentOpenGlContextNT();
12116 									glViewport(0, 0, width, height);
12117 								}
12118 							}
12119 							this.visibleForTheFirstTime();
12120 						}
12121 					}
12122 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12123 					version(without_opengl) {} else {
12124 						if (openglMode == OpenGlOptions.yes) return 1;
12125 					}
12126 					// call windows default handler, so it can paint standard controls
12127 					goto default;
12128 				case WM_CTLCOLORBTN:
12129 				case WM_CTLCOLORSTATIC:
12130 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12131 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12132 					GetSysColorBrush(COLOR_3DFACE);
12133 				//break;
12134 				case WM_SHOWWINDOW:
12135 					this._visible = (wParam != 0);
12136 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12137 						this._visibleForTheFirstTimeCalled = true;
12138 						if (this.visibleForTheFirstTime !is null) {
12139 							version(without_opengl) {} else {
12140 								if(openglMode == OpenGlOptions.yes) {
12141 									this.setAsCurrentOpenGlContextNT();
12142 									glViewport(0, 0, width, height);
12143 								}
12144 							}
12145 							this.visibleForTheFirstTime();
12146 						}
12147 					}
12148 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12149 					break;
12150 				case WM_PAINT: {
12151 					if (!this._visibleForTheFirstTimeCalled) {
12152 						this._visibleForTheFirstTimeCalled = true;
12153 						if (this.visibleForTheFirstTime !is null) {
12154 							version(without_opengl) {} else {
12155 								if(openglMode == OpenGlOptions.yes) {
12156 									this.setAsCurrentOpenGlContextNT();
12157 									glViewport(0, 0, width, height);
12158 								}
12159 							}
12160 							this.visibleForTheFirstTime();
12161 						}
12162 					}
12163 
12164 					BITMAP bm;
12165 					PAINTSTRUCT ps;
12166 
12167 					HDC hdc = BeginPaint(hwnd, &ps);
12168 
12169 					if(openglMode == OpenGlOptions.no) {
12170 
12171 						HDC hdcMem = CreateCompatibleDC(hdc);
12172 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12173 
12174 						GetObject(buffer, bm.sizeof, &bm);
12175 
12176 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12177 						if(resizability == Resizability.automaticallyScaleIfPossible)
12178 						StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12179 						else
12180 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12181 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12182 
12183 						SelectObject(hdcMem, hbmOld);
12184 						DeleteDC(hdcMem);
12185 						EndPaint(hwnd, &ps);
12186 					} else {
12187 						EndPaint(hwnd, &ps);
12188 						version(without_opengl) {} else
12189 							redrawOpenGlSceneNow();
12190 					}
12191 				} break;
12192 				  default:
12193 					return DefWindowProc(hwnd, msg, wParam, lParam);
12194 			}
12195 			 return 0;
12196 
12197 		}
12198 		catch(Throwable t) {
12199 			sdpyPrintDebugString(t.toString);
12200 			return 0;
12201 		}
12202 		}
12203 	}
12204 
12205 	mixin template NativeImageImplementation() {
12206 		HBITMAP handle;
12207 		ubyte* rawData;
12208 
12209 	final:
12210 
12211 		Color getPixel(int x, int y) {
12212 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12213 			// remember, bmps are upside down
12214 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12215 
12216 			Color c;
12217 			if(enableAlpha)
12218 				c.a = rawData[offset + 3];
12219 			else
12220 				c.a = 255;
12221 			c.b = rawData[offset + 0];
12222 			c.g = rawData[offset + 1];
12223 			c.r = rawData[offset + 2];
12224 			c.unPremultiply();
12225 			return c;
12226 		}
12227 
12228 		void setPixel(int x, int y, Color c) {
12229 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12230 			// remember, bmps are upside down
12231 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12232 
12233 			if(enableAlpha)
12234 				c.premultiply();
12235 
12236 			rawData[offset + 0] = c.b;
12237 			rawData[offset + 1] = c.g;
12238 			rawData[offset + 2] = c.r;
12239 			if(enableAlpha)
12240 				rawData[offset + 3] = c.a;
12241 		}
12242 
12243 		void convertToRgbaBytes(ubyte[] where) {
12244 			assert(where.length == this.width * this.height * 4);
12245 
12246 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12247 			int idx = 0;
12248 			int offset = itemsPerLine * (height - 1);
12249 			// remember, bmps are upside down
12250 			for(int y = height - 1; y >= 0; y--) {
12251 				auto offsetStart = offset;
12252 				for(int x = 0; x < width; x++) {
12253 					where[idx + 0] = rawData[offset + 2]; // r
12254 					where[idx + 1] = rawData[offset + 1]; // g
12255 					where[idx + 2] = rawData[offset + 0]; // b
12256 					if(enableAlpha) {
12257 						where[idx + 3] = rawData[offset + 3]; // a
12258 						unPremultiplyRgba(where[idx .. idx + 4]);
12259 						offset++;
12260 					} else
12261 						where[idx + 3] = 255; // a
12262 					idx += 4;
12263 					offset += 3;
12264 				}
12265 
12266 				offset = offsetStart - itemsPerLine;
12267 			}
12268 		}
12269 
12270 		void setFromRgbaBytes(in ubyte[] what) {
12271 			assert(what.length == this.width * this.height * 4);
12272 
12273 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12274 			int idx = 0;
12275 			int offset = itemsPerLine * (height - 1);
12276 			// remember, bmps are upside down
12277 			for(int y = height - 1; y >= 0; y--) {
12278 				auto offsetStart = offset;
12279 				for(int x = 0; x < width; x++) {
12280 					if(enableAlpha) {
12281 						auto a = what[idx + 3];
12282 
12283 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12284 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12285 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12286 						rawData[offset + 3] = a; // a
12287 						//premultiplyBgra(rawData[offset .. offset + 4]);
12288 						offset++;
12289 					} else {
12290 						rawData[offset + 2] = what[idx + 0]; // r
12291 						rawData[offset + 1] = what[idx + 1]; // g
12292 						rawData[offset + 0] = what[idx + 2]; // b
12293 					}
12294 					idx += 4;
12295 					offset += 3;
12296 				}
12297 
12298 				offset = offsetStart - itemsPerLine;
12299 			}
12300 		}
12301 
12302 
12303 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12304 			BITMAPINFO infoheader;
12305 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12306 			infoheader.bmiHeader.biWidth = width;
12307 			infoheader.bmiHeader.biHeight = height;
12308 			infoheader.bmiHeader.biPlanes = 1;
12309 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12310 			infoheader.bmiHeader.biCompression = BI_RGB;
12311 
12312 			handle = CreateDIBSection(
12313 				null,
12314 				&infoheader,
12315 				DIB_RGB_COLORS,
12316 				cast(void**) &rawData,
12317 				null,
12318 				0);
12319 			if(handle is null)
12320 				throw new WindowsApiException("create image failed");
12321 
12322 		}
12323 
12324 		void dispose() {
12325 			DeleteObject(handle);
12326 		}
12327 	}
12328 
12329 	enum KEY_ESCAPE = 27;
12330 }
12331 version(X11) {
12332 	/// This is the default font used. You might change this before doing anything else with
12333 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12334 	/// for cross-platform compatibility.
12335 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12336 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12337 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12338 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12339 
12340 	alias int delegate(XEvent) NativeEventHandler;
12341 	alias Window NativeWindowHandle;
12342 
12343 	enum KEY_ESCAPE = 9;
12344 
12345 	mixin template NativeScreenPainterImplementation() {
12346 		Display* display;
12347 		Drawable d;
12348 		Drawable destiny;
12349 
12350 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12351 		GC gc;
12352 
12353 		__gshared bool fontAttempted;
12354 
12355 		__gshared XFontStruct* defaultfont;
12356 		__gshared XFontSet defaultfontset;
12357 
12358 		XFontStruct* font;
12359 		XFontSet fontset;
12360 
12361 		void create(NativeWindowHandle window) {
12362 			this.display = XDisplayConnection.get();
12363 
12364 			Drawable buffer = None;
12365 			if(auto sw = cast(SimpleWindow) this.window) {
12366 				buffer = sw.impl.buffer;
12367 				this.destiny = cast(Drawable) window;
12368 			} else {
12369 				buffer = cast(Drawable) window;
12370 				this.destiny = None;
12371 			}
12372 
12373 			this.d = cast(Drawable) buffer;
12374 
12375 			auto dgc = DefaultGC(display, DefaultScreen(display));
12376 
12377 			this.gc = XCreateGC(display, d, 0, null);
12378 
12379 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12380 
12381 			ensureDefaultFontLoaded();
12382 
12383 			font = defaultfont;
12384 			fontset = defaultfontset;
12385 
12386 			if(font) {
12387 				XSetFont(display, gc, font.fid);
12388 			}
12389 		}
12390 
12391 		static void ensureDefaultFontLoaded() {
12392 			if(!fontAttempted) {
12393 				auto display = XDisplayConnection.get;
12394 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12395 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12396 				if(font is null) {
12397 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12398 					font = XLoadQueryFont(display, xfontstr.ptr);
12399 				}
12400 
12401 				char** lol;
12402 				int lol2;
12403 				char* lol3;
12404 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12405 
12406 				fontAttempted = true;
12407 
12408 				defaultfont = font;
12409 				defaultfontset = fontset;
12410 			}
12411 		}
12412 
12413 		arsd.color.Rectangle _clipRectangle;
12414 		void setClipRectangle(int x, int y, int width, int height) {
12415 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12416 			if(width == 0 || height == 0) {
12417 				XSetClipMask(display, gc, None);
12418 
12419 				if(xrenderPicturePainter) {
12420 
12421 					XRectangle[1] rects;
12422 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12423 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12424 				}
12425 
12426 				version(with_xft) {
12427 					if(xftFont is null || xftDraw is null)
12428 						return;
12429 					XftDrawSetClip(xftDraw, null);
12430 				}
12431 			} else {
12432 				XRectangle[1] rects;
12433 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12434 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12435 
12436 				if(xrenderPicturePainter)
12437 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12438 
12439 				version(with_xft) {
12440 					if(xftFont is null || xftDraw is null)
12441 						return;
12442 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12443 				}
12444 			}
12445 		}
12446 
12447 		version(with_xft) {
12448 			XftFont* xftFont;
12449 			XftDraw* xftDraw;
12450 
12451 			XftColor xftColor;
12452 
12453 			void updateXftColor() {
12454 				if(xftFont is null)
12455 					return;
12456 
12457 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12458 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12459 
12460 				XftColorAllocValue(
12461 					display,
12462 					DefaultVisual(display, DefaultScreen(display)),
12463 					DefaultColormap(display, 0),
12464 					&colorIn,
12465 					&xftColor
12466 				);
12467 			}
12468 		}
12469 
12470 		void setFont(OperatingSystemFont font) {
12471 			version(with_xft) {
12472 				if(font && font.isXft && font.xftFont)
12473 					this.xftFont = font.xftFont;
12474 				else
12475 					this.xftFont = null;
12476 
12477 				if(this.xftFont) {
12478 					if(xftDraw is null) {
12479 						xftDraw = XftDrawCreate(
12480 							display,
12481 							d,
12482 							DefaultVisual(display, DefaultScreen(display)),
12483 							DefaultColormap(display, 0)
12484 						);
12485 
12486 						updateXftColor();
12487 					}
12488 
12489 					return;
12490 				}
12491 			}
12492 
12493 			if(font && font.font) {
12494 				this.font = font.font;
12495 				this.fontset = font.fontset;
12496 				XSetFont(display, gc, font.font.fid);
12497 			} else {
12498 				this.font = defaultfont;
12499 				this.fontset = defaultfontset;
12500 			}
12501 
12502 		}
12503 
12504 		private Picture xrenderPicturePainter;
12505 
12506 		bool manualInvalidations;
12507 		void invalidateRect(Rectangle invalidRect) {
12508 			// FIXME if manualInvalidations
12509 		}
12510 
12511 		void dispose() {
12512 			this.rasterOp = RasterOp.normal;
12513 
12514 			if(xrenderPicturePainter) {
12515 				XRenderFreePicture(display, xrenderPicturePainter);
12516 				xrenderPicturePainter = None;
12517 			}
12518 
12519 			// FIXME: this.window.width/height is probably wrong
12520 
12521 			// src x,y     then dest x, y
12522 			if(destiny != None) {
12523 				// FIXME: if manual invalidations we can actually only copy some of the area.
12524 				// if(manualInvalidations)
12525 				XSetClipMask(display, gc, None);
12526 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12527 			}
12528 
12529 			XFreeGC(display, gc);
12530 
12531 			version(with_xft)
12532 			if(xftDraw) {
12533 				XftDrawDestroy(xftDraw);
12534 				xftDraw = null;
12535 			}
12536 
12537 			/+
12538 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12539 			if(font && font !is defaultfont) {
12540 				XFreeFont(display, font);
12541 				font = null;
12542 			}
12543 			if(fontset && fontset !is defaultfontset) {
12544 				XFreeFontSet(display, fontset);
12545 				fontset = null;
12546 			}
12547 			+/
12548 			XFlush(display);
12549 
12550 			if(window.paintingFinishedDg !is null)
12551 				window.paintingFinishedDg()();
12552 		}
12553 
12554 		bool backgroundIsNotTransparent = true;
12555 		bool foregroundIsNotTransparent = true;
12556 
12557 		bool _penInitialized = false;
12558 		Pen _activePen;
12559 
12560 		Color _outlineColor;
12561 		Color _fillColor;
12562 
12563 		@property void pen(Pen p) {
12564 			if(_penInitialized && p == _activePen) {
12565 				return;
12566 			}
12567 			_penInitialized = true;
12568 			_activePen = p;
12569 			_outlineColor = p.color;
12570 
12571 			int style;
12572 
12573 			byte dashLength;
12574 
12575 			final switch(p.style) {
12576 				case Pen.Style.Solid:
12577 					style = 0 /*LineSolid*/;
12578 				break;
12579 				case Pen.Style.Dashed:
12580 					style = 1 /*LineOnOffDash*/;
12581 					dashLength = 4;
12582 				break;
12583 				case Pen.Style.Dotted:
12584 					style = 1 /*LineOnOffDash*/;
12585 					dashLength = 1;
12586 				break;
12587 			}
12588 
12589 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
12590 			if(dashLength)
12591 				XSetDashes(display, gc, 0, &dashLength, 1);
12592 
12593 			if(p.color.a == 0) {
12594 				foregroundIsNotTransparent = false;
12595 				return;
12596 			}
12597 
12598 			foregroundIsNotTransparent = true;
12599 
12600 			XSetForeground(display, gc, colorToX(p.color, display));
12601 
12602 			version(with_xft)
12603 				updateXftColor();
12604 		}
12605 
12606 		RasterOp _currentRasterOp;
12607 		bool _currentRasterOpInitialized = false;
12608 		@property void rasterOp(RasterOp op) {
12609 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12610 				return;
12611 			_currentRasterOp = op;
12612 			_currentRasterOpInitialized = true;
12613 			int mode;
12614 			final switch(op) {
12615 				case RasterOp.normal:
12616 					mode = GXcopy;
12617 				break;
12618 				case RasterOp.xor:
12619 					mode = GXxor;
12620 				break;
12621 			}
12622 			XSetFunction(display, gc, mode);
12623 		}
12624 
12625 
12626 		bool _fillColorInitialized = false;
12627 
12628 		@property void fillColor(Color c) {
12629 			if(_fillColorInitialized && _fillColor == c)
12630 				return; // already good, no need to waste time calling it
12631 			_fillColor = c;
12632 			_fillColorInitialized = true;
12633 			if(c.a == 0) {
12634 				backgroundIsNotTransparent = false;
12635 				return;
12636 			}
12637 
12638 			backgroundIsNotTransparent = true;
12639 
12640 			XSetBackground(display, gc, colorToX(c, display));
12641 
12642 		}
12643 
12644 		void swapColors() {
12645 			auto tmp = _fillColor;
12646 			fillColor = _outlineColor;
12647 			auto newPen = _activePen;
12648 			newPen.color = tmp;
12649 			pen(newPen);
12650 		}
12651 
12652 		uint colorToX(Color c, Display* display) {
12653 			auto visual = DefaultVisual(display, DefaultScreen(display));
12654 			import core.bitop;
12655 			uint color = 0;
12656 			{
12657 			auto startBit = bsf(visual.red_mask);
12658 			auto lastBit = bsr(visual.red_mask);
12659 			auto r = cast(uint) c.r;
12660 			r >>= 7 - (lastBit - startBit);
12661 			r <<= startBit;
12662 			color |= r;
12663 			}
12664 			{
12665 			auto startBit = bsf(visual.green_mask);
12666 			auto lastBit = bsr(visual.green_mask);
12667 			auto g = cast(uint) c.g;
12668 			g >>= 7 - (lastBit - startBit);
12669 			g <<= startBit;
12670 			color |= g;
12671 			}
12672 			{
12673 			auto startBit = bsf(visual.blue_mask);
12674 			auto lastBit = bsr(visual.blue_mask);
12675 			auto b = cast(uint) c.b;
12676 			b >>= 7 - (lastBit - startBit);
12677 			b <<= startBit;
12678 			color |= b;
12679 			}
12680 
12681 
12682 
12683 			return color;
12684 		}
12685 
12686 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12687 			// source x, source y
12688 			if(ix >= i.width) return;
12689 			if(iy >= i.height) return;
12690 			if(ix + w > i.width) w = i.width - ix;
12691 			if(iy + h > i.height) h = i.height - iy;
12692 			if(i.usingXshm)
12693 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12694 			else
12695 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12696 		}
12697 
12698 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12699 			if(s.enableAlpha) {
12700 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12701 				if(this.xrenderPicturePainter == None) {
12702 					XRenderPictureAttributes attrs;
12703 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12704 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12705 
12706 					// need to initialize the clip
12707 					XRectangle[1] rects;
12708 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12709 
12710 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12711 				}
12712 
12713 				XRenderComposite(
12714 					display,
12715 					3, // PicOpOver
12716 					s.xrenderPicture,
12717 					None,
12718 					this.xrenderPicturePainter,
12719 					ix,
12720 					iy,
12721 					0,
12722 					0,
12723 					x,
12724 					y,
12725 					w ? w : s.width,
12726 					h ? h : s.height
12727 				);
12728 			} else {
12729 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12730 			}
12731 		}
12732 
12733 		int fontHeight() {
12734 			version(with_xft)
12735 				if(xftFont !is null)
12736 					return xftFont.height;
12737 			if(font)
12738 				return font.max_bounds.ascent + font.max_bounds.descent;
12739 			return 12; // pretty common default...
12740 		}
12741 
12742 		int textWidth(in char[] line) {
12743 			version(with_xft)
12744 			if(xftFont) {
12745 				if(line.length == 0)
12746 					return 0;
12747 				XGlyphInfo extents;
12748 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12749 				return extents.width;
12750 			}
12751 
12752 			if(fontset) {
12753 				if(line.length == 0)
12754 					return 0;
12755 				XRectangle rect;
12756 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12757 
12758 				return rect.width;
12759 			}
12760 
12761 			if(font)
12762 				// FIXME: unicode
12763 				return XTextWidth( font, line.ptr, cast(int) line.length);
12764 			else
12765 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
12766 		}
12767 
12768 		Size textSize(in char[] text) {
12769 			auto maxWidth = 0;
12770 			auto lineHeight = fontHeight;
12771 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
12772 			foreach(line; text.split('\n')) {
12773 				int textWidth = this.textWidth(line);
12774 				if(textWidth > maxWidth)
12775 					maxWidth = textWidth;
12776 				h += lineHeight + 4;
12777 			}
12778 			return Size(maxWidth, h);
12779 		}
12780 
12781 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
12782 			const(char)[] text;
12783 			version(with_xft)
12784 			if(xftFont) {
12785 				text = originalText;
12786 				goto loaded;
12787 			}
12788 
12789 			if(fontset)
12790 				text = originalText;
12791 			else {
12792 				text.reserve(originalText.length);
12793 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
12794 				// then strip the rest so there isn't garbage
12795 				foreach(dchar ch; originalText)
12796 					if(ch < 256)
12797 						text ~= cast(ubyte) ch;
12798 					else
12799 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
12800 			}
12801 			loaded:
12802 			if(text.length == 0)
12803 				return;
12804 
12805 			// FIXME: should we clip it to the bounding box?
12806 			int textHeight = fontHeight;
12807 
12808 			auto lines = text.split('\n');
12809 
12810 			const lineHeight = textHeight;
12811 			textHeight *= lines.length;
12812 
12813 			int cy = y;
12814 
12815 			if(alignment & TextAlignment.VerticalBottom) {
12816 				if(y2 <= 0)
12817 					return;
12818 				auto h = y2 - y;
12819 				if(h > textHeight) {
12820 					cy += h - textHeight;
12821 					cy -= lineHeight / 2;
12822 				}
12823 			} else if(alignment & TextAlignment.VerticalCenter) {
12824 				if(y2 <= 0)
12825 					return;
12826 				auto h = y2 - y;
12827 				if(textHeight < h) {
12828 					cy += (h - textHeight) / 2;
12829 					//cy -= lineHeight / 4;
12830 				}
12831 			}
12832 
12833 			foreach(line; text.split('\n')) {
12834 				int textWidth = this.textWidth(line);
12835 
12836 				int px = x, py = cy;
12837 
12838 				if(alignment & TextAlignment.Center) {
12839 					if(x2 <= 0)
12840 						return;
12841 					auto w = x2 - x;
12842 					if(w > textWidth)
12843 						px += (w - textWidth) / 2;
12844 				} else if(alignment & TextAlignment.Right) {
12845 					if(x2 <= 0)
12846 						return;
12847 					auto pos = x2 - textWidth;
12848 					if(pos > x)
12849 						px = pos;
12850 				}
12851 
12852 				version(with_xft)
12853 				if(xftFont) {
12854 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
12855 
12856 					goto carry_on;
12857 				}
12858 
12859 				if(fontset)
12860 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12861 				else
12862 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12863 				carry_on:
12864 				cy += lineHeight + 4;
12865 			}
12866 		}
12867 
12868 		void drawPixel(int x, int y) {
12869 			XDrawPoint(display, d, gc, x, y);
12870 		}
12871 
12872 		// The basic shapes, outlined
12873 
12874 		void drawLine(int x1, int y1, int x2, int y2) {
12875 			if(foregroundIsNotTransparent)
12876 				XDrawLine(display, d, gc, x1, y1, x2, y2);
12877 		}
12878 
12879 		void drawRectangle(int x, int y, int width, int height) {
12880 			if(backgroundIsNotTransparent) {
12881 				swapColors();
12882 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
12883 				swapColors();
12884 			}
12885 			// 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
12886 			if(foregroundIsNotTransparent)
12887 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
12888 		}
12889 
12890 		/// Arguments are the points of the bounding rectangle
12891 		void drawEllipse(int x1, int y1, int x2, int y2) {
12892 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
12893 		}
12894 
12895 		// NOTE: start and finish are in units of degrees * 64
12896 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
12897 			if(backgroundIsNotTransparent) {
12898 				swapColors();
12899 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
12900 				swapColors();
12901 			}
12902 			if(foregroundIsNotTransparent) {
12903 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
12904 				// Windows draws the straight lines on the edges too so FIXME sort of
12905 			}
12906 		}
12907 
12908 		void drawPolygon(Point[] vertexes) {
12909 			XPoint[16] pointsBuffer;
12910 			XPoint[] points;
12911 			if(vertexes.length <= pointsBuffer.length)
12912 				points = pointsBuffer[0 .. vertexes.length];
12913 			else
12914 				points.length = vertexes.length;
12915 
12916 			foreach(i, p; vertexes) {
12917 				points[i].x = cast(short) p.x;
12918 				points[i].y = cast(short) p.y;
12919 			}
12920 
12921 			if(backgroundIsNotTransparent) {
12922 				swapColors();
12923 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
12924 				swapColors();
12925 			}
12926 			if(foregroundIsNotTransparent) {
12927 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
12928 			}
12929 		}
12930 	}
12931 
12932 	/* XRender { */
12933 
12934 	struct XRenderColor {
12935 		ushort red;
12936 		ushort green;
12937 		ushort blue;
12938 		ushort alpha;
12939 	}
12940 
12941 	alias Picture = XID;
12942 	alias PictFormat = XID;
12943 
12944 	struct XGlyphInfo {
12945 		ushort width;
12946 		ushort height;
12947 		short x;
12948 		short y;
12949 		short xOff;
12950 		short yOff;
12951 	}
12952 
12953 struct XRenderDirectFormat {
12954     short   red;
12955     short   redMask;
12956     short   green;
12957     short   greenMask;
12958     short   blue;
12959     short   blueMask;
12960     short   alpha;
12961     short   alphaMask;
12962 }
12963 
12964 struct XRenderPictFormat {
12965     PictFormat		id;
12966     int			type;
12967     int			depth;
12968     XRenderDirectFormat	direct;
12969     Colormap		colormap;
12970 }
12971 
12972 enum PictFormatID	=   (1 << 0);
12973 enum PictFormatType	=   (1 << 1);
12974 enum PictFormatDepth	=   (1 << 2);
12975 enum PictFormatRed	=   (1 << 3);
12976 enum PictFormatRedMask  =(1 << 4);
12977 enum PictFormatGreen	=   (1 << 5);
12978 enum PictFormatGreenMask=(1 << 6);
12979 enum PictFormatBlue	=   (1 << 7);
12980 enum PictFormatBlueMask =(1 << 8);
12981 enum PictFormatAlpha	=   (1 << 9);
12982 enum PictFormatAlphaMask=(1 << 10);
12983 enum PictFormatColormap =(1 << 11);
12984 
12985 struct XRenderPictureAttributes {
12986 	int 		repeat;
12987 	Picture		alpha_map;
12988 	int			alpha_x_origin;
12989 	int			alpha_y_origin;
12990 	int			clip_x_origin;
12991 	int			clip_y_origin;
12992 	Pixmap		clip_mask;
12993 	Bool		graphics_exposures;
12994 	int			subwindow_mode;
12995 	int			poly_edge;
12996 	int			poly_mode;
12997 	Atom		dither;
12998 	Bool		component_alpha;
12999 }
13000 
13001 alias int XFixed;
13002 
13003 struct XPointFixed {
13004     XFixed  x, y;
13005 }
13006 
13007 struct XCircle {
13008     XFixed x;
13009     XFixed y;
13010     XFixed radius;
13011 }
13012 
13013 struct XTransform {
13014     XFixed[3][3]  matrix;
13015 }
13016 
13017 struct XFilters {
13018     int	    nfilter;
13019     char    **filter;
13020     int	    nalias;
13021     short   *alias_;
13022 }
13023 
13024 struct XIndexValue {
13025     c_ulong    pixel;
13026     ushort   red, green, blue, alpha;
13027 }
13028 
13029 struct XAnimCursor {
13030     Cursor	    cursor;
13031     c_ulong   delay;
13032 }
13033 
13034 struct XLinearGradient {
13035     XPointFixed p1;
13036     XPointFixed p2;
13037 }
13038 
13039 struct XRadialGradient {
13040     XCircle inner;
13041     XCircle outer;
13042 }
13043 
13044 struct XConicalGradient {
13045     XPointFixed center;
13046     XFixed angle; /* in degrees */
13047 }
13048 
13049 enum PictStandardARGB32  = 0;
13050 enum PictStandardRGB24   = 1;
13051 enum PictStandardA8	 =  2;
13052 enum PictStandardA4	 =  3;
13053 enum PictStandardA1	 =  4;
13054 enum PictStandardNUM	 =  5;
13055 
13056 interface XRender {
13057 extern(C) @nogc:
13058 
13059 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13060 
13061 	Status XRenderQueryVersion (Display *dpy,
13062 			int     *major_versionp,
13063 			int     *minor_versionp);
13064 
13065 	Status XRenderQueryFormats (Display *dpy);
13066 
13067 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13068 
13069 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13070 
13071 	XRenderPictFormat *
13072 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13073 
13074 	XRenderPictFormat *
13075 		XRenderFindFormat (Display			*dpy,
13076 				c_ulong		mask,
13077 				const XRenderPictFormat	*templ,
13078 				int				count);
13079 	XRenderPictFormat *
13080 		XRenderFindStandardFormat (Display		*dpy,
13081 				int			format);
13082 
13083 	XIndexValue *
13084 		XRenderQueryPictIndexValues(Display			*dpy,
13085 				const XRenderPictFormat	*format,
13086 				int				*num);
13087 
13088 	Picture XRenderCreatePicture(
13089 		Display *dpy,
13090 		Drawable drawable,
13091 		const XRenderPictFormat *format,
13092 		c_ulong valuemask,
13093 		const XRenderPictureAttributes *attributes);
13094 
13095 	void XRenderChangePicture (Display				*dpy,
13096 				Picture				picture,
13097 				c_ulong			valuemask,
13098 				const XRenderPictureAttributes  *attributes);
13099 
13100 	void
13101 		XRenderSetPictureClipRectangles (Display	    *dpy,
13102 				Picture	    picture,
13103 				int		    xOrigin,
13104 				int		    yOrigin,
13105 				const XRectangle *rects,
13106 				int		    n);
13107 
13108 	void
13109 		XRenderSetPictureClipRegion (Display	    *dpy,
13110 				Picture	    picture,
13111 				Region	    r);
13112 
13113 	void
13114 		XRenderSetPictureTransform (Display	    *dpy,
13115 				Picture	    picture,
13116 				XTransform	    *transform);
13117 
13118 	void
13119 		XRenderFreePicture (Display                   *dpy,
13120 				Picture                   picture);
13121 
13122 	void
13123 		XRenderComposite (Display   *dpy,
13124 				int	    op,
13125 				Picture   src,
13126 				Picture   mask,
13127 				Picture   dst,
13128 				int	    src_x,
13129 				int	    src_y,
13130 				int	    mask_x,
13131 				int	    mask_y,
13132 				int	    dst_x,
13133 				int	    dst_y,
13134 				uint	width,
13135 				uint	height);
13136 
13137 
13138 	Picture XRenderCreateSolidFill (Display *dpy,
13139 			const XRenderColor *color);
13140 
13141 	Picture XRenderCreateLinearGradient (Display *dpy,
13142 			const XLinearGradient *gradient,
13143 			const XFixed *stops,
13144 			const XRenderColor *colors,
13145 			int nstops);
13146 
13147 	Picture XRenderCreateRadialGradient (Display *dpy,
13148 			const XRadialGradient *gradient,
13149 			const XFixed *stops,
13150 			const XRenderColor *colors,
13151 			int nstops);
13152 
13153 	Picture XRenderCreateConicalGradient (Display *dpy,
13154 			const XConicalGradient *gradient,
13155 			const XFixed *stops,
13156 			const XRenderColor *colors,
13157 			int nstops);
13158 
13159 
13160 
13161 	Cursor
13162 		XRenderCreateCursor (Display	    *dpy,
13163 				Picture	    source,
13164 				uint   x,
13165 				uint   y);
13166 
13167 	XFilters *
13168 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13169 
13170 	void
13171 		XRenderSetPictureFilter (Display    *dpy,
13172 				Picture    picture,
13173 				const char *filter,
13174 				XFixed	    *params,
13175 				int	    nparams);
13176 
13177 	Cursor
13178 		XRenderCreateAnimCursor (Display	*dpy,
13179 				int		ncursor,
13180 				XAnimCursor	*cursors);
13181 }
13182 
13183 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13184 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13185 
13186 	/* XRender } */
13187 
13188 	/* Xrandr { */
13189 
13190 struct XRRMonitorInfo {
13191     Atom name;
13192     Bool primary;
13193     Bool automatic;
13194     int noutput;
13195     int x;
13196     int y;
13197     int width;
13198     int height;
13199     int mwidth;
13200     int mheight;
13201     /*RROutput*/ void *outputs;
13202 }
13203 
13204 struct XRRScreenChangeNotifyEvent {
13205     int type;                   /* event base */
13206     c_ulong serial;       /* # of last request processed by server */
13207     Bool send_event;            /* true if this came from a SendEvent request */
13208     Display *display;           /* Display the event was read from */
13209     Window window;              /* window which selected for this event */
13210     Window root;                /* Root window for changed screen */
13211     Time timestamp;             /* when the screen change occurred */
13212     Time config_timestamp;      /* when the last configuration change */
13213     ushort/*SizeID*/ size_index;
13214     ushort/*SubpixelOrder*/ subpixel_order;
13215     ushort/*Rotation*/ rotation;
13216     int width;
13217     int height;
13218     int mwidth;
13219     int mheight;
13220 }
13221 
13222 enum RRScreenChangeNotify = 0;
13223 
13224 enum RRScreenChangeNotifyMask = 1;
13225 
13226 __gshared int xrrEventBase = -1;
13227 
13228 
13229 interface XRandr {
13230 extern(C) @nogc:
13231 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13232 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13233 
13234 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13235 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13236 
13237 	void XRRSelectInput(Display *dpy, Window window, int mask);
13238 }
13239 
13240 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13241 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13242 	/* Xrandr } */
13243 
13244 	/* Xft { */
13245 
13246 	// actually freetype
13247 	alias void FT_Face;
13248 
13249 	// actually fontconfig
13250 	private alias FcBool = int;
13251 	alias void FcCharSet;
13252 	alias void FcPattern;
13253 	alias void FcResult;
13254 	enum FcEndian { FcEndianBig, FcEndianLittle }
13255 	struct FcFontSet {
13256 		int nfont;
13257 		int sfont;
13258 		FcPattern** fonts;
13259 	}
13260 
13261 	// actually XRegion
13262 	struct BOX {
13263 		short x1, x2, y1, y2;
13264 	}
13265 	struct _XRegion {
13266 		c_long size;
13267 		c_long numRects;
13268 		BOX* rects;
13269 		BOX extents;
13270 	}
13271 
13272 	alias Region = _XRegion*;
13273 
13274 	// ok actually Xft
13275 
13276 	struct XftFontInfo;
13277 
13278 	struct XftFont {
13279 		int         ascent;
13280 		int         descent;
13281 		int         height;
13282 		int         max_advance_width;
13283 		FcCharSet*  charset;
13284 		FcPattern*  pattern;
13285 	}
13286 
13287 	struct XftDraw;
13288 
13289 	struct XftColor {
13290 		c_ulong pixel;
13291 		XRenderColor color;
13292 	}
13293 
13294 	struct XftCharSpec {
13295 		dchar           ucs4;
13296 		short           x;
13297 		short           y;
13298 	}
13299 
13300 	struct XftCharFontSpec {
13301 		XftFont         *font;
13302 		dchar           ucs4;
13303 		short           x;
13304 		short           y;
13305 	}
13306 
13307 	struct XftGlyphSpec {
13308 		uint            glyph;
13309 		short           x;
13310 		short           y;
13311 	}
13312 
13313 	struct XftGlyphFontSpec {
13314 		XftFont         *font;
13315 		uint            glyph;
13316 		short           x;
13317 		short           y;
13318 	}
13319 
13320 	interface Xft {
13321 	extern(C) @nogc pure:
13322 
13323 	Bool XftColorAllocName (Display  *dpy,
13324 				const Visual   *visual,
13325 				Colormap cmap,
13326 				const char     *name,
13327 				XftColor *result);
13328 
13329 	Bool XftColorAllocValue (Display         *dpy,
13330 				Visual          *visual,
13331 				Colormap        cmap,
13332 				const XRenderColor    *color,
13333 				XftColor        *result);
13334 
13335 	void XftColorFree (Display   *dpy,
13336 				Visual    *visual,
13337 				Colormap  cmap,
13338 				XftColor  *color);
13339 
13340 	Bool XftDefaultHasRender (Display *dpy);
13341 
13342 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13343 
13344 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13345 
13346 	XftDraw * XftDrawCreate (Display   *dpy,
13347 		       Drawable  drawable,
13348 		       Visual    *visual,
13349 		       Colormap  colormap);
13350 
13351 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13352 			     Pixmap   bitmap);
13353 
13354 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13355 			    Pixmap  pixmap,
13356 			    int     depth);
13357 
13358 	void XftDrawChange (XftDraw  *draw,
13359 		       Drawable drawable);
13360 
13361 	Display * XftDrawDisplay (XftDraw *draw);
13362 
13363 	Drawable XftDrawDrawable (XftDraw *draw);
13364 
13365 	Colormap XftDrawColormap (XftDraw *draw);
13366 
13367 	Visual * XftDrawVisual (XftDraw *draw);
13368 
13369 	void XftDrawDestroy (XftDraw *draw);
13370 
13371 	Picture XftDrawPicture (XftDraw *draw);
13372 
13373 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13374 
13375 	void XftDrawGlyphs (XftDraw          *draw,
13376 				const XftColor *color,
13377 				XftFont          *pub,
13378 				int              x,
13379 				int              y,
13380 				const uint  *glyphs,
13381 				int              nglyphs);
13382 
13383 	void XftDrawString8 (XftDraw             *draw,
13384 				const XftColor    *color,
13385 				XftFont             *pub,
13386 				int                 x,
13387 				int                 y,
13388 				const char     *string,
13389 				int                 len);
13390 
13391 	void XftDrawString16 (XftDraw            *draw,
13392 				const XftColor   *color,
13393 				XftFont            *pub,
13394 				int                x,
13395 				int                y,
13396 				const wchar   *string,
13397 				int                len);
13398 
13399 	void XftDrawString32 (XftDraw            *draw,
13400 				const XftColor   *color,
13401 				XftFont            *pub,
13402 				int                x,
13403 				int                y,
13404 				const dchar   *string,
13405 				int                len);
13406 
13407 	void XftDrawStringUtf8 (XftDraw          *draw,
13408 				const XftColor *color,
13409 				XftFont          *pub,
13410 				int              x,
13411 				int              y,
13412 				const char  *string,
13413 				int              len);
13414 	void XftDrawStringUtf16 (XftDraw             *draw,
13415 				const XftColor    *color,
13416 				XftFont             *pub,
13417 				int                 x,
13418 				int                 y,
13419 				const char     *string,
13420 				FcEndian            endian,
13421 				int                 len);
13422 
13423 	void XftDrawCharSpec (XftDraw                *draw,
13424 				const XftColor       *color,
13425 				XftFont                *pub,
13426 				const XftCharSpec    *chars,
13427 				int                    len);
13428 
13429 	void XftDrawCharFontSpec (XftDraw                    *draw,
13430 				const XftColor           *color,
13431 				const XftCharFontSpec    *chars,
13432 				int                        len);
13433 
13434 	void XftDrawGlyphSpec (XftDraw               *draw,
13435 				const XftColor      *color,
13436 				XftFont               *pub,
13437 				const XftGlyphSpec  *glyphs,
13438 				int                   len);
13439 
13440 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13441 				const XftColor          *color,
13442 				const XftGlyphFontSpec  *glyphs,
13443 				int                       len);
13444 
13445 	void XftDrawRect (XftDraw            *draw,
13446 				const XftColor   *color,
13447 				int                x,
13448 				int                y,
13449 				uint       width,
13450 				uint       height);
13451 
13452 	Bool XftDrawSetClip (XftDraw     *draw,
13453 				Region      r);
13454 
13455 
13456 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13457 				int                   xOrigin,
13458 				int                   yOrigin,
13459 				const XRectangle    *rects,
13460 				int                   n);
13461 
13462 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13463 				int        mode);
13464 
13465 	void XftGlyphExtents (Display            *dpy,
13466 				XftFont            *pub,
13467 				const uint    *glyphs,
13468 				int                nglyphs,
13469 				XGlyphInfo         *extents);
13470 
13471 	void XftTextExtents8 (Display            *dpy,
13472 				XftFont            *pub,
13473 				const char    *string,
13474 				int                len,
13475 				XGlyphInfo         *extents);
13476 
13477 	void XftTextExtents16 (Display           *dpy,
13478 				XftFont           *pub,
13479 				const wchar  *string,
13480 				int               len,
13481 				XGlyphInfo        *extents);
13482 
13483 	void XftTextExtents32 (Display           *dpy,
13484 				XftFont           *pub,
13485 				const dchar  *string,
13486 				int               len,
13487 				XGlyphInfo        *extents);
13488 
13489 	void XftTextExtentsUtf8 (Display         *dpy,
13490 				XftFont         *pub,
13491 				const char *string,
13492 				int             len,
13493 				XGlyphInfo      *extents);
13494 
13495 	void XftTextExtentsUtf16 (Display            *dpy,
13496 				XftFont            *pub,
13497 				const char    *string,
13498 				FcEndian           endian,
13499 				int                len,
13500 				XGlyphInfo         *extents);
13501 
13502 	FcPattern * XftFontMatch (Display           *dpy,
13503 				int               screen,
13504 				const FcPattern *pattern,
13505 				FcResult          *result);
13506 
13507 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13508 
13509 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13510 
13511 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13512 
13513 	FT_Face XftLockFace (XftFont *pub);
13514 
13515 	void XftUnlockFace (XftFont *pub);
13516 
13517 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13518 
13519 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13520 
13521 	dchar XftFontInfoHash (const XftFontInfo *fi);
13522 
13523 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13524 
13525 	XftFont * XftFontOpenInfo (Display        *dpy,
13526 				FcPattern      *pattern,
13527 				XftFontInfo    *fi);
13528 
13529 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13530 
13531 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13532 
13533 	void XftFontClose (Display *dpy, XftFont *pub);
13534 
13535 	FcBool XftInitFtLibrary();
13536 	void XftFontLoadGlyphs (Display          *dpy,
13537 				XftFont          *pub,
13538 				FcBool           need_bitmaps,
13539 				const uint  *glyphs,
13540 				int              nglyph);
13541 
13542 	void XftFontUnloadGlyphs (Display            *dpy,
13543 				XftFont            *pub,
13544 				const uint    *glyphs,
13545 				int                nglyph);
13546 
13547 	FcBool XftFontCheckGlyph (Display  *dpy,
13548 				XftFont  *pub,
13549 				FcBool   need_bitmaps,
13550 				uint  glyph,
13551 				uint  *missing,
13552 				int      *nmissing);
13553 
13554 	FcBool XftCharExists (Display      *dpy,
13555 				XftFont      *pub,
13556 				dchar    ucs4);
13557 
13558 	uint XftCharIndex (Display       *dpy,
13559 				XftFont       *pub,
13560 				dchar      ucs4);
13561 	FcBool XftInit (const char *config);
13562 
13563 	int XftGetVersion ();
13564 
13565 	FcFontSet * XftListFonts (Display   *dpy,
13566 				int       screen,
13567 				...);
13568 
13569 	FcPattern *XftNameParse (const char *name);
13570 
13571 	void XftGlyphRender (Display         *dpy,
13572 				int             op,
13573 				Picture         src,
13574 				XftFont         *pub,
13575 				Picture         dst,
13576 				int             srcx,
13577 				int             srcy,
13578 				int             x,
13579 				int             y,
13580 				const uint *glyphs,
13581 				int             nglyphs);
13582 
13583 	void XftGlyphSpecRender (Display                 *dpy,
13584 				int                     op,
13585 				Picture                 src,
13586 				XftFont                 *pub,
13587 				Picture                 dst,
13588 				int                     srcx,
13589 				int                     srcy,
13590 				const XftGlyphSpec    *glyphs,
13591 				int                     nglyphs);
13592 
13593 	void XftCharSpecRender (Display              *dpy,
13594 				int                  op,
13595 				Picture              src,
13596 				XftFont              *pub,
13597 				Picture              dst,
13598 				int                  srcx,
13599 				int                  srcy,
13600 				const XftCharSpec  *chars,
13601 				int                  len);
13602 	void XftGlyphFontSpecRender (Display                     *dpy,
13603 				int                         op,
13604 				Picture                     src,
13605 				Picture                     dst,
13606 				int                         srcx,
13607 				int                         srcy,
13608 				const XftGlyphFontSpec    *glyphs,
13609 				int                         nglyphs);
13610 
13611 	void XftCharFontSpecRender (Display                  *dpy,
13612 				int                      op,
13613 				Picture                  src,
13614 				Picture                  dst,
13615 				int                      srcx,
13616 				int                      srcy,
13617 				const XftCharFontSpec  *chars,
13618 				int                      len);
13619 
13620 	void XftTextRender8 (Display         *dpy,
13621 				int             op,
13622 				Picture         src,
13623 				XftFont         *pub,
13624 				Picture         dst,
13625 				int             srcx,
13626 				int             srcy,
13627 				int             x,
13628 				int             y,
13629 				const char *string,
13630 				int             len);
13631 	void XftTextRender16 (Display            *dpy,
13632 				int                op,
13633 				Picture            src,
13634 				XftFont            *pub,
13635 				Picture            dst,
13636 				int                srcx,
13637 				int                srcy,
13638 				int                x,
13639 				int                y,
13640 				const wchar   *string,
13641 				int                len);
13642 
13643 	void XftTextRender16BE (Display          *dpy,
13644 				int              op,
13645 				Picture          src,
13646 				XftFont          *pub,
13647 				Picture          dst,
13648 				int              srcx,
13649 				int              srcy,
13650 				int              x,
13651 				int              y,
13652 				const char  *string,
13653 				int              len);
13654 
13655 	void XftTextRender16LE (Display          *dpy,
13656 				int              op,
13657 				Picture          src,
13658 				XftFont          *pub,
13659 				Picture          dst,
13660 				int              srcx,
13661 				int              srcy,
13662 				int              x,
13663 				int              y,
13664 				const char  *string,
13665 				int              len);
13666 
13667 	void XftTextRender32 (Display            *dpy,
13668 				int                op,
13669 				Picture            src,
13670 				XftFont            *pub,
13671 				Picture            dst,
13672 				int                srcx,
13673 				int                srcy,
13674 				int                x,
13675 				int                y,
13676 				const dchar   *string,
13677 				int                len);
13678 
13679 	void XftTextRender32BE (Display          *dpy,
13680 				int              op,
13681 				Picture          src,
13682 				XftFont          *pub,
13683 				Picture          dst,
13684 				int              srcx,
13685 				int              srcy,
13686 				int              x,
13687 				int              y,
13688 				const char  *string,
13689 				int              len);
13690 
13691 	void XftTextRender32LE (Display          *dpy,
13692 				int              op,
13693 				Picture          src,
13694 				XftFont          *pub,
13695 				Picture          dst,
13696 				int              srcx,
13697 				int              srcy,
13698 				int              x,
13699 				int              y,
13700 				const char  *string,
13701 				int              len);
13702 
13703 	void XftTextRenderUtf8 (Display          *dpy,
13704 				int              op,
13705 				Picture          src,
13706 				XftFont          *pub,
13707 				Picture          dst,
13708 				int              srcx,
13709 				int              srcy,
13710 				int              x,
13711 				int              y,
13712 				const char  *string,
13713 				int              len);
13714 
13715 	void XftTextRenderUtf16 (Display         *dpy,
13716 				int             op,
13717 				Picture         src,
13718 				XftFont         *pub,
13719 				Picture         dst,
13720 				int             srcx,
13721 				int             srcy,
13722 				int             x,
13723 				int             y,
13724 				const char *string,
13725 				FcEndian        endian,
13726 				int             len);
13727 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13728 
13729 	}
13730 
13731 	interface FontConfig {
13732 	extern(C) @nogc pure:
13733 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13734 		void FcFontSetDestroy(FcFontSet*);
13735 		char* FcNameUnparse(const FcPattern *);
13736 	}
13737 
13738 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13739 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13740 
13741 
13742 	/* Xft } */
13743 
13744 	class XDisconnectException : Exception {
13745 		bool userRequested;
13746 		this(bool userRequested = true) {
13747 			this.userRequested = userRequested;
13748 			super("X disconnected");
13749 		}
13750 	}
13751 
13752 	/++
13753 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
13754 
13755 		Please note that it returns
13756 	+/
13757 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
13758 
13759 		static XErrorEvent[] errorBuffer;
13760 		
13761 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
13762 			errorBuffer ~= *evt;
13763 			return 0;
13764 		}
13765 
13766 		auto savedErrorHandler = XSetErrorHandler(&handler);
13767 
13768 		try {
13769 			dg();
13770 		} finally {
13771 			XSync(XDisplayConnection.get, 0/*False*/);
13772 			XSetErrorHandler(savedErrorHandler);
13773 		}
13774 
13775 		auto bfr = errorBuffer;
13776 		errorBuffer = null;
13777 
13778 		return bfr;
13779 	}
13780 
13781 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
13782 	class XDisplayConnection {
13783 		private __gshared Display* display;
13784 		private __gshared XIM xim;
13785 		private __gshared char* displayName;
13786 
13787 		private __gshared int connectionSequence_;
13788 		private __gshared bool isLocal_;
13789 
13790 		/// use this for lazy caching when reconnection
13791 		static int connectionSequenceNumber() { return connectionSequence_; }
13792 
13793 		/++
13794 			Guesses if the connection appears to be local.
13795 
13796 			History:
13797 				Added June 3, 2021
13798 		+/
13799 		static @property bool isLocal() nothrow @trusted @nogc {
13800 			return isLocal_;
13801 		}
13802 
13803 		/// Attempts recreation of state, may require application assistance
13804 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
13805 		/// then call this, and if successful, reenter the loop.
13806 		static void discardAndRecreate(string newDisplayString = null) {
13807 			if(insideXEventLoop)
13808 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
13809 
13810 			// 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
13811 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
13812 
13813 			foreach(handle; chnenhm) {
13814 				handle.discardConnectionState();
13815 			}
13816 
13817 			discardState();
13818 
13819 			if(newDisplayString !is null)
13820 				setDisplayName(newDisplayString);
13821 
13822 			auto display = get();
13823 
13824 			foreach(handle; chnenhm) {
13825 				handle.recreateAfterDisconnect();
13826 			}
13827 		}
13828 
13829 		private __gshared EventMask rootEventMask;
13830 
13831 		/++
13832 			Requests the specified input from the root window on the connection, in addition to any other request.
13833 
13834 			
13835 			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.
13836 
13837 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
13838 		+/
13839 		static void addRootInput(EventMask mask) {
13840 			auto old = rootEventMask;
13841 			rootEventMask |= mask;
13842 			get(); // to ensure display connected
13843 			if(display !is null && rootEventMask != old)
13844 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
13845 		}
13846 
13847 		static void discardState() {
13848 			freeImages();
13849 
13850 			foreach(atomPtr; interredAtoms)
13851 				*atomPtr = 0;
13852 			interredAtoms = null;
13853 			interredAtoms.assumeSafeAppend();
13854 
13855 			ScreenPainterImplementation.fontAttempted = false;
13856 			ScreenPainterImplementation.defaultfont = null;
13857 			ScreenPainterImplementation.defaultfontset = null;
13858 
13859 			Image.impl.xshmQueryCompleted = false;
13860 			Image.impl._xshmAvailable = false;
13861 
13862 			SimpleWindow.nativeMapping = null;
13863 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
13864 			// GlobalHotkeyManager
13865 
13866 			display = null;
13867 			xim = null;
13868 		}
13869 
13870 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
13871 		private static void createXIM () {
13872 			import core.stdc.locale : setlocale, LC_ALL;
13873 			import core.stdc.stdio : stderr, fprintf;
13874 			import core.stdc.stdlib : free;
13875 			import core.stdc.string : strdup;
13876 
13877 			static immutable string[3] mtry = [ null, "@im=local", "@im=" ];
13878 
13879 			auto olocale = strdup(setlocale(LC_ALL, null));
13880 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
13881 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
13882 
13883 			//fprintf(stderr, "opening IM...\n");
13884 			foreach (string s; mtry) {
13885 				if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
13886 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
13887 			}
13888 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
13889 		}
13890 
13891 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
13892 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
13893 		static struct ImgList {
13894 			size_t img; // class; hide it from GC
13895 			ImgList* next;
13896 		}
13897 
13898 		static __gshared ImgList* imglist = null;
13899 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
13900 
13901 		static void registerImage (Image img) {
13902 			if (!imglistLocked && img !is null) {
13903 				import core.stdc.stdlib : malloc;
13904 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
13905 				assert(it !is null); // do proper checks
13906 				it.img = cast(size_t)cast(void*)img;
13907 				it.next = imglist;
13908 				imglist = it;
13909 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
13910 			}
13911 		}
13912 
13913 		static void unregisterImage (Image img) {
13914 			if (!imglistLocked && img !is null) {
13915 				import core.stdc.stdlib : free;
13916 				ImgList* prev = null;
13917 				ImgList* cur = imglist;
13918 				while (cur !is null) {
13919 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
13920 					prev = cur;
13921 					cur = cur.next;
13922 				}
13923 				if (cur !is null) {
13924 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
13925 					free(cur);
13926 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
13927 				} else {
13928 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
13929 				}
13930 			}
13931 		}
13932 
13933 		static void freeImages () { // needed for discardAndRecreate
13934 			imglistLocked = true;
13935 			scope(exit) imglistLocked = false;
13936 			ImgList* cur = imglist;
13937 			ImgList* next = null;
13938 			while (cur !is null) {
13939 				import core.stdc.stdlib : free;
13940 				next = cur.next;
13941 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
13942 				(cast(Image)cast(void*)cur.img).dispose();
13943 				free(cur);
13944 				cur = next;
13945 			}
13946 			imglist = null;
13947 		}
13948 
13949 		/// can be used to override normal handling of display name
13950 		/// from environment and/or command line
13951 		static setDisplayName(string newDisplayName) {
13952 			displayName = cast(char*) (newDisplayName ~ '\0');
13953 		}
13954 
13955 		/// resets to the default display string
13956 		static resetDisplayName() {
13957 			displayName = null;
13958 		}
13959 
13960 		///
13961 		static Display* get() {
13962 			if(display is null) {
13963 				if(!librariesSuccessfullyLoaded)
13964 					throw new Exception("Unable to load X11 client libraries");
13965 				display = XOpenDisplay(displayName);
13966 
13967 				isLocal_ = false;
13968 
13969 				connectionSequence_++;
13970 				if(display is null)
13971 					throw new Exception("Unable to open X display");
13972 
13973 				auto str = display.display_name;
13974 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
13975 				// and otherwise it probably isn't
13976 				if(str is null || (str[0] != ':' && str[0] != '/'))
13977 					isLocal_ = false;
13978 				else
13979 					isLocal_ = true;
13980 
13981 				//XSetErrorHandler(&adrlogger);
13982 				//XSynchronize(display, true);
13983 
13984 
13985 				XSetIOErrorHandler(&x11ioerrCB);
13986 				Bool sup;
13987 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
13988 				createXIM();
13989 				version(with_eventloop) {
13990 					import arsd.eventloop;
13991 					addFileEventListeners(display.fd, &eventListener, null, null);
13992 				}
13993 			}
13994 
13995 			return display;
13996 		}
13997 
13998 		extern(C)
13999 		static int x11ioerrCB(Display* dpy) {
14000 			throw new XDisconnectException(false);
14001 		}
14002 
14003 		version(with_eventloop) {
14004 			import arsd.eventloop;
14005 			static void eventListener(OsFileHandle fd) {
14006 				//this.mtLock();
14007 				//scope(exit) this.mtUnlock();
14008 				while(XPending(display))
14009 					doXNextEvent(display);
14010 			}
14011 		}
14012 
14013 		// close connection on program exit -- we need this to properly free all images
14014 		static ~this () {
14015 			// the gui thread must clean up after itself or else Xlib might deadlock
14016 			// using this flag on any thread destruction is the easiest way i know of
14017 			// (shared static this is run by the LAST thread to exit, which may not be
14018 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14019 			if(thisIsGuiThread)
14020 				close();
14021 		}
14022 
14023 		///
14024 		static void close() {
14025 			if(display is null)
14026 				return;
14027 
14028 			version(with_eventloop) {
14029 				import arsd.eventloop;
14030 				removeFileEventListeners(display.fd);
14031 			}
14032 
14033 			// now remove all registered images to prevent shared memory leaks
14034 			freeImages();
14035 
14036 			// tbh I don't know why it is doing this but like if this happens to run
14037 			// from the other thread there's frequent hanging inside here.
14038 			if(thisIsGuiThread)
14039 				XCloseDisplay(display);
14040 			display = null;
14041 		}
14042 	}
14043 
14044 	mixin template NativeImageImplementation() {
14045 		XImage* handle;
14046 		ubyte* rawData;
14047 
14048 		XShmSegmentInfo shminfo;
14049 
14050 		__gshared bool xshmQueryCompleted;
14051 		__gshared bool _xshmAvailable;
14052 		public static @property bool xshmAvailable() {
14053 			if(!xshmQueryCompleted) {
14054 				int i1, i2, i3;
14055 				xshmQueryCompleted = true;
14056 
14057 				if(!XDisplayConnection.isLocal)
14058 					_xshmAvailable = false;
14059 				else
14060 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14061 			}
14062 			return _xshmAvailable;
14063 		}
14064 
14065 		bool usingXshm;
14066 	final:
14067 
14068 		private __gshared bool xshmfailed;
14069 
14070 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14071 			auto display = XDisplayConnection.get();
14072 			assert(display !is null);
14073 			auto screen = DefaultScreen(display);
14074 
14075 			// it will only use shared memory for somewhat largish images,
14076 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14077 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14078 
14079 
14080 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14081 				// the actual use still fails. For example, if the program is in a container and permission denied
14082 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14083 				//
14084 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14085 
14086 
14087 				// synchronize so preexisting buffers are clear
14088 				XSync(display, false);
14089 				xshmfailed = false;
14090 
14091 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14092 
14093 
14094 				usingXshm = true;
14095 				handle = XShmCreateImage(
14096 					display,
14097 					DefaultVisual(display, screen),
14098 					enableAlpha ? 32: 24,
14099 					ImageFormat.ZPixmap,
14100 					null,
14101 					&shminfo,
14102 					width, height);
14103 				if(handle is null)
14104 					goto abortXshm1;
14105 
14106 				if(handle.bytes_per_line != 4 * width)
14107 					goto abortXshm2;
14108 
14109 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14110 				if(shminfo.shmid < 0)
14111 					goto abortXshm3;
14112 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14113 				if(rawData == cast(ubyte*) -1)
14114 					goto abortXshm4;
14115 				shminfo.readOnly = 0;
14116 				XShmAttach(display, &shminfo);
14117 
14118 				// and now to the final error check to ensure it actually worked.
14119 				XSync(display, false);
14120 				if(xshmfailed)
14121 					goto abortXshm5;
14122 
14123 				XSetErrorHandler(oldErrorHandler);
14124 
14125 				XDisplayConnection.registerImage(this);
14126 				// if I don't flush here there's a chance the dtor will run before the
14127 				// ctor and lead to a bad value X error. While this hurts the efficiency
14128 				// it is local anyway so prolly better to keep it simple
14129 				XFlush(display);
14130 
14131 				return;
14132 
14133 				abortXshm5:
14134 					shmdt(shminfo.shmaddr);
14135 					rawData = null;
14136 
14137 				abortXshm4:
14138 					shmctl(shminfo.shmid, IPC_RMID, null);
14139 
14140 				abortXshm3:
14141 					// nothing needed, the shmget failed so there's nothing to free
14142 
14143 				abortXshm2:
14144 					XDestroyImage(handle);
14145 					handle = null;
14146 
14147 				abortXshm1:
14148 					XSetErrorHandler(oldErrorHandler);
14149 					usingXshm = false;
14150 					handle = null;
14151 
14152 					shminfo = typeof(shminfo).init;
14153 
14154 					_xshmAvailable = false; // don't try again in the future
14155 
14156 					//import std.stdio; writeln("fallingback");
14157 
14158 					goto fallback;
14159 
14160 			} else {
14161 				fallback:
14162 
14163 				if (forcexshm) throw new Exception("can't create XShm Image");
14164 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14165 				import core.stdc.stdlib : malloc;
14166 				rawData = cast(ubyte*) malloc(width * height * 4);
14167 
14168 				handle = XCreateImage(
14169 					display,
14170 					DefaultVisual(display, screen),
14171 					enableAlpha ? 32 : 24, // bpp
14172 					ImageFormat.ZPixmap,
14173 					0, // offset
14174 					rawData,
14175 					width, height,
14176 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14177 			}
14178 		}
14179 
14180 		void dispose() {
14181 			// note: this calls free(rawData) for us
14182 			if(handle) {
14183 				if (usingXshm) {
14184 					XDisplayConnection.unregisterImage(this);
14185 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14186 				}
14187 				XDestroyImage(handle);
14188 				if(usingXshm) {
14189 					shmdt(shminfo.shmaddr);
14190 					shmctl(shminfo.shmid, IPC_RMID, null);
14191 				}
14192 				handle = null;
14193 			}
14194 		}
14195 
14196 		Color getPixel(int x, int y) {
14197 			auto offset = (y * width + x) * 4;
14198 			Color c;
14199 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14200 			c.b = rawData[offset + 0];
14201 			c.g = rawData[offset + 1];
14202 			c.r = rawData[offset + 2];
14203 			if(enableAlpha)
14204 				c.unPremultiply;
14205 			return c;
14206 		}
14207 
14208 		void setPixel(int x, int y, Color c) {
14209 			if(enableAlpha)
14210 				c.premultiply();
14211 			auto offset = (y * width + x) * 4;
14212 			rawData[offset + 0] = c.b;
14213 			rawData[offset + 1] = c.g;
14214 			rawData[offset + 2] = c.r;
14215 			if(enableAlpha)
14216 				rawData[offset + 3] = c.a;
14217 		}
14218 
14219 		void convertToRgbaBytes(ubyte[] where) {
14220 			assert(where.length == this.width * this.height * 4);
14221 
14222 			// if rawData had a length....
14223 			//assert(rawData.length == where.length);
14224 			for(int idx = 0; idx < where.length; idx += 4) {
14225 				where[idx + 0] = rawData[idx + 2]; // r
14226 				where[idx + 1] = rawData[idx + 1]; // g
14227 				where[idx + 2] = rawData[idx + 0]; // b
14228 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14229 
14230 				if(enableAlpha)
14231 					unPremultiplyRgba(where[idx .. idx + 4]);
14232 			}
14233 		}
14234 
14235 		void setFromRgbaBytes(in ubyte[] where) {
14236 			assert(where.length == this.width * this.height * 4);
14237 
14238 			// if rawData had a length....
14239 			//assert(rawData.length == where.length);
14240 			for(int idx = 0; idx < where.length; idx += 4) {
14241 				rawData[idx + 2] = where[idx + 0]; // r
14242 				rawData[idx + 1] = where[idx + 1]; // g
14243 				rawData[idx + 0] = where[idx + 2]; // b
14244 				if(enableAlpha) {
14245 					rawData[idx + 3] = where[idx + 3]; // a
14246 					premultiplyBgra(rawData[idx .. idx + 4]);
14247 				}
14248 			}
14249 		}
14250 
14251 	}
14252 
14253 	mixin template NativeSimpleWindowImplementation() {
14254 		GC gc;
14255 		Window window;
14256 		Display* display;
14257 
14258 		Pixmap buffer;
14259 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14260 		XIC xic; // input context
14261 		int curHidden = 0; // counter
14262 		Cursor blankCurPtr = 0;
14263 		int cursorSequenceNumber = 0;
14264 		int warpEventCount = 0; // number of mouse movement events to eat
14265 
14266 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14267 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14268 
14269 		version(without_opengl) {} else
14270 		GLXContext glc;
14271 
14272 		private void fixFixedSize(bool forced=false) (int width, int height) {
14273 			if (forced || this.resizability == Resizability.fixedSize) {
14274 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14275 				XSizeHints sh;
14276 				static if (!forced) {
14277 					c_long spr;
14278 					XGetWMNormalHints(display, window, &sh, &spr);
14279 					sh.flags |= PMaxSize | PMinSize;
14280 				} else {
14281 					sh.flags = PMaxSize | PMinSize;
14282 				}
14283 				sh.min_width = width;
14284 				sh.min_height = height;
14285 				sh.max_width = width;
14286 				sh.max_height = height;
14287 				XSetWMNormalHints(display, window, &sh);
14288 				//XFlush(display);
14289 			}
14290 		}
14291 
14292 		ScreenPainter getPainter(bool manualInvalidations) {
14293 			return ScreenPainter(this, window, manualInvalidations);
14294 		}
14295 
14296 		void move(int x, int y) {
14297 			XMoveWindow(display, window, x, y);
14298 		}
14299 
14300 		void resize(int w, int h) {
14301 			if (w < 1) w = 1;
14302 			if (h < 1) h = 1;
14303 			XResizeWindow(display, window, w, h);
14304 
14305 			// calling this now to avoid waiting for the server to
14306 			// acknowledge the resize; draws without returning to the
14307 			// event loop will thus actually work. the server's event
14308 			// btw might overrule this and resize it again
14309 			recordX11Resize(display, this, w, h);
14310 
14311 			// FIXME: do we need to set this as the opengl context to do the glViewport change?
14312 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
14313 		}
14314 
14315 		void moveResize (int x, int y, int w, int h) {
14316 			if (w < 1) w = 1;
14317 			if (h < 1) h = 1;
14318 			XMoveResizeWindow(display, window, x, y, w, h);
14319 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
14320 		}
14321 
14322 		void hideCursor () {
14323 			if (curHidden++ == 0) {
14324 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14325 					static const(char)[1] cmbmp = 0;
14326 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14327 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14328 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14329 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14330 					XFreePixmap(display, pm);
14331 				}
14332 				XDefineCursor(display, window, blankCurPtr);
14333 			}
14334 		}
14335 
14336 		void showCursor () {
14337 			if (--curHidden == 0) XUndefineCursor(display, window);
14338 		}
14339 
14340 		void warpMouse (int x, int y) {
14341 			// here i will send dummy "ignore next mouse motion" event,
14342 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14343 			// and we don't need to report it to the user (as warping is
14344 			// used when the user needs movement deltas).
14345 			//XClientMessageEvent xclient;
14346 			XEvent e;
14347 			e.xclient.type = EventType.ClientMessage;
14348 			e.xclient.window = window;
14349 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14350 			e.xclient.format = 32;
14351 			e.xclient.data.l[0] = 0;
14352 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14353 			//{ 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]); }
14354 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14355 			// now warp pointer...
14356 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14357 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14358 			// ...and flush
14359 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14360 			XFlush(display);
14361 		}
14362 
14363 		void sendDummyEvent () {
14364 			// here i will send dummy event to ping event queue
14365 			XEvent e;
14366 			e.xclient.type = EventType.ClientMessage;
14367 			e.xclient.window = window;
14368 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14369 			e.xclient.format = 32;
14370 			e.xclient.data.l[0] = 0;
14371 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14372 			XFlush(display);
14373 		}
14374 
14375 		void setTitle(string title) {
14376 			if (title.ptr is null) title = "";
14377 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14378 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14379 			XTextProperty windowName;
14380 			windowName.value = title.ptr;
14381 			windowName.encoding = XA_UTF8; //XA_STRING;
14382 			windowName.format = 8;
14383 			windowName.nitems = cast(uint)title.length;
14384 			XSetWMName(display, window, &windowName);
14385 			char[1024] namebuf = 0;
14386 			auto maxlen = namebuf.length-1;
14387 			if (maxlen > title.length) maxlen = title.length;
14388 			namebuf[0..maxlen] = title[0..maxlen];
14389 			XStoreName(display, window, namebuf.ptr);
14390 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14391 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14392 		}
14393 
14394 		string[] getTitles() {
14395 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14396 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14397 			XTextProperty textProp;
14398 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14399 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14400 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14401 				} else
14402 					return [];
14403 			} else
14404 				return null;
14405 		}
14406 
14407 		string getTitle() {
14408 			auto titles = getTitles();
14409 			return titles.length ? titles[0] : null;
14410 		}
14411 
14412 		void setMinSize (int minwidth, int minheight) {
14413 			import core.stdc.config : c_long;
14414 			if (minwidth < 1) minwidth = 1;
14415 			if (minheight < 1) minheight = 1;
14416 			XSizeHints sh;
14417 			c_long spr;
14418 			XGetWMNormalHints(display, window, &sh, &spr);
14419 			sh.min_width = minwidth;
14420 			sh.min_height = minheight;
14421 			sh.flags |= PMinSize;
14422 			XSetWMNormalHints(display, window, &sh);
14423 			flushGui();
14424 		}
14425 
14426 		void setMaxSize (int maxwidth, int maxheight) {
14427 			import core.stdc.config : c_long;
14428 			if (maxwidth < 1) maxwidth = 1;
14429 			if (maxheight < 1) maxheight = 1;
14430 			XSizeHints sh;
14431 			c_long spr;
14432 			XGetWMNormalHints(display, window, &sh, &spr);
14433 			sh.max_width = maxwidth;
14434 			sh.max_height = maxheight;
14435 			sh.flags |= PMaxSize;
14436 			XSetWMNormalHints(display, window, &sh);
14437 			flushGui();
14438 		}
14439 
14440 		void setResizeGranularity (int granx, int grany) {
14441 			import core.stdc.config : c_long;
14442 			if (granx < 1) granx = 1;
14443 			if (grany < 1) grany = 1;
14444 			XSizeHints sh;
14445 			c_long spr;
14446 			XGetWMNormalHints(display, window, &sh, &spr);
14447 			sh.width_inc = granx;
14448 			sh.height_inc = grany;
14449 			sh.flags |= PResizeInc;
14450 			XSetWMNormalHints(display, window, &sh);
14451 			flushGui();
14452 		}
14453 
14454 		void setOpacity (uint opacity) {
14455 			arch_ulong o = opacity;
14456 			if (opacity == uint.max)
14457 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14458 			else
14459 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14460 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14461 		}
14462 
14463 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14464 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14465 			display = XDisplayConnection.get();
14466 			auto screen = DefaultScreen(display);
14467 
14468 			bool overrideRedirect = false;
14469 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14470 				overrideRedirect = true;
14471 
14472 			version(without_opengl) {}
14473 			else {
14474 				if(opengl == OpenGlOptions.yes) {
14475 					GLXFBConfig fbconf = null;
14476 					XVisualInfo* vi = null;
14477 					bool useLegacy = false;
14478 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14479 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14480 						int[23] visualAttribs = [
14481 							GLX_X_RENDERABLE , 1/*True*/,
14482 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14483 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14484 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14485 							GLX_RED_SIZE     , 8,
14486 							GLX_GREEN_SIZE   , 8,
14487 							GLX_BLUE_SIZE    , 8,
14488 							GLX_ALPHA_SIZE   , 8,
14489 							GLX_DEPTH_SIZE   , 24,
14490 							GLX_STENCIL_SIZE , 8,
14491 							GLX_DOUBLEBUFFER , 1/*True*/,
14492 							0/*None*/,
14493 						];
14494 						int fbcount;
14495 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14496 						if (fbcount == 0) {
14497 							useLegacy = true; // try to do at least something
14498 						} else {
14499 							// pick the FB config/visual with the most samples per pixel
14500 							int bestidx = -1, bestns = -1;
14501 							foreach (int fbi; 0..fbcount) {
14502 								int sb, samples;
14503 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14504 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14505 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14506 							}
14507 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14508 							fbconf = fbc[bestidx];
14509 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14510 							XFree(fbc);
14511 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14512 						}
14513 					}
14514 					if (vi is null || useLegacy) {
14515 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14516 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14517 						useLegacy = true;
14518 					}
14519 					if (vi is null) throw new Exception("no open gl visual found");
14520 
14521 					XSetWindowAttributes swa;
14522 					auto root = RootWindow(display, screen);
14523 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14524 
14525 					swa.override_redirect = overrideRedirect;
14526 
14527 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14528 						0, 0, width, height,
14529 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14530 
14531 					// now try to use `glXCreateContextAttribsARB()` if it's here
14532 					if (!useLegacy) {
14533 						// request fairly advanced context, even with stencil buffer!
14534 						int[9] contextAttribs = [
14535 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14536 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14537 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14538 							// for modern context, set "forward compatibility" flag too
14539 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14540 							0/*None*/,
14541 						];
14542 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14543 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14544 							sdpyOpenGLContextVersion = 0;
14545 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14546 						}
14547 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14548 					} else {
14549 						// fallback to old GLX call
14550 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14551 							sdpyOpenGLContextVersion = 0;
14552 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14553 						}
14554 					}
14555 					// sync to ensure any errors generated are processed
14556 					XSync(display, 0/*False*/);
14557 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14558 					if(glc is null)
14559 						throw new Exception("glc");
14560 				}
14561 			}
14562 
14563 			if(opengl == OpenGlOptions.no) {
14564 
14565 				XSetWindowAttributes swa;
14566 				swa.background_pixel = WhitePixel(display, screen);
14567 				swa.border_pixel = BlackPixel(display, screen);
14568 				swa.override_redirect = overrideRedirect;
14569 				auto root = RootWindow(display, screen);
14570 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14571 
14572 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14573 					0, 0, width, height,
14574 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14575 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14576 
14577 
14578 
14579 				/*
14580 				window = XCreateSimpleWindow(
14581 					display,
14582 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14583 					0, 0, // x, y
14584 					width, height,
14585 					1, // border width
14586 					BlackPixel(display, screen), // border
14587 					WhitePixel(display, screen)); // background
14588 				*/
14589 
14590 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14591 				bufferw = width;
14592 				bufferh = height;
14593 
14594 				gc = DefaultGC(display, screen);
14595 
14596 				// clear out the buffer to get us started...
14597 				XSetForeground(display, gc, WhitePixel(display, screen));
14598 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14599 				XSetForeground(display, gc, BlackPixel(display, screen));
14600 			}
14601 
14602 			// input context
14603 			//TODO: create this only for top-level windows, and reuse that?
14604 			if (XDisplayConnection.xim !is null) {
14605 				xic = XCreateIC(XDisplayConnection.xim,
14606 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
14607 						/*XNClientWindow*/"clientWindow".ptr, window,
14608 						/*XNFocusWindow*/"focusWindow".ptr, window,
14609 						null);
14610 				if (xic is null) {
14611 					import core.stdc.stdio : stderr, fprintf;
14612 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
14613 				}
14614 			}
14615 
14616 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14617 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14618 			// window class
14619 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14620 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14621 				XClassHint klass;
14622 				XWMHints wh;
14623 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14624 					wh.input = true;
14625 					wh.flags |= InputHint;
14626 				}
14627 				XSizeHints size;
14628 				klass.res_name = sdpyWindowClassStr;
14629 				klass.res_class = sdpyWindowClassStr;
14630 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14631 			}
14632 
14633 			setTitle(title);
14634 			SimpleWindow.nativeMapping[window] = this;
14635 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14636 
14637 			// This gives our window a close button
14638 			if (windowType != WindowTypes.eventOnly) {
14639 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14640 				int useAtoms;
14641 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14642 					useAtoms = 2;
14643 				} else {
14644 					useAtoms = 1;
14645 				}
14646 				assert(useAtoms <= atoms.length);
14647 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14648 			}
14649 
14650 			// FIXME: windowType and customizationFlags
14651 			Atom[8] wsatoms; // here, due to goto
14652 			int wmsacount = 0; // here, due to goto
14653 
14654 			try
14655 			final switch(windowType) {
14656 				case WindowTypes.normal:
14657 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14658 				break;
14659 				case WindowTypes.undecorated:
14660 					motifHideDecorations();
14661 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14662 				break;
14663 				case WindowTypes.eventOnly:
14664 					_hidden = true;
14665 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14666 					goto hiddenWindow;
14667 				//break;
14668 				case WindowTypes.nestedChild:
14669 					// handled in XCreateWindow calls
14670 				break;
14671 
14672 				case WindowTypes.dropdownMenu:
14673 					motifHideDecorations();
14674 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14675 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14676 				break;
14677 				case WindowTypes.popupMenu:
14678 					motifHideDecorations();
14679 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14680 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14681 				break;
14682 				case WindowTypes.notification:
14683 					motifHideDecorations();
14684 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14685 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14686 				break;
14687 				/+
14688 				case WindowTypes.menu:
14689 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14690 					motifHideDecorations();
14691 				break;
14692 				case WindowTypes.desktop:
14693 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14694 				break;
14695 				case WindowTypes.dock:
14696 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14697 				break;
14698 				case WindowTypes.toolbar:
14699 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14700 				break;
14701 				case WindowTypes.menu:
14702 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14703 				break;
14704 				case WindowTypes.utility:
14705 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14706 				break;
14707 				case WindowTypes.splash:
14708 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14709 				break;
14710 				case WindowTypes.dialog:
14711 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14712 				break;
14713 				case WindowTypes.tooltip:
14714 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14715 				break;
14716 				case WindowTypes.notification:
14717 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14718 				break;
14719 				case WindowTypes.combo:
14720 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14721 				break;
14722 				case WindowTypes.dnd:
14723 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14724 				break;
14725 				+/
14726 			}
14727 			catch(Exception e) {
14728 				// XInternAtom failed, prolly a WM
14729 				// that doesn't support these things
14730 			}
14731 
14732 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14733 			// the two following flags may be ignored by WM
14734 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14735 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14736 
14737 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14738 
14739 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14740 
14741 			// What would be ideal here is if they only were
14742 			// selected if there was actually an event handler
14743 			// for them...
14744 
14745 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14746 
14747 			hiddenWindow:
14748 
14749 			// set the pid property for lookup later by window managers
14750 			// a standard convenience
14751 			import core.sys.posix.unistd;
14752 			arch_ulong pid = getpid();
14753 
14754 			XChangeProperty(
14755 				display,
14756 				impl.window,
14757 				GetAtom!("_NET_WM_PID", true)(display),
14758 				XA_CARDINAL,
14759 				32 /* bits */,
14760 				0 /*PropModeReplace*/,
14761 				&pid,
14762 				1);
14763 
14764 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
14765 				if(parent is null) assert(0);
14766 				XChangeProperty(
14767 					display,
14768 					impl.window,
14769 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
14770 					XA_WINDOW,
14771 					32 /* bits */,
14772 					0 /*PropModeReplace*/,
14773 					&parent.impl.window,
14774 					1);
14775 
14776 			}
14777 
14778 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
14779 				XMapWindow(display, window);
14780 			} else {
14781 				_hidden = true;
14782 			}
14783 		}
14784 
14785 		void selectDefaultInput(bool forceIncludeMouseMotion) {
14786 			auto mask = EventMask.ExposureMask |
14787 				EventMask.KeyPressMask |
14788 				EventMask.KeyReleaseMask |
14789 				EventMask.PropertyChangeMask |
14790 				EventMask.FocusChangeMask |
14791 				EventMask.StructureNotifyMask |
14792 				EventMask.VisibilityChangeMask
14793 				| EventMask.ButtonPressMask
14794 				| EventMask.ButtonReleaseMask
14795 			;
14796 
14797 			// xshm is our shortcut for local connections
14798 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
14799 				mask |= EventMask.PointerMotionMask;
14800 			else
14801 				mask |= EventMask.ButtonMotionMask;
14802 
14803 			XSelectInput(display, window, mask);
14804 		}
14805 
14806 
14807 		void setNetWMWindowType(Atom type) {
14808 			Atom[2] atoms;
14809 
14810 			atoms[0] = type;
14811 			// generic fallback
14812 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
14813 
14814 			XChangeProperty(
14815 				display,
14816 				impl.window,
14817 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
14818 				XA_ATOM,
14819 				32 /* bits */,
14820 				0 /*PropModeReplace*/,
14821 				atoms.ptr,
14822 				cast(int) atoms.length);
14823 		}
14824 
14825 		void motifHideDecorations(bool hide = true) {
14826 			MwmHints hints;
14827 			hints.flags = MWM_HINTS_DECORATIONS;
14828 			hints.decorations = hide ? 0 : 1;
14829 
14830 			XChangeProperty(
14831 				display,
14832 				impl.window,
14833 				GetAtom!"_MOTIF_WM_HINTS"(display),
14834 				GetAtom!"_MOTIF_WM_HINTS"(display),
14835 				32 /* bits */,
14836 				0 /*PropModeReplace*/,
14837 				&hints,
14838 				hints.sizeof / 4);
14839 		}
14840 
14841 		/*k8: unused
14842 		void createOpenGlContext() {
14843 
14844 		}
14845 		*/
14846 
14847 		void closeWindow() {
14848 			// I can't close this or a child window closing will
14849 			// break events for everyone. So I'm just leaking it right
14850 			// now and that is probably perfectly fine...
14851 			version(none)
14852 			if (customEventFDRead != -1) {
14853 				import core.sys.posix.unistd : close;
14854 				auto same = customEventFDRead == customEventFDWrite;
14855 
14856 				close(customEventFDRead);
14857 				if(!same)
14858 					close(customEventFDWrite);
14859 				customEventFDRead = -1;
14860 				customEventFDWrite = -1;
14861 			}
14862 			if(buffer)
14863 				XFreePixmap(display, buffer);
14864 			bufferw = bufferh = 0;
14865 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
14866 			XDestroyWindow(display, window);
14867 			XFlush(display);
14868 		}
14869 
14870 		void dispose() {
14871 		}
14872 
14873 		bool destroyed = false;
14874 	}
14875 
14876 	bool insideXEventLoop;
14877 }
14878 
14879 version(X11) {
14880 
14881 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
14882 
14883 	private class ResizeEvent {
14884 		int width, height;
14885 	}
14886 
14887 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
14888 		if(win.pendingResizeEvent is null) {
14889 			win.pendingResizeEvent = new ResizeEvent();
14890 			win.addEventListener((ResizeEvent re) {
14891 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
14892 			});
14893 		}
14894 		win.pendingResizeEvent.width = width;
14895 		win.pendingResizeEvent.height = height;
14896 		if(!win.eventQueued!ResizeEvent) {
14897 			win.postEvent(win.pendingResizeEvent);
14898 		}
14899 	}
14900 
14901 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
14902 		if(width != win.width || height != win.height) {
14903 			win._width = width;
14904 			win._height = height;
14905 
14906 			if(win.openglMode == OpenGlOptions.no) {
14907 				// FIXME: could this be more efficient?
14908 
14909 				if (win.bufferw < width || win.bufferh < height) {
14910 					//{ 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); }
14911 					// grow the internal buffer to match the window...
14912 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
14913 					{
14914 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
14915 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
14916 						scope(exit) XFreeGC(win.display, xgc);
14917 						XSetClipMask(win.display, xgc, None);
14918 						XSetForeground(win.display, xgc, 0);
14919 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
14920 					}
14921 					XCopyArea(display,
14922 						cast(Drawable) win.buffer,
14923 						cast(Drawable) newPixmap,
14924 						win.gc, 0, 0,
14925 						win.bufferw < width ? win.bufferw : win.width,
14926 						win.bufferh < height ? win.bufferh : win.height,
14927 						0, 0);
14928 
14929 					XFreePixmap(display, win.buffer);
14930 					win.buffer = newPixmap;
14931 					win.bufferw = width;
14932 					win.bufferh = height;
14933 				}
14934 
14935 				// clear unused parts of the buffer
14936 				if (win.bufferw > width || win.bufferh > height) {
14937 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
14938 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
14939 					scope(exit) XFreeGC(win.display, xgc);
14940 					XSetClipMask(win.display, xgc, None);
14941 					XSetForeground(win.display, xgc, 0);
14942 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
14943 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
14944 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
14945 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
14946 				}
14947 
14948 			}
14949 
14950 			version(without_opengl) {} else
14951 			if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) {
14952 				glViewport(0, 0, width, height);
14953 			}
14954 
14955 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
14956 
14957 			if(win.windowResized !is null) {
14958 				XUnlockDisplay(display);
14959 				scope(exit) XLockDisplay(display);
14960 				win.windowResized(width, height);
14961 			}
14962 		}
14963 	}
14964 
14965 
14966 	/// Platform-specific, you might use it when doing a custom event loop.
14967 	bool doXNextEvent(Display* display) {
14968 		bool done;
14969 		XEvent e;
14970 		XNextEvent(display, &e);
14971 		version(sddddd) {
14972 			import std.stdio, std.conv : to;
14973 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
14974 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
14975 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
14976 			}
14977 		}
14978 
14979 		// filter out compose events
14980 		if (XFilterEvent(&e, None)) {
14981 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
14982 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
14983 			return false;
14984 		}
14985 		// process keyboard mapping changes
14986 		if (e.type == EventType.KeymapNotify) {
14987 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
14988 			XRefreshKeyboardMapping(&e.xmapping);
14989 			return false;
14990 		}
14991 
14992 		version(with_eventloop)
14993 			import arsd.eventloop;
14994 
14995 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
14996 			// see windows impl's comments
14997 			XUnlockDisplay(display);
14998 			scope(exit) XLockDisplay(display);
14999 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15000 			if(ret == 0)
15001 				return done;
15002 		}
15003 
15004 
15005 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15006 			if(win.getNativeEventHandler !is null) {
15007 				XUnlockDisplay(display);
15008 				scope(exit) XLockDisplay(display);
15009 				auto ret = win.getNativeEventHandler()(e);
15010 				if(ret == 0)
15011 					return done;
15012 			}
15013 		}
15014 
15015 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15016 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15017 				// we get this because of the RRScreenChangeNotifyMask
15018 
15019 				// this isn't actually an ideal way to do it since it wastes time
15020 				// but meh it is simple and it works.
15021 				win.actualDpiLoadAttempted = false;
15022 				SimpleWindow.xRandrInfoLoadAttemped = false;
15023 				win.updateActualDpi(); // trigger a reload
15024 			}
15025 		}
15026 
15027 		switch(e.type) {
15028 		  case EventType.SelectionClear:
15029 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15030 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15031 				//import std.stdio; writeln("SelectionClear");
15032 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15033 			}
15034 		  break;
15035 		  case EventType.SelectionRequest:
15036 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15037 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15038 				// import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15039 				XUnlockDisplay(display);
15040 				scope(exit) XLockDisplay(display);
15041 				(*ssh).handleRequest(e);
15042 			}
15043 		  break;
15044 		  case EventType.PropertyNotify:
15045 			// import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15046 
15047 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15048 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15049 					ssh.sendMoreIncr(&e.xproperty);
15050 			}
15051 
15052 
15053 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15054 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15055 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15056 					Atom target;
15057 					int format;
15058 					arch_ulong bytesafter, length;
15059 					void* value;
15060 
15061 					ubyte[] s;
15062 					Atom targetToKeep;
15063 
15064 					XGetWindowProperty(
15065 						e.xproperty.display,
15066 						e.xproperty.window,
15067 						e.xproperty.atom,
15068 						0,
15069 						100000 /* length */,
15070 						true, /* erase it to signal we got it and want more */
15071 						0 /*AnyPropertyType*/,
15072 						&target, &format, &length, &bytesafter, &value);
15073 
15074 					if(!targetToKeep)
15075 						targetToKeep = target;
15076 
15077 					auto id = (cast(ubyte*) value)[0 .. length];
15078 
15079 					handler.handleIncrData(targetToKeep, id);
15080 
15081 					XFree(value);
15082 				}
15083 			}
15084 		  break;
15085 		  case EventType.SelectionNotify:
15086 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15087 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15088 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15089 					XUnlockDisplay(display);
15090 					scope(exit) XLockDisplay(display);
15091 					handler.handleData(None, null);
15092 				} else {
15093 					Atom target;
15094 					int format;
15095 					arch_ulong bytesafter, length;
15096 					void* value;
15097 					XGetWindowProperty(
15098 						e.xselection.display,
15099 						e.xselection.requestor,
15100 						e.xselection.property,
15101 						0,
15102 						100000 /* length */,
15103 						//false, /* don't erase it */
15104 						true, /* do erase it lol */
15105 						0 /*AnyPropertyType*/,
15106 						&target, &format, &length, &bytesafter, &value);
15107 
15108 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15109 
15110 					{
15111 						XUnlockDisplay(display);
15112 						scope(exit) XLockDisplay(display);
15113 
15114 						if(target == XA_ATOM) {
15115 							// initial request, see what they are able to work with and request the best one
15116 							// we can handle, if available
15117 
15118 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15119 							Atom best = handler.findBestFormat(answer);
15120 
15121 							/+
15122 							writeln("got ", answer);
15123 							foreach(a; answer)
15124 								printf("%s\n", XGetAtomName(display, a));
15125 							writeln("best ", best);
15126 							+/
15127 
15128 							if(best != None) {
15129 								// actually request the best format
15130 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15131 							}
15132 						} else if(target == GetAtom!"INCR"(display)) {
15133 							// incremental
15134 
15135 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15136 
15137 							// signal the sending program that we see
15138 							// the incr and are ready to receive more.
15139 							XDeleteProperty(
15140 								e.xselection.display,
15141 								e.xselection.requestor,
15142 								e.xselection.property);
15143 						} else {
15144 							// unsupported type... maybe, forward
15145 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15146 						}
15147 					}
15148 					XFree(value);
15149 					/*
15150 					XDeleteProperty(
15151 						e.xselection.display,
15152 						e.xselection.requestor,
15153 						e.xselection.property);
15154 					*/
15155 				}
15156 			}
15157 		  break;
15158 		  case EventType.ConfigureNotify:
15159 			auto event = e.xconfigure;
15160 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15161 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
15162 
15163 				/+
15164 					The ICCCM says window managers must send a synthetic event when the window
15165 					is moved but NOT when it is resized. In the resize case, an event is sent
15166 					with position (0, 0) which can be wrong and break the dpi calculations.
15167 
15168 					So we only consider the synthetic events from the WM and otherwise
15169 					need to wait for some other event to get the position which... sucks.
15170 
15171 					I'd rather not have windows changing their layout on mouse motion after
15172 					switching monitors... might be forced to but for now just ignoring it.
15173 
15174 					Easiest way to switch monitors without sending a size position is by
15175 					maximize or fullscreen in a setup like mine, but on most setups those
15176 					work on the monitor it is already living on, so it should be ok most the
15177 					time.
15178 				+/
15179 				if(event.send_event) {
15180 					win.screenPositionKnown = true;
15181 					win.screenPositionX = event.x;
15182 					win.screenPositionY = event.y;
15183 					win.updateActualDpi();
15184 				}
15185 
15186 				recordX11ResizeAsync(display, *win, event.width, event.height);
15187 			}
15188 		  break;
15189 		  case EventType.Expose:
15190 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15191 				// if it is closing from a popup menu, it can get
15192 				// an Expose event right by the end and trigger a
15193 				// BadDrawable error ... we'll just check
15194 				// closed to handle that.
15195 				if((*win).closed) break;
15196 				if((*win).openglMode == OpenGlOptions.no) {
15197 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15198 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15199 					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);
15200 				} else {
15201 					// need to redraw the scene somehow
15202 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15203 						XUnlockDisplay(display);
15204 						scope(exit) XLockDisplay(display);
15205 						version(without_opengl) {} else
15206 						win.redrawOpenGlSceneSoon();
15207 					}
15208 				}
15209 			}
15210 		  break;
15211 		  case EventType.FocusIn:
15212 		  case EventType.FocusOut:
15213 
15214 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15215 				/+
15216 
15217 				void info(string detail) {
15218 					string s;
15219 					import std.conv;
15220 					import std.datetime;
15221 					s ~= to!string(Clock.currTime);
15222 					s ~= " ";
15223 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15224 					s ~= " ";
15225 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15226 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15227 					s ~= detail;
15228 					s ~= " ";
15229 
15230 					sdpyPrintDebugString(s);
15231 
15232 				}
15233 
15234 				switch(e.xfocus.detail) {
15235 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15236 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15237 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15238 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15239 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15240 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15241 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15242 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15243 					default:
15244 
15245 				}
15246 				+/
15247 
15248 
15249 				if (win.xic !is null) {
15250 					//{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); }
15251 					if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic);
15252 				}
15253 
15254 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15255 					break; // just ignore these they seem irrelevant
15256 
15257 				auto old = win._focused;
15258 				win._focused = e.type == EventType.FocusIn;
15259 
15260 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15261 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15262 					win._focused = true;
15263 
15264 				if(win.demandingAttention)
15265 					demandAttention(*win, false);
15266 
15267 				if(old != win._focused && win.onFocusChange) {
15268 					XUnlockDisplay(display);
15269 					scope(exit) XLockDisplay(display);
15270 					win.onFocusChange(win._focused);
15271 				}
15272 			}
15273 		  break;
15274 		  case EventType.VisibilityNotify:
15275 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15276 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15277 						if (win.visibilityChanged !is null) {
15278 								XUnlockDisplay(display);
15279 								scope(exit) XLockDisplay(display);
15280 								win.visibilityChanged(false);
15281 							}
15282 					} else {
15283 						if (win.visibilityChanged !is null) {
15284 							XUnlockDisplay(display);
15285 							scope(exit) XLockDisplay(display);
15286 							win.visibilityChanged(true);
15287 						}
15288 					}
15289 				}
15290 				break;
15291 		  case EventType.ClientMessage:
15292 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15293 					// "ignore next mouse motion" event, increment ignore counter for teh window
15294 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15295 						++(*win).warpEventCount;
15296 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15297 					} else {
15298 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15299 					}
15300 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15301 					// user clicked the close button on the window manager
15302 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15303 						XUnlockDisplay(display);
15304 						scope(exit) XLockDisplay(display);
15305 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15306 					}
15307 
15308 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15309 					//import std.stdio; writeln("HAPPENED");
15310 					// user clicked the close button on the window manager
15311 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15312 						XUnlockDisplay(display);
15313 						scope(exit) XLockDisplay(display);
15314 
15315 						auto setTo = *win;
15316 
15317 						if(win.setRequestedInputFocus !is null) {
15318 							auto s = win.setRequestedInputFocus();
15319 							if(s !is null)
15320 								setTo = s;
15321 						}
15322 
15323 						assert(setTo !is null);
15324 
15325 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15326 
15327 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15328 					}
15329 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15330 					foreach(nai; NotificationAreaIcon.activeIcons)
15331 						nai.newManager();
15332 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15333 
15334 					bool xDragWindow = true;
15335 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15336 						//XDefineCursor(display, xDragWindow.impl.window,
15337 							//import std.stdio; writeln("XdndStatus ", e.xclient.data.l);
15338 					}
15339 					if(auto dh = win.dropHandler) {
15340 
15341 						static Atom[3] xFormatsBuffer;
15342 						static Atom[] xFormats;
15343 
15344 						void resetXFormats() {
15345 							xFormatsBuffer[] = 0;
15346 							xFormats = xFormatsBuffer[];
15347 						}
15348 
15349 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15350 							// on Windows it is supposed to return the effect you actually do FIXME
15351 
15352 							auto sourceWindow =  e.xclient.data.l[0];
15353 
15354 							xFormatsBuffer[0] = e.xclient.data.l[2];
15355 							xFormatsBuffer[1] = e.xclient.data.l[3];
15356 							xFormatsBuffer[2] = e.xclient.data.l[4];
15357 
15358 							if(e.xclient.data.l[1] & 1) {
15359 								// can just grab it all but like we don't necessarily need them...
15360 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15361 							} else {
15362 								int len;
15363 								foreach(fmt; xFormatsBuffer)
15364 									if(fmt) len++;
15365 								xFormats = xFormatsBuffer[0 .. len];
15366 							}
15367 
15368 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15369 
15370 							dh.dragEnter(&pkg);
15371 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15372 
15373 							auto pack = e.xclient.data.l[2];
15374 
15375 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15376 
15377 
15378 							XClientMessageEvent xclient;
15379 
15380 							xclient.type = EventType.ClientMessage;
15381 							xclient.window = e.xclient.data.l[0];
15382 							xclient.message_type = GetAtom!"XdndStatus"(display);
15383 							xclient.format = 32;
15384 							xclient.data.l[0] = win.impl.window;
15385 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15386 							auto r = result.consistentWithin;
15387 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15388 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15389 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15390 
15391 							XSendEvent(
15392 								display,
15393 								e.xclient.data.l[0],
15394 								false,
15395 								EventMask.NoEventMask,
15396 								cast(XEvent*) &xclient
15397 							);
15398 
15399 
15400 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15401 							//import std.stdio; writeln("XdndLeave");
15402 							// drop cancelled.
15403 							// data.l[0] is the source window
15404 							dh.dragLeave();
15405 
15406 							resetXFormats();
15407 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15408 							// drop happening, should fetch data, then send finished
15409 							//import std.stdio; writeln("XdndDrop");
15410 
15411 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15412 
15413 							dh.drop(&pkg);
15414 
15415 							resetXFormats();
15416 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15417 							// import std.stdio; writeln("XdndFinished");
15418 
15419 							dh.finish();
15420 						}
15421 
15422 					}
15423 				}
15424 		  break;
15425 		  case EventType.MapNotify:
15426 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15427 					(*win)._visible = true;
15428 					if (!(*win)._visibleForTheFirstTimeCalled) {
15429 						(*win)._visibleForTheFirstTimeCalled = true;
15430 						if ((*win).visibleForTheFirstTime !is null) {
15431 							XUnlockDisplay(display);
15432 							scope(exit) XLockDisplay(display);
15433 							version(without_opengl) {} else {
15434 								if((*win).openglMode == OpenGlOptions.yes) {
15435 									(*win).setAsCurrentOpenGlContextNT();
15436 									glViewport(0, 0, (*win).width, (*win).height);
15437 								}
15438 							}
15439 							(*win).visibleForTheFirstTime();
15440 						}
15441 					}
15442 					if ((*win).visibilityChanged !is null) {
15443 						XUnlockDisplay(display);
15444 						scope(exit) XLockDisplay(display);
15445 						(*win).visibilityChanged(true);
15446 					}
15447 				}
15448 		  break;
15449 		  case EventType.UnmapNotify:
15450 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15451 					win._visible = false;
15452 					if (win.visibilityChanged !is null) {
15453 						XUnlockDisplay(display);
15454 						scope(exit) XLockDisplay(display);
15455 						win.visibilityChanged(false);
15456 					}
15457 			}
15458 		  break;
15459 		  case EventType.DestroyNotify:
15460 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15461 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15462 				win._closed = true; // just in case
15463 				win.destroyed = true;
15464 				if (win.xic !is null) {
15465 					XDestroyIC(win.xic);
15466 					win.xic = null; // just in calse
15467 				}
15468 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15469 				bool anyImportant = false;
15470 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15471 					if(w.beingOpenKeepsAppOpen) {
15472 						anyImportant = true;
15473 						break;
15474 					}
15475 				if(!anyImportant) {
15476 					EventLoop.quitApplication();
15477 					done = true;
15478 				}
15479 			}
15480 			auto window = e.xdestroywindow.window;
15481 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15482 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15483 
15484 			version(with_eventloop) {
15485 				if(done) exit();
15486 			}
15487 		  break;
15488 
15489 		  case EventType.MotionNotify:
15490 			MouseEvent mouse;
15491 			auto event = e.xmotion;
15492 
15493 			mouse.type = MouseEventType.motion;
15494 			mouse.x = event.x;
15495 			mouse.y = event.y;
15496 			mouse.modifierState = event.state;
15497 
15498 			mouse.timestamp = event.time;
15499 
15500 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15501 				mouse.window = *win;
15502 				if (win.warpEventCount > 0) {
15503 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15504 					--(*win).warpEventCount;
15505 					(*win).mdx(mouse); // so deltas will be correctly updated
15506 				} else {
15507 					win.warpEventCount = 0; // just in case
15508 					(*win).mdx(mouse);
15509 					if((*win).handleMouseEvent) {
15510 						XUnlockDisplay(display);
15511 						scope(exit) XLockDisplay(display);
15512 						(*win).handleMouseEvent(mouse);
15513 					}
15514 				}
15515 			}
15516 
15517 		  	version(with_eventloop)
15518 				send(mouse);
15519 		  break;
15520 		  case EventType.ButtonPress:
15521 		  case EventType.ButtonRelease:
15522 			MouseEvent mouse;
15523 			auto event = e.xbutton;
15524 
15525 			mouse.timestamp = event.time;
15526 
15527 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15528 			mouse.x = event.x;
15529 			mouse.y = event.y;
15530 
15531 			static Time lastMouseDownTime = 0;
15532 
15533 			mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15534 			if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time;
15535 
15536 			switch(event.button) {
15537 				case 1: mouse.button = MouseButton.left; break; // left
15538 				case 2: mouse.button = MouseButton.middle; break; // middle
15539 				case 3: mouse.button = MouseButton.right; break; // right
15540 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15541 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15542 				case 6: break; // idk
15543 				case 7: break; // idk
15544 				case 8: mouse.button = MouseButton.backButton; break;
15545 				case 9: mouse.button = MouseButton.forwardButton; break;
15546 				default:
15547 			}
15548 
15549 			// FIXME: double check this
15550 			mouse.modifierState = event.state;
15551 
15552 			//mouse.modifierState = event.detail;
15553 
15554 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15555 				mouse.window = *win;
15556 				(*win).mdx(mouse);
15557 				if((*win).handleMouseEvent) {
15558 					XUnlockDisplay(display);
15559 					scope(exit) XLockDisplay(display);
15560 					(*win).handleMouseEvent(mouse);
15561 				}
15562 			}
15563 			version(with_eventloop)
15564 				send(mouse);
15565 		  break;
15566 
15567 		  case EventType.KeyPress:
15568 		  case EventType.KeyRelease:
15569 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15570 			KeyEvent ke;
15571 			ke.pressed = e.type == EventType.KeyPress;
15572 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15573 
15574 			auto sym = XKeycodeToKeysym(
15575 				XDisplayConnection.get(),
15576 				e.xkey.keycode,
15577 				0);
15578 
15579 			ke.key = cast(Key) sym;//e.xkey.keycode;
15580 
15581 			ke.modifierState = e.xkey.state;
15582 
15583 			// import std.stdio; writefln("%x", sym);
15584 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15585 			int charbuflen = 0; // return value of XwcLookupString
15586 			if (ke.pressed) {
15587 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15588 				if (win !is null && win.xic !is null) {
15589 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15590 					Status status;
15591 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15592 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15593 				} else {
15594 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15595 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15596 					char[16] buffer;
15597 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15598 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15599 				}
15600 			}
15601 
15602 			// if there's no char, subst one
15603 			if (charbuflen == 0) {
15604 				switch (sym) {
15605 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15606 					case 0xff8d: // keypad enter
15607 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15608 					default : // ignore
15609 				}
15610 			}
15611 
15612 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15613 				ke.window = *win;
15614 
15615 
15616 				if(win.inputProxy)
15617 					win = &win.inputProxy;
15618 
15619 				// char events are separate since they are on Windows too
15620 				// also, xcompose can generate long char sequences
15621 				// don't send char events if Meta and/or Hyper is pressed
15622 				// TODO: ctrl+char should only send control chars; not yet
15623 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15624 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15625 				}
15626 
15627 				dchar[32] charsComingBuffer;
15628 				int charsComingPosition;
15629 				dchar[] charsComing = charsComingBuffer[];
15630 
15631 				if (ke.pressed && charbuflen > 0) {
15632 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15633 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15634 						if(charsComingPosition >= charsComing.length)
15635 							charsComing.length = charsComingPosition + 8;
15636 
15637 						charsComing[charsComingPosition++] = ch;
15638 					}
15639 
15640 					charsComing = charsComing[0 .. charsComingPosition];
15641 				} else {
15642 					charsComing = null;
15643 				}
15644 
15645 				ke.charsPossible = charsComing;
15646 
15647 				if (win.handleKeyEvent) {
15648 					XUnlockDisplay(display);
15649 					scope(exit) XLockDisplay(display);
15650 					win.handleKeyEvent(ke);
15651 				}
15652 
15653 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15654 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15655 					XUnlockDisplay(display);
15656 					scope(exit) XLockDisplay(display);
15657 					foreach(ch; charsComing)
15658 						win.handleCharEvent(ch);
15659 				}
15660 			}
15661 
15662 			version(with_eventloop)
15663 				send(ke);
15664 		  break;
15665 		  default:
15666 		}
15667 
15668 		return done;
15669 	}
15670 }
15671 
15672 /* *************************************** */
15673 /*      Done with simpledisplay stuff      */
15674 /* *************************************** */
15675 
15676 // Necessary C library bindings follow
15677 version(Windows) {} else
15678 version(X11) {
15679 
15680 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15681 
15682 // X11 bindings needed here
15683 /*
15684 	A little of this is from the bindings project on
15685 	D Source and some of it is copy/paste from the C
15686 	header.
15687 
15688 	The DSource listing consistently used D's long
15689 	where C used long. That's wrong - C long is 32 bit, so
15690 	it should be int in D. I changed that here.
15691 
15692 	Note:
15693 	This isn't complete, just took what I needed for myself.
15694 */
15695 
15696 import core.stdc.stddef : wchar_t;
15697 
15698 interface XLib {
15699 extern(C) nothrow @nogc {
15700 	char* XResourceManagerString(Display*);
15701 	void XrmInitialize();
15702 	XrmDatabase XrmGetStringDatabase(char* data);
15703 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15704 
15705 	Cursor XCreateFontCursor(Display*, uint shape);
15706 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15707 	int XUndefineCursor(Display* display, Window w);
15708 
15709 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15710 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15711 	int XFreeCursor(Display* display, Cursor cursor);
15712 
15713 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
15714 
15715 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
15716 
15717 	char *XKeysymToString(KeySym keysym);
15718 	KeySym XKeycodeToKeysym(
15719 		Display*		/* display */,
15720 		KeyCode		/* keycode */,
15721 		int			/* index */
15722 	);
15723 
15724 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
15725 
15726 	int XFree(void*);
15727 	int XDeleteProperty(Display *display, Window w, Atom property);
15728 
15729 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
15730 
15731 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
15732 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
15733 		*actual_type_return, int *actual_format_return, arch_ulong
15734 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
15735 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
15736 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
15737 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
15738 
15739 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
15740 
15741 	Window XGetSelectionOwner(Display *display, Atom selection);
15742 
15743 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
15744 
15745 	char** XListFonts(Display*, const char*, int, int*);
15746 	void XFreeFontNames(char**);
15747 
15748 	Display* XOpenDisplay(const char*);
15749 	int XCloseDisplay(Display*);
15750 
15751 	int XSynchronize(Display*, bool);
15752 
15753 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
15754 
15755 	Bool XSupportsLocale();
15756 	char* XSetLocaleModifiers(const(char)* modifier_list);
15757 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15758 	Status XCloseOM(XOM om);
15759 
15760 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15761 	Status XCloseIM(XIM im);
15762 
15763 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15764 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15765 	Display* XDisplayOfIM(XIM im);
15766 	char* XLocaleOfIM(XIM im);
15767 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
15768 	void XDestroyIC(XIC ic);
15769 	void XSetICFocus(XIC ic);
15770 	void XUnsetICFocus(XIC ic);
15771 	//wchar_t* XwcResetIC(XIC ic);
15772 	char* XmbResetIC(XIC ic);
15773 	char* Xutf8ResetIC(XIC ic);
15774 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15775 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15776 	XIM XIMOfIC(XIC ic);
15777 
15778 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
15779 
15780 
15781 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
15782 	int XFreeFont(Display *display, XFontStruct *font_struct);
15783 	int XSetFont(Display* display, GC gc, Font font);
15784 	int XTextWidth(XFontStruct*, in char*, int);
15785 
15786 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
15787 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
15788 
15789 	Window XCreateSimpleWindow(
15790 		Display*	/* display */,
15791 		Window		/* parent */,
15792 		int			/* x */,
15793 		int			/* y */,
15794 		uint		/* width */,
15795 		uint		/* height */,
15796 		uint		/* border_width */,
15797 		uint		/* border */,
15798 		uint		/* background */
15799 	);
15800 	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);
15801 
15802 	int XReparentWindow(Display*, Window, Window, int, int);
15803 	int XClearWindow(Display*, Window);
15804 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
15805 	int XMoveWindow(Display*, Window, int, int);
15806 	int XResizeWindow(Display *display, Window w, uint width, uint height);
15807 
15808 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
15809 
15810 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
15811 
15812 	XImage *XCreateImage(
15813 		Display*		/* display */,
15814 		Visual*		/* visual */,
15815 		uint	/* depth */,
15816 		int			/* format */,
15817 		int			/* offset */,
15818 		ubyte*		/* data */,
15819 		uint	/* width */,
15820 		uint	/* height */,
15821 		int			/* bitmap_pad */,
15822 		int			/* bytes_per_line */
15823 	);
15824 
15825 	Status XInitImage (XImage* image);
15826 
15827 	Atom XInternAtom(
15828 		Display*		/* display */,
15829 		const char*	/* atom_name */,
15830 		Bool		/* only_if_exists */
15831 	);
15832 
15833 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
15834 	char* XGetAtomName(Display*, Atom);
15835 	Status XGetAtomNames(Display*, Atom*, int count, char**);
15836 
15837 	int XPutImage(
15838 		Display*	/* display */,
15839 		Drawable	/* d */,
15840 		GC			/* gc */,
15841 		XImage*	/* image */,
15842 		int			/* src_x */,
15843 		int			/* src_y */,
15844 		int			/* dest_x */,
15845 		int			/* dest_y */,
15846 		uint		/* width */,
15847 		uint		/* height */
15848 	);
15849 
15850 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
15851 
15852 
15853 	int XDestroyWindow(
15854 		Display*	/* display */,
15855 		Window		/* w */
15856 	);
15857 
15858 	int XDestroyImage(XImage*);
15859 
15860 	int XSelectInput(
15861 		Display*	/* display */,
15862 		Window		/* w */,
15863 		EventMask	/* event_mask */
15864 	);
15865 
15866 	int XMapWindow(
15867 		Display*	/* display */,
15868 		Window		/* w */
15869 	);
15870 
15871 	Status XIconifyWindow(Display*, Window, int);
15872 	int XMapRaised(Display*, Window);
15873 	int XMapSubwindows(Display*, Window);
15874 
15875 	int XNextEvent(
15876 		Display*	/* display */,
15877 		XEvent*		/* event_return */
15878 	);
15879 
15880 	int XMaskEvent(Display*, arch_long, XEvent*);
15881 
15882 	Bool XFilterEvent(XEvent *event, Window window);
15883 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
15884 
15885 	Status XSetWMProtocols(
15886 		Display*	/* display */,
15887 		Window		/* w */,
15888 		Atom*		/* protocols */,
15889 		int			/* count */
15890 	);
15891 
15892 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
15893 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
15894 
15895 
15896 	Status XInitThreads();
15897 	void XLockDisplay (Display* display);
15898 	void XUnlockDisplay (Display* display);
15899 
15900 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
15901 
15902 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
15903 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
15904 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
15905 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
15906 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
15907 
15908 
15909 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
15910 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
15911 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
15912 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
15913 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
15914 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
15915 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
15916 	int XDrawPoint(Display*, Drawable, GC, int, int);
15917 	int XSetForeground(Display*, GC, uint);
15918 	int XSetBackground(Display*, GC, uint);
15919 
15920 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
15921 	void XFreeFontSet(Display*, XFontSet);
15922 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
15923 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
15924 
15925 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
15926 	 	
15927 
15928 //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);
15929 
15930 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
15931 	int XSetFunction(Display*, GC, int);
15932 
15933 	GC XCreateGC(Display*, Drawable, uint, void*);
15934 	int XCopyGC(Display*, GC, uint, GC);
15935 	int XFreeGC(Display*, GC);
15936 
15937 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
15938 	bool XCheckMaskEvent(Display*, int, XEvent*);
15939 
15940 	int XPending(Display*);
15941 	int XEventsQueued(Display* display, int mode);
15942 
15943 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
15944 	int XFreePixmap(Display*, Pixmap);
15945 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
15946 	int XFlush(Display*);
15947 	int XBell(Display*, int);
15948 	int XSync(Display*, bool);
15949 
15950 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
15951 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
15952 
15953 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
15954 	int XUngrabKeyboard(Display*, Time);
15955 
15956 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
15957 
15958 	KeySym XStringToKeysym(const char *string);
15959 
15960 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
15961 
15962 	Window XDefaultRootWindow(Display*);
15963 
15964 	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);
15965 
15966 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 
15967 
15968 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
15969 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
15970 
15971 	Status XAllocColor(Display*, Colormap, XColor*);
15972 
15973 	int XWithdrawWindow(Display*, Window, int);
15974 	int XUnmapWindow(Display*, Window);
15975 	int XLowerWindow(Display*, Window);
15976 	int XRaiseWindow(Display*, Window);
15977 
15978 	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);
15979 	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);
15980 
15981 	int XGetInputFocus(Display*, Window*, int*);
15982 	int XSetInputFocus(Display*, Window, int, Time);
15983 
15984 	XErrorHandler XSetErrorHandler(XErrorHandler);
15985 
15986 	int XGetErrorText(Display*, int, char*, int);
15987 
15988 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
15989 
15990 
15991 	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);
15992 	int XUngrabPointer(Display *display, Time time);
15993 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
15994 
15995 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
15996 
15997 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
15998 	int XSetClipMask(Display*, GC, Pixmap);
15999 	int XSetClipOrigin(Display*, GC, int, int);
16000 
16001 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16002 
16003 	void XSetWMName(Display*, Window, XTextProperty*);
16004 	Status XGetWMName(Display*, Window, XTextProperty*);
16005 	int XStoreName(Display* display, Window w, const(char)* window_name);
16006 
16007 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16008 
16009 }
16010 }
16011 
16012 interface Xext {
16013 extern(C) nothrow @nogc {
16014 	Status XShmAttach(Display*, XShmSegmentInfo*);
16015 	Status XShmDetach(Display*, XShmSegmentInfo*);
16016 	Status XShmPutImage(
16017 		Display*            /* dpy */,
16018 		Drawable            /* d */,
16019 		GC                  /* gc */,
16020 		XImage*             /* image */,
16021 		int                 /* src_x */,
16022 		int                 /* src_y */,
16023 		int                 /* dst_x */,
16024 		int                 /* dst_y */,
16025 		uint        /* src_width */,
16026 		uint        /* src_height */,
16027 		Bool                /* send_event */
16028 	);
16029 
16030 	Status XShmQueryExtension(Display*);
16031 
16032 	XImage *XShmCreateImage(
16033 		Display*            /* dpy */,
16034 		Visual*             /* visual */,
16035 		uint        /* depth */,
16036 		int                 /* format */,
16037 		char*               /* data */,
16038 		XShmSegmentInfo*    /* shminfo */,
16039 		uint        /* width */,
16040 		uint        /* height */
16041 	);
16042 
16043 	Pixmap XShmCreatePixmap(
16044 		Display*            /* dpy */,
16045 		Drawable            /* d */,
16046 		char*               /* data */,
16047 		XShmSegmentInfo*    /* shminfo */,
16048 		uint        /* width */,
16049 		uint        /* height */,
16050 		uint        /* depth */
16051 	);
16052 
16053 }
16054 }
16055 
16056 	// this requires -lXpm
16057 	//int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16058 
16059 
16060 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16061 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16062 shared static this() {
16063 	xlib.loadDynamicLibrary();
16064 	xext.loadDynamicLibrary();
16065 }
16066 
16067 
16068 extern(C) nothrow @nogc {
16069 
16070 alias XrmDatabase = void*;
16071 struct XrmValue {
16072 	uint size;
16073 	void* addr;
16074 }
16075 
16076 struct XVisualInfo {
16077 	Visual* visual;
16078 	VisualID visualid;
16079 	int screen;
16080 	uint depth;
16081 	int c_class;
16082 	c_ulong red_mask;
16083 	c_ulong green_mask;
16084 	c_ulong blue_mask;
16085 	int colormap_size;
16086 	int bits_per_rgb;
16087 }
16088 
16089 enum VisualNoMask=	0x0;
16090 enum VisualIDMask=	0x1;
16091 enum VisualScreenMask=0x2;
16092 enum VisualDepthMask=	0x4;
16093 enum VisualClassMask=	0x8;
16094 enum VisualRedMaskMask=0x10;
16095 enum VisualGreenMaskMask=0x20;
16096 enum VisualBlueMaskMask=0x40;
16097 enum VisualColormapSizeMask=0x80;
16098 enum VisualBitsPerRGBMask=0x100;
16099 enum VisualAllMask=	0x1FF;
16100 
16101 enum AnyKey = 0;
16102 enum AnyModifier = 1 << 15;
16103 
16104 // XIM and other crap
16105 struct _XOM {}
16106 struct _XIM {}
16107 struct _XIC {}
16108 alias XOM = _XOM*;
16109 alias XIM = _XIM*;
16110 alias XIC = _XIC*;
16111 
16112 alias XIMStyle = arch_ulong;
16113 enum : arch_ulong {
16114 	XIMPreeditArea      = 0x0001,
16115 	XIMPreeditCallbacks = 0x0002,
16116 	XIMPreeditPosition  = 0x0004,
16117 	XIMPreeditNothing   = 0x0008,
16118 	XIMPreeditNone      = 0x0010,
16119 	XIMStatusArea       = 0x0100,
16120 	XIMStatusCallbacks  = 0x0200,
16121 	XIMStatusNothing    = 0x0400,
16122 	XIMStatusNone       = 0x0800,
16123 }
16124 
16125 
16126 /* X Shared Memory Extension functions */
16127 	//pragma(lib, "Xshm");
16128 	alias arch_ulong ShmSeg;
16129 	struct XShmSegmentInfo {
16130 		ShmSeg shmseg;
16131 		int shmid;
16132 		ubyte* shmaddr;
16133 		Bool readOnly;
16134 	}
16135 
16136 	// and the necessary OS functions
16137 	int shmget(int, size_t, int);
16138 	void* shmat(int, in void*, int);
16139 	int shmdt(in void*);
16140 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16141 
16142 	enum IPC_PRIVATE = 0;
16143 	enum IPC_CREAT = 512;
16144 	enum IPC_RMID = 0;
16145 
16146 /* MIT-SHM end */
16147 
16148 
16149 enum MappingType:int {
16150 	MappingModifier		=0,
16151 	MappingKeyboard		=1,
16152 	MappingPointer		=2
16153 }
16154 
16155 /* ImageFormat -- PutImage, GetImage */
16156 enum ImageFormat:int {
16157 	XYBitmap	=0,	/* depth 1, XYFormat */
16158 	XYPixmap	=1,	/* depth == drawable depth */
16159 	ZPixmap	=2	/* depth == drawable depth */
16160 }
16161 
16162 enum ModifierName:int {
16163 	ShiftMapIndex	=0,
16164 	LockMapIndex	=1,
16165 	ControlMapIndex	=2,
16166 	Mod1MapIndex	=3,
16167 	Mod2MapIndex	=4,
16168 	Mod3MapIndex	=5,
16169 	Mod4MapIndex	=6,
16170 	Mod5MapIndex	=7
16171 }
16172 
16173 enum ButtonMask:int {
16174 	Button1Mask	=1<<8,
16175 	Button2Mask	=1<<9,
16176 	Button3Mask	=1<<10,
16177 	Button4Mask	=1<<11,
16178 	Button5Mask	=1<<12,
16179 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16180 }
16181 
16182 enum KeyOrButtonMask:uint {
16183 	ShiftMask	=1<<0,
16184 	LockMask	=1<<1,
16185 	ControlMask	=1<<2,
16186 	Mod1Mask	=1<<3,
16187 	Mod2Mask	=1<<4,
16188 	Mod3Mask	=1<<5,
16189 	Mod4Mask	=1<<6,
16190 	Mod5Mask	=1<<7,
16191 	Button1Mask	=1<<8,
16192 	Button2Mask	=1<<9,
16193 	Button3Mask	=1<<10,
16194 	Button4Mask	=1<<11,
16195 	Button5Mask	=1<<12,
16196 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16197 }
16198 
16199 enum ButtonName:int {
16200 	Button1	=1,
16201 	Button2	=2,
16202 	Button3	=3,
16203 	Button4	=4,
16204 	Button5	=5
16205 }
16206 
16207 /* Notify modes */
16208 enum NotifyModes:int
16209 {
16210 	NotifyNormal		=0,
16211 	NotifyGrab			=1,
16212 	NotifyUngrab		=2,
16213 	NotifyWhileGrabbed	=3
16214 }
16215 enum NotifyHint = 1;	/* for MotionNotify events */
16216 
16217 /* Notify detail */
16218 enum NotifyDetail:int
16219 {
16220 	NotifyAncestor			=0,
16221 	NotifyVirtual			=1,
16222 	NotifyInferior			=2,
16223 	NotifyNonlinear			=3,
16224 	NotifyNonlinearVirtual	=4,
16225 	NotifyPointer			=5,
16226 	NotifyPointerRoot		=6,
16227 	NotifyDetailNone		=7
16228 }
16229 
16230 /* Visibility notify */
16231 
16232 enum VisibilityNotify:int
16233 {
16234 VisibilityUnobscured		=0,
16235 VisibilityPartiallyObscured	=1,
16236 VisibilityFullyObscured		=2
16237 }
16238 
16239 
16240 enum WindowStackingMethod:int
16241 {
16242 	Above		=0,
16243 	Below		=1,
16244 	TopIf		=2,
16245 	BottomIf	=3,
16246 	Opposite	=4
16247 }
16248 
16249 /* Circulation request */
16250 enum CirculationRequest:int
16251 {
16252 	PlaceOnTop		=0,
16253 	PlaceOnBottom	=1
16254 }
16255 
16256 enum PropertyNotification:int
16257 {
16258 	PropertyNewValue	=0,
16259 	PropertyDelete		=1
16260 }
16261 
16262 enum ColorMapNotification:int
16263 {
16264 	ColormapUninstalled	=0,
16265 	ColormapInstalled		=1
16266 }
16267 
16268 
16269 	struct _XPrivate {}
16270 	struct _XrmHashBucketRec {}
16271 
16272 	alias void* XPointer;
16273 	alias void* XExtData;
16274 
16275 	version( X86_64 ) {
16276 		alias ulong XID;
16277 		alias ulong arch_ulong;
16278 		alias long arch_long;
16279 	} else version (AArch64) {
16280 		alias ulong XID;
16281 		alias ulong arch_ulong;
16282 		alias long arch_long;
16283 	} else {
16284 		alias uint XID;
16285 		alias uint arch_ulong;
16286 		alias int arch_long;
16287 	}
16288 
16289 	alias XID Window;
16290 	alias XID Drawable;
16291 	alias XID Pixmap;
16292 
16293 	alias arch_ulong Atom;
16294 	alias int Bool;
16295 	alias Display XDisplay;
16296 
16297 	alias int ByteOrder;
16298 	alias arch_ulong Time;
16299 	alias void ScreenFormat;
16300 
16301 	struct XImage {
16302 		int width, height;			/* size of image */
16303 		int xoffset;				/* number of pixels offset in X direction */
16304 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16305 		void *data;					/* pointer to image data */
16306 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16307 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16308 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16309 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16310 		int depth;					/* depth of image */
16311 		int bytes_per_line;			/* accelarator to next line */
16312 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16313 		arch_ulong red_mask;	/* bits in z arrangment */
16314 		arch_ulong green_mask;
16315 		arch_ulong blue_mask;
16316 		XPointer obdata;			/* hook for the object routines to hang on */
16317 		static struct F {				/* image manipulation routines */
16318 			XImage* function(
16319 				XDisplay* 			/* display */,
16320 				Visual*				/* visual */,
16321 				uint				/* depth */,
16322 				int					/* format */,
16323 				int					/* offset */,
16324 				ubyte*				/* data */,
16325 				uint				/* width */,
16326 				uint				/* height */,
16327 				int					/* bitmap_pad */,
16328 				int					/* bytes_per_line */) create_image;
16329 			int function(XImage *) destroy_image;
16330 			arch_ulong function(XImage *, int, int) get_pixel;
16331 			int function(XImage *, int, int, arch_ulong) put_pixel;
16332 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16333 			int function(XImage *, arch_long) add_pixel;
16334 		}
16335 		F f;
16336 	}
16337 	version(X86_64) static assert(XImage.sizeof == 136);
16338 	else version(X86) static assert(XImage.sizeof == 88);
16339 
16340 struct XCharStruct {
16341 	short       lbearing;       /* origin to left edge of raster */
16342 	short       rbearing;       /* origin to right edge of raster */
16343 	short       width;          /* advance to next char's origin */
16344 	short       ascent;         /* baseline to top edge of raster */
16345 	short       descent;        /* baseline to bottom edge of raster */
16346 	ushort attributes;  /* per char flags (not predefined) */
16347 }
16348 
16349 /*
16350  * To allow arbitrary information with fonts, there are additional properties
16351  * returned.
16352  */
16353 struct XFontProp {
16354 	Atom name;
16355 	arch_ulong card32;
16356 }
16357 
16358 alias Atom Font;
16359 
16360 struct XFontStruct {
16361 	XExtData *ext_data;           /* Hook for extension to hang data */
16362 	Font fid;                     /* Font ID for this font */
16363 	uint direction;           /* Direction the font is painted */
16364 	uint min_char_or_byte2;   /* First character */
16365 	uint max_char_or_byte2;   /* Last character */
16366 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16367 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16368 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16369 	uint default_char;        /* Char to print for undefined character */
16370 	int n_properties;             /* How many properties there are */
16371 	XFontProp *properties;        /* Pointer to array of additional properties*/
16372 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16373 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16374 	XCharStruct *per_char;        /* first_char to last_char information */
16375 	int ascent;                   /* Max extent above baseline for spacing */
16376 	int descent;                  /* Max descent below baseline for spacing */
16377 }
16378 
16379 
16380 /*
16381  * Definitions of specific events.
16382  */
16383 struct XKeyEvent
16384 {
16385 	int type;			/* of event */
16386 	arch_ulong serial;		/* # of last request processed by server */
16387 	Bool send_event;	/* true if this came from a SendEvent request */
16388 	Display *display;	/* Display the event was read from */
16389 	Window window;	        /* "event" window it is reported relative to */
16390 	Window root;	        /* root window that the event occurred on */
16391 	Window subwindow;	/* child window */
16392 	Time time;		/* milliseconds */
16393 	int x, y;		/* pointer x, y coordinates in event window */
16394 	int x_root, y_root;	/* coordinates relative to root */
16395 	KeyOrButtonMask state;	/* key or button mask */
16396 	uint keycode;	/* detail */
16397 	Bool same_screen;	/* same screen flag */
16398 }
16399 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16400 alias XKeyEvent XKeyPressedEvent;
16401 alias XKeyEvent XKeyReleasedEvent;
16402 
16403 struct XButtonEvent
16404 {
16405 	int type;		/* of event */
16406 	arch_ulong serial;	/* # of last request processed by server */
16407 	Bool send_event;	/* true if this came from a SendEvent request */
16408 	Display *display;	/* Display the event was read from */
16409 	Window window;	        /* "event" window it is reported relative to */
16410 	Window root;	        /* root window that the event occurred on */
16411 	Window subwindow;	/* child window */
16412 	Time time;		/* milliseconds */
16413 	int x, y;		/* pointer x, y coordinates in event window */
16414 	int x_root, y_root;	/* coordinates relative to root */
16415 	KeyOrButtonMask state;	/* key or button mask */
16416 	uint button;	/* detail */
16417 	Bool same_screen;	/* same screen flag */
16418 }
16419 alias XButtonEvent XButtonPressedEvent;
16420 alias XButtonEvent XButtonReleasedEvent;
16421 
16422 struct XMotionEvent{
16423 	int type;		/* of event */
16424 	arch_ulong serial;	/* # of last request processed by server */
16425 	Bool send_event;	/* true if this came from a SendEvent request */
16426 	Display *display;	/* Display the event was read from */
16427 	Window window;	        /* "event" window reported relative to */
16428 	Window root;	        /* root window that the event occurred on */
16429 	Window subwindow;	/* child window */
16430 	Time time;		/* milliseconds */
16431 	int x, y;		/* pointer x, y coordinates in event window */
16432 	int x_root, y_root;	/* coordinates relative to root */
16433 	KeyOrButtonMask state;	/* key or button mask */
16434 	byte is_hint;		/* detail */
16435 	Bool same_screen;	/* same screen flag */
16436 }
16437 alias XMotionEvent XPointerMovedEvent;
16438 
16439 struct XCrossingEvent{
16440 	int type;		/* of event */
16441 	arch_ulong serial;	/* # of last request processed by server */
16442 	Bool send_event;	/* true if this came from a SendEvent request */
16443 	Display *display;	/* Display the event was read from */
16444 	Window window;	        /* "event" window reported relative to */
16445 	Window root;	        /* root window that the event occurred on */
16446 	Window subwindow;	/* child window */
16447 	Time time;		/* milliseconds */
16448 	int x, y;		/* pointer x, y coordinates in event window */
16449 	int x_root, y_root;	/* coordinates relative to root */
16450 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16451 	NotifyDetail detail;
16452 	/*
16453 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16454 	 * NotifyNonlinear,NotifyNonlinearVirtual
16455 	 */
16456 	Bool same_screen;	/* same screen flag */
16457 	Bool focus;		/* Boolean focus */
16458 	KeyOrButtonMask state;	/* key or button mask */
16459 }
16460 alias XCrossingEvent XEnterWindowEvent;
16461 alias XCrossingEvent XLeaveWindowEvent;
16462 
16463 struct XFocusChangeEvent{
16464 	int type;		/* FocusIn or FocusOut */
16465 	arch_ulong serial;	/* # of last request processed by server */
16466 	Bool send_event;	/* true if this came from a SendEvent request */
16467 	Display *display;	/* Display the event was read from */
16468 	Window window;		/* window of event */
16469 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16470 				   NotifyGrab, NotifyUngrab */
16471 	NotifyDetail detail;
16472 	/*
16473 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16474 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16475 	 * NotifyPointerRoot, NotifyDetailNone
16476 	 */
16477 }
16478 alias XFocusChangeEvent XFocusInEvent;
16479 alias XFocusChangeEvent XFocusOutEvent;
16480 
16481 enum CWBackPixmap              = (1L<<0);
16482 enum CWBackPixel               = (1L<<1);
16483 enum CWBorderPixmap            = (1L<<2);
16484 enum CWBorderPixel             = (1L<<3);
16485 enum CWBitGravity              = (1L<<4);
16486 enum CWWinGravity              = (1L<<5);
16487 enum CWBackingStore            = (1L<<6);
16488 enum CWBackingPlanes           = (1L<<7);
16489 enum CWBackingPixel            = (1L<<8);
16490 enum CWOverrideRedirect        = (1L<<9);
16491 enum CWSaveUnder               = (1L<<10);
16492 enum CWEventMask               = (1L<<11);
16493 enum CWDontPropagate           = (1L<<12);
16494 enum CWColormap                = (1L<<13);
16495 enum CWCursor                  = (1L<<14);
16496 
16497 struct XWindowAttributes {
16498 	int x, y;			/* location of window */
16499 	int width, height;		/* width and height of window */
16500 	int border_width;		/* border width of window */
16501 	int depth;			/* depth of window */
16502 	Visual *visual;			/* the associated visual structure */
16503 	Window root;			/* root of screen containing window */
16504 	int class_;			/* InputOutput, InputOnly*/
16505 	int bit_gravity;		/* one of the bit gravity values */
16506 	int win_gravity;		/* one of the window gravity values */
16507 	int backing_store;		/* NotUseful, WhenMapped, Always */
16508 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16509 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16510 	Bool save_under;		/* boolean, should bits under be saved? */
16511 	Colormap colormap;		/* color map to be associated with window */
16512 	Bool map_installed;		/* boolean, is color map currently installed*/
16513 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16514 	arch_long all_event_masks;		/* set of events all people have interest in*/
16515 	arch_long your_event_mask;		/* my event mask */
16516 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16517 	Bool override_redirect;		/* boolean value for override-redirect */
16518 	Screen *screen;			/* back pointer to correct screen */
16519 }
16520 
16521 enum IsUnmapped = 0;
16522 enum IsUnviewable = 1;
16523 enum IsViewable = 2;
16524 
16525 struct XSetWindowAttributes {
16526 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16527 	arch_ulong background_pixel;/* background pixel */
16528 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16529 	arch_ulong border_pixel;/* border pixel value */
16530 	int bit_gravity;         /* one of bit gravity values */
16531 	int win_gravity;         /* one of the window gravity values */
16532 	int backing_store;       /* NotUseful, WhenMapped, Always */
16533 	arch_ulong backing_planes;/* planes to be preserved if possible */
16534 	arch_ulong backing_pixel;/* value to use in restoring planes */
16535 	Bool save_under;         /* should bits under be saved? (popups) */
16536 	arch_long event_mask;         /* set of events that should be saved */
16537 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16538 	Bool override_redirect;  /* boolean value for override_redirect */
16539 	Colormap colormap;       /* color map to be associated with window */
16540 	Cursor cursor;           /* cursor to be displayed (or None) */
16541 }
16542 
16543 
16544 alias int Status;
16545 
16546 
16547 enum EventMask:int
16548 {
16549 	NoEventMask				=0,
16550 	KeyPressMask			=1<<0,
16551 	KeyReleaseMask			=1<<1,
16552 	ButtonPressMask			=1<<2,
16553 	ButtonReleaseMask		=1<<3,
16554 	EnterWindowMask			=1<<4,
16555 	LeaveWindowMask			=1<<5,
16556 	PointerMotionMask		=1<<6,
16557 	PointerMotionHintMask	=1<<7,
16558 	Button1MotionMask		=1<<8,
16559 	Button2MotionMask		=1<<9,
16560 	Button3MotionMask		=1<<10,
16561 	Button4MotionMask		=1<<11,
16562 	Button5MotionMask		=1<<12,
16563 	ButtonMotionMask		=1<<13,
16564 	KeymapStateMask		=1<<14,
16565 	ExposureMask			=1<<15,
16566 	VisibilityChangeMask	=1<<16,
16567 	StructureNotifyMask		=1<<17,
16568 	ResizeRedirectMask		=1<<18,
16569 	SubstructureNotifyMask	=1<<19,
16570 	SubstructureRedirectMask=1<<20,
16571 	FocusChangeMask			=1<<21,
16572 	PropertyChangeMask		=1<<22,
16573 	ColormapChangeMask		=1<<23,
16574 	OwnerGrabButtonMask		=1<<24
16575 }
16576 
16577 struct MwmHints {
16578 	c_ulong flags;
16579 	c_ulong functions;
16580 	c_ulong decorations;
16581 	c_long input_mode;
16582 	c_ulong status;
16583 }
16584 
16585 enum {
16586 	MWM_HINTS_FUNCTIONS = (1L << 0),
16587 	MWM_HINTS_DECORATIONS =  (1L << 1),
16588 
16589 	MWM_FUNC_ALL = (1L << 0),
16590 	MWM_FUNC_RESIZE = (1L << 1),
16591 	MWM_FUNC_MOVE = (1L << 2),
16592 	MWM_FUNC_MINIMIZE = (1L << 3),
16593 	MWM_FUNC_MAXIMIZE = (1L << 4),
16594 	MWM_FUNC_CLOSE = (1L << 5),
16595 
16596 	MWM_DECOR_ALL = (1L << 0),
16597 	MWM_DECOR_BORDER = (1L << 1),
16598 	MWM_DECOR_RESIZEH = (1L << 2),
16599 	MWM_DECOR_TITLE = (1L << 3),
16600 	MWM_DECOR_MENU = (1L << 4),
16601 	MWM_DECOR_MINIMIZE = (1L << 5),
16602 	MWM_DECOR_MAXIMIZE = (1L << 6),
16603 }
16604 
16605 import core.stdc.config : c_long, c_ulong;
16606 
16607 	/* Size hints mask bits */
16608 
16609 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16610 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16611 	enum   PPosition   = (1L << 2)          /* program specified position */;
16612 	enum   PSize       = (1L << 3)          /* program specified size */;
16613 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16614 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16615 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16616 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16617 	enum   PBaseSize   = (1L << 8);
16618 	enum   PWinGravity = (1L << 9);
16619 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16620 	struct XSizeHints {
16621 		arch_long flags;         /* marks which fields in this structure are defined */
16622 		int x, y;           /* Obsolete */
16623 		int width, height;  /* Obsolete */
16624 		int min_width, min_height;
16625 		int max_width, max_height;
16626 		int width_inc, height_inc;
16627 		struct Aspect {
16628 			int x;       /* numerator */
16629 			int y;       /* denominator */
16630 		}
16631 
16632 		Aspect min_aspect;
16633 		Aspect max_aspect;
16634 		int base_width, base_height;
16635 		int win_gravity;
16636 		/* this structure may be extended in the future */
16637 	}
16638 
16639 
16640 
16641 enum EventType:int
16642 {
16643 	KeyPress			=2,
16644 	KeyRelease			=3,
16645 	ButtonPress			=4,
16646 	ButtonRelease		=5,
16647 	MotionNotify		=6,
16648 	EnterNotify			=7,
16649 	LeaveNotify			=8,
16650 	FocusIn				=9,
16651 	FocusOut			=10,
16652 	KeymapNotify		=11,
16653 	Expose				=12,
16654 	GraphicsExpose		=13,
16655 	NoExpose			=14,
16656 	VisibilityNotify	=15,
16657 	CreateNotify		=16,
16658 	DestroyNotify		=17,
16659 	UnmapNotify		=18,
16660 	MapNotify			=19,
16661 	MapRequest			=20,
16662 	ReparentNotify		=21,
16663 	ConfigureNotify		=22,
16664 	ConfigureRequest	=23,
16665 	GravityNotify		=24,
16666 	ResizeRequest		=25,
16667 	CirculateNotify		=26,
16668 	CirculateRequest	=27,
16669 	PropertyNotify		=28,
16670 	SelectionClear		=29,
16671 	SelectionRequest	=30,
16672 	SelectionNotify		=31,
16673 	ColormapNotify		=32,
16674 	ClientMessage		=33,
16675 	MappingNotify		=34,
16676 	LASTEvent			=35	/* must be bigger than any event # */
16677 }
16678 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16679 struct XKeymapEvent
16680 {
16681 	int type;
16682 	arch_ulong serial;	/* # of last request processed by server */
16683 	Bool send_event;	/* true if this came from a SendEvent request */
16684 	Display *display;	/* Display the event was read from */
16685 	Window window;
16686 	byte[32] key_vector;
16687 }
16688 
16689 struct XExposeEvent
16690 {
16691 	int type;
16692 	arch_ulong serial;	/* # of last request processed by server */
16693 	Bool send_event;	/* true if this came from a SendEvent request */
16694 	Display *display;	/* Display the event was read from */
16695 	Window window;
16696 	int x, y;
16697 	int width, height;
16698 	int count;		/* if non-zero, at least this many more */
16699 }
16700 
16701 struct XGraphicsExposeEvent{
16702 	int type;
16703 	arch_ulong serial;	/* # of last request processed by server */
16704 	Bool send_event;	/* true if this came from a SendEvent request */
16705 	Display *display;	/* Display the event was read from */
16706 	Drawable drawable;
16707 	int x, y;
16708 	int width, height;
16709 	int count;		/* if non-zero, at least this many more */
16710 	int major_code;		/* core is CopyArea or CopyPlane */
16711 	int minor_code;		/* not defined in the core */
16712 }
16713 
16714 struct XNoExposeEvent{
16715 	int type;
16716 	arch_ulong serial;	/* # of last request processed by server */
16717 	Bool send_event;	/* true if this came from a SendEvent request */
16718 	Display *display;	/* Display the event was read from */
16719 	Drawable drawable;
16720 	int major_code;		/* core is CopyArea or CopyPlane */
16721 	int minor_code;		/* not defined in the core */
16722 }
16723 
16724 struct XVisibilityEvent{
16725 	int type;
16726 	arch_ulong serial;	/* # of last request processed by server */
16727 	Bool send_event;	/* true if this came from a SendEvent request */
16728 	Display *display;	/* Display the event was read from */
16729 	Window window;
16730 	VisibilityNotify state;		/* Visibility state */
16731 }
16732 
16733 struct XCreateWindowEvent{
16734 	int type;
16735 	arch_ulong serial;	/* # of last request processed by server */
16736 	Bool send_event;	/* true if this came from a SendEvent request */
16737 	Display *display;	/* Display the event was read from */
16738 	Window parent;		/* parent of the window */
16739 	Window window;		/* window id of window created */
16740 	int x, y;		/* window location */
16741 	int width, height;	/* size of window */
16742 	int border_width;	/* border width */
16743 	Bool override_redirect;	/* creation should be overridden */
16744 }
16745 
16746 struct XDestroyWindowEvent
16747 {
16748 	int type;
16749 	arch_ulong serial;		/* # of last request processed by server */
16750 	Bool send_event;	/* true if this came from a SendEvent request */
16751 	Display *display;	/* Display the event was read from */
16752 	Window event;
16753 	Window window;
16754 }
16755 
16756 struct XUnmapEvent
16757 {
16758 	int type;
16759 	arch_ulong serial;		/* # of last request processed by server */
16760 	Bool send_event;	/* true if this came from a SendEvent request */
16761 	Display *display;	/* Display the event was read from */
16762 	Window event;
16763 	Window window;
16764 	Bool from_configure;
16765 }
16766 
16767 struct XMapEvent
16768 {
16769 	int type;
16770 	arch_ulong serial;		/* # of last request processed by server */
16771 	Bool send_event;	/* true if this came from a SendEvent request */
16772 	Display *display;	/* Display the event was read from */
16773 	Window event;
16774 	Window window;
16775 	Bool override_redirect;	/* Boolean, is override set... */
16776 }
16777 
16778 struct XMapRequestEvent
16779 {
16780 	int type;
16781 	arch_ulong serial;	/* # of last request processed by server */
16782 	Bool send_event;	/* true if this came from a SendEvent request */
16783 	Display *display;	/* Display the event was read from */
16784 	Window parent;
16785 	Window window;
16786 }
16787 
16788 struct XReparentEvent
16789 {
16790 	int type;
16791 	arch_ulong serial;	/* # of last request processed by server */
16792 	Bool send_event;	/* true if this came from a SendEvent request */
16793 	Display *display;	/* Display the event was read from */
16794 	Window event;
16795 	Window window;
16796 	Window parent;
16797 	int x, y;
16798 	Bool override_redirect;
16799 }
16800 
16801 struct XConfigureEvent
16802 {
16803 	int type;
16804 	arch_ulong serial;	/* # of last request processed by server */
16805 	Bool send_event;	/* true if this came from a SendEvent request */
16806 	Display *display;	/* Display the event was read from */
16807 	Window event;
16808 	Window window;
16809 	int x, y;
16810 	int width, height;
16811 	int border_width;
16812 	Window above;
16813 	Bool override_redirect;
16814 }
16815 
16816 struct XGravityEvent
16817 {
16818 	int type;
16819 	arch_ulong serial;	/* # of last request processed by server */
16820 	Bool send_event;	/* true if this came from a SendEvent request */
16821 	Display *display;	/* Display the event was read from */
16822 	Window event;
16823 	Window window;
16824 	int x, y;
16825 }
16826 
16827 struct XResizeRequestEvent
16828 {
16829 	int type;
16830 	arch_ulong serial;	/* # of last request processed by server */
16831 	Bool send_event;	/* true if this came from a SendEvent request */
16832 	Display *display;	/* Display the event was read from */
16833 	Window window;
16834 	int width, height;
16835 }
16836 
16837 struct  XConfigureRequestEvent
16838 {
16839 	int type;
16840 	arch_ulong serial;	/* # of last request processed by server */
16841 	Bool send_event;	/* true if this came from a SendEvent request */
16842 	Display *display;	/* Display the event was read from */
16843 	Window parent;
16844 	Window window;
16845 	int x, y;
16846 	int width, height;
16847 	int border_width;
16848 	Window above;
16849 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
16850 	arch_ulong value_mask;
16851 }
16852 
16853 struct XCirculateEvent
16854 {
16855 	int type;
16856 	arch_ulong serial;	/* # of last request processed by server */
16857 	Bool send_event;	/* true if this came from a SendEvent request */
16858 	Display *display;	/* Display the event was read from */
16859 	Window event;
16860 	Window window;
16861 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16862 }
16863 
16864 struct XCirculateRequestEvent
16865 {
16866 	int type;
16867 	arch_ulong serial;	/* # of last request processed by server */
16868 	Bool send_event;	/* true if this came from a SendEvent request */
16869 	Display *display;	/* Display the event was read from */
16870 	Window parent;
16871 	Window window;
16872 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16873 }
16874 
16875 struct XPropertyEvent
16876 {
16877 	int type;
16878 	arch_ulong serial;	/* # of last request processed by server */
16879 	Bool send_event;	/* true if this came from a SendEvent request */
16880 	Display *display;	/* Display the event was read from */
16881 	Window window;
16882 	Atom atom;
16883 	Time time;
16884 	PropertyNotification state;		/* NewValue, Deleted */
16885 }
16886 
16887 struct XSelectionClearEvent
16888 {
16889 	int type;
16890 	arch_ulong serial;	/* # of last request processed by server */
16891 	Bool send_event;	/* true if this came from a SendEvent request */
16892 	Display *display;	/* Display the event was read from */
16893 	Window window;
16894 	Atom selection;
16895 	Time time;
16896 }
16897 
16898 struct XSelectionRequestEvent
16899 {
16900 	int type;
16901 	arch_ulong serial;	/* # of last request processed by server */
16902 	Bool send_event;	/* true if this came from a SendEvent request */
16903 	Display *display;	/* Display the event was read from */
16904 	Window owner;
16905 	Window requestor;
16906 	Atom selection;
16907 	Atom target;
16908 	Atom property;
16909 	Time time;
16910 }
16911 
16912 struct XSelectionEvent
16913 {
16914 	int type;
16915 	arch_ulong serial;	/* # of last request processed by server */
16916 	Bool send_event;	/* true if this came from a SendEvent request */
16917 	Display *display;	/* Display the event was read from */
16918 	Window requestor;
16919 	Atom selection;
16920 	Atom target;
16921 	Atom property;		/* ATOM or None */
16922 	Time time;
16923 }
16924 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
16925 
16926 struct XColormapEvent
16927 {
16928 	int type;
16929 	arch_ulong serial;	/* # of last request processed by server */
16930 	Bool send_event;	/* true if this came from a SendEvent request */
16931 	Display *display;	/* Display the event was read from */
16932 	Window window;
16933 	Colormap colormap;	/* COLORMAP or None */
16934 	Bool new_;		/* C++ */
16935 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
16936 }
16937 version(X86_64) static assert(XColormapEvent.sizeof == 56);
16938 
16939 struct XClientMessageEvent
16940 {
16941 	int type;
16942 	arch_ulong serial;	/* # of last request processed by server */
16943 	Bool send_event;	/* true if this came from a SendEvent request */
16944 	Display *display;	/* Display the event was read from */
16945 	Window window;
16946 	Atom message_type;
16947 	int format;
16948 	union Data{
16949 		byte[20] b;
16950 		short[10] s;
16951 		arch_ulong[5] l;
16952 	}
16953 	Data data;
16954 
16955 }
16956 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
16957 
16958 struct XMappingEvent
16959 {
16960 	int type;
16961 	arch_ulong serial;	/* # of last request processed by server */
16962 	Bool send_event;	/* true if this came from a SendEvent request */
16963 	Display *display;	/* Display the event was read from */
16964 	Window window;		/* unused */
16965 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
16966 				   MappingPointer */
16967 	int first_keycode;	/* first keycode */
16968 	int count;		/* defines range of change w. first_keycode*/
16969 }
16970 
16971 struct XErrorEvent
16972 {
16973 	int type;
16974 	Display *display;	/* Display the event was read from */
16975 	XID resourceid;		/* resource id */
16976 	arch_ulong serial;	/* serial number of failed request */
16977 	ubyte error_code;	/* error code of failed request */
16978 	ubyte request_code;	/* Major op-code of failed request */
16979 	ubyte minor_code;	/* Minor op-code of failed request */
16980 }
16981 
16982 struct XAnyEvent
16983 {
16984 	int type;
16985 	arch_ulong serial;	/* # of last request processed by server */
16986 	Bool send_event;	/* true if this came from a SendEvent request */
16987 	Display *display;/* Display the event was read from */
16988 	Window window;	/* window on which event was requested in event mask */
16989 }
16990 
16991 union XEvent{
16992 	int type;		/* must not be changed; first element */
16993 	XAnyEvent xany;
16994 	XKeyEvent xkey;
16995 	XButtonEvent xbutton;
16996 	XMotionEvent xmotion;
16997 	XCrossingEvent xcrossing;
16998 	XFocusChangeEvent xfocus;
16999 	XExposeEvent xexpose;
17000 	XGraphicsExposeEvent xgraphicsexpose;
17001 	XNoExposeEvent xnoexpose;
17002 	XVisibilityEvent xvisibility;
17003 	XCreateWindowEvent xcreatewindow;
17004 	XDestroyWindowEvent xdestroywindow;
17005 	XUnmapEvent xunmap;
17006 	XMapEvent xmap;
17007 	XMapRequestEvent xmaprequest;
17008 	XReparentEvent xreparent;
17009 	XConfigureEvent xconfigure;
17010 	XGravityEvent xgravity;
17011 	XResizeRequestEvent xresizerequest;
17012 	XConfigureRequestEvent xconfigurerequest;
17013 	XCirculateEvent xcirculate;
17014 	XCirculateRequestEvent xcirculaterequest;
17015 	XPropertyEvent xproperty;
17016 	XSelectionClearEvent xselectionclear;
17017 	XSelectionRequestEvent xselectionrequest;
17018 	XSelectionEvent xselection;
17019 	XColormapEvent xcolormap;
17020 	XClientMessageEvent xclient;
17021 	XMappingEvent xmapping;
17022 	XErrorEvent xerror;
17023 	XKeymapEvent xkeymap;
17024 	arch_ulong[24] pad;
17025 }
17026 
17027 
17028 	struct Display {
17029 		XExtData *ext_data;	/* hook for extension to hang data */
17030 		_XPrivate *private1;
17031 		int fd;			/* Network socket. */
17032 		int private2;
17033 		int proto_major_version;/* major version of server's X protocol */
17034 		int proto_minor_version;/* minor version of servers X protocol */
17035 		char *vendor;		/* vendor of the server hardware */
17036 	    	XID private3;
17037 		XID private4;
17038 		XID private5;
17039 		int private6;
17040 		XID function(Display*)resource_alloc;/* allocator function */
17041 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17042 		int bitmap_unit;	/* padding and data requirements */
17043 		int bitmap_pad;		/* padding requirements on bitmaps */
17044 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17045 		int nformats;		/* number of pixmap formats in list */
17046 		ScreenFormat *pixmap_format;	/* pixmap format list */
17047 		int private8;
17048 		int release;		/* release of the server */
17049 		_XPrivate *private9;
17050 		_XPrivate *private10;
17051 		int qlen;		/* Length of input event queue */
17052 		arch_ulong last_request_read; /* seq number of last event read */
17053 		arch_ulong request;	/* sequence number of last request. */
17054 		XPointer private11;
17055 		XPointer private12;
17056 		XPointer private13;
17057 		XPointer private14;
17058 		uint max_request_size; /* maximum number 32 bit words in request*/
17059 		_XrmHashBucketRec *db;
17060 		int function  (Display*)private15;
17061 		char *display_name;	/* "host:display" string used on this connect*/
17062 		int default_screen;	/* default screen for operations */
17063 		int nscreens;		/* number of screens on this server*/
17064 		Screen *screens;	/* pointer to list of screens */
17065 		arch_ulong motion_buffer;	/* size of motion buffer */
17066 		arch_ulong private16;
17067 		int min_keycode;	/* minimum defined keycode */
17068 		int max_keycode;	/* maximum defined keycode */
17069 		XPointer private17;
17070 		XPointer private18;
17071 		int private19;
17072 		byte *xdefaults;	/* contents of defaults from server */
17073 		/* there is more to this structure, but it is private to Xlib */
17074 	}
17075 
17076 	// I got these numbers from a C program as a sanity test
17077 	version(X86_64) {
17078 		static assert(Display.sizeof == 296);
17079 		static assert(XPointer.sizeof == 8);
17080 		static assert(XErrorEvent.sizeof == 40);
17081 		static assert(XAnyEvent.sizeof == 40);
17082 		static assert(XMappingEvent.sizeof == 56);
17083 		static assert(XEvent.sizeof == 192);
17084     	} else version (AArch64) {
17085         	// omit check for aarch64
17086 	} else {
17087 		static assert(Display.sizeof == 176);
17088 		static assert(XPointer.sizeof == 4);
17089 		static assert(XEvent.sizeof == 96);
17090 	}
17091 
17092 struct Depth
17093 {
17094 	int depth;		/* this depth (Z) of the depth */
17095 	int nvisuals;		/* number of Visual types at this depth */
17096 	Visual *visuals;	/* list of visuals possible at this depth */
17097 }
17098 
17099 alias void* GC;
17100 alias c_ulong VisualID;
17101 alias XID Colormap;
17102 alias XID Cursor;
17103 alias XID KeySym;
17104 alias uint KeyCode;
17105 enum None = 0;
17106 }
17107 
17108 version(without_opengl) {}
17109 else {
17110 extern(C) nothrow @nogc {
17111 
17112 
17113 static if(!SdpyIsUsingIVGLBinds) {
17114 enum GLX_USE_GL=            1;       /* support GLX rendering */
17115 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17116 enum GLX_LEVEL=             3;       /* level in plane stacking */
17117 enum GLX_RGBA=              4;       /* true if RGBA mode */
17118 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17119 enum GLX_STEREO=            6;       /* stereo buffering supported */
17120 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17121 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17122 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17123 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17124 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17125 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17126 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17127 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17128 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17129 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17130 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17131 
17132 
17133 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17134 
17135 
17136 
17137 enum GL_TRUE = 1;
17138 enum GL_FALSE = 0;
17139 alias int GLint;
17140 }
17141 
17142 alias XID GLXContextID;
17143 alias XID GLXPixmap;
17144 alias XID GLXDrawable;
17145 alias XID GLXPbuffer;
17146 alias XID GLXWindow;
17147 alias XID GLXFBConfigID;
17148 alias void* GLXContext;
17149 
17150 }
17151 }
17152 
17153 enum AllocNone = 0;
17154 
17155 extern(C) {
17156 	/* WARNING, this type not in Xlib spec */
17157 	extern(C) alias XIOErrorHandler = int function (Display* display);
17158 }
17159 
17160 extern(C) nothrow
17161 alias XErrorHandler = int function(Display*, XErrorEvent*);
17162 
17163 extern(C) nothrow @nogc {
17164 struct Screen{
17165 	XExtData *ext_data;		/* hook for extension to hang data */
17166 	Display *display;		/* back pointer to display structure */
17167 	Window root;			/* Root window id. */
17168 	int width, height;		/* width and height of screen */
17169 	int mwidth, mheight;	/* width and height of  in millimeters */
17170 	int ndepths;			/* number of depths possible */
17171 	Depth *depths;			/* list of allowable depths on the screen */
17172 	int root_depth;			/* bits per pixel */
17173 	Visual *root_visual;	/* root visual */
17174 	GC default_gc;			/* GC for the root root visual */
17175 	Colormap cmap;			/* default color map */
17176 	uint white_pixel;
17177 	uint black_pixel;		/* White and Black pixel values */
17178 	int max_maps, min_maps;	/* max and min color maps */
17179 	int backing_store;		/* Never, WhenMapped, Always */
17180 	bool save_unders;
17181 	int root_input_mask;	/* initial root input mask */
17182 }
17183 
17184 struct Visual
17185 {
17186 	XExtData *ext_data;	/* hook for extension to hang data */
17187 	VisualID visualid;	/* visual id of this visual */
17188 	int class_;			/* class of screen (monochrome, etc.) */
17189 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17190 	int bits_per_rgb;	/* log base 2 of distinct color values */
17191 	int map_entries;	/* color map entries */
17192 }
17193 
17194 	alias Display* _XPrivDisplay;
17195 
17196 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17197 		assert(dpy !is null);
17198 		return &dpy.screens[scr];
17199 	}
17200 
17201 	extern(D) Window RootWindow(Display *dpy,int scr) {
17202 		return ScreenOfDisplay(dpy,scr).root;
17203 	}
17204 
17205 	struct XWMHints {
17206 		arch_long flags;
17207 		Bool input;
17208 		int initial_state;
17209 		Pixmap icon_pixmap;
17210 		Window icon_window;
17211 		int icon_x, icon_y;
17212 		Pixmap icon_mask;
17213 		XID window_group;
17214 	}
17215 
17216 	struct XClassHint {
17217 		char* res_name;
17218 		char* res_class;
17219 	}
17220 
17221 	extern(D) int DefaultScreen(Display *dpy) {
17222 		return dpy.default_screen;
17223 	}
17224 
17225 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17226 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17227 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17228 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17229 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17230 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17231 
17232 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17233 
17234 	enum int AnyPropertyType = 0;
17235 	enum int Success = 0;
17236 
17237 	enum int RevertToNone = None;
17238 	enum int PointerRoot = 1;
17239 	enum Time CurrentTime = 0;
17240 	enum int RevertToPointerRoot = PointerRoot;
17241 	enum int RevertToParent = 2;
17242 
17243 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17244 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17245 	}
17246 
17247 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17248 		return ScreenOfDisplay(dpy,scr).root_visual;
17249 	}
17250 
17251 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17252 		return ScreenOfDisplay(dpy,scr).default_gc;
17253 	}
17254 
17255 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17256 		return ScreenOfDisplay(dpy,scr).black_pixel;
17257 	}
17258 
17259 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17260 		return ScreenOfDisplay(dpy,scr).white_pixel;
17261 	}
17262 
17263 	alias void* XFontSet; // i think
17264 	struct XmbTextItem {
17265 		char* chars;
17266 		int nchars;
17267 		int delta;
17268 		XFontSet font_set;
17269 	}
17270 
17271 	struct XTextItem {
17272 		char* chars;
17273 		int nchars;
17274 		int delta;
17275 		Font font;
17276 	}
17277 
17278 	enum {
17279 		GXclear        = 0x0, /* 0 */
17280 		GXand          = 0x1, /* src AND dst */
17281 		GXandReverse   = 0x2, /* src AND NOT dst */
17282 		GXcopy         = 0x3, /* src */
17283 		GXandInverted  = 0x4, /* NOT src AND dst */
17284 		GXnoop         = 0x5, /* dst */
17285 		GXxor          = 0x6, /* src XOR dst */
17286 		GXor           = 0x7, /* src OR dst */
17287 		GXnor          = 0x8, /* NOT src AND NOT dst */
17288 		GXequiv        = 0x9, /* NOT src XOR dst */
17289 		GXinvert       = 0xa, /* NOT dst */
17290 		GXorReverse    = 0xb, /* src OR NOT dst */
17291 		GXcopyInverted = 0xc, /* NOT src */
17292 		GXorInverted   = 0xd, /* NOT src OR dst */
17293 		GXnand         = 0xe, /* NOT src OR NOT dst */
17294 		GXset          = 0xf, /* 1 */
17295 	}
17296 	enum QueueMode : int {
17297 		QueuedAlready,
17298 		QueuedAfterReading,
17299 		QueuedAfterFlush
17300 	}
17301 
17302 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17303 
17304 	struct XPoint {
17305 		short x;
17306 		short y;
17307 	}
17308 
17309 	enum CoordMode:int {
17310 		CoordModeOrigin = 0,
17311 		CoordModePrevious = 1
17312 	}
17313 
17314 	enum PolygonShape:int {
17315 		Complex = 0,
17316 		Nonconvex = 1,
17317 		Convex = 2
17318 	}
17319 
17320 	struct XTextProperty {
17321 		const(char)* value;		/* same as Property routines */
17322 		Atom encoding;			/* prop type */
17323 		int format;				/* prop data format: 8, 16, or 32 */
17324 		arch_ulong nitems;		/* number of data items in value */
17325 	}
17326 
17327 	version( X86_64 ) {
17328 		static assert(XTextProperty.sizeof == 32);
17329 	}
17330 
17331 
17332 	struct XGCValues {
17333 		int function_;           /* logical operation */
17334 		arch_ulong plane_mask;/* plane mask */
17335 		arch_ulong foreground;/* foreground pixel */
17336 		arch_ulong background;/* background pixel */
17337 		int line_width;         /* line width */
17338 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17339 		int cap_style;          /* CapNotLast, CapButt,
17340 					   CapRound, CapProjecting */
17341 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17342 		int fill_style;         /* FillSolid, FillTiled,
17343 					   FillStippled, FillOpaeueStippled */
17344 		int fill_rule;          /* EvenOddRule, WindingRule */
17345 		int arc_mode;           /* ArcChord, ArcPieSlice */
17346 		Pixmap tile;            /* tile pixmap for tiling operations */
17347 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17348 		int ts_x_origin;        /* offset for tile or stipple operations */
17349 		int ts_y_origin;
17350 		Font font;              /* default text font for text operations */
17351 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17352 		Bool graphics_exposures;/* boolean, should exposures be generated */
17353 		int clip_x_origin;      /* origin for clipping */
17354 		int clip_y_origin;
17355 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17356 		int dash_offset;        /* patterned/dashed line information */
17357 		char dashes;
17358 	}
17359 
17360 	struct XColor {
17361 		arch_ulong pixel;
17362 		ushort red, green, blue;
17363 		byte flags;
17364 		byte pad;
17365 	}
17366 
17367 	struct XRectangle {
17368 		short x;
17369 		short y;
17370 		ushort width;
17371 		ushort height;
17372 	}
17373 
17374 	enum ClipByChildren = 0;
17375 	enum IncludeInferiors = 1;
17376 
17377 	enum Atom XA_PRIMARY = 1;
17378 	enum Atom XA_SECONDARY = 2;
17379 	enum Atom XA_STRING = 31;
17380 	enum Atom XA_CARDINAL = 6;
17381 	enum Atom XA_WM_NAME = 39;
17382 	enum Atom XA_ATOM = 4;
17383 	enum Atom XA_WINDOW = 33;
17384 	enum Atom XA_WM_HINTS = 35;
17385 	enum int PropModeAppend = 2;
17386 	enum int PropModeReplace = 0;
17387 	enum int PropModePrepend = 1;
17388 
17389 	enum int CopyFromParent = 0;
17390 	enum int InputOutput = 1;
17391 
17392 	// XWMHints
17393 	enum InputHint = 1 << 0;
17394 	enum StateHint = 1 << 1;
17395 	enum IconPixmapHint = (1L << 2);
17396 	enum IconWindowHint = (1L << 3);
17397 	enum IconPositionHint = (1L << 4);
17398 	enum IconMaskHint = (1L << 5);
17399 	enum WindowGroupHint = (1L << 6);
17400 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17401 	enum XUrgencyHint = (1L << 8);
17402 
17403 	// GC Components
17404 	enum GCFunction           =   (1L<<0);
17405 	enum GCPlaneMask         =    (1L<<1);
17406 	enum GCForeground       =     (1L<<2);
17407 	enum GCBackground      =      (1L<<3);
17408 	enum GCLineWidth      =       (1L<<4);
17409 	enum GCLineStyle     =        (1L<<5);
17410 	enum GCCapStyle     =         (1L<<6);
17411 	enum GCJoinStyle   =          (1L<<7);
17412 	enum GCFillStyle  =           (1L<<8);
17413 	enum GCFillRule  =            (1L<<9);
17414 	enum GCTile     =             (1L<<10);
17415 	enum GCStipple           =    (1L<<11);
17416 	enum GCTileStipXOrigin  =     (1L<<12);
17417 	enum GCTileStipYOrigin =      (1L<<13);
17418 	enum GCFont               =   (1L<<14);
17419 	enum GCSubwindowMode     =    (1L<<15);
17420 	enum GCGraphicsExposures=     (1L<<16);
17421 	enum GCClipXOrigin     =      (1L<<17);
17422 	enum GCClipYOrigin    =       (1L<<18);
17423 	enum GCClipMask      =        (1L<<19);
17424 	enum GCDashOffset   =         (1L<<20);
17425 	enum GCDashList    =          (1L<<21);
17426 	enum GCArcMode    =           (1L<<22);
17427 	enum GCLastBit   =            22;
17428 
17429 
17430 	enum int WithdrawnState = 0;
17431 	enum int NormalState = 1;
17432 	enum int IconicState = 3;
17433 
17434 }
17435 } else version (OSXCocoa) {
17436 private:
17437 	alias void* id;
17438 	alias void* Class;
17439 	alias void* SEL;
17440 	alias void* IMP;
17441 	alias void* Ivar;
17442 	alias byte BOOL;
17443 	alias const(void)* CFStringRef;
17444 	alias const(void)* CFAllocatorRef;
17445 	alias const(void)* CFTypeRef;
17446 	alias const(void)* CGContextRef;
17447 	alias const(void)* CGColorSpaceRef;
17448 	alias const(void)* CGImageRef;
17449 	alias ulong CGBitmapInfo;
17450 
17451 	struct objc_super {
17452 		id self;
17453 		Class superclass;
17454 	}
17455 
17456 	struct CFRange {
17457 		long location, length;
17458 	}
17459 
17460 	struct NSPoint {
17461 		double x, y;
17462 
17463 		static fromTuple(T)(T tupl) {
17464 			return NSPoint(tupl.tupleof);
17465 		}
17466 	}
17467 	struct NSSize {
17468 		double width, height;
17469 	}
17470 	struct NSRect {
17471 		NSPoint origin;
17472 		NSSize size;
17473 	}
17474 	alias NSPoint CGPoint;
17475 	alias NSSize CGSize;
17476 	alias NSRect CGRect;
17477 
17478 	struct CGAffineTransform {
17479 		double a, b, c, d, tx, ty;
17480 	}
17481 
17482 	enum NSApplicationActivationPolicyRegular = 0;
17483 	enum NSBackingStoreBuffered = 2;
17484 	enum kCFStringEncodingUTF8 = 0x08000100;
17485 
17486 	enum : size_t {
17487 		NSBorderlessWindowMask = 0,
17488 		NSTitledWindowMask = 1 << 0,
17489 		NSClosableWindowMask = 1 << 1,
17490 		NSMiniaturizableWindowMask = 1 << 2,
17491 		NSResizableWindowMask = 1 << 3,
17492 		NSTexturedBackgroundWindowMask = 1 << 8
17493 	}
17494 
17495 	enum : ulong {
17496 		kCGImageAlphaNone,
17497 		kCGImageAlphaPremultipliedLast,
17498 		kCGImageAlphaPremultipliedFirst,
17499 		kCGImageAlphaLast,
17500 		kCGImageAlphaFirst,
17501 		kCGImageAlphaNoneSkipLast,
17502 		kCGImageAlphaNoneSkipFirst
17503 	}
17504 	enum : ulong {
17505 		kCGBitmapAlphaInfoMask = 0x1F,
17506 		kCGBitmapFloatComponents = (1 << 8),
17507 		kCGBitmapByteOrderMask = 0x7000,
17508 		kCGBitmapByteOrderDefault = (0 << 12),
17509 		kCGBitmapByteOrder16Little = (1 << 12),
17510 		kCGBitmapByteOrder32Little = (2 << 12),
17511 		kCGBitmapByteOrder16Big = (3 << 12),
17512 		kCGBitmapByteOrder32Big = (4 << 12)
17513 	}
17514 	enum CGPathDrawingMode {
17515 		kCGPathFill,
17516 		kCGPathEOFill,
17517 		kCGPathStroke,
17518 		kCGPathFillStroke,
17519 		kCGPathEOFillStroke
17520 	}
17521 	enum objc_AssociationPolicy : size_t {
17522 		OBJC_ASSOCIATION_ASSIGN = 0,
17523 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
17524 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
17525 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
17526 		OBJC_ASSOCIATION_COPY = 0x303 //01403
17527 	}
17528 
17529 	extern(C) {
17530 		id objc_msgSend(id receiver, SEL selector, ...);
17531 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
17532 		id objc_getClass(const(char)* name);
17533 		SEL sel_registerName(const(char)* str);
17534 		Class objc_allocateClassPair(Class superclass, const(char)* name,
17535 									 size_t extra_bytes);
17536 		void objc_registerClassPair(Class cls);
17537 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
17538 		id objc_getAssociatedObject(id object, void* key);
17539 		void objc_setAssociatedObject(id object, void* key, id value,
17540 									  objc_AssociationPolicy policy);
17541 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
17542 		id object_getIvar(id object, Ivar ivar);
17543 		void object_setIvar(id object, Ivar ivar, id value);
17544 		BOOL class_addIvar(Class cls, const(char)* name,
17545 						   size_t size, ubyte alignment, const(char)* types);
17546 
17547 		extern __gshared id NSApp;
17548 
17549 		void CFRelease(CFTypeRef obj);
17550 
17551 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
17552 											const(char)* bytes, long numBytes,
17553 											long encoding,
17554 											BOOL isExternalRepresentation);
17555 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
17556 							 char lossByte, bool isExternalRepresentation,
17557 							 char* buffer, long maxBufLen, long* usedBufLen);
17558 		long CFStringGetLength(CFStringRef theString);
17559 
17560 		CGContextRef CGBitmapContextCreate(void* data,
17561 										   size_t width, size_t height,
17562 										   size_t bitsPerComponent,
17563 										   size_t bytesPerRow,
17564 										   CGColorSpaceRef colorspace,
17565 										   CGBitmapInfo bitmapInfo);
17566 		void CGContextRelease(CGContextRef c);
17567 		ubyte* CGBitmapContextGetData(CGContextRef c);
17568 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
17569 		size_t CGBitmapContextGetWidth(CGContextRef c);
17570 		size_t CGBitmapContextGetHeight(CGContextRef c);
17571 
17572 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
17573 		void CGColorSpaceRelease(CGColorSpaceRef cs);
17574 
17575 		void CGContextSetRGBStrokeColor(CGContextRef c,
17576 										double red, double green, double blue,
17577 										double alpha);
17578 		void CGContextSetRGBFillColor(CGContextRef c,
17579 									  double red, double green, double blue,
17580 									  double alpha);
17581 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
17582 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
17583 									  const(char)* str, size_t length);
17584 		void CGContextStrokeLineSegments(CGContextRef c,
17585 										 const(CGPoint)* points, size_t count);
17586 
17587 		void CGContextBeginPath(CGContextRef c);
17588 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
17589 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
17590 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
17591 							 double startAngle, double endAngle, long clockwise);
17592 		void CGContextAddRect(CGContextRef c, CGRect rect);
17593 		void CGContextAddLines(CGContextRef c,
17594 							   const(CGPoint)* points, size_t count);
17595 		void CGContextSaveGState(CGContextRef c);
17596 		void CGContextRestoreGState(CGContextRef c);
17597 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
17598 								 ulong textEncoding);
17599 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
17600 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
17601 
17602 		void CGImageRelease(CGImageRef image);
17603 	}
17604 
17605 private:
17606     // A convenient method to create a CFString (=NSString) from a D string.
17607     CFStringRef createCFString(string str) {
17608         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
17609                                              kCFStringEncodingUTF8, false);
17610     }
17611 
17612     // Objective-C calls.
17613     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
17614         auto _cmd = sel_registerName(selector.ptr);
17615         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17616         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
17617     }
17618     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
17619         auto _cmd = sel_registerName(selector.ptr);
17620         auto cls = objc_getClass(className);
17621         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17622         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
17623     }
17624     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
17625         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
17626     }
17627 
17628     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
17629     alias objc_msgSend_classMethod!("alloc", id) alloc;
17630     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
17631                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
17632     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
17633     alias objc_msgSend_specialized!("center", void) center;
17634     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
17635     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
17636     alias objc_msgSend_specialized!("release", void) release;
17637     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
17638     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
17639     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
17640     alias objc_msgSend_specialized!("invalidate", void) invalidate;
17641     alias objc_msgSend_specialized!("close", void) close;
17642     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
17643                                     id, double, id, SEL, id, BOOL) scheduledTimer;
17644     alias objc_msgSend_specialized!("run", void) run;
17645     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
17646                                     id) currentNSGraphicsContext;
17647     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
17648     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
17649     alias objc_msgSend_specialized!("superclass", Class) superclass;
17650     alias objc_msgSend_specialized!("init", id) init;
17651     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
17652     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
17653     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
17654                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
17655     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
17656     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
17657     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
17658                                     void, BOOL) activateIgnoringOtherApps;
17659     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
17660                                     id) sharedNSApplication;
17661     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
17662 } else static assert(0, "Unsupported operating system");
17663 
17664 
17665 version(OSXCocoa) {
17666 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
17667 	//
17668 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
17669 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
17670 	//
17671 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
17672 	// Probably won't even fully compile right now
17673 
17674     import std.math : PI;
17675     import std.algorithm : map;
17676     import std.array : array;
17677 
17678     alias SimpleWindow NativeWindowHandle;
17679     alias void delegate(id) NativeEventHandler;
17680 
17681     __gshared Ivar simpleWindowIvar;
17682 
17683     enum KEY_ESCAPE = 27;
17684 
17685     mixin template NativeImageImplementation() {
17686         CGContextRef context;
17687         ubyte* rawData;
17688     final:
17689 
17690 	void convertToRgbaBytes(ubyte[] where) {
17691 		assert(where.length == this.width * this.height * 4);
17692 
17693 		// if rawData had a length....
17694 		//assert(rawData.length == where.length);
17695 		for(long idx = 0; idx < where.length; idx += 4) {
17696 			auto alpha = rawData[idx + 3];
17697 			if(alpha == 255) {
17698 				where[idx + 0] = rawData[idx + 0]; // r
17699 				where[idx + 1] = rawData[idx + 1]; // g
17700 				where[idx + 2] = rawData[idx + 2]; // b
17701 				where[idx + 3] = rawData[idx + 3]; // a
17702 			} else {
17703 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
17704 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
17705 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
17706 				where[idx + 3] = rawData[idx + 3]; // a
17707 
17708 			}
17709 		}
17710 	}
17711 
17712 	void setFromRgbaBytes(in ubyte[] where) {
17713 		// FIXME: this is probably wrong
17714 		assert(where.length == this.width * this.height * 4);
17715 
17716 		// if rawData had a length....
17717 		//assert(rawData.length == where.length);
17718 		for(long idx = 0; idx < where.length; idx += 4) {
17719 			auto alpha = rawData[idx + 3];
17720 			if(alpha == 255) {
17721 				rawData[idx + 0] = where[idx + 0]; // r
17722 				rawData[idx + 1] = where[idx + 1]; // g
17723 				rawData[idx + 2] = where[idx + 2]; // b
17724 				rawData[idx + 3] = where[idx + 3]; // a
17725 			} else {
17726 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
17727 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
17728 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
17729 				rawData[idx + 3] = where[idx + 3]; // a
17730 
17731 			}
17732 		}
17733 	}
17734 
17735 
17736         void createImage(int width, int height, bool forcexshm=false) {
17737             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17738             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
17739                                             colorSpace,
17740                                             kCGImageAlphaPremultipliedLast
17741                                                    |kCGBitmapByteOrder32Big);
17742             CGColorSpaceRelease(colorSpace);
17743             rawData = CGBitmapContextGetData(context);
17744         }
17745         void dispose() {
17746             CGContextRelease(context);
17747         }
17748 
17749         void setPixel(int x, int y, Color c) {
17750             auto offset = (y * width + x) * 4;
17751             if (c.a == 255) {
17752                 rawData[offset + 0] = c.r;
17753                 rawData[offset + 1] = c.g;
17754                 rawData[offset + 2] = c.b;
17755                 rawData[offset + 3] = c.a;
17756             } else {
17757                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
17758                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
17759                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
17760                 rawData[offset + 3] = c.a;
17761             }
17762         }
17763     }
17764 
17765     mixin template NativeScreenPainterImplementation() {
17766         CGContextRef context;
17767         ubyte[4] _outlineComponents;
17768 	id view;
17769 
17770         void create(NativeWindowHandle window) {
17771             context = window.drawingContext;
17772 	    view = window.view;
17773         }
17774 
17775         void dispose() {
17776             	setNeedsDisplay(view, true);
17777         }
17778 
17779 	bool manualInvalidations;
17780 	void invalidateRect(Rectangle invalidRect) { }
17781 
17782 	// NotYetImplementedException
17783 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
17784 	void rasterOp(RasterOp op) {}
17785 	Pen _activePen;
17786 	Color _fillColor;
17787 	Rectangle _clipRectangle;
17788 	void setClipRectangle(int, int, int, int) {}
17789 	void setFont(OperatingSystemFont) {}
17790 	int fontHeight() { return 14; }
17791 
17792 	// end
17793 
17794         void pen(Pen pen) {
17795 	    _activePen = pen;
17796 	    auto color = pen.color; // FIXME
17797             double alphaComponent = color.a/255.0f;
17798             CGContextSetRGBStrokeColor(context,
17799                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
17800 
17801             if (color.a != 255) {
17802                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
17803                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
17804                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
17805                 _outlineComponents[3] = color.a;
17806             } else {
17807                 _outlineComponents[0] = color.r;
17808                 _outlineComponents[1] = color.g;
17809                 _outlineComponents[2] = color.b;
17810                 _outlineComponents[3] = color.a;
17811             }
17812         }
17813 
17814         @property void fillColor(Color color) {
17815             CGContextSetRGBFillColor(context,
17816                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
17817         }
17818 
17819         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
17820 		// NotYetImplementedException for upper left/width/height
17821             auto cgImage = CGBitmapContextCreateImage(image.context);
17822             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17823                                CGBitmapContextGetHeight(image.context));
17824             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17825             CGImageRelease(cgImage);
17826         }
17827 
17828 	version(OSXCocoa) {} else // NotYetImplementedException
17829         void drawPixmap(Sprite image, int x, int y) {
17830 		// FIXME: is this efficient?
17831             auto cgImage = CGBitmapContextCreateImage(image.context);
17832             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17833                                CGBitmapContextGetHeight(image.context));
17834             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17835             CGImageRelease(cgImage);
17836         }
17837 
17838 
17839         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
17840 		// FIXME: alignment
17841             if (_outlineComponents[3] != 0) {
17842                 CGContextSaveGState(context);
17843                 auto invAlpha = 1.0f/_outlineComponents[3];
17844                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
17845                                                   _outlineComponents[1]*invAlpha,
17846                                                   _outlineComponents[2]*invAlpha,
17847                                                   _outlineComponents[3]/255.0f);
17848                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
17849 // auto cfstr = cast(id)createCFString(text);
17850 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
17851 // NSPoint(x, y), null);
17852 // CFRelease(cfstr);
17853                 CGContextRestoreGState(context);
17854             }
17855         }
17856 
17857         void drawPixel(int x, int y) {
17858             auto rawData = CGBitmapContextGetData(context);
17859             auto width = CGBitmapContextGetWidth(context);
17860             auto height = CGBitmapContextGetHeight(context);
17861             auto offset = ((height - y - 1) * width + x) * 4;
17862             rawData[offset .. offset+4] = _outlineComponents;
17863         }
17864 
17865         void drawLine(int x1, int y1, int x2, int y2) {
17866             CGPoint[2] linePoints;
17867             linePoints[0] = CGPoint(x1, y1);
17868             linePoints[1] = CGPoint(x2, y2);
17869             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
17870         }
17871 
17872         void drawRectangle(int x, int y, int width, int height) {
17873             CGContextBeginPath(context);
17874             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
17875             CGContextAddRect(context, rect);
17876             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17877         }
17878 
17879         void drawEllipse(int x1, int y1, int x2, int y2) {
17880             CGContextBeginPath(context);
17881             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
17882             CGContextAddEllipseInRect(context, rect);
17883             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17884         }
17885 
17886         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
17887             // @@@BUG@@@ Does not support elliptic arc (width != height).
17888             CGContextBeginPath(context);
17889             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
17890                             start*PI/(180*64), finish*PI/(180*64), 0);
17891             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17892         }
17893 
17894         void drawPolygon(Point[] intPoints) {
17895             CGContextBeginPath(context);
17896             auto points = array(map!(CGPoint.fromTuple)(intPoints));
17897             CGContextAddLines(context, points.ptr, points.length);
17898             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17899         }
17900     }
17901 
17902     mixin template NativeSimpleWindowImplementation() {
17903         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
17904             synchronized {
17905                 if (NSApp == null) initializeApp();
17906             }
17907 
17908             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
17909 
17910             // create the window.
17911             window = initWithContentRect(alloc("NSWindow"),
17912                                          contentRect,
17913                                          NSTitledWindowMask
17914                                             |NSClosableWindowMask
17915                                             |NSMiniaturizableWindowMask
17916                                             |NSResizableWindowMask,
17917                                          NSBackingStoreBuffered,
17918                                          true);
17919 
17920             // set the title & move the window to center.
17921             auto windowTitle = createCFString(title);
17922             setTitle(window, windowTitle);
17923             CFRelease(windowTitle);
17924             center(window);
17925 
17926             // create area to draw on.
17927             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17928             drawingContext = CGBitmapContextCreate(null, width, height,
17929                                                    8, 4*width, colorSpace,
17930                                                    kCGImageAlphaPremultipliedLast
17931                                                       |kCGBitmapByteOrder32Big);
17932             CGColorSpaceRelease(colorSpace);
17933             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
17934             auto matrix = CGContextGetTextMatrix(drawingContext);
17935             matrix.c = -matrix.c;
17936             matrix.d = -matrix.d;
17937             CGContextSetTextMatrix(drawingContext, matrix);
17938 
17939             // create the subview that things will be drawn on.
17940             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
17941             setContentView(window, view);
17942             object_setIvar(view, simpleWindowIvar, cast(id)this);
17943             release(view);
17944 
17945             setBackgroundColor(window, whiteNSColor);
17946             makeKeyAndOrderFront(window, null);
17947         }
17948         void dispose() {
17949             closeWindow();
17950             release(window);
17951         }
17952         void closeWindow() {
17953             invalidate(timer);
17954             .close(window);
17955         }
17956 
17957         ScreenPainter getPainter(bool manualInvalidations) {
17958 		return ScreenPainter(this, this, manualInvalidations);
17959 	}
17960 
17961         id window;
17962         id timer;
17963         id view;
17964         CGContextRef drawingContext;
17965     }
17966 
17967     extern(C) {
17968     private:
17969         BOOL returnTrue3(id self, SEL _cmd, id app) {
17970             return true;
17971         }
17972         BOOL returnTrue2(id self, SEL _cmd) {
17973             return true;
17974         }
17975 
17976         void pulse(id self, SEL _cmd) {
17977             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17978             simpleWindow.handlePulse();
17979             setNeedsDisplay(self, true);
17980         }
17981         void drawRect(id self, SEL _cmd, NSRect rect) {
17982             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17983             auto curCtx = graphicsPort(currentNSGraphicsContext);
17984             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17985             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
17986                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
17987             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17988             CGImageRelease(cgImage);
17989         }
17990         void keyDown(id self, SEL _cmd, id event) {
17991             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17992 
17993             // the event may have multiple characters, and we send them all at
17994             // once.
17995             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
17996                 auto chars = characters(event);
17997                 auto range = CFRange(0, CFStringGetLength(chars));
17998                 auto buffer = new char[range.length*3];
17999                 long actualLength;
18000                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
18001                                  buffer.ptr, cast(int) buffer.length, &actualLength);
18002                 foreach (dchar dc; buffer[0..actualLength]) {
18003                     if (simpleWindow.handleCharEvent)
18004                         simpleWindow.handleCharEvent(dc);
18005 		    // NotYetImplementedException
18006                     //if (simpleWindow.handleKeyEvent)
18007                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
18008                 }
18009             }
18010 
18011             // the event's 'keyCode' is hardware-dependent. I don't think people
18012             // will like it. Let's leave it to the native handler.
18013 
18014             // perform the default action.
18015 
18016 	    // so the default action is to make a bomp sound and i dont want that
18017 	    // sooooooooo yeah not gonna do that.
18018 
18019             //auto superData = objc_super(self, superclass(self));
18020             //alias extern(C) void function(objc_super*, SEL, id) T;
18021             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
18022         }
18023     }
18024 
18025     // initialize the app so that it can be interacted with the user.
18026     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
18027     private void initializeApp() {
18028         // push an autorelease pool to avoid leaking.
18029         init(alloc("NSAutoreleasePool"));
18030 
18031         // create a new NSApp instance
18032         sharedNSApplication;
18033         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
18034 
18035         // create the "Quit" menu.
18036         auto menuBar = init(alloc("NSMenu"));
18037         auto appMenuItem = init(alloc("NSMenuItem"));
18038         addItem(menuBar, appMenuItem);
18039         setMainMenu(NSApp, menuBar);
18040         release(appMenuItem);
18041         release(menuBar);
18042 
18043         auto appMenu = init(alloc("NSMenu"));
18044         auto quitTitle = createCFString("Quit");
18045         auto q = createCFString("q");
18046         auto quitItem = initWithTitle(alloc("NSMenuItem"),
18047                                       quitTitle, sel_registerName("terminate:"), q);
18048         addItem(appMenu, quitItem);
18049         setSubmenu(appMenuItem, appMenu);
18050         release(quitItem);
18051         release(appMenu);
18052         CFRelease(q);
18053         CFRelease(quitTitle);
18054 
18055         // assign a delegate for the application, allow it to quit when the last
18056         // window is closed.
18057         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
18058                                                     "SDWindowCloseDelegate", 0);
18059         class_addMethod(delegateClass,
18060                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
18061                         &returnTrue3, "c@:@");
18062         objc_registerClassPair(delegateClass);
18063 
18064         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
18065         setDelegate(NSApp, appDelegate);
18066         activateIgnoringOtherApps(NSApp, true);
18067 
18068         // create a new view that draws the graphics and respond to keyDown
18069         // events.
18070         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
18071                                                 "SDGraphicsView", (void*).sizeof);
18072         class_addIvar(viewClass, "simpledisplay_simpleWindow",
18073                       (void*).sizeof, (void*).alignof, "^v");
18074         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
18075                         &pulse, "v@:");
18076         class_addMethod(viewClass, sel_registerName("drawRect:"),
18077                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
18078         class_addMethod(viewClass, sel_registerName("isFlipped"),
18079                         &returnTrue2, "c@:");
18080         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
18081                         &returnTrue2, "c@:");
18082         class_addMethod(viewClass, sel_registerName("keyDown:"),
18083                         &keyDown, "v@:@");
18084         objc_registerClassPair(viewClass);
18085         simpleWindowIvar = class_getInstanceVariable(viewClass,
18086                                                      "simpledisplay_simpleWindow");
18087     }
18088 }
18089 
18090 version(without_opengl) {} else
18091 extern(System) nothrow @nogc {
18092 	//enum uint GL_VERSION = 0x1F02;
18093 	//const(char)* glGetString (/*GLenum*/uint);
18094 	version(X11) {
18095 	static if (!SdpyIsUsingIVGLBinds) {
18096 
18097 		enum GLX_X_RENDERABLE = 0x8012;
18098 		enum GLX_DRAWABLE_TYPE = 0x8010;
18099 		enum GLX_RENDER_TYPE = 0x8011;
18100 		enum GLX_X_VISUAL_TYPE = 0x22;
18101 		enum GLX_TRUE_COLOR = 0x8002;
18102 		enum GLX_WINDOW_BIT = 0x00000001;
18103 		enum GLX_RGBA_BIT = 0x00000001;
18104 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18105 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18106 		enum GLX_SAMPLES = 0x186a1;
18107 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18108 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18109 	}
18110 
18111 		// GLX_EXT_swap_control
18112 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18113 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18114 
18115 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18116 		extern(System) {
18117 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18118 		}
18119 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18120 
18121 		// this made public so we don't have to get it again and again
18122 		public bool glXCreateContextAttribsARB_present () {
18123 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18124 				// get it
18125 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18126 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18127 			}
18128 			return (glXCreateContextAttribsARBFn !is null);
18129 		}
18130 
18131 		// this made public so we don't have to get it again and again
18132 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18133 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18134 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18135 		}
18136 
18137 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18138 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18139 
18140 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18141 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18142 			if (_glx_swapInterval_fn is null) {
18143 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18144 				if (_glx_swapInterval_fn is null) {
18145 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18146 					return;
18147 				}
18148 				version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); }
18149 			}
18150 
18151 			if(glXSwapIntervalMESA is null) {
18152 				// it seems to require both to actually take effect on many computers
18153 				// idk why
18154 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18155 				if(glXSwapIntervalMESA is null)
18156 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18157 			}
18158 
18159 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18160 				glXSwapIntervalMESA(wait ? 1 : 0);
18161 
18162 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18163 		}
18164 	} else version(Windows) {
18165 	static if (!SdpyIsUsingIVGLBinds) {
18166 	enum GL_TRUE = 1;
18167 	enum GL_FALSE = 0;
18168 	alias int GLint;
18169 
18170 	public void* glbindGetProcAddress (const(char)* name) {
18171 		void* res = wglGetProcAddress(name);
18172 		if (res is null) {
18173 			/+
18174 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18175 			import core.sys.windows.windef, core.sys.windows.winbase;
18176 			__gshared HINSTANCE dll = null;
18177 			if (dll is null) {
18178 				dll = LoadLibraryA("opengl32.dll");
18179 				if (dll is null) return null; // <32, but idc
18180 			}
18181 			res = GetProcAddress(dll, name);
18182 			+/
18183 			res = GetProcAddress(gl.libHandle, name);
18184 		}
18185 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18186 		return res;
18187 	}
18188 	}
18189 
18190  
18191  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18192         void wglSetVSync(bool wait) {
18193 		if(wglSwapIntervalEXT is null) {
18194 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18195 			if(wglSwapIntervalEXT is null)
18196 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18197 		}
18198 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18199 			return;
18200 
18201 		wglSwapIntervalEXT(wait ? 1 : 0);
18202 	}
18203 
18204 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18205 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18206 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18207 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18208 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18209 
18210 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18211 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18212 
18213 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18214 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18215 
18216 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18217 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18218 
18219 		void wglInitOtherFunctions () {
18220 			if (wglCreateContextAttribsARB is null) {
18221 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18222 			}
18223 		}
18224 	}
18225 
18226 	static if (!SdpyIsUsingIVGLBinds) {
18227 
18228 	interface GL {
18229 		extern(System) @nogc nothrow:
18230 
18231 		void glGetIntegerv(int, void*);
18232 		void glMatrixMode(int);
18233 		void glPushMatrix();
18234 		void glLoadIdentity();
18235 		void glOrtho(double, double, double, double, double, double);
18236 		void glFrustum(double, double, double, double, double, double);
18237 
18238 		void glPopMatrix();
18239 		void glEnable(int);
18240 		void glDisable(int);
18241 		void glClear(int);
18242 		void glBegin(int);
18243 		void glVertex2f(float, float);
18244 		void glVertex3f(float, float, float);
18245 		void glEnd();
18246 		void glColor3b(byte, byte, byte);
18247 		void glColor3ub(ubyte, ubyte, ubyte);
18248 		void glColor4b(byte, byte, byte, byte);
18249 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18250 		void glColor3i(int, int, int);
18251 		void glColor3ui(uint, uint, uint);
18252 		void glColor4i(int, int, int, int);
18253 		void glColor4ui(uint, uint, uint, uint);
18254 		void glColor3f(float, float, float);
18255 		void glColor4f(float, float, float, float);
18256 		void glTranslatef(float, float, float);
18257 		void glScalef(float, float, float);
18258 		version(X11) {
18259 			void glSecondaryColor3b(byte, byte, byte);
18260 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18261 			void glSecondaryColor3i(int, int, int);
18262 			void glSecondaryColor3ui(uint, uint, uint);
18263 			void glSecondaryColor3f(float, float, float);
18264 		}
18265 
18266 		void glDrawElements(int, int, int, void*);
18267 
18268 		void glRotatef(float, float, float, float);
18269 
18270 		uint glGetError();
18271 
18272 		void glDeleteTextures(int, uint*);
18273 
18274 
18275 		void glRasterPos2i(int, int);
18276 		void glDrawPixels(int, int, uint, uint, void*);
18277 		void glClearColor(float, float, float, float);
18278 
18279 
18280 		void glPixelStorei(uint, int);
18281 
18282 		void glGenTextures(uint, uint*);
18283 		void glBindTexture(int, int);
18284 		void glTexParameteri(uint, uint, int);
18285 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18286 		void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
18287 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18288 			/*GLsizei*/int width, /*GLsizei*/int height,
18289 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
18290 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18291 
18292 		void glLineWidth(int);
18293 
18294 
18295 		void glTexCoord2f(float, float);
18296 		void glVertex2i(int, int);
18297 		void glBlendFunc (int, int);
18298 		void glDepthFunc (int);
18299 		void glViewport(int, int, int, int);
18300 
18301 		void glClearDepth(double);
18302 
18303 		void glReadBuffer(uint);
18304 		void glReadPixels(int, int, int, int, int, int, void*);
18305 
18306 		void glFlush();
18307 		void glFinish();
18308 
18309 		version(Windows) {
18310 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18311 			HGLRC wglCreateContext(HDC);
18312 			HGLRC wglCreateLayerContext(HDC, int);
18313 			BOOL wglDeleteContext(HGLRC);
18314 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18315 			HGLRC wglGetCurrentContext();
18316 			HDC wglGetCurrentDC();
18317 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18318 			PROC wglGetProcAddress(LPCSTR);
18319 			BOOL wglMakeCurrent(HDC, HGLRC);
18320 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18321 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18322 			BOOL wglShareLists(HGLRC, HGLRC);
18323 			BOOL wglSwapLayerBuffers(HDC, UINT);
18324 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18325 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18326 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18327 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18328 		}
18329 
18330 	}
18331 
18332 	interface GL3 {
18333 		extern(System) @nogc nothrow:
18334 
18335 		void glGenVertexArrays(GLsizei, GLuint*);
18336 		void glBindVertexArray(GLuint);
18337 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18338 		void glGenerateMipmap(GLenum);
18339 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18340 		void glStencilMask(GLuint);
18341 		void glStencilFunc(GLenum, GLint, GLuint);
18342 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18343 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18344 		GLuint glCreateProgram();
18345 		GLuint glCreateShader(GLenum);
18346 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18347 		void glCompileShader(GLuint);
18348 		void glGetShaderiv(GLuint, GLenum, GLint*);
18349 		void glAttachShader(GLuint, GLuint);
18350 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18351 		void glLinkProgram(GLuint);
18352 		void glGetProgramiv(GLuint, GLenum, GLint*);
18353 		void glDeleteProgram(GLuint);
18354 		void glDeleteShader(GLuint);
18355 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18356 		void glGenBuffers(GLsizei, GLuint*);
18357 
18358 		void glUniform1f(GLint location, GLfloat v0); 
18359 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 
18360 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 
18361 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 
18362 		void glUniform1i(GLint location, GLint v0); 
18363 		void glUniform2i(GLint location, GLint v0, GLint v1); 
18364 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 
18365 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 
18366 		void glUniform1ui(GLint location, GLuint v0); 
18367 		void glUniform2ui(GLint location, GLuint v0, GLuint v1); 
18368 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 
18369 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 
18370 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 
18371 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 
18372 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 
18373 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 
18374 		void glUniform1iv(GLint location, GLsizei count, const GLint *value); 
18375 		void glUniform2iv(GLint location, GLsizei count, const GLint *value); 
18376 		void glUniform3iv(GLint location, GLsizei count, const GLint *value); 
18377 		void glUniform4iv(GLint location, GLsizei count, const GLint *value); 
18378 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 
18379 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 
18380 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 
18381 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18382 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18383 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18384 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18385 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18386 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18387 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18388 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18389 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18390 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18391 
18392 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18393 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18394 		void glDrawArrays(GLenum, GLint, GLsizei);
18395 		void glStencilOp(GLenum, GLenum, GLenum);
18396 		void glUseProgram(GLuint);
18397 		void glCullFace(GLenum);
18398 		void glFrontFace(GLenum);
18399 		void glActiveTexture(GLenum);
18400 		void glBindBuffer(GLenum, GLuint);
18401 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18402 		void glEnableVertexAttribArray(GLuint);
18403 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18404 		void glUniform1i(GLint, GLint);
18405 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18406 		void glDisableVertexAttribArray(GLuint);
18407 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18408 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18409 		void glLogicOp (GLenum opcode);
18410 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18411 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18412 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18413 		GLenum glCheckFramebufferStatus (GLenum target);
18414 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18415 	}
18416 
18417 	interface GL4 {
18418 		extern(System) @nogc nothrow:
18419 
18420 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18421 			/*GLsizei*/int width, /*GLsizei*/int height,
18422 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
18423 	}
18424 
18425 	interface GLU {
18426 		extern(System) @nogc nothrow:
18427 
18428 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18429 		void gluPerspective(double, double, double, double);
18430 
18431 		char* gluErrorString(uint);
18432 	}
18433 
18434 
18435 	enum GL_RED = 0x1903;
18436 	enum GL_ALPHA = 0x1906;
18437 
18438 	enum uint GL_FRONT = 0x0404;
18439 
18440 	enum uint GL_BLEND = 0x0be2;
18441 	enum uint GL_LEQUAL = 0x0203;
18442 
18443 
18444 	enum uint GL_RGB = 0x1907;
18445 	enum uint GL_BGRA = 0x80e1;
18446 	enum uint GL_RGBA = 0x1908;
18447 	enum uint GL_TEXTURE_2D =   0x0DE1;
18448 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18449 	enum uint GL_NEAREST = 0x2600;
18450 	enum uint GL_LINEAR = 0x2601;
18451 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18452 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18453 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18454 	enum uint GL_REPEAT = 0x2901;
18455 	enum uint GL_CLAMP = 0x2900;
18456 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18457 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18458 	enum uint GL_DECAL = 0x2101;
18459 	enum uint GL_MODULATE = 0x2100;
18460 	enum uint GL_TEXTURE_ENV = 0x2300;
18461 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18462 	enum uint GL_REPLACE = 0x1E01;
18463 	enum uint GL_LIGHTING = 0x0B50;
18464 	enum uint GL_DITHER = 0x0BD0;
18465 
18466 	enum uint GL_NO_ERROR = 0;
18467 
18468 
18469 
18470 	enum int GL_VIEWPORT = 0x0BA2;
18471 	enum int GL_MODELVIEW = 0x1700;
18472 	enum int GL_TEXTURE = 0x1702;
18473 	enum int GL_PROJECTION = 0x1701;
18474 	enum int GL_DEPTH_TEST = 0x0B71;
18475 
18476 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18477 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18478 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18479 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18480 
18481 	enum int GL_POINTS = 0x0000;
18482 	enum int GL_LINES =  0x0001;
18483 	enum int GL_LINE_LOOP = 0x0002;
18484 	enum int GL_LINE_STRIP = 0x0003;
18485 	enum int GL_TRIANGLES = 0x0004;
18486 	enum int GL_TRIANGLE_STRIP = 5;
18487 	enum int GL_TRIANGLE_FAN = 6;
18488 	enum int GL_QUADS = 7;
18489 	enum int GL_QUAD_STRIP = 8;
18490 	enum int GL_POLYGON = 9;
18491 
18492 	alias GLvoid = void;
18493 	alias GLboolean = ubyte;
18494 	alias GLuint = uint;
18495 	alias GLenum = uint;
18496 	alias GLchar = char;
18497 	alias GLsizei = int;
18498 	alias GLfloat = float;
18499 	alias GLintptr = size_t;
18500 	alias GLsizeiptr = ptrdiff_t;
18501 
18502 
18503 	enum uint GL_INVALID_ENUM = 0x0500;
18504 
18505 	enum uint GL_ZERO = 0;
18506 	enum uint GL_ONE = 1;
18507 
18508 	enum uint GL_BYTE = 0x1400;
18509 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18510 	enum uint GL_SHORT = 0x1402;
18511 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18512 	enum uint GL_INT = 0x1404;
18513 	enum uint GL_UNSIGNED_INT = 0x1405;
18514 	enum uint GL_FLOAT = 0x1406;
18515 	enum uint GL_2_BYTES = 0x1407;
18516 	enum uint GL_3_BYTES = 0x1408;
18517 	enum uint GL_4_BYTES = 0x1409;
18518 	enum uint GL_DOUBLE = 0x140A;
18519 
18520 	enum uint GL_STREAM_DRAW = 0x88E0;
18521 
18522 	enum uint GL_CCW = 0x0901;
18523 
18524 	enum uint GL_STENCIL_TEST = 0x0B90;
18525 	enum uint GL_SCISSOR_TEST = 0x0C11;
18526 
18527 	enum uint GL_EQUAL = 0x0202;
18528 	enum uint GL_NOTEQUAL = 0x0205;
18529 
18530 	enum uint GL_ALWAYS = 0x0207;
18531 	enum uint GL_KEEP = 0x1E00;
18532 
18533 	enum uint GL_INCR = 0x1E02;
18534 
18535 	enum uint GL_INCR_WRAP = 0x8507;
18536 	enum uint GL_DECR_WRAP = 0x8508;
18537 
18538 	enum uint GL_CULL_FACE = 0x0B44;
18539 	enum uint GL_BACK = 0x0405;
18540 
18541 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18542 	enum uint GL_VERTEX_SHADER = 0x8B31;
18543 
18544 	enum uint GL_COMPILE_STATUS = 0x8B81;
18545 	enum uint GL_LINK_STATUS = 0x8B82;
18546 
18547 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18548 
18549 	enum uint GL_STATIC_DRAW = 0x88E4;
18550 
18551 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18552 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18553 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18554 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18555 
18556 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18557 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18558 
18559 	enum uint GL_TEXTURE0 = 0x84C0U;
18560 	enum uint GL_TEXTURE1 = 0x84C1U;
18561 
18562 	enum uint GL_ARRAY_BUFFER = 0x8892;
18563 
18564 	enum uint GL_SRC_COLOR = 0x0300;
18565 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18566 	enum uint GL_SRC_ALPHA = 0x0302;
18567 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18568 	enum uint GL_DST_ALPHA = 0x0304;
18569 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18570 	enum uint GL_DST_COLOR = 0x0306;
18571 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18572 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18573 
18574 	enum uint GL_INVERT = 0x150AU;
18575 
18576 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18577 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18578 
18579 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18580 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18581 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18582 
18583 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18584 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18585 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18586 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18587 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18588 
18589 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18590 	enum uint GL_CLEAR = 0x1500U;
18591 	enum uint GL_COPY = 0x1503U;
18592 	enum uint GL_XOR = 0x1506U;
18593 
18594 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18595 
18596 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18597 
18598 	}
18599 }
18600 
18601 /++
18602 	History:
18603 		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.
18604 +/
18605 __gshared bool gluSuccessfullyLoaded = true;
18606 
18607 version(without_opengl) {} else {
18608 static if(!SdpyIsUsingIVGLBinds) {
18609 	version(Windows) {
18610 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18611 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18612 	} else {
18613 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18614 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18615 	}
18616 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18617 
18618 
18619 	shared static this() {
18620 		gl.loadDynamicLibrary();
18621 
18622 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18623 		// unless those functions are actually used
18624 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18625 		glu.loadDynamicLibrary();
18626 	}
18627 }
18628 }
18629 
18630 /++
18631 	Convenience method for converting D arrays to opengl buffer data
18632 
18633 	I would LOVE to overload it with the original glBufferData, but D won't
18634 	let me since glBufferData is a function pointer :(
18635 
18636 	Added: August 25, 2020 (version 8.5)
18637 +/
18638 version(without_opengl) {} else
18639 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18640 	glBufferData(target, data.length, data.ptr, usage);
18641 }
18642 
18643 /+
18644 /++
18645 	A matrix for simple uses that easily integrates with [OpenGlShader].
18646 
18647 	Might not be useful to you since it only as some simple functions and
18648 	probably isn't that fast.
18649 
18650 	Note it uses an inline static array for its storage, so copying it
18651 	may be expensive.
18652 +/
18653 struct BasicMatrix(int columns, int rows, T = float) {
18654 	import core.stdc.math;
18655 
18656 	T[columns * rows] data = 0.0;
18657 
18658 	/++
18659 		Basic operations that operate *in place*.
18660 	+/
18661 	void translate() {
18662 
18663 	}
18664 
18665 	/// ditto
18666 	void scale() {
18667 
18668 	}
18669 
18670 	/// ditto
18671 	void rotate() {
18672 
18673 	}
18674 
18675 	/++
18676 
18677 	+/
18678 	static if(columns == rows)
18679 	static BasicMatrix identity() {
18680 		BasicMatrix m;
18681 		foreach(i; 0 .. columns)
18682 			data[0 + i + i * columns] = 1.0;
18683 		return m;
18684 	}
18685 
18686 	static BasicMatrix ortho() {
18687 		return BasicMatrix.init;
18688 	}
18689 }
18690 +/
18691 
18692 /++
18693 	Convenience class for using opengl shaders.
18694 
18695 	Ensure that you've loaded opengl 3+ and set your active
18696 	context before trying to use this.
18697 
18698 	Added: August 25, 2020 (version 8.5)
18699 +/
18700 version(without_opengl) {} else
18701 final class OpenGlShader {
18702 	private int shaderProgram_;
18703 	private @property void shaderProgram(int a) {
18704 		shaderProgram_ = a;
18705 	}
18706 	/// Get the program ID for use in OpenGL functions.
18707 	public @property int shaderProgram() {
18708 		return shaderProgram_;
18709 	}
18710 
18711 	/++
18712 
18713 	+/
18714 	static struct Source {
18715 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
18716 		string code; ///
18717 	}
18718 
18719 	/++
18720 		Helper method to just compile some shader code and check for errors
18721 		while you do glCreateShader, etc. on the outside yourself.
18722 
18723 		This just does `glShaderSource` and `glCompileShader` for the given code.
18724 
18725 		If you the OpenGlShader class constructor, you never need to call this yourself.
18726 	+/
18727 	static void compile(int sid, Source code) {
18728 		const(char)*[1] buffer;
18729 		int[1] lengthBuffer;
18730 
18731 		buffer[0] = code.code.ptr;
18732 		lengthBuffer[0] = cast(int) code.code.length;
18733 
18734 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
18735 		glCompileShader(sid);
18736 
18737 		int success;
18738 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
18739 		if(!success) {
18740 			char[512] info;
18741 			int len;
18742 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
18743 
18744 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
18745 		}
18746 	}
18747 
18748 	/++
18749 		Calls `glLinkProgram` and throws if error a occurs.
18750 
18751 		If you the OpenGlShader class constructor, you never need to call this yourself.
18752 	+/
18753 	static void link(int shaderProgram) {
18754 		glLinkProgram(shaderProgram);
18755 		int success;
18756 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
18757 		if(!success) {
18758 			char[512] info;
18759 			int len;
18760 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
18761 
18762 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
18763 		}
18764 	}
18765 
18766 	/++
18767 		Constructs the shader object by calling `glCreateProgram`, then
18768 		compiling each given [Source], and finally, linking them together.
18769 
18770 		Throws: on compile or link failure.
18771 	+/
18772 	this(Source[] codes...) {
18773 		shaderProgram = glCreateProgram();
18774 
18775 		int[16] shadersBufferStack;
18776 
18777 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 
18778 			shadersBufferStack[0 .. codes.length] :
18779 			new int[](codes.length);
18780 
18781 		foreach(idx, code; codes) {
18782 			shadersBuffer[idx] = glCreateShader(code.type);
18783 
18784 			compile(shadersBuffer[idx], code);
18785 
18786 			glAttachShader(shaderProgram, shadersBuffer[idx]);
18787 		}
18788 
18789 		link(shaderProgram);
18790 
18791 		foreach(s; shadersBuffer)
18792 			glDeleteShader(s);
18793 	}
18794 
18795 	/// Calls `glUseProgram(this.shaderProgram)`
18796 	void use() {
18797 		glUseProgram(this.shaderProgram);
18798 	}
18799 
18800 	/// Deletes the program.
18801 	void delete_() {
18802 		glDeleteProgram(shaderProgram);
18803 		shaderProgram = 0;
18804 	}
18805 
18806 	/++
18807 		[OpenGlShader.uniforms].name gives you one of these.
18808 
18809 		You can get the id out of it or just assign
18810 	+/
18811 	static struct Uniform {
18812 		/// the id passed to glUniform*
18813 		int id;
18814 
18815 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
18816 		void opAssign(float x, float y, float z, float w) {
18817 			if(id != -1)
18818 			glUniform4f(id, x, y, z, w);
18819 		}
18820 
18821 		void opAssign(float x) {
18822 			if(id != -1)
18823 			glUniform1f(id, x);
18824 		}
18825 
18826 		void opAssign(float x, float y) {
18827 			if(id != -1)
18828 			glUniform2f(id, x, y);
18829 		}
18830 
18831 		void opAssign(T)(T t) {
18832 			t.glUniform(id);
18833 		}
18834 	}
18835 
18836 	static struct UniformsHelper {
18837 		OpenGlShader _shader;
18838 
18839 		@property Uniform opDispatch(string name)() {
18840 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
18841 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
18842 			//if(i == -1)
18843 				//throw new Exception("Could not find uniform " ~ name);
18844 			return Uniform(i);
18845 		}
18846 
18847 		@property void opDispatch(string name, T)(T t) {
18848 			Uniform f = this.opDispatch!name;
18849 			t.glUniform(f);
18850 		}
18851 	}
18852 
18853 	/++
18854 		Gives access to the uniforms through dot access.
18855 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
18856 	+/
18857 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
18858 }
18859 
18860 version(without_opengl) {} else {
18861 /++
18862 	A static container of experimental types and value constructors for opengl 3+ shaders.
18863 
18864 
18865 	You can declare variables like:
18866 
18867 	```
18868 	OGL.vec3f something;
18869 	```
18870 
18871 	But generally it would be used with [OpenGlShader]'s uniform helpers like
18872 
18873 	```
18874 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
18875 	```
18876 
18877 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
18878 
18879 
18880 	History:
18881 		Added December 7, 2021. Not yet stable.
18882 +/
18883 final class OGL {
18884 	static:
18885 
18886 	private template typeFromSpecifier(string specifier) {
18887 		static if(specifier == "f")
18888 			alias typeFromSpecifier = GLfloat;
18889 		else static if(specifier == "i")
18890 			alias typeFromSpecifier = GLint;
18891 		else static if(specifier == "ui")
18892 			alias typeFromSpecifier = GLuint;
18893 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
18894 	}
18895 
18896 	private template CommonType(T...) {
18897 		static if(T.length == 1)
18898 			alias CommonType = T[0];
18899 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
18900 			alias CommonType = CommonType!(C, T[2 .. $]);
18901 	}
18902 
18903 	private template typesToSpecifier(T...) {
18904 		static if(is(CommonType!T == float))
18905 			enum typesToSpecifier = "f";
18906 		else static if(is(CommonType!T == int))
18907 			enum typesToSpecifier = "i";
18908 		else static if(is(CommonType!T == uint))
18909 			enum typesToSpecifier = "ui";
18910 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
18911 	}
18912 
18913 	private template genNames(size_t dim, size_t dim2 = 0) {
18914 		string helper() {
18915 			string s;
18916 			if(dim2) {
18917 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
18918 			} else {
18919 				if(dim > 0) s ~= "type x = 0;";
18920 				if(dim > 1) s ~= "type y = 0;";
18921 				if(dim > 2) s ~= "type z = 0;";
18922 				if(dim > 3) s ~= "type w = 0;";
18923 			}
18924 			return s;
18925 		}
18926 
18927 		enum genNames = helper();
18928 	}
18929 
18930 	// there's vec, arrays of vec, mat, and arrays of mat
18931 	template opDispatch(string name)
18932 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
18933 	{
18934 		static if(name[4] == 'x') {
18935 			enum dimX = cast(int) (name[3] - '0');
18936 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
18937 
18938 			enum dimY = cast(int) (name[5] - '0');
18939 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
18940 
18941 			enum isArray = name[$ - 1] == 'v';
18942 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
18943 			alias type = typeFromSpecifier!typeSpecifier;
18944 		} else {
18945 			enum dim = cast(int) (name[3] - '0');
18946 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
18947 			enum isArray = name[$ - 1] == 'v';
18948 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
18949 			alias type = typeFromSpecifier!typeSpecifier;
18950 		}
18951 
18952 		align(1)
18953 		struct opDispatch {
18954 			align(1):
18955 			static if(name[4] == 'x')
18956 				mixin(genNames!(dimX, dimY));
18957 			else
18958 				mixin(genNames!dim);
18959 
18960 			private void glUniform(OpenGlShader.Uniform assignTo) {
18961 				glUniform(assignTo.id);
18962 			}
18963 			private void glUniform(int assignTo) {
18964 				static if(name[4] == 'x') {
18965 					// FIXME
18966 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
18967 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
18968 				} else
18969 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
18970 			}
18971 		}
18972 	}
18973 
18974 	auto vec(T...)(T members) {
18975 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
18976 	}
18977 }
18978 }
18979 
18980 version(linux) {
18981 	version(with_eventloop) {} else {
18982 		private int epollFd = -1;
18983 		void prepareEventLoop() {
18984 			if(epollFd != -1)
18985 				return; // already initialized, no need to do it again
18986 			import ep = core.sys.linux.epoll;
18987 
18988 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
18989 			if(epollFd == -1)
18990 				throw new Exception("epoll create failure");
18991 		}
18992 	}
18993 } else version(Posix) {
18994 	void prepareEventLoop() {}
18995 }
18996 
18997 version(X11) {
18998 	import core.stdc.locale : LC_ALL; // rdmd fix
18999 	__gshared bool sdx_isUTF8Locale;
19000 
19001 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19002 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19003 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19004 	// anal magic is here. I (Ketmar) hope you like it.
19005 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19006 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19007 	// later.
19008 
19009 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19010 	shared static this () {
19011 		if(!librariesSuccessfullyLoaded)
19012 			return;
19013 
19014 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19015 
19016 		// this doesn't hurt; it may add some locking, but the speed is still
19017 		// allows doing 60 FPS videogames; also, ignore the result, as most
19018 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19019 		// never seen this failing).
19020 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19021 
19022 		setlocale(LC_ALL, "");
19023 		// check if out locale is UTF-8
19024 		auto lct = setlocale(LC_CTYPE, null);
19025 		if (lct is null) {
19026 			sdx_isUTF8Locale = false;
19027 		} else {
19028 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19029 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19030 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19031 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19032 				{
19033 					sdx_isUTF8Locale = true;
19034 					break;
19035 				}
19036 			}
19037 		}
19038 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19039 	}
19040 }
19041 
19042 class ExperimentalTextComponent2 {
19043 	/+
19044 		Stage 1: get it working monospace
19045 		Stage 2: use proportional font
19046 		Stage 3: allow changes in inline style
19047 		Stage 4: allow new fonts and sizes in the middle
19048 		Stage 5: optimize gap buffer
19049 		Stage 6: optimize layout
19050 		Stage 7: word wrap
19051 		Stage 8: justification
19052 		Stage 9: editing, selection, etc.
19053 
19054 			Operations:
19055 				insert text
19056 				overstrike text
19057 				select
19058 				cut
19059 				modify
19060 	+/
19061 
19062 	/++
19063 		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.
19064 	+/
19065 	this(SimpleWindow window) {
19066 		this.window = window;
19067 	}
19068 
19069 	private SimpleWindow window;
19070 
19071 
19072 	/++
19073 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19074 		representing the internal parts. The first pass is focused on the x parameter, then the
19075 		renderer is responsible for going back to the parts in the current line and calling
19076 		adjustDownForAscent to change the y params.
19077 	+/
19078 	static interface ComponentRenderHelper {
19079 
19080 		/+
19081 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19082 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19083 			to move (adjust y to make room for new line) until you get back to the same position,
19084 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19085 
19086 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19087 			once you reach something that is unchanged, you can stop.
19088 		+/
19089 
19090 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19091 
19092 		int ascent() const;
19093 		int descent() const;
19094 
19095 		int advance() const;
19096 
19097 		bool endsWithExplititLineBreak() const;
19098 	}
19099 
19100 	static interface RenderResult {
19101 		/++
19102 			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.
19103 		+/
19104 		void popFront();
19105 		@property bool empty() const;
19106 		@property ComponentRenderHelper front() const;
19107 
19108 		void repositionForNextLine(Point baseline, int availableWidth);
19109 	}
19110 
19111 	static interface ComponentInFlow {
19112 		void draw(ScreenPainter painter);
19113 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19114 
19115 		bool startsWithExplicitLineBreak() const;
19116 	}
19117 
19118 	static class TextFlowComponent : ComponentInFlow {
19119 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19120 
19121 		Color foreground;
19122 		Color background;
19123 
19124 		OperatingSystemFont font; // should NEVER be null
19125 
19126 		ubyte attributes; // underline, strike through, display on new block
19127 
19128 		version(Windows)
19129 			const(wchar)[] content;
19130 		else
19131 			const(char)[] content; // this should NEVER have a newline, except at the end
19132 
19133 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19134 
19135 		// could prolly put some spacing around it too like margin / padding
19136 
19137 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19138 			in { assert(font !is null);
19139 			     assert(!font.isNull); }
19140 			do
19141 		{
19142 			this.foreground = f;
19143 			this.background = b;
19144 			this.font = font;
19145 
19146 			this.attributes = attr;
19147 			version(Windows) {
19148 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19149 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19150 				auto buffer = new wchar[](sz);
19151 				this.content = makeWindowsString(c, buffer, conversionFlags);
19152 			} else {
19153 				this.content = c.dup;
19154 			}
19155 		}
19156 
19157 		void draw(ScreenPainter painter) {
19158 			painter.setFont(this.font);
19159 			painter.outlineColor = this.foreground;
19160 			painter.fillColor = Color.transparent;
19161 			foreach(rendered; this.rendered) {
19162 				// the component works in term of baseline,
19163 				// but the painter works in term of upper left bounding box
19164 				// so need to translate that
19165 
19166 				if(this.background.a) {
19167 					painter.fillColor = this.background;
19168 					painter.outlineColor = this.background;
19169 
19170 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19171 
19172 					painter.outlineColor = this.foreground;
19173 					painter.fillColor = Color.transparent;
19174 				}
19175 
19176 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19177 
19178 				// FIXME: strike through, underline, highlight selection, etc.
19179 			}
19180 		}
19181 	}
19182 
19183 	// I could split the parts into words on render
19184 	// for easier word-wrap, each one being an unbreakable "inline-block"
19185 	private TextFlowComponent[] parts;
19186 	private int needsRerenderFrom;
19187 
19188 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19189 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19190 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19191 	}
19192 
19193 	static struct RenderedComponent {
19194 		int startX;
19195 		int startY;
19196 		short width;
19197 		// 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!
19198 		// for individual chars in here you've gotta process on demand
19199 		version(Windows)
19200 			const(wchar)[] slice;
19201 		else
19202 			const(char)[] slice;
19203 	}
19204 
19205 
19206 	void rerender(Rectangle boundingBox) {
19207 		Point baseline = boundingBox.upperLeft;
19208 
19209 		this.boundingBox.left = boundingBox.left;
19210 		this.boundingBox.top = boundingBox.top;
19211 
19212 		auto remainingParts = parts;
19213 
19214 		int largestX;
19215 
19216 
19217 		foreach(part; parts)
19218 			part.font.prepareContext(window);
19219 		scope(exit)
19220 		foreach(part; parts)
19221 			part.font.releaseContext();
19222 
19223 		calculateNextLine:
19224 
19225 		int nextLineHeight = 0;
19226 		int nextBiggestDescent = 0;
19227 
19228 		foreach(part; remainingParts) {
19229 			auto height = part.font.ascent;
19230 			if(height > nextLineHeight)
19231 				nextLineHeight = height;
19232 			if(part.font.descent > nextBiggestDescent)
19233 				nextBiggestDescent = part.font.descent;
19234 			if(part.content.length && part.content[$-1] == '\n')
19235 				break;
19236 		}
19237 
19238 		baseline.y += nextLineHeight;
19239 		auto lineStart = baseline;
19240 
19241 		while(remainingParts.length) {
19242 			remainingParts[0].rendered = null;
19243 
19244 			bool eol;
19245 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19246 				eol = true;
19247 
19248 			// FIXME: word wrap
19249 			auto font = remainingParts[0].font;
19250 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19251 			auto width = font.stringWidth(slice, window);
19252 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19253 
19254 			remainingParts = remainingParts[1 .. $];
19255 			baseline.x += width;
19256 
19257 			if(eol) {
19258 				baseline.y += nextBiggestDescent;
19259 				if(baseline.x > largestX)
19260 					largestX = baseline.x;
19261 				baseline.x = lineStart.x;
19262 				goto calculateNextLine;
19263 			}
19264 		}
19265 
19266 		if(baseline.x > largestX)
19267 			largestX = baseline.x;
19268 
19269 		this.boundingBox.right = largestX;
19270 		this.boundingBox.bottom = baseline.y;
19271 	}
19272 
19273 	// you must call rerender first!
19274 	void draw(ScreenPainter painter) {
19275 		foreach(part; parts) {
19276 			part.draw(painter);
19277 		}
19278 	}
19279 
19280 	struct IdentifyResult {
19281 		TextFlowComponent part;
19282 		int charIndexInPart;
19283 		int totalCharIndex = -1; // if this is -1, it just means the end
19284 
19285 		Rectangle boundingBox;
19286 	}
19287 
19288 	IdentifyResult identify(Point pt, bool exact = false) {
19289 		if(parts.length == 0)
19290 			return IdentifyResult(null, 0);
19291 
19292 		if(pt.y < boundingBox.top) {
19293 			if(exact)
19294 				return IdentifyResult(null, 1);
19295 			return IdentifyResult(parts[0], 0);
19296 		}
19297 		if(pt.y > boundingBox.bottom) {
19298 			if(exact)
19299 				return IdentifyResult(null, 2);
19300 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19301 		}
19302 
19303 		int tci = 0;
19304 
19305 		// I should probably like binary search this or something...
19306 		foreach(ref part; parts) {
19307 			foreach(rendered; part.rendered) {
19308 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19309 				if(rect.contains(pt)) {
19310 					auto x = pt.x - rendered.startX;
19311 					auto estimatedIdx = x / part.font.averageWidth;
19312 
19313 					if(estimatedIdx < 0)
19314 						estimatedIdx = 0;
19315 
19316 					if(estimatedIdx > rendered.slice.length)
19317 						estimatedIdx = cast(int) rendered.slice.length;
19318 
19319 					int idx;
19320 					int x1, x2;
19321 					if(part.font.isMonospace) {
19322 						auto w = part.font.averageWidth;
19323 						if(!exact && x > (estimatedIdx + 1) * w)
19324 							return IdentifyResult(null, 4);
19325 						idx = estimatedIdx;
19326 						x1 = idx * w;
19327 						x2 = (idx + 1) * w;
19328 					} else {
19329 						idx = estimatedIdx;
19330 
19331 						part.font.prepareContext(window);
19332 						scope(exit) part.font.releaseContext();
19333 
19334 						// int iterations;
19335 
19336 						while(true) {
19337 							// iterations++;
19338 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19339 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19340 
19341 							x1 += rendered.startX;
19342 							x2 += rendered.startX;
19343 
19344 							if(pt.x < x1) {
19345 								if(idx == 0) {
19346 									if(exact)
19347 										return IdentifyResult(null, 6);
19348 									else
19349 										break;
19350 								}
19351 								idx--;
19352 							} else if(pt.x > x2) {
19353 								idx++;
19354 								if(idx > rendered.slice.length) {
19355 									if(exact)
19356 										return IdentifyResult(null, 5);
19357 									else
19358 										break;
19359 								}
19360 							} else if(pt.x >= x1 && pt.x <= x2) {
19361 								if(idx)
19362 									idx--; // point it at the original index
19363 								break; // we fit
19364 							}
19365 						}
19366 
19367 						// import std.stdio; writeln(iterations)
19368 					}
19369 
19370 
19371 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19372 				}
19373 			}
19374 			tci += cast(int) part.content.length; // FIXME: utf-8?
19375 		}
19376 		return IdentifyResult(null, 3);
19377 	}
19378 
19379 	Rectangle boundingBox; // only set after [rerender]
19380 
19381 	// text will be positioned around the exclusion zone
19382 	static struct ExclusionZone {
19383 
19384 	}
19385 
19386 	ExclusionZone[] exclusionZones;
19387 }
19388 
19389 
19390 // Don't use this yet. When I'm happy with it, I will move it to the
19391 // regular module namespace.
19392 mixin template ExperimentalTextComponent() {
19393 
19394 static:
19395 
19396 	alias Rectangle = arsd.color.Rectangle;
19397 
19398 	struct ForegroundColor {
19399 		Color color;
19400 		alias color this;
19401 
19402 		this(Color c) {
19403 			color = c;
19404 		}
19405 
19406 		this(int r, int g, int b, int a = 255) {
19407 			color = Color(r, g, b, a);
19408 		}
19409 
19410 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19411 			return ForegroundColor(mixin("Color." ~ s));
19412 		}
19413 	}
19414 
19415 	struct BackgroundColor {
19416 		Color color;
19417 		alias color this;
19418 
19419 		this(Color c) {
19420 			color = c;
19421 		}
19422 
19423 		this(int r, int g, int b, int a = 255) {
19424 			color = Color(r, g, b, a);
19425 		}
19426 
19427 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19428 			return BackgroundColor(mixin("Color." ~ s));
19429 		}
19430 	}
19431 
19432 	static class InlineElement {
19433 		string text;
19434 
19435 		BlockElement containingBlock;
19436 
19437 		Color color = Color.black;
19438 		Color backgroundColor = Color.transparent;
19439 		ushort styles;
19440 
19441 		string font;
19442 		int fontSize;
19443 
19444 		int lineHeight;
19445 
19446 		void* identifier;
19447 
19448 		Rectangle boundingBox;
19449 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19450 
19451 		bool isMergeCompatible(InlineElement other) {
19452 			return
19453 				containingBlock is other.containingBlock &&
19454 				color == other.color &&
19455 				backgroundColor == other.backgroundColor &&
19456 				styles == other.styles &&
19457 				font == other.font &&
19458 				fontSize == other.fontSize &&
19459 				lineHeight == other.lineHeight &&
19460 				true;
19461 		}
19462 
19463 		int xOfIndex(size_t index) {
19464 			if(index < letterXs.length)
19465 				return letterXs[index];
19466 			else
19467 				return boundingBox.right;
19468 		}
19469 
19470 		InlineElement clone() {
19471 			auto ie = new InlineElement();
19472 			ie.tupleof = this.tupleof;
19473 			return ie;
19474 		}
19475 
19476 		InlineElement getPreviousInlineElement() {
19477 			InlineElement prev = null;
19478 			foreach(ie; this.containingBlock.parts) {
19479 				if(ie is this)
19480 					break;
19481 				prev = ie;
19482 			}
19483 			if(prev is null) {
19484 				BlockElement pb;
19485 				BlockElement cb = this.containingBlock;
19486 				moar:
19487 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19488 					if(ie is cb)
19489 						break;
19490 					pb = ie;
19491 				}
19492 				if(pb is null)
19493 					return null;
19494 				if(pb.parts.length == 0) {
19495 					cb = pb;
19496 					goto moar;
19497 				}
19498 
19499 				prev = pb.parts[$-1];
19500 
19501 			}
19502 			return prev;
19503 		}
19504 
19505 		InlineElement getNextInlineElement() {
19506 			InlineElement next = null;
19507 			foreach(idx, ie; this.containingBlock.parts) {
19508 				if(ie is this) {
19509 					if(idx + 1 < this.containingBlock.parts.length)
19510 						next = this.containingBlock.parts[idx + 1];
19511 					break;
19512 				}
19513 			}
19514 			if(next is null) {
19515 				BlockElement n;
19516 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19517 					if(ie is this.containingBlock) {
19518 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19519 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19520 						break;
19521 					}
19522 				}
19523 				if(n is null)
19524 					return null;
19525 
19526 				if(n.parts.length)
19527 					next = n.parts[0];
19528 				else {} // FIXME
19529 
19530 			}
19531 			return next;
19532 		}
19533 
19534 	}
19535 
19536 	// Block elements are used entirely for positioning inline elements,
19537 	// which are the things that are actually drawn.
19538 	class BlockElement {
19539 		InlineElement[] parts;
19540 		uint alignment;
19541 
19542 		int whiteSpace; // pre, pre-wrap, wrap
19543 
19544 		TextLayout containingLayout;
19545 
19546 		// inputs
19547 		Point where;
19548 		Size minimumSize;
19549 		Size maximumSize;
19550 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19551 		void* identifier;
19552 
19553 		Rectangle margin;
19554 		Rectangle padding;
19555 
19556 		// outputs
19557 		Rectangle[] boundingBoxes;
19558 	}
19559 
19560 	struct TextIdentifyResult {
19561 		InlineElement element;
19562 		int offset;
19563 
19564 		private TextIdentifyResult fixupNewline() {
19565 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19566 				offset--;
19567 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19568 				offset--;
19569 			}
19570 			return this;
19571 		}
19572 	}
19573 
19574 	class TextLayout {
19575 		BlockElement[] blocks;
19576 		Rectangle boundingBox_;
19577 		Rectangle boundingBox() { return boundingBox_; }
19578 		void boundingBox(Rectangle r) {
19579 			if(r != boundingBox_) {
19580 				boundingBox_ = r;
19581 				layoutInvalidated = true;
19582 			}
19583 		}
19584 
19585 		Rectangle contentBoundingBox() {
19586 			Rectangle r;
19587 			foreach(block; blocks)
19588 			foreach(ie; block.parts) {
19589 				if(ie.boundingBox.right > r.right)
19590 					r.right = ie.boundingBox.right;
19591 				if(ie.boundingBox.bottom > r.bottom)
19592 					r.bottom = ie.boundingBox.bottom;
19593 			}
19594 			return r;
19595 		}
19596 
19597 		BlockElement[] getBlocks() {
19598 			return blocks;
19599 		}
19600 
19601 		InlineElement[] getTexts() {
19602 			InlineElement[] elements;
19603 			foreach(block; blocks)
19604 				elements ~= block.parts;
19605 			return elements;
19606 		}
19607 
19608 		string getPlainText() {
19609 			string text;
19610 			foreach(block; blocks)
19611 				foreach(part; block.parts)
19612 					text ~= part.text;
19613 			return text;
19614 		}
19615 
19616 		string getHtml() {
19617 			return null; // FIXME
19618 		}
19619 
19620 		this(Rectangle boundingBox) {
19621 			this.boundingBox = boundingBox;
19622 		}
19623 
19624 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19625 			auto be = new BlockElement();
19626 			be.containingLayout = this;
19627 			if(after is null)
19628 				blocks ~= be;
19629 			else {
19630 				foreach(idx, b; blocks) {
19631 					if(b is after.containingBlock) {
19632 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19633 						break;
19634 					}
19635 				}
19636 			}
19637 			return be;
19638 		}
19639 
19640 		void clear() {
19641 			blocks = null;
19642 			selectionStart = selectionEnd = caret = Caret.init;
19643 		}
19644 
19645 		void addText(Args...)(Args args) {
19646 			if(blocks.length == 0)
19647 				addBlock();
19648 
19649 			InlineElement ie = new InlineElement();
19650 			foreach(idx, arg; args) {
19651 				static if(is(typeof(arg) == ForegroundColor))
19652 					ie.color = arg;
19653 				else static if(is(typeof(arg) == TextFormat)) {
19654 					if(arg & 0x8000) // ~TextFormat.something turns it off
19655 						ie.styles &= arg;
19656 					else
19657 						ie.styles |= arg;
19658 				} else static if(is(typeof(arg) == string)) {
19659 					static if(idx == 0 && args.length > 1)
19660 						static assert(0, "Put styles before the string.");
19661 					size_t lastLineIndex;
19662 					foreach(cidx, char a; arg) {
19663 						if(a == '\n') {
19664 							ie.text = arg[lastLineIndex .. cidx + 1];
19665 							lastLineIndex = cidx + 1;
19666 							ie.containingBlock = blocks[$-1];
19667 							blocks[$-1].parts ~= ie.clone;
19668 							ie.text = null;
19669 						} else {
19670 
19671 						}
19672 					}
19673 
19674 					ie.text = arg[lastLineIndex .. $];
19675 					ie.containingBlock = blocks[$-1];
19676 					blocks[$-1].parts ~= ie.clone;
19677 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
19678 				}
19679 			}
19680 
19681 			invalidateLayout();
19682 		}
19683 
19684 		void tryMerge(InlineElement into, InlineElement what) {
19685 			if(!into.isMergeCompatible(what)) {
19686 				return; // cannot merge, different configs
19687 			}
19688 
19689 			// cool, can merge, bring text together...
19690 			into.text ~= what.text;
19691 
19692 			// and remove what
19693 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
19694 				if(what.containingBlock.parts[a] is what) {
19695 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
19696 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
19697 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
19698 
19699 				}
19700 			}
19701 
19702 			// FIXME: ensure no other carets have a reference to it
19703 		}
19704 
19705 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
19706 		TextIdentifyResult identify(int x, int y, bool exact = false) {
19707 			TextIdentifyResult inexactMatch;
19708 			foreach(block; blocks) {
19709 				foreach(part; block.parts) {
19710 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
19711 
19712 						// FIXME binary search
19713 						int tidx;
19714 						int lastX;
19715 						foreach_reverse(idxo, lx; part.letterXs) {
19716 							int idx = cast(int) idxo;
19717 							if(lx <= x) {
19718 								if(lastX && lastX - x < x - lx)
19719 									tidx = idx + 1;
19720 								else
19721 									tidx = idx;
19722 								break;
19723 							}
19724 							lastX = lx;
19725 						}
19726 
19727 						return TextIdentifyResult(part, tidx).fixupNewline;
19728 					} else if(!exact) {
19729 						// we're not in the box, but are we on the same line?
19730 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
19731 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
19732 					}
19733 				}
19734 			}
19735 
19736 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
19737 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
19738 
19739 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
19740 		}
19741 
19742 		void moveCaretToPixelCoordinates(int x, int y) {
19743 			auto result = identify(x, y);
19744 			caret.inlineElement = result.element;
19745 			caret.offset = result.offset;
19746 		}
19747 
19748 		void selectToPixelCoordinates(int x, int y) {
19749 			auto result = identify(x, y);
19750 
19751 			if(y < caretLastDrawnY1) {
19752 				// on a previous line, carat is selectionEnd
19753 				selectionEnd = caret;
19754 
19755 				selectionStart = Caret(this, result.element, result.offset);
19756 			} else if(y > caretLastDrawnY2) {
19757 				// on a later line
19758 				selectionStart = caret;
19759 
19760 				selectionEnd = Caret(this, result.element, result.offset);
19761 			} else {
19762 				// on the same line...
19763 				if(x <= caretLastDrawnX) {
19764 					selectionEnd = caret;
19765 					selectionStart = Caret(this, result.element, result.offset);
19766 				} else {
19767 					selectionStart = caret;
19768 					selectionEnd = Caret(this, result.element, result.offset);
19769 				}
19770 
19771 			}
19772 		}
19773 
19774 
19775 		/// Call this if the inputs change. It will reflow everything
19776 		void redoLayout(ScreenPainter painter) {
19777 			//painter.setClipRectangle(boundingBox);
19778 			auto pos = Point(boundingBox.left, boundingBox.top);
19779 
19780 			int lastHeight;
19781 			void nl() {
19782 				pos.x = boundingBox.left;
19783 				pos.y += lastHeight;
19784 			}
19785 			foreach(block; blocks) {
19786 				nl();
19787 				foreach(part; block.parts) {
19788 					part.letterXs = null;
19789 
19790 					auto size = painter.textSize(part.text);
19791 					version(Windows)
19792 						if(part.text.length && part.text[$-1] == '\n')
19793 							size.height /= 2; // windows counts the new line at the end, but we don't want that
19794 
19795 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
19796 
19797 					foreach(idx, char c; part.text) {
19798 							// FIXME: unicode
19799 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
19800 					}
19801 
19802 					pos.x += size.width;
19803 					if(pos.x >= boundingBox.right) {
19804 						pos.y += size.height;
19805 						pos.x = boundingBox.left;
19806 						lastHeight = 0;
19807 					} else {
19808 						lastHeight = size.height;
19809 					}
19810 
19811 					if(part.text.length && part.text[$-1] == '\n')
19812 						nl();
19813 				}
19814 			}
19815 
19816 			layoutInvalidated = false;
19817 		}
19818 
19819 		bool layoutInvalidated = true;
19820 		void invalidateLayout() {
19821 			layoutInvalidated = true;
19822 		}
19823 
19824 // FIXME: caret can remain sometimes when inserting
19825 // FIXME: inserting at the beginning once you already have something can eff it up.
19826 		void drawInto(ScreenPainter painter, bool focused = false) {
19827 			if(layoutInvalidated)
19828 				redoLayout(painter);
19829 			foreach(block; blocks) {
19830 				foreach(part; block.parts) {
19831 					painter.outlineColor = part.color;
19832 					painter.fillColor = part.backgroundColor;
19833 
19834 					auto pos = part.boundingBox.upperLeft;
19835 					auto size = part.boundingBox.size;
19836 
19837 					painter.drawText(pos, part.text);
19838 					if(part.styles & TextFormat.underline)
19839 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
19840 					if(part.styles & TextFormat.strikethrough)
19841 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
19842 				}
19843 			}
19844 
19845 			// on every redraw, I will force the caret to be
19846 			// redrawn too, in order to eliminate perceived lag
19847 			// when moving around with the mouse.
19848 			eraseCaret(painter);
19849 
19850 			if(focused) {
19851 				highlightSelection(painter);
19852 				drawCaret(painter);
19853 			}
19854 		}
19855 
19856 		Color selectionXorColor = Color(255, 255, 127);
19857 
19858 		void highlightSelection(ScreenPainter painter) {
19859 			if(selectionStart is selectionEnd)
19860 				return; // no selection
19861 
19862 			if(selectionStart.inlineElement is null) return;
19863 			if(selectionEnd.inlineElement is null) return;
19864 
19865 			assert(selectionStart.inlineElement !is null);
19866 			assert(selectionEnd.inlineElement !is null);
19867 
19868 			painter.rasterOp = RasterOp.xor;
19869 			painter.outlineColor = Color.transparent;
19870 			painter.fillColor = selectionXorColor;
19871 
19872 			auto at = selectionStart.inlineElement;
19873 			auto atOffset = selectionStart.offset;
19874 			bool done;
19875 			while(at) {
19876 				auto box = at.boundingBox;
19877 				if(atOffset < at.letterXs.length)
19878 					box.left = at.letterXs[atOffset];
19879 
19880 				if(at is selectionEnd.inlineElement) {
19881 					if(selectionEnd.offset < at.letterXs.length)
19882 						box.right = at.letterXs[selectionEnd.offset];
19883 					done = true;
19884 				}
19885 
19886 				painter.drawRectangle(box.upperLeft, box.width, box.height);
19887 
19888 				if(done)
19889 					break;
19890 
19891 				at = at.getNextInlineElement();
19892 				atOffset = 0;
19893 			}
19894 		}
19895 
19896 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
19897 		bool caretShowingOnScreen = false;
19898 		void drawCaret(ScreenPainter painter) {
19899 			//painter.setClipRectangle(boundingBox);
19900 			int x, y1, y2;
19901 			if(caret.inlineElement is null) {
19902 				x = boundingBox.left;
19903 				y1 = boundingBox.top + 2;
19904 				y2 = boundingBox.top + painter.fontHeight;
19905 			} else {
19906 				x = caret.inlineElement.xOfIndex(caret.offset);
19907 				y1 = caret.inlineElement.boundingBox.top + 2;
19908 				y2 = caret.inlineElement.boundingBox.bottom - 2;
19909 			}
19910 
19911 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
19912 				eraseCaret(painter);
19913 
19914 			painter.pen = Pen(Color.white, 1);
19915 			painter.rasterOp = RasterOp.xor;
19916 			painter.drawLine(
19917 				Point(x, y1),
19918 				Point(x, y2)
19919 			);
19920 			painter.rasterOp = RasterOp.normal;
19921 			caretShowingOnScreen = !caretShowingOnScreen;
19922 
19923 			if(caretShowingOnScreen) {
19924 				caretLastDrawnX = x;
19925 				caretLastDrawnY1 = y1;
19926 				caretLastDrawnY2 = y2;
19927 			}
19928 		}
19929 
19930 		Rectangle caretBoundingBox() {
19931 			int x, y1, y2;
19932 			if(caret.inlineElement is null) {
19933 				x = boundingBox.left;
19934 				y1 = boundingBox.top + 2;
19935 				y2 = boundingBox.top + 16;
19936 			} else {
19937 				x = caret.inlineElement.xOfIndex(caret.offset);
19938 				y1 = caret.inlineElement.boundingBox.top + 2;
19939 				y2 = caret.inlineElement.boundingBox.bottom - 2;
19940 			}
19941 
19942 			return Rectangle(x, y1, x + 1, y2);
19943 		}
19944 
19945 		void eraseCaret(ScreenPainter painter) {
19946 			//painter.setClipRectangle(boundingBox);
19947 			if(!caretShowingOnScreen) return;
19948 			painter.pen = Pen(Color.white, 1);
19949 			painter.rasterOp = RasterOp.xor;
19950 			painter.drawLine(
19951 				Point(caretLastDrawnX, caretLastDrawnY1),
19952 				Point(caretLastDrawnX, caretLastDrawnY2)
19953 			);
19954 
19955 			caretShowingOnScreen = false;
19956 			painter.rasterOp = RasterOp.normal;
19957 		}
19958 
19959 		/// Caret movement api
19960 		/// These should give the user a logical result based on what they see on screen...
19961 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
19962 		void moveUp() {
19963 			if(caret.inlineElement is null) return;
19964 			auto x = caret.inlineElement.xOfIndex(caret.offset);
19965 			auto y = caret.inlineElement.boundingBox.top + 2;
19966 
19967 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
19968 			if(y < 0)
19969 				return;
19970 
19971 			auto i = identify(x, y);
19972 
19973 			if(i.element) {
19974 				caret.inlineElement = i.element;
19975 				caret.offset = i.offset;
19976 			}
19977 		}
19978 		void moveDown() {
19979 			if(caret.inlineElement is null) return;
19980 			auto x = caret.inlineElement.xOfIndex(caret.offset);
19981 			auto y = caret.inlineElement.boundingBox.bottom - 2;
19982 
19983 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
19984 
19985 			auto i = identify(x, y);
19986 			if(i.element) {
19987 				caret.inlineElement = i.element;
19988 				caret.offset = i.offset;
19989 			}
19990 		}
19991 		void moveLeft() {
19992 			if(caret.inlineElement is null) return;
19993 			if(caret.offset)
19994 				caret.offset--;
19995 			else {
19996 				auto p = caret.inlineElement.getPreviousInlineElement();
19997 				if(p) {
19998 					caret.inlineElement = p;
19999 					if(p.text.length && p.text[$-1] == '\n')
20000 						caret.offset = cast(int) p.text.length - 1;
20001 					else
20002 						caret.offset = cast(int) p.text.length;
20003 				}
20004 			}
20005 		}
20006 		void moveRight() {
20007 			if(caret.inlineElement is null) return;
20008 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20009 				caret.offset++;
20010 			} else {
20011 				auto p = caret.inlineElement.getNextInlineElement();
20012 				if(p) {
20013 					caret.inlineElement = p;
20014 					caret.offset = 0;
20015 				}
20016 			}
20017 		}
20018 		void moveHome() {
20019 			if(caret.inlineElement is null) return;
20020 			auto x = 0;
20021 			auto y = caret.inlineElement.boundingBox.top + 2;
20022 
20023 			auto i = identify(x, y);
20024 
20025 			if(i.element) {
20026 				caret.inlineElement = i.element;
20027 				caret.offset = i.offset;
20028 			}
20029 		}
20030 		void moveEnd() {
20031 			if(caret.inlineElement is null) return;
20032 			auto x = int.max;
20033 			auto y = caret.inlineElement.boundingBox.top + 2;
20034 
20035 			auto i = identify(x, y);
20036 
20037 			if(i.element) {
20038 				caret.inlineElement = i.element;
20039 				caret.offset = i.offset;
20040 			}
20041 
20042 		}
20043 		void movePageUp(ref Caret caret) {}
20044 		void movePageDown(ref Caret caret) {}
20045 
20046 		void moveDocumentStart(ref Caret caret) {
20047 			if(blocks.length && blocks[0].parts.length)
20048 				caret = Caret(this, blocks[0].parts[0], 0);
20049 			else
20050 				caret = Caret.init;
20051 		}
20052 
20053 		void moveDocumentEnd(ref Caret caret) {
20054 			if(blocks.length) {
20055 				auto parts = blocks[$-1].parts;
20056 				if(parts.length) {
20057 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20058 				} else {
20059 					caret = Caret.init;
20060 				}
20061 			} else
20062 				caret = Caret.init;
20063 		}
20064 
20065 		void deleteSelection() {
20066 			if(selectionStart is selectionEnd)
20067 				return;
20068 
20069 			if(selectionStart.inlineElement is null) return;
20070 			if(selectionEnd.inlineElement is null) return;
20071 
20072 			assert(selectionStart.inlineElement !is null);
20073 			assert(selectionEnd.inlineElement !is null);
20074 
20075 			auto at = selectionStart.inlineElement;
20076 
20077 			if(selectionEnd.inlineElement is at) {
20078 				// same element, need to chop out
20079 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20080 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20081 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20082 			} else {
20083 				// different elements, we can do it with slicing
20084 				at.text = at.text[0 .. selectionStart.offset];
20085 				if(selectionStart.offset < at.letterXs.length)
20086 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20087 
20088 				at = at.getNextInlineElement();
20089 
20090 				while(at) {
20091 					if(at is selectionEnd.inlineElement) {
20092 						at.text = at.text[selectionEnd.offset .. $];
20093 						if(selectionEnd.offset < at.letterXs.length)
20094 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20095 						selectionEnd.offset = 0;
20096 						break;
20097 					} else {
20098 						auto cfd = at;
20099 						cfd.text = null; // delete the whole thing
20100 
20101 						at = at.getNextInlineElement();
20102 
20103 						if(cfd.text.length == 0) {
20104 							// and remove cfd
20105 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20106 								if(cfd.containingBlock.parts[a] is cfd) {
20107 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20108 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20109 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20110 
20111 								}
20112 							}
20113 						}
20114 					}
20115 				}
20116 			}
20117 
20118 			caret = selectionEnd;
20119 			selectNone();
20120 
20121 			invalidateLayout();
20122 
20123 		}
20124 
20125 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20126 		void insert(in char[] text) {
20127 			foreach(dchar ch; text)
20128 				insert(ch);
20129 		}
20130 		/// ditto
20131 		void insert(dchar ch) {
20132 
20133 			bool selectionDeleted = false;
20134 			if(selectionStart !is selectionEnd) {
20135 				deleteSelection();
20136 				selectionDeleted = true;
20137 			}
20138 
20139 			if(ch == 127) {
20140 				delete_();
20141 				return;
20142 			}
20143 			if(ch == 8) {
20144 				if(!selectionDeleted)
20145 					backspace();
20146 				return;
20147 			}
20148 
20149 			invalidateLayout();
20150 
20151 			if(ch == 13) ch = 10;
20152 			auto e = caret.inlineElement;
20153 			if(e is null) {
20154 				addText("" ~ cast(char) ch) ; // FIXME
20155 				return;
20156 			}
20157 
20158 			if(caret.offset == e.text.length) {
20159 				e.text ~= cast(char) ch; // FIXME
20160 				caret.offset++;
20161 				if(ch == 10) {
20162 					auto c = caret.inlineElement.clone;
20163 					c.text = null;
20164 					c.letterXs = null;
20165 					insertPartAfter(c,e);
20166 					caret = Caret(this, c, 0);
20167 				}
20168 			} else {
20169 				// FIXME cast char sucks
20170 				if(ch == 10) {
20171 					auto c = caret.inlineElement.clone;
20172 					c.text = e.text[caret.offset .. $];
20173 					if(caret.offset < c.letterXs.length)
20174 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20175 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20176 					if(caret.offset <= e.letterXs.length) {
20177 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20178 					}
20179 					insertPartAfter(c,e);
20180 					caret = Caret(this, c, 0);
20181 				} else {
20182 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20183 					caret.offset++;
20184 				}
20185 			}
20186 		}
20187 
20188 		void insertPartAfter(InlineElement what, InlineElement where) {
20189 			foreach(idx, p; where.containingBlock.parts) {
20190 				if(p is where) {
20191 					if(idx + 1 == where.containingBlock.parts.length)
20192 						where.containingBlock.parts ~= what;
20193 					else
20194 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20195 					return;
20196 				}
20197 			}
20198 		}
20199 
20200 		void cleanupStructures() {
20201 			for(size_t i = 0; i < blocks.length; i++) {
20202 				auto block = blocks[i];
20203 				for(size_t a = 0; a < block.parts.length; a++) {
20204 					auto part = block.parts[a];
20205 					if(part.text.length == 0) {
20206 						for(size_t b = a; b < block.parts.length - 1; b++)
20207 							block.parts[b] = block.parts[b+1];
20208 						block.parts = block.parts[0 .. $-1];
20209 					}
20210 				}
20211 				if(block.parts.length == 0) {
20212 					for(size_t a = i; a < blocks.length - 1; a++)
20213 						blocks[a] = blocks[a+1];
20214 					blocks = blocks[0 .. $-1];
20215 				}
20216 			}
20217 		}
20218 
20219 		void backspace() {
20220 			try_again:
20221 			auto e = caret.inlineElement;
20222 			if(e is null)
20223 				return;
20224 			if(caret.offset == 0) {
20225 				auto prev = e.getPreviousInlineElement();
20226 				if(prev is null)
20227 					return;
20228 				auto newOffset = cast(int) prev.text.length;
20229 				tryMerge(prev, e);
20230 				caret.inlineElement = prev;
20231 				caret.offset = prev is null ? 0 : newOffset;
20232 
20233 				goto try_again;
20234 			} else if(caret.offset == e.text.length) {
20235 				e.text = e.text[0 .. $-1];
20236 				caret.offset--;
20237 			} else {
20238 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20239 				caret.offset--;
20240 			}
20241 			//cleanupStructures();
20242 
20243 			invalidateLayout();
20244 		}
20245 		void delete_() {
20246 			if(selectionStart !is selectionEnd)
20247 				deleteSelection();
20248 			else {
20249 				auto before = caret;
20250 				moveRight();
20251 				if(caret != before) {
20252 					backspace();
20253 				}
20254 			}
20255 
20256 			invalidateLayout();
20257 		}
20258 		void overstrike() {}
20259 
20260 		/// Selection API. See also: caret movement.
20261 		void selectAll() {
20262 			moveDocumentStart(selectionStart);
20263 			moveDocumentEnd(selectionEnd);
20264 		}
20265 		bool selectNone() {
20266 			if(selectionStart != selectionEnd) {
20267 				selectionStart = selectionEnd = Caret.init;
20268 				return true;
20269 			}
20270 			return false;
20271 		}
20272 
20273 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20274 		/// They will modify the current selection if there is one and will splice one in if needed.
20275 		void changeAttributes() {}
20276 
20277 
20278 		/// Text search api. They manipulate the selection and/or caret.
20279 		void findText(string text) {}
20280 		void findIndex(size_t textIndex) {}
20281 
20282 		// sample event handlers
20283 
20284 		void handleEvent(KeyEvent event) {
20285 			//if(event.type == KeyEvent.Type.KeyPressed) {
20286 
20287 			//}
20288 		}
20289 
20290 		void handleEvent(dchar ch) {
20291 
20292 		}
20293 
20294 		void handleEvent(MouseEvent event) {
20295 
20296 		}
20297 
20298 		bool contentEditable; // can it be edited?
20299 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20300 		bool contentSelectable; // selectable?
20301 
20302 		Caret caret;
20303 		Caret selectionStart;
20304 		Caret selectionEnd;
20305 
20306 		bool insertMode;
20307 	}
20308 
20309 	struct Caret {
20310 		TextLayout layout;
20311 		InlineElement inlineElement;
20312 		int offset;
20313 	}
20314 
20315 	enum TextFormat : ushort {
20316 		// decorations
20317 		underline = 1,
20318 		strikethrough = 2,
20319 
20320 		// font selectors
20321 
20322 		bold = 0x4000 | 1, // weight 700
20323 		light = 0x4000 | 2, // weight 300
20324 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20325 		// bold | light is really invalid but should give weight 500
20326 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20327 
20328 		italic = 0x4000 | 8,
20329 		smallcaps = 0x4000 | 16,
20330 	}
20331 
20332 	void* findFont(string family, int weight, TextFormat formats) {
20333 		return null;
20334 	}
20335 
20336 }
20337 
20338 /++
20339 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20340 
20341 	History:
20342 		Added February 19, 2021
20343 +/
20344 /// Group: drag_and_drop
20345 interface DropHandler {
20346 	/++
20347 		Called when the drag enters the handler's area.
20348 	+/
20349 	DragAndDropAction dragEnter(DropPackage*);
20350 	/++
20351 		Called when the drag leaves the handler's area or is
20352 		cancelled. You should free your resources when this is called.
20353 	+/
20354 	void dragLeave();
20355 	/++
20356 		Called continually as the drag moves over the handler's area.
20357 
20358 		Returns: feedback to the dragger
20359 	+/
20360 	DropParameters dragOver(Point pt);
20361 	/++
20362 		The user dropped the data and you should process it now. You can
20363 		access the data through the given [DropPackage].
20364 	+/
20365 	void drop(scope DropPackage*);
20366 	/++
20367 		Called when the drop is complete. You should free whatever temporary
20368 		resources you were using. It is often reasonable to simply forward
20369 		this call to [dragLeave].
20370 	+/
20371 	void finish();
20372 
20373 	/++
20374 		Parameters returned by [DropHandler.drop].
20375 	+/
20376 	static struct DropParameters {
20377 		/++
20378 			Acceptable action over this area.
20379 		+/
20380 		DragAndDropAction action;
20381 		/++
20382 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20383 
20384 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20385 		+/
20386 		Rectangle consistentWithin;
20387 	}
20388 }
20389 
20390 /++
20391 	History:
20392 		Added February 19, 2021
20393 +/
20394 /// Group: drag_and_drop
20395 enum DragAndDropAction {
20396 	none = 0,
20397 	copy,
20398 	move,
20399 	link,
20400 	ask,
20401 	custom
20402 }
20403 
20404 /++
20405 	An opaque structure representing dropped data. It contains
20406 	private, platform-specific data that your `drop` function
20407 	should simply forward.
20408 
20409 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20410 
20411 	History:
20412 		Added February 19, 2021
20413 +/
20414 /// Group: drag_and_drop
20415 struct DropPackage {
20416 	/++
20417 		Lists the available formats as magic numbers. You should compare these
20418 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20419 		understand the passed data.
20420 	+/
20421 	DraggableData.FormatId[] availableFormats() {
20422 		version(X11) {
20423 			return xFormats;
20424 		} else version(Windows) {
20425 			if(pDataObj is null)
20426 				return null;
20427 
20428 			typeof(return) ret;
20429 
20430 			IEnumFORMATETC ef;
20431 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20432 				FORMATETC fmt;
20433 				ULONG fetched;
20434 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20435 					if(fetched == 0)
20436 						break;
20437 
20438 					if(fmt.lindex != -1)
20439 						continue;
20440 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20441 						continue;
20442 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20443 						continue;
20444 
20445 					ret ~= fmt.cfFormat;
20446 				}
20447 			}
20448 
20449 			return ret;
20450 		}
20451 	}
20452 
20453 	/++
20454 		Gets data from the drop and optionally accepts it.
20455 
20456 		Returns:
20457 			void because the data is fed asynchronously through the `dg` parameter.
20458 
20459 		Params:
20460 			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.
20461 
20462 			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.
20463 
20464 			Calling `getData` again after accepting a drop is not permitted.
20465 
20466 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20467 
20468 			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.
20469 
20470 		Throws:
20471 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20472 
20473 		History:
20474 			Included in first release of [DropPackage].
20475 	+/
20476 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20477 		version(X11) {
20478 
20479 			auto display = XDisplayConnection.get();
20480 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20481 			auto best = format;
20482 
20483 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20484 
20485 				XDisplay* display;
20486 				Atom selectionAtom;
20487 				DraggableData.FormatId best;
20488 				DraggableData.FormatId format;
20489 				void delegate(scope ubyte[] data) dg;
20490 				DragAndDropAction acceptedAction;
20491 				Window sourceWindow;
20492 				SimpleWindow win;
20493 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20494 					this.display = display;
20495 					this.win = win;
20496 					this.sourceWindow = sourceWindow;
20497 					this.format = format;
20498 					this.selectionAtom = selectionAtom;
20499 					this.best = best;
20500 					this.dg = dg;
20501 					this.acceptedAction = acceptedAction;
20502 				}
20503 
20504 
20505 				mixin X11GetSelectionHandler_Basics;
20506 
20507 				void handleData(Atom target, in ubyte[] data) {
20508 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20509 
20510 					dg(cast(ubyte[]) data);
20511 
20512 					if(acceptedAction != DragAndDropAction.none) {
20513 						auto display = XDisplayConnection.get;
20514 
20515 						XClientMessageEvent xclient;
20516 
20517 						xclient.type = EventType.ClientMessage;
20518 						xclient.window = sourceWindow;
20519 						xclient.message_type = GetAtom!"XdndFinished"(display);
20520 						xclient.format = 32;
20521 						xclient.data.l[0] = win.impl.window;
20522 						xclient.data.l[1] = 1; // drop successful
20523 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20524 
20525 						XSendEvent(
20526 							display,
20527 							sourceWindow,
20528 							false,
20529 							EventMask.NoEventMask,
20530 							cast(XEvent*) &xclient
20531 						);
20532 
20533 						XFlush(display);
20534 					}
20535 				}
20536 
20537 				Atom findBestFormat(Atom[] answer) {
20538 					Atom best = None;
20539 					foreach(option; answer) {
20540 						if(option == format) {
20541 							best = option;
20542 							break;
20543 						}
20544 						/*
20545 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20546 							best = option;
20547 							break;
20548 						} else if(option == XA_STRING) {
20549 							best = option;
20550 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20551 							best = option;
20552 						}
20553 						*/
20554 					}
20555 					return best;
20556 				}
20557 			}
20558 
20559 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20560 
20561 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20562 
20563 		} else version(Windows) {
20564 
20565 			// clean up like DragLeave
20566 			// pass effect back up
20567 
20568 			FORMATETC t;
20569 			assert(format >= 0 && format <= ushort.max);
20570 			t.cfFormat = cast(ushort) format;
20571 			t.lindex = -1;
20572 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20573 			t.tymed = TYMED.TYMED_HGLOBAL;
20574 
20575 			STGMEDIUM m;
20576 
20577 			if(pDataObj.GetData(&t, &m) != S_OK) {
20578 				// fail
20579 			} else {
20580 				// succeed, take the data and clean up
20581 
20582 				// FIXME: ensure it is legit HGLOBAL
20583 				auto handle = m.hGlobal;
20584 
20585 				if(handle) {
20586 					auto sz = GlobalSize(handle);
20587 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20588 						scope(exit) GlobalUnlock(handle);
20589 						scope(exit) GlobalFree(handle);
20590 
20591 						auto data = ptr[0 .. sz];
20592 
20593 						dg(data);
20594 					}
20595 				}
20596 			}
20597 		}
20598 	}
20599 
20600 	private:
20601 
20602 	version(X11) {
20603 		SimpleWindow win;
20604 		Window sourceWindow;
20605 		Time dataTimestamp;
20606 
20607 		Atom[] xFormats;
20608 	}
20609 	version(Windows) {
20610 		IDataObject pDataObj;
20611 	}
20612 }
20613 
20614 /++
20615 	A generic helper base class for making a drop handler with a preference list of custom types.
20616 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20617 	droppers too.
20618 
20619 	It assumes the whole window it used, but you can subclass to change that.
20620 
20621 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20622 
20623 	History:
20624 		Added February 19, 2021
20625 +/
20626 /// Group: drag_and_drop
20627 class GenericDropHandlerBase : DropHandler {
20628 	// no fancy state here so no need to do anything here
20629 	void finish() { }
20630 	void dragLeave() { }
20631 
20632 	private DragAndDropAction acceptedAction;
20633 	private DraggableData.FormatId acceptedFormat;
20634 	private void delegate(scope ubyte[]) acceptedHandler;
20635 
20636 	struct FormatHandler {
20637 		DraggableData.FormatId format;
20638 		void delegate(scope ubyte[]) handler;
20639 	}
20640 
20641 	protected abstract FormatHandler[] formatHandlers();
20642 
20643 	DragAndDropAction dragEnter(DropPackage* pkg) {
20644 		debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20645 		foreach(fmt; formatHandlers())
20646 		foreach(f; pkg.availableFormats())
20647 			if(f == fmt.format) {
20648 				acceptedFormat = f;
20649 				acceptedHandler = fmt.handler;
20650 				return acceptedAction = DragAndDropAction.copy;
20651 			}
20652 		return acceptedAction = DragAndDropAction.none;
20653 	}
20654 	DropParameters dragOver(Point pt) {
20655 		return DropParameters(acceptedAction);
20656 	}
20657 
20658 	void drop(scope DropPackage* dropPackage) {
20659 		if(!acceptedFormat || acceptedHandler is null) {
20660 			debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20661 			return; // prolly shouldn't happen anyway...
20662 		}
20663 
20664 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20665 	}
20666 }
20667 
20668 /++
20669 	A simple handler for making your window accept drops of plain text.
20670 
20671 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20672 
20673 	History:
20674 		Added February 22, 2021
20675 +/
20676 /// Group: drag_and_drop
20677 class TextDropHandler : GenericDropHandlerBase {
20678 	private void delegate(in char[] text) dg;
20679 
20680 	/++
20681 
20682 	+/
20683 	this(void delegate(in char[] text) dg) {
20684 		this.dg = dg;
20685 	}
20686 
20687 	protected override FormatHandler[] formatHandlers() {
20688 		version(X11)
20689 			return [
20690 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
20691 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
20692 			];
20693 		else version(Windows)
20694 			return [
20695 				FormatHandler(CF_UNICODETEXT, &translator),
20696 			];
20697 	}
20698 
20699 	private void translator(scope ubyte[] data) {
20700 		version(X11)
20701 			dg(cast(char[]) data);
20702 		else version(Windows)
20703 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
20704 	}
20705 }
20706 
20707 /++
20708 	A simple handler for making your window accept drops of files, issued to you as file names.
20709 
20710 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20711 
20712 	History:
20713 		Added February 22, 2021
20714 +/
20715 /// Group: drag_and_drop
20716 
20717 class FilesDropHandler : GenericDropHandlerBase {
20718 	private void delegate(in char[][]) dg;
20719 
20720 	/++
20721 
20722 	+/
20723 	this(void delegate(in char[][] fileNames) dg) {
20724 		this.dg = dg;
20725 	}
20726 
20727 	protected override FormatHandler[] formatHandlers() {
20728 		version(X11)
20729 			return [
20730 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
20731 			];
20732 		else version(Windows)
20733 			return [
20734 				FormatHandler(CF_HDROP, &translator),
20735 			];
20736 	}
20737 
20738 	private void translator(scope ubyte[] data) {
20739 		version(X11) {
20740 			char[] listString = cast(char[]) data;
20741 			char[][16] buffer;
20742 			int count;
20743 			char[][] result = buffer[];
20744 
20745 			void commit(char[] s) {
20746 				if(count == result.length)
20747 					result.length += 16;
20748 				if(s.length > 7 && s[0 ..7] == "file://")
20749 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
20750 				result[count++] = s;
20751 			}
20752 
20753 			size_t last;
20754 			foreach(idx, char c; listString) {
20755 				if(c == '\n') {
20756 					commit(listString[last .. idx - 1]); // a \r
20757 					last = idx + 1; // a \n
20758 				}
20759 			}
20760 
20761 			if(last < listString.length) {
20762 				commit(listString[last .. $]);
20763 			}
20764 
20765 			// FIXME: they are uris now, should I translate it to local file names?
20766 			// of course the host name is supposed to be there cuz of X rokking...
20767 
20768 			dg(result[0 .. count]);
20769 		} else version(Windows) {
20770 
20771 			static struct DROPFILES {
20772 				DWORD pFiles;
20773 				POINT pt;
20774 				BOOL  fNC;
20775 				BOOL  fWide;
20776 			}
20777 
20778 
20779 			const(char)[][16] buffer;
20780 			int count;
20781 			const(char)[][] result = buffer[];
20782 			size_t last;
20783 
20784 			void commitA(in char[] stuff) {
20785 				if(count == result.length)
20786 					result.length += 16;
20787 				result[count++] = stuff;
20788 			}
20789 
20790 			void commitW(in wchar[] stuff) {
20791 				commitA(makeUtf8StringFromWindowsString(stuff));
20792 			}
20793 
20794 			void magic(T)(T chars) {
20795 				size_t idx;
20796 				while(chars[idx]) {
20797 					last = idx;
20798 					while(chars[idx]) {
20799 						idx++;
20800 					}
20801 					static if(is(T == char*))
20802 						commitA(chars[last .. idx]);
20803 					else
20804 						commitW(chars[last .. idx]);
20805 					idx++;
20806 				}
20807 			}
20808 
20809 			auto df = cast(DROPFILES*) data.ptr;
20810 			if(df.fWide) {
20811 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
20812 				magic(chars);
20813 			} else {
20814 				char* chars = cast(char*) (data.ptr + df.pFiles);
20815 				magic(chars);
20816 			}
20817 			dg(result[0 .. count]);
20818 		}
20819 	}
20820 }
20821 
20822 /++
20823 	Interface to describe data being dragged. See also [draggable] helper function.
20824 
20825 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20826 
20827 	History:
20828 		Added February 19, 2021
20829 +/
20830 interface DraggableData {
20831 	version(X11)
20832 		alias FormatId = Atom;
20833 	else
20834 		alias FormatId = uint;
20835 	/++
20836 		Gets the platform-specific FormatId associated with the given named format.
20837 
20838 		This may be a MIME type, but may also be other various strings defined by the
20839 		programs you want to interoperate with.
20840 
20841 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
20842 		and convert it to some particular type for you.
20843 	+/
20844 	static FormatId getFormatId(string name)() {
20845 		version(X11)
20846 			return GetAtom!name(XDisplayConnection.get);
20847 		else version(Windows) {
20848 			static UINT cache;
20849 			if(!cache)
20850 				cache = RegisterClipboardFormatA(name);
20851 			return cache;
20852 		} else
20853 			throw new NotYetImplementedException();
20854 	}
20855 
20856 	/++
20857 		Looks up a string to represent the name for the given format, if there is one.
20858 
20859 		You should avoid using this function because it is slow. It is provided more for
20860 		debugging than for primary use.
20861 	+/
20862 	static string getFormatName(FormatId format) {
20863 		version(X11) {
20864 			if(format == 0)
20865 				return "None";
20866 			else
20867 				return getAtomName(format, XDisplayConnection.get);
20868 		} else version(Windows) {
20869 			switch(format) {
20870 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
20871 				case CF_DIBV5: return "CF_DIBV5";
20872 				case CF_RIFF: return "CF_RIFF";
20873 				case CF_WAVE: return "CF_WAVE";
20874 				case CF_HDROP: return "CF_HDROP";
20875 				default:
20876 					char[1024] name;
20877 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
20878 					return name[0 .. count].idup;
20879 			}
20880 		}
20881 	}
20882 
20883 	FormatId[] availableFormats();
20884 	// Return the slice of data you filled, empty slice if done.
20885 	// this is to support the incremental thing
20886 	ubyte[] getData(FormatId format, return scope ubyte[] data);
20887 
20888 	size_t dataLength(FormatId format);
20889 }
20890 
20891 /++
20892 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20893 
20894 	History:
20895 		Added February 19, 2021
20896 +/
20897 DraggableData draggable(string s) {
20898 	version(X11)
20899 	return new class X11SetSelectionHandler_Text, DraggableData {
20900 		this() {
20901 			super(s);
20902 		}
20903 
20904 		override FormatId[] availableFormats() {
20905 			return X11SetSelectionHandler_Text.availableFormats();
20906 		}
20907 
20908 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
20909 			return X11SetSelectionHandler_Text.getData(format, data);
20910 		}
20911 
20912 		size_t dataLength(FormatId format) {
20913 			return s.length;
20914 		}
20915 	};
20916 	version(Windows)
20917 	return new class DraggableData {
20918 		FormatId[] availableFormats() {
20919 			return [CF_UNICODETEXT];
20920 		}
20921 
20922 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
20923 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
20924 		}
20925 
20926 		size_t dataLength(FormatId format) {
20927 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
20928 		}
20929 	};
20930 }
20931 
20932 /++
20933 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20934 
20935 	History:
20936 		Added February 19, 2021
20937 +/
20938 /// Group: drag_and_drop
20939 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
20940 in {
20941 	assert(window !is null);
20942 	assert(handler !is null);
20943 }
20944 do
20945 {
20946 	version(X11) {
20947 		auto sh = cast(X11SetSelectionHandler) handler;
20948 		if(sh is null) {
20949 			// gotta make my own adapter.
20950 			sh = new class X11SetSelectionHandler {
20951 				mixin X11SetSelectionHandler_Basics;
20952 
20953 				Atom[] availableFormats() { return handler.availableFormats(); }
20954 				ubyte[] getData(Atom format, return scope ubyte[] data) {
20955 					return handler.getData(format, data);
20956 				}
20957 
20958 				// since the drop selection is only ever used once it isn't important
20959 				// to reset it.
20960 				void done() {}
20961 			};
20962 		}
20963 		return doDragDropX11(window, sh, action);
20964 	} else version(Windows) {
20965 		return doDragDropWindows(window, handler, action);
20966 	} else throw new NotYetImplementedException();
20967 }
20968 
20969 version(Windows)
20970 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
20971 	IDataObject obj = new class IDataObject {
20972 		ULONG refCount;
20973 		ULONG AddRef() {
20974 			return ++refCount;
20975 		}
20976 		ULONG Release() {
20977 			return --refCount;
20978 		}
20979 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
20980 			if (IID_IUnknown == *riid) {
20981 				*ppv = cast(void*) cast(IUnknown) this;
20982 			}
20983 			else if (IID_IDataObject == *riid) {
20984 				*ppv = cast(void*) cast(IDataObject) this;
20985 			}
20986 			else {
20987 				*ppv = null;
20988 				return E_NOINTERFACE;
20989 			}
20990 
20991 			AddRef();
20992 			return NOERROR;
20993 		}
20994 
20995 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
20996 			// import std.stdio; writeln("Advise");
20997 			return E_NOTIMPL;
20998 		}
20999 		HRESULT DUnadvise(DWORD dwConnection) {
21000 			return E_NOTIMPL;
21001 		}
21002 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21003 			// import std.stdio; writeln("EnumDAdvise");
21004 			return OLE_E_ADVISENOTSUPPORTED;
21005 		}
21006 		// tell what formats it supports
21007 
21008 		FORMATETC[] types;
21009 		this() {
21010 			FORMATETC t;
21011 			foreach(ty; handler.availableFormats()) {
21012 				assert(ty <= ushort.max && ty >= 0);
21013 				t.cfFormat = cast(ushort) ty;
21014 				t.lindex = -1;
21015 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21016 				t.tymed = TYMED.TYMED_HGLOBAL;
21017 			}
21018 			types ~= t;
21019 		}
21020 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21021 			if(dwDirection == DATADIR.DATADIR_GET) {
21022 				*ppenumFormatEtc = new class IEnumFORMATETC {
21023 					ULONG refCount;
21024 					ULONG AddRef() {
21025 						return ++refCount;
21026 					}
21027 					ULONG Release() {
21028 						return --refCount;
21029 					}
21030 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21031 						if (IID_IUnknown == *riid) {
21032 							*ppv = cast(void*) cast(IUnknown) this;
21033 						}
21034 						else if (IID_IEnumFORMATETC == *riid) {
21035 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21036 						}
21037 						else {
21038 							*ppv = null;
21039 							return E_NOINTERFACE;
21040 						}
21041 
21042 						AddRef();
21043 						return NOERROR;
21044 					}
21045 
21046 
21047 					int pos;
21048 					this() {
21049 						pos = 0;
21050 					}
21051 
21052 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21053 						// import std.stdio; writeln("clone");
21054 						return E_NOTIMPL; // FIXME
21055 					}
21056 
21057 					// Caller is responsible for freeing memory
21058 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21059 						// fetched may be null if celt is one
21060 						if(celt != 1)
21061 							return E_NOTIMPL; // FIXME
21062 
21063 						if(celt + pos > types.length)
21064 							return S_FALSE;
21065 
21066 						*rgelt = types[pos++];
21067 
21068 						if(pceltFetched !is null)
21069 							*pceltFetched = 1;
21070 
21071 						// import std.stdio; writeln("ok celt ", celt);
21072 						return S_OK;
21073 					}
21074 
21075 					HRESULT Reset() {
21076 						pos = 0;
21077 						return S_OK;
21078 					}
21079 
21080 					HRESULT Skip(ULONG celt) {
21081 						if(celt + pos <= types.length) {
21082 							pos += celt;
21083 							return S_OK;
21084 						}
21085 						return S_FALSE;
21086 					}
21087 				};
21088 
21089 				return S_OK;
21090 			} else
21091 				return E_NOTIMPL;
21092 		}
21093 		// given a format, return the format you'd prefer to use cuz it is identical
21094 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21095 			// FIXME: prolly could be better but meh
21096 			// import std.stdio; writeln("gcf: ", *pformatectIn);
21097 			*pformatetcOut = *pformatectIn;
21098 			return S_OK;
21099 		}
21100 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21101 			foreach(ty; types) {
21102 				if(ty == *pformatetcIn) {
21103 					auto format = ty.cfFormat;
21104 					// import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty);
21105 					STGMEDIUM medium;
21106 					medium.tymed = TYMED.TYMED_HGLOBAL;
21107 
21108 					auto sz = handler.dataLength(format);
21109 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21110 					if(handle is null) throw new Exception("GlobalAlloc");
21111 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21112 						auto slice = data[0 .. sz];
21113 						scope(exit)
21114 							GlobalUnlock(handle);
21115 
21116 						handler.getData(format, cast(ubyte[]) slice[]);
21117 					}
21118 
21119 
21120 					medium.hGlobal = handle; // FIXME
21121 					*pmedium = medium;
21122 					return S_OK;
21123 				}
21124 			}
21125 			return DV_E_FORMATETC;
21126 		}
21127 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21128 			// import std.stdio; writeln("GDH: ", *pformatetcIn);
21129 			return E_NOTIMPL; // FIXME
21130 		}
21131 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21132 			auto search = *pformatetc;
21133 			search.tymed &= TYMED.TYMED_HGLOBAL;
21134 			foreach(ty; types)
21135 				if(ty == search) {
21136 					// import std.stdio; writeln("QueryGetData ", search, " ", types[0]);
21137 					return S_OK;
21138 				}
21139 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21140 				//import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]);
21141 			}
21142 			return S_FALSE;
21143 		}
21144 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21145 			// import std.stdio; writeln("SetData: ");
21146 			return E_NOTIMPL;
21147 		}
21148 	};
21149 
21150 
21151 	IDropSource src = new class IDropSource {
21152 		ULONG refCount;
21153 		ULONG AddRef() {
21154 			return ++refCount;
21155 		}
21156 		ULONG Release() {
21157 			return --refCount;
21158 		}
21159 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21160 			if (IID_IUnknown == *riid) {
21161 				*ppv = cast(void*) cast(IUnknown) this;
21162 			}
21163 			else if (IID_IDropSource == *riid) {
21164 				*ppv = cast(void*) cast(IDropSource) this;
21165 			}
21166 			else {
21167 				*ppv = null;
21168 				return E_NOINTERFACE;
21169 			}
21170 
21171 			AddRef();
21172 			return NOERROR;
21173 		}
21174 
21175 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21176 			if(fEscapePressed)
21177 				return DRAGDROP_S_CANCEL;
21178 			if(!(grfKeyState & MK_LBUTTON))
21179 				return DRAGDROP_S_DROP;
21180 			return S_OK;
21181 		}
21182 
21183 		int GiveFeedback(uint dwEffect) {
21184 			return DRAGDROP_S_USEDEFAULTCURSORS;
21185 		}
21186 	};
21187 
21188 	DWORD effect;
21189 
21190 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21191 
21192 	DROPEFFECT de = win32DragAndDropAction(action);
21193 
21194 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21195 	// but still prolly a FIXME
21196 
21197 	auto ret = DoDragDrop(obj, src, de, &effect);
21198 	/+
21199 	import std.stdio;
21200 	if(ret == DRAGDROP_S_DROP)
21201 		writeln("drop ", effect);
21202 	else if(ret == DRAGDROP_S_CANCEL)
21203 		writeln("cancel");
21204 	else if(ret == S_OK)
21205 		writeln("ok");
21206 	else writeln(ret);
21207 	+/
21208 
21209 	return ret;
21210 }
21211 
21212 version(Windows)
21213 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21214 	DROPEFFECT de;
21215 
21216 	with(DragAndDropAction)
21217 	with(DROPEFFECT)
21218 	final switch(action) {
21219 		case none: de = DROPEFFECT_NONE; break;
21220 		case copy: de = DROPEFFECT_COPY; break;
21221 		case move: de = DROPEFFECT_MOVE; break;
21222 		case link: de = DROPEFFECT_LINK; break;
21223 		case ask: throw new Exception("ask not implemented yet");
21224 		case custom: throw new Exception("custom not implemented yet");
21225 	}
21226 
21227 	return de;
21228 }
21229 
21230 
21231 /++
21232 	History:
21233 		Added February 19, 2021
21234 +/
21235 /// Group: drag_and_drop
21236 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21237 	version(X11) {
21238 		auto display = XDisplayConnection.get;
21239 
21240 		Atom atom = 5; // right???
21241 
21242 		XChangeProperty(
21243 			display,
21244 			window.impl.window,
21245 			GetAtom!"XdndAware"(display),
21246 			XA_ATOM,
21247 			32 /* bits */,
21248 			PropModeReplace,
21249 			&atom,
21250 			1);
21251 
21252 		window.dropHandler = handler;
21253 	} else version(Windows) {
21254 
21255 		initDnd();
21256 
21257 		auto dropTarget = new class (handler) IDropTarget {
21258 			DropHandler handler;
21259 			this(DropHandler handler) {
21260 				this.handler = handler;
21261 			}
21262 			ULONG refCount;
21263 			ULONG AddRef() {
21264 				return ++refCount;
21265 			}
21266 			ULONG Release() {
21267 				return --refCount;
21268 			}
21269 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21270 				if (IID_IUnknown == *riid) {
21271 					*ppv = cast(void*) cast(IUnknown) this;
21272 				}
21273 				else if (IID_IDropTarget == *riid) {
21274 					*ppv = cast(void*) cast(IDropTarget) this;
21275 				}
21276 				else {
21277 					*ppv = null;
21278 					return E_NOINTERFACE;
21279 				}
21280 
21281 				AddRef();
21282 				return NOERROR;
21283 			}
21284 
21285 
21286 			// ///////////////////
21287 
21288 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21289 				DropPackage dropPackage = DropPackage(pDataObj);
21290 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21291 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21292 			}
21293 
21294 			HRESULT DragLeave() {
21295 				handler.dragLeave();
21296 				// release the IDataObject if needed
21297 				return S_OK;
21298 			}
21299 
21300 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21301 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21302 
21303 				*pdwEffect = win32DragAndDropAction(res.action);
21304 				// same as DragEnter basically
21305 				return S_OK;
21306 			}
21307 
21308 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21309 				DropPackage pkg = DropPackage(pDataObj);
21310 				handler.drop(&pkg);
21311 
21312 				return S_OK;
21313 			}
21314 		};
21315 		// Windows can hold on to the handler and try to call it
21316 		// during which time the GC can't see it. so important to
21317 		// manually manage this. At some point i'll FIXME and make
21318 		// all my com instances manually managed since they supposed
21319 		// to respect the refcount.
21320 		import core.memory;
21321 		GC.addRoot(cast(void*) dropTarget);
21322 
21323 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21324 			throw new Exception("register");
21325 
21326 		window.dropHandler = handler;
21327 	} else throw new NotYetImplementedException();
21328 }
21329 
21330 
21331 
21332 static if(UsingSimpledisplayX11) {
21333 
21334 enum _NET_WM_STATE_ADD = 1;
21335 enum _NET_WM_STATE_REMOVE = 0;
21336 enum _NET_WM_STATE_TOGGLE = 2;
21337 
21338 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21339 void demandAttention(SimpleWindow window, bool needs = true) {
21340 	demandAttention(window.impl.window, needs);
21341 }
21342 
21343 /// ditto
21344 void demandAttention(Window window, bool needs = true) {
21345 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21346 }
21347 
21348 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21349 	auto display = XDisplayConnection.get();
21350 	if(atom == None)
21351 		return; // non-failure error
21352 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21353 
21354 	XClientMessageEvent xclient;
21355 
21356 	xclient.type = EventType.ClientMessage;
21357 	xclient.window = window;
21358 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21359 	xclient.format = 32;
21360 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21361 	xclient.data.l[1] = atom;
21362 	xclient.data.l[2] = atom2;
21363 	xclient.data.l[3] = 1;
21364 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21365 
21366 	XSendEvent(
21367 		display,
21368 		RootWindow(display, DefaultScreen(display)),
21369 		false,
21370 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21371 		cast(XEvent*) &xclient
21372 	);
21373 
21374 	/+
21375 	XChangeProperty(
21376 		display,
21377 		window.impl.window,
21378 		GetAtom!"_NET_WM_STATE"(display),
21379 		XA_ATOM,
21380 		32 /* bits */,
21381 		PropModeAppend,
21382 		&atom,
21383 		1);
21384 	+/
21385 }
21386 
21387 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21388 	Atom actionAtom;
21389 	with(DragAndDropAction)
21390 	final switch(action) {
21391 		case none: actionAtom = None; break;
21392 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21393 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21394 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21395 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21396 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21397 	}
21398 
21399 	return actionAtom;
21400 }
21401 
21402 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21403 	// FIXME: I need to show user feedback somehow.
21404 	auto display = XDisplayConnection.get;
21405 
21406 	auto actionAtom = dndActionAtom(display, action);
21407 	assert(actionAtom, "Don't use action none to accept a drop");
21408 
21409 	setX11Selection!"XdndSelection"(window, handler, null);
21410 
21411 	auto oldKeyHandler = window.handleKeyEvent;
21412 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21413 
21414 	auto oldCharHandler = window.handleCharEvent;
21415 	scope(exit) window.handleCharEvent = oldCharHandler;
21416 
21417 	auto oldMouseHandler = window.handleMouseEvent;
21418 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21419 
21420 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21421 
21422 	import core.sys.posix.sys.time;
21423 	timeval tv;
21424 	gettimeofday(&tv, null);
21425 
21426 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21427 
21428 	Time lastMouseTimestamp;
21429 
21430 	bool dnding = true;
21431 	Window lastIn = None;
21432 
21433 	void leave() {
21434 		if(lastIn == None)
21435 			return;
21436 
21437 		XEvent ev;
21438 		ev.xclient.type = EventType.ClientMessage;
21439 		ev.xclient.window = lastIn;
21440 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21441 		ev.xclient.format = 32;
21442 		ev.xclient.data.l[0] = window.impl.window;
21443 
21444 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21445 		XFlush(display);
21446 
21447 		lastIn = None;
21448 	}
21449 
21450 	void enter(Window w) {
21451 		assert(lastIn == None);
21452 
21453 		lastIn = w;
21454 
21455 		XEvent ev;
21456 		ev.xclient.type = EventType.ClientMessage;
21457 		ev.xclient.window = lastIn;
21458 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21459 		ev.xclient.format = 32;
21460 		ev.xclient.data.l[0] = window.impl.window;
21461 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21462 
21463 		auto types = handler.availableFormats();
21464 		assert(types.length > 0);
21465 
21466 		ev.xclient.data.l[2] = types[0];
21467 		if(types.length > 1)
21468 			ev.xclient.data.l[3] = types[1];
21469 		if(types.length > 2)
21470 			ev.xclient.data.l[4] = types[2];
21471 
21472 		// FIXME: other types?!?!? and make sure we skip TARGETS
21473 
21474 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21475 		XFlush(display);
21476 	}
21477 
21478 	void position(int rootX, int rootY) {
21479 		assert(lastIn != None);
21480 
21481 		XEvent ev;
21482 		ev.xclient.type = EventType.ClientMessage;
21483 		ev.xclient.window = lastIn;
21484 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21485 		ev.xclient.format = 32;
21486 		ev.xclient.data.l[0] = window.impl.window;
21487 		ev.xclient.data.l[1] = 0; // reserved
21488 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21489 		ev.xclient.data.l[3] = dataTimestamp;
21490 		ev.xclient.data.l[4] = actionAtom;
21491 
21492 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21493 		XFlush(display);
21494 
21495 	}
21496 
21497 	void drop() {
21498 		XEvent ev;
21499 		ev.xclient.type = EventType.ClientMessage;
21500 		ev.xclient.window = lastIn;
21501 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21502 		ev.xclient.format = 32;
21503 		ev.xclient.data.l[0] = window.impl.window;
21504 		ev.xclient.data.l[1] = 0; // reserved
21505 		ev.xclient.data.l[2] = dataTimestamp;
21506 
21507 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21508 		XFlush(display);
21509 
21510 		lastIn = None;
21511 		dnding = false;
21512 	}
21513 
21514 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21515 	// but idk if i should...
21516 
21517 	window.setEventHandlers(
21518 		delegate(KeyEvent ev) {
21519 			if(ev.pressed == true && ev.key == Key.Escape) {
21520 				// cancel
21521 				dnding = false;
21522 			}
21523 		},
21524 		delegate(MouseEvent ev) {
21525 			if(ev.timestamp < lastMouseTimestamp)
21526 				return;
21527 
21528 			lastMouseTimestamp = ev.timestamp;
21529 
21530 			if(ev.type == MouseEventType.motion) {
21531 				auto display = XDisplayConnection.get;
21532 				auto root = RootWindow(display, DefaultScreen(display));
21533 
21534 				Window topWindow;
21535 				int rootX, rootY;
21536 
21537 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21538 
21539 				if(topWindow == None)
21540 					return;
21541 
21542 				top:
21543 				if(auto result = topWindow in eligibility) {
21544 					auto dropWindow = *result;
21545 					if(dropWindow == None) {
21546 						leave();
21547 						return;
21548 					}
21549 
21550 					if(dropWindow != lastIn) {
21551 						leave();
21552 						enter(dropWindow);
21553 						position(rootX, rootY);
21554 					} else {
21555 						position(rootX, rootY);
21556 					}
21557 				} else {
21558 					// determine eligibility
21559 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21560 					if(data.length == 1) {
21561 						// in case there is no WM or it isn't reparenting
21562 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21563 					} else {
21564 
21565 						Window tryScanChildren(Window search, int maxRecurse) {
21566 							// could be reparenting window manager, so gotta check the next few children too
21567 							Window child;
21568 							int x;
21569 							int y;
21570 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21571 
21572 							if(child == None)
21573 								return None;
21574 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21575 							if(data.length == 1) {
21576 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21577 							} else {
21578 								if(maxRecurse)
21579 									return tryScanChildren(child, maxRecurse - 1);
21580 								else
21581 									return None;
21582 							}
21583 
21584 						}
21585 
21586 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21587 						auto topResult = tryScanChildren(topWindow, 3);
21588 						// it is easy to have a false negative due to the mouse going over a WM
21589 						// child window like the close button if separate from the frame... so I
21590 						// can't really cache negatives, :(
21591 						if(topResult != None) {
21592 							eligibility[topWindow] = topResult;
21593 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21594 						}
21595 					}
21596 
21597 				}
21598 
21599 			} else if(ev.type == MouseEventType.buttonReleased) {
21600 				drop();
21601 				dnding = false;
21602 			}
21603 		}
21604 	);
21605 
21606 	window.grabInput();
21607 	scope(exit)
21608 		window.releaseInputGrab();
21609 
21610 
21611 	EventLoop.get.run(() => dnding);
21612 
21613 	return 0;
21614 }
21615 
21616 /// X-specific
21617 TrueColorImage getWindowNetWmIcon(Window window) {
21618 	try {
21619 		auto display = XDisplayConnection.get;
21620 
21621 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21622 
21623 		if (data.length > arch_ulong.sizeof * 2) {
21624 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21625 			// these are an array of rgba images that we have to convert into pixmaps ourself
21626 
21627 			int width = cast(int) meta[0];
21628 			int height = cast(int) meta[1];
21629 
21630 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21631 
21632 			static if(arch_ulong.sizeof == 4) {
21633 				bytes = bytes[0 .. width * height * 4];
21634 				alias imageData = bytes;
21635 			} else static if(arch_ulong.sizeof == 8) {
21636 				bytes = bytes[0 .. width * height * 8];
21637 				auto imageData = new ubyte[](4 * width * height);
21638 			} else static assert(0);
21639 
21640 
21641 
21642 			// this returns ARGB. Remember it is little-endian so
21643 			//                                         we have BGRA
21644 			// our thing uses RGBA, which in little endian, is ABGR
21645 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21646 				auto r = bytes[idx + 2];
21647 				auto g = bytes[idx + 1];
21648 				auto b = bytes[idx + 0];
21649 				auto a = bytes[idx + 3];
21650 
21651 				imageData[idx2 + 0] = r;
21652 				imageData[idx2 + 1] = g;
21653 				imageData[idx2 + 2] = b;
21654 				imageData[idx2 + 3] = a;
21655 			}
21656 
21657 			return new TrueColorImage(width, height, imageData);
21658 		}
21659 
21660 		return null;
21661 	} catch(Exception e) {
21662 		return null;
21663 	}
21664 }
21665 
21666 } /* UsingSimpledisplayX11 */
21667 
21668 
21669 void loadBinNameToWindowClassName () {
21670 	import core.stdc.stdlib : realloc;
21671 	version(linux) {
21672 		// args[0] MAY be empty, so we'll just use this
21673 		import core.sys.posix.unistd : readlink;
21674 		char[1024] ebuf = void; // 1KB should be enough for everyone!
21675 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
21676 		if (len < 1) return;
21677 	} else /*version(Windows)*/ {
21678 		import core.runtime : Runtime;
21679 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
21680 		auto ebuf = Runtime.args[0];
21681 		auto len = ebuf.length;
21682 	}
21683 	auto pos = len;
21684 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
21685 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
21686 	if (sdpyWindowClassStr is null) return; // oops
21687 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
21688 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
21689 }
21690 
21691 /++
21692 	An interface representing a font that is drawn with custom facilities.
21693 
21694 	You might want [OperatingSystemFont] instead, which represents
21695 	a font loaded and drawn by functions native to the operating system.
21696 
21697 	WARNING: I might still change this.
21698 +/
21699 interface DrawableFont : MeasurableFont {
21700 	/++
21701 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
21702 
21703 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
21704 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
21705 		fill color, but that's up to the implementation.
21706 	+/
21707 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
21708 
21709 	/++
21710 		Requests that the given string is added to the image cache. You should only do this rarely, but
21711 		if you have a string that you know will be used over and over again, adding it to a cache can
21712 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
21713 		to implement this as a do-nothing method).
21714 	+/
21715 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
21716 }
21717 
21718 /++
21719 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
21720 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
21721 
21722 	You should also consider [OperatingSystemFont], which loads and draws a font with
21723 	facilities native to the user's operating system. You might also consider
21724 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
21725 	of game, as they have their own ways to draw text too.
21726 
21727 	Be warned: this can be slow, especially on remote connections to the X server, since
21728 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
21729 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
21730 	experiment in your specific case.
21731 
21732 	Please note that the return type of [DrawableFont] also includes an implementation of
21733 	[MeasurableFont].
21734 +/
21735 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
21736 	import arsd.ttf;
21737 	static class ArsdTtfFont : DrawableFont {
21738 		TtfFont font;
21739 		int size;
21740 		this(in ubyte[] data, int size) {
21741 			font = TtfFont(data);
21742 			this.size = size;
21743 
21744 
21745 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
21746 			int ascent_, descent_, line_gap;
21747 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
21748 
21749 			int advance, lsb;
21750 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
21751 			xWidth = cast(int) (advance * scale);
21752 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
21753 			MWidth = cast(int) (advance * scale);
21754 		}
21755 
21756 		private int ascent_;
21757 		private int descent_;
21758 		private int xWidth;
21759 		private int MWidth;
21760 
21761 		bool isMonospace() {
21762 			return xWidth == MWidth;
21763 		}
21764 		int averageWidth() {
21765 			return xWidth;
21766 		}
21767 		int height() {
21768 			return size;
21769 		}
21770 		int ascent() {
21771 			return ascent_;
21772 		}
21773 		int descent() {
21774 			return descent_;
21775 		}
21776 
21777 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
21778 			int width, height;
21779 			font.getStringSize(s, size, width, height);
21780 			return width;
21781 		}
21782 
21783 
21784 
21785 		Sprite[string] cache;
21786 
21787 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
21788 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
21789 			cache[text] = sprite;
21790 		}
21791 
21792 		Image stringToImage(Color fg, Color bg, in char[] text) {
21793 			int width, height;
21794 			auto data = font.renderString(text, size, width, height);
21795 			auto image = new TrueColorImage(width, height);
21796 			int pos = 0;
21797 			foreach(y; 0 .. height)
21798 			foreach(x; 0 .. width) {
21799 				fg.a = data[0];
21800 				bg.a = 255;
21801 				auto color = alphaBlend(fg, bg);
21802 				image.imageData.bytes[pos++] = color.r;
21803 				image.imageData.bytes[pos++] = color.g;
21804 				image.imageData.bytes[pos++] = color.b;
21805 				image.imageData.bytes[pos++] = data[0];
21806 				data = data[1 .. $];
21807 			}
21808 			assert(data.length == 0);
21809 
21810 			return Image.fromMemoryImage(image);
21811 		}
21812 
21813 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
21814 			Sprite sprite = (text in cache) ? *(text in cache) : null;
21815 
21816 			auto fg = painter.impl._outlineColor;
21817 			auto bg = painter.impl._fillColor;
21818 
21819 			if(sprite !is null) {
21820 				auto w = cast(SimpleWindow) painter.window;
21821 				assert(w !is null);
21822 
21823 				sprite.drawAt(painter, upperLeft);
21824 			} else {
21825 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
21826 			}
21827 		}
21828 	}
21829 
21830 	return new ArsdTtfFont(data, size);
21831 }
21832 
21833 class NotYetImplementedException : Exception {
21834 	this(string file = __FILE__, size_t line = __LINE__) {
21835 		super("Not yet implemented", file, line);
21836 	}
21837 }
21838 
21839 ///
21840 __gshared bool librariesSuccessfullyLoaded = true;
21841 ///
21842 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
21843 
21844 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
21845 	mixin(staticForeachReplacement!Iface);
21846 
21847 	void loadDynamicLibrary() @nogc {
21848 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
21849 	}
21850 
21851         void loadDynamicLibraryForReal() {
21852                 foreach(name; __traits(derivedMembers, Iface)) {
21853                         mixin("alias tmp = " ~ name ~ ";");
21854                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
21855                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
21856                 }
21857         }
21858 }
21859 
21860 private const(char)[] staticForeachReplacement(Iface)() pure {
21861 /*
21862 	// just this for gdc 9....
21863 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
21864 
21865         static foreach(name; __traits(derivedMembers, Iface))
21866                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
21867 */
21868 
21869 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
21870 	size_t pos;
21871 
21872 	void append(in char[] what) {
21873 		if(pos + what.length > code.length)
21874 			code.length = (code.length * 3) / 2;
21875 		code[pos .. pos + what.length] = what[];
21876 		pos += what.length;
21877 	}
21878 
21879         foreach(name; __traits(derivedMembers, Iface)) {
21880                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
21881 		append(name);
21882 		append(`")) `);
21883 		append(name);
21884 		append(";");
21885 	}
21886 
21887 	return code[0 .. pos];
21888 }
21889 
21890 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
21891 	mixin(staticForeachReplacement!Iface);
21892 
21893 	private __gshared void* libHandle;
21894 	private __gshared bool attempted;
21895 
21896         void loadDynamicLibrary() @nogc {
21897 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
21898 	}
21899 
21900 	bool loadAttempted() {
21901 		return attempted;
21902 	}
21903 	bool loadSuccessful() {
21904 		return libHandle !is null;
21905 	}
21906 
21907         void loadDynamicLibraryForReal() {
21908 		attempted = true;
21909                 version(Posix) {
21910                         import core.sys.posix.dlfcn;
21911 			version(OSX) {
21912 				version(X11)
21913                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
21914 				else
21915                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
21916 			} else {
21917                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
21918 				if(libHandle is null)
21919                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
21920 			}
21921 
21922 			static void* loadsym(void* l, const char* name) {
21923 				import core.stdc.stdlib;
21924 				if(l is null)
21925 					return &abort;
21926 				return dlsym(l, name);
21927 			}
21928                 } else version(Windows) {
21929                         import core.sys.windows.winbase;
21930                         libHandle = LoadLibrary(library ~ ".dll");
21931 			static void* loadsym(void* l, const char* name) {
21932 				import core.stdc.stdlib;
21933 				if(l is null)
21934 					return &abort;
21935 				return GetProcAddress(l, name);
21936 			}
21937                 }
21938                 if(libHandle is null) {
21939 			success = false;
21940                         //throw new Exception("load failure of library " ~ library);
21941 		}
21942                 foreach(name; __traits(derivedMembers, Iface)) {
21943                         mixin("alias tmp = " ~ name ~ ";");
21944                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
21945                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
21946                 }
21947         }
21948 
21949         void unloadDynamicLibrary() {
21950                 version(Posix) {
21951                         import core.sys.posix.dlfcn;
21952                         dlclose(libHandle);
21953                 } else version(Windows) {
21954                         import core.sys.windows.winbase;
21955                         FreeLibrary(libHandle);
21956                 }
21957                 foreach(name; __traits(derivedMembers, Iface))
21958                         mixin(name ~ " = null;");
21959         }
21960 }
21961 
21962 /+
21963 	The GC can be called from any thread, and a lot of cleanup must be done
21964 	on the gui thread. Since the GC can interrupt any locks - including being
21965 	triggered inside a critical section - it is vital to avoid deadlocks to get
21966 	these functions called from the right place.
21967 
21968 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
21969 	right now.
21970 
21971 	The cleanup function is run when the event loop gets around to it, which is just
21972 	whenever there's something there after it has been woken up for other work. It does
21973 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
21974 	(Well actually it might be ok but i don't wanna mess with it right now.)
21975 +/
21976 private struct CleanupQueue {
21977 	import core.stdc.stdlib;
21978 
21979 	void queue(alias func, T...)(T args) {
21980 		static struct Args {
21981 			T args;
21982 		}
21983 		static struct RealJob {
21984 			Job j;
21985 			Args a;
21986 		}
21987 		static void call(Job* data) {
21988 			auto rj = cast(RealJob*) data;
21989 			func(rj.a.args);
21990 		}
21991 
21992 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
21993 		thing.j.call = &call;
21994 		thing.a.args = args;
21995 
21996 		buffer[tail++] = cast(Job*) thing;
21997 
21998 		// FIXME: set overflowed
21999 	}
22000 
22001 	void process() {
22002 		const tail = this.tail;
22003 
22004 		while(tail != head) {
22005 			Job* job = cast(Job*) buffer[head++];
22006 			job.call(job);
22007 			free(job);
22008 		}
22009 
22010 		if(overflowed)
22011 			throw new Exception("cleanup overflowed");
22012 	}
22013 
22014 	private:
22015 
22016 	ubyte tail; // must ONLY be written by queue
22017 	ubyte head; // must ONLY be written by process
22018 	bool overflowed;
22019 
22020 	static struct Job {
22021 		void function(Job*) call;
22022 	}
22023 
22024 	void*[256] buffer;
22025 }
22026 private __gshared CleanupQueue cleanupQueue;
22027 
22028 version(X11)
22029 /++
22030 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22031 
22032 	$(WARNING
22033 		This function is exempted from stability guarantees.
22034 	)
22035 +/
22036 float customScalingFactorForMonitor(int monitorNumber) {
22037 	import core.stdc.stdlib;
22038 	auto val = getenv("ARSD_SCALING_FACTOR");
22039 
22040 	if(val is null)
22041 		return 1.0;
22042 
22043 	char[16] buffer = 0;
22044 	int pos;
22045 
22046 	const(char)* at = val;
22047 
22048 	foreach(item; 0 .. monitorNumber + 1) {
22049 		if(*at == 0)
22050 			break; // reuse the last number when we at the end of the string
22051 		pos = 0;
22052 		while(pos + 1 < buffer.length && *at && *at != ';') {
22053 			buffer[pos++] = *at;
22054 			at++;
22055 		}
22056 		if(*at)
22057 			at++; // skip the semicolon
22058 		buffer[pos] = 0;
22059 	}
22060 
22061 	//sdpyPrintDebugString(buffer[0 .. pos]);
22062 
22063 	import core.stdc.math;
22064 	auto f = atof(buffer.ptr);
22065 
22066 	if(f <= 0.0 || isnan(f) || isinf(f))
22067 		return 1.0;
22068 
22069 	return f;
22070 }
22071 
22072 void guiAbortProcess(string msg) {
22073 	import core.stdc.stdlib;
22074 	version(Windows) {
22075 		WCharzBuffer t = WCharzBuffer(msg);
22076 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22077 	} else {
22078 		import core.stdc.stdio;
22079 		fwrite(msg.ptr, 1, msg.length, stderr);
22080 		msg = "\n";
22081 		fwrite(msg.ptr, 1, msg.length, stderr);
22082 		fflush(stderr);
22083 	}
22084 
22085 	abort();
22086 }
22087 
22088 private int minInternal(int a, int b) {
22089 	return (a < b) ? a : b;
22090 }
22091 
22092 private alias scriptable = arsd_jsvar_compatible;